9.0 KiB
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 统一映射为普通中文安全错误态:“无法查看该记录,记录可能不存在或你无权访问”;不渲染已取得的局部摘要,不区分可用于资源枚举的原因。
- 采购列表与详情沿用原
RequirePermissionanyOf;资产列表与详情沿用原资产 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.manageguard、直接访问、取消返回、创建成功 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 和所有前端消费者。