feat(management-navigation-single-company-ux): 收敛管理导航与单公司配置体验
这个提交包含在:
@@ -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>
|
||||
```
|
||||
|
||||
在新工单中引用
屏蔽一个用户