Commit 4d4d51c8796b5a15b5632bcd2f912c42ca2baac4
1 parent
4c3d8325
docs(spec:FE-03): 派生规格
Showing
1 changed file
with
188 additions
and
0 deletions
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` 调用时裁决。 | ... | ... |