Files
AIclinicalresearch/frontend/src/components/review/ScoreCard.tsx
HaHafeng 8eef9e0544 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
2025-11-21 20:12:38 +08:00

132 lines
2.5 KiB
TypeScript

import { Card, Progress, Space, Typography, Tag } from 'antd';
import { TrophyOutlined, CheckCircleOutlined, WarningOutlined, CloseCircleOutlined } from '@ant-design/icons';
import { getScoreLevel } from '../../api/reviewApi';
const { Title, Text } = Typography;
interface ScoreCardProps {
title: string;
score: number;
maxScore?: number;
description?: string;
showProgress?: boolean;
size?: 'small' | 'default' | 'large';
}
/**
* 评分卡片组件
* 用于展示单项评分
*/
const ScoreCard = ({
title,
score,
maxScore = 100,
description,
showProgress = true,
size = 'default',
}: ScoreCardProps) => {
const { level, text, color } = getScoreLevel(score);
// 获取等级图标
const getLevelIcon = () => {
switch (level) {
case 'excellent':
return <TrophyOutlined style={{ color }} />;
case 'good':
return <CheckCircleOutlined style={{ color }} />;
case 'fair':
return <WarningOutlined style={{ color }} />;
case 'poor':
return <CloseCircleOutlined style={{ color }} />;
}
};
// 根据size调整字体大小
const scoreFontSize = size === 'large' ? 56 : size === 'small' ? 32 : 44;
const titleSize = size === 'large' ? 3 : size === 'small' ? 5 : 4;
return (
<Card hoverable>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
{/* 标题 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Title level={titleSize as 3 | 4 | 5} style={{ margin: 0 }}>
{title}
</Title>
<Tag icon={getLevelIcon()} color={color} style={{ margin: 0 }}>
{text}
</Tag>
</div>
{/* 分数 */}
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: scoreFontSize, fontWeight: 'bold', color }}>
{score.toFixed(1)}
</div>
<Text type="secondary" style={{ fontSize: size === 'small' ? 12 : 14 }}>
{maxScore}
</Text>
</div>
{/* 进度条 */}
{showProgress && (
<Progress
percent={Math.round((score / maxScore) * 100)}
strokeColor={color}
status="active"
/>
)}
{/* 描述 */}
{description && (
<Text type="secondary" style={{ fontSize: 12 }}>
{description}
</Text>
)}
</Space>
</Card>
);
};
export default ScoreCard;