## 新增功能 ### 设置页面(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
94 lines
2.6 KiB
TypeScript
94 lines
2.6 KiB
TypeScript
// ============================================================
|
||
// 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;
|
||
}
|