## 架构重构
- 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 不安全)
219 lines
6.2 KiB
TypeScript
219 lines
6.2 KiB
TypeScript
import {post, get} from '../request';
|
||
|
||
// ============================================================
|
||
// AuthUrlObj — 认证模块路由常量
|
||
// 集中管理,后续接口升级 v2 等只需改此处
|
||
// ============================================================
|
||
|
||
const AuthUrlObj = {
|
||
login: '/api/v1/auth/login',
|
||
register: '/api/v1/auth/register',
|
||
sendSms: '/api/v1/auth/send-sms',
|
||
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 const UserRoleTypeEnum = {
|
||
User: "user",
|
||
Admin: "admin",
|
||
SuperAdmin: "superadmin",
|
||
} as const;
|
||
|
||
export type UserRoleTypeValue = typeof UserRoleTypeEnum[keyof typeof UserRoleTypeEnum];
|
||
|
||
export const UserRoleTypeLabelMap: Record<UserRoleTypeValue, string> = {
|
||
[UserRoleTypeEnum.User]: "用户账户",
|
||
[UserRoleTypeEnum.Admin]: "管理账户",
|
||
[UserRoleTypeEnum.SuperAdmin]: "超管账户"
|
||
};
|
||
|
||
export const UserAccountTypeEnum = {
|
||
Individual: 'individual',
|
||
Company: 'company',
|
||
Employee: 'employee',
|
||
} as const;
|
||
|
||
export type UserAccountTypeValue = typeof UserAccountTypeEnum[keyof typeof UserAccountTypeEnum];
|
||
|
||
export const UserAccountTypeLabelMap: Record<UserAccountTypeValue, string> = {
|
||
[UserAccountTypeEnum.Individual]: "个人用户",
|
||
[UserAccountTypeEnum.Company]: "企业用户",
|
||
[UserAccountTypeEnum.Employee]: "员工账号"
|
||
};
|
||
|
||
/** 短信验证码请求体 */
|
||
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;
|
||
// 设备标识(网卡 MAC),8~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: UserRoleTypeValue;
|
||
email: string;
|
||
balance_cent: 0;
|
||
account_type: UserAccountTypeValue;
|
||
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: UserRoleTypeValue;
|
||
email: string;
|
||
balance_cent: number;
|
||
account_type: UserAccountTypeValue;
|
||
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 | null;
|
||
balance_cent: number;
|
||
gift_balance_cent: number;
|
||
role: UserRoleTypeValue;
|
||
status: string;
|
||
account_type: UserAccountTypeValue;
|
||
display_name: string | null;
|
||
real_name: string | null;
|
||
phone: string;
|
||
company_id: string | null;
|
||
discount_rate: number | null;
|
||
subscription_plan: string;
|
||
subscription_expires_at: Date;
|
||
seat_limit: number | null;
|
||
last_login_at: Date;
|
||
created_at: Date;
|
||
model_package_id: number;
|
||
vip_level_id: number;
|
||
software_expires_at: Date;
|
||
referral_code: string | null;
|
||
admin_permissions: string[] | null;
|
||
}
|
||
|
||
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 async login(data: LoginRequestBody): Promise<LoginResponseBody> {
|
||
return await post<LoginResponseBody>(AuthUrlObj.login, data);
|
||
}
|
||
|
||
/** 注册 */
|
||
static async register(data: RegisterRequestBody): Promise<RegisterResponseBody> {
|
||
return await post<RegisterResponseBody>(AuthUrlObj.register, data);
|
||
}
|
||
|
||
/** 发送短信验证码 */
|
||
static async sendSms(data: SendSMSRequestBody): Promise<void> {
|
||
return await post<void>(AuthUrlObj.sendSms, data);
|
||
}
|
||
|
||
/** 刷新 Token */
|
||
static async refreshToken(data: RefreshTokenRequestBody): Promise<RefreshTokenResponseBody> {
|
||
return await post<RefreshTokenResponseBody>(AuthUrlObj.refreshToken, data);
|
||
}
|
||
|
||
/** 获取当前用户信息 */
|
||
static async getUserInfo(signal?: AbortSignal): Promise<UserInfoBody> {
|
||
return await get<UserInfoBody>(AuthUrlObj.userInfo, undefined, { signal });
|
||
}
|
||
|
||
/** 修改密码(已登录) */
|
||
static async changePassword(data: ChangePasswordRequestBody): Promise<void> {
|
||
return await post<void>(AuthUrlObj.changePassword, data);
|
||
}
|
||
|
||
/** 手机验证码重置密码 */
|
||
static async resetPassword(data: UsePhoneResetPasswordRequestBody): Promise<void> {
|
||
return await post<void>(AuthUrlObj.resetPassword, data);
|
||
}
|
||
|
||
/** 退出登录 */
|
||
static async logout(): Promise<void> {
|
||
return await post<void>(AuthUrlObj.logout);
|
||
}
|
||
}
|