fix(admin): family-aware right-tree badges; flatten union size-chart cells
- 右树徽标/定位/配置按钮改按族语义:族内任一链接已配置 → 全族显示绿色 已配置(本链接未单独建商品时标题注明并入该款),配置入口在全族未配置时 才出现;拖拽已配置族链接配置时拦截提示(公开契约一族一款,重复商品官网 不可见);分组头计数与仅未配置筛选同步族感知(AUTM003/DG120 场景) - 编辑弹窗族级尺码表把 measurements 数组摊平成列键(与成员级渲染一致), 修复列头在、格子全空的问题
This commit is contained in:
@@ -286,26 +286,40 @@ function buildRightTree(tree: OriginGoodsTreeResponse) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const roots = tree.tree.map(mapCat)
|
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<string, number>()
|
||||||
|
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
|
let count = 0
|
||||||
for (const n of nodes) {
|
for (const n of nodes) {
|
||||||
if (n.isOG) {
|
n.configuredEff = n.isOG ? n.configuredEff : fillCategory(n.children ?? [])
|
||||||
n.configuredEff = n.configuredCount > 0 ? 1 : 0
|
|
||||||
} else {
|
|
||||||
n.configuredEff = fillConfiguredEff(n.children ?? [])
|
|
||||||
}
|
|
||||||
count += n.configuredEff
|
count += n.configuredEff
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
})(roots)
|
||||||
fillConfiguredEff(roots)
|
|
||||||
rightTreeData.value = roots
|
rightTreeData.value = roots
|
||||||
}
|
}
|
||||||
|
|
||||||
function rightFilterNode(_value: string, data: any) {
|
function rightFilterNode(_value: string, data: any) {
|
||||||
if (data.isOG) {
|
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
|
if (searchKeyword.value && !data.label.includes(searchKeyword.value)) return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -398,6 +412,11 @@ function collectSiblings(ogNode: any): any[] {
|
|||||||
|
|
||||||
function openConfigModal(og: any, dropTarget: any) {
|
function openConfigModal(og: any, dropTarget: any) {
|
||||||
const ogNode = findOgNodeById(og.rawId)
|
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 }
|
configOG.value = { ...og, familyId: og.familyId ?? ogNode?.familyId ?? null }
|
||||||
configDropTarget.value = dropTarget
|
configDropTarget.value = dropTarget
|
||||||
configSiblings.value = ogNode ? collectSiblings(ogNode) : []
|
configSiblings.value = ogNode ? collectSiblings(ogNode) : []
|
||||||
@@ -1053,6 +1072,11 @@ onMounted(() => loadAll())
|
|||||||
class="og-badge og-badge--ok"
|
class="og-badge og-badge--ok"
|
||||||
:title="`已配置 ${data.configuredCount} 国`"
|
:title="`已配置 ${data.configuredCount} 国`"
|
||||||
>已配置{{ data.configuredCount > 1 ? ' ' + data.configuredCount : '' }}</span>
|
>已配置{{ data.configuredCount > 1 ? ' ' + data.configuredCount : '' }}</span>
|
||||||
|
<span
|
||||||
|
v-else-if="data.familyConfiguredCount > 0"
|
||||||
|
class="og-badge og-badge--ok"
|
||||||
|
:title="`族 ${data.familyCode || ''} 已配置为官网商品,本链接并入该款`"
|
||||||
|
>已配置</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="data.familyId"
|
v-else-if="data.familyId"
|
||||||
class="og-badge og-badge--family"
|
class="og-badge og-badge--family"
|
||||||
@@ -1079,12 +1103,12 @@ onMounted(() => loadAll())
|
|||||||
@click.stop="handleSyncOriginDetail(data)"
|
@click.stop="handleSyncOriginDetail(data)"
|
||||||
/>
|
/>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="data.configuredCount > 0"
|
v-if="data.configuredCount > 0 || data.familyConfiguredCount > 0"
|
||||||
size="small" link :icon="Aim" title="定位到官网商品"
|
size="small" link :icon="Aim" title="定位到官网商品"
|
||||||
@click.stop="locateInLeftTree(data.rawId)"
|
@click.stop="locateInLeftTree(data.rawId)"
|
||||||
/>
|
/>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="!data.configuredCount"
|
v-if="!data.configuredCount && !data.familyConfiguredCount"
|
||||||
size="small" type="primary" link class="og-config-btn"
|
size="small" type="primary" link class="og-config-btn"
|
||||||
@click.stop="openConfigFromRightTree(data)"
|
@click.stop="openConfigFromRightTree(data)"
|
||||||
>配置</el-button>
|
>配置</el-button>
|
||||||
|
|||||||
@@ -157,7 +157,16 @@ const memberPriceSaving = ref<string>('')
|
|||||||
const familyDetailRaw = ref<any>(null)
|
const familyDetailRaw = ref<any>(null)
|
||||||
|
|
||||||
// ─── 族级并集视图:尺码表/包装规格(族物化并集)+ SKU(价格矩阵 = 各链接动态合并) ───
|
// ─── 族级并集视图:尺码表/包装规格(族物化并集)+ 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<string, string>, item: any) => {
|
||||||
|
out[item.key] = item.cm ?? '-'
|
||||||
|
return out
|
||||||
|
}, {}),
|
||||||
|
})),
|
||||||
|
)
|
||||||
const familySizeColumns = computed(() => familyDetailRaw.value?.sizeChart?.columns ?? [])
|
const familySizeColumns = computed(() => familyDetailRaw.value?.sizeChart?.columns ?? [])
|
||||||
const familyPackageRows = computed(() => familyDetailRaw.value?.packageSpecs?.rows ?? [])
|
const familyPackageRows = computed(() => familyDetailRaw.value?.packageSpecs?.rows ?? [])
|
||||||
const familySkuRows = computed(() => {
|
const familySkuRows = computed(() => {
|
||||||
|
|||||||
@@ -155,16 +155,18 @@ pnpm --filter @inkreach/api backfill:product-families
|
|||||||
- 编辑弹窗商品「名称」预填解析后的「品名 型号」,保存即采用解析名;右侧原产品库与配置弹窗
|
- 编辑弹窗商品「名称」预填解析后的「品名 型号」,保存即采用解析名;右侧原产品库与配置弹窗
|
||||||
始终显示**原始链接名**;
|
始终显示**原始链接名**;
|
||||||
- 配置弹窗「合并同名」支持**搜索过滤**同分类链接(含已配置链接)。
|
- 配置弹窗「合并同名」支持**搜索过滤**同分类链接(含已配置链接)。
|
||||||
- 右树徽标语义:**已配置 N**(绿,已配置为官网商品)/ **成族**(蓝,已归族但尚未配置)/
|
- 右树徽标语义(**族感知**,公开契约一族一款):**已配置 N**(绿,本链接已配置为官网商品,
|
||||||
**未配置**(橙);「定位到官网商品」仅在已配置的链接上出现(按 主链接 → 同族 → 旧副源
|
N=国数)/ **已配置**(绿,本链接未单独建商品但所属族已配置)/ **成族**(蓝,已归族且全族
|
||||||
顺序定位并高亮),「配置」在未配置的链接上出现;
|
未配置)/ **未配置**(橙);「定位到官网商品」在自身或同族已配置时出现(按 主链接 → 同族 →
|
||||||
|
旧副源顺序定位并高亮),「配置」仅在全族未配置时出现;把已配置族的链接拖拽/点击配置会被
|
||||||
|
拦截提示(避免产出官网不可见的重复商品);
|
||||||
|
- 族分组头部计数与「仅未配置」筛选同样按族语义(族已配置 → 计入已配置)。
|
||||||
- 人工改价/自动成族等族管理 API(`/product-families/*`)保留,供脚本或后续界面使用;
|
- 人工改价/自动成族等族管理 API(`/product-families/*`)保留,供脚本或后续界面使用;
|
||||||
- 页面组件化:`GoodsView.vue` 拆出 `components/` 下的 GoodsEditDialog(编辑弹窗)、
|
- 页面组件化:`GoodsView.vue` 拆出 `components/` 下的 GoodsEditDialog(编辑弹窗)、
|
||||||
GoodsConfigDialog(配置弹窗)、CustomGoodDialog(自定义商品)、TagFilterPopover(标签筛选弹层)。
|
GoodsConfigDialog(配置弹窗)、CustomGoodDialog(自定义商品)、TagFilterPopover(标签筛选弹层)。
|
||||||
|
|
||||||
> 注意:同一族的多条链接(如 单面印花/双面印花)如果各自都点了一次「配置」,会产生多个
|
> 注意:历史上每族多点「配置」会产生多个同名的官网商品(重复项可手动删除)。现在族已配置后
|
||||||
> 解析后同名的官网商品(名称相同但关联链接不同)——族合并的预期用法是每族配置一个商品,
|
> 「配置」入口会被拦截;存量重复商品(如早期 DG120/AUTM003 测试期间产生的)仍建议清理。
|
||||||
> 其余链接保持为族成员即可;重复的可手动删除。
|
|
||||||
|
|
||||||
**链接级标签(自动派生 + 人工修正,2026-08 规则改版)**:
|
**链接级标签(自动派生 + 人工修正,2026-08 规则改版)**:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user