Files
inkreach-official-website/docs/references/product-center.md
T
yeuimu 674d38a9a7 fix(api,admin): stop test fixture junk leaking to shared dev DB; badge text x个配置
清理标签筛选里的「Upd {ts}-v2」空分组与原产品库树里的
「Tree Cat {ts}」垃圾分类——两者均为集成测试夹具残留:
- tag-groups spec「改名」用例把分组改成 -v2 后缀,afterAll 按原名
  删除永远漏掉它(累积 31 个空分组);修复:改名前把 v2 名一并登记
- origin-goods spec getTree 用例的 finally 先删 good,被
  goodOriginGood 外键限制挡住且错误被 .catch 吞掉,分类/商品/国家/
  链接全部残留;修复:先删引用行再删 good
- 已清库:31 个 Upd 空分组、Tree Cat 分类 + Tree Good 商品 +
  Tree Country 国家 + 2 条 tree-a/b 链接;复跑两 spec 复核零残留

左树一族一行徽标文案按用户反馈去掉「N 条链接」,仅显示「M 个配置」
(memberCount 字段保留在 DTO,悬浮弹层行为不变)

验证:tag-groups + origin-goods spec 13/13 绿且零残留;admin
vue-tsc 干净 + vitest 22/22
2026-08-30 03:15:18 +08:00

16 KiB
Raw Blame History

产品中心

官网产品中心位于 apps/website/app/pages/product-center.vue,访问路径为 /product-center

默认展示

页面首次加载时展示全部商品和全部国家,不默认选择分类、物流或工艺标签。每页默认请求 20 条商品,用户操作筛选项后再按真实业务 ID 查询。

筛选与分页

  • 左侧分类树:选择父分类或子分类。
  • 国家:点击国家胶囊筛选,点击“全部”取消国家筛选。
  • 物流与工艺:按标签组展示下拉选项,每个分组内暂为单选,不同分组可各选择一项。
  • 筛选菜单在选择选项或点击组件外部后自动关闭;已选 Tag 使用独立标签行。仅选择工艺时标签从最左侧开始;物流与工艺同时选择时按物流、工艺顺序与上方筛选项对应排列。
  • 搜索:输入关键词后按回车或点击“搜索”。
  • 商品卡:整卡链接到 https://inkpod.vip/portal/detail/{商品ID},在新窗口打开。
  • 公共商品接口的 id 来自 originGood.sdsGoodId,即 InkPOD 商品 IDgoods.good_id 仅作为本站数据库内部主键使用。
  • URL 国家筛选:支持 /product-center?countryId={后台国家ID},仅接受后台已配置的国家 ID。
  • 无结果:展示设计稿空状态,可通过“查看全部商品”或“清空筛选”恢复完整列表。
  • 加载中:展示两行共八个商品骨架卡,筛选栏保持可操作。
  • 分页:支持页码、前后翻页、每页数量和页码跳转。

