Files
cmautobuy/docs/task/43-admin-蝦皮数据页分页与状态筛选.md
T
chengmaandClaude Opus 5 6d40c1e986 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>
2026-08-09 10:06:42 +08:00

7.9 KiB
Raw Blame History

43 Admin 蝦皮数据页分页与状态筛选

  • 类型:需求(界面)
  • 父级大工单:#14
  • 所属 MVP / 版本:#15 / MVP
  • 关联:#38(导入)、#41(商品级列表)
  • 状态:验收通过
  • 日期:2026-08-08
  • Gitea 工单:#43
  • [注意] 全项目第一个做分页的页面,本工单定下的模式后面四个页面照抄

背景与目标

① 一次吐 3.4MB

实测导入真实样本后打开 /shopee:HTML 3.4 MB、表格 5195 行。

② 但只加分页会制造新问题

商品总数        5195
未填 PDD 链接   5195  (100%)
有待补规格      6

要找出那 6 个待补的商品,得翻 260 页。

页面上只有「商品 ID 搜索」——得先知道 ID 才能搜。但「哪些商品需要我处理」 这个问题,恰恰是不知道 ID 的时候才要问的。

只做分页,情况会从「5195 行糊在一起」变成「260 页里藏着 6 个」,两种都找不到。 所以分页和状态筛选一起做。

最终方案

每页 20 条,规范一起改

docs/admin/05 §3 原来写的是 [建议] 一页 50 条, 已改成 [必须] 五个模块统一每页 20 条并写明理由: 20 行在 1366×768 上正好一屏不用滚动。

不改规范的话,下一个人做顺运宝页会照着 50 做,五个页面又不一致。

分页在数据库做,COUNT 与列表共用筛选拼装

[必须] LIMIT ? OFFSET ?,不把 5195 行查出来再在 Go 里切片—— 这正是 3.4MB 的成因。

[必须] 总数用单独的 COUNT(*),与列表查询共用同一套筛选条件拼装函数。 分开写两份 WHERE,迟早有一天会忘了给 COUNT 也加条件,页码算错而且没人发现。 这条 #19 已经踩过一次。

状态条显示全量,不是本页

[必须]

✗  共 20 个商品                        ← 操作员以为总共就 20 个
✓  共 5195 个商品 · 第 1/260 页
✓  待补规格:6 个商品 · 第 1/1 页        ← 筛选后显示筛选结果总数

状态筛选四个取值

取值 SQL 条件
全部 —
待补规格 EXISTS (SELECT 1 FROM shopee_skus s WHERE s.goods_id = p.goods_id AND s.parse_ok = 0)
未填 PDD 链接 pdd_goods_url IS NULL OR pdd_goods_url = ''
已填链接 pdd_goods_url IS NOT NULL AND pdd_goods_url <> ''

[必须] 「待补规格」用 EXISTS,不用 JOIN + DISTINCT—— 一个商品有多个失败 SKU 时 JOIN 会出重复行,DISTINCT 又让 LIMIT/OFFSET 的行为难推理。

[必须] 筛选参数认不出来的一律当「全部」,不报错。

page 越界要兜住

  • page < 1 / 非数字 → 当作 1
  • page > 总页数 → 显示最后一页,不是空表格(空表格会让操作员以为数据没了)
  • 总数为 0 → 显示 第 1/1 页,不出现 第 1/0 页
  • 翻页保留当前筛选和关键词

分页控件是 <a href>

[必须] 纯 GET 导航,浏览器前进后退和书签都正常工作,不用 JS。

[必须] 首末页时对应按钮用 <span class="disabled"> 禁用—— 语义上不再是链接,且不只靠颜色区分。

与建单方案的差异

  1. 分页通用逻辑单独放 admin/service/pagination.go(工单预计文件表未列)。 工单要求「模式要给后面四页照抄」,独立文件让后面的页面能直接 import 复用, 不用从蝦皮专属文件里摘代码。
  2. PaginationView 用 FirstURL/PrevURL/NextURL/LastURL 字段, 而不是原计划的页码字段——见下面那个坑。

实现踩到并修掉的坑

html/template 的 URL 上下文转义。 最初写成:

