feat(rvw): Complete Phase 4-5 - Bug fixes and Word export

Summary:
- Fix methodology score display issue in task list (show score instead of 'warn')
- Add methodology_score field to database schema
- Fix report display when only methodology agent is selected
- Implement Word document export using docx library
- Update documentation to v3.0/v3.1

Backend changes:
- Add methodologyScore to Prisma schema and TaskSummary type
- Update reviewWorker to save methodologyScore
- Update getTaskList to return methodologyScore

Frontend changes:
- Install docx and file-saver libraries
- Implement handleExportReport with Word generation
- Fix activeTab auto-selection based on available data
- Add proper imports for docx components

Documentation:
- Update RVW module status to 90% (Phase 1-5 complete)
- Update system status document to v3.0

Tested: All review workflows verified, Word export functional
This commit is contained in:
2026-01-10 22:52:15 +08:00
parent 179afa2c6b
commit 440f75255e
237 changed files with 3942 additions and 657 deletions

View File

@@ -11,6 +11,7 @@ import {
BatchToolbar,
AgentModal,
ReportDetail,
TaskDetail,
} from './components';
import * as api from './api';
import type { ReviewTask, ReviewReport, TaskFilters, AgentType } from './types';
@@ -28,6 +29,10 @@ export default function Dashboard() {
// 报告详情
const [reportDetail, setReportDetail] = useState<ReviewReport | null>(null);
// 任务详情(支持进度显示)
const [viewingTask, setViewingTask] = useState<ReviewTask | null>(null);
const [currentJobId, setCurrentJobId] = useState<string | null>(null);
// ==================== 数据加载 ====================
const loadTasks = useCallback(async () => {
@@ -114,12 +119,25 @@ export default function Dashboard() {
};
const handleConfirmRun = async (agents: AgentType[]) => {
// 🔥 保存到局部变量避免onClose后丢失
const taskToRun = pendingTaskForRun;
// 立即关闭弹窗
setAgentModalVisible(false);
setPendingTaskForRun(null);
try {
if (pendingTaskForRun) {
// 单个任务
if (taskToRun) {
// 单个任务 - 启动后跳转到详情页显示进度
message.loading({ content: '正在启动审查...', key: 'run' });
await api.runTask(pendingTaskForRun.id, agents);
const { jobId } = await api.runTask(taskToRun.id, agents);
message.success({ content: '审查已启动', key: 'run', duration: 2 });
// 更新任务状态后跳转到详情页传递jobId
const updatedTask = await api.getTask(taskToRun.id);
setCurrentJobId(jobId);
setViewingTask(updatedTask);
return;
} else {
// 批量任务
const pendingIds = selectedIds.filter(id => {
@@ -145,13 +163,22 @@ export default function Dashboard() {
};
const handleViewReport = async (task: ReviewTask) => {
// 直接使用TaskDetail视图支持进度和报告
setViewingTask(task);
};
const handleDeleteTask = async (task: ReviewTask) => {
if (!window.confirm(`确定要删除 "${task.fileName}" 吗?`)) {
return;
}
try {
message.loading({ content: '加载报告中...', key: 'report' });
const report = await api.getTaskReport(task.id);
setReportDetail(report);
message.destroy('report');
message.loading({ content: '正在删除...', key: 'delete' });
await api.deleteTask(task.id);
message.success({ content: '删除成功', key: 'delete', duration: 2 });
loadTasks();
} catch (error: any) {
message.error({ content: '加载报告失败', key: 'report', duration: 3 });
message.error({ content: error.message || '删除失败', key: 'delete', duration: 3 });
}
};
@@ -159,9 +186,29 @@ export default function Dashboard() {
setReportDetail(null);
};
// 返回列表并刷新
const handleBackFromDetail = () => {
setViewingTask(null);
setCurrentJobId(null);
loadTasks();
};
// ==================== 渲染 ====================
// 报告详情视图
// 任务详情视图(支持进度显示)
if (viewingTask) {
return (
<div className="h-full flex overflow-hidden bg-slate-50">
<Sidebar
currentView={currentView}
onViewChange={setCurrentView}
/>
<TaskDetail task={viewingTask} jobId={currentJobId} onBack={handleBackFromDetail} />
</div>
);
}
// 报告详情视图(旧版,保留兼容)
if (reportDetail) {
return (
<div className="h-full flex overflow-hidden bg-slate-50">
@@ -207,6 +254,7 @@ export default function Dashboard() {
onSelectChange={setSelectedIds}
onViewReport={handleViewReport}
onRunTask={handleRunTask}
onDeleteTask={handleDeleteTask}
/>
)}
</div>
@@ -234,3 +282,4 @@ export default function Dashboard() {
);
}