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:
2025-11-18 21:51:51 +08:00
parent e3e7e028e8
commit 3634933ece
213 changed files with 20054 additions and 442 deletions

View 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');
}