2026-06-01-FE-01.md 18.1 KB

FE-01 登录页 — 实现规格(前端)

阶段:前端(frontend)。作用域限定 frontend/ 下的页面 / 组件 / 路由 / store / api / 样式。 SSoT 引用:需求卡片 docs/01-需求清单/USR-用户管理/REQ-USR-004.md;原型 prototype/erp.html#screen-login 区域,布局/交互权威);API 契约 docs/05-API接口契约.md § REQ-USR-004;技术规范 docs/04-技术规范.md § 零 / § 二;Design Tokens src/styles/tokens.css。 本规格只消费已锁定事实。后端身份认证、BCrypt 比对、JWT 签发、限流等业务逻辑全部在后端(见 REQ-USR-004 后端规格),前端只负责采集输入、提交、依据响应/错误码渲染状态与文案。


1. 关联 REQ + 关联原型

维度 内容
业务功能 FE-01 登录页(用户名/密码/版本下拉登录)
关联 REQ REQ-USR-004 登录用户(主);其「版本」下拉数据依赖后端 GET /api/usr/companies(REQ-USR-004 后端补齐的配套只读端点)
关联原型 prototype/erp.html<section id="screen-login">(含 .login-wrap / .login-head / .login-hero / .login-card / .login-foot
路由 /login(React Router v6)。登录成功后跳转主页落地路由(属 FE-02 范畴,本页只负责导航跳转动作,目标路径默认 /,见 § 7 决策 D3)
落地组件目录 frontend/src/pages/usr/Login/(页面);登录态写入 frontend/src/store/slices/authSlice;接口走 frontend/src/api/usrApi.ts + frontend/src/api/request.ts

原型 #screen-login 用纯静态 HTML + 内联 demo 脚本(goTo('login') 默认进登录页、.submit[data-go=main] 点击直接切主页、#ver-drop 点击切换 .open 展开版本项)模拟交互。本规格按 React + AntD 5 复刻其布局与交互语义,但表单校验、提交、下拉取数、错误反馈改为真实对接后端。


2. 组件树(按区域分块,推导自 prototype DOM)

页面根 LoginPage(路由 /login 挂载),结构对应原型 .login-wrap(占满视口、纵向 flex:头部 / 主视觉 / 页脚):

LoginPage (容器,对应 .login-wrap:position 占满、flex column、背景 --color-bg-base)
├── LoginHeader (对应 .login-head:左 Logo SVG + 品牌名「Antler ERP」+ 副标题「欢迎登录EBC平台」)
│   ├── BrandLogo (鹿角 SVG,复用原型 inline svg path)
│   ├── BrandName  ("Antler ERP")
│   └── BrandSub   ("欢迎登录EBC平台")
├── LoginHero (对应 .login-hero:占满剩余高度的主视觉区,深蓝渐变 + 网格透视背景)
│   ├── HeroText (对应 .login-text:英文标语 / 中文「企业业务能力平台」/ 巨型 "ERP")
│   └── LoginCard (对应 .login-card:右侧浮层登录卡片,AntD <Card> 或 <Form> 容器)
│       ├── CardTitle ("用户登录")
│       └── LoginForm (AntD <Form>,提交触发认证)
│           ├── Form.Item[sUserName]  → <Input prefix={用户图标}>      占位「请输入你的用户名」
│           ├── Form.Item[password]   → <Input.Password prefix={锁图标}> 占位「请输入你的密码」(输入显示星号)
│           ├── Form.Item[companyId]  → <Select>(版本下拉,options 来自 GET /api/usr/companies)
│           └── SubmitButton          → <Button type="primary" htmlType="submit" block loading={submitting}> "登 录"
└── LoginFooter (对应 .login-foot:版权 / 备案号文本条,置底)
  • 控件选型(依据 docs/04 § 零 frontend.ui_lib = Ant Design 5.x):
    • 用户名 → Input,带 prefix 用户图标(@ant-design/icons UserOutlined)。
    • 密码 → Input.Password,带 prefix 锁图标(LockOutlined),AntD 默认掩码显示,满足卡片「输入显示星号」。
    • 版本 → Select(单选),optionsGET /api/usr/companies 返回项映射(label=sCompanyName(含 sVersion 时拼接展示,见 § 6 规则)value=id)。原型用自定义 .lf.dropdown 模拟,本规格以 AntD Select 等价复刻下拉交互。
    • 登录按钮 → Button type="primary" block,提交中置 loading
  • 整页用 FormonFinish 触发提交,onFinishFailed 不阻断——校验失败 AntD 就近红字提示)。
  • 页面顶栏(#topbar)在登录态隐藏(原型 goTo('login')topbar.display='none');本规格中登录页是独立路由,不渲染应用顶栏 / 导航壳(顶栏属 FE-02)。

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

状态 触发时机 UI 表现
companiesLoading(版本下拉加载中) 页面挂载即调 GET /api/usr/companies 拉版本项(卡片「预加载=页面加载时」) 版本 Selectloading 且禁用,placeholder="加载版本中…";用户名/密码可先填
idle(正常待输入) 版本项加载完成、表单未提交 三字段可编辑,版本 Select 展示选项;登录按钮可点;无错误提示
empty(版本列表为空) GET /api/usr/companies 返回 data 为空数组 版本 Select 显示空态 notFoundContent="暂无可用版本";版本必填校验仍生效,无法提交(见 § 5);下方轻量提示「未获取到可登录版本,请联系管理员」
submitting(表单提交中) 点击「登录」且前端校验通过,POST /api/usr/login 进行中 登录按钮 loading 且禁用,三字段禁用(防重复提交);拦截重复回车提交
error(登录失败 / 取数失败) 登录接口返回非 0 code(40001/40101/40302/42901)或网络异常;或版本接口取数失败 按 § 4 文案规则在卡片内/全局 message.error 展示对应文案;按钮恢复可点;密码框清空并聚焦(见 § 6 规则 5);版本取数失败时给「版本加载失败,点击重试」入口
success(登录成功) 登录接口返回 code=0,拿到 token + user 写入 authSlice(token + user)→ 持久化 token(见 § 6 规则 6)→ message.success("登录成功")navigate(目标路由, { replace:true })(默认 /

状态以本地组件态 + RTK authSlice 表达:submitting 用本地 useState / Form 提交态;companiesLoading / empty 用本地态;success 时 dispatch authSlicesetCredentials


4. 消费的后端端点(对齐 docs/05)

端点 方法 触发 请求 成功响应(取用字段) 失败处理
/api/usr/login POST 点击「登录」且校验通过 JSON { sUserName, password, companyId } Result<{ token, user:{ id, sUserName, sUserType, sLanguage } }>code=0 见下错误码表
/api/usr/companies GET 页面挂载时(版本下拉预加载) 无参 Result<List<{ id, sCompanyName, sVersion }>>code=0 取数失败 → 版本 Select 空 + 重试入口 + message.error("版本加载失败")

/api/usr/companies 在 docs/05 主清单未单列,但 REQ-USR-004 后端规格(docs/superpowers/specs/2026-06-01-REQ-USR-004.md § 8 D1)明确补齐该放行只读端点专供登录「版本」下拉取数;前端据此消费(见 § 7 决策 D1)。

请求 / 响应约定(依据 docs/04 § 1.4 / § 2.3 / § 2.4):

  • 统一走 frontend/src/api/request.ts 的 Axios 实例(baseURL 指向 /api,端口取 config-vars.yaml backend.http_port=5172,开发期经 Vite proxy 转发,见 § 7 决策 D2)。
  • 响应拦截器拆 Resultcode=0data;非 0 code 抛出供页面按错误码分流文案;登录端点放行、不带 Authorization 头(请求拦截器仅对已登录态注入 token)。

错误码 → 前端文案(对齐 docs/05 § REQ-USR-004)

code 含义(后端) 前端文案 展示方式
0 成功 「登录成功」 message.success 后跳转
40001 参数校验失败(缺用户名/密码/版本,或 companyId 非法) 「请填写用户名、密码并选择版本」 卡片内 Alert/message.error;正常情况下前端必填校验已拦截,此为兜底
40101 认证失败(用户名或密码错误,不区分以防枚举) 「用户名或密码错误」 message.error + 密码框清空聚焦
40302 账号已禁用(iIsVoid=1) 「该账号已被禁用,请联系管理员」 message.error
42901 登录过于频繁(连续失败超阈值被限流) 「登录尝试过于频繁,请稍后再试」 message.error
网络/超时/5xx 请求异常 「网络异常,请稍后重试」 响应拦截器兜底 message.error

40101 严格沿用后端「不区分账号不存在/密码错误」的统一提示,前端不得自行细化为「该用户不存在」等可枚举文案(卡片边界 + REQ-USR-004 后端规格规则 2/3)。


5. 业务规则前端复刻清单(逐条)

# 规则 触发时机 前端报错文案 来源
BR1 用户名必填 失焦 / 提交时 AntD Form 校验 「请输入用户名」 REQ-USR-004 输入表(用户名 必填=是)/ 原型占位「请输入你的用户名」
BR2 密码必填 失焦 / 提交时校验 「请输入密码」 REQ-USR-004 输入表(密码 必填=是)/ 原型占位「请输入你的密码」
BR3 密码输入掩码显示(星号) 输入时 —(无报错,Input.Password 默认掩码) REQ-USR-004 输入表(密码 业务规则=「输入显示星号」)
BR4 版本必填(下拉单选) 提交时校验 「请选择版本」 REQ-USR-004 输入表(版本 必填=是,输入方式=下拉单选)
BR5 版本选项来自后端公司表,页面加载时预取 页面挂载 取数失败:「版本加载失败」 REQ-USR-004 输入表(版本 显示来源=公司表、预加载=页面加载时)+ 依赖接口注记
BR6 认证失败统一提示(防账号枚举),不区分账号不存在/密码错误 登录接口返回 40101 「用户名或密码错误」 REQ-USR-004 边界 + 后端规格规则 2/3
BR7 禁用用户禁止登录 登录接口返回 40302 「该账号已被禁用,请联系管理员」 REQ-USR-004 边界(已禁用用户禁止登录)
BR8 连续失败限流提示 登录接口返回 42901 「登录尝试过于频繁,请稍后再试」 REQ-USR-004 跨字段规则(连续失败需限流)+ 后端规格规则 8
BR9 登录成功签发 token,前端据此进入受保护区 登录接口返回 code=0 —(成功,message.success("登录成功") REQ-USR-004 跨字段规则(登录成功签发访问令牌)+ docs/04 § 2.2 登录态进 store
BR10 防重复提交 submitting 期间 —(按钮 loading + 字段禁用拦截重复提交) 通用交互安全(提交中态)+ docs/04 § 2.4 错误就近处理
BR11 前端不做密码强度/明文处理,仅原样提交(明文经 HTTPS,后端 BCrypt 比对) 提交时 REQ-USR-004 后端契约(password 提交明文经 HTTPS)/ docs/04 § 1.7

前端只做输入完整性校验(必填/格式);身份真伪、禁用判定、限流计数均由后端裁决,前端按返回码渲染文案,不复制后端认证逻辑。


6. 交互与实现要点(复刻原型语义)

  1. 页面布局:复刻 .login-wrap 三段式——顶部品牌头(.login-head)、中部深蓝主视觉 + 右侧浮层登录卡(.login-hero + .login-card 绝对定位居右垂直居中)、底部版权条(.login-foot)。主视觉的网格透视 / 径向渐变背景按原型 ::before/::after 装饰性复刻(纯展示,无交互)。
  2. 登录卡定位:卡片宽约 380px,右侧 8% 处垂直居中(原型 .login-card{right:8%;top:50%;transform:translateY(-50%)}),带阴影浮层。响应式收窄时允许卡片回流居中(默认行为,见 § 7 决策 D4)。
  3. 版本下拉:原型默认值「标准版」、点击 #ver-drop 展开 .opt 列表。本规格用 AntD Select,options 全部来自 GET /api/usr/companies不硬编码「标准版」(原型为静态 demo 值);若返回项含 sVersion 则下拉 label 展示为 sCompanyName(sVersion),否则仅 sCompanyNamevalue 一律取 id,提交时作为 companyId。仅一项时默认选中该项。
  4. 提交触发:原型按钮 data-go="main" 是直接切屏的 demo 行为;本规格改为 Form.onFinish → 调 POST /api/usr/login,成功才跳转,失败留在登录页并提示。支持回车提交(AntD Form 默认)。
  5. 失败后处理40101/42901 等失败后清空密码字段并聚焦密码框,用户名与版本保留,便于重试(通用登录交互;不属硬业务规则,登记于 § 7 决策 D5)。
  6. 登录态落地(docs/04 § 2.2 / § 2.3):成功后 token + userauthSlice.setCredentials 进 Redux store;token 同时持久化(默认 localStorage,键名 xly_erp_token,供刷新后 request.ts 注入 Authorization: Bearer,见 § 7 决策 D6);随后 navigate('/', { replace:true })
  7. 路由守卫协作:登录页本身为放行路由;已登录用户访问 /login 时直接重定向到主页(避免重复登录)。守卫逻辑实现细节属路由壳(FE-02),本页只在 success 后触发导航。

7. Design Tokens 引用清单(src/styles/tokens.css,仅 var(--color-*)

约束:组件样式只用 var(--color-*),禁止硬编码 hex/rgba;色值冲突时 tokens.css 优先于 prototype/(原型内联 :root 变量为 demo 私有,不作为色值 SSoT)。AntD 主题色经 ConfigProvider 对齐 --color-primary

用途 Token 备注
登录按钮 / 主操作 / 链接强调 var(--color-primary) 对应原型 .submit 蓝色按钮;同时作为 AntD 主题 colorPrimary
登录卡背景 / 输入框背景 var(--color-form-bg-edit) 卡片与可编辑输入框底色(白)
输入框字体色 var(--color-form-fg) 输入文本色
下拉项 hover 背景 var(--color-form-bg-hover) 版本 Select 选项 hover(原型 .opt .o:hover
通用文字 / 标题 var(--color-text) 卡片标题「用户登录」、品牌副标题
次要文字 / 占位 / 页脚版权 var(--color-text-secondary) 输入占位、.login-foot 版权文本、备案号
边框 / 分隔线 / 输入框描边 var(--color-border) 输入框边框(原型 .lf 描边)、卡片描边
页面基础背景 var(--color-bg-base) .login-wrap / .login-head / .login-foot 浅灰底(原型用 #eaedf2,统一映射到基础底色 token)
错误 / 失败提示文字 var(--color-error) 校验红字、message.error 强调(AntD 默认已用主题 error,显式登记以备自定义文案样式)

主视觉 .login-hero 的深蓝渐变 / 网格透视为纯装饰,tokens.css 未定义对应深色品牌色 token;本规格将其作为登录页局部装饰样式保留在 Login 的 scoped 样式里,不引入新全局 token,也不挪用语义 token(见 § 8 决策 D7)。该装饰不承载状态语义,不违反「语义色只用 token」约束。


8. 自主决策记录(decisions)

# 问题 选择 依据 置信度
D1 版本下拉数据从哪个端点取(docs/05 主清单只列 POST /api/usr/login 消费 GET /api/usr/companies(放行只读端点) REQ-USR-004 后端规格 § 8 D1 已补齐该端点专供登录「版本」预加载;卡片输入表标版本「显示来源=公司表、预加载=页面加载时」 high
D2 开发期前端如何到达后端(跨端口 5173→5172) request.ts baseURL=/api,Vite dev proxy 把 /api 转发到 http://localhost:5172(取 config-vars backend.http_port docs/04 § 2.3「baseURL 指向后端 /api」;config-vars 锁定 dev_port=5173 / http_port=5172,proxy 是 Vite 标准跨端口方案 high
D3 登录成功后跳转目标路由 默认跳主页 /(应用落地路由,属 FE-02 范畴);用 replace:true 防回退到登录页 原型登录按钮 data-go="main" 即进主页;主页路由壳由 FE-02 定义,本页仅触发导航到根路由 high
D4 小屏 / 窄视口下登录卡布局 默认随容器回流(卡片可居中),不专门设计移动端断点 目标用户为「企业内部管理人员」桌面端 ERP(CLAUDE.md),原型为定宽桌面布局;移动适配非本 REQ 验收项 medium
D5 登录失败后是否清空密码 / 聚焦 失败(40101/42901 等)后清空密码框并聚焦,保留用户名与版本 通用安全登录交互(避免残留密码、便于重试),不与任何业务规则冲突;非硬性需求,故登记 medium
D6 token 持久化方式 localStorage(键 xly_erp_token),供刷新后 request.ts 注入;登录态同时进 Redux authSlice docs/04 § 2.2「登录态/token 用 Redux 管理」+ § 2.3「请求拦截器注入 Authorization」;持久化需跨刷新留存,localStorage 为常见取舍(无 SSoT 明确指定,故登记;如后续要求更严可换 sessionStorage/HttpOnly) medium
D7 主视觉深蓝渐变 / 网格背景的色值来源(tokens.css 无对应深色品牌 token) 作为登录页局部装饰样式保留(scoped),不新增全局 token、不挪用语义 token;语义色(按钮/文字/边框/错误)严格走 token tokens.css 仅定义语义/状态色,无品牌主视觉深色;该背景纯装饰无状态语义,局部化最小侵入;符合「语义色只用 var(--color-*)」约束 medium
D8 版本下拉 label 显示格式(含 sVersion 时) sCompanyName(sVersion),无 sVersion 时仅 sCompanyName;value 恒取 id 后端返回 {id, sCompanyName, sVersion},sVersion 可为 null;拼接展示更可辨识账套,value 用 id 对齐 login 入参 companyId high

本规格不含后端实现细节(认证比对 / 令牌签发 / 数据访问 / 库表迁移等均不在前端作用域);所有认证裁决与错误码均由后端产生,前端仅消费与渲染。