2026-06-01-FE-04.md 36.8 KB

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 Tokens src/styles/tokens.css;承载外壳 FE-02 规格 docs/superpowers/specs/2026-06-01-FE-02.mdAppLayout + 标签栈「用户信息单据」+ 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.mdauthSlice + 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/usersPUT /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.tsUserCreateReq / 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 条 + AntD Buttontype="text",白字图标按钮,复刻原型 .tb-btn),「保存」type="primary"(或主操作高亮)配 SaveOutlined、「新增」PlusCircleOutlined、「取消」CloseCircleOutlined、设置 SettingOutlined;占位按钮(删除/作废/重置密码/取消作废/功能)渲染但 disabled 或点击 message.info("功能开发中")(见 § 8 D8)。
    • 表单网格 → AntD Formlayout 自定义为 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),表头「全选」用 Checkboxindeterminate 表达半选);数据来自 GET /api/usr/permissions,勾选集合即提交的 permissionIds
  • 页面在受保护区内渲染(无 token 由 FE-02 RequireAuth 拦截重定向,本页不重复守卫)。顶栏 / 标签栈属 FE-02,本页只提供内容区;保存成功/取消后由本页 navigate('/usr/users') 回流列表(标签联动属 FE-02,见 § 8 D6)。

3. 页面状态机(≥5 态)

单据态以「初始化加载 / 正常编辑 / 表单提交中 / 提交失败 / 提交成功 / 预填或下拉取数失败」表达。mode 由路由判定(/usr/users/newcreate/usr/users/:idedit);页面 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/employeesGET /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)。
  • 响应拦截器统一拆 Resultcode=0data;非 0 code 抛业务错误(默认弹 message.error,本页对可就近展示的字段错误覆盖为表单内提示);401 统一跳 /login(docs/04 § 2.4)。本页方法集中在 usrApi.tscreateUser / 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.successnavigate('/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/employeesvalue=iIncrementlabel=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/permissionsvalue=iIncrementlabel=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.successnavigate),由 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 先例),否则前端下拉无数据来源——此为跨阶段待对齐项,记于此供编码阶段消化。