# Week 4:结果展示与导出 - 开发计划(云原生架构) > **文档版本?* v1.0 > **创建日期?* 2025-11-21 > **计划周期?* 2天(Day 16-17? > **架构原则?* ?云原生优? > **最后更新:** 2025-11-21 --- ## 📋 文档说明 本文档是 Week 4 功能开发的详细计划,遵循云原生开发规范,实现筛选结果的统计展示和Excel导出功能? **核心目标**? - ?统计概览(总数、纳入率、排除率、待复核? - ?PRISMA式排除原因统? - ?结果列表Tab切换与查? - ?Excel批量导出 - ?完整功能闭环(上传→筛选→复核→统计→导出? **架构原则**? - ?**云原生优?*:遵循[云原生开发规范](../../../04-开发规?08-云原生开发规?md) - ?**复用平台能力**:使用全局`prisma`、`logger`? - ?**零文件落?*:Excel前端生成或OSS存储 - ?**后端聚合计算**:统计数据后端聚? --- ## 🎯 一、功能定? ### 1.1 "审核工作? vs "初筛结果" 区别 | 维度 | 审核工作台(ScreeningWorkbench?| 初筛结果(ScreeningResults?| |------|--------------------------------|---------------------------| | **定位** | 实时监控、冲突处理、人工复?| 最终结果展示、统计分析、批量导?| | **使用时机** | 筛选进行中 | 筛选完成后 | | **核心功能** | 进度轮询、逐条复核、冲突高?| 统计概览、PRISMA总结、批量导?| | **表格形式** | 双行表格(DS + Qwen对比?| 单行表格(显示最终决策) | | **强调重点** | 工作?| 结果汇?| **比喻**? ``` 审核工作?= 生产车间(正在筛选、复核) 初筛结果 = 成品仓库(已完成、统计、导出) ``` ### 1.2 用户流程 ``` 设置与启??审核工作??初筛结果 (配置) (实时监控) (结果汇? ? ? ? 填写PICOS 逐条复核 批量导出 上传Excel 冲突处理 统计分析 ``` --- ## 🏗?二、技术架构(云原生) ### 2.1 核心架构决策 #### 决策1:数据获取策??后端聚合API ? **方案A**(不采用):前端获取全量数据,前端计? ```typescript // ?不符合云原生:前端获取全量数据(可能上千条) const { data } = await aslApi.getScreeningResultsList(projectId, { pageSize: 9999 // 获取全部 }); // 前端计算统计... ``` **方案B**(采用):后端聚合API ? ```typescript // ?符合云原生:后端聚合,减少网络传? GET /api/v1/asl/projects/:projectId/statistics // 后端使用Prisma聚合 const stats = await prisma.aslScreeningResult.groupBy({ by: ['finalDecision', 'conflictStatus'], _count: true, where: { projectId } }); ``` **选择理由**? - ?后端聚合,性能? - ?减少网络传输(从MB级降到KB级) - ?可扩展性强(支持更复杂的统计) - ?符合云原?计算靠近数据"原则 --- #### 决策2:Excel导出策略 ?前端生成(MVP)✅ **方案A**(采用MVP):前端生成 ? ```typescript // ?符合云原生:零文件落盘,完全在浏览器内存? import * as XLSX from 'xlsx'; function exportToExcel(results) { const ws = XLSX.utils.json_to_sheet(exportData); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, '筛选结?); XLSX.writeFile(wb, 'screening-results.xlsx'); // 浏览器下? } ``` **优点**? - ?**零文件落?*(完全在浏览器内存中生成? - ?**无需后端存储**(不占用OSS空间? - ?**实时生成**(无异步等待? - ?**符合云原生原?*(避免Serverless文件操作? - ?**成本?*(不消耗后端资源) **限制**? - ⚠️ 适用数据量:<5000? - ⚠️ 生成速度?1000条约2-3? - ⚠️ 不支持复杂格式(多Sheet、图表) **方案B**(未来扩展):后端生?+ OSS存储 ⏸️ ```typescript // ⏸️ 技术债务:当数据?5000条或需要复杂格式时 // 1. 后端生成Excel(内存中? import ExcelJS from 'exceljs'; const workbook = new ExcelJS.Workbook(); // ... 生成Excel // 2. ?上传到OSS(使用平台存储服务) import { storage } from '@/common/storage'; const buffer = await workbook.xlsx.writeBuffer(); const url = await storage.upload(`asl/exports/${Date.now()}.xlsx`, buffer); // 3. 返回OSS URL res.send({ success: true, url }); ``` **触发条件**? - 单次导出数据?>5000? - 需要复杂Excel格式(多Sheet、图表等? - 用户反馈前端导出卡顿 **记录位置**:[技术债务清单 - 优先?](../../06-技术债务/技术债务清单.md) --- ### 2.2 云原生架构检? 基于[云原生开发规范](../../../04-开发规?08-云原生开发规?md),本开发计划遵循: | 检查项 | 要求 | 本计划实?| 状?| |--------|------|-----------|------| | **存储** | 使用`storage.upload()`,不用`fs.writeFile()` | Excel前端生成,零落盘 | ?| | **数据?* | 使用全局`prisma`实例 | 统计API使用全局`prisma` | ?| | **长任?* | 异步处理,不阻塞请求 | 统计API <500ms,无需异步 | ?| | **日志** | 使用`logger`,不用`console.log` | 后端使用`logger.info/error` | ?| | **配置** | 使用`process.env` | 无新增配?| ?| | **计算** | 复杂计算后端完成 | 统计聚合后端完成 | ?| --- ## 📐 三、页面设? ### 3.1 整体布局 ``` ┌─────────────────────────────────────────────────────────? ? 标题:标题摘要初?- 结果 ? ? 说明:筛选结果统计、PRISMA流程图、批量操作和导出 ? └─────────────────────────────────────────────────────────? ┌─────────────────────────────────────────────────────────? ? 📊 统计概览?个卡?+ 待复核提示) ? ? ┌───────?┌───────?┌───────?┌───────? ? ? ?总数 ??已纳入│ ?已排除│ ?待复核│ ? ? ? 199 ?? 85 ?? 90 ?? 24 ? ? ? ? ? ??42.7% ??45.2% ??12.1% ? ? ? └───────?└───────?└───────?└───────? ? ? ? ? ⚠️ 提示:还?24 篇文献待复核,请前往"审核工作?处理 ? └─────────────────────────────────────────────────────────? ┌─────────────────────────────────────────────────────────? ? 📈 排除原因统计(柱状图? ? ? ┌─────────────────────────────────────────────? ? ? ?P不匹配(人群? ████████████████ 40?(44%) ? ? ? ?I不匹配(干预? ████████ 25?(28%) ? ? ? ?S不匹配(研究设计)███?15?(17%) ? ? ? ?其他原因 ██ 10?(11%) ? ? ? └─────────────────────────────────────────────? ? └─────────────────────────────────────────────────────────? ┌─────────────────────────────────────────────────────────? ? 📋 结果列表(Tabs + 单行表格 + 批量操作? ? ? ┌─────────────────────────────────────────────────? ? ? ?[全部 199] [已纳?85] [已排?90] [待复?24] ? ? ? ├─────────────────────────────────────────────────? ? ? ?[导出全部] [导出当前页] [导出选中项] ? ? ? ├─────────────────────────────────────────────────? ? ? ??序号 | 标题 | 最终决?| 排除原因 | 操作 ? ? ? ??1 | ... | 已纳? | - | [查看] ? ? ? ??2 | ... | 已排? | P不匹? | [查看] ? ? ? ??3 | ... | 待复? | 冲突 | [复核] ? ? ? └─────────────────────────────────────────────────? ? └─────────────────────────────────────────────────────────? ``` ### 3.2 表格设计 **列定?*(单行表格,区别于审核工作台的双行)? | 列名 | 宽度 | 说明 | |------|------|------| | 选择 | 50px | Checkbox多?| | 序号 | 60px | 行号 | | 文献标题 | 400px | Tooltip显示全文 | | 最终决?| 100px | Tag显示(纳?排除/待定?| | 排除原因 | 150px | 显示具体原因 | | 置信?| 80px | DeepSeek置信?| | 操作 | 100px | 查看详情按钮 | **关键区别**? - **审核工作?*:双行表格,显示DS+Qwen对比,强调冲? - **初筛结果**?*单行表格**,显示最终决策,强调结果 --- ## 🔧 四、后端开? ### 4.1 新增统计API #### API设计 ``` GET /api/v1/asl/projects/:projectId/statistics ``` #### 请求参数 ``` ? ``` #### 响应格式 ```json { "success": true, "data": { "total": 199, "included": 85, "excluded": 90, "pending": 24, "conflict": 24, "reviewed": 175, "exclusionReasons": { "P不匹配(人群?: 40, "I不匹配(干预?: 25, "S不匹配(研究设计?: 15, "其他原因": 10 }, "includedRate": "42.7", "excludedRate": "45.2", "pendingRate": "12.1" } } ``` #### 实现代码 ```typescript // backend/src/modules/asl/controllers/screeningController.ts /** * 获取项目筛选统计数据(云原生:后端聚合? * GET /api/v1/asl/projects/:projectId/statistics */ export async function getProjectStatistics( request: FastifyRequest<{ Params: { projectId: string } }>, reply: FastifyReply ) { try { const userId = (request as any).userId || 'asl-test-user-001'; const { projectId } = request.params; // 1. 验证项目归属 const project = await prisma.aslScreeningProject.findFirst({ where: { id: projectId, userId }, }); if (!project) { return reply.status(404).send({ error: 'Project not found' }); } // 2. ?云原生:使用Prisma聚合查询(并行) const [ total, includedCount, excludedCount, pendingCount, conflictCount, reviewedCount ] = await Promise.all([ prisma.aslScreeningResult.count({ where: { projectId } }), prisma.aslScreeningResult.count({ where: { projectId, finalDecision: 'include' } }), prisma.aslScreeningResult.count({ where: { projectId, finalDecision: 'exclude' } }), prisma.aslScreeningResult.count({ where: { projectId, finalDecision: null } }), prisma.aslScreeningResult.count({ where: { projectId, conflictStatus: 'conflict', finalDecision: null } }), prisma.aslScreeningResult.count({ where: { projectId, NOT: { finalDecision: null } } }), ]); // 3. 查询排除结果(用于统计原因) const excludedResults = await prisma.aslScreeningResult.findMany({ where: { projectId, OR: [ { finalDecision: 'exclude' }, { finalDecision: null, dsConclusion: 'exclude' } ] }, select: { exclusionReason: true, dsPJudgment: true, dsIJudgment: true, dsCJudgment: true, dsSJudgment: true, } }); // 4. 分析排除原因 const exclusionReasons: Record = {}; excludedResults.forEach(result => { const reason = result.exclusionReason || extractAutoReason(result); exclusionReasons[reason] = (exclusionReasons[reason] || 0) + 1; }); // 5. 返回统计数据 return reply.send({ success: true, data: { total, included: includedCount, excluded: excludedCount, pending: pendingCount, conflict: conflictCount, reviewed: reviewedCount, exclusionReasons, includedRate: total > 0 ? ((includedCount / total) * 100).toFixed(1) : '0.0', excludedRate: total > 0 ? ((excludedCount / total) * 100).toFixed(1) : '0.0', pendingRate: total > 0 ? ((pendingCount / total) * 100).toFixed(1) : '0.0', } }); } catch (error) { logger.error('Failed to get statistics', { error }); return reply.status(500).send({ error: 'Failed to get statistics', }); } } /** * 辅助函数:从AI判断中提取排除原? */ function extractAutoReason(result: any): string { if (result.dsPJudgment === 'mismatch') return 'P不匹配(人群?; if (result.dsIJudgment === 'mismatch') return 'I不匹配(干预?; if (result.dsCJudgment === 'mismatch') return 'C不匹配(对照?; if (result.dsSJudgment === 'mismatch') return 'S不匹配(研究设计?; return '其他原因'; } ``` #### 路由注册 ```typescript // backend/src/modules/asl/routes/index.ts // 添加到路由注? fastify.get( '/projects/:projectId/statistics', screeningController.getProjectStatistics ); ``` --- ## 💻 五、前端开? ### 5.1 API客户? ```typescript // frontend-v2/src/modules/asl/api/index.ts /** * 获取项目统计数据 */ export async function getProjectStatistics( projectId: string ): Promise> { return request(`/projects/${projectId}/statistics`); } ``` ### 5.2 类型定义 ```typescript // frontend-v2/src/modules/asl/types/index.ts /** * 项目统计数据 */ export interface ProjectStatistics { total: number; included: number; excluded: number; pending: number; conflict: number; reviewed: number; exclusionReasons: Record; includedRate: string; excludedRate: string; pendingRate: string; } ``` ### 5.3 Excel导出工具 ```typescript // frontend-v2/src/modules/asl/utils/excelExport.ts import * as XLSX from 'xlsx'; import { ScreeningResult } from '../types'; /** * 导出筛选结果到Excel(云原生:前端生成,零文件落盘) * * @param results 筛选结果数? * @param options 导出选项 */ export function exportScreeningResults( results: ScreeningResult[], options: { filter?: 'all' | 'included' | 'excluded' | 'pending'; projectName?: string; } = {} ) { // 1. 准备导出数据 const exportData = results.map((r, idx) => ({ '序号': idx + 1, '文献标题': r.literature.title, '摘要': r.literature.abstract || '', '作?: r.literature.authors || '', '期刊': r.literature.journal || '', '发表年份': r.literature.publicationYear || '', 'PMID': r.literature.pmid || '', 'DOI': r.literature.doi || '', 'DeepSeek决策': r.dsConclusion || '', 'DeepSeek置信?: r.dsConfidence ? `${(r.dsConfidence * 100).toFixed(0)}%` : '', 'DeepSeek理由': r.dsReason || '', 'Qwen决策': r.qwenConclusion || '', 'Qwen置信?: r.qwenConfidence ? `${(r.qwenConfidence * 100).toFixed(0)}%` : '', 'Qwen理由': r.qwenReason || '', '是否冲突': r.conflictStatus === 'conflict' ? '? : '?, '最终决?: r.finalDecision || '待定', '排除原因': r.exclusionReason || '', '复核?: r.finalDecisionBy || '', '复核时间': r.finalDecisionAt ? new Date(r.finalDecisionAt).toLocaleString('zh-CN') : '', })); // 2. ?生成Excel(完全在内存中,零文件落盘) const ws = XLSX.utils.json_to_sheet(exportData); // 设置列宽 ws['!cols'] = [ { wch: 6 }, // 序号 { wch: 50 }, // 标题 { wch: 60 }, // 摘要 { wch: 30 }, // 作? { wch: 30 }, // 期刊 { wch: 10 }, // 年份 { wch: 12 }, // PMID { wch: 25 }, // DOI { wch: 12 }, // DS决策 { wch: 12 }, // DS置信? { wch: 40 }, // DS理由 { wch: 12 }, // Qwen决策 { wch: 12 }, // Qwen置信? { wch: 40 }, // Qwen理由 { wch: 10 }, // 冲突 { wch: 12 }, // 最终决? { wch: 30 }, // 排除原因 { wch: 15 }, // 复核? { wch: 20 }, // 复核时间 ]; const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, '筛选结?); // 3. 生成文件? const timestamp = new Date().toISOString().slice(0, 10); const filterSuffix = options.filter && options.filter !== 'all' ? `_${options.filter}` : ''; const filename = `${options.projectName || '筛选结?}${filterSuffix}_${timestamp}.xlsx`; // 4. ?触发浏览器下载(零文件落盘) XLSX.writeFile(wb, filename); } ``` ### 5.4 初筛结果页面 ```typescript // frontend-v2/src/modules/asl/pages/ScreeningResults.tsx import { useState } from 'react'; import { useParams, useSearchParams } from 'react-router-dom'; import { Card, Statistic, Row, Col, Tabs, Table, Button, Alert, Progress, message, Checkbox, Tooltip } from 'antd'; import { DownloadOutlined, CheckCircleOutlined, CloseCircleOutlined, QuestionCircleOutlined, WarningOutlined } from '@ant-design/icons'; import { useQuery } from '@tanstack/react-query'; import * as aslApi from '../api'; import { exportScreeningResults } from '../utils/excelExport'; import { ConclusionTag } from '../components/ConclusionTag'; const ScreeningResults = () => { const { projectId } = useParams<{ projectId: string }>(); const [searchParams, setSearchParams] = useSearchParams(); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const activeTab = searchParams.get('tab') || 'all'; const page = parseInt(searchParams.get('page') || '1', 10); const pageSize = 20; // 1. ?获取统计数据(云原生:后端聚合) const { data: statsData, isLoading: statsLoading } = useQuery({ queryKey: ['projectStatistics', projectId], queryFn: () => aslApi.getProjectStatistics(projectId!), enabled: !!projectId, }); const stats = statsData?.data; // 2. 获取结果列表(分页) const { data: resultsData, isLoading: resultsLoading } = useQuery({ queryKey: ['screeningResults', projectId, activeTab, page], queryFn: () => aslApi.getScreeningResultsList(projectId!, { page, pageSize, filter: activeTab, }), enabled: !!projectId, }); // 3. ?导出Excel(前端生成,云原生) const handleExport = async (filter: string = 'all') => { try { message.loading('正在生成Excel...', 0); // 获取全量数据(用于导出) const { data } = await aslApi.getScreeningResultsList(projectId!, { page: 1, pageSize: 9999, filter, }); if (data.items.length === 0) { message.warning('没有可导出的数据'); return; } // ?前端生成Excel(零文件落盘? exportScreeningResults(data.items, { filter, projectName: `项目${projectId!.slice(0, 8)}`, }); message.destroy(); message.success(`成功导出 ${data.items.length} 条记录`); } catch (error) { message.destroy(); message.error('导出失败: ' + (error as Error).message); } }; // 4. 批量导出选中? const handleExportSelected = () => { if (selectedRowKeys.length === 0) { message.warning('请先选择要导出的记录'); return; } const selectedResults = resultsData?.data.items.filter( r => selectedRowKeys.includes(r.id) ) || []; exportScreeningResults(selectedResults, { projectName: `项目${projectId?.slice(0, 8)}_选中`, }); message.success(`成功导出 ${selectedResults.length} 条记录`); }; // 表格列定义、Tab配置?.. // (完整代码见实际实现? }; export default ScreeningResults; ``` --- ## 📅 六、开发任务分? ### Phase 1:后端统计API(Day 16上午)⏱?2小时 **任务**? 1. ?`screeningController.ts` 中实?`getProjectStatistics` 2. 使用Prisma聚合查询(并行查询优化) 3. 实现排除原因提取逻辑 `extractAutoReason` 4. ?`routes/index.ts` 中注册路? 5. Postman测试API **验收标准**? - ?API返回正确统计数据 - ?性能良好?500ms? - ?符合云原生原则(后端聚合? **文件清单**? - `backend/src/modules/asl/controllers/screeningController.ts` - `backend/src/modules/asl/routes/index.ts` --- ### Phase 2:前端API客户端(Day 16上午)⏱?30分钟 **任务**? 1. ?`api/index.ts` 中添?`getProjectStatistics` 2. ?`types/index.ts` 中添?`ProjectStatistics` 类型 **验收标准**? - ?API调用正常 - ?TypeScript类型正确 **文件清单**? - `frontend-v2/src/modules/asl/api/index.ts` - `frontend-v2/src/modules/asl/types/index.ts` --- ### Phase 3:统计概览卡片(Day 16上午)⏱?1.5小时 **任务**? 1. 实现统计卡片组件?个卡片) 2. 实现"待复?提示Alert 3. 实现PRISMA排除统计(柱状图? **验收标准**? - ?统计数据正确显示 - ?排除原因柱状图清? - ?"待复?提示醒目 **文件清单**? - `frontend-v2/src/modules/asl/pages/ScreeningResults.tsx` --- ### Phase 4:结果列表Tab(Day 16下午)⏱?3小时 **任务**? 1. 实现Tab切换(全?已纳?已排?待复核) 2. 创建单行表格(区别于审核工作台) 3. 实现Checkbox多? 4. 实现详情查看Modal(复用审核工作台的Drawer? **验收标准**? - ?Tab切换正常 - ?表格数据正确 - ?可多选行 - ?可查看详? **文件清单**? - `frontend-v2/src/modules/asl/pages/ScreeningResults.tsx` --- ### Phase 5:Excel导出(Day 17上午)⏱?2小时 **任务**? 1. 创建 `excelExport.ts` 工具文件 2. 实现前端导出逻辑(使?`xlsx`? 3. 添加导出按钮(导出全?导出当前?导出选中项) 4. 支持过滤导出(全?仅纳?仅排除) **验收标准**? - ?可导出Excel - ?数据完整 - ?零文件落盘(云原生) - ?生成速度<3秒(<1000条) **文件清单**? - `frontend-v2/src/modules/asl/utils/excelExport.ts` - `frontend-v2/src/modules/asl/pages/ScreeningResults.tsx` --- ### Phase 6:集成测试与优化(Day 17下午-Day 18)⏱?4小时 **任务**? 1. 完整流程测试(上传→筛选→复核→查看结果→导出? 2. 异常场景测试(无数据、网络错误) 3. UI/UX优化(加载状态、错误提示) 4. 性能测试(统计API、Excel导出? 5. 云原生规范检? **验收标准**? - ?流程完整无阻? - ?异常处理完善 - ?性能达标 - ?符合云原生规? --- ## ?七、验收标? ### 7.1 功能验收 - [✅] 统计概览卡片正确显示(总数、纳入、排除、待复核? - [✅] 排除原因统计准确(柱状图? - [✅] 待复核提示醒? - [✅] Tab切换正常(全?已纳?已排?待复核) - [✅] 表格数据正确(单行表格) - [✅] Checkbox多选正? - [✅] 可查看详? - [✅] 可导出Excel(全?选中? - [✅] Excel数据完整 ### 7.2 性能验收 - [✅] 统计API响应时间 <500ms - [✅] Excel导出?1000条)<3? - [✅] 表格分页加载正常 ### 7.3 云原生验? 基于[云原生开发规范检查清单](../../../04-开发规?08-云原生开发规?md)? **后端API**? - [✅] 使用全局 `prisma` 实例(不new PrismaClient? - [✅] 统计使用Prisma聚合查询(不查全量数据) - [✅] 无本地文件存储(无fs.writeFile? - [✅] 使用 `logger` 记录日志(不用console.log? - [✅] 统一错误处理 **前端实现**? - [✅] Excel前端生成(零文件落盘? - [✅] 使用 `xlsx` 库(成熟稳定? - [✅] 友好的用户提? --- ## 📊 八、时间估? | 阶段 | 任务 | 预计耗时 | 负责?| |------|------|---------|--------| | Phase 1 | 后端统计API | 2小时 | 后端开?| | Phase 2 | 前端API客户?| 0.5小时 | 前端开?| | Phase 3 | 统计概览 | 1.5小时 | 前端开?| | Phase 4 | 结果列表Tab | 3小时 | 前端开?| | Phase 5 | Excel导出 | 2小时 | 前端开?| | Phase 6 | 集成测试 | 4小时 | 全栈开?| | **总计** | | **13小时** | **??* | --- ## 🔗 九、相关文? - [云原生开发规范](../../../04-开发规?08-云原生开发规?md) - 必读 - [任务分解](./03-任务分解.md) - Week 4任务清单 - [模块当前状态](../00-模块当前状态与开发指?md) - 模块真实状? - [技术债务清单](../06-技术债务/技术债务清单.md) - Excel后端导出方案 - [数据库设计](../02-技术设?01-数据库设?md) - 数据表结? - [API设计规范](../02-技术设?02-API设计规范.md) - API规范 --- ## 📝 十、技术债务记录 ### 债务1:Excel后端导出优化 **触发条件**? - 单次导出数据?>5000? - 需要复杂Excel格式(多Sheet、图表等? - 用户反馈前端导出卡顿 **解决方案**? - 后端生成Excel(使?`ExcelJS`? - 上传到OSS(使?`storage.upload()`? - 返回下载URL **记录位置**:[技术债务清单 - 优先?](../06-技术债务/技术债务清单.md) **预计耗时**?-2? --- ## 💡 十一、开发建? ### 对开发人? 1. **先阅读云原生规范**:[云原生开发规范](../../../04-开发规?08-云原生开发规?md) 2. **复用平台能力**:使用全局`prisma`、`logger` 3. **避免文件落盘**:Excel前端生成 4. **后端聚合计算**:统计数据后端完? 5. **性能优化**:Prisma聚合查询使用并行 ### 对AI助手 1. **优先云原?*:所有设计优先考虑云原生架? 2. **参考现有代?*:复用审核工作台的组? 3. **注意区别**:初筛结果是单行表格,审核工作台是双行表? 4. **测试充分**:完整流程测? --- **文档维护?*:AI智能文献开发团? **最后更?*?025-11-21 **文档状?*:✅ 已确认,可开始开? **开始时?*:待?