# 05 Admin 界面规范
- 文档状态:基线草案,待界面评审
- 技术栈:Go `html/template` 服务端渲染 + 手写 CSS + 原生 JS / htmx
本文档中 `[必须]` / `[建议]` / `[待定]` 的含义见 [文档索引](../README.md#文档标注说明)。
没有标注的默认是 `[必须]`。看不懂的词查 [术语表](00-glossary.md)。
## 1. 设计目标
- 五个模块**长得一样**,写完第一个,其余四个照抄结构改字段;
- 操作员一眼看出"这条数据卡在哪一步、下一步该点什么";
- 不引入前端框架和构建流程;
- 破坏性操作(删除、导入)必须有确认,不能手滑。
## 2. 统一的三段式布局
`[必须]` 五个模块全部用这个结构,**不要给某个模块搞特殊**:
```text
┌────────────────────────────────────────────────────────────┐
│ [导航] 蝦皮数据 │ PDD 商品 │ 顺运宝数据 │ 采集采购 │ 客户端 │
├────────────────────────────────────────────────────────────┤
│ 顶部工具条:[操作按钮…] [搜索框] [清除] [搜索] [删除] │
├────────────────────────────────────────────────────────────┤
│ ☐ │ 列1 │ 列2 │ 列3 │ … │
│ ☐ │ │ │ │ │
│ 数据表格(占满剩余高度) │
│ │
├────────────────────────────────────────────────────────────┤
│ 底部状态条:最近一次操作的结果和关键统计 │
└────────────────────────────────────────────────────────────┘
```
`[必须]` 三段做成 `templates/partials/` 里的公共片段复用,
**不要每个页面复制一份**——改一次样式要改五处,必然改漏。
`[必须]` 页面在 1366×768 上可用。列多了让表格**横向滚动**,不要压缩列宽把字挤成两行。
`[必须]` 公共弹窗可以通过关闭按钮、取消按钮、Esc 或直接点击灰色遮罩关闭;
但从输入框或弹窗内容开始拖选、在遮罩上松开时不得关闭。遮罩关闭逻辑必须同时确认
指针按下和最终点击都发生在遮罩本身,避免误丢用户尚未提交的输入。
### 2.1 跨模块导航状态
顶部导航切换六个主模块时,应在当前浏览器标签页内记住每个模块最后一次稳定的
搜索、筛选和页码。返回模块时使用带真实查询参数的 URL,因此 F5、复制链接以及
浏览器前进、后退仍按浏览器原生行为工作。
- 状态按登录账号隔离,退出登录时清理当前账号在本标签页的记录。
- 只保存模块主列表的白名单查询参数;清除筛选后,模块根地址会覆盖旧记录。
- 不保存复选框、弹窗、临时成功或错误提示、未提交表单和敏感信息。
- 不跨标签页、浏览器或设备同步;浏览器存储不可用时,导航退回模块根地址。
- 导航链接本身必须保留可用的模块根地址,不能依赖 JavaScript 才能进入页面。
## 3. 表格通用规则
- `[必须]` 第一列是勾选框,表头有全选。
- `[必须]` 行的身份用**业务主键**(商品規格ID、货运单ID、任务编号、序列号),
**不得用行号**——排序和筛选一变行号就错位。
- `[必须]` 批量删除要二次确认,弹窗里写清**删几条、能不能恢复**。
硬删写"将删除 N 条,不可恢复";软删除的(如 PDD 商品)要说明恢复方式和会丢什么,
见 §5.6。含糊其辞和吓唬人一样糟——两种都会让操作员不敢动手。
- `[必须]` 删除、导入用 **POST**,不得用 GET。浏览器和插件会预取 GET 链接。
- `[建议]` 默认按 `更新时间 DESC` 排序。
- `[必须]` 默认每页 **20** 条,可选 50 或 100 条,超过分页,见 §3.2。
搜索走数据库,不要一次查出来在内存里过滤。
- `[必须]` 长文本(商品标题)截断显示,鼠标悬停给完整内容。
- `[必须]` 空状态要分情况,文案不能都是"暂无数据":
- 从没导入过 → "还没有数据,点左上角『导入』开始"
- 搜索无结果 → "没有匹配的记录" + 清除条件入口
### 3.1 搜索框宽度
`[必须]` 搜索框加 `class="search-narrow"` 收到 30%,让「搜索」按钮紧挨着它。
目前 **PDD 商品页、采集采购页**在用;
蝦皮数据页和客户端列表页仍是撑满的(暂未统一)。
顺运宝页一行同时有店铺和订单号两个输入框,单独使用 `syb-search-input`,桌面端
固定约 140px;不能为了这个例外修改通用输入框规则。
`[必须]` **不要去改 `.toolbar input[type="text"]` 那条通用规则**——
五个页面共用它,改了会把没打算改的页面一起改掉。
`[必须]` 搜索表单上的 `grow` 类**要保留**,它负责把「删除」按钮顶到最右侧。
去掉的话删除按钮会跑到搜索按钮旁边。
`[必须]` **不要额外加对齐样式**(`margin-right: auto`、`justify-content` 之类)。
输入框不再伸展之后,多余空间自然留在按钮之后,按钮就贴着输入框了;
额外加会和 `grow` 叠加出难预料的结果。
`[建议]` placeholder 要短。收到 30% 之后长文案显示不全——
真装不下就缩短文案,**不要把宽度调回去**。
`[必须]` 有搜索或筛选条件的主列表,在“搜索”按钮前提供常驻“清除”入口。清除后移除
该列表的关键词、状态、类型、创建人等普通查询条件,省略 `page` 回到第 1 页,同时保留
归一化后的 `page_size`。回收站、未登记商品等独立查看范围不是普通搜索条件,清除时应
继续保留,避免操作员意外离开当前范围。该入口使用普通 GET 链接,不依赖 JavaScript。
### 3.2 分页
`[必须]` 使用公共分页的列表(蝦皮、PDD、顺运宝、采集采购、客户端、用户、
商品目录导入记录、档口入库码)统一提供 **20 / 50 / 100 条**三档,默认 100。
默认 100 便于集中查看和批量勾选;20 和 50 用于降低单页密度,最大值
固定为 100,不提供“全部”或任意自定义数量。顺运宝同步记录是紧凑弹窗,继续固定
每页 10 条;顺运宝上游接口的抓取页容量也不是这里的界面设置。
蝦皮数据页(§4)是全项目第一个做分页的页面,本节是它定下的通用规则,
其余主列表复用公共实现,不要各写一套:
- `[必须]` 分页用 SQL 的 `LIMIT ? OFFSET ?`,**不得**把全量查出来在 Go 里
切片——这正是蝦皮数据页改之前一次吐 3.4MB HTML 的成因(实测 5195 行)。
- `[必须]` 总数用**单独的 `COUNT(*)`** 查询,并且和列表查询**共用同一套
筛选条件拼装函数**。分开写两份 `WHERE` 迟早会漏改一处,页码跟着算错,
而且不会报错、不容易发现(#19 已经踩过一次)。
- `[必须]` 页码参数是 `?page=N`,从 **1** 开始,不是 0——给操作员看的东西
不要 0-based。
- `[必须]` 每页条数参数是 `?page_size=N`,只接受 20、50、100;缺失、非数字或
其他值都回退为 100,不能把用户输入直接交给 SQL `LIMIT`。
- `[必须]` 修改每页条数时回到第 1 页;筛选、翻页、写操作跳转、F5、浏览器
前进后退和模块状态恢复都保留归一化后的 `page_size`。
- `[必须]` 选择 20、50、100 后立即提交 GET 表单并刷新,不显示重复的“应用”按钮。
自动提交要显示加载状态并防重复;JavaScript 不可用时仍可使用已有分页链接,
只是不能切换每页条数。
- `[必须]` 越界要兜住:
- `page` 非数字、`0`、负数 → 当作第 1 页;
- `page` 超过总页数 → 显示**最后一页**(有数据的那页),不是空表格;
- 总数为 0 → 显示"第 1/1 页",不出现"第 1/0 页"。
- `[必须]` 底部状态条显示**全量总数**(当前筛选条件下的总数),不是本页
行数——"共 20 个商品"会让操作员以为总共就 20 个。筛选后显示筛选结果的
总数,例如"待补规格:6 个商品 · 第 1/1 页",不是本页凑出来的数字。
- `[必须]` 翻页时**保留当前筛选和关键词**——用查询参数原样带过去,
丢了的话操作员翻到第二页筛选就没了,会以为数据变了。
- `[必须]` 分页控件用 ``,**不要用 JS**。这是纯 GET 导航,
浏览器的前进后退和书签都该正常工作。
- `[必须]` 第一页时"首页 / 上一页"禁用,最后一页时"下一页 / 末页"禁用;
禁用要有**视觉 + 语义**区分(不可点的元素不要还是 ``),不能只靠颜色。
- `[必须]` 固定底部状态条把状态提示和分页组件作为同一组整体靠右,状态提示
位于分页左侧;窄窗口可以换成上下两行,两行仍分别靠右,且不得遮住状态文字、
分页按钮或表格最后一行。
- `[必须]` 表格勾选只代表当前页。翻页后不保留勾选,也不提供跨页批量操作,
避免操作员看不到将被处理的记录。
- `[必须]` 每页条数只控制读取和展示,不能充当外部写操作的安全上限。档口入库码
只读匹配允许处理当前页全部选中记录,内部每次最多读取 100 个顺运宝货运单;
回写允许提交当前页全部选中记录,页面立即返回。后台每次最多从数据库读取 20 条,
但远端仍逐条执行写前核验、最多一次写入和写后复核。
- `[必须]` 档口入库码后台回写进度使用文字和原生 `