Files
ele-HeiXiu/src/services/types.ts
YoungestSongMo 4237dcbaf6 feat: 数据获取架构重构 + Mac指纹采集 + Token刷新容错 + 账单页面 (0.0.19)
## 架构重构
  - useAsyncData 集成 AbortController:cleanup 时真正中断 HTTP 请求,
    不再仅丢弃响应(解决 StrictMode 双重请求问题)
  - API 模块统一 signal?: AbortSignal 参数,透传 axios config
  - use-api.ts 成为数据 Hook 集中管理中心,新增 useAccountInfo、
    useBillingBalance、useBillingLedger
  - 页面组件改为纯渲染:const {data,loading,error,refetch} = useXxx()

  ## 新功能
  - Mac 硬件指纹采集(electron/preload/device.ts):
    ioreg(UUID) + sysctl(CPU) + system_profiler(HDD三级回退)
  - 账单页面 BillingInfo(余额卡片 + 账单表格 + 分页)
  - PageDispatcher 注册 /billing 页面(FloatingPanel 960×680)
  - 共享工具函数 centToYuan / formatDate(src/utils/display.ts)

  ## Bug 修复
  - Token 刷新容错:网络错误时保留 refresh_token,等网络恢复后重试;
    仅 RefreshError(token 过期)时清除(auth-token.ts)
  - AppProvider 自动登录时 nullable 字段默认值(email→''、admin_permissions→[])
  - AccountInfo 重构消除 ~40 行手写状态管理代码

  ## 代码质量
  - AccountInfo 消除 centToYuan/formatDate 重复定义
  - BillingInfo 标题层级精简(移除冗余页面/Card标题)
  - antd Table align 字面量类型修正('left' as const)
  - billing.ts signal 参数位置修复(params→config)
  - modelFirstFetchDone 模块级变量移除(StrictMode 不安全)
2026-06-11 20:08:44 +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;
}