refactor(asl): ASL frontend architecture refactoring with left navigation
- feat: Create ASLLayout component with 7-module left navigation - feat: Implement Title Screening Settings page with optimized PICOS layout - feat: Add placeholder pages for Workbench and Results - fix: Fix nested routing structure for React Router v6 - fix: Resolve Spin component warning in MainLayout - fix: Add QueryClientProvider to App.tsx - style: Optimize PICOS form layout (P+I left, C+O+S right) - style: Align Inclusion/Exclusion criteria side-by-side - docs: Add architecture refactoring and routing fix reports Ref: Week 2 Frontend Development Scope: ASL module MVP - Title Abstract Screening
This commit is contained in:
267
frontend-v2/src/modules/asl/api/index.ts
Normal file
267
frontend-v2/src/modules/asl/api/index.ts
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* ASL模块 - API客户端
|
||||
*
|
||||
* 负责所有与后端的API交互
|
||||
*/
|
||||
|
||||
import type {
|
||||
ScreeningProject,
|
||||
CreateProjectRequest,
|
||||
Literature,
|
||||
ImportLiteraturesRequest,
|
||||
ScreeningResult,
|
||||
ScreeningTask,
|
||||
ApiResponse,
|
||||
PaginatedResponse,
|
||||
ProjectStatistics
|
||||
} from '../types';
|
||||
|
||||
// API基础URL
|
||||
const API_BASE_URL = '/api/v1/asl';
|
||||
|
||||
// 通用请求函数
|
||||
async function request<T = any>(
|
||||
url: string,
|
||||
options?: RequestInit
|
||||
): Promise<T> {
|
||||
const response = await fetch(`${API_BASE_URL}${url}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options?.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({
|
||||
message: 'Network error'
|
||||
}));
|
||||
throw new Error(error.message || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// ==================== 项目管理API ====================
|
||||
|
||||
/**
|
||||
* 创建筛选项目
|
||||
*/
|
||||
export async function createProject(
|
||||
data: CreateProjectRequest
|
||||
): Promise<ApiResponse<ScreeningProject>> {
|
||||
return request('/projects', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目列表
|
||||
*/
|
||||
export async function listProjects(): Promise<ApiResponse<ScreeningProject[]>> {
|
||||
return request('/projects');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目详情
|
||||
*/
|
||||
export async function getProject(
|
||||
projectId: string
|
||||
): Promise<ApiResponse<ScreeningProject>> {
|
||||
return request(`/projects/${projectId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
*/
|
||||
export async function updateProject(
|
||||
projectId: string,
|
||||
data: Partial<CreateProjectRequest>
|
||||
): Promise<ApiResponse<ScreeningProject>> {
|
||||
return request(`/projects/${projectId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
*/
|
||||
export async function deleteProject(
|
||||
projectId: string
|
||||
): Promise<ApiResponse<void>> {
|
||||
return request(`/projects/${projectId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 文献管理API ====================
|
||||
|
||||
/**
|
||||
* 批量导入文献(JSON格式)
|
||||
*/
|
||||
export async function importLiteratures(
|
||||
projectId: string,
|
||||
data: ImportLiteraturesRequest
|
||||
): Promise<ApiResponse<{
|
||||
imported: number;
|
||||
duplicates: number;
|
||||
failed: number;
|
||||
}>> {
|
||||
return request(`/projects/${projectId}/literatures/import-json`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文献列表
|
||||
*/
|
||||
export async function listLiteratures(
|
||||
projectId: string,
|
||||
params?: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
): Promise<ApiResponse<PaginatedResponse<Literature>>> {
|
||||
const queryString = new URLSearchParams(
|
||||
params as Record<string, string>
|
||||
).toString();
|
||||
return request(`/projects/${projectId}/literatures?${queryString}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文献
|
||||
*/
|
||||
export async function deleteLiterature(
|
||||
projectId: string,
|
||||
literatureId: string
|
||||
): Promise<ApiResponse<void>> {
|
||||
return request(`/projects/${projectId}/literatures/${literatureId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 筛选任务API ====================
|
||||
|
||||
/**
|
||||
* 启动筛选任务
|
||||
*/
|
||||
export async function startScreening(
|
||||
projectId: string
|
||||
): Promise<ApiResponse<{ taskId: string }>> {
|
||||
return request(`/projects/${projectId}/screening/start`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务进度
|
||||
*/
|
||||
export async function getTaskProgress(
|
||||
taskId: string
|
||||
): Promise<ApiResponse<ScreeningTask>> {
|
||||
return request(`/screening/tasks/${taskId}/progress`);
|
||||
}
|
||||
|
||||
// ==================== 筛选结果API ====================
|
||||
|
||||
/**
|
||||
* 获取筛选结果列表
|
||||
*/
|
||||
export async function getScreeningResults(
|
||||
projectId: string,
|
||||
params?: {
|
||||
conflictOnly?: boolean;
|
||||
finalDecision?: 'include' | 'exclude' | 'pending';
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
): Promise<ApiResponse<PaginatedResponse<ScreeningResult>>> {
|
||||
const queryString = new URLSearchParams(
|
||||
params as Record<string, string>
|
||||
).toString();
|
||||
return request(`/projects/${projectId}/screening/results?${queryString}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单个筛选结果
|
||||
*/
|
||||
export async function updateScreeningResult(
|
||||
resultId: string,
|
||||
data: {
|
||||
finalDecision?: 'include' | 'exclude' | 'pending';
|
||||
reviewComment?: string;
|
||||
}
|
||||
): Promise<ApiResponse<ScreeningResult>> {
|
||||
return request(`/screening/results/${resultId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新筛选结果
|
||||
*/
|
||||
export async function batchUpdateScreeningResults(data: {
|
||||
resultIds: string[];
|
||||
finalDecision: 'include' | 'exclude' | 'pending';
|
||||
decisionMethod?: string;
|
||||
reviewComment?: string;
|
||||
}): Promise<ApiResponse<{ updated: number }>> {
|
||||
return request('/screening/results/batch-update', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 导出API ====================
|
||||
|
||||
/**
|
||||
* 导出筛选结果为Excel
|
||||
*/
|
||||
export async function exportScreeningResults(
|
||||
projectId: string,
|
||||
params?: {
|
||||
filter?: 'all' | 'included' | 'excluded' | 'pending';
|
||||
}
|
||||
): Promise<Blob> {
|
||||
const queryString = new URLSearchParams(
|
||||
params as Record<string, string>
|
||||
).toString();
|
||||
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/projects/${projectId}/screening/results/export?${queryString}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
}
|
||||
|
||||
// ==================== 统计API ====================
|
||||
|
||||
/**
|
||||
* 获取项目统计信息
|
||||
*/
|
||||
export async function getProjectStatistics(
|
||||
projectId: string
|
||||
): Promise<ApiResponse<ProjectStatistics>> {
|
||||
return request(`/projects/${projectId}/statistics`);
|
||||
}
|
||||
|
||||
// ==================== 健康检查API ====================
|
||||
|
||||
/**
|
||||
* 模块健康检查
|
||||
*/
|
||||
export async function healthCheck(): Promise<ApiResponse<{
|
||||
status: string;
|
||||
module: string;
|
||||
}>> {
|
||||
return request('/health');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user