# 检索过渡界面与关系面板按需显示方案 > 创建日期:2026-08-21 > 状态:已完成(tsc 通过;vitest 343 全绿,其中新增 15 例) > 范围:纯前端(无后端改动) ## 1. 需求 1. **关系面板按需显示**:未点击(选中)主案时,主页面下方"配套方案 / 支撑成果"等区域整体隐藏,不再显示"请选择主案"占位。 2. **检索重构**:检索激活时,中央列表切换为**过渡平铺界面**: - 主案、配套方案(子案)、支撑成果等全部命中文档平铺到一个列表; - 列与"主体方案"列表一致(序号 / 名称 / 密级 / 提供单位 / 操作); - 名称之后用方框标注方案类型(主体方案 / 配套方案 / 支撑成果 / 方案训考资料 / 典型案例); - 保留单击预览(右侧栏)、双击查看(弹窗)能力; - 操作列新增**定位**按钮: - 主案:跳转到其分类,选中该主案,展开下方关系面板; - 子案 / 附件:先定位到其主案(跳主案分类 + 选中主案 + 展开面板); - 附件挂多个主案时,弹框让用户选择目标主案。 3. **分页**:命中过多时平铺列表分页展示(每页 20 条)。 4. **后台管理页**(方案数据管理后台 → 主体方案模式)做同样改动。 ## 2. 现状分析 | 项 | 现状 | 位置 | |---|---|---| | 关系面板 | `!hideRelations` 即渲染,未选中时显示占位 | App.tsx RealMainPage / RealAdminPage | | 检索 | 主案列表 `documentType=MAIN + aggregateHits=true`,子案/附件命中仅以徽章(子案 N / 附件 N)聚合到主案行 | App.tsx:2712-2723、F4DocumentTable | | 后端 documents 接口 | `documentType` 支持 `MAIN,SUB_PLAN` 逗号组合 | document_query_service.py:424 | | 后端 attachments 接口 | 支持 keyword / contentKeyword / securityLevels / organizationKeyword 全部检索条件 | attachment_query_service.py | | 附件反查主案 | `attachmentApi.mainPlans(id)` → `MainPlanBrief[]`(id/documentName/categoryName/securityLevel,**无 categoryId**) | attachmentApi.ts:58 | | pageSize 上限 | 后端 100/页 | common/pagination.py:14 | **结论**:两个既有接口即可覆盖平铺数据,无需后端改动;附件定位需经 `documentApi.detail(mainPlanId)` 补全 categoryId。 ## 3. 设计 ### 3.1 数据层:`features/documents/globalSearchResults.ts`(新建) ```typescript useGlobalSearchResults(active: boolean, keyword: string | undefined, advanced: AdvancedQuery) ``` - `active` 为 false 时直接返回空态(不发请求); - 并行两路,各自**循环拉全量**(pageSize=100,翻页直到拉完,单源上限 10 页 / 1000 条防失控,超限截断): - `documentApi.list({ documentType: "MAIN,SUB_PLAN", keyword, ...advanced, sortBy: "updatedAt", sortDirection: "desc" })` - `attachmentApi.list({ keyword, ...advanced, sortBy: "updatedAt", sortDirection: "desc" })` - 请求版本号 + AbortController 防竞态(参照 AttachmentBindingModal 既有模式); - 纯函数(可单测): - `mergeSearchResults(plans: DocumentSummary[], attachments: DocumentSummary[]): DocumentSummary[]` —— 合并、按 updatedAt 降序稳定排序; - `paginateFlat(items: T[], page: number, pageSize: number): { items: T[]; totalPages: number; total: number }` —— 前端分页。 ### 3.2 表格扩展:F4DocumentTable 增加可选 props(DRY,不新建表格) - `showTypeBadge?: boolean`:名称单元格后渲染类型方框(`border` + 主题蓝小字号方框): - `MAIN` → "主体方案";`SUB_PLAN` → "配套方案";`ATTACHMENT` → `attachmentTypes` 字典标签(支撑成果 / 方案训考资料 / 典型案例); - `onLocate?: (item: DocumentSummary) => void`:操作列末尾追加"定位"按钮。 ### 3.3 检索过渡视图(RealMainPage / RealAdminPage 对称接入) - `searchActive = simpleKeyword !== "" || 任一高级条件非空`; - `searchActive` 时中央列表区整体替换为检索视图: - 标题栏:`全局检索结果`(命中合计、类型构成、超 1000 截断提示); - 平铺表格:`showTypeBadge` + `onLocate` + `onView`(双击)、`onSelect`(单击预览); - `F4Pager` 前端分页(每页 20); - 检索态下左侧分类树与检索栏保持可用;分页/翻页不清空检索条件; - 关系面板显示条件统一改为:`!hideRelations && selected?.documentType === MAIN`(需求 1 与检索态共用,占位分支删除)。 ### 3.4 定位逻辑(页面内 `locateItem(item)`) ``` 目标主案 resolution: MAIN → item 自身 SUB_PLAN → 结果集中找 id === item.parentDocumentId 的 MAIN;找不到 → documentApi.detail(parentDocumentId) ATTACHMENT → attachmentApi.mainPlans(item.id): 0 个 → 错误提示"该资料未挂载任何主案,无法定位" 1 个 → documentApi.detail(mainPlanId) 多个 → 弹 LocateTargetModal 选择 → detail(选定 id) 跳转动作(统一): resetGlobalSearch() // 清空检索,退出过渡界面 setCategoryId(主案.categoryId) setSelected(主案 summary) // 关系面板随 selectedMainId 展开 setHideRelations(false) setPage/setPlanPage(1) ``` - 后台页:`admin` 模式下 detail 可能受可见性限制,失败时给出 HttpError 原始 message 提示。 ### 3.5 新组件:`LocateTargetModal`(多主案选择弹框) - props:`attachment: DocumentSummary`、`candidates: MainPlanBrief[]`、`onConfirm(mainPlanId)`、`onClose`; - 样式仿 F4ReverseModal(NAVY 标题栏 + 白底列表 + 单选确认); - 列显示:主案名称 / 分类(categoryName)/ 密级。 ## 4. 实施阶段 | 阶段 | 内容 | 验证 | |---|---|---| | 1 | 关系面板按需显示(主页 + 后台页) | tsc;未选中时面板隐藏 | | 2 | globalSearchResults hook(全量拉取/合并/分页纯函数) | 纯函数单测 | | 3 | F4DocumentTable 扩展 + 检索视图 + locateItem + LocateTargetModal(两页接入) | tsc + scope 断言 | | 4 | 测试补充 + 全量回归 | vitest(基线 328+)、tsc | ## 5. 风险 - **低**:附件 summary `categoryId` 为 null —— 定位统一走主案 categoryId,已规避; - **低**:命中超 1000 条/源 —— 截断并提示,不做深度聚合; - **低**:后台页 detail 权限受限 —— 失败展示原始错误信息; - **低**:检索请求竞态 —— 版本号 + AbortController 既有模式。