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:
@@ -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 {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取任务详情 */
|
||||
|
||||
154
src/services/modules/vip-api.ts
Normal file
154
src/services/modules/vip-api.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user