# 前端功能规格 — 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 | 请求体: ```json { "username": "alice", "password": "Password1!", "companyCode": "HQ" } ``` 响应(成功): ```json { "code": 200, "data": { "accessToken": "", "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 配 `