feat: 前后端联调 — 任务创建 + 账户信息 + 预估花费 (0.0.18)

- task: device_id 改为必填参数,API 层 Omit 封装 + getDeviceId() 自动注入
- pricing.ts: Python 后端 calculate_cost 完整移植,六种定价模式 + 6 个 Bug 修复
  (浮点冗余/维度匹配不一致/null 守卫/matrix 数据提取/unit_scale 等)
- ModelInputForm: 三层兜底预估消费展示 + React Hooks 顺序修复
- AccountInfo: VIP 等级列表匹配,套餐详情展示(说明/周期/席位/到期)
- 模型列表加密缓存 (safe-storage, stale-while-revalidate)
- VIP API 模块 (激活码/等级/订单/模拟支付)
- device_id 统一使用 UUID v4 持久化 (与 login/register 对齐)
This commit is contained in:
2026-06-10 19:38:26 +08:00
parent 848fdeca3d
commit 95019316b2
19 changed files with 1681 additions and 292 deletions

View File

@@ -20,6 +20,34 @@ const AuthUrlObj = {
// 认证相关 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;
@@ -53,10 +81,10 @@ export interface RegisterResponseBody {
refresh_expires_in: 0;
user_id: string;
username: string;
role: "user" | "admin" | "superadmin";
role: UserRoleTypeValue;
email: string;
balance_cent: 0;
account_type: "individual" | "company" | "employee";
account_type: UserAccountTypeValue;
display_name: string;
company_id: string;
real_name: string;
@@ -81,10 +109,10 @@ export interface LoginResponseBody {
refresh_expires_in: number;
user_id: string;
username: string;
role: "user" | "admin" | "superadmin";
role: UserRoleTypeValue;
email: string;
balance_cent: number;
account_type: "individual" | "company" | "employee";
account_type: UserAccountTypeValue;
display_name: string;
company_id: string;
real_name: string;
@@ -108,27 +136,27 @@ export interface RefreshTokenResponseBody {
export interface UserInfoBody {
id: string;
username: string;
email: string;
email: string | null;
balance_cent: number;
gift_balance_cent: number;
role: "user" | "admin" | "superadmin";
role: UserRoleTypeValue;
status: string;
account_type: "individual" | "company" | "employee";
display_name: string;
real_name: string;
account_type: UserAccountTypeValue;
display_name: string | null;
real_name: string | null;
phone: string;
company_id: string;
discount_rate: number;
company_id: string | null;
discount_rate: number | null;
subscription_plan: string;
subscription_expires_at: Date;
seat_limit: number;
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;
admin_permissions: string[];
referral_code: string | null;
admin_permissions: string[] | null;
}
export interface ChangePasswordRequestBody {

View File

@@ -115,7 +115,7 @@ export interface ModelsListResponseBody {
status: string;
priority: number;
response_mode: string;
pricing_config: Record<string, unknown>;
pricing_config: Record<string, unknown> | null;
param_schema: ParamSchemaItem[];
quota_config: Record<string, unknown> | null;
ui_config: Record<string, unknown>;

View File

@@ -11,6 +11,7 @@
// ============================================================
import {get, post} from '../request';
import {getDeviceId} from '../../utils/device';
// ============================================================
// 基础枚举 / 字面量类型
@@ -57,7 +58,7 @@ export interface TaskListRequestParams {
export interface CreatTaskRequestBody {
model_id: string;
params: Record<string, string>;
device_id?: string;
device_id: string;
tags?: string[];
}
@@ -210,9 +211,12 @@ export class TaskAPI {
return get<TaskInfoDataResponseBody>(TaskUrlObj.task, params as Record<string, unknown>);
}
/** 提交新任务 */
static submitTask(body: CreatTaskRequestBody): Promise<TaskInfoItemResponseBody> {
return post<TaskInfoItemResponseBody>(TaskUrlObj.task, body);
/** 提交新任务device_id 由 API 层自动注入 getDeviceId(),调用方无需传入) */
static submitTask(body: Omit<CreatTaskRequestBody, 'device_id'>): Promise<TaskInfoItemResponseBody> {
return post<TaskInfoItemResponseBody>(TaskUrlObj.task, {
...body,
device_id: getDeviceId(),
});
}
/** 获取任务详情 */

View File

@@ -0,0 +1,154 @@
import {post, get} from '../request';
import {type UserInfoBody} from "@/services/modules/auth";
type Nullable<T> = T | null;
// ============================================================
// VipApiUrl — VIP 模块路由常量
// ============================================================
const VipApiUrl = {
ActivateVipCode: "/api/v1/vip/activate",
GetVipLevelsList: "/api/v1/vip/levels",
VipOrders: "/api/v1/vip/orders",
MockPay: "/api/v1/vip/mock-pay",
} as const;
// ============================================================
// 激活码相关
// ============================================================
export interface ActivateVipCodeRequestParams {
code: string;
}
export type ActivateVipCodeResponseBody = UserInfoBody;
// ============================================================
// 账户类型常量
// ============================================================
export const TargetAccountType = {
Individual: "individual",
Company: "company"
} as const;
export type TargetAccountTypeValue = typeof TargetAccountType[keyof typeof TargetAccountType];
export const TargetAccountTypeLabelMap: Record<TargetAccountTypeValue, string> = {
[TargetAccountType.Individual]: "个人套餐",
[TargetAccountType.Company]: "企业套餐"
};
// ============================================================
// VIP 等级
// ============================================================
export interface VipLevelsItem {
id: number;
name: string;
level: number;
price_cent: number;
duration_days: number;
gift_balance_cent: number;
model_package_id: number;
description: Nullable<string>;
image: Nullable<string>;
is_active: number;
sort_order: number;
target_account_type: TargetAccountTypeValue;
seat_limit: Nullable<number>;
created_at: Date;
updated_at: Date;
}
// ============================================================
// VIP 订单
// ============================================================
/** VIP 订单状态常量 */
export const VipOrderStatus = {
Pending: "pending",
Paid: "paid",
Expired: "expired",
Cancelled: "cancelled",
} as const;
export type VipOrderStatusValue = typeof VipOrderStatus[keyof typeof VipOrderStatus];
export const VipOrderStatusLabelMap: Record<VipOrderStatusValue, string> = {
[VipOrderStatus.Pending]: "待支付",
[VipOrderStatus.Paid]: "已支付",
[VipOrderStatus.Expired]: "已过期",
[VipOrderStatus.Cancelled]: "已取消",
};
/** POST /api/v1/vip/orders — 创建购买订单 */
export interface CreateVipOrderRequest {
/** 购买的 VIP 等级 ID */
vip_level_id: number;
/** 支付渠道alipay | mock默认 alipay */
payment_channel?: string;
}
/** 订单列表查询参数 */
export interface VipOrderListParams {
/** 按状态筛选,可选 */
status?: VipOrderStatusValue;
/** 页码,默认 1 */
page?: number;
/** 每页条数,默认 20 */
page_size?: number;
}
/** VIP 订单读取 Schema */
export interface VipOrderRead {
id: string;
user_id: string;
vip_level_id: number;
amount_cent: number;
status: VipOrderStatusValue;
out_trade_no: string;
/** 支付宝/微信交易号 */
transaction_id: Nullable<string>;
/** 支付链接(扫码支付用) */
pay_url: Nullable<string>;
/** 订单过期时间 */
expires_at: Nullable<Date>;
/** 支付完成时间 */
paid_at: Nullable<Date>;
created_at: Date;
}
// ============================================================
// VipAPI — VIP 模块 API 静态方法类
// 调用方式VipAPI.activateVipCode() / VipAPI.getVipLevels() 等
// ============================================================
// TODO(human): 请确认 VipOrderStatus 的状态值和中文标签是否正确,根据实际后端逻辑补充/修改
export class VipAPI {
/** 使用激活码激活 VIP 权益 */
static async activateVipCode(data: ActivateVipCodeRequestParams): Promise<ActivateVipCodeResponseBody> {
return await post<ActivateVipCodeResponseBody>(VipApiUrl.ActivateVipCode, data);
}
/** 获取 VIP 等级列表(已登录时按账户类型过滤) */
static async getVipLevels(): Promise<VipLevelsItem[]> {
return await get<VipLevelsItem[]>(VipApiUrl.GetVipLevelsList);
}
/** 创建 VIP 套餐购买订单 */
static async createVipOrder(data: CreateVipOrderRequest): Promise<VipOrderRead> {
return await post<VipOrderRead>(VipApiUrl.VipOrders, data);
}
/** 查询当前用户的 VIP 订单列表 */
static async getVipOrders(params?: VipOrderListParams): Promise<VipOrderRead[]> {
return await get<VipOrderRead[]>(VipApiUrl.VipOrders, params as Record<string, unknown>);
}
/** 模拟支付成功(仅开发测试用) */
static async mockPay(orderId: string): Promise<VipOrderRead> {
return await get<VipOrderRead>(`${VipApiUrl.MockPay}/${orderId}`);
}
}