Major Changes: - Database: Install pg_bigm/pgvector plugins, create test database - Python service: v1.0 -> v1.1, add pymupdf4llm/openpyxl/pypandoc - Node.js backend: v1.3 -> v1.7, fix pino-pretty and ES Module imports - Frontend: v1.2 -> v1.3, skip TypeScript check for deployment - Code recovery: Restore empty files from local backup Technical Fixes: - Fix pino-pretty error in production (conditional loading) - Fix ES Module import paths (add .js extensions) - Fix OSSAdapter TypeScript errors - Update Prisma Schema (63 models, 16 schemas) - Update environment variables (DATABASE_URL, EXTRACTION_SERVICE_URL, OSS) - Remove deprecated variables (REDIS_URL, DIFY_API_URL, DIFY_API_KEY) Documentation: - Create 0126 deployment folder with 8 documents - Update database development standards v2.0 - Update SAE deployment status records Deployment Status: - PostgreSQL: ai_clinical_research_test with plugins - Python: v1.1 @ 172.17.173.84:8000 - Backend: v1.7 @ 172.17.173.89:3001 - Frontend: v1.3 @ 172.17.173.90:80 Tested: All services running successfully on SAE
138 lines
4.0 KiB
TypeScript
138 lines
4.0 KiB
TypeScript
/**
|
||
* 规范性评估报告组件
|
||
*/
|
||
import { AlertTriangle, CheckCircle, XCircle } from 'lucide-react';
|
||
import type { EditorialReviewResult } from '../types';
|
||
import ScoreRing from './ScoreRing';
|
||
|
||
interface EditorialReportProps {
|
||
data: EditorialReviewResult;
|
||
}
|
||
|
||
export default function EditorialReport({ data }: EditorialReportProps) {
|
||
const getStatusIcon = (status: 'pass' | 'warning' | 'fail') => {
|
||
switch (status) {
|
||
case 'pass':
|
||
return <CheckCircle className="w-5 h-5 text-green-500" />;
|
||
case 'warning':
|
||
return <AlertTriangle className="w-5 h-5 text-amber-500" />;
|
||
case 'fail':
|
||
return <XCircle className="w-5 h-5 text-red-500" />;
|
||
}
|
||
};
|
||
|
||
const getStatusTag = (status: 'pass' | 'warning' | 'fail') => {
|
||
switch (status) {
|
||
case 'pass':
|
||
return <span className="tag tag-green">通过</span>;
|
||
case 'warning':
|
||
return <span className="tag tag-amber">警告</span>;
|
||
case 'fail':
|
||
return <span className="tag tag-red">不通过</span>;
|
||
}
|
||
};
|
||
|
||
const getItemBgClass = (status: 'pass' | 'warning' | 'fail') => {
|
||
switch (status) {
|
||
case 'pass':
|
||
return '';
|
||
case 'warning':
|
||
return 'bg-amber-50/50';
|
||
case 'fail':
|
||
return 'bg-red-50/50';
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6 fade-in">
|
||
{/* 总分卡片 */}
|
||
<div className={`bg-white p-6 rounded-2xl shadow-sm border flex items-center gap-8 ${
|
||
data.overall_score >= 80 ? 'border-green-200' :
|
||
data.overall_score >= 60 ? 'border-amber-200' : 'border-red-200'
|
||
}`}>
|
||
<ScoreRing score={data.overall_score} size="medium" />
|
||
<div className="flex-1">
|
||
<h3 className="font-bold text-lg text-slate-800">
|
||
{data.overall_score >= 80 ? '基本符合稿约规范' :
|
||
data.overall_score >= 60 ? '部分符合稿约规范' : '不符合稿约规范'}
|
||
</h3>
|
||
<p className="text-slate-600 text-sm mt-1">{data.summary}</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 检测详情 */}
|
||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||
<div className="p-4 bg-slate-50 border-b border-gray-200 font-bold text-sm text-slate-700">
|
||
检测详情({data.items.length}项)
|
||
</div>
|
||
|
||
<div className="divide-y divide-gray-100">
|
||
{data.items.map((item, index) => (
|
||
<div key={index} className={`p-5 ${getItemBgClass(item.status)}`}>
|
||
<div className="flex gap-4">
|
||
<div className="mt-1">{getStatusIcon(item.status)}</div>
|
||
<div className="flex-1">
|
||
<div className="flex justify-between items-start">
|
||
<h4 className="font-bold text-sm text-slate-800">{item.criterion}</h4>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-xs text-slate-500">{item.score}分</span>
|
||
{getStatusTag(item.status)}
|
||
</div>
|
||
</div>
|
||
|
||
{item.issues && item.issues.length > 0 && (
|
||
<div className="mt-3 space-y-1">
|
||
{item.issues.map((issue, i) => (
|
||
<p key={i} className="text-sm text-slate-600">• {issue}</p>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{item.suggestions && item.suggestions.length > 0 && (
|
||
<div className="mt-3 bg-white border border-gray-200 p-3 rounded-lg">
|
||
<p className="text-xs font-bold text-slate-500 mb-1">建议:</p>
|
||
{item.suggestions.map((suggestion, i) => (
|
||
<p key={i} className="text-xs text-slate-600">• {suggestion}</p>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|