Files
ele-HeiXiu/src/services/types.ts
YoungestSongMo 3becdb85d4 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
2026-06-03 19:25:15 +08:00

94 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// API 响应通用类型
// ============================================================
/** 后端统一响应结构 */
export interface ApiResponse<T = unknown> {
/** 业务状态码0 表示成功 */
code: number;
/** 响应数据 */
data: T;
/** 提示信息 */
message: string;
/** 请求追踪 ID可选用于排查问题 */
traceId?: string;
}
/** 分页请求参数 */
export interface PaginationParams {
page: number;
pageSize: number;
}
/** 分页响应数据 */
export interface PaginatedData<T> {
/** 数据列表 */
list: T[];
/** 总条数 */
total: number;
/** 当前页码 */
page: number;
/** 每页条数 */
pageSize: number;
}
/** 分页响应包装 */
export type PaginatedResponse<T> = ApiResponse<PaginatedData<T>>;
/** 请求错误类型 */
export enum RequestErrorType {
/** 网络错误(断网等) */
NETWORK = 'NETWORK',
/** 请求超时 */
TIMEOUT = 'TIMEOUT',
/** HTTP 状态码异常4xx / 5xx */
HTTP = 'HTTP',
/** 业务错误code ≠ 0 */
BUSINESS = 'BUSINESS',
/** 请求被取消 */
CANCELLED = 'CANCELLED',
/** 认证过期refresh_token 失效,需重新登录) */
AUTH_EXPIRED = 'AUTH_EXPIRED',
}
/** 请求错误 */
export class RequestError extends Error {
type: RequestErrorType;
code?: number;
httpStatus?: number;
traceId?: string;
constructor(
type: RequestErrorType,
message: string,
options?: { code?: number; httpStatus?: number; traceId?: string },
) {
super(message);
this.name = 'RequestError';
this.type = type;
this.code = options?.code;
this.httpStatus = options?.httpStatus;
this.traceId = options?.traceId;
}
}
// ---------- 自定义错误子类(全局统一捕获 → 事件总线触发)----------
/**
* RefreshError — refresh_token 过期/无效,需重新登录。
* 全局响应拦截器自动捕获此错误并 emit AUTH_REQUIRED调用方无需手动处理。
*
* 用法throw new RefreshError('登录已过期,请重新登录');
*/
export class RefreshError extends RequestError {
constructor(message = '登录已过期,请重新登录') {
super(RequestErrorType.AUTH_EXPIRED, message);
this.name = 'RefreshError';
}
}
/** 判断一个错误是否为 RefreshError支持 instanceof 或 type 判断) */
export function isRefreshError(err: unknown): err is RefreshError {
return err instanceof RefreshError || (err as RequestError)?.type === RequestErrorType.AUTH_EXPIRED;
}