feat(edit): 商品编辑页优化

This commit is contained in:
R524809
2026-09-21 17:44:50 +08:00
parent 75fa2b60f3
commit 05747d6f22
20 changed files with 1006 additions and 244 deletions
+6 -4
View File
@@ -70,7 +70,9 @@ Headers:`Idempotency-Key` 必填,页头平台由 header 指定,body 为空
} }
```
`variants` 每项同时返回通用 SKU、合并后实物/包装规格、当前平台 listing 摘要(含 `offer_id`、价格、首次导入状态、最近脱敏失败原因)及已选媒体。动态属性 schema 由第 3 节的独立接口按当前类目读取。候选和已选媒体只含 `asset_id`、尺寸、MIME、来源角色与短时预览 URL;不含 `object_key` 或永久 URL。
`variants` 每项同时返回通用 SKU、合并后实物/包装规格、当前平台 listing 摘要(含 `offer_id`、价格、首次导入状态、最近脱敏失败原因)、SKU 覆盖的 `tags` / `description` 及已选媒体。SKU 的 `tags: []`、`description: null` 分别表示继承 SPU 主题标签、产品描述;动态属性 schema 由第 3 节的独立接口按当前类目读取。候选和已选媒体只含 `asset_id`、尺寸、MIME、来源角色与短时预览 URL;不含 `object_key` 或永久 URL。
`default_package_spec` 是 SPU 的存储事实,`variants[].package_spec` 仅保存该 SKU 的覆盖值;`effective_package_spec` 按“SKU 覆盖值优先、SPU 默认值回退”解析。Ozon 发布适配器在创建 job 时才将每个目标 SKU 的有效值物化为独立 `items[]`:`length_mm → depth`、`width_mm → width`、`height_mm → height`、`shipping_weight_g → weight`,并固定带上 `dimension_unit: "mm"` 与 `weight_unit: "g"`。不得为了 payload 预先将 SPU 默认值复制到所有 SKU 草稿。商品净重/尺寸不是这组固定物流字段;它们的 SPU/SKU 展示归属由当前 Ozon schema 的 `is_aspect` 决定。
`source_material` 是供页面展示和 AI 使用的**有效来源素材**,由当前 `collection_items.normalized_payload` 的最新规范化值与用户明确修改过的顶层字段合并而成。未修改字段随重复采集自动更新;已修改字段继续保留用户值。`source_material_state.latest` 保留本次最新规范化来源,`overridden_fields` 表示用户覆盖字段,`changed_fields` 表示复采后与用户稿不同的覆盖字段,页面可据此提示、逐项恢复或全部采用最新采集值。原始 `normalized_payload` 与 `raw_evidence` 始终作为采集证据保留,不因编辑而改变。
@@ -100,7 +102,7 @@ Headers:`If-Match: "{product_revision}:{profile_revision}"`、`Idempotency-Key
"specification_values": [{ "axis_key": "color", "value_key": "black", "value_name": "黑色" }], "purchase_cost_cny": "80.00",
"physical_spec": { "length_mm": null, "width_mm": null, "height_mm": null, "net_weight_g": null, "status": null },
"package_spec": { "length_mm": null, "width_mm": null, "height_mm": null, "shipping_weight_g": null, "status": null },
"offer_id": null,
"offer_id": null, "tags": ["tag"], "description": "该 SKU 的描述",
"media_asset_ids": ["uuid", "uuid"]
}],
"attributes": [{ "scope": "common", "variant_id": null, "platform_attribute_id": "100", "complex_id": "0", "values": ["value"], "language_code": "ru", "source": "manual", "confirmation_status": "confirmed" }]
@@ -116,7 +118,7 @@ Headers:`If-Match: "{product_revision}:{profile_revision}"`、`Idempotency-Key
- 类目切换或平台 schema 移除属性时,原有不再适用的属性不会进入新的发布草稿;服务端将它们写入 `unmapped_attributes` 并在编辑快照中返回。用户删除 SKU 时,其 SKU 属性随 SKU 一起移除,不生成无主未映射属性。
- `variants` 是当前完整未删除 SKU 集合。省略既有 SKU 表示软删除;无论当前 SPU 状态都允许新增、复制、删除。`copy_from_variant_id` 仅用于新行,复制通用 SKU 数据但不复制任何平台导入映射,生成的新 SKU 从 `not_listed` 开始。
- 删除已导入 SKU 只从 Atlas 草稿及当前平台可发布集合移除,**不删除、不下架、不修改** Ozon 商品或在线镜像;其他平台历史映射保留。删除后当前平台只剩已导入 SKU 时,SPU 汇总自然为“已上架”。
- `null` 的 SKU 规格字段表示继承父商品;包装物流规格的 `status` 为 `missing`/`ambiguous` 时,不允许定价确认或首次上架,但仍允许保存草稿。实物规格只影响尺寸图,不用于物流。
- `null` 的 SKU 规格字段表示继承父商品;包装物流规格的 `status` 为 `missing`/`ambiguous` 时,不允许定价确认或首次上架,但仍允许保存草稿。发布前服务端必须对每个待发布 SKU 解析 `effective_package_spec`,确认长、宽、高、重量均存在且大于 0 后才可生成 Ozon item。实物规格不用于物流;是否向 SPU 或 SKU 展示由其对应的类目 schema 属性决定,尺寸图仍可使用已确认的有效实物规格。
- `target_store_id` 只能是当前工作空间、当前平台的活动店铺。变更目标店铺只更新**尚未导入** listing 的将来发布目标;`imported` listing 永远保留其首次导入时的店铺快照。
- 类目变化后不匹配的旧属性作为未映射草稿返回 warnings,不能自动进入发布 payload。属性由平台 schema 校验 `type`、必填、字典、多值/复杂属性和 `is_aspect`;客户端不能用任意 JSON 绕过。
- `media_asset_ids` 是本 SKU 当前平台的最终选图顺序;首期最多 15 张。格式只允许 JPG/PNG/WebP,单文件最多 20 MB,边长超过 10,000 px 拒绝,短边小于 700 px 不得作为首图并给出警告。第一张是主图。
@@ -277,7 +279,7 @@ Headers:`Idempotency-Key` 必填。Body:
服务端在创建任务前重新运行第 3 节校验。只允许当前 profile 中状态为 `not_listed` 或 `failed` 的未删除 SKU;`submitting` 返回 `409 SKU_IMPORT_IN_PROGRESS`,`imported` 返回 `422 SKU_ALREADY_IMPORTED`,不得重新提交或把编辑草稿当平台更新。
校验通过后在同一事务创建 `product_publish_job` 和每个 SKU 的 `product_publish_job_item`,固化 profile、目标店铺、类目/属性、价格快照、图片访问 URL、offer ID 与平台适配请求,再投递 Worker。返回 `202`:
校验通过后在同一事务创建 `product_publish_job` 和每个 SKU 的 `product_publish_job_item`,固化 profile、目标店铺、类目/属性、价格快照、图片访问 URL、offer ID、该 SKU 的有效包装规格和平台适配请求,再投递 Worker。适配请求中的每个 Ozon `items[]` 必须包含该 SKU 独立解析出的 `depth`、`width`、`height`、`dimension_unit`、`weight`、`weight_unit`,不得省略或传 0。返回 `202`:
```json
{ "data": { "publish_job": { "id": "uuid", "status": "queued", "store_id": "uuid", "item_count": 2 } } }
@@ -4,7 +4,7 @@
## 1. 目标与原则
Ozon 商品的必填属性由 `description_category_id` 与 `type_id` 对应的类目 schema 决定;同一 Ozon 平台的所有门店共用该 schema。编辑器不得为儿童玩具、服装、食品等类目分别硬编码字段,而应使用 Ozon 返回的 `is_required`、`is_aspect`、`dictionary_id`、`is_collection`、`max_value_count`、`type` 与复杂属性信息驱动展示、填报和发布校验。当前编辑器和首次发布预检均读取平台级类目、属性和字典缓存;真正创建发布 job 并调用 Ozon 的闭环仍待后续实现。
Ozon 商品的必填属性由 `description_category_id` 与 `type_id` 对应的类目 schema 决定;同一 Ozon 平台的所有门店共用该 schema。编辑器不得为儿童玩具、服装、食品等类目分别硬编码字段,而应使用 Ozon 返回的 `is_required`、`is_aspect`、`dictionary_id`、`is_collection`、`max_value_count`、`type` 与复杂属性信息驱动展示、填报和发布校验。唯一的编辑器业务例外是名为“商品颜色”的 SKU 字典属性:即使 schema 标记为单值或选填,也按多选控件、最多 100 个值和上架必填处理。当前编辑器和首次发布预检均读取平台级类目、属性和字典缓存;真正创建发布 job 并调用 Ozon 的闭环仍待后续实现。
页面继续以 Atlas 商品和 SKU 为编辑主体:主要信息保留稳定的商品级字段,Ozon 类目属性按通用属性和 SKU 变体属性拆分。保存草稿允许信息不完整;首次上架必须由服务端按当时有效的类目 schema 重新校验。
@@ -21,7 +21,9 @@ Ozon 商品的必填属性由 `description_category_id` 与 `type_id` 对应的
- 品牌;
- 默认包装重量与默认包装尺寸。
默认包装规格是父商品级默认值。每个 SKU 默认继承该值;当某个 SKU 的包装不同,可在 SKU 卡片中设置包装重量和包装尺寸覆盖值。SKU 未填写覆盖值不表示缺失,而是继续继承主要信息中的默认包装规格。
默认包装规格是父商品级默认值。每个 SKU 默认继承该值;当某个 SKU 的包装不同,可在 SKU 卡片中设置包装重量和包装尺寸覆盖值。SKU 未填写覆盖值不表示缺失,而是继续继承主要信息中的默认包装规格。包装重量和包装尺寸属于 Ozon 导入接口的固定物流字段,而不是由类目 schema 决定归属的动态属性:编辑器可以在重量、尺寸后分别提供“按 SKU 填写”的入口,但这只开启对应字段的 SKU 覆盖编辑,不应删除 SPU 默认值或把继承值冗余持久化到所有 SKU。
创建 Ozon 发布请求时,服务端必须为每个待发布 SKU 计算有效包装规格:SKU 覆盖值优先,否则取 SPU 默认值。每个 `/v3/product/import` 的 `items[]` 都要写入 `depth`(由 `length_mm` 映射)、`width`、`height`、`dimension_unit=mm`、`weight`(由 `shipping_weight_g` 映射)和 `weight_unit=g`;四个数值均必须存在且大于 0。该“逐 SKU 展开”发生在发布快照/payload 适配层,不要求草稿存储复制同一组 SPU 值。
品牌和型号既是主要信息,也是可供采集、AI 与多平台复用的 Atlas 标准字段。如果 Ozon 当前类目同时返回对应平台属性,发布适配器应建立显式映射并确保只有一个最终事实来源;字典型品牌必须由用户确认 Ozon 字典值,不能把自由文本直接冒充字典 ID。
@@ -37,6 +39,8 @@ Ozon 商品的必填属性由 `description_category_id` 与 `type_id` 对应的
- 整数、十进制、布尔、文本和字典分别使用适合的输入控件。
- 复杂属性按 Ozon 的复杂属性结构分组展示,发布时进入 `complex_attributes`,不得扁平化为普通属性。
“商品净重”和“商品尺寸”不等同于 Ozon 的包装物流规格,也不能因为 SKU 存在而默认显示。它们只有在当前类目 schema 返回相应属性时才展示:`is_aspect=true` 放入 SKU,`is_aspect=false` 放入 SPU;是否为上架必填由 `is_required` 决定。Atlas 可继续保存实物规格供尺寸图等内部能力使用,但不能把它当作 Ozon 固定物流字段,也不得替代有效包装规格。
例如,儿童玩具类目把「最小儿童年龄」和「最大儿童年龄」标为必填时,两个字段应直接显示在产品属性区,并在各自 Label 前显示红色 `*`。如果它们是字典属性,必须选择 Ozon 返回的年龄值,而不是任意输入文本。
### 2.3 SKU 设置
@@ -49,7 +53,7 @@ Ozon schema 中 `is_aspect=true` 的属性放入每个 SKU 卡片的「Ozon 变
- Ozon 变体属性,例如颜色、尺码;
- 采购价、售价和划线价;
- SKU 图片;
- SKU 实物规格;
- 当前 schema 要求或允许的 SKU 级商品净重/尺寸属性;
- 对父商品默认包装规格的可选覆盖。
SKU 中的 Ozon 必填变体属性同样只在 Label 前显示红色 `*`。父商品默认包装规格与 SKU 覆盖值应明确显示继承关系,例如「继承默认值:320 g」和「为此 SKU 单独设置」。
@@ -157,7 +161,7 @@ GET /api/product-platform-profiles/{profile_id}/validation
- 所有通用必填属性有合法值;
- 每个待上架 SKU 的必填变体属性有合法值;
- 字典值、单/多值数量、数值类型与复杂属性结构有效;
- SKU 的 `offer_id`、条形码、价格、图片和有效包装规格符合发布要求;
- SKU 的 `offer_id`、条形码、价格、图片和有效包装规格符合发布要求;有效包装规格按“SKU 覆盖值 → SPU 默认值”解析,且在创建发布快照前必须可展开为该 SKU 的完整 Ozon 物流字段;
- 已上架 SKU 返回 `not_publishable_again`,不作为普通字段缺失处理。
`POST /publish-jobs` 必须调用同一套校验,不能信任浏览器校验结果。保存草稿和创建发布任务应继续保持各自的事务与幂等边界。
+2 -2
View File
@@ -35,11 +35,11 @@
保存仍由服务端按平台 schema 与字典值复核。选择类目和属性与商品草稿同一次保存,`platform_type_id` 从平台类目记录取得;类目变更或 schema 下线后,失配属性保留为未映射草稿,不能自动进入发布请求。首次发布前已提供 `publish-preflights` 复用同一套目录新鲜度与本地校验逻辑;真正创建发布任务并调用 Ozon 的流程尚未实现。完整规则见[平台目录缓存刷新方案](../architecture/platform-catalog-cache-refresh-plan.md)。
类目、标题、型号、品牌以及默认包装重量和默认包装尺寸固定放在「主要信息」区。SKU 默认继承父商品包装规格,仅在包装不同的情况下填写覆盖值。平台动态必填字段统一使用表单标准表现,在 Label 前显示红色 `*`;不追加「Ozon 必填」标签,也不在区块标题显示完成数量。更完整的目标交互、属性值表达、SKU 关联和发布校验改造见 [`ozon-dynamic-attributes-and-sku-form-plan.md`](./ozon-dynamic-attributes-and-sku-form-plan.md)。
类目、标题、型号、品牌以及默认包装重量和默认包装尺寸固定放在「主要信息」区。SKU 默认继承父商品包装规格,仅在包装不同的情况下填写覆盖值;重量、尺寸可分别开启 SKU 覆盖编辑,但 SPU 默认值仍是未覆盖 SKU 的事实来源。发布适配器必须在生成每个 Ozon `items[]` 时解析有效包装规格,而不是要求草稿向每个 SKU 重复保存相同值。平台动态必填字段统一使用表单标准表现,在 Label 前显示红色 `*`;不追加「Ozon 必填」标签,也不在区块标题显示完成数量。更完整的目标交互、属性值表达、SKU 关联和发布校验改造见 [`ozon-dynamic-attributes-and-sku-form-plan.md`](./ozon-dynamic-attributes-and-sku-form-plan.md)。
## 3. SKU 卡片和列表
卡片默认展示:序号、SKU 名称、当前平台上架状态、最多 3 张图片缩略图(可切换为 5)、货号、条形码、采购价、售价、划线价、定价按钮、两组规格与 Ozon 变体属性。列表仅是紧凑视图,字段和数据完全相同。货号由用户填写,不自动复用来源 SKU;缺少货号的 SKU 可保存但不能被选择首次上架。SKU 包装规格为空时继承主要信息中的默认包装规格;只有该 SKU 包装不同才填写覆盖值。首期不支持视频上传、编辑或发布;视频能力待 Ozon 对应接口和审核规则确定后再开启。
卡片默认展示:序号、SKU 名称、当前平台上架状态、最多 3 张图片缩略图(可切换为 5)、货号、条形码、采购价、售价、划线价、定价按钮、按 schema 展示的商品规格与 Ozon 变体属性。列表仅是紧凑视图,字段和数据完全相同。货号由用户填写,不自动复用来源 SKU;缺少货号的 SKU 可保存但不能被选择首次上架。SKU 包装规格为空时继承主要信息中的默认包装规格;只有该 SKU 包装不同才填写覆盖值。商品净重和商品尺寸不是 Ozon 固定物流字段,只在当前 schema 的对应属性为 SKU 级(`is_aspect=true`)时展示;商品级属性继续在 SPU 展示。首期不支持视频上传、编辑或发布;视频能力待 Ozon 对应接口和审核规则确定后再开启。
SKU 名称只用于展示;采集 SKU 用稳定 `source_variant_key` 关联,手工 SKU 用本地 UUID。规格字段 `null` 仅表示没有覆盖父商品默认值,绝不能据此判断是否单规格或缺少 SKU。