From fe6ab3825d21b02333d9bacad761b6c8133d561e Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Fri, 28 Aug 2026 19:16:15 +0800 Subject: [PATCH] fix(admin): family-aware right-tree badges; flatten union size-chart cells MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 右树徽标/定位/配置按钮改按族语义:族内任一链接已配置 → 全族显示绿色 已配置(本链接未单独建商品时标题注明并入该款),配置入口在全族未配置时 才出现;拖拽已配置族链接配置时拦截提示(公开契约一族一款,重复商品官网 不可见);分组头计数与仅未配置筛选同步族感知(AUTM003/DG120 场景) - 编辑弹窗族级尺码表把 measurements 数组摊平成列键(与成员级渲染一致), 修复列头在、格子全空的问题 --- apps/admin/src/views/goods/GoodsView.vue | 48 ++++++++++++++----- .../goods/components/GoodsEditDialog.vue | 11 ++++- docs/references/product-center.md | 14 +++--- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/apps/admin/src/views/goods/GoodsView.vue b/apps/admin/src/views/goods/GoodsView.vue index b38bcc8..4197c4c 100644 --- a/apps/admin/src/views/goods/GoodsView.vue +++ b/apps/admin/src/views/goods/GoodsView.vue @@ -286,26 +286,40 @@ function buildRightTree(tree: OriginGoodsTreeResponse) { } } const roots = tree.tree.map(mapCat) - // 已配置数 = 已配置为官网商品的链接(成族但未配置不算) - function fillConfiguredEff(nodes: any[]): number { + // 族级配置聚合:公开契约一族一款 —— 族内任一链接已配置,全族视为已配置 + const ogNodes: any[] = [] + ;(function collect(nodes: any[]) { + for (const n of nodes) { + if (n.isOG) ogNodes.push(n) + else collect(n.children ?? []) + } + })(roots) + const familyConfigured = new Map() + for (const og of ogNodes) { + if (og.familyId) { + familyConfigured.set(og.familyId, (familyConfigured.get(og.familyId) ?? 0) + og.configuredCount) + } + } + for (const og of ogNodes) { + og.familyConfiguredCount = og.familyId ? (familyConfigured.get(og.familyId) ?? 0) : 0 + og.configuredEff = og.configuredCount > 0 || og.familyConfiguredCount > 0 ? 1 : 0 + } + // 分组头部已配置数 = 有效已配置的链接数(含族配置带动) + ;(function fillCategory(nodes: any[]): number { let count = 0 for (const n of nodes) { - if (n.isOG) { - n.configuredEff = n.configuredCount > 0 ? 1 : 0 - } else { - n.configuredEff = fillConfiguredEff(n.children ?? []) - } + n.configuredEff = n.isOG ? n.configuredEff : fillCategory(n.children ?? []) count += n.configuredEff } return count - } - fillConfiguredEff(roots) + })(roots) rightTreeData.value = roots } function rightFilterNode(_value: string, data: any) { if (data.isOG) { - if (showUnconfiguredOnly.value && data.configuredCount > 0) return false + const configured = data.configuredCount > 0 || data.familyConfiguredCount > 0 + if (showUnconfiguredOnly.value && configured) return false if (searchKeyword.value && !data.label.includes(searchKeyword.value)) return false return true } @@ -398,6 +412,11 @@ 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) : [] @@ -1053,6 +1072,11 @@ onMounted(() => loadAll()) class="og-badge og-badge--ok" :title="`已配置 ${data.configuredCount} 国`" >已配置{{ data.configuredCount > 1 ? ' ' + data.configuredCount : '' }} + 已配置 loadAll()) @click.stop="handleSyncOriginDetail(data)" /> 配置 diff --git a/apps/admin/src/views/goods/components/GoodsEditDialog.vue b/apps/admin/src/views/goods/components/GoodsEditDialog.vue index 21e06ca..dcc01ba 100644 --- a/apps/admin/src/views/goods/components/GoodsEditDialog.vue +++ b/apps/admin/src/views/goods/components/GoodsEditDialog.vue @@ -157,7 +157,16 @@ const memberPriceSaving = ref('') const familyDetailRaw = ref(null) // ─── 族级并集视图:尺码表/包装规格(族物化并集)+ SKU(价格矩阵 = 各链接动态合并) ─── -const familySizeRows = computed(() => familyDetailRaw.value?.sizeChart?.rows ?? []) +// 尺码行把 measurements 数组摊平成 { 列key: cm }(与成员级渲染一致,否则格子全空) +const familySizeRows = computed(() => + ((familyDetailRaw.value?.sizeChart?.rows ?? []) as any[]).map((row) => ({ + ...row, + ...(row.measurements ?? []).reduce((out: Record, item: any) => { + out[item.key] = item.cm ?? '-' + return out + }, {}), + })), +) const familySizeColumns = computed(() => familyDetailRaw.value?.sizeChart?.columns ?? []) const familyPackageRows = computed(() => familyDetailRaw.value?.packageSpecs?.rows ?? []) const familySkuRows = computed(() => { diff --git a/docs/references/product-center.md b/docs/references/product-center.md index 7aa51e4..56d09cf 100644 --- a/docs/references/product-center.md +++ b/docs/references/product-center.md @@ -155,16 +155,18 @@ pnpm --filter @inkreach/api backfill:product-families - 编辑弹窗商品「名称」预填解析后的「品名 型号」,保存即采用解析名;右侧原产品库与配置弹窗 始终显示**原始链接名**; - 配置弹窗「合并同名」支持**搜索过滤**同分类链接(含已配置链接)。 -- 右树徽标语义:**已配置 N**(绿,已配置为官网商品)/ **成族**(蓝,已归族但尚未配置)/ - **未配置**(橙);「定位到官网商品」仅在已配置的链接上出现(按 主链接 → 同族 → 旧副源 - 顺序定位并高亮),「配置」在未配置的链接上出现; +- 右树徽标语义(**族感知**,公开契约一族一款):**已配置 N**(绿,本链接已配置为官网商品, + N=国数)/ **已配置**(绿,本链接未单独建商品但所属族已配置)/ **成族**(蓝,已归族且全族 + 未配置)/ **未配置**(橙);「定位到官网商品」在自身或同族已配置时出现(按 主链接 → 同族 → + 旧副源顺序定位并高亮),「配置」仅在全族未配置时出现;把已配置族的链接拖拽/点击配置会被 + 拦截提示(避免产出官网不可见的重复商品); +- 族分组头部计数与「仅未配置」筛选同样按族语义(族已配置 → 计入已配置)。 - 人工改价/自动成族等族管理 API(`/product-families/*`)保留,供脚本或后续界面使用; - 页面组件化:`GoodsView.vue` 拆出 `components/` 下的 GoodsEditDialog(编辑弹窗)、 GoodsConfigDialog(配置弹窗)、CustomGoodDialog(自定义商品)、TagFilterPopover(标签筛选弹层)。 -> 注意:同一族的多条链接(如 单面印花/双面印花)如果各自都点了一次「配置」,会产生多个 -> 解析后同名的官网商品(名称相同但关联链接不同)——族合并的预期用法是每族配置一个商品, -> 其余链接保持为族成员即可;重复的可手动删除。 +> 注意:历史上每族多点「配置」会产生多个同名的官网商品(重复项可手动删除)。现在族已配置后 +> 「配置」入口会被拦截;存量重复商品(如早期 DG120/AUTM003 测试期间产生的)仍建议清理。 **链接级标签(自动派生 + 人工修正,2026-08 规则改版)**: