Features - User Management (Phase 4.1): - Database: Add user_modules table for fine-grained module permissions - Database: Add 4 user permissions (view/create/edit/delete) to role_permissions - Backend: UserService (780 lines) - CRUD with tenant isolation - Backend: UserController + UserRoutes (648 lines) - 13 API endpoints - Backend: Batch import users from Excel - Frontend: UserListPage (412 lines) - list/filter/search/pagination - Frontend: UserFormPage (341 lines) - create/edit with module config - Frontend: UserDetailPage (393 lines) - details/tenant/module management - Frontend: 3 modal components (592 lines) - import/assign/configure - API: GET/POST/PUT/DELETE /api/admin/users/* endpoints Architecture Upgrade - Module Permission System: - Backend: Add getUserModules() method in auth.service - Backend: Login API returns modules array in user object - Frontend: AuthContext adds hasModule() method - Frontend: Navigation filters modules based on user.modules - Frontend: RouteGuard checks requiredModule instead of requiredVersion - Frontend: Remove deprecated version-based permission system - UX: Only show accessible modules in navigation (clean UI) - UX: Smart redirect after login (avoid 403 for regular users) Fixes: - Fix UTF-8 encoding corruption in ~100 docs files - Fix pageSize type conversion in userService (String to Number) - Fix authUser undefined error in TopNavigation - Fix login redirect logic with role-based access check - Update Git commit guidelines v1.2 with UTF-8 safety rules Database Changes: - CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled) - ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code) - INSERT 4 permissions + role assignments - UPDATE PUBLIC tenant with 8 module subscriptions Technical: - Backend: 5 new files (~2400 lines) - Frontend: 10 new files (~2500 lines) - Docs: 1 development record + 2 status updates + 1 guideline update - Total: ~4900 lines of code Status: User management 100% complete, module permission system operational
1601 lines
43 KiB
Markdown
1601 lines
43 KiB
Markdown
# DC模块 Tool-B 开发计划
|
||
|
||
> **文档版本:** V2.0 (MVP完成)
|
||
> **创建日期:** 2025-12-02
|
||
> **完成日期:** 2025-12-03
|
||
> **实际周期:** 2个工作日
|
||
> **状态:** ✅ MVP完成
|
||
|
||
---
|
||
|
||
## 🎉 MVP完成通告(2025-12-03)
|
||
|
||
**Tool B病历结构化机器人MVP版本已完成!**
|
||
|
||
- ✅ 前端5步工作流完整实现(~1400行)
|
||
- ✅ 8个API端点全部对接并测试通过
|
||
- ✅ LLM双模型提取验证成功(DeepSeek-V3 + Qwen-Max)
|
||
- ✅ 真实数据测试:9条病理报告提取成功
|
||
- ✅ Excel导出功能可用
|
||
- ⚠️ 4个技术债务待处理(见`07-技术债务/Tool-B技术债务清单.md`)
|
||
|
||
**完成详情:** 参见 `06-开发记录/Tool-B-MVP完成总结-2025-12-03.md`
|
||
|
||
---
|
||
|
||
## 原始开发计划
|
||
|
||
> **目标:** 完成Tool-B(病历结构化机器人)的前端开发和完整功能集成
|
||
|
||
---
|
||
|
||
## 📋 一、项目当前状态调研总结
|
||
|
||
### 1.1 后端代码状态 ✅ 100%完成
|
||
|
||
#### 通用能力平台(可复用)
|
||
基于 `backend/src/common/` 平台基础设施,已完整实现:
|
||
|
||
| 能力模块 | 导入路径 | 功能说明 | 状态 |
|
||
|---------|---------|---------|------|
|
||
| **存储服务** | `@/common/storage` | 文件上传/下载(Local ↔ OSS) | ✅ 完成 |
|
||
| **日志系统** | `@/common/logging` | 结构化日志(Winston) | ✅ 完成 |
|
||
| **缓存服务** | `@/common/cache` | 缓存(Memory ↔ Redis) | ✅ 完成 |
|
||
| **异步任务** | `@/common/jobs` | 长时间任务队列 | ✅ 完成 |
|
||
| **LLM工厂** | `@/common/llm/adapters/LLMFactory` | 统一LLM调用(DeepSeek/Qwen/GPT/Claude) | ✅ 完成 |
|
||
| **数据库** | `@/config/database` | Prisma连接池 | ✅ 完成 |
|
||
|
||
**复用策略**:
|
||
- ✅ Tool B后端代码已100%复用平台能力
|
||
- ✅ 前端开发应参考后端模式,复用前端共享组件
|
||
|
||
---
|
||
|
||
#### Tool B后端实现状态
|
||
|
||
**代码位置**:`backend/src/modules/dc/tool-b/`
|
||
|
||
| 文件 | 功能 | 代码量 | 状态 | 复用能力 |
|
||
|-----|------|--------|------|---------|
|
||
| **services/HealthCheckService.ts** | Excel健康检查 | ~190行 | ✅ 完成 | storage, logger, cache, prisma |
|
||
| **services/TemplateService.ts** | 预设模板管理 | ~243行 | ✅ 完成 | logger, prisma |
|
||
| **services/DualModelExtractionService.ts** | 双模型提取 | ~390行 | ✅ 完成 | LLMFactory, logger, prisma |
|
||
| **services/ConflictDetectionService.ts** | 冲突检测算法 | ~215行 | ✅ 完成 | logger |
|
||
| **controllers/ExtractionController.ts** | API控制器 | ~388行 | ✅ 完成 | 全部服务 |
|
||
| **routes/index.ts** | 路由配置 | ~115行 | ✅ 完成 | - |
|
||
| **index.ts** | 模块入口 | ~117行 | ✅ 完成 | - |
|
||
|
||
**总计**:约1,658行,7个文件,100%完成
|
||
|
||
---
|
||
|
||
#### API端点清单
|
||
|
||
**Base URL**: `/api/v1/dc/tool-b`
|
||
|
||
| 方法 | 路径 | 功能 | 请求体 | 响应 | 状态 |
|
||
|------|------|------|--------|------|------|
|
||
| **POST** | `/health-check` | Excel列健康检查 | `{fileKey, columnName}` | 健康度报告 | ✅ 完成 |
|
||
| **GET** | `/templates` | 获取预设模板列表 | - | 模板数组 | ✅ 完成 |
|
||
| **POST** | `/tasks` | 创建提取任务 | `{projectName, fileKey, textColumn, diseaseType, reportType}` | `{taskId}` | ✅ 完成 |
|
||
| **GET** | `/tasks/:taskId/progress` | 查询任务进度 | - | 进度详情 | ✅ 完成 |
|
||
| **GET** | `/tasks/:taskId/items` | 获取验证网格数据 | `?status=conflict&page=1` | 分页数据 | ✅ 完成 |
|
||
| **POST** | `/items/:itemId/resolve` | 裁决冲突 | `{resolvedData}` | 成功状态 | ✅ 完成 |
|
||
|
||
**预设模板**(3个):
|
||
1. 肺癌病理报告(`lung_cancer/pathology`)- 5个字段
|
||
2. 糖尿病入院记录(`diabetes/admission`)- 5个字段
|
||
3. 高血压门诊病历(`hypertension/outpatient`)- 5个字段
|
||
|
||
---
|
||
|
||
### 1.2 数据库状态 ✅ 已验证完成(2025-12-02)
|
||
|
||
**Schema**: `dc_schema`(独立隔离)
|
||
|
||
| 表名 | 用途 | 字段数 | 关键字段 | 状态 | 数据量 |
|
||
|------|------|--------|---------|------|--------|
|
||
| **dc_health_checks** | 健康检查记录 | 10 | status, emptyRate, avgLength | ✅ 已创建 | 2条 |
|
||
| **dc_templates** | 预设模板 | 7 | diseaseType, reportType, fields | ✅ 已创建 | **3条** |
|
||
| **dc_extraction_tasks** | 提取任务 | 21 | status, totalCount, processedCount | ✅ 已创建 | 1条 |
|
||
| **dc_extraction_items** | 提取明细 | 15 | resultA, resultB, conflictFields | ✅ 已创建 | 4条 |
|
||
|
||
**✅ 验证结果(2025-12-02)**:
|
||
- ✅ **dc_schema已存在**
|
||
- ✅ **4个表全部创建成功**
|
||
- ✅ **3个预设模板已初始化**:
|
||
1. 肺癌病理报告 (lung_cancer/pathology)
|
||
2. 糖尿病入院记录 (diabetes/admission)
|
||
3. 高血压门诊病历 (hypertension/outpatient)
|
||
- ✅ **有测试数据可用于开发调试**
|
||
|
||
**验证脚本**:
|
||
```bash
|
||
cd backend
|
||
node scripts/check-dc-tables.mjs # 执行数据库表检查脚本
|
||
```
|
||
|
||
**结论**:✅ 数据库完全准备就绪,可以开始前端开发!
|
||
|
||
---
|
||
|
||
### 1.3 前端代码状态 ❌ 0%(仅Placeholder)
|
||
|
||
**代码位置**:`frontend-v2/src/modules/dc/`
|
||
|
||
```
|
||
frontend-v2/src/modules/dc/
|
||
├── index.tsx # ❌ 仅Placeholder(14行)
|
||
├── components/ # 📁 空
|
||
├── pages/ # 📁 空文件夹结构
|
||
│ ├── tool-a/ # 📁 空
|
||
│ ├── tool-b/ # 📁 空
|
||
│ └── tool-c/ # 📁 空
|
||
└── types/ # 📁 空
|
||
```
|
||
|
||
**当前内容**:
|
||
```typescript
|
||
// frontend-v2/src/modules/dc/index.tsx
|
||
import Placeholder from '@/shared/components/Placeholder'
|
||
|
||
const DCModule = () => {
|
||
return (
|
||
<Placeholder
|
||
title="数据清洗模块"
|
||
description="功能规划中,将提供智能数据清洗和整理工具"
|
||
moduleName="DC - Data Cleaning"
|
||
/>
|
||
)
|
||
}
|
||
export default DCModule
|
||
```
|
||
|
||
---
|
||
|
||
### 1.4 前端架构设计 ✅ 已明确
|
||
|
||
#### 系统级架构
|
||
- **导航模式**:顶部导航
|
||
- **路由路径**:`/data-cleaning`
|
||
- **技术栈**:React 19 + TypeScript + Vite + Ant Design 5
|
||
|
||
#### DC模块架构方案
|
||
|
||
**选择:方案A - 独立Portal页面**(推荐)
|
||
|
||
```
|
||
/data-cleaning (Portal工作台 - 总览页)
|
||
├── 快速启动区(3个工具卡片)
|
||
│ ├── [超级合并器] → /data-cleaning/tool-a
|
||
│ ├── [病历结构化] → /data-cleaning/tool-b
|
||
│ └── [数据编辑器] → /data-cleaning/tool-c
|
||
├── 最近任务列表(实时进度)
|
||
└── 数据资产库(文件管理)
|
||
|
||
/data-cleaning/tool-b (Tool B - 全屏5步流程)
|
||
├── Step 1: 上传与健康检查
|
||
├── Step 2: 智能模板配置
|
||
├── Step 3: 双盲提取进度
|
||
├── Step 4: 冲突验证网格 ⭐ 核心
|
||
└── Step 5: 结果导出
|
||
```
|
||
|
||
**参考模块**:
|
||
- ASL模块(`frontend-v2/src/modules/asl/`)
|
||
- 有完整的布局、页面、组件结构
|
||
- 使用React Router嵌套路由
|
||
- 左侧导航 + 右侧内容区
|
||
|
||
---
|
||
|
||
## 🎯 二、开发计划总览
|
||
|
||
### 2.1 开发阶段划分
|
||
|
||
| 阶段 | 任务 | 预计工时 | 优先级 | 目标 |
|
||
|------|------|---------|--------|------|
|
||
| **Phase 1** | Portal工作台页面 | 4-6h | P0 | DC模块入口 |
|
||
| **Phase 2** | Tool B - Step 1&2 | 6h | P0 | 上传+配置 |
|
||
| **Phase 3** | Tool B - Step 3 | 3h | P0 | 进度监控 |
|
||
| **Phase 4** | Tool B - Step 4 | 9h | P0 | 冲突验证网格⭐ |
|
||
| **Phase 5** | Tool B - Step 5 | 3h | P0 | 结果导出 |
|
||
| **Phase 6** | 集成测试 | 4h | P1 | 端到端验证 |
|
||
|
||
**总计**:约29-31小时(4-5个工作日)
|
||
|
||
---
|
||
|
||
### 2.2 里程碑时间表
|
||
|
||
| 里程碑 | 完成标志 | 预计完成 |
|
||
|--------|---------|---------|
|
||
| **M1: Portal上线** | 用户可访问DC模块入口 | Day 1 |
|
||
| **M2: Tool B可用** | Step1-5全部完成 | Day 4 |
|
||
| **M3: 集成测试通过** | 端到端流程测试通过 | Day 5 |
|
||
| **M4: 文档完善** | 开发文档和用户文档 | Day 6 |
|
||
|
||
---
|
||
|
||
## 📐 三、Phase 1: Portal工作台开发(Day 1)
|
||
|
||
### 3.1 目标
|
||
创建DC模块的入口页面,适配系统顶部导航,提供工具启动、任务监控和文件管理功能。
|
||
|
||
### 3.2 设计参考
|
||
- **原型文件**:`docs/03-业务模块/DC-数据清洗整理/03-UI设计/智能数据清洗工作台V2.html`
|
||
- **PRD文档**:`docs/03-业务模块/DC-数据清洗整理/01-需求分析/PRD:智能数据清洗工作台 (The Data Cleaning Portal).md`
|
||
|
||
### 3.3 功能清单
|
||
|
||
#### 3.3.1 快速启动区(3个工具卡片)
|
||
**组件**:`components/ToolCard.tsx`
|
||
|
||
```typescript
|
||
interface ToolCardProps {
|
||
id: 'tool-a' | 'tool-b' | 'tool-c';
|
||
title: string;
|
||
description: string;
|
||
icon: ReactNode;
|
||
color: 'blue' | 'purple' | 'emerald';
|
||
status: 'ready' | 'disabled';
|
||
onClick: () => void;
|
||
}
|
||
```
|
||
|
||
**卡片内容**:
|
||
1. **Tool A - 超级合并器**
|
||
- 图标:FileSpreadsheet(蓝色)
|
||
- 描述:"解决多源数据时间轴对齐难题"
|
||
- 状态:disabled(暂未开发)
|
||
|
||
2. **Tool B - 病历结构化机器人** ⭐ 本次开发
|
||
- 图标:Bot(紫色)
|
||
- 描述:"利用大模型提取非结构化文本"
|
||
- 状态:ready
|
||
- 点击跳转:`/data-cleaning/tool-b`
|
||
|
||
3. **Tool C - 科研数据编辑器**
|
||
- 图标:Table2(绿色)
|
||
- 描述:"Excel风格的在线清洗工具"
|
||
- 状态:disabled(暂未开发)
|
||
|
||
---
|
||
|
||
#### 3.3.2 最近任务列表
|
||
**组件**:`components/TaskList.tsx`
|
||
|
||
**API**:`GET /api/v1/dc/tasks/recent`(需后端新增)
|
||
|
||
**数据结构**:
|
||
```typescript
|
||
interface Task {
|
||
id: string;
|
||
name: string;
|
||
tool: 'tool-a' | 'tool-b' | 'tool-c';
|
||
status: 'pending' | 'processing' | 'completed' | 'failed';
|
||
progress: number; // 0-100
|
||
createdAt: string;
|
||
completedAt?: string;
|
||
}
|
||
```
|
||
|
||
**功能**:
|
||
- 显示最近10条任务
|
||
- 实时轮询进度(processing状态时,每5秒轮询)
|
||
- 智能流转按钮:
|
||
- Tool A完成 → [下载] + [去AI提取](跳Tool B)
|
||
- Tool B完成 → [下载] + [去清洗](跳Tool C)
|
||
- Tool C完成 → [下载]
|
||
|
||
---
|
||
|
||
#### 3.3.3 数据资产库
|
||
**组件**:`components/AssetLibrary.tsx`
|
||
|
||
**API**:`GET /api/v1/dc/assets`(需后端新增)
|
||
|
||
**Tab分类**:
|
||
- **全部**:所有文件
|
||
- **处理结果**:工具A/B/C生成的文件(绿色/蓝色图标)
|
||
- **原始上传**:用户直接上传的底表(灰色图标)
|
||
|
||
**功能**:
|
||
- 文件卡片显示(文件名、行数、标签、修改时间)
|
||
- 快捷操作:[下载] [去处理] [分析]
|
||
- 底部固定按钮:[+ 上传原始文件到库]
|
||
|
||
---
|
||
|
||
### 3.4 技术实现
|
||
|
||
#### 3.4.1 目录结构
|
||
```
|
||
frontend-v2/src/modules/dc/
|
||
├── pages/
|
||
│ └── Portal.tsx # ⭐ Portal主页面
|
||
├── components/
|
||
│ ├── ToolCard.tsx # 工具卡片
|
||
│ ├── TaskList.tsx # 任务列表
|
||
│ └── AssetLibrary.tsx # 数据资产库
|
||
├── hooks/
|
||
│ ├── useRecentTasks.ts # 任务列表Hook
|
||
│ └── useAssets.ts # 资产列表Hook
|
||
├── services/
|
||
│ └── portalApi.ts # Portal API封装
|
||
└── types/
|
||
└── portal.ts # Portal类型定义
|
||
```
|
||
|
||
---
|
||
|
||
#### 3.4.2 路由配置
|
||
```typescript
|
||
// frontend-v2/src/modules/dc/index.tsx
|
||
import { Routes, Route } from 'react-router-dom';
|
||
import Portal from './pages/Portal';
|
||
import ToolBModule from './pages/tool-b';
|
||
|
||
const DCModule = () => {
|
||
return (
|
||
<Routes>
|
||
<Route path="" element={<Portal />} />
|
||
<Route path="tool-b/*" element={<ToolBModule />} />
|
||
{/* 未来扩展 */}
|
||
<Route path="tool-a/*" element={<ToolAPlaceholder />} />
|
||
<Route path="tool-c/*" element={<ToolCPlaceholder />} />
|
||
</Routes>
|
||
);
|
||
};
|
||
|
||
export default DCModule;
|
||
```
|
||
|
||
---
|
||
|
||
#### 3.4.3 API对接
|
||
|
||
**需要后端新增的API**:
|
||
```typescript
|
||
// GET /api/v1/dc/tasks/recent
|
||
interface GetRecentTasksResponse {
|
||
tasks: Task[];
|
||
}
|
||
|
||
// GET /api/v1/dc/assets
|
||
interface GetAssetsResponse {
|
||
assets: Asset[];
|
||
}
|
||
```
|
||
|
||
**临时方案**(后端API未开发时):
|
||
- 使用Mock数据
|
||
- 后续替换为真实API
|
||
|
||
---
|
||
|
||
### 3.5 验收标准
|
||
|
||
- [ ] Portal页面可访问(`http://localhost:3000/data-cleaning`)
|
||
- [ ] 3个工具卡片正确显示
|
||
- [ ] Tool B卡片可点击跳转(其他两个显示disabled)
|
||
- [ ] 任务列表显示Mock数据
|
||
- [ ] 数据资产库Tab切换正常
|
||
- [ ] 整体样式符合系统设计规范
|
||
|
||
---
|
||
|
||
## 🛠️ 四、Phase 2: Tool B - Step 1&2(Day 2)
|
||
|
||
### 4.1 Step 1: 文件上传与健康检查(3小时)
|
||
|
||
#### 4.1.1 页面设计
|
||
**组件**:`pages/tool-b/Step1Upload.tsx`
|
||
|
||
**UI参考**:原型V4第260-310行
|
||
|
||
**布局**:
|
||
1. 文件信息卡片(显示已上传文件)
|
||
2. 列选择下拉框
|
||
3. 健康检查结果卡片(动态显示)
|
||
|
||
---
|
||
|
||
#### 4.1.2 功能实现
|
||
|
||
**1. Excel文件上传**
|
||
```typescript
|
||
// 使用Ant Design Upload组件
|
||
import { Upload } from 'antd';
|
||
|
||
// 上传到Storage
|
||
const handleUpload = async (file: File) => {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
const response = await fetch('/api/v1/storage/upload', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const { fileKey } = await response.json();
|
||
setFileKey(fileKey); // 保存fileKey用于后续步骤
|
||
};
|
||
```
|
||
|
||
**2. 列选择与健康检查**
|
||
```typescript
|
||
const [columns, setColumns] = useState<string[]>([]);
|
||
const [selectedColumn, setSelectedColumn] = useState('');
|
||
const [healthResult, setHealthResult] = useState<HealthCheckResult | null>(null);
|
||
|
||
// 自动检测列名(可选:从后端获取)
|
||
useEffect(() => {
|
||
if (fileKey) {
|
||
// TODO: 调用API获取Excel列名
|
||
// 或前端解析Excel(使用xlsx库)
|
||
}
|
||
}, [fileKey]);
|
||
|
||
// 健康检查
|
||
const handleHealthCheck = async (columnName: string) => {
|
||
setIsChecking(true);
|
||
try {
|
||
const response = await fetch('/api/v1/dc/tool-b/health-check', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ fileKey, columnName })
|
||
});
|
||
|
||
const result = await response.json();
|
||
setHealthResult(result.data);
|
||
} finally {
|
||
setIsChecking(false);
|
||
}
|
||
};
|
||
```
|
||
|
||
**3. 健康度显示**
|
||
```typescript
|
||
// 根据status显示不同样式
|
||
{healthResult?.status === 'good' && (
|
||
<Alert
|
||
type="success"
|
||
icon={<CheckCircle2 />}
|
||
message="健康度优秀,适合提取"
|
||
description={
|
||
<div className="mt-2">
|
||
<span>平均字符: {healthResult.avgLength}</span>
|
||
<span className="ml-4">空值率: {(healthResult.emptyRate * 100).toFixed(1)}%</span>
|
||
<span className="ml-4 text-purple-600 font-bold">
|
||
预计Token: {healthResult.estimatedTokens.toLocaleString()}
|
||
</span>
|
||
</div>
|
||
}
|
||
/>
|
||
)}
|
||
|
||
{healthResult?.status === 'bad' && (
|
||
<Alert
|
||
type="error"
|
||
icon={<AlertTriangle />}
|
||
message="警告:该列不适合AI处理"
|
||
description={healthResult.message}
|
||
/>
|
||
)}
|
||
```
|
||
|
||
---
|
||
|
||
### 4.2 Step 2: 智能模板配置(3小时)
|
||
|
||
#### 4.2.1 页面设计
|
||
**组件**:`pages/tool-b/Step2Schema.tsx`
|
||
|
||
**UI参考**:原型V4第313-372行
|
||
|
||
**布局**:
|
||
1. 疾病类型与报告类型选择(级联)
|
||
2. 字段列表(左侧,可编辑)
|
||
3. Prompt预览(右侧,代码高亮)
|
||
|
||
---
|
||
|
||
#### 4.2.2 功能实现
|
||
|
||
**1. 获取模板列表**
|
||
```typescript
|
||
const [templates, setTemplates] = useState<Template[]>([]);
|
||
const [diseaseType, setDiseaseType] = useState('');
|
||
const [reportType, setReportType] = useState('');
|
||
const [fields, setFields] = useState<Field[]>([]);
|
||
|
||
useEffect(() => {
|
||
// 获取所有模板
|
||
fetch('/api/v1/dc/tool-b/templates')
|
||
.then(res => res.json())
|
||
.then(data => setTemplates(data.data.templates));
|
||
}, []);
|
||
|
||
// 当选择疾病类型和报告类型时,自动加载字段
|
||
useEffect(() => {
|
||
if (diseaseType && reportType) {
|
||
const template = templates.find(
|
||
t => t.diseaseType === diseaseType && t.reportType === reportType
|
||
);
|
||
if (template) {
|
||
setFields(template.fields);
|
||
}
|
||
}
|
||
}, [diseaseType, reportType, templates]);
|
||
```
|
||
|
||
**2. 字段编辑**
|
||
```typescript
|
||
// 添加字段
|
||
const handleAddField = () => {
|
||
setFields([...fields, {
|
||
id: `custom_${Date.now()}`,
|
||
name: '新字段',
|
||
desc: '字段描述',
|
||
width: 'w-32'
|
||
}]);
|
||
};
|
||
|
||
// 删除字段
|
||
const handleDeleteField = (id: string) => {
|
||
setFields(fields.filter(f => f.id !== id));
|
||
};
|
||
|
||
// 编辑字段
|
||
const handleEditField = (id: string, key: 'name' | 'desc', value: string) => {
|
||
setFields(fields.map(f =>
|
||
f.id === id ? { ...f, [key]: value } : f
|
||
));
|
||
};
|
||
```
|
||
|
||
**3. Prompt预览**
|
||
```typescript
|
||
// 动态生成Prompt预览
|
||
const generatePrompt = () => {
|
||
return `You are an expert in ${diseaseType.replace('_', ' ')} pathology.
|
||
Extract fields in JSON format:
|
||
|
||
{
|
||
${fields.map(f => ` "${f.name}": "string", // ${f.desc}`).join('\n')}
|
||
}
|
||
|
||
Original text:
|
||
{originalText}
|
||
|
||
Output JSON only.`;
|
||
};
|
||
|
||
// 使用代码高亮库(如react-syntax-highlighter)
|
||
<SyntaxHighlighter language="javascript" style={atomOneDark}>
|
||
{generatePrompt()}
|
||
</SyntaxHighlighter>
|
||
```
|
||
|
||
---
|
||
|
||
### 4.3 验收标准
|
||
|
||
**Step 1:**
|
||
- [ ] Excel上传成功,获取fileKey
|
||
- [ ] 列名列表正确显示
|
||
- [ ] 健康检查API调用成功
|
||
- [ ] 健康度卡片根据结果正确显示(绿色/红色)
|
||
- [ ] Token预估数值正确
|
||
- [ ] 空值率>50%时禁止进入下一步
|
||
|
||
**Step 2:**
|
||
- [ ] 模板列表加载成功
|
||
- [ ] 疾病类型和报告类型选择框正常
|
||
- [ ] 选择模板后字段自动加载
|
||
- [ ] 字段支持添加/删除/编辑
|
||
- [ ] Prompt预览实时更新
|
||
- [ ] 代码高亮正确显示
|
||
|
||
---
|
||
|
||
## ⚙️ 五、Phase 3: Tool B - Step 3(Day 3上午)
|
||
|
||
### 5.1 处理进度监控(3小时)
|
||
|
||
#### 5.1.1 页面设计
|
||
**组件**:`pages/tool-b/Step3Processing.tsx`
|
||
|
||
**UI参考**:原型V4第375-400行
|
||
|
||
**布局**:
|
||
1. 圆形进度环(双模型动画)
|
||
2. 进度百分比和文本
|
||
3. 日志滚动区域
|
||
|
||
---
|
||
|
||
#### 5.1.2 功能实现
|
||
|
||
**1. 创建任务**
|
||
```typescript
|
||
const handleStartExtraction = async () => {
|
||
const response = await fetch('/api/v1/dc/tool-b/tasks', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
projectName: 'Test Project',
|
||
sourceFileKey: fileKey,
|
||
textColumn: selectedColumn,
|
||
diseaseType,
|
||
reportType,
|
||
modelA: 'deepseek-v3',
|
||
modelB: 'qwen-max'
|
||
})
|
||
});
|
||
|
||
const { taskId } = await response.json();
|
||
setTaskId(taskId);
|
||
|
||
// 开始轮询
|
||
startPolling(taskId);
|
||
};
|
||
```
|
||
|
||
**2. 进度轮询**
|
||
```typescript
|
||
const startPolling = (taskId: string) => {
|
||
const interval = setInterval(async () => {
|
||
const response = await fetch(`/api/v1/dc/tool-b/tasks/${taskId}/progress`);
|
||
const data = await response.json();
|
||
|
||
setProgress(data.progress); // 百分比
|
||
setLogs(prevLogs => [...prevLogs, data.latestLog]); // 新增日志
|
||
|
||
if (data.status === 'completed') {
|
||
clearInterval(interval);
|
||
// 跳转到Step 4
|
||
setTimeout(() => navigate(`/data-cleaning/tool-b/verify/${taskId}`), 800);
|
||
} else if (data.status === 'failed') {
|
||
clearInterval(interval);
|
||
message.error('提取任务失败');
|
||
}
|
||
}, 5000); // 每5秒轮询
|
||
|
||
return () => clearInterval(interval);
|
||
};
|
||
```
|
||
|
||
**3. 进度动画**
|
||
```typescript
|
||
// 使用Ant Design Progress组件或自定义SVG
|
||
<Progress
|
||
type="circle"
|
||
percent={progress}
|
||
format={percent => (
|
||
<div>
|
||
<div className="text-2xl font-bold">{percent}%</div>
|
||
<div className="text-sm text-gray-500">双盲提取中</div>
|
||
</div>
|
||
)}
|
||
strokeColor={{
|
||
'0%': '#9333ea',
|
||
'100%': '#4f46e5'
|
||
}}
|
||
/>
|
||
|
||
// 双模型动画(两个小球跳动)
|
||
<div className="flex gap-2">
|
||
<div className="w-3 h-3 bg-blue-500 rounded-full animate-bounce" />
|
||
<div className="w-3 h-3 bg-orange-500 rounded-full animate-bounce delay-200" />
|
||
</div>
|
||
```
|
||
|
||
**4. 日志显示**
|
||
```typescript
|
||
<div className="h-40 overflow-y-auto bg-slate-50 p-4 rounded font-mono text-xs">
|
||
{logs.map((log, i) => (
|
||
<div key={i} className="mb-1 text-slate-600">
|
||
<span className="text-slate-400">[{log.timestamp}]</span> {log.message}
|
||
</div>
|
||
))}
|
||
<div className="animate-pulse text-purple-500">_</div>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
### 5.2 验收标准
|
||
|
||
- [ ] 点击"开始提取"后成功创建任务
|
||
- [ ] 获取到taskId
|
||
- [ ] 进度轮询正常(每5秒)
|
||
- [ ] 进度条实时更新
|
||
- [ ] 日志滚动区域正常显示
|
||
- [ ] 任务完成后自动跳转到Step 4
|
||
- [ ] 任务失败时显示错误提示
|
||
|
||
---
|
||
|
||
## 🎯 六、Phase 4: Tool B - Step 4(Day 3下午+Day 4)⭐ 核心
|
||
|
||
### 6.1 冲突验证网格(9小时)
|
||
|
||
这是整个Tool B最复杂、最核心的页面!
|
||
|
||
#### 6.1.1 页面设计
|
||
**组件**:`pages/tool-b/Step4Verify.tsx`
|
||
|
||
**UI参考**:原型V4第402-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<string, {
|
||
A: string; // DeepSeek结果
|
||
B: string; // Qwen结果
|
||
chosen: string | null; // 用户采纳的值(null=未解决)
|
||
}>;
|
||
status: 'clean' | 'conflict' | 'resolved';
|
||
conflictFields: string[]; // 冲突字段名称列表
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### 6.1.4 功能实现
|
||
|
||
**1. 获取验证数据**
|
||
```typescript
|
||
const [rows, setRows] = useState<VerifyRow[]>([]);
|
||
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<VerifyRow> = [
|
||
{
|
||
title: '#',
|
||
dataIndex: 'rowIndex',
|
||
width: 60,
|
||
fixed: 'left',
|
||
},
|
||
{
|
||
title: '原文摘要',
|
||
dataIndex: 'originalText',
|
||
width: 200,
|
||
render: (text, record) => (
|
||
<div className="flex items-center gap-2">
|
||
<FileText size={14} className="text-slate-300" />
|
||
<Tooltip title={text}>
|
||
<span className="truncate w-40">{text}</span>
|
||
</Tooltip>
|
||
</div>
|
||
),
|
||
},
|
||
// 动态字段列(根据模板生成)
|
||
...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 <ConflictCell
|
||
fieldName={field.name}
|
||
valueA={cellData.A}
|
||
valueB={cellData.B}
|
||
onAdopt={(value) => handleAdopt(record.id, field.name, value)}
|
||
/>;
|
||
}
|
||
|
||
return <CleanCell value={cellData.chosen || cellData.A} />;
|
||
},
|
||
})),
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
width: 100,
|
||
fixed: 'right',
|
||
render: (status) => {
|
||
if (status === 'clean' || status === 'resolved') {
|
||
return <Tag color="success">通过</Tag>;
|
||
}
|
||
return <Tag color="warning" className="animate-pulse">待裁决</Tag>;
|
||
},
|
||
},
|
||
];
|
||
```
|
||
|
||
**3. 冲突单元格组件** ⭐ 核心
|
||
```typescript
|
||
// components/ConflictCell.tsx
|
||
interface ConflictCellProps {
|
||
fieldName: string;
|
||
valueA: string;
|
||
valueB: string;
|
||
onAdopt: (value: string) => void;
|
||
}
|
||
|
||
const ConflictCell: React.FC<ConflictCellProps> = ({ fieldName, valueA, valueB, onAdopt }) => {
|
||
return (
|
||
<div className="flex flex-col gap-1.5 bg-orange-50 p-2 rounded">
|
||
{/* 选项A - DeepSeek */}
|
||
<button
|
||
className="text-left text-xs px-2 py-1.5 rounded border border-blue-200 bg-white hover:bg-blue-50 hover:border-blue-400 transition-all flex justify-between group"
|
||
onClick={() => onAdopt(valueA)}
|
||
>
|
||
<Tooltip title={valueA}>
|
||
<span className="truncate max-w-[100px]">{valueA}</span>
|
||
</Tooltip>
|
||
<Badge className="text-[10px] text-blue-400 group-hover:text-blue-600">DS</Badge>
|
||
</button>
|
||
|
||
{/* 选项B - Qwen */}
|
||
<button
|
||
className="text-left text-xs px-2 py-1.5 rounded border border-orange-200 bg-white hover:bg-orange-50 hover:border-orange-400 transition-all flex justify-between group"
|
||
onClick={() => onAdopt(valueB)}
|
||
>
|
||
<Tooltip title={valueB}>
|
||
<span className="truncate max-w-[100px]">{valueB}</span>
|
||
</Tooltip>
|
||
<Badge className="text-[10px] text-orange-400 group-hover:text-orange-600">QW</Badge>
|
||
</button>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
**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<string | null>(null);
|
||
|
||
// 点击行时打开侧边栏
|
||
const onRow = (record: VerifyRow) => ({
|
||
onClick: () => setSelectedRowId(record.id),
|
||
});
|
||
|
||
// Drawer组件
|
||
<Drawer
|
||
title="病历原文详情"
|
||
placement="right"
|
||
width={400}
|
||
open={!!selectedRowId}
|
||
onClose={() => setSelectedRowId(null)}
|
||
>
|
||
{(() => {
|
||
const row = rows.find(r => r.id === selectedRowId);
|
||
if (!row) return null;
|
||
|
||
return (
|
||
<div className="prose">
|
||
<p className="whitespace-pre-wrap font-serif leading-7">
|
||
{row.fullText}
|
||
</p>
|
||
|
||
<Divider />
|
||
|
||
<div className="flex flex-wrap gap-2">
|
||
{Object.keys(row.results).map(fieldName => (
|
||
<Tag
|
||
key={fieldName}
|
||
color={row.conflictFields.includes(fieldName) ? 'orange' : 'default'}
|
||
>
|
||
{fieldName}
|
||
</Tag>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
})()}
|
||
</Drawer>
|
||
```
|
||
|
||
**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;
|
||
|
||
<div className="flex items-center gap-4 mb-4">
|
||
<div className="bg-slate-100 px-3 py-1.5 rounded">
|
||
总数据: <strong>{rows.length}</strong>
|
||
</div>
|
||
|
||
{conflictCount > 0 ? (
|
||
<div className="bg-orange-50 px-3 py-1.5 rounded text-orange-700 animate-pulse">
|
||
<AlertTriangle size={16} className="inline mr-1" />
|
||
<strong>{conflictCount}</strong> 条冲突待裁决
|
||
</div>
|
||
) : (
|
||
<div className="bg-emerald-50 px-3 py-1.5 rounded text-emerald-700">
|
||
<CheckCircle2 size={16} className="inline mr-1" />
|
||
所有冲突已解决
|
||
</div>
|
||
)}
|
||
|
||
<Button onClick={handleExport}>导出当前结果</Button>
|
||
<Button type="primary" onClick={handleComplete}>完成并入库</Button>
|
||
</div>
|
||
```
|
||
|
||
---
|
||
|
||
### 6.2 验收标准
|
||
|
||
**数据加载**:
|
||
- [ ] 成功获取验证数据
|
||
- [ ] 表格列根据模板动态生成
|
||
- [ ] 分页功能正常
|
||
|
||
**冲突单元格**:
|
||
- [ ] 冲突单元格显示A/B两个按钮
|
||
- [ ] 按钮显示正确的值和模型标识(DS/QW)
|
||
- [ ] 点击按钮后采纳值
|
||
- [ ] UI乐观更新(立即生效)
|
||
- [ ] API调用成功
|
||
|
||
**侧边栏**:
|
||
- [ ] 点击行时侧边栏滑出
|
||
- [ ] 显示完整病历原文
|
||
- [ ] 显示字段标签(冲突字段高亮)
|
||
- [ ] 点击关闭按钮或外部区域关闭侧边栏
|
||
|
||
**Toolbar**:
|
||
- [ ] 冲突数统计正确
|
||
- [ ] 实时更新(裁决后减少)
|
||
- [ ] 导出按钮可点击
|
||
- [ ] 完成按钮跳转到Step 5
|
||
|
||
---
|
||
|
||
## 📦 七、Phase 5: Tool B - Step 5(Day 5上午)
|
||
|
||
### 7.1 结果导出(3小时)
|
||
|
||
#### 7.1.1 页面设计
|
||
**组件**:`pages/tool-b/Step5Result.tsx`
|
||
|
||
**UI参考**:原型V4第572-607行
|
||
|
||
**布局**:
|
||
1. 完成图标和标题
|
||
2. 统计卡片(4个)
|
||
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. [ ] 验证字段自动加载(5个字段)
|
||
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, // 每5秒轮询
|
||
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<string, {
|
||
A: string;
|
||
B: string;
|
||
chosen: string | null;
|
||
}>;
|
||
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: 智能模板配置(3个预设模板)
|
||
- ✅ Step 3: 双盲提取进度监控
|
||
- ✅ Step 4: 冲突验证网格(支持裁决)
|
||
- ✅ Step 5: 结果导出(Excel + 流转)
|
||
|
||
---
|
||
|
||
### 12.3 代码质量
|
||
|
||
**代码规范**:
|
||
- ✅ 遵守云原生开发规范
|
||
- ✅ TypeScript类型安全
|
||
- ✅ ESLint无错误
|
||
- ✅ 代码注释完善
|
||
|
||
**性能指标**:
|
||
- ✅ 首屏加载 < 2s
|
||
- ✅ 表格滚动流畅
|
||
- ✅ 裁决响应 < 500ms
|
||
- ✅ 内存占用 < 200MB
|
||
|
||
**用户体验**:
|
||
- ✅ 流畅的5步流程
|
||
- ✅ 实时进度反馈
|
||
- ✅ 直观的冲突验证界面
|
||
- ✅ 友好的错误提示
|
||
|
||
---
|
||
|
||
## 🚀 十三、下一步行动
|
||
|
||
### 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:实现3个工具卡片**
|
||
```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的前端开发,实现从文件上传到结果导出的完整5步流程,特别是**冲突验证网格**这一核心功能。
|
||
|
||
### 关键成功因素
|
||
1. ✅ **后端已100%完成**,前端可专注UI和交互
|
||
2. ✅ **平台能力完善**,可直接复用,无需重复开发
|
||
3. ✅ **设计文档齐全**,有清晰的原型和需求
|
||
4. ✅ **参考模块成熟**,ASL模块可作为参考
|
||
5. ✅ **架构方案明确**,采用方案A(独立Portal)
|
||
|
||
### 预期收益
|
||
- **用户价值**:提供AI驱动的病历结构化能力,双模型交叉验证提高数据质量
|
||
- **技术价值**:完整的前端模块化架构,可复用组件库
|
||
- **商业价值**:Tool B可独立销售,满足医疗数据处理需求
|
||
|
||
---
|
||
|
||
**开发计划制定完成!** ✅
|
||
|
||
**下一步**:验证数据库表 → 测试后端API → 开始Phase 1(Portal页面开发)
|
||
|
||
**预计完成时间**:5-6个工作日
|
||
|
||
**祝开发顺利!** 🚀
|
||
|