feat: 实现设置页面 + 路由系统 + JWT认证体系 + 公告优化 + 错误类型系统

## 新增功能

### 设置页面(Router + Modal 叠加)
- HashRouter 路由系统,Electron file:// 协议兼容
- 设置页以居中 Modal 叠加首页,仅 X 按钮关闭
- 主题切换 Segmented 控件(与首页 Switch 共享 Context)
- 大模型产物存储仓库路径配置(原生文件夹选择对话框)
- 企业版额外:团队创作存储仓库路径
- 系统信息展示(平台/版本/版本类型/环境)
- 版本更新检查(状态机:idle→checking→no-update/available→downloading→downloaded→error)

### JWT 认证体系
- 双 Token 机制:access_token + refresh_token
- Axios 拦截器 401 自动刷新 + 并发请求去重队列
- 启动时 refresh_token 静默续期(自动登录)
- 记住密码:Base64 编码存储,表单自动回填
- setTokenRefreshHandler 解耦模式:request.ts 不知晓 auth

### 自定义错误类型系统
- RefreshError 继承 RequestError,type = AUTH_EXPIRED
- 全局拦截器捕获 RefreshError → 自动 clearToken + AUTH_REQUIRED
- 任何位置 throw new RefreshError() 即可触发登录 Modal

### 公告卡片优化
- 长文本 Tooltip 悬浮预览
- 点击卡片弹出详情 Modal(全文保留换行)

### SettingsContext(类似 Vue Pinia)
- 集中式设置状态管理
- localStorage 持久化:ele-heixiu-output-path / ele-heixiu-team-repo-path
- useSettings() 全局消费

## 架构变更
- App.tsx → HashRouter + AppRoutes + LoginPage 弹层
- Layout.tsx = NavBar + <Outlet /> 页面壳
- HomePage.tsx 从 App.tsx 提取
- NavBar 使用 useNavigate() 真实路由导航
- useUpdater 改为模块级单例 IPC 监听器(修复 MaxListenersExceededWarning)
- electron/main.ts 新增 FILE_DIALOG IPC handler

## 依赖
- 新增 react-router-dom
This commit is contained in:
2026-06-03 19:25:15 +08:00
parent 767bb8b297
commit 3becdb85d4
34 changed files with 2253 additions and 1880 deletions

View File

@@ -0,0 +1,190 @@
import {post, get} from '../request';
// ============================================================
// AuthUrlObj — 认证模块路由常量
// 集中管理,后续接口升级 v2 等只需改此处
// ============================================================
const AuthUrlObj = {
login: '/api/v1/auth/login',
register: '/api/v1/auth/register',
sendSms: '/api/v1/auth/sms/send',
refreshToken: '/api/v1/auth/refresh',
userInfo: '/api/v1/auth/me',
changePassword: '/api/v1/auth/change-password',
resetPassword: '/api/v1/auth/reset-password',
logout: '/api/v1/auth/logout',
} as const;
// ============================================================
// 认证相关 DTO 传输模型
// ============================================================
/** 短信验证码请求体 */
export interface SendSMSRequestBody {
phone: string;
}
/** 注册请求体 */
export interface RegisterRequestBody {
// 用户名3~64 位,仅允许字母、数字、-、_
username: string;
// 密码6~128 位
password: string;
// 手机号5~32 位
phone: string;
// 短信验证码4~8 位
sms_code: string;
// 设备标识(网卡 MAC8~128 位
device_id: string;
// 设备标签/备注(可选)
device_label: string;
// 个人昵称(可选)
display_name: string;
// 邀请码(可选),填写后触发邀请佣金逻辑
referral_code: string;
}
export interface RegisterResponseBody {
access_token: string;
refresh_token: string;
token_type: string;
expires_in: 0;
refresh_expires_in: 0;
user_id: string;
username: string;
role: "user" | "admin" | "superadmin";
email: string;
balance_cent: 0;
account_type: "individual" | "company" | "employee";
display_name: string;
company_id: string;
real_name: string;
phone: string;
license_code: string;
admin_permissions: string []
}
/** 登录请求体 */
export interface LoginRequestBody {
username: string;
password: string;
device_id: string;
user_ip: string;
}
export interface LoginResponseBody {
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
refresh_expires_in: number;
user_id: string;
username: string;
role: "user" | "admin" | "superadmin";
email: string;
balance_cent: number;
account_type: "individual" | "company" | "employee";
display_name: string;
company_id: string;
real_name: string;
phone: string;
license_code: string;
admin_permissions: string[];
}
export interface RefreshTokenRequestBody {
refresh_token: string;
}
export interface RefreshTokenResponseBody {
access_token: string;
refresh_token: string;
token_type: string;
expires_in: number;
refresh_expires_in: number;
}
export interface UserInfoBody {
id: string;
username: string;
email: string;
balance_cent: number;
gift_balance_cent: number;
role: "user" | "admin" | "superadmin";
status: string;
account_type: "individual" | "company" | "employee";
display_name: string;
real_name: string;
phone: string;
company_id: string;
discount_rate: number;
subscription_plan: string;
subscription_expires_at: Date;
seat_limit: number;
last_login_at: Date;
created_at: Date;
model_package_id: number;
vip_level_id: number;
software_expires_at: Date;
referral_code: string;
admin_permissions: string[];
}
export interface ChangePasswordRequestBody {
current_password: string;
new_password: string;
}
export interface UsePhoneResetPasswordRequestBody {
phone: string;
sms_code: string;
new_password: string;
}
// ============================================================
// AuthAPI — 认证相关 API 静态方法类
// 调用方式AuthAPI.login() / AuthAPI.register() 等
// ============================================================
export class AuthAPI {
/** 登录 */
static login(data: LoginRequestBody): Promise<LoginResponseBody> {
return post<LoginResponseBody>(AuthUrlObj.login, data);
}
/** 注册 */
static register(data: RegisterRequestBody): Promise<RegisterResponseBody> {
return post<RegisterResponseBody>(AuthUrlObj.register, data);
}
/** 发送短信验证码 */
static sendSms(data: SendSMSRequestBody): Promise<void> {
return post<void>(AuthUrlObj.sendSms, data);
}
/** 刷新 Token */
static refreshToken(data: RefreshTokenRequestBody): Promise<RefreshTokenResponseBody> {
return post<RefreshTokenResponseBody>(AuthUrlObj.refreshToken, data);
}
/** 获取当前用户信息 */
static getUserInfo(): Promise<UserInfoBody> {
return get<UserInfoBody>(AuthUrlObj.userInfo);
}
/** 修改密码(已登录) */
static changePassword(data: ChangePasswordRequestBody): Promise<void> {
return post<void>(AuthUrlObj.changePassword, data);
}
/** 手机验证码重置密码 */
static resetPassword(data: UsePhoneResetPasswordRequestBody): Promise<void> {
return post<void>(AuthUrlObj.resetPassword, data);
}
/** 退出登录 */
static logout(): Promise<void> {
return post<void>(AuthUrlObj.logout);
}
}