封装评查点的相关接口,完成评查点列表的简单搜索和查询

This commit is contained in:
2025-04-02 19:17:44 +08:00
parent 706cea8705
commit 2bde2bd76e
41 changed files with 11174 additions and 609 deletions
+180 -274
View File
@@ -1,286 +1,192 @@
// app/api/client.ts
import { mockData, type MockApiResponse } from './mock';
/**
* API响应类型
*/
export type ApiResponse<T> = {
data?: T;
error?: string;
status: number;
};
data?: T;
error?: string;
status: number;
headers?: Record<string, string>; // 添加对响应头的支持
};
export type QueryParams = Record<string, string | number | boolean | undefined>;
// 获取 API 基础 URL
const API_BASE_URL = '172.16.0.119:9000/admin';
// 是否使用模拟数据(开发环境使用)
const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题
/**
* 构建完整的 API URL
*/
function buildUrl(endpoint: string, params?: QueryParams): string {
// 创建 URL 字符串
const url = new URL(
endpoint.startsWith('http') ? endpoint : `http://${API_BASE_URL}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`,
typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000'
);
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()
// 添加查询参数
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.append(key, String(value));
}
],
// 规则列表模拟数据
'/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('/');
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;
}
};
/**
* 获取模拟响应数据
*/
function getMockResponse<T>(endpoint: string): ApiResponse<T> {
console.log(`[开发模式] 使用模拟数据: ${endpoint}`);
// 移除开头的斜杠以便于匹配
const path = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 查找匹配的模拟数据
for (const mockPath in mockData) {
const normalizedMockPath = mockPath.startsWith('/') ? mockPath.substring(1) : mockPath;
if (path === normalizedMockPath || path.startsWith(normalizedMockPath + '/')) {
// 如果是详情查询 (如 /evaluation_points/1)
if (path.includes('/') && path !== normalizedMockPath) {
const id = parseInt(path.split('/')[1]);
const mockDataItem = mockData[mockPath as keyof typeof mockData] as MockApiResponse<unknown>;
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 (Array.isArray(mockDataItem.data)) {
const item = mockDataItem.data.find((item: {id: number}) => item.id === id);
if (item) {
return {
data: {
code: 0,
msg: "成功",
data: item
} as unknown as T,
status: 200
};
}
}
// 处理分页
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');
return { error: '未找到数据', status: 404 };
}
// 数据库连接授权信息
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
// 返回列表数据
return {
data: mockData[mockPath as keyof typeof 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);
}
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('Accept')) {
headers.set('Accept', 'application/json');
}
// 发送请求,5秒超时
const response = await fetchWithTimeout(url, {
...options,
headers
}, 10000);
// 解析响应
let data = null;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json') && response.status !== 204) {
data = await response.json();
}
// 收集响应头信息
const responseHeaders: Record<string, string> = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
// 检查API返回的状态码
if (data && 'code' in data && data.code !== 0) {
console.error(`API请求失败: ${data.msg || '未知错误'} - ${url}`);
return {
error: data.msg || '请求失败',
status: response.status,
headers: responseHeaders
};
}
if (!response.ok) {
console.error(`HTTP请求失败: ${response.status} - ${url}`);
return {
error: data?.msg || `请求失败: ${response.status}`,
status: response.status,
headers: responseHeaders
};
}
return {
data,
status: response.status,
headers: responseHeaders
};
} catch (error) {
console.error('API请求失败:', error);
// 如果超时或网络错误,使用模拟数据(仅开发环境)
if (process.env.NODE_ENV !== 'production') {
console.warn('自动使用模拟数据作为回退');
return getMockResponse<T>(endpoint);
}
return {
error: error instanceof Error ? error.message : '未知错误',
status: 500
};
}
}
+467 -68
View File
@@ -1,4 +1,5 @@
import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete } from '../postgrest-client';
import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete, type PostgrestParams } from '../postgrest-client';
import dayjs from 'dayjs';
/**
* 评查点列表查询参数
@@ -10,6 +11,8 @@ export interface RulesQueryParams {
groupId?: string;
isActive?: boolean;
keyword?: string;
orderBy?: string;
orderDirection?: 'asc' | 'desc';
}
/**
@@ -21,7 +24,48 @@ export interface RulesListResponse {
}
/**
* 评查点详情
* API返回的评查点详情
*/
export interface ApiRule {
id: number;
code: string;
name: string;
evaluation_point_groups_id: number;
risk: string;
description: string;
is_enabled: boolean;
evaluation_point_groups?: {
name: string;
};
references_laws: Record<string, unknown>;
extraction_config: {
type: string;
fields: string[];
prompt_setting?: {
type: string;
template: string;
};
};
evaluation_config: {
rules: Array<{
id: string;
type: string;
config: Record<string, unknown>;
}>;
logicType: string;
};
pass_message: string;
fail_message: string;
suggestion_message: string;
suggestion_message_type: string;
post_action: string;
action_config: string;
created_at: string;
updated_at: string;
}
/**
* 评查点详情(前端模型)
*/
export interface Rule {
id: string;
@@ -38,13 +82,42 @@ export interface Rule {
}
/**
* 评查点分组
* 映射API返回的评查点数据到前端模型
* @param apiRule API返回的评查点数据
* @returns 前端评查点模型
*/
export interface RuleGroup {
id: string;
name: string;
description: string;
isActive: boolean;
function mapApiRuleToFrontendModel(apiRule: ApiRule): Rule {
// 风险等级映射到优先级
const priorityMap: Record<string, string> = {
'高': 'high',
'中': 'medium',
'低': 'low'
};
// 规则类型映射(这里根据实际业务逻辑设置一个默认值)
const ruleType = 'essential'; // 实际应用中可能需要从其他字段推断
// 优先使用关联查询获取的分组名称,如果不存在则使用默认值
const groupName = apiRule.evaluation_point_groups?.name || `${apiRule.evaluation_point_groups_id}`;
return {
id: apiRule.id.toString(),
code: apiRule.code,
name: apiRule.name,
ruleType: ruleType,
groupId: apiRule.evaluation_point_groups_id.toString(),
groupName: groupName,
priority: priorityMap[apiRule.risk] || 'medium',
description: apiRule.description,
isActive: apiRule.is_enabled,
createdAt: apiRule.created_at,
updatedAt: apiRule.updated_at
};
}
// 格式化日期的辅助函数
function formatDate(dateString: string): string {
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss');
}
/**
@@ -54,68 +127,190 @@ export interface RuleGroup {
*/
export async function getRulesList(params: RulesQueryParams): Promise<{data: RulesListResponse; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 构建 PostgREST 查询参数
const { page = 1, pageSize = 10, ...filters } = params;
const offset = (page - 1) * pageSize;
// 解构并设置默认值
const {
page = 1,
pageSize = 10,
ruleType,
groupId,
isActive,
keyword,
orderBy = 'created_at',
orderDirection = 'desc'
} = params;
// 构建过滤条件
const filterArray: Record<string, unknown> = {};
if (filters.ruleType) {
filterArray['rule_type'] = `eq.${filters.ruleType}`;
}
if (filters.groupId) {
filterArray['group_id'] = `eq.${filters.groupId}`;
}
if (filters.isActive !== undefined) {
filterArray['is_active'] = `eq.${filters.isActive}`;
}
if (filters.keyword) {
// 关键字搜索
filterArray['or'] = `name.ilike.*${filters.keyword}*,code.ilike.*${filters.keyword}*`;
}
// 执行多个 API 调用(获取评查点列表、总数和评查点组)
const [rulesResponse, countResponse] = await Promise.all([
postgrestGet<Rule[]>('/evaluation_points', {
select: '*',
order: 'created_at.desc',
limit: pageSize,
offset,
filter: filterArray
}),
// 构建PostgrestParams参数
const postgrestParams: PostgrestParams = {
// 使用PostgREST资源嵌入语法获取关联数据
// 这里使用外键关系自动关联evaluation_point_groups表
select: `
id,
code,
name,
evaluation_point_groups_id,
evaluation_point_groups(name),
risk,
description,
is_enabled,
created_at,
updated_at
`,
// 设置分页
limit: pageSize,
offset: (page - 1) * pageSize,
postgrestGet<[{count: number}]>('/evaluation_points/count', {
filter: filterArray
}),
// 设置排序
order: `${orderBy}.${orderDirection}`,
]);
// 处理错误情况
if (rulesResponse.error) {
return { error: `获取评查点列表失败: ${rulesResponse.error}`, status: 500 };
}
if (countResponse.error) {
return { error: `获取评查点总数失败: ${countResponse.error}`, status: 500 };
}
// 确保数据不为 undefined,提供默认值
const rules = rulesResponse.data || [];
const totalCount = countResponse.data && countResponse.data[0] ? countResponse.data[0].count : 0;
// 成功返回数据
return {
data: {
rules,
totalCount,
// 构建过滤条件
filter: {},
// 添加额外头部,用于获取总记录数
headers: {
'Prefer': 'count=exact'
}
};
// 添加精确匹配过滤
if (groupId) {
postgrestParams.filter!['evaluation_point_groups_id'] = `eq.${groupId}`;
}
if (isActive !== undefined) {
postgrestParams.filter!['is_enabled'] = `eq.${isActive}`;
}
// 添加模糊搜索
if (keyword) {
// 使用PostgREST的or条件查询
// 同时搜索name和code字段
postgrestParams.or = [
{ name: `ilike.*${keyword}*` },
{ code: `ilike.*${keyword}*` }
];
}
// 使用postgrestGet发送请求
const response = await postgrestGet<{code: number; msg: string; data: ApiRule[]}>('evaluation_points', postgrestParams);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data || !Array.isArray(response.data.data)) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 获取API返回的所有评查点
const apiRules = response.data.data;
// 从响应头中获取总记录数(如果存在)
// 注意:这里假设总记录数会在接口响应的某个位置返回
// 实际使用时,需要根据 PostgREST 的响应格式进行调整
let totalCount = 0;
// 尝试从响应中获取总数
if (response.headers && response.headers['content-range']) {
// 例如 Content-Range: 0-9/42 表示总共有 42 条记录
const range = response.headers['content-range'];
const total = range.split('/')[1];
if (total !== '*') { // '*' 表示未知总数
totalCount = parseInt(total, 10);
}
} else {
// 如果没有响应头,则使用当前返回的数据长度作为默认值
// 这种情况下分页可能不准确
totalCount = apiRules.length;
console.warn('未能从响应中获取总记录数,使用当前页数据长度作为默认值');
}
// 打印第一个数据项来查看结构(仅用于调试)
if (apiRules.length > 0) {
console.log('评查点数据示例:', JSON.stringify(apiRules[0], null, 2));
}
// 收集所有分组ID (多进行一步查找表的操作)
const groupIds = [...new Set(apiRules.map(rule => rule.evaluation_point_groups_id))];
// 如果有分组ID,查询分组信息
const groupsMap: Record<string, string> = {};
if (groupIds.length > 0) {
try {
// 构建查询参数
const groupsParams: PostgrestParams = {
select: 'id,name',
filter: {
'id': `in.(${groupIds.join(',')})`
}
};
// 查询评查点分组表
const groupsResponse = await postgrestGet<{code: number; msg: string; data: {id: number; name: string}[]}>('evaluation_point_groups', groupsParams);
if (groupsResponse.data?.data) {
// 创建ID到名称的映射
groupsResponse.data.data.forEach(group => {
groupsMap[group.id.toString()] = group.name;
});
// 打印分组数据(仅用于调试)
console.log('分组数据:', groupsMap);
}
} catch (error) {
console.error('获取分组数据失败:', error);
// 失败不阻止主流程,使用默认分组名
}
}
// 应用本地过滤 - 只在API不支持的情况下使用
let filteredRules = [...apiRules];
// 如果有ruleType过滤(API暂不支持),在本地过滤
if (ruleType) {
// 注意:这是本地过滤,实际情况下最好在API层面支持
filteredRules = filteredRules.filter(() => {
// 实现一个映射逻辑,比如根据其他字段推导ruleType
const derivedType = 'essential'; // 此处应为实际推导逻辑
return derivedType === ruleType;
});
}
// 如果进行了本地过滤,则需要调整总记录数
if (ruleType) {
totalCount = filteredRules.length;
}
// 将API返回的数据映射到前端模型,并附加分组名称
const mappedRules = filteredRules.map(apiRule => {
// 创建修改版的apiRule,添加从分组映射获取的名称
const enhancedApiRule = {
...apiRule,
// 从映射中获取分组名称,如果不存在则使用默认值
evaluation_point_groups: {
name: groupsMap[apiRule.evaluation_point_groups_id.toString()] || `${apiRule.evaluation_point_groups_id}`
// name: apiRule.evaluation_point_groups?.name || `${apiRule.evaluation_point_groups_id}`
}
};
const rule = mapApiRuleToFrontendModel(enhancedApiRule);
// 格式化日期字段
rule.createdAt = formatDate(rule.createdAt);
rule.updatedAt = formatDate(rule.updatedAt);
return rule;
});
// 返回结果
return {
data: {
rules: mappedRules,
totalCount
}
};
} catch (error) {
console.error('获取评查点列表出错:', error);
return {
@@ -131,7 +326,86 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul
* @returns 评查点详情
*/
export async function getRule(id: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
return postgrestGet<Rule>(`/evaluation_points/${id}`);
try {
// 使用postgrestGet获取单个评查点数据
const postgrestParams: PostgrestParams = {
// 使用PostgREST资源嵌入语法获取关联数据
select: `
id,
code,
name,
evaluation_point_groups_id,
risk,
description,
is_enabled,
references_laws,
extraction_config,
evaluation_config,
pass_message,
fail_message,
suggestion_message,
suggestion_message_type,
post_action,
action_config,
created_at,
updated_at
`
};
// 获取评查点详情
const response = await postgrestGet<{code: number; msg: string; data: ApiRule}>(`evaluation_points/${id}`, postgrestParams);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
const apiRule = response.data.data;
// 获取分组信息
try {
if (apiRule.evaluation_point_groups_id) {
const groupParams: PostgrestParams = {
select: 'id,name',
filter: {
'id': `eq.${apiRule.evaluation_point_groups_id}`
}
};
// 查询评查点分组
const groupResponse = await postgrestGet<{code: number; msg: string; data: {id: number; name: string}[]}>('evaluation_point_groups', groupParams);
if (groupResponse.data?.data && groupResponse.data.data.length > 0) {
// 将分组信息添加到评查点数据中
const group = groupResponse.data.data[0];
apiRule.evaluation_point_groups = { name: group.name };
}
}
} catch (error) {
console.error('获取分组信息失败:', error);
// 忽略错误,使用默认分组名
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(apiRule);
// 格式化日期字段
rule.createdAt = formatDate(rule.createdAt);
rule.updatedAt = formatDate(rule.updatedAt);
return { data: rule };
} catch (error) {
console.error('获取评查点详情出错:', error);
return {
error: error instanceof Error ? error.message : '获取评查点详情失败',
status: 500
};
}
}
/**
@@ -140,7 +414,57 @@ export async function getRule(id: string): Promise<{data: Rule; error?: never} |
* @returns 创建的评查点
*/
export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
return postgrestPost<Rule, Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>('/evaluation_points', ruleData);
try {
// 将前端模型转换为API接受的格式
const apiRuleData = {
code: ruleData.code,
name: ruleData.name,
evaluation_point_groups_id: parseInt(ruleData.groupId),
risk: ruleData.priority === 'high' ? '高' : ruleData.priority === 'medium' ? '中' : '低',
description: ruleData.description,
is_enabled: ruleData.isActive,
// 以下是默认值,实际应用中需要根据业务逻辑设置
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: []
},
evaluation_config: {
rules: [],
logicType: "and"
},
pass_message: "",
fail_message: "",
suggestion_message: "",
suggestion_message_type: "warning",
post_action: "none",
action_config: ""
};
// 使用postgrestPost创建评查点
const response = await postgrestPost<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>('evaluation_points', apiRuleData);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(response.data.data);
return { data: rule };
} catch (error) {
console.error('创建评查点出错:', error);
return {
error: error instanceof Error ? error.message : '创建评查点失败',
status: 500
};
}
}
/**
@@ -150,7 +474,58 @@ export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'upda
* @returns 更新后的评查点
*/
export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
return postgrestPut<Rule, Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>>(`/evaluation_points/${id}`, ruleData);
try {
// 构建API接受的更新数据
const apiRuleData: Record<string, unknown> = {};
if (ruleData.code !== undefined) {
apiRuleData.code = ruleData.code;
}
if (ruleData.name !== undefined) {
apiRuleData.name = ruleData.name;
}
if (ruleData.groupId !== undefined) {
apiRuleData.evaluation_point_groups_id = parseInt(ruleData.groupId);
}
if (ruleData.priority !== undefined) {
apiRuleData.risk = ruleData.priority === 'high' ? '高' : ruleData.priority === 'medium' ? '中' : '低';
}
if (ruleData.description !== undefined) {
apiRuleData.description = ruleData.description;
}
if (ruleData.isActive !== undefined) {
apiRuleData.is_enabled = ruleData.isActive;
}
// 使用postgrestPut更新评查点
const response = await postgrestPut<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>(`evaluation_points/${id}`, apiRuleData);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(response.data.data);
return { data: rule };
} catch (error) {
console.error('更新评查点出错:', error);
return {
error: error instanceof Error ? error.message : '更新评查点失败',
status: 500
};
}
}
/**
@@ -159,7 +534,31 @@ export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' |
* @returns 删除结果
*/
export async function deleteRule(id: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
return postgrestDelete<Rule>(`/evaluation_points/${id}`);
try {
// 使用postgrestDelete删除评查点
const response = await postgrestDelete<{code: number; msg: string; data: ApiRule}>(`evaluation_points/${id}`);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(response.data.data);
return { data: rule };
} catch (error) {
console.error('删除评查点出错:', error);
return {
error: error instanceof Error ? error.message : '删除评查点失败',
status: 500
};
}
}
/**
+33
View File
@@ -0,0 +1,33 @@
/**
* 评查点分组模拟数据
*/
export const evaluationPointGroupsMockData = {
code: 0,
msg: "成功",
data: [
{
id: 1,
name: "行政处罚",
description: "行政处罚评查点",
is_enabled: true
},
{
id: 2,
name: "专卖许可证",
description: "专卖许可证评查点",
is_enabled: true
},
{
id: 3,
name: "合同审核",
description: "合同审核评查点",
is_enabled: true
},
{
id: 4,
name: "立案文书",
description: "立案文书评查点",
is_enabled: true
}
]
};
+177
View File
@@ -0,0 +1,177 @@
/**
* 评查点模拟数据
*/
export const evaluationPointsMockData = {
code: 0,
msg: "成功",
data: [
{
id: 5,
code: "LIAN_WEN_SHU_WAN_ZHENG_XING_JIAN_CHA",
name: "立案文书完整性检查",
evaluation_point_groups_id: 1,
risk: "高",
description: "须立案而没有立案文书的(2分)",
is_enabled: true,
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: ["立案报告表-负责人意见"]
},
evaluation_config: {
rules: [
{
id: "规则1",
type: "exists",
config: {
logic: "and",
fields: ["立案报告表-负责人意见"]
}
},
{
id: "规则2",
type: "regex",
config: {
field: "立案报告表-负责人意见",
pattern: "同意",
matchType: "match"
}
}
],
logicType: "and"
},
pass_message: "立案文件合格,负责人意见已填写且同意。",
fail_message: "立案文件不合格,负责人意见没有内容,请核对检查。",
suggestion_message: "请检查立案报告表中的负责人意见栏,确保已填写且同意。",
suggestion_message_type: "warning",
post_action: "none",
action_config: "",
created_at: "2023-10-01T00:00:00+00:00",
updated_at: "2023-10-01T00:00:00+00:00"
},
{
id: 6,
code: "AN_JIAN_LAI_YUAN_YI_ZHI_XING_JIAO_YAN",
name: "案件来源一致性校验",
evaluation_point_groups_id: 1,
risk: "中",
description: "没有记载案件来源或案件来源与其他文书不一致的(0.5分)",
is_enabled: true,
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: ["立案报告表-案件来源", "案件处理审批表-案件来源", "案件调查终结报告-案件来源"]
},
evaluation_config: {
rules: [
{
id: "规则1",
type: "exists",
config: {
logic: "and",
fields: ["立案报告表-案件来源", "案件处理审批表-案件来源", "案件调查终结报告-案件来源"]
}
}
],
logicType: "and"
},
pass_message: "案件来源完整",
fail_message: "案件来源信息不一致或缺失,请核对。",
suggestion_message: "请检查立案报告表、案件处理审批表和案件调查终结报告中的案件来源信息,确保一致。",
suggestion_message_type: "warning",
post_action: "none",
action_config: "",
created_at: "2023-10-01T00:00:00+00:00",
updated_at: "2023-10-01T00:00:00+00:00"
},
{
id: 7,
code: "AN_YOU_FA_AN_SHI_JIAN_HE_FA_AN_DI_DIAN_JI_ZAI_ZHUN_QUE_XING_YOU_WU",
name: "案由、发案时间和发案地点记载准确性-有无",
evaluation_point_groups_id: 1,
risk: "高",
description: "没有记载或错误记载案由、发案时间和发案地点的(1分)",
is_enabled: true,
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: ["立案报告表-案由", "立案报告表-案件来源", "案件处理审批表-案件来源", "案件调查终结报告-案件来源"],
prompt_setting: {
type: "custom",
template: "从立案报告表中抽取案由信息。"
}
},
evaluation_config: {
rules: [
{
id: "规则1",
type: "exists",
config: {
logic: "and",
fields: ["立案报告表-案由"]
}
}
],
logicType: "and"
},
pass_message: "案由、发案时间和发案地点记录准确。",
fail_message: "案由、发案时间和发案地点记录有误或缺失,请核对。",
suggestion_message: "请检查立案报告表中的案由信息,确保已填写。",
suggestion_message_type: "warning",
post_action: "none",
action_config: "",
created_at: "2023-10-01T00:00:00+00:00",
updated_at: "2023-10-01T00:00:00+00:00"
},
{
id: 8,
code: "AN_YOU_FA_AN_SHI_JIAN_HE_FA_AN_DI_DIAN_JI_ZAI_ZHUN_QUE_XING_YI_ZHI",
name: "案由、发案时间和发案地点记载准确性-一致",
evaluation_point_groups_id: 1,
risk: "高",
description: "案由、发案时间和发案地点记载不一致的(1分)",
is_enabled: true,
references_laws: {},
extraction_config: {
type: "LLM-VL",
fields: ["立案报告表-案发时间", "立案报告表-案发地址", "现场笔录-检查时间", "现场笔录-检查地点"],
prompt_setting: {
type: "system",
template: "从立案报告表和现场笔录中抽取案发时间、案发地址、检查时间和检查地点信息。"
}
},
evaluation_config: {
rules: [
{
id: "规则1",
type: "consistency",
config: {
logic: "and",
pairs: [
{
sourceField: "立案报告表-案发时间",
targetField: "现场笔录-检查时间",
compareMethod: "exact"
},
{
sourceField: "立案报告表-案发地址",
targetField: "现场笔录-检查地点",
compareMethod: "semantic"
}
]
}
}
],
logicType: "and"
},
pass_message: "案由、发案时间和发案地点记录准确。",
fail_message: "案由、发案时间和发案地点记录有误或缺失,请核对。",
suggestion_message: "请检查立案报告表和现场笔录中的案发时间和地点信息,确保一致。",
suggestion_message_type: "warning",
post_action: "none",
action_config: "",
created_at: "2023-10-01T00:00:00+00:00",
updated_at: "2023-10-01T00:00:00+00:00"
}
]
};
+22
View File
@@ -0,0 +1,22 @@
/**
* 统一导出所有模拟数据
*/
import { evaluationPointsMockData } from './evaluation_points';
import { evaluationPointGroupsMockData } from './evaluation_point_groups';
/**
* 模拟数据响应格式
*/
export interface MockApiResponse<T> {
code: number;
msg: string;
data: T;
}
/**
* 系统模拟数据
*/
export const mockData = {
'/evaluation_points': evaluationPointsMockData,
'/evaluation_point_groups': evaluationPointGroupsMockData
};
+204 -13
View File
@@ -12,7 +12,147 @@ export interface PostgrestParams {
offset?: number;
filter?: Record<string, unknown>;
schema?: string; // 指定 PostgreSQL schema
or?: Array<Record<string, unknown>> | string; // 支持OR条件查询
[key: string]: unknown; // 允许添加其他参数
headers?: Record<string, string>;
}
/**
* 打印 PostgREST 查询日志
* @param endpoint 端点
* @param params 参数
*/
function logPostgrestQuery(endpoint: string, params?: QueryParams): void {
if (process.env.NODE_ENV !== 'production') {
const baseUrl = 'http://172.16.0.119:9000/admin';
console.log('\n📦 PostgREST 查询日志 ========================');
console.log(`📦 API 端点: ${baseUrl}/${endpoint}`);
if (params && Object.keys(params).length > 0) {
console.log('📦 查询参数:');
// 以可读格式单独打印每个参数
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
if (key === 'select' && typeof value === 'string') {
// 美化 select 参数,使其看起来像 SQL 查询
console.log(` - ${key}:`);
const fields = value.replace(/\s+/g, ' ').trim().split(',');
fields.forEach(field => {
console.log(` ${field.trim()}`);
});
} else if (key === 'order' && typeof value === 'string') {
// 格式化排序参数
console.log(` - ${key}: ${value.replace(/\./g, ' ')}`); // 例如:created_at desc
} else if (key === 'or' && typeof value === 'string') {
// 格式化OR条件
console.log(` - ${key}: ${value.replace(/\./g, ' -> ').replace(/,/g, ' 或 ')}`);
} else {
console.log(` - ${key}: ${JSON.stringify(value)}`);
}
}
});
// 构建人类可读的简化URL
const readableQueryString = Object.entries(params)
.filter(([, value]) => value !== undefined)
.map(([key, value]) => {
if (key === 'select' && typeof value === 'string') {
// 简化select查询
return `${key}=...`;
}
return `${key}=${value}`;
})
.join('&');
console.log(`\n📦 可读URL: ${baseUrl}/${endpoint}${readableQueryString ? '?' + readableQueryString : ''}`);
// 格式化查询为 PostgreSQL 风格的查询
let postgrestQuery = `SELECT `;
if (params.select && typeof params.select === 'string') {
postgrestQuery += params.select.replace(/\s+/g, ' ').trim();
} else {
postgrestQuery += '*';
}
postgrestQuery += ` FROM ${endpoint}`;
const conditions: string[] = [];
// 添加过滤条件
if (params.filter) {
Object.entries(params.filter).forEach(([key, value]) => {
if (value !== undefined && typeof value === 'string') {
// 解析 eq.X, neq.X, gt.X 等
const parts = value.toString().split('.');
if (parts.length >= 2) {
const operator = parts[0];
const operatorMap: Record<string, string> = {
'eq': '=',
'neq': '!=',
'gt': '>',
'gte': '>=',
'lt': '<',
'lte': '<=',
'like': 'LIKE',
'ilike': 'ILIKE'
};
const sqlOperator = operatorMap[operator] || operator;
const value = parts.slice(1).join('.');
conditions.push(`${key} ${sqlOperator} '${value}'`);
}
}
});
}
// 添加 OR 条件
if (params.or) {
if (typeof params.or === 'string') {
if (params.or.startsWith('(') && params.or.endsWith(')')) {
// 处理 or=(cond1,cond2) 格式
const orConditions = params.or.slice(1, -1).split(',').map(cond => {
const [field, operator] = cond.split('.');
return `${field} ${operator.replace(/ilike/i, 'ILIKE')}`;
});
if (orConditions.length > 0) {
conditions.push(`(${orConditions.join(' OR ')})`);
}
} else {
// 简单 or 条件
conditions.push(params.or);
}
}
}
// 添加WHERE子句
if (conditions.length > 0) {
postgrestQuery += ` WHERE ${conditions.join(' AND ')}`;
}
// 添加ORDER BY
if (params.order && typeof params.order === 'string') {
const [field, direction] = params.order.split('.');
postgrestQuery += ` ORDER BY ${field} ${direction.toUpperCase()}`;
}
// 添加LIMIT和OFFSET
if (params.limit !== undefined) {
postgrestQuery += ` LIMIT ${params.limit}`;
}
if (params.offset !== undefined) {
postgrestQuery += ` OFFSET ${params.offset}`;
}
console.log('\n📦 等效SQL查询:');
console.log(postgrestQuery);
console.log('=========================================\n');
}
}
}
/**
@@ -47,6 +187,30 @@ export function transformParams(params: PostgrestParams): QueryParams {
result.schema = params.schema;
}
// 处理或条件 (OR) - 两种格式支持
if (params.or) {
// 如果是字符串格式 (例如 "name.ilike.*keyword*,code.ilike.*keyword*")
if (typeof params.or === 'string') {
result.or = params.or;
}
// 如果是数组格式, 转换为 PostgREST 的 or=(condition1,condition2) 格式
else if (Array.isArray(params.or)) {
const orConditions: string[] = [];
params.or.forEach(condition => {
const entries = Object.entries(condition);
if (entries.length === 1) {
const [field, operator] = entries[0];
orConditions.push(`${field}.${operator}`);
}
});
if (orConditions.length > 0) {
result.or = `(${orConditions.join(',')})`;
}
}
}
// 处理过滤条件 - PostgresREST 格式
if (params.filter) {
Object.entries(params.filter).forEach(([key, value]) => {
@@ -61,7 +225,7 @@ export function transformParams(params: PostgrestParams): QueryParams {
// 处理其他额外参数
Object.entries(params).forEach(([key, value]) => {
// 跳过已处理的特殊参数
if (!['select', 'order', 'limit', 'offset', 'filter', 'schema'].includes(key) && value !== undefined) {
if (!['select', 'order', 'limit', 'offset', 'filter', 'schema', 'or'].includes(key) && value !== undefined) {
result[key] = value as string | number | boolean;
}
});
@@ -75,15 +239,29 @@ export function transformParams(params: PostgrestParams): QueryParams {
* @param params 查询参数
* @returns 响应数据
*/
export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams): Promise<{data: T; headers?: Record<string, string>; error?: never} | {data?: never; error: string; status?: number}> {
try {
const queryParams = params ? transformParams(params) : {};
// 添加前缀表示使用 docauditai 数据库
const apiEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 确保端点没有前导斜杠,因为API_BASE_URL已经包含了路径前缀
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 打印查询信息
logPostgrestQuery(apiEndpoint, queryParams);
// 提取并移除自定义头部参数
const headers: Record<string, string> = params?.headers || {};
// 清除查询参数中的headers属性,避免将其作为URL参数
if (queryParams.headers) {
delete queryParams.headers;
}
const response = await apiRequest<T>(
apiEndpoint,
{ method: 'GET' },
{
method: 'GET',
headers: headers
},
queryParams
);
@@ -91,7 +269,11 @@ export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams
throw new Error(response.error);
}
return { data: response.data as T };
// 返回数据和响应头
return {
data: response.data as T,
headers: response.headers // 假设apiRequest函数已返回响应头
};
} catch (error) {
const apiError = handleApiError(error);
return { error: apiError.message, status: apiError.status };
@@ -106,8 +288,11 @@ export async function postgrestGet<T>(endpoint: string, params?: PostgrestParams
*/
export async function postgrestPost<T, D = Record<string, unknown>>(endpoint: string, data: D): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 添加前缀表示使用 docauditai 数据库
const apiEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 确保端点没有前导斜杠
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 打印查询信息(POST请求只打印端点)
logPostgrestQuery(apiEndpoint);
const response = await apiRequest<T>(
apiEndpoint,
@@ -139,13 +324,16 @@ export async function postgrestPost<T, D = Record<string, unknown>>(endpoint: st
*/
export async function postgrestPut<T, D = Record<string, unknown>>(endpoint: string, data: D): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 添加前缀表示使用 docauditai 数据库
const apiEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 确保端点没有前导斜杠
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 打印查询信息(PUT请求只打印端点)
logPostgrestQuery(apiEndpoint);
const response = await apiRequest<T>(
apiEndpoint,
{
method: 'PUT',
method: 'PATCH',
body: JSON.stringify(data),
headers: {
'Prefer': 'return=representation'
@@ -171,8 +359,11 @@ export async function postgrestPut<T, D = Record<string, unknown>>(endpoint: str
*/
export async function postgrestDelete<T>(endpoint: string): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 添加前缀表示使用 docauditai 数据库
const apiEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
// 确保端点没有前导斜杠
const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
// 打印查询信息(DELETE请求只打印端点)
logPostgrestQuery(apiEndpoint);
const response = await apiRequest<T>(
apiEndpoint,