From 152241e6664615522a46d5503cc5af68e8e574bf Mon Sep 17 00:00:00 2001 From: gitadmin Date: Thu, 14 May 2026 17:01:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=89=8D=E7=AB=AF=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E5=92=8C=E5=85=A8=E9=83=A8=E5=B7=A5=E5=8D=95=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=94=B9=E4=B8=BA=E5=9F=BA=E4=BA=8Epermissions?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E6=8E=A7=E5=88=B6=E6=98=BE=E9=9A=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sidebar: isAdmin boolean → permissions string[],所有导航项按 perm 属性过滤 - 侧边栏 navItems 新增 perm 字段,canSee() 检查 tickets:read/tickets:create/tickets:import/reports:read - "全部工单"仅 permissions.includes('*') 可见,"系统设置"区由 hasAnyAdminPerm() 控制 - 设置子项按各自 perm 过滤(users:read/roles:read/api-keys:read) - /tickets/all 页面权限检查同步改为 permissions.includes('*') --- src/app/(app)/tickets/all/page.tsx | 2 +- src/components/layout/Sidebar.tsx | 41 +++++++++++++++++++----------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/app/(app)/tickets/all/page.tsx b/src/app/(app)/tickets/all/page.tsx index aace297..4015c3e 100644 --- a/src/app/(app)/tickets/all/page.tsx +++ b/src/app/(app)/tickets/all/page.tsx @@ -12,7 +12,7 @@ export default function AllTicketsPage() { fetch('/api/auth/me') .then(r => r.json()) .then(u => { - if (u.user?.role !== 'admin') { + if (!u.user?.permissions?.includes('*')) { router.replace('/tickets/pending') } else { setReady(true) diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 7e70e45..647e880 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -5,42 +5,53 @@ import { usePathname } from 'next/navigation' import { LayoutDashboard, FileText, Settings, Users, Shield, Key, Clock, CheckCircle, PlusSquare, Upload, List } from 'lucide-react' const navItems = [ - { href: '/dashboard', label: '仪表盘', icon: LayoutDashboard }, - { href: '/tickets/pending', label: '待办工单', icon: Clock }, - { href: '/tickets/completed', label: '已办工单', icon: CheckCircle }, - { href: '/tickets/create', label: '手动建单', icon: PlusSquare }, - { href: '/tickets/import', label: '导入工单', icon: Upload }, - { href: '/reports', label: '报告管理', icon: FileText }, + { href: '/dashboard', label: '仪表盘', icon: LayoutDashboard, perm: null }, + { href: '/tickets/pending', label: '待办工单', icon: Clock, perm: 'tickets:read' }, + { href: '/tickets/completed', label: '已办工单', icon: CheckCircle, perm: 'tickets:read' }, + { href: '/tickets/create', label: '手动建单', icon: PlusSquare, perm: 'tickets:create' }, + { href: '/tickets/import', label: '导入工单', icon: Upload, perm: 'tickets:import' }, + { href: '/reports', label: '报告管理', icon: FileText, perm: 'reports:read' }, ] const settingsItems = [ - { href: '/settings/users', label: '用户管理', icon: Users }, - { href: '/settings/roles', label: '角色权限', icon: Shield }, - { href: '/settings/api-keys', label: 'API Key', icon: Key }, + { href: '/settings/users', label: '用户管理', icon: Users, perm: 'users:read' }, + { href: '/settings/roles', label: '角色权限', icon: Shield, perm: 'roles:read' }, + { href: '/settings/api-keys', label: 'API Key', icon: Key, perm: 'api-keys:read' }, ] +function hasAnyAdminPerm(permissions: string[]): boolean { + return permissions.includes('*') || permissions.some(p => p.startsWith('users:') || p.startsWith('roles:') || p.startsWith('api-keys:')) +} + export default function Sidebar() { const pathname = usePathname() - const [isAdmin, setIsAdmin] = useState(false) + const [permissions, setPermissions] = useState([]) useEffect(() => { fetch('/api/auth/me') .then(r => r.json()) - .then(u => { if (u.user?.role === 'admin') setIsAdmin(true) }) + .then(u => { if (u.user?.permissions) setPermissions(u.user.permissions) }) .catch(() => {}) }, []) + + const canSee = (perm: string | null) => { + if (perm === null) return true + if (permissions.includes('*')) return true + return permissions.includes(perm) + } + return (