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:
2026-01-07 22:39:08 +08:00
parent 06028c6952
commit 179afa2c6b
226 changed files with 5860 additions and 21 deletions

View File

@@ -0,0 +1,113 @@
/**
* 方法学评估报告组件
*/
import { XCircle, AlertTriangle, CheckCircle } from 'lucide-react';
import type { MethodologyReviewResult } from '../types';
import ScoreRing from './ScoreRing';
interface MethodologyReportProps {
data: MethodologyReviewResult;
}
export default function MethodologyReport({ data }: MethodologyReportProps) {
const getSeverityStyle = (severity: 'major' | 'minor') => {
return severity === 'major'
? { border: 'border-red-200', bg: 'bg-red-50', icon: <XCircle className="w-4 h-4 text-red-500" />, label: '严重' }
: { border: 'border-amber-200', bg: 'bg-amber-50', icon: <AlertTriangle className="w-4 h-4 text-amber-500" />, label: '轻微' };
};
const getOverallStatus = () => {
if (data.overall_score >= 80) return { label: '通过', color: 'text-green-700', bg: 'bg-green-50' };
if (data.overall_score >= 60) return { label: '存疑', color: 'text-amber-700', bg: 'bg-amber-50' };
return { label: '不通过', color: 'text-red-700', bg: 'bg-red-50' };
};
const status = getOverallStatus();
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 flex items-center gap-2">
<span className={`text-sm px-2 py-0.5 rounded ${status.bg} ${status.color}`}>
{status.label}
</span>
</h3>
<p className="text-slate-600 text-sm mt-1">{data.summary}</p>
</div>
</div>
{/* 分项详情 */}
{data.parts.map((part, partIndex) => (
<div key={partIndex} 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 flex justify-between items-center">
<span className="font-bold text-sm text-slate-700">{part.part}</span>
<div className="flex items-center gap-2">
<span className="text-xs text-slate-500">{part.score}</span>
{part.issues.length === 0 ? (
<span className="tag tag-green flex items-center gap-1">
<CheckCircle className="w-3 h-3" />
</span>
) : (
<span className="tag tag-amber">
{part.issues.length}
</span>
)}
</div>
</div>
{part.issues.length > 0 ? (
<div className="divide-y divide-gray-100">
{part.issues.map((issue, issueIndex) => {
const severity = getSeverityStyle(issue.severity);
return (
<div key={issueIndex} className={`p-5 ${severity.bg}`}>
<div className="flex gap-3">
<div className="mt-0.5">{severity.icon}</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="font-bold text-sm text-slate-800">{issue.type}</span>
<span className={`text-xs px-1.5 py-0.5 rounded ${
issue.severity === 'major' ? 'bg-red-100 text-red-700' : 'bg-amber-100 text-amber-700'
}`}>
{severity.label}
</span>
</div>
<p className="text-sm text-slate-600">{issue.description}</p>
{issue.location && (
<p className="text-xs text-slate-400 mt-1">
{issue.location}
</p>
)}
{issue.suggestion && (
<div className="mt-3 bg-white border border-gray-200 p-3 rounded-lg">
<p className="text-xs text-slate-600">
<strong></strong>{issue.suggestion}
</p>
</div>
)}
</div>
</div>
</div>
);
})}
</div>
) : (
<div className="p-6 text-center text-slate-500 text-sm">
<CheckCircle className="w-8 h-8 text-green-400 mx-auto mb-2" />
</div>
)}
</div>
))}
</div>
);
}