{percent}%
双盲提取?/div>
)}
strokeColor={{
'0%': '#9333ea',
'100%': '#4f46e5'
}}
/>
// 双模型动画(两个小球跳动?
```
**4. 日志显示**
```typescript
{logs.map((log, i) => (
[{log.timestamp}] {log.message}
))}
_
```
---
### 5.2 验收标准
- [ ] 点击"开始提?后成功创建任?- [ ] 获取到taskId
- [ ] 进度轮询正常(每5秒)
- [ ] 进度条实时更?- [ ] 日志滚动区域正常显示
- [ ] 任务完成后自动跳转到Step 4
- [ ] 任务失败时显示错误提?
---
## 🎯 六、Phase 4: Tool B - Step 4(Day 3下午+Day 4)⭐ 核心
### 6.1 冲突验证网格?小时?
这是整个Tool B最复杂、最核心的页面!
#### 6.1.1 页面设计
**组件**:`pages/tool-b/Step4Verify.tsx`
**UI参?*:原型V4?02-569?
**布局**?```
┌─────────────────────────────────────────────────??Toolbar: 统计信息 + [导出][完成] ?├─────────────────────────────────────────────────?? ?? 全景验证网格(表格) ?? ┌────┬────────────┬────────┬────────┬──────? ?? ?# ?原文摘要 ?字段1 ?字段2 ?状?? ?? ├────┼────────────┼────────┼────────┼──────? ?? ?1 ?病理诊断.. ?冲突单元格(A/B按钮)│待裁决│ ?? ?2 ?送检组织.. ?绿色(一致)?绿色 ?通过 ? ?? └────┴────────────┴────────┴────────┴──────? ?? ?└─────────────────────────────────────────────────? ?点击?┌──────────────────────────────────??侧边栏(Drawer? ??─────────────────────────────── ??病历原文详情 ?? ??病理诊断?右肺上叶)浸润性腺?.. ??肿瘤大小 3.2*2.5*2.0cm... ??... ?? ??[关闭] ?└──────────────────────────────────?```
---
#### 6.1.2 技术选型
**方案选择**?
**Option 1: Ant Design Table**(推荐,数据?1000行)
- 优点:开箱即用,API友好,样式统一
- 缺点:数据量大时可能卡顿
- 适用场景:大部分场景(预计数据量<500行)
**Option 2: TanStack Table**(高性能,数据量>1000行)
- 优点:虚拟滚动,性能极佳
- 缺点:Headless需自己实现UI
- 适用场景:处理大数据?
**决策**:先用Ant Design Table,如性能不佳再迁移到TanStack Table
---
#### 6.1.3 核心数据结构
```typescript
interface VerifyRow {
id: string;
rowIndex: number;
originalText: string; // 原文摘要(前50字)
fullText: string; // 原文全文(侧边栏显示? results: Record
;
status: 'clean' | 'conflict' | 'resolved';
conflictFields: string[]; // 冲突字段名称列表
}
```
---
#### 6.1.4 功能实现
**1. 获取验证数据**
```typescript
const [rows, setRows] = useState([]);
const [pagination, setPagination] = useState({ page: 1, pageSize: 20, total: 0 });
useEffect(() => {
fetch(`/api/v1/dc/tool-b/tasks/${taskId}/items?status=conflict&page=${pagination.page}`)
.then(res => res.json())
.then(data => {
setRows(data.data.items);
setPagination({ ...pagination, total: data.data.pagination.total });
});
}, [taskId, pagination.page]);
```
**2. 表格列配?*
```typescript
const columns: ColumnsType = [
{
title: '#',
dataIndex: 'rowIndex',
width: 60,
fixed: 'left',
},
{
title: '原文摘要',
dataIndex: 'originalText',
width: 200,
render: (text, record) => (
{text}
),
},
// 动态字段列(根据模板生成)
...fields.map(field => ({
title: field.name,
dataIndex: ['results', field.name],
width: 180,
render: (cellData: { A: string; B: string; chosen: string | null }, record: VerifyRow) => {
const isConflict = cellData.A !== cellData.B && cellData.chosen === null;
if (isConflict) {
return handleAdopt(record.id, field.name, value)}
/>;
}
return ;
},
})),
{
title: '状?,
dataIndex: 'status',
width: 100,
fixed: 'right',
render: (status) => {
if (status === 'clean' || status === 'resolved') {
return 通过;
}
return 待裁?/Tag>;
},
},
];
```
**3. 冲突单元格组?* ?核心
```typescript
// components/ConflictCell.tsx
interface ConflictCellProps {
fieldName: string;
valueA: string;
valueB: string;
onAdopt: (value: string) => void;
}
const ConflictCell: React.FC = ({ fieldName, valueA, valueB, onAdopt }) => {
return (
{/* 选项A - DeepSeek */}
{/* 选项B - Qwen */}
);
};
```
**4. 裁决逻辑**
```typescript
const handleAdopt = async (itemId: string, fieldName: string, value: string) => {
// 乐观更新UI
setRows(prevRows =>
prevRows.map(row => {
if (row.id !== itemId) return row;
const newResults = { ...row.results };
newResults[fieldName].chosen = value;
// 检查该行是否还有未解决的冲? const hasConflict = Object.values(newResults).some(
cell => cell.chosen === null && cell.A !== cell.B
);
return {
...row,
results: newResults,
status: hasConflict ? 'conflict' : 'resolved',
};
})
);
// 调用API
try {
await fetch(`/api/v1/dc/tool-b/items/${itemId}/resolve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
resolvedData: { [fieldName]: value }
})
});
} catch (error) {
message.error('裁决失败');
// 回滚UI
// TODO: 实现回滚逻辑
}
};
```
**5. 侧边栏原文显?*
```typescript
const [selectedRowId, setSelectedRowId] = useState(null);
// 点击行时打开侧边?const onRow = (record: VerifyRow) => ({
onClick: () => setSelectedRowId(record.id),
});
// Drawer组件
setSelectedRowId(null)}
>
{(() => {
const row = rows.find(r => r.id === selectedRowId);
if (!row) return null;
return (
{row.fullText}
{Object.keys(row.results).map(fieldName => (
{fieldName}
))}
);
})()}
```
**6. Toolbar统计**
```typescript
const conflictCount = rows.filter(r => r.status === 'conflict').length;
const resolvedCount = rows.filter(r => r.status === 'resolved').length;
const cleanCount = rows.filter(r => r.status === 'clean').length;
总数? {rows.length}
{conflictCount > 0 ? (
) : (
所有冲突已解决
)}
```
---
### 6.2 验收标准
**数据加载**?- [ ] 成功获取验证数据
- [ ] 表格列根据模板动态生?- [ ] 分页功能正常
**冲突单元?*?- [ ] 冲突单元格显示A/B两个按钮
- [ ] 按钮显示正确的值和模型标识(DS/QW?- [ ] 点击按钮后采纳?- [ ] UI乐观更新(立即生效)
- [ ] API调用成功
**侧边?*?- [ ] 点击行时侧边栏滑?- [ ] 显示完整病历原文
- [ ] 显示字段标签(冲突字段高亮)
- [ ] 点击关闭按钮或外部区域关闭侧边栏
**Toolbar**?- [ ] 冲突数统计正?- [ ] 实时更新(裁决后减少?- [ ] 导出按钮可点?- [ ] 完成按钮跳转到Step 5
---
## 📦 七、Phase 5: Tool B - Step 5(Day 5上午?
### 7.1 结果导出?小时?
#### 7.1.1 页面设计
**组件**:`pages/tool-b/Step5Result.tsx`
**UI参?*:原型V4?72-607?
**布局**?1. 完成图标和标?2. 统计卡片?个)
3. 操作按钮(下载、流转)
---
#### 7.1.2 功能实现
**1. 统计数据获取**
```typescript
const [stats, setStats] = useState({
totalCount: 0,
cleanCount: 0,
conflictCount: 0,
failedCount: 0,
totalTokens: 0,
totalCost: 0,
});
useEffect(() => {
fetch(`/api/v1/dc/tool-b/tasks/${taskId}/progress`)
.then(res => res.json())
.then(data => setStats(data.data));
}, [taskId]);
```
**2. Excel导出**
```typescript
const handleExport = async () => {
try {
const response = await fetch(`/api/v1/dc/tool-b/tasks/${taskId}/export`, {
method: 'POST'
});
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `extraction_result_${taskId}.xlsx`;
a.click();
message.success('导出成功');
} catch (error) {
message.error('导出失败');
}
};
```
**3. 流转到工具C**
```typescript
const handleGoToToolC = () => {
// 跳转到工具C,传递taskId
navigate(`/data-cleaning/tool-c?sourceTaskId=${taskId}`);
};
```
---
### 7.2 验收标准
- [ ] 统计卡片数据正确显示
- [ ] Token消耗和成本计算正确
- [ ] 下载按钮触发Excel导出
- [ ] Excel文件包含4个Sheet(完整结果、无冲突、冲突项、失败项?- [ ] 流转到工具C按钮可点击(提示暂未开发)
- [ ] 返回Portal按钮可点?
---
## 🧪 八、Phase 6: 集成测试(Day 5下午?
### 8.1 端到端测试清?
#### 8.1.1 完整流程测试
**测试场景**:肺癌病理报告提?
**测试步骤**?1. [ ] 访问Portal页面(`/data-cleaning`?2. [ ] 点击Tool B卡片,跳转到Step 1
3. [ ] 上传测试Excel文件(包含病理报告列?4. [ ] 选择"病理报告"列,触发健康检?5. [ ] 验证健康度显示为"绿色 - 优秀"
6. [ ] 进入Step 2,选择"肺癌 + 病理报告"
7. [ ] 验证字段自动加载?个字段)
8. [ ] 编辑一个字段(修改描述?9. [ ] 进入Step 3,点?开始提?
10. [ ] 验证进度条开始更?11. [ ] 验证日志滚动显示
12. [ ] 等待任务完成(或模拟完成?13. [ ] 自动跳转到Step 4
14. [ ] 验证验证网格加载数据
15. [ ] 找到一个冲突单元格,点?DS"按钮采纳
16. [ ] 验证冲突数减1
17. [ ] 点击某行,验证侧边栏显示原文
18. [ ] 点击"完成并入?,跳转到Step 5
19. [ ] 验证统计卡片数据正确
20. [ ] 点击"下载",验证Excel下载成功
---
#### 8.1.2 异常场景测试
**测试场景1:健康检查失?*
- [ ] 上传Excel,选择空值率>50%的列
- [ ] 验证健康度显示为"红色 - 警告"
- [ ] 验证"下一?按钮被禁?
**测试场景2:任务失?*
- [ ] 模拟API返回失败状?- [ ] 验证错误提示显示
- [ ] 验证可以重新尝试
**测试场景3:网络错?*
- [ ] 断网后点?下一?
- [ ] 验证错误提示显示
- [ ] 重新联网后可继续
---
### 8.2 性能测试
**测试场景**:大数据量验证网?
**测试步骤**?1. [ ] 加载500行数?2. [ ] 验证表格滚动流畅(无卡顿?3. [ ] 验证分页功能正常
4. [ ] 验证排序/筛选功能(如实现)
**性能指标**?- [ ] 首屏加载时间 < 2?- [ ] 表格滚动帧率 > 30fps
- [ ] 裁决操作响应时间 < 500ms
---
### 8.3 兼容性测?
**浏览?*?- [ ] Chrome 120+
- [ ] Edge 120+
- [ ] Firefox 120+
- [ ] Safari 17+(Mac?
**分辨?*?- [ ] 1920x1080(常规)
- [ ] 1440x900(笔记本?- [ ] 2560x1440(高分屏?
---
## 📝 九、文档完善(Day 6?
### 9.1 开发文?
**需要补充的文档**?1. [ ] **前端代码结构说明**(`docs/03-业务模块/DC-数据清洗整理/02-技术设?前端架构设计.md`?2. [ ] **组件使用文档**(`frontend-v2/src/modules/dc/README.md`?3. [ ] **API对接文档**(补充到现有API设计文档?4. [ ] **本开发计划的完成总结**(`docs/03-业务模块/DC-数据清洗整理/06-开发记?Tool-B开发完成总结.md`?
---
### 9.2 用户文档(可选)
**需要创建的文档**?1. [ ] **Tool B使用指南**(`docs/03-业务模块/DC-数据清洗整理/07-用户手册/Tool-B使用指南.md`?2. [ ] **常见问题FAQ**(`docs/03-业务模块/DC-数据清洗整理/07-用户手册/FAQ.md`?
---
## 🎯 十、关键技术要?
### 10.1 必须遵守的规?
#### 云原生规??强制
参考:`docs/04-开发规?08-云原生开发规?md`
**禁止**?- ?使用`fs.writeFile()`等本地文件操?- ?使用全局变量缓存数据
- ?硬编码配置(IP、密钥等?- ?同步长任务(>10秒)
- ?重复实现平台能力
**必须**?- ?使用`storage`服务存储文件
- ?使用`cache`服务缓存数据
- ?使用环境变量配置
- ?异步任务 + 进度轮询
- ?复用平台基础设施
---
### 10.2 复用策略
#### 后端复用(参考)
```typescript
// ?正确:复用平台能?import { storage } from '@/common/storage';
import { logger } from '@/common/logging';
import { cache } from '@/common/cache';
import { LLMFactory } from '@/common/llm/adapters/LLMFactory';
```
#### 前端复用(需实现?```typescript
// 复用ASL模块的组件和Hook
import { usePolling } from '@/shared/hooks/usePolling';
import { FileUploader } from '@/shared/components/FileUploader';
import { ProgressBar } from '@/shared/components/ProgressBar';
```
---
### 10.3 状态管?
**推荐方案**:React Query(ASL模块使用?
```typescript
// 使用React Query管理API调用
import { useQuery, useMutation } from '@tanstack/react-query';
// 获取任务进度
const { data: progress } = useQuery({
queryKey: ['task-progress', taskId],
queryFn: () => fetch(`/api/v1/dc/tool-b/tasks/${taskId}/progress`).then(res => res.json()),
refetchInterval: 5000, // ?秒轮? enabled: !!taskId && status === 'processing',
});
// 裁决冲突
const resolveMutation = useMutation({
mutationFn: ({ itemId, resolvedData }) =>
fetch(`/api/v1/dc/tool-b/items/${itemId}/resolve`, {
method: 'POST',
body: JSON.stringify({ resolvedData }),
}),
onSuccess: () => {
queryClient.invalidateQueries(['task-items', taskId]);
message.success('裁决成功');
},
});
```
---
### 10.4 TypeScript类型定义
**统一类型文件**:`frontend-v2/src/modules/dc/types/toolB.ts`
```typescript
// 健康检查结?export interface HealthCheckResult {
status: 'good' | 'bad';
emptyRate: number;
avgLength: number;
totalRows: number;
estimatedTokens: number;
message: string;
}
// 模板定义
export interface Template {
id: string;
diseaseType: string;
reportType: string;
displayName: string;
fields: Field[];
}
export interface Field {
id: string;
name: string;
desc: string;
width?: string;
}
// 任务状?export interface Task {
id: string;
projectName: string;
status: 'pending' | 'processing' | 'completed' | 'failed';
totalCount: number;
processedCount: number;
cleanCount: number;
conflictCount: number;
failedCount: number;
totalTokens: number;
totalCost: number;
}
// 验证?export interface VerifyRow {
id: string;
rowIndex: number;
originalText: string;
fullText: string;
results: Record;
status: 'clean' | 'conflict' | 'resolved';
conflictFields: string[];
}
```
---
## 📊 十一、风险评估与应对
### 11.1 技术风?
| 风险 | 概率 | 影响 | 应对措施 |
|------|------|------|---------|
| **数据库表未创?* | ?| ?| 立即执行`npx prisma db push`验证 |
| **后端API未测?* | ?| ?| 开发前先用REST Client测试6个端?|
| **性能问题(大数据量)** | ?| ?| 预留TanStack Table迁移方案 |
| **Excel解析失败** | ?| ?| 使用xlsx库,增加错误处理 |
| **LLM调用超时** | ?| ?| 后端已实现重试机?|
---
### 11.2 时间风险
| 风险 | 概率 | 影响 | 应对措施 |
|------|------|------|---------|
| **Step 4开发超?* | ?| ?| 预留2天(9小时),可拆分子任务 |
| **集成测试发现问题** | ?| ?| 预留1天缓冲时?|
| **原型与需求不?* | ?| ?| 开发前与产品确认原?|
---
### 11.3 依赖风险
| 风险 | 概率 | 影响 | 应对措施 |
|------|------|------|---------|
| **后端API变更** | ?| ?| 使用TypeScript类型,编译时检?|
| **平台能力Bug** | ?| ?| 已完整测试,风险?|
| **设计变更** | ?| ?| 组件化设计,易于修改 |
---
## 🎉 十二、预期成?
### 12.1 交付物清?
**代码**?- [ ] Portal工作台页面(完整?- [ ] Tool B 5个Step页面(完整)
- [ ] 共享组件库(TaskList、AssetLibrary、ConflictCell等)
- [ ] API服务封装(toolBApi.ts、portalApi.ts?- [ ] TypeScript类型定义(完整)
- [ ] 样式文件(TailwindCSS + Ant Design?
**文档**?- [ ] 前端架构设计文档
- [ ] 组件使用文档
- [ ] API对接文档
- [ ] 开发完成总结
- [ ] 用户使用指南(可选)
**测试**?- [ ] 端到端测试报?- [ ] 性能测试报告
- [ ] 兼容性测试报?
---
### 12.2 功能完整?
**Portal工作?*?- ?3个工具卡片(Tool B可用?- ?最近任务列表(实时轮询?- ?数据资产库(Tab切换?
**Tool B - 病历结构化机器人**?- ?Step 1: Excel上传 + 健康检?- ?Step 2: 智能模板配置?个预设模板)
- ?Step 3: 双盲提取进度监控
- ?Step 4: 冲突验证网格(支持裁决)
- ?Step 5: 结果导出(Excel + 流转?
---
### 12.3 代码质量
**代码规范**?- ?遵守云原生开发规?- ?TypeScript类型安全
- ?ESLint无错?- ?代码注释完善
**性能指标**?- ?首屏加载 < 2s
- ?表格滚动流畅
- ?裁决响应 < 500ms
- ?内存占用 < 200MB
**用户体验**?- ?流畅?步流?- ?实时进度反馈
- ?直观的冲突验证界?- ?友好的错误提?
---
## 🚀 十三、下一步行?
### 13.1 立即执行(Day 0?
**1. 验证数据库表**
```bash
cd AIclinicalresearch/backend
npx prisma db push --skip-generate
npx prisma studio # 可视化验?```
**2. 测试后端API**
```bash
# 启动后端服务
npm run dev
# 使用REST Client测试6个API端点
# 或使用Postman/Insomnia
```
**3. Git提交计划**
```bash
# 建议每完成一个Phase就提?git commit -m "feat(dc): Complete Portal page (Phase 1)"
git commit -m "feat(dc/tool-b): Complete Step1&2 (Phase 2)"
git commit -m "feat(dc/tool-b): Complete Step4 conflict grid (Phase 4)"
```
---
### 13.2 开发启动(Day 1?
**任务1:创建Portal页面骨架**
```bash
cd frontend-v2/src/modules/dc
mkdir -p pages components hooks services types
# 创建Portal.tsx
touch pages/Portal.tsx
# 创建路由配置
# 修改 index.tsx
```
**任务2:实?个工具卡?*
```bash
# 创建ToolCard组件
touch components/ToolCard.tsx
```
**任务3:实现任务列表(使用Mock数据?*
```bash
# 创建TaskList组件
touch components/TaskList.tsx
# 创建useRecentTasks Hook
touch hooks/useRecentTasks.ts
```
---
### 13.3 持续跟进
**每日检查点**?- [ ] 代码是否Git提交
- [ ] Todo列表是否更新
- [ ] 遇到的问题是否记?- [ ] API调用是否正常
**每周检查点**?- [ ] 功能完成度是否符合预?- [ ] 是否需要调整计?- [ ] 是否需要额外资?
---
## 📞 十四、联系与支持
### 14.1 技术支?
**遇到问题?*?1. 查阅平台基础设施文档(`backend/src/common/README.md`?2. 查阅云原生开发规范(`docs/04-开发规?08-云原生开发规?md`?3. 参考ASL模块代码(`frontend-v2/src/modules/asl/`?4. 查看后端API代码(`backend/src/modules/dc/tool-b/`?
### 14.2 代码审查
**提交前自检**?- [ ] 是否遵守云原生规?- [ ] 是否复用平台能力
- [ ] TypeScript类型是否完整
- [ ] 是否有TODO/FIXME注释
- [ ] 是否有console.log(应使用logger?
---
## 🎯 十五、总结
### 核心目标
完成DC模块Tool B的前端开发,实现从文件上传到结果导出的完?步流程,特别?*冲突验证网格**这一核心功能?
### 关键成功因素
1. ?**后端?00%完成**,前端可专注UI和交?2. ?**平台能力完善**,可直接复用,无需重复开?3. ?**设计文档齐全**,有清晰的原型和需?4. ?**参考模块成?*,ASL模块可作为参?5. ?**架构方案明确**,采用方案A(独立Portal?
### 预期收益
- **用户价?*:提供AI驱动的病历结构化能力,双模型交叉验证提高数据质量
- **技术价?*:完整的前端模块化架构,可复用组件库
- **商业价?*:Tool B可独立销售,满足医疗数据处理需?
---
**开发计划制定完成!** ?
**下一?*:验证数据库??测试后端API ?开始Phase 1(Portal页面开发)
**预计完成时间**?-6个工作日
**祝开发顺利!** 🚀