2026-05-15-FE-01.md
8.43 KB
前端功能规格 — FE-01 用户登录
关联 REQ:REQ-USR-001 关联原型:prototype/erp.html#screen-login 日期:2026-05-15
一、功能概述
未登录用户访问任意路径都重定向到 /login。登录页提交 username + password + companyCode 三字段 → 调用 POST /api/v1/auth/login → 成功后把 accessToken + userInfo 存入 Redux 内存 → 跳转 /users(FE-02 用户管理页)。错误按后端返回的错误码做差异化提示(密码错 / 账号作废 / 账号锁定 / 公司不存在 / 字段格式错)。
关键约束:
- accessToken 仅存 Redux store 内存;刷新 / 关闭浏览器会清空 → 重登。与 docs/04 § 2.5 推荐方案对齐
- 公司下拉硬编码
{HQ: 总部}单选项;后续 REQ 提供GET /api/v1/companies后替换为动态加载 - 与 prototype 布局保持视觉一致(左侧 hero 文字 + 右侧 login-card 表单 + 顶部 logo + 底部 copyright)
二、组件树
基于 prototype/erp.html#screen-login 推导:
LoginPage (pages/login/LoginPage.tsx, route="/login")
├── LoginHeader
│ ├── Logo (svg)
│ ├── BrandName ("Antler ERP")
│ └── SubTitle ("欢迎登录EBC平台")
├── LoginHero
│ ├── HeroText
│ │ ├── EnglishLine ("Enterprise Business Capability")
│ │ ├── ChineseLine ("企业业务能力平台")
│ │ └── BigLabel ("ERP")
│ └── LoginCard
│ ├── CardTitle ("用户登录")
│ ├── UsernameField (Input + UserIcon)
│ ├── PasswordField (Input.Password + LockIcon)
│ ├── CompanyDropdown (Select,默认 HQ)
│ ├── ErrorBanner (条件渲染,根据 errorState)
│ └── SubmitButton ("登 录")
└── LoginFooter
├── CopyrightText
└── ICPNumber
三、页面状态机
| # | 状态 | 触发条件 | 视觉表现 | 用户可执行操作 |
|---|---|---|---|---|
| 1 | idle | 初次进入 / token 失效跳转 | 空表单,submit 可点 | 输入字段,点击 submit |
| 2 | empty | 字段未填完整 | 字段下方红色提示"必填";submit disabled | 继续输入 |
| 3 | submitting | 用户点击 submit + 前端校验过 | submit 显示 loading 旋转图 + 文字"登录中...";所有字段 disabled | 等待响应 |
| 4 | error_credentials | 后端返 40101 | submit 恢复可点;ErrorBanner 显示"用户名或密码错误";username + password 字段红边框 | 修改字段重试 |
| 5 | error_locked | 后端返 42301 | submit 不可点(持续直到 lockUntil);ErrorBanner 显示"账号已锁定,请于 HH:MM 后再试"(用 dayjs format data.lockUntil) |
等待解锁或换账号 |
| 6 | error_deleted | 后端返 40103 | ErrorBanner 显示"账号已被作废,禁止登录";红色严重图标 | 换账号 |
| 7 | error_company | 后端返 40004 | CompanyDropdown 红边框 + 下方提示"公司不存在或已删除" | 重选公司 |
| 8 | error_format | 后端返 40001 / 前端校验失败 | 对应字段下方提示具体格式错误 | 修改字段 |
| 9 | error_network | 网络超时 / 5xx | ErrorBanner 显示"网络异常,请检查连接后重试"(黄色) | 等待 / 点 submit 重试 |
| 10 | success | 后端返 200 + data | 短暂 200ms 显示"登录成功"绿色提示后跳转 /users
|
(自动跳转,不可操作) |
四、消费的后端端点
| # | 方法 | 路径 | 触发时机 | 关联 REQ |
|---|---|---|---|---|
| 1 | POST | /api/v1/auth/login |
用户点击 submit + 前端校验通过 | REQ-USR-001 |
请求体:
{ "username": "alice", "password": "Password1!", "companyCode": "HQ" }
响应(成功):
{
"code": 200,
"data": {
"accessToken": "<JWT>",
"tokenType": "Bearer",
"expiresInSec": 7200,
"userInfo": { "userId": 42, "username": "alice", "userType": "NORMAL",
"language": "zh-CN", "employeeName": "张三", "companyCode": "HQ" }
}
}
五、业务规则前端复刻清单
| # | 规则描述 | 触发时机 | 报错文案 | 来源 REQ |
|---|---|---|---|---|
| 1 | username 必填且非空白 | submit 前 / blur | "请输入用户名" | REQ-USR-001 |
| 2 | password 必填且非空白 | submit 前 / blur | "请输入密码" | REQ-USR-001 |
| 3 | companyCode 必填(默认已选 HQ) | submit 前 | "请选择公司" | REQ-USR-001 |
| 4 | 收到 40101 → 通用文案,不区分用户名/密码错 | 响应处理 | "用户名或密码错误" | REQ-USR-001 |
| 5 | 收到 40103 → 账号作废 | 响应处理 | "账号已被作废,禁止登录" | REQ-USR-001 |
| 6 | 收到 42301 → 账号锁定 + 显示 lockUntil | 响应处理 | "账号已锁定,请于 {HH:MM} 后再试" | REQ-USR-001 |
| 7 | 收到 40004 → 公司不存在 | 响应处理 | "公司不存在或已删除" | REQ-USR-001 |
| 8 | 收到 40001 → 字段格式错误 | 响应处理 | 后端 message 透传 | REQ-USR-001 |
| 9 | 网络错误 / 5xx | 响应处理 | "网络异常,请检查连接后重试" | docs/04 § 2.4 |
| 10 | 登录成功 → 写 Redux auth + 跳 /users | 响应处理 | (无文案,跳转) | REQ-USR-001 |
| 11 | 密码输入显示星号 | 字段渲染 | (Input.Password 内置) | REQ-USR-001 输入表 |
| 12 | 显示 lockUntil 用 dayjs format HH:mm(24h) |
错误处理 | (文案见 # 6) | spec § 5 |
要求:每条规则必须在前端 form-level 校验中复刻,不仅依赖后端报错。文案与后端语义一致。
六、Design Tokens 引用清单
--color-primary (submit 按钮 bg / 标题色)
--color-primary-hover (submit hover bg)
--color-primary-active (submit active bg)
--color-text (表单 label)
--color-text-secondary (sub-title "欢迎登录EBC平台" / copyright)
--color-error (错误状态字段红边框 + ErrorBanner 文字)
--color-border (输入框边框 idle 态)
--color-bg-page (登录页底色)
--color-bg-container (login-card 卡片 bg)
--color-warning (网络错误 banner 黄色)
--color-success (登录成功提示绿色)
来源:docs/06 § 二(已锁定全局调色板 + 组件级状态色)。本 FE 不引入新 token。
七、交互流程关键路径
[用户访问 / 任何路径]
→ router/RequireAuth 检测 Redux auth.accessToken == null
→ 重定向到 /login
[用户在 /login]
1. 页面挂载 → useEffect 把公司下拉默认值设为 "HQ"
2. 用户填写 username + password
3. 点 submit → form.validateFields() → submitting 态
4. 调用 authApi.login(req) → axios POST /api/v1/auth/login
5a. 成功(code=200):
- dispatch(authSlice.actions.setSession({ accessToken, userInfo }))
- 200ms 提示 "登录成功"
- navigate('/users', { replace: true })
5b. 失败(throw BizError {code, message, data}):
- 根据 code 切换错误状态
- 把 message 显示到 ErrorBanner
- 字段标红(按状态机表)
5c. 锁定(code=42301):
- 启动 setInterval 每秒检查 data.lockUntil > now()
- lockUntil 过期 → 自动清除锁定态,submit 恢复可点
5d. 网络错(无 code):
- ErrorBanner 显示通用网络异常 + 黄色
[F5 刷新 / 重启浏览器]
→ Redux 内存丢失 → RequireAuth 检测 token=null → 重定向 /login
→ 用户须重登(与 docs/04 § 2.5 安全约束一致)
八、备注与开放问题
- GET /api/v1/companies 暂未实现:spec § 一 已说明硬编码 HQ;后续运营模块 / FE 阶段补全时只需把 dropdown 改为动态 API 调用即可,state/UI 结构不变
-
登录成功跳转目标:当前为
/users(FE-02)。若未来引入 dashboard,按角色 /userInfo.userType派发,但本 FE 不实现该分支 - HTTPS / token in transit:依赖部署 Nginx TLS 终止(docs/07 § 二),前端代码不在 axios 层做额外加密
- i18n 推迟:本 FE 报错文案硬编码中文,后续接入 i18n 时再抽出
- prototype 顶部 logo + 底部 copyright 视觉保留但 SVG 路径直接复制 prototype 内容,不再独立美工
-
公司下拉点击展开:prototype 用纯 CSS hover 展开(
.opt块),React 实现用 Ant DesignSelect组件,行为等价 -
a11y:所有 input 配
<label>;submit 按钮type="submit"让 Enter 键提交(与 prototype 行为一致)