文件
hyb-oa/.trellis/tasks/08-05-business-detail-routing/design.md
T

9.0 KiB

设计说明

1. 架构边界与目标路由

本任务只迁移采购、资产已有详情/编辑能力,并为“提交前没有实体 ID”的资产创建表单提供独立路由。列表数据、详情 API、mutation payload、权限数据源、审计和状态机保持原合同,不新增后端 endpoint 或数据库字段。

模块 列表 创建 详情
采购 /purchase-requests 不新增;列表先创建草稿 /purchase-requests/:purchaseRequestId
资产 /assets /assets/new /assets/:assetId

路由注册与页面元数据必须把 /assets/new 作为静态路径处理,不能落入 :assetId。PAGE_TITLES 增加采购详情、资产新建、资产详情层级,导航仍按父模块高亮。

2. 页面与数据职责拆分

  • PurchaseRequestsPage 收敛为采购列表页:持有 URL 筛选、分页、列表 query、创建草稿入口和行级详情入口。
  • PurchaseRequestDetailPage 承接现有采购 detail query、表单 hydration、附件、采购单归档、提交/撤回、审计/状态动作和 dirty 状态;不得为渲染详情请求完整采购列表。
  • AssetRegistryPage 收敛为资产列表页:持有 URL 筛选、资产列表 query、资产分类管理、移动扫码入口、创建入口和行级详情入口。
  • AssetDetailPage 与 AssetCreatePage 使用同一资产编辑工作区/section 集合:详情模式加载实体并按现有权限编辑与流转;创建模式使用初始表单并只执行 POST,避免复制两份长表单。
  • 列表页不再加载详情专用 options;详情/创建页只加载自身表单所需的分类、公司、成本中心、候选采购等数据。React Query 可复用缓存,但职责不能靠隐藏 JSX 维持。
  • 优先提取页面内类型、纯函数和有明确领域含义的 section;不建立跨采购/资产的“万能详情页”。

3. 列表筛选 URL 合同

筛选参数由 URL 直接派生,避免“URL 一份、本地 state 一份”的双向同步 effect。筛选输入使用 replace 更新当前列表 history entry;采购翻页同样保留当前筛选。资产文本搜索维持现有即时查询体验,但用 deferred/debounce 值触发请求,避免每次键入造成不必要的渲染/请求抖动。

模块 查询参数 默认与约束
采购 status、scope、supplierId、legalEntityId、costCenterId、page 无 status 时为 draft;status=all 表示全部;无 scope 时为 own;scope=all 仅 canViewAll 生效;page 只接受正整数
资产 q、status、assetType、sensitivity、categoryId、legalEntityId、costCenterId、assigneeId、scope 无 scope 时保留现有按权限决定的默认范围;显式 own/all 仅在有全量权限时可切换,否则强制 own

联动筛选一次性更新:公司变化且原成本中心不再属于该公司时,同一次 replace 删除 costCenterId;采购非页码筛选变化同时把 page 归一到 1。未知枚举、无效页码或越权范围只在读取层回落,不进入 API 查询条件。

4. 导航、返回与创建完成

  • 列表进入详情使用 history push,并把 pathname + search 作为 location.state.from;浏览器后退自然回到原列表 entry,显式返回使用经校验的来源。
  • 扩展 pcDetailNavigation 为显式 allowlist 合同,不把任意字符串当 return URL。采购详情允许采购列表来源以及受控的项目详情来源;资产详情/创建只允许资产列表来源。绝对 URL、协议相对 URL、hash、当前页和其他模块来源全部拒绝。
  • 无安全来源时,采购详情回到 /purchase-requests,资产详情/创建回到 /assets。从全局搜索或通知进入时,浏览器后退仍按 history 返回原页面,显式返回使用安全列表兜底。
  • 采购创建保留现有“先 POST 空草稿”合同;成功后 push 到采购详情并携带当前列表来源。
  • 资产列表的“新增资产”push 到 /assets/new 并携带列表来源。创建成功后使用 replace 进入 /assets/:assetId,继续携带原列表来源,避免返回到已提交的创建表单。
  • 项目详情中的采购链接携带项目详情作为安全来源;详情返回文案按来源显示“返回项目详情”或“返回采购列表”。

