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
69 lines
1.5 KiB
TypeScript
69 lines
1.5 KiB
TypeScript
/**
|
||
* 筛选任务轮询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,
|
||
};
|
||
}
|
||
|
||
|
||
|
||
|