diff --git a/apps/admin/src/views/goods/GoodsView.vue b/apps/admin/src/views/goods/GoodsView.vue index de1f200..898a356 100644 --- a/apps/admin/src/views/goods/GoodsView.vue +++ b/apps/admin/src/views/goods/GoodsView.vue @@ -214,12 +214,32 @@ const filteredGoods = computed(() => { return sorted }) +/** 左树一族只显示一行:同款(同 familyId)多条配置保留代表行(排序最前), + * 其余配置计数挂在 configCount,悬浮徽标可查看并打开 */ +function distinctByFamily(nodes: any[]): any[] { + const seen = new Set() + return nodes.map(n => { + const fid = n.raw?.originGood?.family?.familyId + ? String(n.raw.originGood.family.familyId) + : null + if (!fid) return n + if (seen.has(fid)) return null + seen.add(fid) + const siblings = nodes.filter(m => + String(m.raw?.originGood?.family?.familyId ?? '') === fid) + n.configCount = siblings.length + n.familyConfigs = siblings + return n + }).filter(Boolean) +} + function mapCatNodes(nodes: CategoryTree[], goods: Good[]): any[] { return nodes.map(n => { const childNodes = n.children?.length ? mapCatNodes(n.children, goods) : [] const myGoods = goods.filter(g => g.categoryId === n.id).map(goodToNode) - const children = [...childNodes, ...myGoods] - const goodCount = children.reduce((sum: number, c: any) => sum + (c.goodCount ?? (c.isGood ? 1 : 0)), 0) + const children = [...childNodes, ...distinctByFamily(myGoods)] + const goodCount = children.reduce((sum: number, c: any) => + sum + (c.isGood ? 1 + (c.configCount ? c.configCount - 1 : 0) : (c.goodCount ?? 0)), 0) return { id: n.id, label: n.categoryName, @@ -247,7 +267,7 @@ const leftTreeData = computed(() => { isCat: true, raw: c, goodCount: cGoods.length, - children: cGoods.map(goodToNode), + children: distinctByFamily(cGoods.map(goodToNode)), } }) } @@ -398,18 +418,26 @@ function findOgNodeById(rawId: string | number): any { return found } +/** 链接名的款号段(第 2 段,如 BRTF004);同款号 = 同款 */ +function skuCodeOf(name: string | null | undefined): string { + return (name ?? '').split('-').map(s => s.trim())[1] ?? '' +} + function collectSiblings(ogNode: any): any[] { const result: any[] = [] + const myFamily = ogNode.familyId ? String(ogNode.familyId) : null + const mySku = skuCodeOf(ogNode.goodName ?? ogNode.label) function traverse(nodes: any[]) { for (const n of nodes) { - // 全局同款链接(跨分类):合并同名按名称组键匹配, - // 不局限于当前分类;含已配置的,勾选归族操作幂等 - if ( - n.isOG && - String(n.rawId) !== String(ogNode.rawId) && - sameOriginGroup(ogNode.goodName ?? ogNode.label, n.goodName ?? n.label) - ) { - result.push(n) + // 全局同款链接(跨分类/跨工艺/跨物流):族归属精确匹配,或款号一致; + // 含已配置的,勾选归族操作幂等 + if (n.isOG && String(n.rawId) !== String(ogNode.rawId)) { + const nFamily = n.familyId ? String(n.familyId) : null + const nSku = skuCodeOf(n.goodName ?? n.label) + const same = (myFamily && nFamily && myFamily === nFamily) + || (Boolean(mySku) && Boolean(nSku) && mySku === nSku) + || (!mySku && sameOriginGroup(ogNode.goodName ?? ogNode.label, n.goodName ?? n.label)) + if (same) result.push(n) } if (n.children?.length) traverse(n.children) } @@ -420,11 +448,6 @@ function collectSiblings(ogNode: any): any[] { function openConfigModal(og: any, dropTarget: any) { const ogNode = findOgNodeById(og.rawId) - // 公开契约一族一款:族已配置后再配置成员只会产出官网不可见的重复商品 - if ((ogNode?.familyConfiguredCount ?? 0) > 0 || (ogNode?.configuredCount ?? 0) > 0) { - ElMessage.info('该链接所属族已配置为官网商品,无需重复配置') - return - } configOG.value = { ...og, familyId: og.familyId ?? ogNode?.familyId ?? null } configDropTarget.value = dropTarget configSiblings.value = ogNode ? collectSiblings(ogNode) : [] @@ -575,6 +598,12 @@ function locateInLeftTree(originGoodId: string) { const node = tree.getNode(keyId) if (node) node.expanded = true } + // 族折叠后商品嵌套在族组节点下:沿父链全部展开(两种模式通用) + let anc = tree.getNode(targetKey)?.parent as any + while (anc?.data) { + anc.expanded = true + anc = anc.parent + } setTimeout(() => { tree.setCurrentKey(targetKey) // 闪烁高亮定位行(2.4s 后自动消失) @@ -703,6 +732,13 @@ function onTreeGoodDragLeave(_event: DragEvent, data: any) { if (treeDragOver.value?.id === data.goodId) treeDragOver.value = null } +/** 国家分组内:找商品所在容器数组(即国家根 children) */ +function findContainingArray(group: any, goodId: string): any[] | null { + return (group.children ?? []).some((c: any) => c.isGood && c.goodId === goodId) + ? group.children + : null +} + async function onTreeGoodDrop(event: DragEvent, data: any) { event.preventDefault() event.stopPropagation() @@ -713,14 +749,12 @@ async function onTreeGoodDrop(event: DragEvent, data: any) { if (mode.value !== 'country' || !draggedId || !target) return if (target.id !== data.goodId || draggedId === data.goodId) return - // 目标所在国家分组的商品(新顺序) - const group = leftTreeData.value.find((c: any) => - (c.children ?? []).some((g: any) => g.goodId === target.id), - ) + // 目标所在国家分组(跨国拖动不支持:商品归属固定国家) + const group = leftTreeData.value.find((c: any) => findContainingArray(c, target.id)) if (!group) return const nodes = [...group.children] const fromIdx = nodes.findIndex(n => n.goodId === draggedId) - if (fromIdx === -1) return // 跨国拖动不支持(商品归属固定国家) + if (fromIdx === -1) return const [moved] = nodes.splice(fromIdx, 1) let insertIdx = nodes.findIndex(n => n.goodId === target.id) if (target.pos === 'after') insertIdx++ @@ -972,6 +1006,11 @@ onMounted(() => loadAll())
+ + 品类 + 国家 + 全局 + @@ -996,14 +1035,8 @@ onMounted(() => loadAll()) 搜索 - 新增自定义商品 -
- - 品类 - 国家 - 全局 - + 新增自定义商品
@@ -1103,6 +1136,24 @@ onMounted(() => loadAll()) {{ cleanLinkName(data.goodName) }} ×{{ data.mergedCount }} + + +
+
+ {{ cleanLinkName(c.goodName) }} + {{ c.country }}{{ c.raw?.category?.categoryName ? ' · ' + c.raw.category.categoryName : '' }} + 打开 +
+
+
@@ -1155,7 +1206,7 @@ onMounted(() => loadAll()) :expand-on-click-node="true" >