5. 路径构造与现有入口

  • 新增单一的采购/资产详情路径构造函数,对实体 ID 使用路径段编码;列表、创建成功、项目关联、通知和搜索解析都消费该函数,避免分散拼接。
  • GlobalSearchDialog 对 purchase_request 与 asset 使用 entityType + entityId 构造 canonical 路径,不继续信任旧服务端 href 的 ?id= 或列表级值;其他实体仍沿用原合同。本任务因此无需修改全局搜索 API shape。
  • PC notificationLinks 将 purchase_request、asset/asset_card 直接解析为新详情路径,停止生成未消费的 highlight;移动通知解析保持现有移动路由边界。
  • ProjectDetailPage 的采购关联改为新详情路径;没有现有入口的模块不为满足“路由统一”额外新增按钮。
  • client/src 中旧 ?id= 只允许出现在明确命名的 legacy redirect/parser 与兼容测试中。服务端历史集成审计字段或未被前端消费的 routePath 不在本任务内,不能借机改 API 或集成逻辑。

6. 旧链接兼容

  • /purchase-requests?id=<id> 与 /assets?id=<id> 在对应列表入口、列表 query 启动前解析;有非空 ID 时 replace 到 canonical 详情路径,避免先闪现列表或同时请求列表/详情。
  • redirect 从查询参数中删除 id,将剩余且仍属于对应列表的参数归一化后写入安全 state.from;canonical 详情 URL 不携带列表筛选噪音。
  • 空 ID、只有空白字符或缺少 ID 时不重定向,列表按其余有效筛选正常展示。未知实体 ID 进入详情后的统一错误态。
  • redirect 使用 replace,历史栈不保留旧 URL;仓库内新入口全部使用 canonical 路径,旧格式仅作为外部收藏、旧 witness 和历史链接兼容层。

7. Dirty guard、错误态与权限

  • 采购详情、资产详情和资产创建页面继续注册 useUnsavedChangesGuard;显式返回、浏览器后退、跨模块链接和直接路由离开均由现有 provider 阻断。
  • 用户取消离开时不执行 navigate,保留当前 URL、实体和全部输入;确认后才执行原导航意图。资产创建成功后的 replace 只在 mutation 成功后发生。
  • 403/404 统一映射为普通中文安全错误态:“无法查看该记录,记录可能不存在或你无权访问”;不渲染已取得的局部摘要,不区分可用于资源枚举的原因。
  • 采购列表与详情沿用原 RequirePermission anyOf;资产列表与详情沿用原资产 anyOf。/assets/new 单独要求 asset.manage,页面内编辑、流转、敏感字段和附件权限继续使用现有细粒度判断。
  • API endpoint、请求 payload、审计、高风险确认、附件下载原因和状态矩阵均不改变。

8. 移动兼容

  • 现有 /mobile/purchase-requests/:id 不变;runtimeContext 继续把 PC 采购详情映射到对应移动详情。
  • 本任务不新增移动资产详情或创建能力。移动工作台偏好环境访问 /assets、/assets/new 或 /assets/:assetId 时统一回落到既有 /mobile/assets/scan,保持旧 /assets?id= 在移动端的能力边界。
  • 新 PC 详情路由不得改变移动底部导航、企业微信入口或资产扫码/盘点流程。

9. 测试策略

  • 为两个模块建立同构路由合同:列表不发详情 query、详情不发完整列表 query、列表进入、直接深链、刷新、返回恢复、旧链 replace、无效/无权限、dirty 取消/确认。
  • 单独覆盖资产 /assets/new:静态路由优先、asset.manage guard、直接访问、取消返回、创建成功 replace、失败保留输入、成功后后退不回创建页。
  • 覆盖 URL 筛选事实源:直接打开筛选 URL、筛选 replace、采购 page 重置/恢复、范围权限收紧、公司/成本中心联动归一。
  • 覆盖项目关联、全局搜索、PC 通知的 canonical URL;全局搜索 purchase/asset 不受服务端旧 href 影响。
  • 更新 procurement-flow.spec.ts、assets-flow.spec.ts、r103-cross-page-ui-consistency.spec.ts、pc-detail-mobile-visual-wave3.spec.ts、global-search.spec.ts 及通知/移动路由用例,必要时增加专门的 business-detail-routing.spec.ts。
  • 测试使用完整 URL 与页面 marker 断言,不用父列表路径前缀误认动态详情;403 与 404 都断言同一安全文案。

10. 回滚

  • 新路由、页面拆分、筛选 URL、入口消费者和 legacy redirect 作为同一任务提交;回滚时整体恢复 ?id= 页面形态,避免消费者指向不存在的页面。
  • 无 schema、API 或数据迁移。应用回滚不处理数据库;若需部分回滚,必须至少同时回滚 route registry、lazy exports、canonical path builder 和所有前端消费者。