feat(asl): Complete Week 4 - Results display and Excel export with hybrid solution
Features: - Backend statistics API (cloud-native Prisma aggregation) - Results page with hybrid solution (AI consensus + human final decision) - Excel export (frontend generation, zero disk write, cloud-native) - PRISMA-style exclusion reason analysis with bar chart - Batch selection and export (3 export methods) - Fixed logic contradiction (inclusion does not show exclusion reason) - Optimized table width (870px, no horizontal scroll) Components: - Backend: screeningController.ts - add getProjectStatistics API - Frontend: ScreeningResults.tsx - complete results page (hybrid solution) - Frontend: excelExport.ts - Excel export utility (40 columns full info) - Frontend: ScreeningWorkbench.tsx - add navigation button - Utils: get-test-projects.mjs - quick test tool Architecture: - Cloud-native: backend aggregation reduces network transfer - Cloud-native: frontend Excel generation (zero file persistence) - Reuse platform: global prisma instance, logger - Performance: statistics API < 500ms, Excel export < 3s (1000 records) Documentation: - Update module status guide (add Week 4 features) - Update task breakdown (mark Week 4 completed) - Update API design spec (add statistics API) - Update database design (add field usage notes) - Create Week 4 development plan - Create Week 4 completion report - Create technical debt list Test: - End-to-end flow test passed - All features verified - Performance test passed - Cloud-native compliance verified Ref: Week 4 Development Plan Scope: ASL Module MVP - Title Abstract Screening Results Cloud-Native: Backend aggregation + Frontend Excel generation
This commit is contained in:
68
frontend-v2/src/modules/asl/hooks/useScreeningTask.ts
Normal file
68
frontend-v2/src/modules/asl/hooks/useScreeningTask.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 筛选任务轮询Hook
|
||||
* 用于获取和轮询筛选任务进度
|
||||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { aslApi } from '../api';
|
||||
|
||||
interface UseScreeningTaskOptions {
|
||||
projectId: string;
|
||||
enabled?: boolean;
|
||||
pollingInterval?: number; // 轮询间隔(毫秒),默认1000
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用筛选任务Hook
|
||||
*/
|
||||
export function useScreeningTask({
|
||||
projectId,
|
||||
enabled = true,
|
||||
pollingInterval = 1000,
|
||||
}: UseScreeningTaskOptions) {
|
||||
const { data, isLoading, error, refetch } = useQuery({
|
||||
queryKey: ['screening-task', projectId],
|
||||
queryFn: () => aslApi.getScreeningTask(projectId),
|
||||
enabled: enabled && !!projectId,
|
||||
refetchInterval: (query) => {
|
||||
const task = query.state.data?.data;
|
||||
// 如果任务已完成或失败,停止轮询
|
||||
if (task?.status === 'completed' || task?.status === 'failed') {
|
||||
return false;
|
||||
}
|
||||
return pollingInterval;
|
||||
},
|
||||
staleTime: 0, // 始终视为过时,确保轮询生效
|
||||
});
|
||||
|
||||
const task = data?.data;
|
||||
|
||||
// 计算进度百分比
|
||||
const progress = task
|
||||
? Math.round((task.processedItems / task.totalItems) * 100)
|
||||
: 0;
|
||||
|
||||
// 判断是否正在运行
|
||||
const isRunning = task?.status === 'running' || task?.status === 'pending';
|
||||
|
||||
// 判断是否已完成
|
||||
const isCompleted = task?.status === 'completed';
|
||||
|
||||
// 判断是否失败
|
||||
const isFailed = task?.status === 'failed';
|
||||
|
||||
return {
|
||||
task,
|
||||
progress,
|
||||
isRunning,
|
||||
isCompleted,
|
||||
isFailed,
|
||||
isLoading,
|
||||
error,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user