fix(admin,api): always show family composition badge N条链接·M个配置
按用户反馈(PLTM006 明明能查到配置数却不显示):左树徽标此前只在 同分类内 ≥2 个配置时才出现,单配置族一行上什么都不显示,而右树同族 链接都挂着绿色已配置徽标,两边数字对不上,反复造成「配置数没显示」 的误解。 - goods API:GOOD_INCLUDE 的 family 带 _count.originGoods, GoodDto.originGood.family 增加 memberCount(族成员链接数) - admin:distinctByFamily 记录 linkCount;一族一行的徽标常显 「N 条链接 · M 个配置」(链接数=族成员数,配置数=该族官网商品数), 悬浮仍列出全部配置可打开 - 文档同步徽标语义 效果:PLTM006 行显示「4 条链接 · 1 个配置」(真值:4 条链接仅配了 1 个官网商品);PLTF026 的「4 条链接 · 4 个配置」同屏可对比,其中 4 个配置为历史同国家重复配置,建议另行清理。 验证:admin vue-tsc 干净 + vitest 22/22;api goods spec 18/18; Prisma _count 查询实测返回 originGoods=4
This commit is contained in:
@@ -360,7 +360,7 @@ export interface OriginGood {
|
|||||||
sizeRowCount?: number
|
sizeRowCount?: number
|
||||||
packageRowCount?: number
|
packageRowCount?: number
|
||||||
productCode?: string | null
|
productCode?: string | null
|
||||||
family?: { familyId: string; familyCode: string | null; familyName: string } | null
|
family?: { familyId: string; familyCode: string | null; familyName: string; memberCount?: number } | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Origin Goods Tree types
|
// Origin Goods Tree types
|
||||||
|
|||||||
@@ -216,7 +216,8 @@ const filteredGoods = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
/** 左树一族只显示一行:同款(同 familyId)多条配置保留代表行(排序最前),
|
/** 左树一族只显示一行:同款(同 familyId)多条配置保留代表行(排序最前),
|
||||||
* 其余配置计数挂在 configCount,悬浮徽标可查看并打开 */
|
* 构成常显在徽标上:「N 条链接 · M 个配置」(链接数=族成员数,配置数=该族官网商品数),
|
||||||
|
* 悬浮徽标可查看全部配置并打开 */
|
||||||
function distinctByFamily(nodes: any[]): any[] {
|
function distinctByFamily(nodes: any[]): any[] {
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
return nodes.map(n => {
|
return nodes.map(n => {
|
||||||
@@ -230,6 +231,8 @@ function distinctByFamily(nodes: any[]): any[] {
|
|||||||
String(m.raw?.originGood?.family?.familyId ?? '') === fid)
|
String(m.raw?.originGood?.family?.familyId ?? '') === fid)
|
||||||
n.configCount = siblings.length
|
n.configCount = siblings.length
|
||||||
n.familyConfigs = siblings
|
n.familyConfigs = siblings
|
||||||
|
n.linkCount = siblings.find(m => m.raw?.originGood?.family?.memberCount != null)
|
||||||
|
?.raw.originGood.family.memberCount ?? null
|
||||||
return n
|
return n
|
||||||
}).filter(Boolean)
|
}).filter(Boolean)
|
||||||
}
|
}
|
||||||
@@ -1170,14 +1173,14 @@ onMounted(() => loadAll())
|
|||||||
<span v-if="data.mergedCount > 1" class="gv-merged-badge">×{{ data.mergedCount }}</span>
|
<span v-if="data.mergedCount > 1" class="gv-merged-badge">×{{ data.mergedCount }}</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-popover
|
<el-popover
|
||||||
v-if="data.configCount > 1"
|
v-if="data.linkCount || data.configCount > 1"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
:width="380"
|
:width="380"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
popper-class="cfg-pop-popper"
|
popper-class="cfg-pop-popper"
|
||||||
>
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<span class="cfg-badge" :title="`同款共 ${data.configCount} 个配置`" @click.stop>{{ data.configCount }} 个配置</span>
|
<span class="cfg-badge" :title="`同款共 ${data.linkCount ?? data.configCount} 条链接 · ${data.configCount} 个官网配置`" @click.stop>{{ data.linkCount ? `${data.linkCount} 条链接 · ` : '' }}{{ data.configCount }} 个配置</span>
|
||||||
</template>
|
</template>
|
||||||
<div class="cfg-pop-list">
|
<div class="cfg-pop-list">
|
||||||
<div v-for="c in (data.familyConfigs || [])" :key="c.goodId" class="cfg-pop-row">
|
<div v-for="c in (data.familyConfigs || [])" :key="c.goodId" class="cfg-pop-row">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export interface GoodRelations {
|
|||||||
goodName: string | null;
|
goodName: string | null;
|
||||||
goodImage: string | null;
|
goodImage: string | null;
|
||||||
goodPrice: unknown;
|
goodPrice: unknown;
|
||||||
family?: { id: bigint; familyCode: string | null; familyName: string } | null;
|
family?: { id: bigint; familyCode: string | null; familyName: string; _count?: { originGoods: number } } | null;
|
||||||
detail?: {
|
detail?: {
|
||||||
productCode: string | null;
|
productCode: string | null;
|
||||||
syncedAt: Date;
|
syncedAt: Date;
|
||||||
@@ -128,7 +128,7 @@ export class GoodDto {
|
|||||||
sizeRowCount: number;
|
sizeRowCount: number;
|
||||||
packageRowCount: number;
|
packageRowCount: number;
|
||||||
productCode: string | null;
|
productCode: string | null;
|
||||||
family?: { familyId: string; familyCode: string | null; familyName: string } | null;
|
family?: { familyId: string; familyCode: string | null; familyName: string; memberCount?: number } | null;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
static from(
|
static from(
|
||||||
@@ -223,6 +223,7 @@ export class GoodDto {
|
|||||||
familyId: rel.originGood.family.id.toString(),
|
familyId: rel.originGood.family.id.toString(),
|
||||||
familyCode: rel.originGood.family.familyCode,
|
familyCode: rel.originGood.family.familyCode,
|
||||||
familyName: rel.originGood.family.familyName,
|
familyName: rel.originGood.family.familyName,
|
||||||
|
memberCount: rel.originGood.family._count?.originGoods,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,15 @@ const GOOD_INCLUDE = {
|
|||||||
detail: true,
|
detail: true,
|
||||||
variants: { orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] },
|
variants: { orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] },
|
||||||
_count: { select: { variants: true } },
|
_count: { select: { variants: true } },
|
||||||
family: { select: { id: true, familyCode: true, familyName: true, stale: true } },
|
family: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
familyCode: true,
|
||||||
|
familyName: true,
|
||||||
|
stale: true,
|
||||||
|
_count: { select: { originGoods: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
goodTags: { include: { tag: true } },
|
goodTags: { include: { tag: true } },
|
||||||
|
|||||||
@@ -173,8 +173,9 @@ curl -X POST /product-families/organize -H "$AUTH" # 后台「整理
|
|||||||
主链接 → 同族 → 旧副源顺序定位并高亮);「配置」按钮仅在本链接及其同款族均未配置时出现,
|
主链接 → 同族 → 旧副源顺序定位并高亮);「配置」按钮仅在本链接及其同款族均未配置时出现,
|
||||||
拖拽到左侧分类/国家的路径同样被拦截(提示同款已配置、不允许重复配置);
|
拖拽到左侧分类/国家的路径同样被拦截(提示同款已配置、不允许重复配置);
|
||||||
- 族分组头部计数与「仅未配置」筛选同样按族语义(族已配置 → 计入已配置)。
|
- 族分组头部计数与「仅未配置」筛选同样按族语义(族已配置 → 计入已配置)。
|
||||||
- **左树一族一行**(国家/品类模式):同款多条配置只显示排序最前的一条,名称旁
|
- **左树一族一行**(国家/品类模式):同款多条配置只显示排序最前的一条,名称旁常显
|
||||||
「N 个配置」徽标悬浮列出其余配置(可打开编辑),分组头部计数仍按全部商品统计。
|
「N 条链接 · M 个配置」徽标(链接数=族成员数,配置数=该族官网商品数;单链接单配置也显示,
|
||||||
|
避免漏看),悬浮列出全部配置(名称/国家·分类/打开),分组头部计数仍按全部商品统计。
|
||||||
- 人工改价/自动成族等族管理 API(`/product-families/*`)保留,供脚本或后续界面使用;
|
- 人工改价/自动成族等族管理 API(`/product-families/*`)保留,供脚本或后续界面使用;
|
||||||
- 页面组件化:`GoodsView.vue` 拆出 `components/` 下的 GoodsEditDialog(编辑弹窗)、
|
- 页面组件化:`GoodsView.vue` 拆出 `components/` 下的 GoodsEditDialog(编辑弹窗)、
|
||||||
GoodsConfigDialog(配置弹窗)、CustomGoodDialog(自定义商品)、TagFilterPopover(标签筛选弹层)。
|
GoodsConfigDialog(配置弹窗)、CustomGoodDialog(自定义商品)、TagFilterPopover(标签筛选弹层)。
|
||||||
|
|||||||
Reference in New Issue
Block a user