docs: 归档 #41 #43,记录 #38 #41 #43 验收通过

#41 归档记录了两条防信息丢失的设计(SKU 数列、待补列),以及最危险的
「部分失败」场景实测:28431952912 颜色4/尺码5 数字看着正常,
里面有 4 个 SKU 解析失败,靠整行标黄才藏不住。

#43 归档记录了实现踩到的 html/template URL 上下文转义坑:
夹在字面量 & 中间的动态内容会被整体当成一个参数值转义,
?/= 变成 %3F/%3D 让链接失效,只有真跑起来看 HTML 才发现。

也记了架构角色第二个变异第一次打偏、显示「跳过」的事——
探针无效和代码有问题是两回事,#38 刚犯过一次。

三份都如实列了「未验证到的部分」,主要是浏览器实机交互和
列宽百分比在 table-cell 上的实际行为。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
chengma
2026-08-09 10:06:42 +08:00
co-authored by Claude Opus 5
parent edfe39cc35
commit 6d40c1e986
3 changed files with 370 additions and 1 deletions
@@ -0,0 +1,168 @@
# 41 Admin 蝦皮数据页改为商品级列表与规格弹窗
- 类型:需求(界面重构)
- 父级大工单:#14
- 所属 MVP / 版本:#15 / MVP
- 关联:#38(导入)、#18(弹窗机制来源)
- 状态:验收通过
- 日期:2026-08-08
- Gitea 工单:http://ilaer.eicp.net:8418/chengma/cmautobuy/issues/41
## 背景与目标
蝦皮数据页原来是 **SKU 级一行**。问题不在行数(6092 → 5195 只少 15%,
平均每个商品仅 1.17 个 SKU),在**语义错了**:
`pdd_goods_url` / `pdd_goods_id` 在 `shopee_products` 上,是**商品级**字段。
实测商品 `24420648774` 有 **62 个 SKU**——列表里 62 行显示的是**同一个
PDD 链接**,将来双击任意一行编辑的也是**同一个字段**。操作员会以为
「我改的是这一行的链接」。
## 最终方案
```text
☐ │ 商品 ID │ 商品名称 │ 颜色 │ 尺码 │ SKU │ 待补 │ PDD 链接 │ 采集状态 │ 更新时间
15 5 28 2
```
### 必须加「SKU 数」列
`[必须]` 只显示颜色数和尺码数会**严重误导**。实测:
```
颜色数×尺码数 > 实际 SKU 数的商品:602 个(占 11.6%)
goods_id 颜色数 尺码数 相乘 实际 SKU
26886533818 15 5 75 28 ← 差 2.7 倍
24773321934 10 6 60 14
27739534478 12 6 72 36
```
看到「颜色 15 / 尺码 5」,正常人会以为有 75 个规格要匹配,**实际只有 28 个**。
根因:蝦皮报表只含**有销售成绩的** SKU,数据本来就不全。三个数字放在一起,
操作员才看得出「这商品在报表里只有 28 条记录,不是 75 条」。
### 必须加「待补」列并整行标黄
`[必须]` 原来 `parse_ok = 0` 的行显示 `—` 并整行标黄,一眼能看见要人工补。
聚合成数量之后这个信号会丢。实测:
```
23 条解析失败的 SKU,分布在 6 个商品里
4 个是「部分失败」 → 显示「颜色 3」,坏的那几个直接消失
2 个是「全部失败」 → 显示「颜色 0 / 尺码 0」
```
**「部分失败」那 4 个最危险**——数字看着正常,坏数据被吃掉,永远没人去补。
`[必须]` 颜色数 / 尺码数**只统计 `parse_ok = 1`** 的 SKU。
把解析失败的空值算进 DISTINCT 会多出一个「空颜色」。
### 弹窗只读,待补的行显示原文
`[必须]` 待补的行必须显示 `spec_raw` 原文——不显示的话人工不知道该填什么,
保留原文这个设计就白做了。
`[必须]` 弹窗**只读**,`ShopeeSave` 仍是 501。不放一个点了没反应的保存按钮。
复用 #18 的弹窗机制(`data-detail-url` / `data-detail-slot` / `data-detail-id`),
`app.js` **未改动**。
## 与建单方案的差异
1. **详情路由用 `?id=` 而不是工单写的 `?goods_id=`。** `app.js` 里
`fetch(base + "?id=" + ...)` 的参数名是硬编码的,而「不改 `app.js`」是
更高优先级的约束。与 PDD 页的 `/pdd/detail?id=` 也保持了一致。
2. 新增 `admin/service/shopee_list_test.go`(工单预计文件表未列)。
## 改了哪些
- `admin/repository/shopee.go`:`ListShopeeProducts` 商品级聚合查询、
`GetShopeeProductByGoodsID`、`GetShopeeCollectStatus`、
`ListShopeeSKUsByGoodsID`;删除不再使用的 SKU 级查询。
- `admin/service/shopee_list.go`:视图改商品级,新增弹窗用的 `ShopeeProductDetail`。
- `admin/service/shopee_list_test.go`:新建。
- `admin/handler/web/shopee.go`:`ShopeeList` 改写;新增只读 `ShopeeDetail`。
- `admin/handler/web/web.go`:`GET /shopee/detail` 路由。
- `admin/templates/shopee/list.html`:列重写、双击绑定、弹窗壳子。
- `admin/templates/shopee/detail_modal.html`:新建。
- `admin/static/css/app.css`:`.col-title`。
## 验收结果
架构角色独立复跑,用真实样本导入后逐条核对:
| 检查 | 实测结果 |
|---|---|
| 总行数 | `共 5195 个商品` |
| `24420648774` | **1 行**(原 62 行),颜色10 / 尺码8 / SKU**62** / 待补0 |
| `26886533818` | 颜色**15** / 尺码**5** / SKU**28**(不是 75) |
| **`28431952912`(部分失败)** | 颜色4 / 尺码5 / SKU24 / 待补**4**,**整行标黄** |
| `24023581128`(全部失败) | 颜色0 / 尺码0 / SKU1 / 待补1,整行标黄 |
| 弹窗显示原文 | `原文:面膜安全褲3條膚色,【2xl 70-85公斤可穿】` |
| PDD 链接为空 | 显示「未填写」并标红 |
| `app.js` / `shopee_import.go` | 均未改动 |
| `ShopeeSave` / `Delete` / `Collect` | 仍是 501(3 处) |
| 五个页面 | 全部 200 |
`28431952912` 那条是最危险的场景——「部分失败」,颜色尺码数字看着完全正常
(4 和 5),但里面有 4 个 SKU 解析失败。标黄之后藏不住了。
## 测试
架构角色亲自执行,**用 `GOTOOLCHAIN=go1.23.0` 固定工具链**:
```
GOTOOLCHAIN=go1.23.0 go vet ./... 无输出
GOTOOLCHAIN=go1.23.0 gofmt -l . 无输出
GOTOOLCHAIN=go1.23.0 go test ./... -count=1
ok cmautobuy/admin 0.049s
ok cmautobuy/admin/repository 0.753s
ok cmautobuy/admin/service 2.081s
```
### 变异测试(工单未要求,架构角色补做)
| 变异 | 结果 |
|---|---|
| 颜色/尺码数不再过滤 `parse_ok = 1` | 4 个用例变红 |
| 待补数恒为 0(标黄失效) | 4 个用例变红 |
这两条正是本工单加进去防信息丢失的,现在有测试守着。
**未验证到的部分:**
- **浏览器里的真实交互**:双击开弹窗只验证了服务端 HTML 和 `/shopee/detail`
的返回内容,**没在真实浏览器里点过**。
- **1366×768 下的表格横向滚动**未做视觉走查。
- **`max-width` 用百分比作用在 `<td>` 上,浏览器行为不完全一致**——
部分浏览器对 `table-cell` 的 `max-width` 处理比较随意。若实机看下来
宽度完全没变化,需改成固定像素(`.truncate` 用的 `max-width: 280px`
那个路子是确定可行的)。
用户已实机确认并验收通过。
## 追加微调
用户看过实际渲染后要求商品名称列「缩小到现在的 60%」,
`max-width` 从 **30% → 18%**(提交 `ccc146c`)。
理由写进 CSS 注释:蝦皮标题普遍很长(实测最长 60+ 字,全是关键词堆砌),
不收窄的话这一列会把后面的颜色/尺码/SKU/待补几个数字挤出屏幕——
而那几个数字才是这个页面要看的东西。完整标题靠悬停的 `title` 属性看。
数值只写在 `app.css` 一处,模板注释不再重复具体百分比,
免得下次改了数值忘了同步。
## 遗留问题
1. `ShopeeSave` / `ShopeeDelete` / `ShopeeCollect` 仍是 501,属后续工单。
2. 手动新增 SKU(`05` §4.4)未做。蝦皮报表只含有销售成绩的 SKU,
导入进来的数据本身就是不全的。
3. 浏览器实机交互与列宽百分比的实际效果需人工确认。
## 相关提交
- `64d5e74` feat: 蝦皮数据页改为商品级列表与规格弹窗 (#41)
- `ccc146c` style: 蝦皮商品名称列再收窄到 18% (#41)