286 lines
8.9 KiB
TypeScript
286 lines
8.9 KiB
TypeScript
// app/api/client.ts
|
|
export type ApiResponse<T> = {
|
|
data?: T;
|
|
error?: string;
|
|
status: number;
|
|
};
|
|
|
|
export type QueryParams = Record<string, string | number | boolean | undefined>;
|
|
|
|
// 基本数据类型
|
|
interface BaseItem {
|
|
id: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
// 为模拟数据预定义类型定义(导出以允许其他文件引用)
|
|
// 这些类型被用在模拟数据中,虽然没有直接引用
|
|
export interface Document extends BaseItem {
|
|
name: string;
|
|
type: string;
|
|
size: number;
|
|
status: string;
|
|
uploadDate: string;
|
|
lastModified: string;
|
|
}
|
|
|
|
export interface Rule extends BaseItem {
|
|
code: string;
|
|
name: string;
|
|
ruleType: string;
|
|
groupId: string;
|
|
groupName: string;
|
|
priority: string;
|
|
description: string;
|
|
isActive: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface RuleGroup extends BaseItem {
|
|
name: string;
|
|
description: string;
|
|
isActive: boolean;
|
|
}
|
|
|
|
export interface CountItem extends BaseItem {
|
|
count: number;
|
|
}
|
|
|
|
// 获取 API 基础 URL,支持服务器端和客户端环境
|
|
const API_BASE_URL = typeof process !== 'undefined' && process.env.API_BASE_URL
|
|
? process.env.API_BASE_URL
|
|
: 'http://nas.7bm.co:54302/api/docauditai'; // 如果服务器不可用,会自动使用模拟数据
|
|
|
|
// 获取 API 访问令牌
|
|
const API_TOKEN = typeof process !== 'undefined' && process.env.API_TOKEN
|
|
? process.env.API_TOKEN
|
|
: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYXBpX3VzZXIifQ.KVLm7rZOF0MuX3MR9LqiYA14gba-MaDK_EQXrJ9u5_Y';
|
|
|
|
// 是否使用模拟数据(开发环境使用)
|
|
const USE_MOCK_DATA = true; // 设置为 true 可启用模拟数据
|
|
|
|
/**
|
|
* 构建完整的 API URL,支持服务器端和客户端环境
|
|
*/
|
|
function buildUrl(endpoint: string, params?: QueryParams): string {
|
|
// 创建 URL 字符串
|
|
const url = new URL(
|
|
endpoint.startsWith('http') ? endpoint : API_BASE_URL + endpoint,
|
|
// 服务器端使用绝对 URL,客户端使用相对 URL
|
|
typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'
|
|
);
|
|
|
|
// 添加查询参数
|
|
if (params) {
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
url.searchParams.append(key, String(value));
|
|
}
|
|
});
|
|
}
|
|
|
|
return url.toString();
|
|
}
|
|
|
|
// 超时控制
|
|
const fetchWithTimeout = async (url: string, options: RequestInit, timeout = 5000) => {
|
|
const controller = new AbortController();
|
|
const id = setTimeout(() => controller.abort(), timeout);
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
...options,
|
|
signal: controller.signal
|
|
});
|
|
clearTimeout(id);
|
|
return response;
|
|
} catch (error) {
|
|
clearTimeout(id);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// 模拟数据库响应的类型
|
|
type MockData = {
|
|
[key: string]: BaseItem[];
|
|
};
|
|
|
|
// 模拟数据库响应
|
|
const mockDataResponses: MockData = {
|
|
// 文档列表模拟数据
|
|
'/documents': [
|
|
{
|
|
id: '1',
|
|
name: '合同.pdf',
|
|
type: 'contract',
|
|
size: 1024000,
|
|
status: 'approved',
|
|
uploadDate: new Date().toISOString(),
|
|
lastModified: new Date().toISOString()
|
|
},
|
|
{
|
|
id: '2',
|
|
name: '报告.docx',
|
|
type: 'report',
|
|
size: 512000,
|
|
status: 'pending',
|
|
uploadDate: new Date().toISOString(),
|
|
lastModified: new Date().toISOString()
|
|
}
|
|
],
|
|
// 规则列表模拟数据
|
|
'/evaluation_points': [
|
|
{
|
|
id: '1',
|
|
code: 'R001',
|
|
name: '合同名称',
|
|
ruleType: 'essential',
|
|
groupId: '1',
|
|
groupName: '合同基本要素类检查',
|
|
priority: 'high',
|
|
description: '文档必须包含合同名称',
|
|
isActive: true,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
},
|
|
{
|
|
id: '2',
|
|
code: 'R002',
|
|
name: '合同编号',
|
|
ruleType: 'legal',
|
|
groupId: '2',
|
|
groupName: '销售合同专项检查',
|
|
priority: 'medium',
|
|
description: '文档必须包含合同编号',
|
|
isActive: true,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
}
|
|
],
|
|
// 评查点组列表模拟数据
|
|
'/evaluation_point_groups': [
|
|
{ id: '1', name: '合同基本要素类检查', description: '合同基本要素类检查', isActive: true },
|
|
{ id: '2', name: '销售合同专项检查', description: '销售合同专项检查', isActive: true },
|
|
{ id: '3', name: '采购合同专项检查', description: '采购合同专项检查', isActive: true },
|
|
{ id: '4', name: '专卖许可证审核规则', description: '专卖许可证审核规则', isActive: true },
|
|
{ id: '5', name: '行政处罚规范性检查', description: '行政处罚规范性检查', isActive: true }
|
|
],
|
|
// 计数查询 - 为了满足 BaseItem 类型,添加 id 字段
|
|
'/evaluation_points/count': [{ id: 'count', count: 2 }],
|
|
'/documents/count': [{ id: 'count', count: 2 }]
|
|
};
|
|
|
|
/**
|
|
* 获取模拟响应数据
|
|
* @param endpoint API端点
|
|
* @param params 查询参数
|
|
* @returns 模拟的响应数据
|
|
*/
|
|
function getMockResponse<T>(endpoint: string, params?: QueryParams): ApiResponse<T> {
|
|
console.log(`[开发模式] 使用模拟数据: ${endpoint}`);
|
|
|
|
// 移除开头的斜杠以便于匹配
|
|
const path = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
|
|
|
|
// 检查是否有匹配的路径
|
|
for (const [mockPath, mockData] of Object.entries(mockDataResponses)) {
|
|
const normalizedMockPath = mockPath.startsWith('/') ? mockPath.substring(1) : mockPath;
|
|
if (path === normalizedMockPath || path.startsWith(normalizedMockPath + '/') || path.startsWith(normalizedMockPath + '?')) {
|
|
// 如果 ID 路径参数 (如 /rules/1),返回单个项目
|
|
const pathParts = path.split('/');
|
|
const mockPathParts = normalizedMockPath.split('/');
|
|
|
|
if (pathParts.length > mockPathParts.length && !isNaN(Number(pathParts[mockPathParts.length]))) {
|
|
const id = pathParts[mockPathParts.length];
|
|
const item = mockData.find(i => i.id === id);
|
|
return item
|
|
? { data: item as unknown as T, status: 200 }
|
|
: { error: '未找到', status: 404 };
|
|
}
|
|
|
|
// 处理分页
|
|
if (params?.limit && params?.offset) {
|
|
const limit = Number(params.limit);
|
|
const offset = Number(params.offset);
|
|
const paginatedData = mockData.slice(offset, offset + limit);
|
|
return { data: paginatedData as unknown as T, status: 200 };
|
|
}
|
|
|
|
// 返回完整数据
|
|
return { data: mockData as unknown as T, status: 200 };
|
|
}
|
|
}
|
|
|
|
// 没有匹配的模拟数据
|
|
return { error: '没有匹配的模拟数据', status: 404 };
|
|
}
|
|
|
|
/**
|
|
* 通用 API 请求函数
|
|
*/
|
|
export async function apiRequest<T>(
|
|
endpoint: string,
|
|
options: RequestInit = {},
|
|
params?: QueryParams
|
|
): Promise<ApiResponse<T>> {
|
|
// 如果使用模拟数据,直接返回模拟响应
|
|
if (USE_MOCK_DATA) {
|
|
return getMockResponse<T>(endpoint, params);
|
|
}
|
|
|
|
try {
|
|
// 构建 URL
|
|
const url = buildUrl(endpoint, params);
|
|
|
|
// 设置默认请求头
|
|
const headers = new Headers(options.headers || {});
|
|
if (!headers.has('Content-Type') && options.method !== 'GET') {
|
|
headers.set('Content-Type', 'application/json');
|
|
}
|
|
|
|
// 数据库连接授权信息
|
|
if (!headers.has('Authorization')) {
|
|
headers.set('Authorization', `Bearer ${API_TOKEN}`);
|
|
}
|
|
|
|
// 发送请求,5秒超时
|
|
const response = await fetchWithTimeout(url, {
|
|
...options,
|
|
headers
|
|
}, 5000);
|
|
|
|
// 解析响应
|
|
let data = null;
|
|
const contentType = response.headers.get('content-type');
|
|
if (contentType && contentType.includes('application/json') && response.status !== 204) {
|
|
data = await response.json();
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.error(`API请求失败: ${response.status} - ${url}`);
|
|
return {
|
|
error: data?.message || `请求失败: ${response.status}`,
|
|
status: response.status
|
|
};
|
|
}
|
|
|
|
return {
|
|
data,
|
|
status: response.status
|
|
};
|
|
} catch (error) {
|
|
console.error('API请求失败:', error);
|
|
|
|
// 如果超时或网络错误,使用模拟数据(仅开发环境)
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
console.warn('自动使用模拟数据作为回退');
|
|
return getMockResponse<T>(endpoint, params);
|
|
}
|
|
|
|
return {
|
|
error: error instanceof Error ? error.message : '未知错误',
|
|
status: 500
|
|
};
|
|
}
|
|
} |