<a href="?{{.BaseQuery}}&page={{.Next}}">

模板引擎把夹在字面量 & 中间的动态内容当成单个参数值整体转义, ? 和 = 变成 %3F / %3D,链接直接失效。

改为在 Go 里把整段 URL 拼好(PaginationURL),模板作为单个 pipeline 输出, 并加了回归测试 TestNewPaginationView_URL带上筛选条件 / TestPaginationURL_无筛选条件时只有page。

这种坑只有真跑起来看 HTML 才发现,读代码看不出来。

改了哪些

  • admin/repository/shopee.go:ShopeeFilter / shopeeFilterClause(共用拼装)/ ListShopeeProducts 加 LIMIT/OFFSET / CountShopeeProductsFiltered。
  • admin/service/pagination.go:新建。PageSize=20、ParsePage、ClampPage、 TotalPages、PaginationView、PaginationURL——供后面四页复用。
  • admin/service/pagination_test.go:新建。
  • admin/service/shopee_list.go:分页/筛选参数、状态选项、状态条文案。
  • admin/service/shopee_list_test.go:补分页与筛选用例。
  • admin/handler/web/shopee.go:读 page / status。
  • admin/templates/shopee/list.html:状态筛选下拉。
  • admin/templates/partials/footer.html:分页控件({{if .Pagination}}, 对没做分页的页面无影响)。
  • admin/static/css/app.css:.pagination。
  • docs/admin/05-ui-specification.md:§3 每页条数改 20; 新增 §3.2 分页通用规则(写在 §3 下,不是蝦皮页那一节);§4.1、§4.5。
  • docs/admin/01-requirements.md:§4.1 状态筛选说明。

验收结果

架构角色独立复跑,导入真实样本后逐条核对:

场景 实测结果
/shopee 16,693 字节 / 20 行 / 共 5195 个商品 · 第 1/260 页
?page=9999 13,565 字节 / 15 行有数据 / 第 260/260 页
?page=abc 第 1 页,不报错
?status=pending_spec 7,410 字节 / 6 行 / 待补规格:6 个商品 · 第 1/1 页
?status=no_link 未填 PDD 链接:5195 个商品 · 第 1/260 页
?status=zzz(乱填) 退回「全部」,20 行,不报错
空库 共 0 个商品 · 第 1/1 页(不是 第 1/0 页)
首页时按钮 <span class="disabled">首页 / 上一页
分页链接 ?status=no_link&amp;page=3,含 %3F/%3D 0 次
分页规则位置 05 §3.2(通用小节)
每页条数 05 §3 已改为「五个模块统一每页 20 条」并写明理由

HTML 大小 3.4 MB → 16.7 KB。

测试

架构角色亲自执行,用 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.025s
    ok  cmautobuy/admin/repository 0.680s
    ok  cmautobuy/admin/service    2.018s

变异测试(工单未要求,架构角色补做)

变异 结果
COUNT 不再套用筛选条件(页码算错) 2 个用例变红
page 越界不再兜到最后一页 2 个用例变红

第一个是 #19 踩过的老坑,现在有测试守着了。

[注意] 第二个变异架构角色第一次用正则打偏了(没匹配到), 显示「跳过」。看清 ClampPage 的实际写法后重打才生效。 探针无效和代码有问题是两回事,必须先确认变异真的改变了行为—— 这个教训 #38 刚犯过一次。

未验证到的部分:

  • 浏览器里的真实交互:分页链接点击、前进后退、书签, 只验证了生成的 HTML 结构和禁用状态的 DOM 差异 (<span class="disabled"> vs <a>),没在真实浏览器里点过。
  • 1366×768 下分页控件的视觉效果未做走查。
  • 未跑并发场景(纯只读查询,理论上不涉及并发写入)。

用户已实机确认并验收通过。

遗留问题

  1. 另外四个页面(PDD / 顺运宝 / 采集采购 / 客户端)尚未接入分页。 通用逻辑已放在 service/pagination.go,规则已写进 05 §3.2, 后续各自开工单接入即可。
  2. 浏览器实机交互需人工确认。

相关提交

  • edfe39c feat: 蝦皮数据页分页与状态筛选 (#43)