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 Design Select 组件,行为等价
  • a11y:所有 input 配 <label>;submit 按钮 type="submit" 让 Enter 键提交(与 prototype 行为一致)