所有查询通过 useProductCenter.ts 调用 /api/backend/* 代理路由,不在页面中硬编码业务 ID。

后台配置联动

  • 品类树、国家、Tag 与 Tag Group 均来自 NestJS /public/* 接口。
  • 国家旗帜读取 countryIcon,品类图标读取 categoryIcon;前端不提供外部旗帜 CDN 或固定名称兜底。
  • /uploads/*/assets/* 等后台相对资源地址由 useProductCenter.ts 根据 runtimeConfig.public.backendUrl 转为完整 URL。
  • 物流和工艺筛选分别由后台名称包含“物流”和“工艺”的 Tag Group 生成;“印刷位置”等其他分组仍可用于商品卡片标签,但不会自动成为顶部筛选项。
  • Figma 基准图标位于 apps/api/public/product-center/,通过后端 /assets/product-center/* 提供。
  • 更新现有数据库图标配置:pnpm --filter @inkreach/api configure:product-center-icons。脚本按中文名称幂等更新,不创建缺失的国家或品类。

响应式

桌面采用 240px 分类栏,页面左右保留 2456px 自适应留白,分类栏可滚动但隐藏可见 scrollbar。商品网格以 240px 为最小卡片宽度自动增加或减少列数:1440px 基准宽度保持四列,1920px 可显示六列。国家筛选区按内容自然增高,不保留固定空白。

左侧品类栏不显示最左侧边线,一级品类之间保留 10px 间距;国家筛选区及物流、工艺、搜索工具区的底部分隔线从内容区向右延伸至页面边缘。

宽度低于 1000px 后分类栏变为抽屉;移动端商品网格降为两列或单列,国家筛选仅在自身区域横向滚动,不会撑宽页面。

多源合并商品(已废弃,由产品族替代)

2026-08-28 起,该机制已被「产品族」完全替代:多链接合并由族承载(尺码/包装并集 + 五维价格矩阵),配置/编辑商品不再写入 mergedOriginGoodIdsgood_origin_goods 表 只读保留(历史数据仍可命中详情),观察期后删除。以下为历史行为记录:

  • 数据层:主源存 goods.origin_good_id,副源存中间表 good_origin_goods
  • 详情可达性:官网上通过任一关联原产品的 sdsGoodId 都能访问到该商品详情(族机制下 同样成立:族内任何成员链接的 sdsGoodId 均命中同一商品)。

该 Good 级合并能力保留可用;新一级的「产品族(SPU)合并」见下节,公开读路径接入族数据属于三期范围。

产品族(SPU 层,一期后端已上线)

设计文档:docs/superpowers/specs/2026-08-28-product-family-merge-design.md

模型SDS 叶子分类即产品模型(如分类 DG001 180G纯棉T恤(JSA002 下 16 条链接), 族 = 同分类链接的合并体;链接名解析列(国家(物流)品名-SKU-工艺[-仓库])提供 物流/工艺归因。例:DG001 族 = 16 链接 × 3 工艺 × 3 物流 × 9 尺码(S~XXXXXL 并集)× 20 颜色 ≈ 470 格价格矩阵,起价取光板(不打印)最低价。

价格价格 = f(尺码, 颜色, 物流, 工艺[含印花数量]),全部从成员链接变体推导透传 (同格子多仓库取最低价,来源全保留);family_price_overrides 表支持按格人工改价, 删覆盖即恢复推导价。

关键端点用法示例JWT):

# 1. 自动成族:先预览
curl -X POST /product-families/auto-group -H "Authorization: Bearer $T" \
  -d '{"apply": false}'
# → { applied: 0, groups: [{ groupKey: "cat:8240", familyName: "DG001 180G纯棉T恤(JSA002", memberCount: 16, ... }] }

# 2. 应用(幂等,可重复执行)
curl -X POST /product-families/auto-group -H "Authorization: Bearer $T" -d '{"apply": true}'

# 3. 族详情:成员 + 并集尺码表/包装 + 五维价格矩阵
curl /product-families/12 -H "Authorization: Bearer $T"

# 4. 人工改价(维度必须存在于族矩阵选项,否则 400 并列出非法键)
curl -X PUT /product-families/12/price-overrides -H "Authorization: Bearer $T" \
  -d '{"items": [{"sizeId":"size_S","colorId":"color_blk","craft":"单面印花","logistics":"包邮","price": 23, "note": "促销"}]}'

# 5. 删覆盖恢复推导价
curl -X DELETE /product-families/12/price-overrides -H "Authorization: Bearer $T" \
  -d '{"cells": [{"sizeId":"size_S","colorId":"color_blk","craft":"单面印花","logistics":"包邮"}]}'

# 6. 族内创建自定义商品(人工补链接,物流/工艺归因必填)
curl -X POST /product-families/12/members/custom -H "Authorization: Bearer $T" \
  -d '{"goodName":"美国(海运)180g纯棉T恤-DG001-烫画","logisticsLabel":"海运","craftLabel":"烫画",
       "variants":[{"sku":"C-M","sizeId":"size_M","sizeName":"M","colorId":"color_red","colorName":"红色","price": 33}]}'

三层架构(解析去运行时化)

  1. 同步 = 纯镜像:SDS 给什么存什么(名称原文/图/价/分类 ID),不解析、不归族、不写解析列;
  2. 整理 = 显式人工动作(所有解析/派生集中于此,可审查可重跑):回填解析列 → 派生链接标签 (未人工接管的 SDS 链接按名称刷新;人工接管永不覆盖)→ 自动建族 → 全量重算。入口二选一:
pnpm --filter @inkreach/api organize                          # CLI
curl -X POST /product-families/organize -H "$AUTH"            # 后台「整理」按钮(同一逻辑)
  1. 重算 = 自动的结构化聚合:归族/成员变化/详情同步/标签调整自动触发;输入只有 成员变体 + 链接标签 + 覆盖价,不解析名称——上游改名永远不会倒灌已派生的标签与矩阵; 未整理(无标签)的 SDS 成员不进矩阵,整理后齐全。

派生默认(脚本层假设,显式可审查):工艺无关键字 → 烫画;印花数量无单/双面且工艺非不打印 → 单面印花;不打印/光板 无印花面不补(矩阵中印花数量以单面占位,纯结构化规则)。 「恢复自动」(链接标签重置)本身是显式人工动作,同样走整理服务的单链接派生。

公开读路径(四期族化契约,默认开启)PUBLIC_DETAIL_FROM_FAMILY=false 可应急回退旧行为。 公开端点以款(族)为一等公民

  • GET /public/goods一族一条(族内多条 Good 去重,代表行=排序第一条), goodId = 族 IDtotal = 族数,分页作用于分组后;price = 族矩阵最低价(起价), 价格排序按族起价重排;无族商品(自定义)完全不返回(列表/首页/分类树/标签统计同理)。

  • GET /public/goods/:goodIdgoodId族 ID(唯一公开寻址键;旧 SDS 链接 ID 与 自定义商品 sdsGoodId 均 404)。响应为款级聚合:

    • 公共信息取代表 Good(名称/主图/国家/分类/标签)+ 主链接(详情文案);
    • variants = 全体族成员 ∪ 旧副源(过渡期),按 (链接, 变体) 再按 颜色+尺码 去重; mediaByColor 同源;options/media 并入族成员 detail
    • sizeChart / packageSpecs = 族物化并集(空并集回退主链接合并结果);
    • 族块(默认输出):family 含并集尺码表/包装 + 严格五维价格矩阵 + 族起价:
{
  "family": {
    "familyId": "12", "familyCode": "DG015", "familyName": "DG015 180G纯棉T恤",
    "sizes": [...], "colors": [...],
    "printCounts": ["单面印花", "双面印花"], "crafts": ["烫画", "直喷", "不打印"], "logistics": ["包邮", "不包邮"],
    "sizeChart": { /* 并集 */ }, "packageSpecs": { /* 并集 */ },
    "priceMatrix": { /* rows  {sizeId, colorId, printCount, craft, logistics, price, manual, sources} */ },
    "minPrice": "29.5"
  }
}

