FE-04 用户信息单据 — 实现规格(前端)
阶段:前端(frontend)。作用域限定
frontend/下的页面 / 组件 / 路由 / store / api / 样式。 SSoT 引用:需求卡片docs/01-需求清单/USR-用户管理/REQ-USR-001.md(增加用户)+docs/01-需求清单/USR-用户管理/REQ-USR-002.md(修改用户);原型prototype/erp.html(<section id="screen-userdetail">区域:.toolbar/.form-grid/.tabs-row/.perm-list,布局与交互权威,含setUserDetailMode('new')新增/编辑切换脚本);API 契约docs/05-API接口契约.md§ REQ-USR-001(POST /api/usr/users)+ § REQ-USR-002(PUT /api/usr/users/{id})+ § REQ-USR-003(GET /api/usr/users,编辑预填复用,见 § 8 D4);技术规范docs/04-技术规范.md§ 零(技术栈)/ § 2.1 目录约定 / § 2.2 状态管理 / § 2.3 请求封装 / § 2.4 错误处理;Design Tokenssrc/styles/tokens.css;承载外壳 FE-02 规格docs/superpowers/specs/2026-06-01-FE-02.md(AppLayout+ 标签栈「用户信息单据」+RequireAuth守卫 + 路由/usr/users/new与/usr/users/:id);列表来源 FE-03 规格docs/superpowers/specs/2026-06-01-FE-03.md(「新增」按钮 →/usr/users/new、双击行 →/usr/users/:id);登录态/请求基建 FE-01 规格docs/superpowers/specs/2026-06-01-FE-01.md(authSlice+request.ts+ token 持久化键xly_erp_token,及「支撑读端点供下拉预加载」先例GET /api/usr/companies)。本规格只消费已锁定事实。用户名全局唯一校验、用户名格式(3-20 位字母数字下划线)、密码 BCrypt 初始化(默认 666666)、权限组授权多对多写入与全量覆盖、职员/权限 id 存在性校验、管理员权限判定、审计字段(制单人/创建时间)系统生成等业务真伪裁决全部在后端(见 REQ-USR-001 / REQ-USR-002 后端规格)。前端只负责:按新增/编辑模式渲染单据表单、预填默认值或原值、采集字段、做轻量必填/格式前置校验(减少无效请求)、提交后按响应码反馈、成功后回流列表并刷新。本 FE 同时承载新增(
POST)与修改(PUT)两种写场景,共用同一单据组件按mode分支。
1. 关联 REQ + 关联原型
| 维度 | 内容 |
|---|---|
| 业务功能 | FE-04 用户信息单据(新增/修改用户表单 + 权限组勾选,对接 POST /api/usr/users 与 PUT /api/usr/users/{id}) |
| 关联 REQ(增) | REQ-USR-001 增加用户。表 1 输入字段:创建时间(系统生成/只读,预加载=页面加载、默认当前日期)、制单人(系统生成/只读,默认当前登录用户)、员工名(下拉单选,来源职员表,可选)、用户号(手工输入,必填,关联职员后自动带出)、用户名(手工输入,必填,关联职员后自动带出)、类型(下拉单选,普通用户/超级管理员,默认普通用户)、语言(下拉单选,中文/英文/繁体)、单据修改权限(复选框,默认否)、密码(系统生成不显示,默认 666666)。表 2 权限组:复选框 + 权限分类(页面加载预取)。输出表 1:用户号。 |
| 关联 REQ(改) | REQ-USR-002 修改用户。表 1 字段同上但「预加载=页面加载时」「默认值=原值」(编辑预填);用户名作为唯一标识不可修改;密码不在本接口修改。表 2 权限组:复选框预加载原值(已授权项勾选)。输出表 1:用户 id。 |
| 关联原型 |
prototype/erp.html → <section id="screen-userdetail">:.toolbar(新增/修改/删除/保存/取消/功能/作废/重置密码/取消作废 + 设置齿轮)、.form-grid(3 列表单网格:创建时间只读 / 制单人只读 / 员工名下拉 / 用户名输入 / 类型下拉 / 语言下拉 / 用户号输入 / 单据修改权限复选框)、.tabs-row(权限页签:权限组 / 客户查看权限 / 供应商查看权限 / 人员查看权限 / 工序查看权限 / 司机查看权限)、.perm-list(权限分类列表,每行复选框 + 权限分类名 + 表头排序图标);脚本 setUserDetailMode('new') 在新增态清空各字段、制单人显示「保存后自动生成」、清除权限勾选。 |
| 路由 | 新增 /usr/users/new;修改 /usr/users/:id(React Router v6,受保护区子路由,由 FE-02 AppLayout + RequireAuth 包裹,挂载于「用户信息单据」标签的 <Outlet/>)。入口:FE-03 工具栏「新增」→ navigate('/usr/users/new');FE-03 双击行 → navigate('/usr/users/'+row.id);FE-02 导航/常用操作的「新增用户」入口 → /usr/users/new。 |
| 落地组件目录 | 页面 frontend/src/pages/usr/UserDetail/(index.tsx 单据容器 + 子组件 UserDetailToolbar.tsx / UserBasicForm.tsx(表单网格)/ PermissionTabs.tsx(权限页签条)/ PermissionGroupList.tsx(权限分类勾选列表));接口走 frontend/src/api/usrApi.ts(新增 createUser / updateUser / getUserDetail / listEmployees / listPermissions 方法)+ frontend/src/api/request.ts(FE-01 已建);类型集中 frontend/src/api/types.ts(UserCreateReq / UserUpdateReq / EmployeeOption / PermissionItem / 复用 UserVO);单据态就近用页面 hook(不进全局 store,见 § 8 D7)。 |
原型
#screen-userdetail为纯静态 HTML + 内联 demo 脚本:.form-grid各字段值写死(f-ctime/f-creator/f-empname/f-username/f-userno/f-type/f-lang),setUserDetailMode('new')仅做 DOM 文本清空,.perm-list由静态perms数组渲染、复选框无真实勾选态,工具栏按钮(保存/取消等)无后端动作。本规格按 React + AntD 5 复刻其布局与交互语义,但表单数据、下拉数据源、权限勾选、保存提交改为真实对接后端。原型.tabs-row中除「权限组」外的 5 个查看权限页签(客户/供应商/人员/工序/司机)在 docs/01 REQ-USR-001/002 卡片与 docs/05 契约中均无对应字段或端点,按占位处理(见 § 8 D9);工具栏「删除/作废/重置密码/取消作废/功能」同样无对应 REQ/端点,按占位处理(见 § 8 D8)。
2. 组件树(按区域分块,推导自 prototype DOM)
页面根 UserDetailPage(路由 /usr/users/new 与 /usr/users/:id 共用,渲染于 FE-02 AppLayout 的 <Outlet/> 内),纵向结构对应原型 #screen-userdetail(工具栏 / 表单网格 / 权限页签条 / 权限分类列表):
UserDetailPage(单据容器,对应 #screen-userdetail;按路由判定 mode:有 :id → 'edit',/new → 'create')
├── UserDetailToolbar(对应 .toolbar:深色工具条)
│ ├── BtnSave(对应「保存」:校验通过后提交 create/update,见 § 5 BR12)
│ ├── BtnCancel(对应「取消」:放弃编辑回到列表 /usr/users,见 § 5 BR13)
│ ├── BtnNew(对应「新增」:在单据内切到 create 模式 / 跳 /usr/users/new,见 § 5 BR14)
│ ├── BtnDelete / BtnVoid / BtnResetPwd / BtnUnvoid / BtnFunc(对应「删除/作废/重置密码/取消作废/功能」:占位,无后端端点,见 § 8 D8)
│ └── GearSetting(对应 .gear「⚙」:偏好设置占位,见 § 8 D8)
├── UserBasicForm(对应 .form-grid:3 列表单网格,AntD Form)
│ ├── FieldCreateTime(对应 #f-ctime .field.readonly:创建时间,只读;create 态空/「保存后自动生成」,edit 态回填原值,见 § 5 BR1)
│ ├── FieldCreator(对应 #f-creator .field.readonly:制单人,只读;create 态「保存后自动生成」,edit 态回填原值 sCreator,见 § 5 BR2)
│ ├── FieldEmployee(对应 #f-empname .field.with-caret:员工名下拉单选,options 来自 GET /api/usr/employees,可空,选中联动带出用户号/用户名,见 § 5 BR5)
│ ├── FieldUserName(对应 #f-username input:用户名,必填;create 可编辑,edit 只读不可改(唯一标识),见 § 5 BR3)
│ ├── FieldUserType(对应 #f-type .field.with-caret:类型下拉单选,普通用户/超级管理员,create 默认普通用户,见 § 5 BR6)
│ ├── FieldLanguage(对应 #f-lang .field.with-caret:语言下拉单选,中文/英文/繁体,见 § 5 BR7)
│ ├── FieldUserNo(对应 #f-userno input:用户号,必填,关联职员后可自动带出,见 § 5 BR4/BR5)
│ └── FieldCanModifyBill(对应 .form-cell .cb「单据修改权限」:复选框,默认否,见 § 5 BR8)
│ (密码字段不在 UI 呈现:create 由后端默认 666666 初始化,edit 不改密码;对应卡片「密码=系统生成/不显示」,见 § 5 BR9)
├── PermissionTabs(对应 .tabs-row:权限页签条)
│ ├── TabPermGroup(对应「权限组」active 页签:本 FE 唯一有数据的页签,承载权限分类勾选)
│ └── TabPlaceholder[](对应「客户查看权限/供应商查看权限/人员查看权限/工序查看权限/司机查看权限」:占位页签,无 REQ/端点,见 § 8 D9)
└── PermissionGroupList(对应 .perm-list:权限分类勾选列表)
├── HeadRow(对应 .perm-row.head:表头「☐ 权限分类 ⇅」,含全选复选框 + 排序图标占位)
└── PermRow[](对应 .perm-row:每行复选框 + 权限分类名;数据来自 GET /api/usr/permissions;勾选集合 → 提交 permissionIds,见 § 5 BR10/BR11)
- 控件选型(依据
docs/04 § 零frontend.ui_lib = Ant Design 5.x):- 工具栏 → 自定义深色
div条 + AntDButton(type="text",白字图标按钮,复刻原型.tb-btn),「保存」type="primary"(或主操作高亮)配SaveOutlined、「新增」PlusCircleOutlined、「取消」CloseCircleOutlined、设置SettingOutlined;占位按钮(删除/作废/重置密码/取消作废/功能)渲染但disabled或点击message.info("功能开发中")(见 § 8 D8)。 - 表单网格 → AntD
Form(layout自定义为 3 列网格,复刻原型.form-grid grid-template-columns:repeat(3,1fr)),各字段为Form.Item;必填项标required(红色*复刻原型.lbl.req::before)。 - 员工名 / 类型 / 语言 →
Select(单选);员工名 options 来自GET /api/usr/employees,类型/语言 options 为固定枚举(见 § 5 BR6/BR7,不杜撰端点)。 - 用户名 / 用户号 →
Input(用户名 edit 态disabled,见 BR3)。 - 单据修改权限 →
Checkbox。 - 创建时间 / 制单人 → 只读展示(
Input disabled或纯文本div,复刻原型.field.readonly)。 - 权限页签 → AntD
Tabs(仅「权限组」页签有内容,其余占位页签disabled或空态,见 § 8 D9)。 - 权限分类列表 → AntD
Checkbox.Group+ 列表行(复刻原型.perm-list .perm-row),表头「全选」用Checkbox(indeterminate表达半选);数据来自GET /api/usr/permissions,勾选集合即提交的permissionIds。
- 工具栏 → 自定义深色
- 页面在受保护区内渲染(无 token 由 FE-02
RequireAuth拦截重定向,本页不重复守卫)。顶栏 / 标签栈属 FE-02,本页只提供内容区;保存成功/取消后由本页navigate('/usr/users')回流列表(标签联动属 FE-02,见 § 8 D6)。
3. 页面状态机(≥5 态)
单据态以「初始化加载 / 正常编辑 / 表单提交中 / 提交失败 / 提交成功 / 预填或下拉取数失败」表达。mode 由路由判定(/usr/users/new → create,/usr/users/:id → edit);页面 hook 持有 { mode, formValues, employees, permissions, checkedPermissionIds, loading, submitting, error }:
| 状态 | 触发时机 | UI 表现 |
|---|---|---|
initialLoading(初始化加载中 / loading) |
页面挂载即预取下拉数据:GET /api/usr/employees(员工名)+ GET /api/usr/permissions(权限分类);edit 态额外取该用户详情(见 § 8 D4)。对应卡片「预加载=页面加载时」 |
单据区 AntD Spin 遮罩或骨架;表单字段禁用直至数据就绪;工具栏「保存」禁用 |
editing(正常编辑 / 正常态) |
下拉数据就绪、(edit 态)原值回填完成、表单未提交 | 表单字段按 mode 可编辑(create 全部可编辑+默认值预填;edit 用户名只读、其余回填原值可改);权限列表渲染并按已授权回勾(edit);「保存」可点 |
submitting(表单提交中 / 提交中) |
点击「保存」且前置校验通过,调 POST(create)/ PUT(edit)期间 |
「保存」按钮置 loading 并禁用(防重复提交,见 § 5 BR15);表单整体置 disabled/遮罩;其余工具栏按钮禁用 |
submitError(提交失败) |
接口返回非 0 code(40001/40901/40301/40401)或网络/超时/5xx |
按 § 4 错误码表在表单内(就近字段)或全局 message.error 展示文案;用户名冲突(40901)就近高亮用户名字段(docs/04 § 2.4「表单提交错误就近在表单展示」);保留已填值可修正重试;按钮恢复可点 |
submitSuccess(提交成功) |
接口返回 code=0,返回 data.id
|
message.success(create:「用户创建成功」/ edit:「保存成功」);按 BR16 回流:navigate('/usr/users') 回到列表并触发列表刷新(标签联动属 FE-02);或留在单据切到 edit 态(取舍见 § 8 D6) |
loadError(预填/下拉取数失败) |
GET /api/usr/employees、GET /api/usr/permissions 或 edit 详情取数失败(非 0 / 网络异常) |
对应区域显错误占位 + 「加载失败,点击重试」入口(员工/权限下拉空 + 重试;edit 详情失败给整页重试或返回列表);message.error 文案见 § 4;被动 401 由 request.ts 拦截器统一跳 /login(docs/04 § 2.4) |
状态以页面本地 hook(
useState/自定义useUserDetail)表达:{ mode, formValues, employees, permissions, checkedPermissionIds, loading, submitting, error };不进全局store(单据态为页面就近态,docs/04 § 2.2「跨页面共享的才进 store」,见 § 8 D7)。authResolving/unauthenticated由 FE-02 守卫承担,本页不重复建态。
4. 消费的后端端点(对齐 docs/05)
| 端点 | 方法 | 触发时机 | 请求参数 | 成功响应 | 失败处理 |
|---|---|---|---|---|---|
/api/usr/users |
POST | create 态点击「保存」且校验通过 | JSON body { sUserName, sUserNo?, iEmployeeId?, sUserType, sLanguage, iCanModifyBill?(0/1), permissionIds?(number[]), initialPassword?(默认666666,前端不显式传,由后端默认) }(对齐 docs/05 § REQ-USR-001) |
Result<{ id:number }>,code=0,返回新建用户主键 id |
非 0 code 按错误码表反馈;用户名冲突就近高亮 |
/api/usr/users/{id} |
PUT | edit 态点击「保存」且校验通过 | 路径 id;JSON body { sUserNo?, iEmployeeId?, sUserType, sLanguage, iCanModifyBill?(0/1), iIsVoid?(0/1), permissionIds?(number[],全量覆盖语义) }(sUserName 不可改、密码不在本接口改;对齐 docs/05 § REQ-USR-002) |
Result<{ id:number }>,code=0,返回被修改用户 id |
非 0 code 按错误码表反馈 |
/api/usr/users |
GET | edit 态预填该用户原值(复用 REQ-USR-003 列表查询,按用户名/号精确匹配定位单条,见 § 8 D4) | query { queryField, matchType:等于, queryValue, pageNum:1, pageSize:1 }(对齐 docs/05 § REQ-USR-003) |
Result<PageResult<UserVO>>,取 records[0] 作为原值(UserVO 不含密码) |
取数失败 → 整页重试或返回列表 + message.error
|
/api/usr/employees |
GET | 页面挂载(员工名下拉预加载) | 无参(或可选关键字,MVP 无参全量) |
Result<List<{ iIncrement, sEmployeeName, sEmployeeNo, 部门? }>>,code=0(映射为 EmployeeOption{ value=iIncrement, label=sEmployeeName }) |
取数失败 → 员工 Select 空 + 重试入口 + message.error("员工列表加载失败")(端点来源见 § 8 D1) |
/api/usr/permissions |
GET | 页面挂载(权限组列表预加载) | 无参(MVP 全量) |
Result<List<{ iIncrement, sPermissionName, sPermissionCategory }>>,code=0(映射为 PermissionItem{ id=iIncrement, name=sPermissionName, category=sPermissionCategory }) |
取数失败 → 权限列表空 + 重试入口 + message.error("权限列表加载失败")(端点来源见 § 8 D2) |
请求 / 响应约定(依据 docs/04 § 2.3 / § 2.4):
- 统一走
frontend/src/api/request.ts的 Axios 实例(FE-01 已建:baseURL=/api,开发期经 Vite proxy 转发到后端端口config-vars.yaml backend.http_port=5172;请求拦截器注入Authorization: Bearer <token>,token 取 localStorage 键xly_erp_token)。 - 响应拦截器统一拆
Result:code=0取data;非 0code抛业务错误(默认弹message.error,本页对可就近展示的字段错误覆盖为表单内提示);401统一跳/login(docs/04 § 2.4)。本页方法集中在usrApi.ts(createUser/updateUser/getUserDetail/listEmployees/listPermissions),页面不直接散用 axios(docs/04 § 2.3)。
错误码表(对齐 docs/05 § REQ-USR-001 / § REQ-USR-002)
| code | 含义 | 前端文案 | 处理 |
|---|---|---|---|
0 |
成功 | —(message.success + 回流列表) |
create:「用户创建成功」/ edit:「保存成功」 |
40001 |
参数校验失败(字段格式/必填/枚举越界/关联 id 不存在) | 「提交信息有误,请检查后重试」(能定位到具体字段时就近提示,如用户名格式「用户名须为 3-20 位字母数字下划线」) |
message.error + 就近字段高亮;保留已填值,正常情况下前端前置校验已拦截,此为兜底 |
40901 |
用户名已存在(sUserName 全局唯一冲突,仅 create) | 「用户名已存在,请更换」 | 用户名字段就近 Form.Item 报错高亮 + message.error;聚焦用户名框 |
40401 |
用户不存在(id 无对应记录,仅 edit) | 「该用户不存在或已被删除」 |
message.error + 提供「返回列表」入口(数据已不在) |
40301 |
无权限(非管理员调用) | 「无权限执行此操作」 |
message.error;正常情况下入口仅对管理员开放,此为后端兜底 |
| 网络/超时/5xx | 请求异常 | 「保存失败,请稍后重试」 | 响应拦截器兜底 message.error;按钮恢复可点 |
401 |
登录失效(被动) | 「登录已失效,请重新登录」 | 由 request.ts 拦截器统一跳 /login(docs/04 § 2.4),本页不单独处理 |
create 态可能命中 40001/40901/40301;edit 态可能命中 40001/40401/40301(无 40901,用户名不可改)。前端按 mode 仅订阅相关码,未列码归入「保存失败」兜底。
5. 业务规则前端复刻清单(逐条)
| # | 规则 | 触发时机 | 前端报错 / 反馈文案 | 来源 |
|---|---|---|---|---|
| BR1 | 创建时间为系统生成、只读;create 态显示空/「保存后自动生成」,edit 态回填原值 tCreateDate,前端均不提交该字段 |
渲染单据 | —(只读展示,不参与提交) | REQ-USR-001/002 表 1「创建时间=系统生成/只读,保存后自动生成」;原型 #f-ctime .readonly + setUserDetailMode('new') 置空 |
| BR2 | 制单人为系统生成、只读;create 态显示「保存后自动生成」,edit 态回填原值 sCreator,前端不提交 |
渲染单据 | —(只读展示,不提交) | REQ-USR-001/002 表 1「制单人=系统生成/只读,默认当前登录用户/原值」;原型 #f-creator + 新增态文本「保存后自动生成」 |
| BR3 | 用户名必填;create 态可手工输入(前端前置校验 3-20 位字母数字下划线),edit 态只读不可改(唯一标识) | create 输入 / edit 渲染 | create 格式不符:「用户名须为 3-20 位字母数字下划线」;为空:「请输入用户名」 | REQ-USR-001 表 1「用户名=必填/手工输入」+ docs/05 § REQ-USR-001「3-20 位字母数字下划线,全局唯一」;REQ-USR-002 跨字段「sUserName 不可修改」 |
| BR4 | 用户号必填,手工输入 | 提交校验 | 为空:「请输入用户号」 | REQ-USR-001/002 表 1「用户号=必填/手工输入」 |
| BR5 | 选择「员工名」后自动带出用户号/用户名(前端联动预填,用户仍可改) | 员工名下拉选中 | —(联动填充,无报错) | REQ-USR-001/002 表 1「关联职员选择后自动输入员工姓名」;REQ-USR-001 后端规格 D3「自动带出属前端交互」 |
| BR6 | 类型下拉单选,取值仅普通用户/超级管理员;create 默认「普通用户」,edit 回填原值 | 渲染下拉 / 提交校验 | 越界由 Select 限制(无自由输入);必填为空:「请选择类型」 |
REQ-USR-001 表 1「类型=必填/下拉/默认普通用户」;REQ-USR-002「默认原值」;docs/05 枚举 {普通用户,超级管理员} |
| BR7 | 语言下拉单选,取值仅中文/英文/繁体;必填 | 渲染下拉 / 提交校验 | 必填为空:「请选择语言」 | REQ-USR-001/002 表 1「语言=必填/下拉/中文,英文,繁体」 |
| BR8 | 单据修改权限复选框,默认否(create)/ 原值(edit);提交为 0/1 | 渲染 / 提交 | —(布尔,无报错) | REQ-USR-001/002 表 1「单据修改权限=布尔/复选框/默认否」+ docs/05 iCanModifyBill(0/1)
|
| BR9 | 密码不在 UI 呈现:create 由后端默认 666666 初始化(前端不显式传),edit 不修改密码 | 提交 | —(前端不采集/不显示密码) | REQ-USR-001 表 1「密码=系统生成/不显示/默认 666666」;REQ-USR-002 跨字段「密码不在该接口修改」 |
| BR10 | 权限组:列表项来自后端权限分类,复选框勾选集合即提交的 permissionIds;edit 态按已授权回勾 |
渲染权限列表 / 提交 | —(勾选集合,无报错;取数失败见 § 4 loadError) | REQ-USR-001/002 表 2「权限组复选框 + 权限分类」;docs/05 permissionIds(number[])
|
| BR11 | edit 态权限为全量覆盖语义:提交的 permissionIds 即该用户最终授权全集(不勾即取消授权;空集清空) |
edit 保存 | —(前端提交当前勾选全集,覆盖语义由后端执行) | REQ-USR-002 后端规格 D4「permissionIds 全量覆盖」+ § 3 规则 8 |
| BR12 | 点击「保存」:前置必填/格式校验通过后,create 调 POST /api/usr/users、edit 调 PUT /api/usr/users/{id}
|
点击「保存」 | 校验不通过:就近字段报错不发请求;通过后按 § 4 反馈 | 原型 .toolbar「保存」;docs/05 § REQ-USR-001/002 |
| BR13 | 点击「取消」:放弃当前编辑,回到用户列表 /usr/users(不提交) |
点击「取消」 | —(navigate('/usr/users');有未保存改动时可二次确认,见 § 8 D5) |
原型 .toolbar「取消」 |
| BR14 | 点击「新增」:在单据内切到 create 模式(或 navigate('/usr/users/new')),清空各字段、制单人显示「保存后自动生成」、清除权限勾选 |
点击「新增」 | —(复刻 setUserDetailMode('new')) |
原型 .toolbar「新增」+ setUserDetailMode('new') 脚本 |
| BR15 | 提交进行中禁用「保存」按钮并置 loading,防重复提交 | submitting 态 | —(按钮 loading+disabled) |
docs/04 § 2.4 错误处理 / 通用表单防重(登记 § 8 D5) |
| BR16 | 保存成功后回流用户列表并触发列表刷新(保证新增/修改实时反映在列表,对应验收) | submitSuccess |
message.success 后 navigate('/usr/users')
|
REQ-USR-001 验收「提交合法数据后用户记录出现在列表」;REQ-USR-002 验收「修改后立即反映在用户列表」 |
| BR17 | edit 态进入时按路由 :id 预填该用户原值(基本字段 + 已授权权限回勾) |
edit 页面挂载 | 取数失败按 § 4 loadError | REQ-USR-002 表 1「预加载=页面加载时/默认值=原值」;表 2「预加载原值」 |
本页只做字段采集 + 轻量前置校验 + 写提交 + 反馈回流;用户名唯一性/格式终判、枚举与外键存在性、权限多对多写入与全量覆盖、密码 BCrypt 初始化、管理员权限、审计字段生成等真伪裁决全部由后端在
POST /api/usr/users/PUT /api/usr/users/{id}内完成。前端前置校验仅为减少无效请求与即时反馈,不替代后端校验、不预判结果、不杜撰业务端点。
6. 字段定义与提交映射(对齐 REQ 表 1/表 2 + docs/05 请求体)
| 表单字段(中文标签,对应原型) | 提交字段(docs/05) | mode 行为 | 渲染/校验说明 |
|---|---|---|---|
| 创建时间 | —(不提交) | create 空/占位;edit 回填 tCreateDate
|
只读(BR1) |
| 制单人 | —(不提交) | create「保存后自动生成」;edit 回填 sCreator
|
只读(BR2) |
| 员工名 |
iEmployeeId(可选) |
create 可选空;edit 回填原值 |
Select,options 来自 GET /api/usr/employees(value=iIncrement,label=sEmployeeName);选中联动带出用户号/用户名(BR5) |
| 用户名 |
sUserName(create 必传;edit 不传) |
create 可编辑必填;edit 只读不可改 |
Input,前置校验 3-20 位字母数字下划线(BR3) |
| 用户号 |
sUserNo(可选,本 UI 必填采集) |
create/edit 均可编辑,可由员工名联动带出 |
Input,前端必填(BR4),后端契约可空 |
| 类型 |
sUserType(必填) |
create 默认普通用户;edit 回填原值 |
Select,枚举 {普通用户,超级管理员}(BR6) |
| 语言 |
sLanguage(必填) |
create 必选;edit 回填原值 |
Select,枚举 {中文,英文,繁体}(BR7) |
| 单据修改权限 |
iCanModifyBill(0/1) |
create 默认 0;edit 回填原值 |
Checkbox(BR8) |
| (作废,仅 edit 可选) |
iIsVoid(0/1,仅 PUT) |
edit 可改(控制启停用);create 不提交 | docs/05 § REQ-USR-002 含 iIsVoid;原型工具栏「作废/取消作废」语义映射到此布尔(见 § 8 D8),MVP 可作为表单内启停用开关或暂随占位按钮,登记 D8 |
| 权限组勾选 |
permissionIds(number[]) |
create 勾选集;edit 全量覆盖集 |
Checkbox.Group,项来自 GET /api/usr/permissions(value=iIncrement,label=sPermissionName/sPermissionCategory)(BR10/BR11) |
| (密码) |
initialPassword(create 可选,前端不传由后端默认 666666) |
不在 UI | 不采集(BR9) |
输出:create 返回
data.id(卡片输出表 1「用户号」语义上对应新建用户主键,前端用于回流/提示);edit 返回data.id(被改用户)。前端不直接展示返回 id,仅用于成功反馈与列表刷新定位。
7. Design Tokens 引用清单(src/styles/tokens.css,仅 var(--color-*))
约束:组件样式只用
var(--color-*),禁止硬编码 hex/rgba;色值冲突时tokens.css优先于prototype/(原型内联:root变量为 demo 私有,不作色值 SSoT)。AntD 主题色经 FE-02/FE-01 已配置的ConfigProvider对齐--color-primary。
| 用途 | Token | 备注 |
|---|---|---|
| 主操作(保存按钮 / 激活页签下划线 / 链接强调) | var(--color-primary) |
对应原型「保存」主操作、.tabs-row .tb.active 蓝色下划线;同时作为 AntD colorPrimary
|
| 页面 / 内容区基础背景 | var(--color-bg-base) |
单据内容区浅灰底(外壳 Outlet 内) |
| 表单网格 / 权限列表背景(白) | var(--color-form-bg-edit) |
对应原型 .form-grid / .perm-list 白底(原型 --panel/#fff) |
| 可编辑输入框背景(用户名/用户号/下拉) | var(--color-form-bg-edit) |
可编辑字段底(原型 --field-bg 映射到表单可编辑 token) |
| 只读字段背景(创建时间/制单人) | var(--color-form-bg-readonly) |
对应原型 .field.readonly(原型 --field-bg-readonly) |
| 下拉项 hover 背景 | var(--color-form-bg-hover) |
员工名/类型/语言 Select 选项 hover(tokens 注「仅下拉框使用」) |
| 表单文字 / 字段值 | var(--color-form-fg) |
输入与展示文字 |
| 权限列表表头背景 | var(--color-table-header-bg) |
对应原型 .perm-row.head 表头底(原型 --header-bg) |
| 权限列表表头文字 | var(--color-table-header-fg) |
「权限分类」表头文字 |
| 权限行 hover 背景 | var(--color-table-row-bg-hover) |
权限行悬停高亮(复刻原型行 hover 浅色) |
| 通用文字 / 标签文字 | var(--color-text) |
字段标签、页签文字 |
| 次要文字 / 占位提示 | var(--color-text-secondary) |
占位页签、占位按钮、空态提示、设置齿轮 |
| 必填星号 / 用户名冲突等校验错误 | var(--color-error) |
必填 *(复刻原型 .lbl.req 红 --label)、message.error、字段就近报错强调 |
| 边框 / 分隔线 / 网格线 | var(--color-border) |
表单网格分隔、权限行下边线、页签条下边线(原型 --border) |
| 警告提示 | var(--color-warning) |
未保存改动二次确认等轻提示(D5) |
| 成功提示 | var(--color-success) |
message.success(创建/保存成功) |
工具栏
.toolbar的深色底(原型--toolbar-bg:#2c2f36)在tokens.css中无对应语义 token;与 FE-02/FE-03 一致,本规格将单据工具条深色底作为页面局部装饰样式保留在UserDetail的 scoped 样式里(不新增全局 token、不挪用语义 token)。该深色仅承载工具条容器视觉,不承载状态语义,不违反「语义色只用 token」约束(见 § 8 D10)。原型必填标签红色--label:#f04848在 tokens 中映射到var(--color-error)(语义=校验错误/必填强调),以 tokens.css 为准。
8. 自主决策记录(decisions)
| # | 问题 | 选择 | 依据 | 置信度 |
|---|---|---|---|---|
| D1 | 员工名下拉数据从哪个端点取(docs/05 主清单与 REQ-USR-001/002 后端规格均只列写端点 POST/PUT,未定义员工读端点) |
消费支撑只读端点 GET /api/usr/employees(返回 usr_employee 的 iIncrement/sEmployeeName/sEmployeeNo/部门),映射为员工名下拉 options |
REQ-USR-001/002 表 1 明确「员工名=下拉单选/显示来源=职员表/预加载=页面加载时」,必须有读源;docs/03 usr_employee 定义了 sEmployeeName 且注「用户『员工名』下拉来源」;沿用 FE-01 § 8 D1 既有先例(下拉所需的支撑只读端点由对应后端 REQ 补齐,如 GET /api/usr/companies);不杜撰参数、仅按职员表字段定义最小读端点,留待后端实现期补契约/或后端 REQ 补齐 |
medium |
| D2 | 权限组列表数据从哪个端点取(docs/05 未定义权限读端点) | 消费支撑只读端点 GET /api/usr/permissions(返回 usr_permission 的 iIncrement/sPermissionName/sPermissionCategory),映射为权限分类勾选列表 |
REQ-USR-001/002 表 2「权限组复选框 + 权限分类/预加载=页面加载时」必须有读源;docs/03 usr_permission 定义 sPermissionName/sPermissionCategory 且注「对应新增/修改用户界面『权限组』」;提交字段 permissionIds 需先有可选权限项;与 D1 同先例处理,不杜撰额外语义 |
medium |
| D3 | 「权限分类」列表是按分类聚合展示还是逐权限项展示 | MVP 按后端返回的权限项逐行展示(每行一个可勾选权限),列名「权限分类」沿用原型表头;若后端按分类聚合返回则按分类分组渲染(实现期对齐响应结构) | docs/03 usr_permission 注「权限粒度(按分类/按具体功能点)待确认」为 DB 文档遗留占位(标注「需用户审阅」),非本前端可消解;原型 .perm-list 逐行渲染 perms 数组(扁平项),故 MVP 取逐行展示最贴合原型;提交 permissionIds 为权限项 id 集合(docs/05 permissionIds(number[])),与逐项勾选一致 |
medium |
| D4 | edit 态预填用户原值从哪个端点取(docs/05 无单用户详情 GET 端点,仅列表 GET /api/usr/users) |
复用 REQ-USR-003 列表端点 GET /api/usr/users,以「等于」匹配 + pageSize=1 按 :id 对应的用户名/用户号定位取 records[0];不杜撰新的 GET /api/usr/users/{id} 详情端点 |
docs/05 仅定义列表查询端点,无单据详情端点;硬约束禁止编造后端端点;列表 UserVO 已含编辑所需基本字段(不含密码,正符合「密码不在编辑接口」);FE-03 双击行携带的 id 与列表行数据可经路由 state 传递时甚至无需重新取数(见备注)。备注:FE-03 双击进入时可经 React Router navigate(..., {state:{user}}) 把行数据带入 edit 预填,避免二次请求;二者择一实现期定,列渲染语义不变 |
medium |
| D5 | 「保存」防重复提交 / 「取消」未保存改动是否二次确认 | 提交中禁用「保存」并置 loading(BR15);「取消」时若表单有未保存改动弹 AntD Modal.confirm「放弃未保存的修改?」,无改动直接返回 |
通用表单交互安全实践(防重复写、防误丢编辑),不与任何业务规则冲突;非硬性 REQ 要求,故登记 | medium |
| D6 | 保存成功后留在单据还是回流列表 | 回流用户列表 /usr/users 并触发列表刷新(message.success 后 navigate),由 FE-02 标签栈联动(关闭/切回列表标签语义属 FE-02) |
REQ-USR-001/002 验收均要求「记录出现/反映在用户列表」,回流列表最直接满足验收且与原型「单据隶属列表标签下」结构一致(FE-02 BR5/BR6 标签联动);留在单据需额外切 edit 态,MVP 取回流更简 | medium |
| D7 | 单据态放页面本地 hook 还是全局 store | 页面本地 hook(useUserDetail,含 mode/formValues/employees/permissions/checkedPermissionIds/loading/submitting/error),不进全局 store |
docs/04 § 2.2「服务端数据优先就近在页面用 hooks 拉取,跨页面共享的才进 store」;单据数据仅本页用,无跨页共享需求;与 FE-03 D6 一致 | high |
| D8 | 工具栏「删除/作废/重置密码/取消作废/功能」按钮(原型有,但 docs/05/REQ 无对应端点)如何处理 | 占位渲染(disabled 或点击 message.info("功能开发中")),不杜撰后端端点;其中「作废/取消作废」语义对应 iIsVoid 0/1,MVP 可在 edit 表单内以启停用开关承载(提交 PUT 的 iIsVoid),其余删除/重置密码/功能纯占位 |
docs/05 仅定义 create/update/list/login 端点,无删除/重置密码/作废独立端点(硬约束禁止编造);iIsVoid 是 PUT 已有字段(docs/05 § REQ-USR-002),故「作废」可经 PUT 实现而无需新端点;其余按钮无任何端点旁证,占位化最小侵入 |
medium |
| D9 | 权限页签条除「权限组」外的 5 个查看权限页签(客户/供应商/人员/工序/司机)如何处理 | 占位页签(disabled 或空态「功能开发中」),不实现、不取数 |
REQ-USR-001/002 卡片仅定义「权限组」(表 2),其余查看权限在 docs/01/docs/03/docs/05 均无字段/表/端点;原型为静态 demo 页签(无脚本);占位化不杜撰功能 | high |
| D10 | 工具栏深色底色值来源(tokens.css 无工具条深色 token) | 作为页面局部装饰样式保留在 UserDetail scoped 样式,不新增全局 token、不挪用语义 token;语义色严格走 token |
tokens.css 仅定义语义/状态色,无工具条品牌深色 token;深色底纯装饰无状态语义,局部化最小侵入;与 FE-02/FE-03 一致 | medium |
| D11 | 用户号契约可空但本 UI 是否必填 | 本 UI 按 REQ 卡片表 1「用户号=必填」前端必填采集;docs/05 标 sUserNo(可选) 为后端宽容接收(联动带出/为空存 null),二者不冲突 |
REQ-USR-001/002 表 1 明确「用户号 必填=是」(业务期望填写);docs/05/后端 D3 标可选是后端容错(按传入值落库);前端按业务卡片必填、后端按契约兜底,取更严的前端必填满足业务期望 | medium |
本规格不含后端实现细节(用户名唯一性/格式终判、枚举与外键存在性校验、权限多对多写入与全量覆盖、密码 BCrypt 初始化、管理员权限、审计字段生成、数据访问、库表迁移等均不在前端作用域);除按 D1/D2/D4 消费/复用支撑读端点外不杜撰任何写端点;登录态/请求基建/路由守卫/标签栈复用 FE-01 + FE-02 + FE-03 已落地资产,所有写操作真伪由后端在
POST /api/usr/users/PUT /api/usr/users/{id}调用时裁决。D1/D2 的支撑只读端点(员工/权限下拉数据源)须在后端编码期于 REQ-USR-001/002 后端实现内补齐契约(如同 FE-01 的GET /api/usr/companies先例),否则前端下拉无数据来源——此为跨阶段待对齐项,记于此供编码阶段消化。