Commit 4d4d51c8796b5a15b5632bcd2f912c42ca2baac4

Authored by zichun
1 parent 4c3d8325

docs(spec:FE-03): 派生规格

docs/superpowers/specs/2026-06-01-FE-03.md 0 → 100644
  1 +# FE-03 用户列表与查询 — 实现规格(前端)
  2 +
  3 +> 阶段:前端(frontend)。作用域限定 `frontend/` 下的页面 / 组件 / 路由 / store / api / 样式。
  4 +> SSoT 引用:需求卡片 `docs/01-需求清单/USR-用户管理/REQ-USR-003.md`;原型 `prototype/erp.html`(`<section id="screen-userlist">` 区域:`.toolbar` / `.filterbar` / `.table-shell .grid-table#user-table` / `.pager`,布局与交互权威);API 契约 `docs/05-API接口契约.md` § REQ-USR-003(`GET /api/usr/users`);技术规范 `docs/04-技术规范.md` § 零(技术栈)/ § 2.1 目录约定 / § 2.3 接口通信 / § 2.4 状态与错误 / § 3.2 分页查询;Design Tokens `src/styles/tokens.css`;承载外壳 FE-02 规格 `docs/superpowers/specs/2026-06-01-FE-02.md`(`AppLayout` + 标签栈 + `RequireAuth` 守卫);登录态/请求基建 FE-01 规格 `docs/superpowers/specs/2026-06-01-FE-01.md`(`authSlice` + `request.ts` + token 持久化键 `xly_erp_token`)。
  5 +> 本规格只消费已锁定事实。查询条件解析、文本模糊/精确匹配、分页越界回退、密码与敏感字段过滤等业务逻辑全部在后端(见 REQ-USR-003 后端规格),前端只负责采集筛选条件、发起分页查询、依据响应渲染表格 / 分页 / 空态 / 错误态。本 FE 为**只读查询**,不产生任何写副作用。
  6 +
  7 +---
  8 +
  9 +## 1. 关联 REQ + 关联原型
  10 +
  11 +| 维度 | 内容 |
  12 +|---|---|
  13 +| 业务功能 | FE-03 用户列表与查询(工具栏刷新/导出 + 筛选条件 + 用户表格 + 分页,对接 `GET /api/usr/users`) |
  14 +| 关联 REQ | REQ-USR-003 查询用户(主)。输入表 1:查询字段(下拉单选,默认「用户名」)/ 匹配方式(下拉单选,默认「包含」)/ 查询值(手工输入,空为全部);输出表 1:序号 / 用户名 / 员工名 / 用户号 / 部门 / 用户类型 / 语言 / 作废 / 登录日期 / 制单人 / 制单日期 |
  15 +| 关联原型 | `prototype/erp.html` → `<section id="screen-userlist">`:`.toolbar`(刷新 / 新增 / 导出Excel / 设置齿轮)、`.filterbar`(下拉 + 文本框 + 搜索 / 清空)、`.table-shell > table.grid-table#user-table`(表头 + `tbody#user-tbody`)、`.pager`(统计文案 + 上/下页 + 当前页 + 每页条数下拉) |
  16 +| 路由 | `/usr/users`(React Router v6,受保护区子路由,由 FE-02 `AppLayout` + `RequireAuth` 包裹)。从 FE-02 主页「常用操作 > 用户列表」或导航总览「用户管理 > 用户列表 ★」进入并在顶栏打开「用户列表」标签(标签栈逻辑属 FE-02) |
  17 +| 落地组件目录 | 页面 `frontend/src/pages/usr/UserList/`(`index.tsx` + 子组件 `UserToolbar.tsx` / `UserFilterBar.tsx` / `UserTable.tsx`);接口走 `frontend/src/api/usrApi.ts`(新增 `listUsers` 方法)+ `frontend/src/api/request.ts`(FE-01 已建);类型集中 `frontend/src/api/types.ts`(`UserVO` / `PageResult<T>` / `UserListQuery`);列表查询态就近用页面 hook(不进全局 store,见 § 8 决策 D6) |
  18 +
  19 +> 原型 `#screen-userlist` 为纯静态 HTML + 内联 demo 脚本:`users` 数组直接渲染 `tbody`、`filterbar` 的下拉/输入无真实查询、`.pager` 文案为写死 demo(「共37个单据 共37条记录」「10000 条/页」)、行 `dblclick` 调 `goTo('userdetail')` 切到用户单据屏、工具栏「新增」`data-add-user` 切到新增单据。本规格按 React + AntD 5 复刻其**布局与交互语义**,但表格数据、筛选、分页改为真实对接 `GET /api/usr/users`;「新增」「行双击进单据」为跳转到 FE-04 的导航动作(目标页内容属 FE-04,本页只发起 `navigate`)。
  20 +
  21 +---
  22 +
  23 +## 2. 组件树(按区域分块,推导自 prototype DOM)
  24 +
  25 +页面根 `UserListPage`(路由 `/usr/users` 挂载,渲染于 FE-02 `AppLayout` 的 `<Outlet/>` 内),纵向结构对应原型 `#screen-userlist`(工具栏 / 筛选栏 / 表格壳 / 分页栏):
  26 +
  27 +```
  28 +UserListPage(页面容器,对应 #screen-userlist:纵向布局,内容区填充外壳 Outlet)
  29 +├── UserToolbar(对应 .toolbar:深色工具条,左侧动作按钮 + 右侧设置齿轮占位)
  30 +│ ├── BtnRefresh(对应 .toolbar 刷新:重新拉取当前查询条件 + 当前页,见 § 5 BR8)
  31 +│ ├── BtnAdd(对应 .tb-btn#btn-add[data-add-user]:跳转 FE-04 新增用户 `/usr/users/new`;本页仅 navigate)
  32 +│ ├── BtnExportExcel(对应 .toolbar「导出Excel」:导出当前查询结果,见 § 5 BR9 / § 8 决策 D5)
  33 +│ └── GearSetting(对应 .toolbar .gear「⚙」:列设置占位,无后端,见 § 8 决策 D7)
  34 +├── UserFilterBar(对应 .filterbar:查询条件行)
  35 +│ ├── ScopeSelect(对应首个 <select>「全部用户」:用户范围下拉,原型 demo 项,见 § 8 决策 D2 —— 默认不映射后端参数)
  36 +│ ├── QueryFieldSelect(对应第 2 个 <select>「用户名」:查询字段下拉单选,options=用户名/员工名/用户号/部门/用户类型/作废/登录日期/制单人,默认「用户名」→ 提交 queryField)
  37 +│ ├── MatchTypeSelect(对应第 3 个 <select>「包含」:匹配方式下拉单选,options=包含/不包含/等于,默认「包含」→ 提交 matchType)
  38 +│ ├── QueryValueInput(对应 .filterbar <input>:查询值文本框,空为查询全部 → 提交 queryValue)
  39 +│ ├── FilterMoreToggle(对应 .filterbar .down「▾」:更多条件占位,见 § 8 决策 D3)
  40 +│ ├── BtnSearch(对应 .filterbar .btn「搜索」:以当前条件回到第 1 页发起查询,见 § 5 BR7)
  41 +│ └── BtnClear(对应 .filterbar .btn.ghost「⊗ 清空」:重置筛选为默认值并回到第 1 页全量查询,见 § 5 BR10)
  42 +├── UserTable(对应 .table-shell > table.grid-table#user-table:可横向滚动用户表格)
  43 +│ ├── RadioColumn(对应 thead 首列空 + tbody .radio-cell .radio-dot:行选择单选标记,见 § 8 决策 D8)
  44 +│ ├── 列:序号 / 用户名 / 员工名 / 用户号 / 部门 / 用户类型 / 语言 / 作废 / 登录日期 / 制单人 / 制单日期(对应 REQ 输出表 1 + 原型 thead)
  45 +│ └── Row[](对应 tbody#user-tbody 各 <tr>;双击行 → 跳转 FE-04 修改单据 `/usr/users/:id`,见 § 5 BR12)
  46 +└── UserPager(对应 .pager:右对齐分页条)
  47 + ├── PageSummary(对应「共 N 条记录」统计文案,来源 PageResult.total)
  48 + ├── Pager(对应 ‹ / 当前页 / › :上一页 / 当前页 / 下一页)
  49 + └── PageSizeSelect(对应 .pager <select>「条/页」:每页条数下拉,见 § 8 决策 D4)
  50 +```
  51 +
  52 +- 控件选型(依据 `docs/04 § 零` `frontend.ui_lib = Ant Design 5.x`):
  53 + - 工具栏 → 自定义深色 `div` 条 + AntD `Button`(`type="text"`,白字图标按钮,复刻原型 `.tb-btn`),「刷新」配 `ReloadOutlined`、「新增」`PlusCircleOutlined`、「导出Excel」`FileExcelOutlined`、设置 `SettingOutlined`。
  54 + - 筛选栏 → AntD `Select`(查询字段 / 匹配方式 / 用户范围,单选)+ `Input`(查询值,支持回车触发搜索 `onPressEnter`)+ `Button`(搜索 `type="primary"` 配 `SearchOutlined` / 清空 `type="default"`)。可用 AntD `Form` 承载(`layout="inline"`),亦可受控本地态,取舍登记 § 8 D6。
  55 + - 用户表格 → AntD `Table`(`rowKey="id"`,`columns` 对应输出列,服务端分页 `pagination` 受控;`loading` 绑加载态;`scroll={{ x }}` 复刻原型横向滚动 `white-space:nowrap`;行 `onRow.onDoubleClick` → 跳 FE-04)。「作废」列用 AntD `Tag` 或只读 `Checkbox`(`iIsVoid` 0/1 → 否/是,复刻原型 `tbody` 复选框单元,本页只读不可勾,见 § 5 BR6)。
  56 + - 分页 → 用 AntD `Table` 内置 `pagination`(受控 `current`/`pageSize`/`total`/`showSizeChanger`/`showTotal`),复刻原型 `.pager`「共 N 条记录 + 上/下页 + 当前页 + 每页条数」;不另造分页组件。
  57 + - 行选择标记 → AntD `Table` 的 `rowSelection={{ type:'radio' }}`(单选,复刻原型 `.radio-dot`),选择仅用于「双击/选中后进单据」语义,不参与查询(见 § 8 D8)。
  58 +- 页面在受保护区内渲染(无 token 由 FE-02 `RequireAuth` 拦截重定向,本页不重复守卫)。顶栏 / 标签栈属 FE-02,本页只提供内容区。
  59 +
  60 +---
  61 +
  62 +## 3. 页面状态机(≥5 态)
  63 +
  64 +列表查询态以「首次加载 / 加载中 / 正常有数据 / 空结果 / 错误 / 导出中」表达;查询参数(queryField/matchType/queryValue/pageNum/pageSize)由页面 hook 持有,任一变更触发取数:
  65 +
  66 +| 状态 | 触发时机 | UI 表现 |
  67 +|---|---|---|
  68 +| `idle/initialLoading`(首次加载 / loading) | 页面挂载即以默认条件(queryField=用户名、matchType=包含、queryValue 空、pageNum=1、pageSize=见 § 8 D4)调 `GET /api/usr/users`(REQ 输入表「预加载=页面加载时」) | 表格区 AntD `Table` 置 `loading`(骨架 / `Spin` 遮罩),分页与工具栏可见但表体显加载占位;筛选栏可操作 |
  69 +| `loading`(查询/翻页/刷新进行中) | 点击「搜索」/「清空」/「刷新」/ 切页 / 改每页条数后重新取数 | `Table` `loading=true`,保留上次行直至新数据返回(避免闪烁);「搜索」「刷新」按钮置 `loading`/禁用防重复提交 |
  70 +| `success`(正常有数据) | 接口返回 `code=0` 且 `data.records` 非空 | 渲染表格行(序号按当前页 `(pageNum-1)*pageSize + index + 1` 生成,见 § 5 BR1);分页显示真实 `total`/`pageNum`/`pageSize`;统计文案「共 {total} 条记录」 |
  71 +| `empty`(无匹配结果) | 接口返回 `code=0` 且 `data.records` 为空数组(含「条件无匹配」与「全量为空」) | `Table` 渲染 AntD `Empty`「暂无匹配的用户」占位,**不报错**(REQ 验收:无匹配返回空列表而非报错);分页 `total=0`,统计「共 0 条记录」 |
  72 +| `error`(查询失败) | 接口返回非 0 `code`(40001/42201)或网络/超时/5xx | 表格区显错误占位(AntD `Result`/`Empty` + 「加载失败,点击重试」)+ `message.error` 文案(见 § 4);保留筛选条件可重试;被动 401 由 `request.ts` 拦截器统一跳 `/login`(docs/04 § 2.4,不在本页处理) |
  73 +| `exporting`(导出 Excel 进行中) | 点击「导出Excel」 | 「导出Excel」按钮置 `loading` 并禁用;完成后 `message.success("导出成功")` / 失败 `message.error("导出失败")`(导出实现见 § 8 D5) |
  74 +
  75 +> 状态以页面本地 hook(`useState`/自定义 `useUserList`)表达:`{ list, total, loading, error, query:{queryField,matchType,queryValue,pageNum,pageSize}, exporting }`;不进全局 `store`(列表数据为页面就近态,docs/04 § 2.2「跨页面共享的才进 store」,见 § 8 D6)。`authResolving`/`unauthenticated` 由 FE-02 守卫承担,本页不重复建态。
  76 +
  77 +---
  78 +
  79 +## 4. 消费的后端端点(对齐 docs/05)
  80 +
  81 +| 端点 | 方法 | 触发时机 | 请求参数 | 成功响应 | 失败处理 |
  82 +|---|---|---|---|---|---|
  83 +| `/api/usr/users` | GET | 页面挂载(默认条件)/ 点击搜索 / 清空 / 刷新 / 切页 / 改每页条数 | query:`{ queryField?(用户名/员工名/用户号/部门/用户类型/作废/登录日期/制单人,默认用户名), matchType?(包含/不包含/等于,默认包含), queryValue?(空为全部), pageNum(默认1), pageSize(默认10,最大100) }`(对齐 docs/05 § REQ-USR-003) | `Result<PageResult<UserVO>>`,`code=0`;`PageResult={ records:UserVO[], total, pageNum, pageSize }`;`UserVO={ id, sUserName, 员工名, sUserNo, 部门, sUserType, sLanguage, iIsVoid, tLastLoginDate, sCreator, tCreateDate }`(密码与敏感字段后端不返回) | 非 0 `code` 弹 `message.error`(见错误码表);表格回退到空/错误占位 + 重试 |
  84 +
  85 +请求 / 响应约定(依据 `docs/04 § 2.3 / § 2.4 / § 3.2`):
  86 +- 统一走 `frontend/src/api/request.ts` 的 Axios 实例(FE-01 已建:`baseURL=/api`,开发期经 Vite proxy 转发到后端 `http://localhost:5172`,端口取 `config-vars.yaml backend.http_port=5172`;请求拦截器注入 `Authorization: Bearer <token>`,token 取 localStorage 键 `xly_erp_token`)。
  87 +- 响应拦截器统一拆 `Result`:`code=0` 取 `data`;非 0 `code` 抛业务错误并弹 `message.error`;`401` 统一跳 `/login`(docs/04 § 2.4)。本页方法集中在 `usrApi.ts` 的 `listUsers(query): Promise<PageResult<UserVO>>`,页面不直接散用 axios(docs/04 § 2.3)。
  88 +- 分页对齐 docs/04 § 3.2:`pageNum`/`pageSize` 传后端,回显 `PageResult` 的 `total`/`pageNum`/`pageSize`;文本条件模糊匹配、枚举/外键条件精确匹配、空条件全量分页均由后端裁决(前端不预判匹配语义,原样传 `matchType`)。
  89 +
  90 +#### 错误码表(对齐 docs/05 § REQ-USR-003)
  91 +
  92 +| code | 含义 | 前端文案 | 处理 |
  93 +|---|---|---|---|
  94 +| `0` | 成功 | —(渲染结果或空态) | 正常渲染 |
  95 +| `42201` | 分页参数非法(pageNum<1 或 pageSize 超上限 100) | 「分页参数有误,已重置为第 1 页」 | `message.warning` + 前端把分页重置为合法值(pageNum=1,pageSize 收敛至 ≤100)后重查;正常情况下前端分页组件已约束,此为兜底 |
  96 +| `40001` | 查询参数校验失败 | 「查询条件有误,请检查后重试」 | `message.error` + 保留条件不自动重查 |
  97 +| 网络/超时/5xx | 请求异常 | 「加载失败,请稍后重试」 | 响应拦截器兜底 `message.error` + 表格错误占位「点击重试」 |
  98 +| `401` | 登录失效(被动) | 「登录已失效,请重新登录」 | 由 `request.ts` 拦截器统一跳 `/login`(docs/04 § 2.4),本页不单独处理 |
  99 +
  100 +---
  101 +
  102 +## 5. 业务规则前端复刻清单(逐条)
  103 +
  104 +| # | 规则 | 触发时机 | 前端报错 / 反馈文案 | 来源 |
  105 +|---|---|---|---|---|
  106 +| BR1 | 「序号」列为系统生成的连续行号,按当前分页计算 `(pageNum-1)*pageSize + 行内索引 + 1` | 每次渲染表格行 | —(纯展示,无报错) | REQ-USR-003 输出表 1「序号 = 系统生成」;原型 `renderUsers` 用 `i+1` |
  107 +| BR2 | 查询字段默认「用户名」、匹配方式默认「包含」、查询值默认空 | 页面挂载初始化筛选栏 | —(默认值预填,对应原型下拉默认选中项) | REQ-USR-003 输入表 1「默认值=用户名 / 包含」「预加载=页面加载时」 |
  108 +| BR3 | 查询值为空时按「选择全部」处理(不传或传空 `queryValue`,返回全量分页) | 搜索 / 清空 / 默认加载时 queryValue 为空 | —(无报错,全量分页) | REQ-USR-003 输入表 1「查询值…空为选择全部」+ docs/04 § 3.2「空条件返回全量分页」 |
  109 +| BR4 | 查询字段与匹配方式取值受限于固定枚举(字段:用户名/员工名/用户号/部门/用户类型/作废/登录日期/制单人;匹配:包含/不包含/等于) | 渲染下拉 options | —(`Select` 仅提供合法选项,无自由输入) | REQ-USR-003 输入表 1「显示来源」枚举列表 |
  110 +| BR5 | 查询为只读,不产生写副作用 | 任何查询 / 翻页 / 刷新 | —(仅 GET,不改任何数据) | REQ-USR-003 边界「查询为只读,不产生写副作用」 |
  111 +| BR6 | 「作废」列只读展示 `iIsVoid`(0=否 / 1=是),不可在列表中编辑勾选 | 渲染「作废」单元 | —(只读 `Tag`/禁用 `Checkbox`,点击无效) | REQ-USR-003 输出表 1「作废=布尔」;原型 `tbody` 复选框为 demo 展示(本页只读) |
  112 +| BR7 | 点击「搜索」以当前筛选条件回到第 1 页发起查询 | 点击「搜索」或查询值框回车 | —(成功渲染 / 失败按 § 4 文案) | 原型 `.filterbar .btn`「搜索」;docs/04 § 3.2 分页查询 |
  113 +| BR8 | 点击「刷新」以**当前已生效的查询条件 + 当前页**重新取数(不重置条件,不回第 1 页) | 点击工具栏「刷新」 | —(刷新成功 / 失败按 § 4) | 原型 `.toolbar` 刷新图标语义(重载当前视图) |
  114 +| BR9 | 「导出Excel」导出**当前查询条件命中的结果**(非仅当前页),导出过程禁用按钮 | 点击「导出Excel」 | 成功 `message.success("导出成功")` / 失败 `message.error("导出失败")`(实现见 § 8 D5) | 原型 `.toolbar`「导出Excel」 |
  115 +| BR10 | 点击「清空」重置查询字段=用户名、匹配方式=包含、查询值=空、范围=全部,回到第 1 页全量查询 | 点击「清空」 | —(重置后自动重查全量) | 原型 `.filterbar .btn.ghost`「⊗ 清空」 |
  116 +| BR11 | 分页参数变更(切页 / 改每页条数)即重新取数;改每页条数回到第 1 页 | 点击上/下页、选每页条数 | —(成功渲染) | docs/04 § 3.2 分页;AntD `Table` 受控分页惯例 |
  117 +| BR12 | 双击表格行 → 跳转 FE-04 修改用户单据(`/usr/users/:id`,携该行 `id`) | 双击任一数据行 | —(`navigate('/usr/users/'+row.id)`,目标内容属 FE-04) | 原型 `tr.addEventListener('dblclick', ()=>goTo('userdetail'))` |
  118 +| BR13 | 「新增」→ 跳转 FE-04 新增用户单据(`/usr/users/new`) | 点击工具栏「新增」 | —(`navigate('/usr/users/new')`,目标内容属 FE-04) | 原型 `.tb-btn#btn-add[data-add-user]` → `setUserDetailMode('new')` + `openTab('userdetail')` |
  119 +| BR14 | 无匹配结果时展示空态而非错误 | 接口返回空 `records` | AntD `Empty`「暂无匹配的用户」(无 `message.error`) | REQ-USR-003 验收「无匹配时返回空列表而非报错」 |
  120 +| BR15 | 分页越界由后端回退到最后一页,前端按响应回显实际 `pageNum` | 请求的 `pageNum` 超出总页数 | —(前端以响应 `PageResult.pageNum` 同步分页当前页,不报错) | REQ-USR-003 验收「分页参数越界时返回最后一页」(前端信任后端回显) |
  121 +
  122 +> 本页只做**条件采集 + 分页查询 + 只读展示 + 导航跳转**;查询匹配语义、敏感字段过滤、越界回退等真伪裁决全部由后端在 `GET /api/usr/users` 内完成,前端不复制后端逻辑、不预判结果、不杜撰端点。
  123 +
  124 +---
  125 +
  126 +## 6. 列定义与字段映射(对齐 REQ 输出表 1 + UserVO)
  127 +
  128 +| 列(中文表头,对应原型 thead / REQ 输出表 1) | 数据字段(UserVO) | 渲染说明 |
  129 +|---|---|---|
  130 +| 序号 | —(前端生成) | `(pageNum-1)*pageSize + index + 1`(BR1) |
  131 +| 用户名 | `sUserName` | 文本,可作为「双击进单据」主标识 |
  132 +| 员工名 | `员工名`(来自职员表关联) | 文本,可空(原型有空值行如 `admin`/`李丹`) |
  133 +| 用户号 | `sUserNo` | 文本,可空 |
  134 +| 部门 | `部门`(来自职员表关联) | 文本,可空 |
  135 +| 用户类型 | `sUserType` | 文本(普通用户 / 超级管理员,已为中文) |
  136 +| 语言 | `sLanguage` | 文本(中文 / 英文 / 繁体) |
  137 +| 作废 | `iIsVoid` | 只读布尔:0→否、1→是(BR6,`Tag`/禁用 `Checkbox`) |
  138 +| 登录日期 | `tLastLoginDate` | 日期时间文本,可空(原型有空值,如 `lzj` 行) |
  139 +| 制单人 | `sCreator` | 文本 |
  140 +| 制单日期 | `tCreateDate` | 日期时间文本 |
  141 +
  142 +> 列顺序与表头文案以原型 `#user-table` thead + REQ 输出表 1 为准(原型表头「作」为「作废」的窄列简写,本规格列名用「作废」全称)。`员工名`/`部门` 在 docs/05 `UserVO` 中以中文键名给出(后端关联职员表派生字段),前端类型按响应实际键名映射(见 § 8 D9)。
  143 +
  144 +---
  145 +
  146 +## 7. Design Tokens 引用清单(`src/styles/tokens.css`,仅 `var(--color-*)`)
  147 +
  148 +> 约束:组件样式只用 `var(--color-*)`,禁止硬编码 hex/rgba;色值冲突时 `tokens.css` 优先于 `prototype/`(原型内联 `:root` 变量为 demo 私有,不作色值 SSoT)。AntD 主题色经 FE-02/FE-01 已配置的 `ConfigProvider` 对齐 `--color-primary`。
  149 +
  150 +| 用途 | Token | 备注 |
  151 +|---|---|---|
  152 +| 主操作(搜索按钮 / 当前页码高亮 / 链接强调) | `var(--color-primary)` | 对应原型 `.filterbar .btn` / `.pager .pgcur` 蓝;同时作为 AntD `colorPrimary` |
  153 +| 页面 / 内容区基础背景 | `var(--color-bg-base)` | 内容区浅灰底(外壳 Outlet 内) |
  154 +| 筛选栏 / 表格 / 分页栏背景(白) | `var(--color-form-bg-edit)` | 对应原型 `.filterbar` / `.grid-table` / `.pager` 白底(原型 `--panel` `#fff`) |
  155 +| 表头背景 | `var(--color-table-header-bg)` | 对应原型 `.grid-table thead th` 表头底(原型 `--header-bg`) |
  156 +| 表头文字 | `var(--color-table-header-fg)` | 列头文字 |
  157 +| 表格行文字 | `var(--color-table-row-fg)` | 用户名 / 部门等单元文字 |
  158 +| 行 hover 背景 | `var(--color-table-row-bg-hover)` | 行悬停高亮(对应原型 `.grid-table tbody tr:hover` 浅蓝,映射到行 hover token) |
  159 +| 选中行背景 | `var(--color-table-row-bg-selected)` | 单选行选中高亮(`rowSelection` 选中行,对应原型 `.radio-dot` 选中语义) |
  160 +| 表单输入框背景(查询值框 / 下拉) | `var(--color-form-bg-edit)` | 筛选栏 `Input`/`Select` 可编辑底(原型 `--field-bg`/白底映射到表单可编辑 token) |
  161 +| 下拉项 hover 背景 | `var(--color-form-bg-hover)` | `Select` 选项悬停(tokens 注「仅下拉框使用」) |
  162 +| 通用文字 / 统计文案 | `var(--color-text)` | 分页「共 N 条记录」、列内容文本 |
  163 +| 次要文字 / 占位 / 空态提示 | `var(--color-text-secondary)` | 空态「暂无匹配的用户」、占位图标(齿轮 / 更多) |
  164 +| 边框 / 分隔线 / 表格网格线 | `var(--color-border)` | 筛选栏下边线、表格单元边框、分页栏上边线(原型 `--border`) |
  165 +| 错误 / 失败提示 | `var(--color-error)` | `message.error`(查询失败 / 导出失败)、错误占位强调(原型 `--danger`) |
  166 +| 警告提示 | `var(--color-warning)` | `message.warning`(分页参数越界兜底 42201) |
  167 +| 成功提示 | `var(--color-success)` | `message.success("导出成功")` |
  168 +
  169 +> 顶栏 `.toolbar` 的深色底(原型 `--toolbar-bg:#2c2f36`)在 `tokens.css` 中**无对应语义 token**(与 FE-02 顶栏深色处理一致);本规格将用户列表工具条深色底作为**页面局部装饰样式**保留在 `UserList` 的 scoped 样式里(不新增全局 token、不挪用语义 token,与 FE-02 § 8 D9 一致,见 § 8 决策 D10)。该深色仅承载工具条容器视觉,不承载状态语义,不违反「语义色只用 token」约束。
  170 +
  171 +---
  172 +
  173 +## 8. 自主决策记录(decisions)
  174 +
  175 +| # | 问题 | 选择 | 依据 | 置信度 |
  176 +|---|---|---|---|---|
  177 +| D1 | 列表数据 / 筛选 / 分页是真实对接后端还是沿用原型静态 `users` 数组 | 真实对接 `GET /api/usr/users`,原型 `users` demo 数组仅作列结构参照,不写入前端 | docs/05 § REQ-USR-003 明确定义该分页查询端点;REQ-USR-003 为真实查询功能;原型静态数据是 demo,不应固化到生产代码 | high |
  178 +| D2 | 筛选栏首个下拉「全部用户」(原型 demo 项)映射到哪个后端参数 | 作为「用户范围」前端筛选项,默认「全部用户」时**不向后端传额外参数**(仅 queryField/matchType/queryValue + 分页参与查询);保留控件位以复刻原型布局 | docs/05 § REQ-USR-003 请求参数仅含 queryField/matchType/queryValue/pageNum/pageSize,无「范围」参数;原型「全部用户」为单项 demo(仅一个 `<option>`),无其他范围语义可锁;不杜撰后端不存在的参数 | high |
  179 +| D3 | 筛选栏「▾」更多条件按钮(原型 `.filterbar .down`)行为 | 视觉占位(高级筛选预留),点击不触发额外后端参数;MVP 仅复刻表 1 的 3 个查询输入 | REQ-USR-003 输入表 1 仅定义查询字段/匹配方式/查询值 3 项;原型 `.down` 无对应交互脚本(纯 demo);占位化是最小且不杜撰功能的处理 | medium |
  180 +| D4 | 每页条数 `pageSize` 默认值与可选项(原型写死「10000 条/页」) | 默认 `pageSize=10`(对齐 docs/05 默认),`showSizeChanger` 可选 `[10,20,50,100]`(上限对齐 docs/05 最大 100);不采用原型 demo 的 10000 | docs/05 § REQ-USR-003「pageSize 默认10,最大100」+ REQ 边界「单页最大条数受限(默认100)」;原型 10000 为 demo 写死值,超出后端上限 100,不可沿用 | high |
  181 +| D5 | 「导出Excel」如何实现(docs/05 无导出端点) | MVP 阶段前端导出:拉取当前查询条件命中的结果(按 `total` 在上限内一次或分批取),用前端库(如 `xlsx`/SheetJS)生成 .xlsx 下载;不杜撰后端导出端点 | docs/05 接口清单无 `/export` 端点,不得编造后端端点(硬约束);原型「导出Excel」为工具栏动作但无后端脚本;前端导出当前结果集是不依赖新端点的可行实现;若结果量超单次查询上限,按分页循环取(受 pageSize≤100 约束)。实现期如后端补导出端点可切换 | medium |
  182 +| D6 | 列表查询态放页面本地 hook 还是全局 store | 页面本地 hook(`useUserList`,含 list/total/loading/error/query),不进全局 `store` | docs/04 § 2.2「服务端数据优先就近在页面用 hooks 拉取,跨页面共享的才进 store」;列表数据仅本页用,无跨页共享需求 | high |
  183 +| D7 | 工具栏设置齿轮「⚙」(原型 `.gear`)行为 | 视觉占位(列显隐/偏好设置预留),无后端动作 | 原型 `.gear` 无交互脚本(demo);docs/05 无对应端点;占位化最小侵入 | medium |
  184 +| D8 | 表格行选择标记(原型 `.radio-dot` 单选圆点)是否参与查询 | 用 AntD `rowSelection={{type:'radio'}}` 复刻单选语义,仅用于「选中行 → 可进单据」交互,不参与查询条件、不向后端传选中项 | 原型行选择为单选圆点(无多选/批量动作脚本);REQ-USR-003 查询参数无「选中行」语义;选择仅服务于进入 FE-04 单据,不污染查询 | medium |
  185 +| D9 | `UserVO` 中 `员工名`/`部门` 为中文键名,前端类型如何映射 | 前端 `UserVO` 类型按 docs/05 响应实际键名定义(`员工名`/`部门` 作为对象键,或在 api 层做一次到 `employeeName`/`departmentName` 的别名映射后供组件用);列渲染以 docs/05 给定键名为准 | docs/05 § REQ-USR-003 `UserVO` 显式列出中文键名(后端关联职员表派生);前端不擅自改后端响应键名,按契约消费;是否做内部别名属实现细节,列渲染语义不变 | medium |
  186 +| D10 | 工具栏深色底色值来源(tokens.css 无对应工具条深色 token) | 作为页面局部装饰样式保留在 `UserList` scoped 样式,不新增全局 token、不挪用语义 token;语义色(主操作/文字/边框/错误等)严格走 token | tokens.css 仅定义语义/状态色,无工具条品牌深色 token;深色底纯装饰无状态语义,局部化最小侵入;与 FE-02 § 8 D9 处理一致 | medium |
  187 +
  188 +> 本规格不含后端实现细节(查询解析 / 匹配语义 / 敏感字段过滤 / 越界回退 / 数据访问 / 库表迁移等均不在前端作用域),亦不杜撰任何后端端点(导出按 D5 前端实现);登录态 / 请求基建 / 路由守卫复用 FE-01 + FE-02 已落地资产,所有查询真伪由后端在 `GET /api/usr/users` 调用时裁决。