矩阵维度来源是链接级标签(印花数量/印刷工艺/物流渠道三组;人工接管后按人工标签), 不再用原始链接名的第 3 段(craftLabel 含「直喷单面/光板不打印」等噪声)。名称派生规则已 覆盖裸「单面/双面」写法(如「直喷双面」→ 双面印花 + 直喷)。光板/不打印链接无印花面概念, 矩阵中印花数量维回退「单面印花」占位。前端本地按五维联动 priceMatrix 即可实时算价 (设计 D4)。

边界声明:价格区间筛选(minPrice/maxPrice)仍作用于链接 goodPrice(族内任一链接命中即 返回该族;SQL 层无法廉价解析族矩阵 JSON);good_origin_goods 转只读保留,观察期后另行删除。

后台操作入口(族替代旧主源/副源,界面保持原有布局)

  • 商品配置页布局不变(左树=官网商品、右树=原产品库分类平铺);配置弹窗保持原「合并同名」 勾选流程,提交时静默把勾选链接与主链接归入同一族(无族自动成族);族内不区分主次 (成员列表无设为主链接按钮,主链接仅作为详情数据源的内部实现);
  • 编辑弹窗的「关联原产品」区块:显示族编码与链接数,成员行显示原始链接名(便于核对) 并可逐个展开——查看成员详情(SDS ID / 链接价格 / SKU 数 / 物流备注 / 工艺位置 / 仓库)、 配置该链接的标签(保存后人工接管;「恢复自动」回到按名称派生)、 改价格(尺码/颜色/价格 可编辑表格,写入族价格覆盖)、 查看成员自己的详情(商品详情 / 尺码表 / 包装规格 / SKU 四个页签,懒加载 GET /origin-goods/:id,SKU 表带 物流/工艺/印花数量 维度列;未同步时可在面板内直接同步); SDS 商品不再在弹窗底部展示单一链接的旧详情区(自定义商品的编辑表单仍保留在底部);
  • 成员列表下方有族级并集视图页签:尺码表 / 包装规格(族物化并集)与 SKU(全族动态合并)——SKU 即价格矩阵行,各链接变体按 物流×工艺×印花数量×尺码×颜色 归并(同组合取最低价,「链接数」列显示来源数,人工价带标签),无需额外请求;
  • 编辑弹窗商品「名称」预填解析后的「品名 型号」,保存即采用解析名;右侧原产品库与配置弹窗 始终显示原始链接名
  • 配置弹窗「合并同名」区块常显,候选为全局同款链接:按 链接款号(名称第 2 段, 如 BRTF004)或族归属跨分类匹配,同款不同工艺/物流/仓库均在列(含已配置链接), 支持搜索过滤;无候选时显示空态提示。
  • 右树徽标语义(族感知,公开契约一族一款):已配置 N(绿,本链接已配置为官网商品, N=国数)/ 同款已配置(绿,本链接未单独建商品但所属族已配置,无需再配)/ 成族(蓝, 已归族且全族未配置)/ 未配置(橙);「定位到官网商品」在自身或同族已配置时出现(按 主链接 → 同族 → 旧副源顺序定位并高亮);「配置」按钮仅在本链接及其同款族均未配置时出现, 拖拽到左侧分类/国家的路径同样被拦截(提示同款已配置、不允许重复配置);
  • 族分组头部计数与「仅未配置」筛选同样按族语义(族已配置 → 计入已配置)。
  • 左树一族一行(国家/品类模式):同款多条配置只显示排序最前的一条,名称旁常显 「M 个配置」徽标(M=该族官网商品数;单配置也显示,避免漏看),悬浮列出全部配置 (名称/国家·分类/打开),分组头部计数仍按全部商品统计。
  • 人工改价/自动成族等族管理 API/product-families/*)保留,供脚本或后续界面使用;
  • 页面组件化:GoodsView.vue 拆出 components/ 下的 GoodsEditDialog(编辑弹窗)、 GoodsConfigDialog(配置弹窗)、CustomGoodDialog(自定义商品)、TagFilterPopover(标签筛选弹层)。

注意:公开契约一族一款,同款(同族)已配置后不允许再配置——配置按钮隐藏、拖拽路径拦截, 避免产生官网不可见的冗余商品;历史重复商品(如早期 DG120/AUTM003 测试期间产生的)仍建议清理。

链接级标签(自动派生 + 人工修正,2026-08 规则改版)

  • 标签与产品链接一一对应(每条链接因印花数量/工艺/物流不同而价格不同), 落库在链接级 origin_good_tags 表(manual 标记人工/派生);商品的自动组标签是其 主链接标签的镜像
  • 解析规则(apps/api/src/product-families/auto-tag-rules.ts,与 admin 端 utils/origin-name.ts#deriveLinkTagNames 同构):
    • 印花数量:名称含「双面印花」→ 双面印花;否则含「单面印花」→ 单面印花(组「印花数量」);
    • 工艺:名称含「直喷」→ 直喷;含「不打印」或「光板」→ 不打印光板即为不打印, 组「印刷工艺」);都不含 → 默认 烫画
    • 物流:含「不包邮」→ 不包邮;否则含「包邮」→ 包邮(组「物流渠道」,先判不包邮防子串误命中);
  • 每次族重算/成员变更/商品创建更新时:未接管的 SDS 链接按名称刷新派生行; 人工接管的链接(tagsManual=true)永不被覆盖;商品镜像其链接的有效标签; 缺失的组与标签自动补建;
  • 人工修正入口:编辑弹窗成员行展开 → 修改标签 → 保存标签(全量替换为人工行, PUT /origin-goods/:id/tags);「恢复自动」清掉人工行回到派生(DELETE /origin-goods/:id/tags);
  • 其他分组(如风格类)不受影响,保持人工管理;自定义链接(无名称可解析)同样支持人工配置;
  • 实测:美国(包邮)…-DG001-单面印花包邮 / 烫画 / 单面印花 美国(不包邮光板)…-JSA002-不打印不包邮 / 不打印 / 光板 美国(不包邮)…-DG501-双面印花不包邮 / 烫画 / 双面印花

链接名称展示解析:后台所有链接名展示位(左右树、族成员列表、配置/编辑弹窗、搜索候选) 统一解析为「品名 型号」(如 美国(包邮)180g纯棉T恤成人款-DG001-单面印花180g纯棉T恤成人款 DG001),悬停 tooltip 保留原始全名;搜索同时匹配原始名与解析名 (utils/origin-name.ts#cleanLinkName)。

验证

pnpm --filter @inkreach/website test --run
pnpm --filter @inkreach/website build
pnpm --filter @inkreach/website dev
pnpm --filter @inkreach/api test -- --runInBand
pnpm --filter @inkreach/api build