feat(management-navigation-single-company-ux): 收敛管理导航与单公司配置体验

这个提交包含在:
2026-08-03 12:43:00 +08:00
父节点 f4ddc18f12
当前提交 4b0184c64b
共修改 36 个文件,包含 1249 行新增和 178 行删除
@@ -2107,3 +2107,82 @@ const actions = collectVisibleActions();
expect(actions.map(({ key, label }) => ({ key, label }))).toEqual(EXPECTED_ACTIONS);
for (const action of actions) expect(action.height).toBeGreaterThanOrEqual(44);
```
## Scenario: Management Route Metadata And Permission Disclosure
### 1. Scope / Trigger
- Trigger: adding or changing a PC management landing item, management subpage,
nested management detail, breadcrumb, sidebar ownership or recent-visit title.
### 2. Signatures
- Registry: `client/src/features/navigation/managementRoutes.ts`.
- Resolver inputs: `pathname: string` plus the current `permissions: string[]`.
- Router permissions: `getManagementRoutePermissions(key)`.
- Recent visits: `writeRecentVisit(path, title, permissions)` and
`readRecentVisits(permissions)`.
### 3. Contracts
- The management landing cards, root-navigation permission union, sidebar
active state, Topbar title/breadcrumb and recent-visit filtering derive from
the same registry entry.
- React Router keeps explicit component declarations and consumes the same
registry permissions. Server authorization remains the hard access boundary;
navigation metadata only enforces least disclosure.
- Match the most specific route deliberately. A nested employee profile must
resolve after its exact siblings and must not inherit a broader prefix by
accident.
- A user without the registered permission receives no restricted title,
breadcrumb or recent-visit link. Revoked permissions also clean stale local
recent-visit records.
- The sidebar keeps one `管理` root entry. Do not spread low-frequency child
pages back into the primary navigation.
### 4. Validation & Error Matrix
- Registered route + allowed permission -> natural Chinese title, parent chain
and management sidebar active state.
- Registered route + denied permission -> no restricted metadata; Router and
server guards still reject access.
- Unknown registry key passed to the Router helper -> throw during development
instead of silently using an empty permission list.
- Newly declared application route missing from the route manifest -> local
candidate route gate fails.
### 5. Good/Base/Bad Cases
- Good: `/people/:employeeId` uses the registry for both
`管理 / 人员 / 员工档案` and its `system.users` Router guard.
- Base: `/system/settings` displays only `管理`, with no self-referencing
breadcrumb.
- Bad: add a path to `SystemSettingsPage`, `NavLinks`, `PAGE_TITLES` and Router
as four hand-maintained facts.
### 6. Tests Required
- Registry contract covers unique keys, expected path inventory, parents,
permissions, landing metadata and root permission derivation.
- Permission-negative coverage asserts no title/breadcrumb disclosure and
filters a previously stored recent visit after permission revocation.
- Focused Playwright covers direct management deep links, sidebar active state,
nested breadcrumbs and the management-root return link.
- Run `cd client && npm run typecheck && npm run build` and
`bash scripts/qa/run-local-candidate.sh list`.
### 7. Wrong vs Correct
#### Wrong
```tsx
const MANAGEMENT_ACTIVE_PATHS = ["/people", "/system/users"];
<RequirePermission anyOf={["system.users"]}>...</RequirePermission>
```
#### Correct
```tsx
isManagementPath(pathname, permissions);
<RequirePermission anyOf={getManagementRoutePermissions("people")}>...</RequirePermission>
```