From 14d81045e6dff9dc712cb8555d3a9b9d51ea139c Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 2 Jul 2026 11:59:36 +0800 Subject: [PATCH] feat(frontend): add usePermission hook, PermissionButton, PermissionRoute, and 403 handling --- frontend/src/api/index.ts | 5 ++++ frontend/src/components/PermissionButton.tsx | 17 ++++++++++++++ frontend/src/components/PermissionRoute.tsx | 24 ++++++++++++++++++++ frontend/src/hooks/usePermission.ts | 21 +++++++++++++++++ frontend/src/pages/Login/index.tsx | 1 + 5 files changed, 68 insertions(+) create mode 100644 frontend/src/components/PermissionButton.tsx create mode 100644 frontend/src/components/PermissionRoute.tsx create mode 100644 frontend/src/hooks/usePermission.ts diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 0c27f23..4a64a37 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -19,8 +19,13 @@ api.interceptors.response.use( if (err.response?.status === 401) { localStorage.removeItem('token'); localStorage.removeItem('user'); + localStorage.removeItem('permissions'); window.location.href = '/login'; } + if (err.response?.status === 403) { + const msg = err.response?.data?.message || '权限不足'; + console.warn('[403]', msg); + } return Promise.reject(err.response?.data || err); }, ); diff --git a/frontend/src/components/PermissionButton.tsx b/frontend/src/components/PermissionButton.tsx new file mode 100644 index 0000000..49ad1f9 --- /dev/null +++ b/frontend/src/components/PermissionButton.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Button } from 'antd'; +import type { ButtonProps } from 'antd'; +import { usePermission } from '../hooks/usePermission'; + +interface PermissionButtonProps extends ButtonProps { + permission: string; + children: React.ReactNode; +} + +const PermissionButton: React.FC = ({ permission, children, ...btnProps }) => { + const { hasPermission } = usePermission(); + if (!hasPermission(permission)) return null; + return ; +}; + +export default PermissionButton; diff --git a/frontend/src/components/PermissionRoute.tsx b/frontend/src/components/PermissionRoute.tsx new file mode 100644 index 0000000..848c52c --- /dev/null +++ b/frontend/src/components/PermissionRoute.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Result } from 'antd'; +import { usePermission } from '../hooks/usePermission'; + +interface PermissionRouteProps { + permission: string; + children: React.ReactNode; +} + +const PermissionRoute: React.FC = ({ permission, children }) => { + const { hasPermission } = usePermission(); + if (!hasPermission(permission)) { + return ( + + ); + } + return <>{children}; +}; + +export default PermissionRoute; diff --git a/frontend/src/hooks/usePermission.ts b/frontend/src/hooks/usePermission.ts new file mode 100644 index 0000000..214a00f --- /dev/null +++ b/frontend/src/hooks/usePermission.ts @@ -0,0 +1,21 @@ +import { useMemo } from 'react'; + +export function usePermission() { + const permissions: string[] = useMemo(() => { + try { + return JSON.parse(localStorage.getItem('permissions') || '[]'); + } catch { + return []; + } + }, []); + + const hasPermission = (code: string): boolean => permissions.includes(code); + + const hasAnyPermission = (...codes: string[]): boolean => + codes.some(c => permissions.includes(c)); + + const hasAllPermissions = (...codes: string[]): boolean => + codes.every(c => permissions.includes(c)); + + return { permissions, hasPermission, hasAnyPermission, hasAllPermissions }; +} diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx index f14decc..6bd4044 100644 --- a/frontend/src/pages/Login/index.tsx +++ b/frontend/src/pages/Login/index.tsx @@ -16,6 +16,7 @@ const LoginPage: React.FC = () => { const res: any = await api.post('/auth/login', values); localStorage.setItem('token', res.access_token); localStorage.setItem('user', JSON.stringify(res.user)); + localStorage.setItem('permissions', JSON.stringify(res.user.permissions || [])); message.success('登录成功'); navigate('/dashboard'); } catch (err: any) {