# 全局搜索与高级检索方案 > 状态:已确认,开始实施 > 创建日期:2026-08-14 > 推进顺序:P1 → F1 → F2 → F3 → F4 → 测试 ## 一、需求最终确认 ### 1. 数据模型新增 | 字段 | 类型 | 适用对象 | 默认值 | 可改 | |------|------|---------|--------|------| | `organization_id` | 外键 → organizations | 主案/子案/附件 | 创建者当前 `organization_id` | ✅ | | `organization_name` | 字符串(冗余) | 同上 | 创建者当前 `organization_name` | ✅(联动 id 改名) | > 与现有的"创建者"字段独立维护。用户调岗不影响历史文档归属。 ### 2. 检索条件矩阵 | 条件 | 控件 | 匹配字段 | 默认值 | 行为 | |------|------|---------|--------|------| | 简单关键词 | 顶部全局文本框 | 标题(主案/子案/附件) | 空=不过滤 | 模糊匹配 | | 正文关键词 | 高级检索文本框 | `contentText`/`searchText` | 空=不过滤 | 模糊匹配 | | 密级 | 高级检索多选框 | `securityLevel` | 不勾=不过滤 | 勾选项内 OR | | 所属部门 | 高级检索文本框 | `organization_name` | 空=不过滤 | 模糊匹配 | **叠加**:四条件全部 AND。 ### 3. 列表行为 **主案列表**(顶部表格): - 简单关键词命中标题 → 入列表 - 简单关键词未命中主案标题,但命中其下可见子案/附件标题 → 主案入列表,显示徽章 - `📄 子案 N` / `📎 附件 N`(同时命中同时显示) - 高级检索条件(正文/密级/部门)只过滤主案本身——主案不满足则不入列表(即便其下子案/附件满足) - 主案标题中的简单关键词 → 高亮 **子案/附件 Tab**(选中主案后): - 应用全部四条件到该主案下的子案/附件 - 简单关键词匹配标题 - 正文关键词匹配正文 - 密级匹配密级 - 所属部门匹配所属部门 - 仅显示满足全部条件的子案/附件 - 无满足项 → 完全空白 - 标题中的简单关键词 → 高亮 - 正文关键词命中 → 行下方延展 `contentSnippet` 片段并高亮(沿用现有样式) ### 4. UI 结构 ``` ┌──────────────────────────────────────────────────────────┐ │ 🔍 [ 全局关键词(匹配标题) ] [🔍] [⚙高级检索] │ │ ┌─高级检索(折叠态默认隐藏)────────────────────────────┐ │ │ │ 正文: [ ] 密级: ☐公开 ☐内部 ☐秘密 ☐机密 ☐绝密 │ │ │ │ 部门: [ ] │ │ │ └─────────────────────────────────────────────────────┘ │ ├──────────────────────────────────────────────────────────┤ │ 主案列表(含命中徽章) │ │ ┌────┬──────────────┬──────┬────┬────┬───────┐ │ │ │序号│ 名称 │ 类型 │密级│部门│更新 │ │ │ │ 1 │ 2026**作战** │ 秘密 │秘密│作战│07-12 │ │ │ │ 2 │ 训练方案 📄📎│ 内部 │内部│训练│06-30 │ │ │ └────┴──────────────┴──────┴────┴────┴───────┘ │ ├──────────────────────────────────────────────────────────┤ │ [子案(N)] [附件(N)] │ │ ┌────┬──────────────┬──────┬─────────────┐ │ │ │序号│ 名称 │ 密级 │ 部门 │ │ │ │ 1 │ 南路**作战** │ 秘密 │ 作战部 │ │ │ │ │ └ 📝 "...**作战**分队由..." │ ← 正文片段 │ │ └────┴──────────────┴──────┴─────────────┘ │ └──────────────────────────────────────────────────────────┘ ``` --- ## 二、实施计划 ### P1. 后端数据模型与迁移(约 3h) | 步骤 | 文件 | 说明 | |------|------|------| | P1.1 | `backend/dms/models/document.py` | `Document` 模型新增 `organization_id` 外键 + `organization_name` 冗余字段(参照现有 `category_id`/`category_name` 模式) | | P1.2 | `backend/migrations/versions/0006_document_organization.py` | down_revision=`0005_category_flat_mode`;添加列;按 `creator_id → users.organization_id` 回填历史;回填 `organization_name` | | P1.3 | `backend/dms/services/document_query_service.py` `document_summary` | 响应 DTO 新增 `organizationId`/`organizationName`;`document_mutation_service.py` 创建/更新分支接收 `organizationId`(缺省取 `AuthContext.organization_id`) | | P1.4 | `backend/dms/services/organization_service.py` `update_organization` | 在第 381-395 行附近追加 Document 的 `organization_name` 批量刷新(与 User 刷新一致) | ### P2. 后端接口扩展(约 4h) | 步骤 | 文件 | 说明 | |------|------|------| | P2.1 | `backend/dms/api/v1/documents.py` | `list` 接口新增查询参数:`content_keyword`、`security_levels`(多选)、`organization_keyword` | | P2.2 | 同上 | 新增 `list_aggregated` 端点或扩展现有 `list`:返回主案时附带 `subHitCount`、`attachmentHitCount`、`titleHit`(主案本身是否命中) | | P2.3 | `backend/dms/api/v1/documents.py` | `sub-plans/{main_id}` 与 `attachments` 接口同步扩展四条件参数 | | P2.4 | `backend/dms/api/v1/documents.py` | 文档创建:`organization_id` 缺省取 `current_user.organization_id`;更新:管理员可改 | ### P3. 后端服务层(约 3h) | 步骤 | 文件 | 说明 | |------|------|------| | P3.1 | `backend/dms/services/document_service.py` | `list_documents` 增加高级检索过滤分支(与现有 `keyword` 标题匹配并存) | | P3.2 | 同上 | 新增 `list_main_documents_aggregated`:处理"主案或其下子案/附件命中"的并集逻辑,返回主案列表 + 命中计数 | | P3.3 | 同上 | 命中计数来源:按主案 ID 批量查询可见子案/附件的标题命中数 | ### F1. 前端类型与 API 客户端(约 2h) | 步骤 | 文件 | 说明 | |------|------|------| | F1.1 | `frontend/src/features/documents/documentTypes.ts` | `DocumentSummary` 新增 `organizationId`、`organizationName`、`subHitCount?`、`attachmentHitCount?`、`titleHit?` | | F1.2 | `frontend/src/features/documents/documentApi.ts` | 查询参数类型扩展;新增 `searchAggregated` 方法(或扩展现有 `list`) | | F1.3 | `frontend/src/features/attachments/attachmentApi.ts` | 同步扩展查询参数 | ### F2. 前端检索栏(约 3h) | 步骤 | 文件 | 说明 | |------|------|------| | F2.1 | `frontend/src/features/documents/GlobalSearchBar.tsx`(新) | 顶部全局检索栏:含简单关键词输入 + 高级检索折叠行 | | F2.2 | `frontend/src/app/App.tsx` | 删除 `RealMainPage` / `RealAdminPage` 中三处 `DocumentSearchToolbar` 调用 | | F2.3 | 同 F2.1 | 高级检索折叠行:正文关键词 + 密级多选 + 所属部门;密级选项来自 `shared/api/enums.ts` 的 `SecurityLevel` | ### F3. 前端列表渲染(约 3h) | 步骤 | 文件 | 说明 | |------|------|------| | F3.1 | `frontend/src/app/App.tsx` `F4DocumentTable` | 名称列高亮渲染(接收 `highlightKeyword`);命中徽章列(`subHitCount`/`attachmentHitCount`);新增"所属部门"列 | | F3.2 | 同上 | 选中主案后,子案/附件 Tab 调用扩展接口传入四条件 | | F3.3 | 同上 | 行下方延展沿用现有 `contentSnippet` 样式 | ### F4. 前端状态管理(约 2h) | 步骤 | 文件 | 说明 | |------|------|------| | F4.1 | `frontend/src/features/documents/documentState.ts` | 新增全局检索状态:`simpleKeyword`、`advancedFilters`(contentKeyword、securityLevels、organizationKeyword) | | F4.2 | `frontend/src/app/App.tsx` | 主案查询使用全局状态;选中主案后子案/附件查询复用全局状态 | | F4.3 | 同上 | 关键词防抖沿用 300ms | ### 测试(约 4h) | 步骤 | 文件 | 说明 | |------|------|------| | T1.1 | `backend/tests/dms/test_global_search.py` | 后端:覆盖 6 种命中组合(主案命中、子案命中、附件命中、混合、空、高级检索过滤) | | T1.2 | `backend/tests/dms/test_document_organization.py` | 后端:默认值取创建者组织、可修改、不联动调岗 | | T2.1 | `frontend/src/features/documents/__tests__/GlobalSearchBar.test.tsx` | 前端:折叠/展开、条件生效、密级多选 | | T2.2 | `frontend/src/app/__tests__/F4DocumentTable.test.tsx` | 前端:徽章显示、高亮渲染、所属部门列 | --- ## 三、风险与缓解 | 编号 | 风险 | 缓解 | |------|------|------| | R1 | 聚合查询在大数据量下慢 | 在 `title`、`organization_name`、`security_level` 上加索引;必要时切全文索引 | | R2 | 历史数据密级为空 | 迁移中给默认值(最低密级) | | R3 | 组织改名时冗余字段不同步 | 沿用 `move organization` 级联策略:移动组织时同步刷新所有文档的 `organization_name` | | R4 | 简单关键词匹配标题但子案不可见 | 聚合时按 ACL 校验子案/附件可见性,不可见的命中不计入计数 | | R5 | 索引膨胀 | 仅在 `title`、`organization_name` 上加 BTREE 索引;`contentText` 不加索引(按需切全文) | --- ## 四、工作量估算 | 阶段 | 工作量 | |------|-------| | P1 后端模型+迁移 | ~3h | | P2 后端接口扩展 | ~4h | | P3 后端服务层 | ~3h | | F1 前端类型与API | ~2h | | F2 前端检索栏 | ~3h | | F3 前端列表渲染 | ~3h | | F4 前端状态管理 | ~2h | | 测试 | ~4h | | 联调与回归 | ~2h | | **合计** | **~26h** | --- ## 五、推进顺序 按用户要求:**P1 → F1 → F2 → F3 → F4 → 测试** - P1 完成后即可推进 F1(前端类型可基于后端 DTO 设计提前确定) - P2/P3 在 F 阶段并行推进(前端先用 mock 数据,后端接口 ready 后切换) - 测试阶段:后端测试在 P3 完成后立即写;前端测试在 F4 完成后立即写 - 联调与回归:所有阶段完成后做一次完整联调