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
13 KiB
Week 2 Day 3 开发完成报告
日期: 2025-11-19
模块: ASL-AI智能文献
任务: 审核工作台(双行表格)+ 人工复核功能
📊 完成概述
✅ 所有计划任务已完成
核心功能
- ✅ 后端API实现(任务进度、结果列表、人工复核)
- ✅ 前端类型定义(完全匹配后端Schema)
- ✅ 前端API客户端(新增4个API函数)
- ✅ UI组件(JudgmentBadge、ConclusionTag)
- ✅ 自定义Hooks(useScreeningTask、useScreeningResults)
- ✅ 数据转换工具(双行表格数据转换)
- ✅ 审核工作台主页面(双行表格展示)
- ✅ 详情Modal(完整AI判断结果展示)
- ✅ 复核Modal(人工决策提交)
🔧 技术实现
1. 后端API(新增)
文件
backend/src/modules/asl/controllers/screeningController.ts
API端点
| 方法 | 路径 | 功能 |
|---|---|---|
| GET | /projects/:projectId/screening-task |
获取筛选任务进度 |
| GET | /projects/:projectId/screening-results |
获取筛选结果列表(分页) |
| GET | /screening-results/:resultId |
获取单个结果详情 |
| POST | /screening-results/:resultId/review |
提交人工复核 |
关键特性
- 后端分页:符合云原生架构,减少内存占用和响应时间
- 筛选功能:支持
all/conflict/included/excluded/reviewed - 冲突检测:仅当两个模型结论不一致时标记为冲突
- 人工复核:更新
finalDecision、finalDecisionBy、conflictStatus
2. 前端类型系统
文件
frontend-v2/src/modules/asl/types/index.ts
新增类型
// 判断类型
export type JudgmentType = 'match' | 'partial' | 'mismatch' | null;
// 结论类型
export type ConclusionType = 'include' | 'exclude' | 'uncertain' | null;
// 冲突状态
export type ConflictStatus = 'none' | 'conflict' | 'resolved';
// 筛选结果(完整匹配后端Schema)
export interface ScreeningResult {
// DeepSeek模型
dsModelName: string;
dsPJudgment: JudgmentType;
dsConclusion: ConclusionType;
dsReason: string | null;
// ... 省略其他字段
// Qwen模型
qwenModelName: string;
qwenPJudgment: JudgmentType;
qwenConclusion: ConclusionType;
// ... 省略其他字段
// 冲突和决策
conflictStatus: ConflictStatus;
finalDecision: 'include' | 'exclude' | 'pending' | null;
}
// 双行表格数据
export interface DoubleRowData {
key: string;
literatureIndex: number;
isFirstRow: boolean;
modelName: string;
P: JudgmentType;
I: JudgmentType;
C: JudgmentType;
S: JudgmentType;
conclusion: ConclusionType;
confidence: number | null;
hasConflict: boolean;
originalResult: ScreeningResult;
}
3. 前端API客户端
文件
frontend-v2/src/modules/asl/api/index.ts
新增函数
// 获取筛选任务
export async function getScreeningTask(projectId: string)
// 获取结果列表(分页)
export async function getScreeningResultsList(
projectId: string,
params?: { page, pageSize, filter }
)
// 获取结果详情
export async function getScreeningResultDetail(resultId: string)
// 提交人工复核
export async function reviewScreeningResult(
resultId: string,
data: { decision: 'include' | 'exclude', note?: string }
)
4. UI组件
JudgmentBadge (判断结果徽章)
文件: frontend-v2/src/modules/asl/components/JudgmentBadge.tsx
功能:
- 显示PICOS各维度判断(match/partial/mismatch)
- 颜色编码:绿色(匹配)/ 橙色(部分)/ 红色(不匹配)
- 支持Tooltip显示证据
ConclusionTag (结论标签)
文件: frontend-v2/src/modules/asl/components/ConclusionTag.tsx
功能:
- 显示筛选结论(纳入/排除/不确定)
- 颜色编码:绿色(纳入)/ 灰色(排除)/ 橙色(不确定)
- 支持大小调整(small/middle/large)
5. 自定义Hooks
useScreeningTask (任务轮询)
文件: frontend-v2/src/modules/asl/hooks/useScreeningTask.ts
功能:
- 2秒轮询任务进度
- 任务完成/失败时自动停止轮询
- 返回进度百分比、状态标记
关键实现:
refetchInterval: (query) => {
const task = query.state.data?.data;
if (task?.status === 'completed' || task?.status === 'failed') {
return false; // 停止轮询
}
return 2000; // 2秒轮询
}
useScreeningResults (结果列表)
文件: frontend-v2/src/modules/asl/hooks/useScreeningResults.ts
功能:
- 分页查询筛选结果
- 支持筛选条件切换
- 集成人工复核Mutation
keepPreviousData: true避免页面切换闪烁
6. 数据转换工具
文件
frontend-v2/src/modules/asl/utils/tableTransform.ts
核心函数
// 将ScreeningResult[]转为双行表格数据
export function transformToDoubleRows(results: ScreeningResult[]): DoubleRowData[]
// 判断是否冲突
export function hasConflict(result: ScreeningResult): boolean
// 获取最终决策
export function getFinalDecision(result: ScreeningResult): string
// 计算进度百分比
export function calculateProgress(processed: number, total: number): number
双行转换逻辑:
- 每篇文献生成2行数据
- 第1行:DeepSeek结果(
isFirstRow: true) - 第2行:Qwen结果(
isFirstRow: false) - 序号、标题、操作列使用
rowSpan: 2合并
7. 审核工作台主页面
文件
frontend-v2/src/modules/asl/pages/ScreeningWorkbench.tsx
页面结构
审核工作台
├── 任务进度卡片
│ ├── 进度条(实时更新)
│ ├── 统计信息(已处理/成功/冲突/失败)
│ └── 刷新按钮
│
├── 筛选Tab
│ ├── 全部
│ ├── 待复核(有冲突)⚠️
│ ├── 已纳入
│ ├── 已排除
│ └── 已复核
│
└── 双行表格
├── 列:序号、标题、模型、P、I、C、S、结论、操作
├── 行:每篇文献2行(DeepSeek + Qwen)
├── 冲突高亮(红色背景)
└── 分页(50篇/页,100行数据)
关键特性
- 双行表格:使用
rowSpan实现合并单元格 - 冲突高亮:
rowClassName动态添加bg-red-50 - 智能轮询:任务运行时显示Spin,完成后加载结果
- 分页优化:
pageSize * 2处理双行数据
表格列定义示例
{
title: '#',
dataIndex: 'literatureIndex',
width: 60,
align: 'center',
onCell: (record) => ({
rowSpan: record.isFirstRow ? 2 : 0, // 第1行跨2行,第2行不渲染
}),
}
8. 详情Modal
文件
frontend-v2/src/modules/asl/components/DetailModal.tsx
展示内容
-
文献信息
- 标题、作者、期刊、年份、PMID、摘要
-
DeepSeek结果
- 模型标签(蓝色)
- 结论Tag + 置信度
- PICOS四维度判断
- 完整判断理由(蓝色背景)
-
Qwen结果
- 模型标签(紫色)
- 结论Tag + 置信度
- PICOS四维度判断
- 完整判断理由(紫色背景)
-
冲突提示(如果有)
- 红色提示框
- 建议人工复核
-
人工复核结果(如果有)
- 绿色背景
- 显示决策和备注
9. 复核Modal
文件
frontend-v2/src/modules/asl/components/ReviewModal.tsx
功能
-
文献摘要展示
- 显示标题供复核参考
-
AI判断对比
- 表格形式对比DeepSeek和Qwen
- 显示结论和置信度
- 冲突提示
-
备注输入
- TextArea,可选填写
- 用于记录排除原因或特殊说明
-
决策按钮
- 绿色"纳入"按钮
- 灰色"排除"按钮
- 提交后自动刷新列表
📂 文件变更统计
后端(Backend)
新增文件:
src/modules/asl/controllers/screeningController.ts(315行)
修改文件:
src/modules/asl/routes/index.ts- 注册新路由
前端(Frontend)
新增文件:
src/modules/asl/types/index.ts- 更新类型定义src/modules/asl/api/index.ts- 新增API函数src/modules/asl/components/JudgmentBadge.tsx(77行)src/modules/asl/components/ConclusionTag.tsx(71行)src/modules/asl/components/DetailModal.tsx(178行)src/modules/asl/components/ReviewModal.tsx(157行)src/modules/asl/hooks/useScreeningTask.ts(62行)src/modules/asl/hooks/useScreeningResults.ts(79行)src/modules/asl/utils/tableTransform.ts(92行)src/modules/asl/pages/ScreeningWorkbench.tsx(371行)
总计:
- 后端新增:~315行
- 前端新增:~1087行
- 总计:~1402行代码
🎯 功能演示流程
1. 从设置页面启动筛选
用户 → 设置与启动页面 → 上传Excel → 填写PICOS →
点击"开始AI初筛" → 自动跳转审核工作台
2. 审核工作台
进入页面 → 显示任务进度(2秒轮询)→
任务完成 → 加载筛选结果(双行表格)→
冲突文献高亮显示(红色背景)
3. 查看详情
点击"查看详情"按钮 → 弹出DetailModal →
显示完整AI判断结果 →
DeepSeek + Qwen详细对比 →
查看判断理由和证据
4. 人工复核
点击"人工复核"按钮(仅冲突文献显示)→
弹出ReviewModal →
对比两个模型结论 →
填写备注(可选)→
点击"纳入"或"排除" →
提交成功 → 列表自动刷新
5. 筛选Tab切换
点击"待复核(有冲突)"Tab →
仅显示冲突文献 →
点击"已纳入"Tab →
显示所有纳入的文献
🔍 关键技术点
1. 双行表格实现
方案: 使用Ant Design Table的 rowSpan 属性
优势:
- 原生支持,性能好
- 代码简洁
- 渲染效率高
实现步骤:
- 数据转换:1篇文献 → 2行数据
- 列定义:第1行
rowSpan: 2,第2行rowSpan: 0 - 样式:冲突行统一背景色
2. 任务轮询机制
技术: React Query的 refetchInterval
智能停止:
refetchInterval: (query) => {
const task = query.state.data?.data;
if (task?.status === 'completed' || task?.status === 'failed') {
return false; // 停止
}
return 2000; // 继续轮询
}
3. 后端分页
为什么选择后端分页?
在云原生架构(Serverless SAE + RDS)下:
- ✅ 减少单次查询数据量
- ✅ 降低内存占用
- ✅ 提升响应速度
- ✅ 适合大数据量场景
- ✅ 符合Serverless按请求计费的成本优化策略
实现:
SELECT * FROM asl_screening_results
WHERE project_id = ?
ORDER BY conflict_status DESC, created_at DESC
LIMIT 50 OFFSET 0;
4. 冲突检测逻辑
规则: 仅当 dsConclusion !== qwenConclusion 时标记冲突
不考虑:
- PICOS各维度差异
- 置信度差异
- 证据短语差异
原因: 用户明确要求"仅结论不一致算冲突"
✅ 测试检查清单
后端API
GET /projects/:projectId/screening-task- 返回任务进度GET /projects/:projectId/screening-results?page=1&pageSize=50&filter=conflict- 返回冲突结果GET /screening-results/:resultId- 返回详情POST /screening-results/:resultId/review- 提交复核
前端UI
- 任务进度实时更新(2秒轮询)
- 双行表格正确显示(每篇文献2行)
- 冲突文献红色高亮
- 筛选Tab切换正常
- 详情Modal显示完整信息
- 复核Modal提交成功
- 分页功能正常
边界情况
- 无projectId时显示错误提示
- 任务运行中显示Spin
- 任务失败显示错误信息
- 空数据显示Empty组件
- 网络错误处理
🚀 下一步计划(Week 2 Day 4-5)
Day 4: 优化与增强
- 批量操作功能
- 导出Excel功能
- 搜索和过滤优化
- 性能优化
Day 5: 结果展示页面
- 统计图表
- 排除原因分析
- 导出最终结果
- 整体测试和调优
📝 开发总结
完成度
- ✅ 100% - 所有Day 3计划任务已完成
- ✅ 代码质量良好,无linter错误
- ✅ 类型定义完整,TypeScript类型安全
- ✅ 组件化设计,可复用性强
技术亮点
- 双行表格:创新使用
rowSpan实现复杂布局 - 智能轮询:任务完成自动停止,节省资源
- 后端分页:云原生架构最佳实践
- 类型安全:完整的TypeScript类型定义
- 组件复用:Badge、Tag、Modal高度封装
遇到的挑战
-
❌ 后端字段映射:初始类型定义与Schema不匹配
- ✅ 解决:详细阅读Prisma Schema,精确匹配字段名
-
❌ 双行表格rowSpan:第一次实现时数据转换有误
- ✅ 解决:理解
isFirstRow标记,正确设置rowSpan: 2和rowSpan: 0
- ✅ 解决:理解
-
❌ 轮询停止机制:任务完成后仍在轮询
- ✅ 解决:使用React Query的智能
refetchInterval函数
- ✅ 解决:使用React Query的智能
开发效率
- 总耗时: 约2小时
- 代码行数: 1402行
- 文件数量: 11个文件
🎉 结语
Day 3任务圆满完成!
审核工作台是整个ASL模块的核心功能,实现了:
- ✅ 双模型结果对比展示
- ✅ 冲突检测与高亮
- ✅ 人工复核完整流程
- ✅ 实时任务进度监控
- ✅ 云原生架构最佳实践
期待继续Day 4-5的开发,完善整个标题摘要初筛功能!🚀
报告日期: 2025-11-19
报告人: AI Assistant
审核人: 待定