适配后端统一模型 ID、协议字段、UUID 自动生成和结构化错误响应: - 类型定义:Provider 新增 protocol 字段,Model 新增 unifiedId,CreateModelInput 移除 id - API 客户端:提取结构化错误响应中的错误码 - 供应商管理:添加协议选择下拉框和表格列 - 模型管理:移除 ID 输入,显示统一模型 ID(只读) - Hooks:错误码映射为友好中文消息 - 测试:所有组件测试通过,mock 数据适配新字段 - 文档:更新 README 说明协议字段和统一模型 ID
82 lines
2.2 KiB
TypeScript
82 lines
2.2 KiB
TypeScript
import { ApiError } from '@/types';
|
|
|
|
const API_BASE = import.meta.env.VITE_API_BASE || '';
|
|
|
|
function toCamelCase(str: string): string {
|
|
return str.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
}
|
|
|
|
function toSnakeCase(str: string): string {
|
|
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
}
|
|
|
|
function transformKeys<T>(obj: unknown, transformer: (key: string) => string): T {
|
|
if (Array.isArray(obj)) {
|
|
return obj.map((item) => transformKeys(item, transformer)) as T;
|
|
}
|
|
if (obj !== null && typeof obj === 'object') {
|
|
const result: Record<string, unknown> = {};
|
|
for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
|
|
result[transformer(key)] = transformKeys(value, transformer);
|
|
}
|
|
return result as T;
|
|
}
|
|
return obj as T;
|
|
}
|
|
|
|
export function fromApi<T>(data: unknown): T {
|
|
return transformKeys<T>(data, toCamelCase);
|
|
}
|
|
|
|
export function toApi<T>(data: unknown): T {
|
|
return transformKeys<T>(data, toSnakeCase);
|
|
}
|
|
|
|
export async function request<T>(
|
|
method: string,
|
|
path: string,
|
|
body?: unknown,
|
|
): Promise<T> {
|
|
const url = `${API_BASE}${path}`;
|
|
const options: RequestInit = {
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
};
|
|
|
|
if (body !== undefined) {
|
|
options.body = JSON.stringify(toApi(body));
|
|
}
|
|
|
|
const response = await fetch(url, options);
|
|
|
|
if (!response.ok) {
|
|
let message = `请求失败 (${response.status})`;
|
|
let code: string | undefined;
|
|
try {
|
|
const errorData = await response.json();
|
|
if (typeof errorData === 'object' && errorData !== null) {
|
|
// 提取结构化错误响应
|
|
if ('error' in errorData && typeof errorData.error === 'string') {
|
|
message = errorData.error;
|
|
} else if ('message' in errorData && typeof errorData.message === 'string') {
|
|
message = errorData.message;
|
|
}
|
|
// 提取错误码
|
|
if ('code' in errorData && typeof errorData.code === 'string') {
|
|
code = errorData.code;
|
|
}
|
|
}
|
|
} catch {
|
|
// ignore JSON parse error
|
|
}
|
|
throw new ApiError(response.status, message, code);
|
|
}
|
|
|
|
if (response.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
|
|
const data = await response.json();
|
|
return fromApi<T>(data);
|
|
}
|