全局搜索与高级检索方案
状态:已确认,开始实施
创建日期: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 完成后立即写
- 联调与回归:所有阶段完成后做一次完整联调