# 切片 1: 按钮级权限控制 实施计划 > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 实现前端按钮级权限控制,让无权限用户看不到操作按钮(hidden 模式)。 **Architecture:** JWT claims 已包含 `permissions: Vec`(后端登录时写入),前端复用 `client.ts` 的 `decodeJwtPayload` 提取权限码列表,存入 Zustand auth store。新增 `usePermission` hook + `AuthButton` / `AuthGuard` 声明式组件,包裹健康模块 15 个页面的操作按钮。 **Tech Stack:** React 19 + TypeScript + Zustand 5 + Ant Design 6 **设计规格:** `docs/superpowers/specs/2026-04-25-feature-completion-design.md` §2 --- ## Chunk 1: 权限基础设施 ### Task 1: 从 JWT 提取 permissions 并存入 auth store **Files:** - Modify: `apps/web/src/stores/auth.ts` **背景:** JWT payload 已包含 `permissions` 字段(string 数组)。`client.ts` 已有 `decodeJwtPayload` 函数。auth store 登录时已存 `access_token` 到 localStorage,可从中解码权限。 - [ ] **Step 1: 在 auth store 中添加 permissions 状态和提取逻辑** 在 `apps/web/src/stores/auth.ts` 中: 1. 新增辅助函数(文件顶部,import 之后): ```typescript function extractPermissions(): string[] { const token = localStorage.getItem('access_token'); if (!token) return []; try { const parts = token.split('.'); if (parts.length !== 3) return []; const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'))); return Array.isArray(payload.permissions) ? payload.permissions : []; } catch { return []; } } ``` 2. 修改 `restoreInitialState` 返回值,增加 `permissions`: ```typescript function restoreInitialState(): { user: UserInfo | null; isAuthenticated: boolean; permissions: string[] } { const token = localStorage.getItem('access_token'); const userStr = localStorage.getItem('user'); if (token && userStr) { try { const user = JSON.parse(userStr) as UserInfo; return { user, isAuthenticated: true, permissions: extractPermissions() }; } catch { localStorage.removeItem('user'); } } return { user: null, isAuthenticated: false, permissions: [] }; } ``` 3. 修改 `AuthState` 接口,增加 `permissions`: ```typescript interface AuthState { user: UserInfo | null; isAuthenticated: boolean; loading: boolean; permissions: string[]; login: (username: string, password: string) => Promise; logout: () => Promise; loadFromStorage: () => void; } ``` 4. 修改 store 创建,初始化 `permissions`,在 login/logout 中同步更新: ```typescript export const useAuthStore = create((set) => ({ user: initial.user, isAuthenticated: initial.isAuthenticated, loading: false, permissions: initial.permissions, login: async (username, password) => { set({ loading: true }); try { const resp = await apiLogin({ username, password }); localStorage.setItem('access_token', resp.access_token); localStorage.setItem('refresh_token', resp.refresh_token); localStorage.setItem('user', JSON.stringify(resp.user)); set({ user: resp.user, isAuthenticated: true, loading: false, permissions: extractPermissions() }); } catch (error) { set({ loading: false }); throw error; } }, logout: async () => { try { await apiLogout(); } catch { // Ignore logout API errors } localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); localStorage.removeItem('user'); set({ user: null, isAuthenticated: false, permissions: [] }); }, loadFromStorage: () => { const state = restoreInitialState(); set({ user: state.user, isAuthenticated: state.isAuthenticated, permissions: state.permissions }); }, })); ``` - [ ] **Step 2: 验证编译通过** Run: `cd apps/web && npx tsc --noEmit` Expected: 无类型错误 - [ ] **Step 3: 提交** ```bash git add apps/web/src/stores/auth.ts git commit -m "feat(web): auth store 添加 permissions 状态,从 JWT 解码提取" ``` --- ### Task 2: 创建 usePermission hook **Files:** - Create: `apps/web/src/hooks/usePermission.ts` - [ ] **Step 1: 创建 usePermission hook** ```typescript import { useAuthStore } from '../stores/auth'; export function usePermission(code: string): { hasPermission: boolean } { const permissions = useAuthStore((s) => s.permissions); return { hasPermission: permissions.includes(code) }; } ``` - [ ] **Step 2: 验证编译通过** Run: `cd apps/web && npx tsc --noEmit` Expected: 无类型错误 - [ ] **Step 3: 提交** ```bash git add apps/web/src/hooks/usePermission.ts git commit -m "feat(web): 添加 usePermission hook" ``` --- ### Task 3: 创建 AuthButton + AuthGuard 组件 **Files:** - Create: `apps/web/src/components/AuthButton.tsx` - Create: `apps/web/src/components/AuthGuard.tsx` - [ ] **Step 1: 创建 AuthButton 组件** `apps/web/src/components/AuthButton.tsx`: ```typescript import type { ReactNode } from 'react'; import { usePermission } from '../hooks/usePermission'; interface AuthButtonProps { code: string; children: ReactNode; } export function AuthButton({ code, children }: AuthButtonProps) { const { hasPermission } = usePermission(code); if (!hasPermission) return null; return <>{children}; } ``` - [ ] **Step 2: 创建 AuthGuard 组件** `apps/web/src/components/AuthGuard.tsx`: ```typescript import type { ReactNode } from 'react'; import { usePermission } from '../hooks/usePermission'; interface AuthGuardProps { code: string; children: ReactNode; } export function AuthGuard({ code, children }: AuthGuardProps) { const { hasPermission } = usePermission(code); if (!hasPermission) return null; return <>{children}; } ``` - [ ] **Step 3: 验证编译通过** Run: `cd apps/web && npx tsc --noEmit` Expected: 无类型错误 - [ ] **Step 4: 提交** ```bash git add apps/web/src/components/AuthButton.tsx apps/web/src/components/AuthGuard.tsx git commit -m "feat(web): 添加 AuthButton/AuthGuard 声明式权限组件" ``` --- ## Chunk 2: 健康模块页面按钮权限改造 ### Task 4: PatientList 按钮权限 **Files:** - Modify: `apps/web/src/pages/health/PatientList.tsx` **改造目标:** - 第 304 行 `新建患者` 按钮 → `` - 第 242-267 行 操作列的编辑/删除按钮 → `` - [ ] **Step 1: 添加 import** 在 PatientList.tsx 顶部 import 区域添加: ```typescript import { AuthButton } from '../../components/AuthButton'; ``` - [ ] **Step 2: 包裹新建患者按钮** 将第 304 行: ```tsx ``` 改为: ```tsx ``` - [ ] **Step 3: 包裹操作列按钮** 将 columns 操作列的 render(第 241-270 行): ```tsx render: (_: unknown, record: PatientListItem) => (