feat: PDD 商品页面 (#18)

打通 Client↔Admin 闭环的一环:录入 PDD 链接 → 创建采集任务 →
客户端领走去采 → 规格和价格显示在页面上。#16 写的 MarkCollecting 和
SoftDeletePddProduct 至此才有生产调用方。

链接解析严格、不做容错兜底:goods_id 上有 UNIQUE 约束,防重全靠它。
猜一个的话同一商品会存成好几行、采好几遍,规格映射还说不清指向哪一行。
短链一律拒绝,卡域名是为了拦"粘了淘宝链接"这种失误。

创建采集任务先占状态再建任务:MarkCollecting 只在 pending/failed 时成功,
同时充当"有没有人已经在采"的判断,与建任务在同一事务里,
所以并发点多次只会建出一个任务(实测 4 并发 → 1 条)。

submit.go 的 collectedData 增加 Dimensions —— 没有它就只能按 Go 的 map
遍历,而 map 无序,同一商品每次刷新"颜色/尺码"的先后都可能变。

顺带修 #16 一处缺陷:EnsurePddProduct 复活分支清空了 skus_json 却漏了
title,导致复活后状态显示"未采集"但标题还留着旧值。

审查打回一次:清理"PDD 链接唯一的录入口"这一过期说法(全库 5 处),
以及 shopee.go 里"空 → no_link"的过期 TODO —— no_link 已在 #16
从 CHECK 约束删除,照写会直接撞约束。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
chengma
2026-08-07 11:27:06 +08:00
co-authored by Claude Opus 5
parent fab20cfd2f
commit eb8d357f5a
16 changed files with 2333 additions and 72 deletions
+85 -16
View File
@@ -30,19 +30,22 @@ Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行
## 3. 完整业务链路
这条链路是理解全部四个模块的关键,先看懂它:
这条链路是理解全部五个模块的关键,先看懂它:
```text
蝦皮报表 ──导入──→ 商品表 + SKU 表
│
编辑弹窗:人工填 PDD 链接
│ 点【采集】
│
↓
创建采集任务 → 分配 Client → 被领取
PDD 商品页 ──────→ PDD 商品表
│ 点【创建采集任务】
↓
创建采集任务(不指定 Client)→ 谁领到算谁的
↓
Client 采回 PDD 的颜色尺码和价格
↓
存入 商品表.pdd_data
存入 PDD 商品表.skus_json
│
顺运宝货运单 ─同步─→ 货运单表
│ 双击行
@@ -62,18 +65,24 @@ Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行
## 4. 产品范围
顶级导航固定四个模块,顺序不变:
顶级导航固定五个模块,顺序不变:
| # | 模块 | 职责 |
|---|---|---|
| 1 | 蝦皮数据 | 商品档案、PDD 链接、发起采集 |
| 2 | 顺运宝数据 | 货运单、规格匹配、生成采购任务 |
| 3 | 采购任务 | 执行进度跟踪 |
| 4 | 客户端列表 | 客户端注册与状态 |
| 2 | PDD 商品 | PDD 商品档案、发起采集、查看采回来的规格价格 |
| 3 | 顺运宝数据 | 货运单、规格匹配、生成采购任务 |
| 4 | 采购任务 | 执行进度跟踪 |
| 5 | 客户端列表 | 客户端注册与状态 |
四个模块**统一使用三段式页面布局**:顶部工具条 / 中间带勾选的表格 / 底部状态条。
五个模块**统一使用三段式页面布局**:顶部工具条 / 中间带勾选的表格 / 底部状态条。
详见 [05 界面规范](05-ui-specification.md)。
PDD 商品之所以单独一个模块,是因为它在数据上就是**独立实体**
(自己的表、自己的采集状态,可以被多个蝦皮商品共用),
见 [03 数据模型 §4](03-data-model.md)。挂在蝦皮模块下面的话,
同一个 PDD 商品被两个蝦皮商品引用时就说不清该显示在谁名下。
### 4.1 蝦皮数据模块
**顶部工具条:** 导入按钮、商品 ID 搜索框、搜索按钮、删除按钮、批量采集按钮。
@@ -90,13 +99,18 @@ Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行
| 采集状态 | 见 §6 |
| 更新时间 | |
**双击行打开编辑弹窗**,这是 PDD 链接唯一的录入口:
**双击行打开编辑弹窗**,这是**从蝦皮商品出发**填 PDD 链接、发起采集的入口:
- 可编辑:PDD 链接、颜色、尺码、建议;
- 弹窗内提供【采集】按钮,紧挨 PDD 链接输入框;
- `[必须]` 采集按钮**只出现在弹窗里**,不要放在每个表格行——
- `[必须]` 采集按钮**在本页只出现在弹窗里**,不要放在每个表格行——
一个商品有 N 个 SKU 行,放行上就是 N 个按钮干同一件事,还会建出 N 个重复任务。
`[注意]` PDD 商品模块(§4.2)也能录入链接和发起采集,两处并存。
本页的入口目前**还是骨架**(点了返回 501),
两者要不要合并、以哪个为准,属于「蝦皮↔PDD 关联入口」的范围,
由那张工单统一决定,不要在本模块单独改。
**其他要求:**
- `[必须]` 支持**手动新增**一条 SKU。报表只含有销售成绩的 SKU(样本里平均每商品仅 1.17 个),
@@ -106,7 +120,59 @@ Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行
**底部状态条:** 最近一次导入的时间、条数、失败行数。
### 4.2 顺运宝数据模块
### 4.2 PDD 商品模块
维护拼多多商品档案,并发起采集。这条链路**不依赖蝦皮和顺运宝的任何数据**,
可以单独跑通:建商品 → 建采集任务 → Client 领走执行 → 提交结果 → 页面显示已采集。
**顶部工具条:** 创建按钮、创建采集任务按钮、采集状态筛选、商品 ID/链接搜索框、搜索按钮、删除按钮。
**中间表格:**
| 列 | 说明 |
|---|---|
| 勾选 | 支持批量删除、批量建采集任务 |
| 商品 ID | 从链接解析出来的 `goods_id` |
| 标题 | 采集回来的,未采集时显示占位文案 |
| PDD 链接 | 截断显示,可点开 |
| 采集状态 | 见 §6.1,用中文文字,不能只靠颜色 |
| 规格数 | 从 `skus_json` 算 |
| 采集时间 | 未采集显示 `—` |
| 更新时间 | |
`[必须]` **规格数必须显示。** 采到 1 个和采到 20 个差别很大——
只采到 1 个通常意味着客户端没点开规格面板,是采集有问题。
不显示这一列的话,要点进每个商品才能发现。
**创建:** `[必须]` 只填 PDD 链接,其余字段全靠采集回填。
`[必须]` **链接必须能解析出 `goods_id`,解析不出来直接报错并且不写库**。
不接受短链接——`goods_id` 上有 UNIQUE 约束,防重全靠它,
拿不到就没法查重,同一个商品会存成好几行。
**双击行打开弹窗:** 查看商品信息,可编辑链接,并显示采回来的规格和价格。
- `[必须]` **规格和价格必须显示**,这是采集结果的全部价值所在——
不显示的话操作员没法确认"采得对不对、是不是我要的那个商品"。
- `[必须]` 维度顺序按 `skus_json` 里的 `dimensions`(Go 的 map 无序,必须靠它定顺序)。
- `[必须]` 价格显示成 `¥12.56`,底层存整数分;`price_cent` 为 `null` 时显示"未采到",
**不得显示成 ¥0.00**。
- `[必须]` 编辑链接时新链接必须还是**同一个商品**。换成别的商品要新建一条:
这一行上挂着采集结果和 SKU 映射,`goods_id` 一换那些数据就全指到错的商品上了。
**创建采集任务:**
- `[必须]` **不指定客户端**,谁领到就在领取时标记谁。采集是纯读取操作,
哪台机器跑都一样,指定了反而会在那台机器关着的时候干等。
- `[必须]` 按 `goods_id` 去重;`collecting` 状态的跳过,并在结果里说明跳过了几个。
- `[必须]` 按钮文案用「**创建采集任务**」,不要用「采集」。它做的是建一个任务,
不是立刻去采——真正的采集要等 Client 来领、去手机上跑,可能几秒也可能几分钟。
**删除:** 软删除。二次确认要写明"重新创建同一链接可恢复,**但采集结果会清空**"。
不硬删是因为 SKU 映射指向它,硬删会把人工攒了很久的匹配成果一起带走。
**底部状态条:** 各采集状态的条数统计。
### 4.3 顺运宝数据模块
**顶部工具条:** 同步按钮、订单号搜索框、搜索按钮、创建采购任务按钮、删除按钮。
@@ -143,7 +209,7 @@ Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行
**底部状态条:** 最近同步时间、待匹配条数。
### 4.3 采购任务模块
### 4.4 采购任务模块
**顶部工具条:** 订单号搜索框、搜索按钮、删除按钮。
@@ -164,7 +230,7 @@ Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行
**底部状态条:** 各状态的任务条数统计。
### 4.4 客户端列表模块
### 4.5 客户端列表模块
**顶部工具条:** 客户端名称搜索框、搜索按钮、删除按钮。
@@ -261,7 +327,7 @@ Admin 的职责是把这三方的数据串起来,最终产出 Client 能执行
MVP 包含:
- 四模块页面框架和统一的三段式布局;
- 五模块页面框架和统一的三段式布局;
- SQLite 建库与迁移;
- 蝦皮 Excel 导入(upsert)、搜索、批量删除、手动新增、编辑弹窗;
- PDD 链接录入与发起采集;
@@ -308,7 +374,10 @@ MVP 之后:
**已定案:**
- PDD 链接**人工填写**,入口在蝦皮数据模块的编辑弹窗,采集按钮也在那里。
- PDD 链接**人工填写**,不自动抓取。
- PDD 商品是**独立模块**,创建商品和创建采集任务都在那里;
蝦皮数据模块的编辑弹窗负责的是「这个蝦皮商品对应哪个 PDD 商品」。
- **采集任务不指定客户端**,谁领到算谁的;采购任务仍可分配,也允许留空。
- 任务**分配给指定客户端**,Client 只领分给自己的。
- **不加心跳接口**;设置页显式登记,领取接口保留兼容登记,在线状态由最近活动时间派生。
- SKU 映射**独立成表且可复用**,同一蝦皮 SKU 只人工匹配一次。