新增: - ForgotPasswordModal — 手机号+短信验证码重置密码 (AuthAPI.resetPassword) - ChangePasswordModal — 当前密码验证后修改密码 (AuthAPI.changePassword) - useSmsCooldown Hook — 通用短信发送冷却逻辑,供注册/忘记密码复用 - PasswordSetting — 设置页账号安全区块,双选项:修改密码 / 短信验证码重置 修改: - LoginForm \"忘记密码?\" 链接接入 ForgotPasswordModal(替换 TODO) - LoginPage 移除 edition 业务版本 UI 区分,登录/注册 Tab 全局统一 - 设置页 Section 组件移入 blocks/ 子目录(git mv 保留历史) - auth.ts sendSms URL 修正: /sms/send → /send-sms 清理: - settings/index.ts 移除 4 个未使用 re-export,精简为仅 SettingsPage - navbar/index.ts 移除未使用 re-export,精简为仅 NavBar 文档: - WORKLOG.md 新增 2026-06-08 工作日志 - ARCHITECTURE.md / PROJECT.md 更新设置页目录结构与新组件 - TODO.md 忘记密码流程标记已完成 + 短信人机验证列入后期优化
11 KiB
11 KiB
船长 · HeiXiu — 前端架构文档
最后更新:2026-06-08
目录结构
src/
├── components/ # 全局组件(Provider、导航栏、通用组件)
│ ├── AppProvider.tsx # 应用根 Provider:登录态 + 平台/版本 + 自动登录
│ ├── ThemeProvider.tsx # 主题 Provider:亮/暗切换 + Ant Design ConfigProvider
│ ├── navbar/ # 自定义导航栏(替代系统菜单)
│ └── common/ # 通用组件(LegalTextModal 等)
├── contexts/ # React Context 定义 + Consumer Hook
│ └── app-context.ts # AppContextValue 类型 + useAppContext()
├── hooks/ # 通用 Hook
│ ├── use-theme.ts # 主题切换
│ └── use-updater.ts # 版本更新
├── pages/ # 页面
│ ├── login/ # 登录/注册/忘记密码/修改密码
│ │ ├── index.tsx # LoginPage — Modal 弹层
│ │ ├── components/ # LoginForm / RegisterForm / ForgotPasswordModal / ChangePasswordModal / FormField
│ │ ├── config/ # 字段配置 / 校验规则
│ │ ├── hooks/ # use-auth-state / use-sms-cooldown
│ │ └── types.ts # LoginFormValues / RegisterFormValues / FieldConfig
│ └── settings/ # 设置面板(主题 / 存储 / 账号安全 / 系统信息 / 更新)
│ ├── SettingsPage.tsx # 设置 Modal 主页面
│ ├── index.ts # barrel export
│ └── blocks/ # 设置子区块组件
│ ├── ThemeSetting.tsx
│ ├── StoragePathSetting.tsx
│ ├── SystemInfoSetting.tsx
│ ├── UpdateSetting.tsx
│ └── PasswordSetting.tsx
├── services/ # HTTP 层 + API 模块
│ ├── request.ts # Axios 封装(通用 HTTP 层,不知晓 auth)
│ ├── auth-token.ts # Token 生命周期(refresh_token 存取 + 401 刷新回调)
│ ├── types.ts # ApiResponse / RequestError / 分页类型
│ └── modules/ # API 模块(一个模块一个文件)
│ ├── index.ts # barrel 统一导出
│ ├── auth.ts # AuthAPI class + DTO 类型 + AuthUrlObj
│ └── announcement.ts
├── theme/ # Tailwind + Ant Design 主题
├── utils/ # 工具函数
│ ├── event-bus.ts # 发布-订阅事件总线
│ ├── device.ts # getDeviceId() — 设备 UUID
│ ├── platform.ts # 平台检测
│ └── ipc.ts # Electron IPC 安全封装
└── assets/ # 静态资源
分层架构
┌─────────────────────────────────────────────────┐
│ UI 层(React 组件) │
│ AppProvider / LoginPage / NavBar / ... │
│ 职责:渲染 + 用户交互 + 消费 Context │
├─────────────────────────────────────────────────┤
│ 状态层(Context + Hooks) │
│ AppContext / useAuthState / event-bus │
│ 职责:跨组件共享状态 + 跨模块事件通信 │
├─────────────────────────────────────────────────┤
│ 服务层(API + Token) │
│ AuthAPI / auth-token / request.ts │
│ 职责:HTTP 通信 + Token 生命周期 + 401 重试 │
├─────────────────────────────────────────────────┤
│ 存储层 │
│ localStorage(access_token / refresh_token / │
│ device-id / auth 偏好) │
└─────────────────────────────────────────────────┘
层间依赖规则
- UI 层 → 可以 import 状态层 + 服务层
- 状态层 → 可以 import 服务层
- 服务层 → 可以 import 工具层(event-bus),不 import UI 或状态层
- request.ts → 通用 HTTP 层,不 import 任何 auth 模块(通过
setTokenRefreshHandler解耦)
核心流程
1. 登录
LoginForm.onSubmit(values)
→ LoginPage.handleLogin
→ AuthAPI.login({ username, password, device_id, user_ip })
→ AppProvider.login(auth)
├─ setToken(access_token) → localStorage
├─ setStoredRefreshToken(...) → localStorage
├─ setUser(auth) → state
├─ setIsLoggedIn(true) → state
└─ emit(LOGIN_SUCCESS, auth) → 事件总线
→ onClose() → 关闭登录弹窗
2. 自动登录(静默 refresh)
App 启动 → AppProvider useEffect
→ getAutoLoginFlag() === true ?
→ 是 → getStoredRefreshToken()
→ AuthAPI.refreshToken({ refresh_token })
✅ 成功 → setToken + setUser + emit(LOGIN_SUCCESS) (无弹窗)
❌ 失败 → clearStoredRefreshToken + clearSavedAuth (下次需手动登录)
→ 否 → 跳过
3. 401 → 刷新 Token → 自动重试
任意请求发起
→ 服务端返回 401(业务码 401 或 HTTP 401)
→ request.ts handle401(config)
→ refreshHandler 已注册?
→ 是 → 调 refreshHandler()
→ auth-token.ts 回调
→ AuthAPI.refreshToken({ refresh_token })
✅ 成功 → 用新 token 重试原请求(用户完全无感)
❌ 失败 → clearToken + emit(AUTH_REQUIRED) → 弹出登录框
→ 否 → clearToken + emit(AUTH_REQUIRED) → 弹出登录框
并发 401 处理:
请求 A 收到 401 → 开始 refresh
请求 B 同时收到 401 → 发现 isRefreshing=true → 加入队列等待
refresh 完成 → 新 token 广播给所有排队请求 → 全部重试
4. 退出登录
NavBar / 设置页 → AppProvider.logout()
├─ clearToken()
├─ clearStoredRefreshToken()
├─ setUser(null)
├─ setIsLoggedIn(false)
└─ emit(LOGOUT)
关键模块说明
request.ts(通用 HTTP 层)
- 职责:Axios 实例 + 拦截器 +
get/post/put/del+ 错误分类 - 不依赖任何 auth 模块
- 暴露
setTokenRefreshHandler(fn)供 auth-token.ts 注册 401 回调 - 暴露
getToken()/setToken()/clearToken()供外部存取 access_token - 401 拦截逻辑内置重试队列,支持并发请求去重
auth-token.ts(Token 生命周期)
- 职责:refresh_token 的存取 + 注册
handle401回调 initAuthRefresh()在 AppProvider 挂载时调用一次- 回调内部调用
AuthAPI.refreshToken(),成则更新双 token,败则清除
event-bus.ts(事件总线)
- 纯发布-订阅,不依赖任何框架
- 用于跨模块解耦通信(如 axios 拦截器无法直接用 React Context)
- 预定义事件:
AUTH_REQUIRED/SHOW_LOGIN/LOGIN_SUCCESS/LOGOUT/TASK_SUBMITTED/OPEN_SETTINGS
页面叠加层模式(QT 多窗口模型映射)
本项目是 Python QT 桌面应用的 Web 重构版。QT 应用以"新开窗口"为常态,原窗口不因新窗口打开而销毁。Web 端对应原则:
| QT 原版 | Web 对应 | 实现方式 |
|---|---|---|
| 新开窗口叠加在主窗口之上 | Modal / Drawer | 事件总线驱动,主页面不卸载 |
| Tab 切换 / 视图替换 | React Router | 跨页状态放 Provider 或 localStorage |
当前叠加层:
- LoginPage — Modal,事件总线
SHOW_LOGIN/AUTH_REQUIRED触发 - SettingsPage — Modal,事件总线
OPEN_SETTINGS触发 - AnnouncementDrawer — Drawer,NavBar 内部 state 控制
反模式警告:叠加层绝不应通过路由跳转实现。navigate('/xxx') + useLocation 判断显隐会导致原有页面卸载,丢失所有组件状态。
API 模块规范
每个 services/modules/*.ts 遵循统一范式:
XxxUrlObj— 模块路由常量(不 export,模块私有)- DTO 类型 — 请求体/响应体 interface
XxxAPIclass — 静态方法,调用方式XxxAPI.method()
示例:
// auth.ts
const AuthUrlObj = {login: '/api/v1/auth/login', ...} as const;
export interface LoginRequestBody {}
export interface LoginResponseBody {}
export class AuthAPI {
static login(data: LoginRequestBody): Promise<LoginResponseBody> {
return post(AuthUrlObj.login, data);
}
}
数据存储(localStorage)
| Key | 类型 | 说明 |
|---|---|---|
ele-heixiu-token |
string |
JWT access_token |
ele-heixiu-refresh-token |
string |
JWT refresh_token |
ele-heixiu-device-id |
string(UUID v4) |
设备唯一标识,首次自动生成 |
ele-heixiu-auth |
{ username, remember, autoLogin } |
记住账号偏好(不存密码) |
环境变量
主进程加载顺序(由高到低优先级)
- OS 环境变量 —
set KEY=VALUE && .\app.exe,不覆盖已有值 .env.local— 不进 git,个人覆盖(打包后连测试服务器等特殊场景).env.{mode}— 开发:.env.development,构建:.env.production- 代码默认值 —
updater.ts内getApiBaseUrl()的 fallback
安全说明:
UPDATE_API_URL是公开的 API 端点地址,不是密钥。反编译 ASAR 可看到,但这不影响安全——应用本身就要连到这个地址。真正的敏感信息(JWT token、密码)是运行时获取的,不写死在代码中。
渲染进程(Vite 编译时注入)
| 变量 | 开发值 | 生产值 |
|---|---|---|
VITE_API_BASE_URL |
http://8.160.179.64:8000 |
https://www.heixiu.net |
VITE_EDITION |
personal |
personal / enterprise |
主进程(Node.js 运行时读取)
| 变量 | 开发值(.env.development) | 生产值(.env.production) | 默认值 |
|---|---|---|---|
UPDATE_API_URL |
http://127.0.0.1:8000 |
https://www.heixiu.com |
https://www.heixiu.com |
EDITION |
personal |
personal |
personal |