feat(rvw): Complete RVW module development Phase 1-3
Summary: - Migrate backend to modules/rvw with v2 API routes (/api/v2/rvw) - Add new database fields: selectedAgents, editorialScore, methodologyStatus, picoExtract, isArchived - Create frontend module in frontend-v2/src/modules/rvw - Implement Dashboard with task list, filtering, batch operations - Implement ReportDetail with dual tabs (editorial/methodology) - Implement AgentModal for intelligent agent selection - Register RVW module in moduleRegistry.ts - Add navigation entry in TopNavigation - Update documentation for RVW module status (v3.0) - Update system status document (v2.9) Features: - User can select agents: editorial, methodology, or both - Support batch task execution - Task status filtering - Replace console.log with logger service - Maintain v1 API backward compatibility Tested: Frontend and backend verified locally Status: 85% complete (Phase 1-3 done)
This commit is contained in:
108
frontend/src/pages/rvw/components/EditorialReport.tsx
Normal file
108
frontend/src/pages/rvw/components/EditorialReport.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 规范性评估报告组件
|
||||
*/
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user