diff --git a/CLAUDE.md b/CLAUDE.md index 0d1f79c..1e36da8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Seclusion 是一个基于 Next.js + NestJS 的 Monorepo 项目模板,使用 pnpm workspace + Turborepo 管理。 +### 环境要求 + +- Node.js >= 20.0.0 +- pnpm >= 9.0.0 + ## Commands ```bash @@ -39,12 +44,33 @@ cd apps/api && pnpm db:studio # 打开 Prisma Studio ### Monorepo 结构 -- **apps/web** - Next.js 16 前端 (端口 3000),使用 React 19 +- **apps/web** - Next.js 16 前端 (端口 3000),使用 React 19 + Tailwind CSS v4 - **apps/api** - NestJS 10 后端 (端口 4000,API 文档: /api/docs) - **packages/shared** - 共享类型定义、工具函数和加密模块 - **packages/eslint-config** - 共享 ESLint 9 flat config 配置 - **packages/typescript-config** - 共享 TypeScript 配置 +### 前端架构 (apps/web) + +**状态管理分类** + +| 状态类型 | 管理方式 | 示例 | +|----------|----------|------| +| 服务端状态 | TanStack Query | 用户列表、详情数据 | +| 客户端全局状态 | Zustand | 认证信息、主题设置 | +| 组件本地状态 | useState | 表单输入、弹窗开关 | +| URL 状态 | Next.js Router | 分页参数、筛选条件 | + +**数据请求分层** + +``` +Component → Hook (TanStack Query) → Service → http +``` + +- **Service 层** (`services/*.service.ts`): 封装 API 调用 +- **Hook 层** (`hooks/use*.ts`): 封装 TanStack Query,提供 queryKey 管理 +- **http** (`lib/http.ts`): Axios HTTP 客户端封装,含加密处理、Token 自动刷新 + ### 后端模块 (apps/api) NestJS 采用模块化架构: diff --git a/docs/index.md b/docs/index.md index 67f7c21..39b8266 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,11 +4,18 @@ ## 文档列表 -| 文档 | 说明 | 适用场景 | -| ---------------------------------------------------- | ----------------- | -------------------------------- | -| [design.md](./design.md) | 项目设计文档 | 了解整体架构、技术选型、模块设计 | -| [backend/soft-delete.md](./backend/soft-delete.md) | 软删除设计文档 | 了解软删除机制、扩展新模型 | -| [backend/crud-service.md](./backend/crud-service.md) | CRUD Service 模板 | 快速创建标准 CRUD 服务 | +| 文档 | 说明 | 适用场景 | +| ---------------------------------------------------- | -------------------- | -------------------------------- | +| [design.md](./design.md) | 项目设计文档 | 了解整体架构、技术选型、模块设计 | +| [backend/soft-delete.md](./backend/soft-delete.md) | 软删除设计文档 | 了解软删除机制、扩展新模型 | +| [backend/crud-service.md](./backend/crud-service.md) | CRUD Service 模板 | 快速创建标准 CRUD 服务 | +| [../plop/README.md](../plop/README.md) | CRUD 代码生成器 | 一键生成全栈 CRUD 模块 | + +## 任务文档 + +| 文档 | 说明 | 日期 | +| -------------------------------------------------------------- | ------------------ | ---------- | +| [workflow/250117-权限管理体系实施.md](./workflow/250117-权限管理体系实施.md) | 菜单及路由权限管理体系 | 2025-01-17 | ## 快速链接 diff --git a/docs/web/DESIGN.md b/docs/web/DESIGN.md index 1ec6e55..11e858c 100644 --- a/docs/web/DESIGN.md +++ b/docs/web/DESIGN.md @@ -15,7 +15,7 @@ | **表单处理** | React Hook Form | ^7.x | 高性能表单库 | | **表单验证** | Zod | ^3.x | TypeScript 优先的 Schema 验证 | | **图标** | Lucide Icons | latest | shadcn/ui 默认搭配 | -| **HTTP 客户端** | Native Fetch | - | 已封装 apiFetch | +| **HTTP 客户端** | Axios | ^1.x | 封装为 http 模块 | ## 二、目录结构 @@ -106,12 +106,12 @@ apps/web/src/ │ └── captcha.service.ts # 验证码 API │ ├── lib/ # 工具库 -│ ├── api.ts # API 请求封装 (已有) +│ ├── http.ts # Axios HTTP 客户端封装 │ ├── crypto.ts # 加密模块 (已有) +│ ├── auth.ts # 认证相关工具函数 │ ├── query-client.ts # TanStack Query 配置 │ ├── validations.ts # Zod Schema 定义 -│ ├── utils.ts # 通用工具函数 -│ └── cn.ts # className 合并工具 +│ └── utils.ts # 通用工具函数 │ ├── types/ # 类型定义 │ └── index.ts # 类型导出 (已有) @@ -257,7 +257,7 @@ export const useUIStore = create()( └──────┬──────┘ │ 调用 ┌──────▼──────┐ -│ apiFetch │ HTTP 请求 + 加密处理 +│ http │ Axios HTTP 客户端 + 加密处理 └─────────────┘ ``` @@ -265,12 +265,13 @@ export const useUIStore = create()( ```typescript // services/user.service.ts -import { apiFetch } from '@/lib/api'; +import { http } from '@/lib/http'; import type { UserResponse, PaginatedResponse, UpdateUserDto } from '@seclusion/shared'; +import { API_ENDPOINTS } from '@/config/constants'; export interface GetUsersParams { page?: number; @@ -280,47 +281,28 @@ export interface GetUsersParams { export const userService = { // 获取用户列表 - getUsers: async (params: GetUsersParams, token: string) => { - const searchParams = new URLSearchParams(); - if (params.page) searchParams.set('page', String(params.page)); - if (params.pageSize) searchParams.set('pageSize', String(params.pageSize)); - if (params.search) searchParams.set('search', params.search); - - const query = searchParams.toString(); - return apiFetch>( - `/users${query ? `?${query}` : ''}`, - { token } - ); + getUsers: (params: GetUsersParams = {}): Promise> => { + return http.get>(API_ENDPOINTS.USERS, { params }); }, // 获取单个用户 - getUser: async (id: string, token: string) => { - return apiFetch(`/users/${id}`, { token }); + getUser: (id: string): Promise => { + return http.get(`${API_ENDPOINTS.USERS}/${id}`); }, // 更新用户 - updateUser: async (id: string, data: UpdateUserDto, token: string) => { - return apiFetch(`/users/${id}`, { - method: 'PATCH', - body: JSON.stringify(data), - token, - }); + updateUser: (id: string, data: UpdateUserDto): Promise => { + return http.patch(`${API_ENDPOINTS.USERS}/${id}`, data); }, // 删除用户 - deleteUser: async (id: string, token: string) => { - return apiFetch(`/users/${id}`, { - method: 'DELETE', - token, - }); + deleteUser: (id: string): Promise => { + return http.delete(`${API_ENDPOINTS.USERS}/${id}`); }, // 恢复用户 - restoreUser: async (id: string, token: string) => { - return apiFetch(`/users/${id}/restore`, { - method: 'PATCH', - token, - }); + restoreUser: (id: string): Promise => { + return http.patch(`${API_ENDPOINTS.USERS}/${id}/restore`); }, }; ``` @@ -331,7 +313,7 @@ export const userService = { // hooks/useUsers.ts import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { userService, type GetUsersParams } from '@/services/user.service'; -import { useAuthStore } from '@/stores/authStore'; +import { useIsAuthenticated } from '@/stores'; // Query Keys export const userKeys = { @@ -344,34 +326,33 @@ export const userKeys = { // 获取用户列表 export function useUsers(params: GetUsersParams = {}) { - const token = useAuthStore((state) => state.token); + const isAuthenticated = useIsAuthenticated(); return useQuery({ queryKey: userKeys.list(params), - queryFn: () => userService.getUsers(params, token!), - enabled: !!token, + queryFn: () => userService.getUsers(params), + enabled: isAuthenticated, }); } // 获取单个用户 export function useUser(id: string) { - const token = useAuthStore((state) => state.token); + const isAuthenticated = useIsAuthenticated(); return useQuery({ queryKey: userKeys.detail(id), - queryFn: () => userService.getUser(id, token!), - enabled: !!token && !!id, + queryFn: () => userService.getUser(id), + enabled: isAuthenticated && !!id, }); } // 更新用户 export function useUpdateUser() { const queryClient = useQueryClient(); - const token = useAuthStore((state) => state.token); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) => - userService.updateUser(id, data, token!), + userService.updateUser(id, data), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: userKeys.lists() }); queryClient.invalidateQueries({ queryKey: userKeys.detail(id) }); @@ -382,10 +363,9 @@ export function useUpdateUser() { // 删除用户 export function useDeleteUser() { const queryClient = useQueryClient(); - const token = useAuthStore((state) => state.token); return useMutation({ - mutationFn: (id: string) => userService.deleteUser(id, token!), + mutationFn: (id: string) => userService.deleteUser(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: userKeys.lists() }); }, diff --git a/docs/workflow/250117-权限管理体系实施.md b/docs/workflow/250117-权限管理体系实施.md new file mode 100644 index 0000000..add5fed --- /dev/null +++ b/docs/workflow/250117-权限管理体系实施.md @@ -0,0 +1,544 @@ +# 菜单及路由管理体系实施文档 + +## 需求确认 + +| 项目 | 选择 | +|------|------| +| 菜单来源 | 混合模式(核心菜单静态 + 扩展菜单数据库)| +| 权限粒度 | 按钮级别 | +| 权限模型 | RBAC(角色-权限模型)| +| 超级管理员 | 需要内置 | +| 权限编码 | `资源:操作` 格式(如 `user:create`)| +| 前端权限 | Hook + 组件 + 指令 | +| 路由守卫 | 自动拦截(跳转 403)| + +--- + +## 一、数据库模型设计 + +### 新增模型 + +```prisma +// 角色表 +model Role { + id String @id @default(cuid(2)) + code String @unique // 角色编码: admin, user + name String // 角色名称 + description String? + isSystem Boolean @default(false) // 系统内置角色不可删 + isEnabled Boolean @default(true) + sort Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + + users UserRole[] + permissions RolePermission[] + menus RoleMenu[] +} + +// 权限表 +model Permission { + id String @id @default(cuid(2)) + code String @unique // 权限编码: user:create + name String + description String? + resource String // 资源: user + action String // 操作: create + isEnabled Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + + roles RolePermission[] +} + +// 菜单表 +model Menu { + id String @id @default(cuid(2)) + parentId String? + code String @unique + name String + type String @default("menu") // dir / menu / button + path String? + icon String? // Lucide 图标名 + component String? + permission String? // 关联权限编码 + isExternal Boolean @default(false) + isHidden Boolean @default(false) + isEnabled Boolean @default(true) + isStatic Boolean @default(true) // 静态/动态菜单 + sort Int @default(0) + meta Json? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + + parent Menu? @relation("MenuTree", fields: [parentId], references: [id]) + children Menu[] @relation("MenuTree") + roles RoleMenu[] +} + +// 关联表 +model UserRole { ... } +model RolePermission { ... } +model RoleMenu { ... } + +// User 模型扩展 +model User { + // ... 现有字段 + isSuperAdmin Boolean @default(false) // 超级管理员标记 + roles UserRole[] +} +``` + +### 超级管理员设计 + +- `User.isSuperAdmin` 字段标记 +- 超管拥有所有权限,跳过权限检查 +- 超管标记仅允许数据库直接修改 + +--- + +## 二、共享类型定义 + +**文件**: `packages/shared/src/types/permission.ts` + +### 枚举常量 + +```typescript +export const MenuType = { DIR: 'dir', MENU: 'menu', BUTTON: 'button' } as const; +export const SystemRoleCode = { SUPER_ADMIN: 'super_admin', ADMIN: 'admin', USER: 'user' } as const; +export const PermissionAction = { CREATE: 'create', READ: 'read', UPDATE: 'update', DELETE: 'delete' } as const; +``` + +### 接口类型 + +- `RoleResponse` / `RoleDetailResponse` - 角色响应 +- `PermissionResponse` - 权限响应 +- `MenuResponse` / `MenuTreeNode` - 菜单响应 +- `AuthUserWithPermissions` - 带权限的认证用户 +- `UserMenusAndPermissionsResponse` - 用户菜单和权限响应 + +--- + +## 三、后端模块设计 + +### 目录结构 + +``` +apps/api/src/permission/ +├── decorators/ +│ ├── index.ts +│ └── require-permission.decorator.ts +├── guards/ +│ ├── index.ts +│ └── permission.guard.ts +├── dto/ +│ ├── index.ts +│ ├── permission.dto.ts +│ ├── role.dto.ts +│ └── menu.dto.ts +├── services/ +│ ├── index.ts +│ ├── permission.service.ts +│ ├── role.service.ts +│ └── menu.service.ts +├── controllers/ +│ ├── index.ts +│ ├── permission.controller.ts +│ ├── role.controller.ts +│ └── menu.controller.ts +├── index.ts +└── permission.module.ts +``` + +### 核心实现 + +**权限装饰器** `@RequirePermission(...permissions)` + +```typescript +export const RequirePermission = (...permissions: string[]) => + SetMetadata(PERMISSION_KEY, permissions); +``` + +**权限守卫** `PermissionGuard` + +```typescript +canActivate(context) { + const permissions = reflector.get(PERMISSION_KEY, handler); + if (!permissions) return true; + + const user = request.user; + if (user.isSuperAdmin) return true; // 超管直接通过 + + return permissions.some(p => user.permissions.includes(p)); +} +``` + +**JWT 策略更新** - 返回用户权限信息 + +```typescript +validate(payload) { + const user = await prisma.user.findUnique({ + include: { roles: { include: { role: { include: { permissions } } } } } + }); + return { ...user, roles: [...], roleIds: [...], permissions: [...] }; +} +``` + +### API 端点 + +| 模块 | 端点 | 权限 | +|------|------|------| +| 角色 | `GET/POST /roles` | `role:read/create` | +| 角色 | `GET/PATCH/DELETE /roles/:id` | `role:read/update/delete` | +| 权限 | `GET /permissions` | `permission:read` | +| 菜单 | `GET/POST /menus` | `menu:read/create` | +| 菜单 | `PATCH/DELETE /menus/:id` | `menu:update/delete` | +| 当前用户 | `GET /auth/menus` | 登录即可 | + +--- + +## 四、前端权限系统设计 + +### 目录结构 + +``` +apps/web/src/ +├── stores/permissionStore.ts # 权限状态 +├── hooks/usePermission.ts # 权限检查 Hook +├── components/permission/ +│ ├── index.ts +│ ├── PermissionGuard.tsx # 权限守卫组件 +│ └── WithPermission.tsx # 权限 HOC +├── services/permission.service.ts # 权限服务 +├── config/route-permissions.ts # 路由权限映射 +├── lib/icons.ts # 图标映射 +└── app/403/page.tsx # 无权限页面 +``` + +### 权限 Store + +```typescript +interface PermissionState { + menus: MenuTreeNode[]; + permissions: string[]; + isSuperAdmin: boolean; + isLoaded: boolean; +} + +interface PermissionActions { + setPermissionData: (data) => void; + clearPermissionData: () => void; + hasPermission: (p: string | string[]) => boolean; + hasAllPermissions: (permissions: string[]) => boolean; +} +``` + +### usePermission Hook + +```typescript +function usePermission() { + const { permissions, isSuperAdmin } = usePermissionStore(); + + const hasPermission = (permission: string | string[]) => { + if (isSuperAdmin) return true; + if (Array.isArray(permission)) return permission.some(p => permissions.includes(p)); + return permissions.includes(permission); + }; + + return { hasPermission, hasAnyPermission, hasAllPermissions }; +} +``` + +### PermissionGuard 组件 + +```tsx + + + + +无权限}> + + + + + + +``` + +### 路由守卫 + +在 Dashboard Layout 中实现: + +1. 登录后获取用户菜单和权限 +2. 存入 permissionStore +3. 路由变化时检查权限 +4. 无权限跳转 403 页面 + +--- + +## 五、文件清单 + +### 后端文件 + +| 优先级 | 文件路径 | 说明 | +|--------|----------|------| +| P0 | `apps/api/prisma/schema.prisma` | 数据库模型 | +| P0 | `apps/api/prisma/seed.ts` | 种子数据 | +| P0 | `apps/api/src/permission/guards/permission.guard.ts` | 权限守卫 | +| P0 | `apps/api/src/permission/decorators/require-permission.decorator.ts` | 权限装饰器 | +| P0 | `apps/api/src/auth/strategies/jwt.strategy.ts` | JWT 策略(已更新) | +| P0 | `apps/api/src/auth/auth.controller.ts` | 认证控制器(已更新) | +| P0 | `apps/api/src/auth/auth.module.ts` | 认证模块(已更新) | +| P1 | `apps/api/src/permission/services/*.ts` | 服务层 | +| P1 | `apps/api/src/permission/controllers/*.ts` | 控制器层 | +| P1 | `apps/api/src/permission/dto/*.ts` | DTO 定义 | +| P1 | `apps/api/src/permission/permission.module.ts` | 权限模块 | +| P1 | `apps/api/src/app.module.ts` | 应用模块(已更新) | +| P1 | `apps/api/src/prisma/prisma.service.ts` | Prisma 服务(已更新) | + +### 共享包文件 + +| 文件路径 | 说明 | +|----------|------| +| `packages/shared/src/types/permission.ts` | 权限相关类型定义 | +| `packages/shared/src/types/index.ts` | 类型导出(已更新) | + +### 前端文件 + +| 优先级 | 文件路径 | 说明 | +|--------|----------|------| +| P0 | `apps/web/src/stores/permissionStore.ts` | 权限状态管理 | +| P0 | `apps/web/src/app/(dashboard)/layout.tsx` | Dashboard 布局(已更新) | +| P1 | `apps/web/src/hooks/usePermission.ts` | 权限检查 Hook | +| P1 | `apps/web/src/components/permission/PermissionGuard.tsx` | 权限守卫组件 | +| P1 | `apps/web/src/components/permission/WithPermission.tsx` | 权限 HOC | +| P1 | `apps/web/src/components/layout/Sidebar.tsx` | 侧边栏(已更新) | +| P1 | `apps/web/src/lib/icons.ts` | 图标映射工具 | +| P1 | `apps/web/src/config/route-permissions.ts` | 路由权限配置 | +| P1 | `apps/web/src/config/constants.ts` | 常量配置(已更新) | +| P1 | `apps/web/src/services/permission.service.ts` | 权限服务 | +| P1 | `apps/web/src/app/403/page.tsx` | 403 页面 | +| P2 | `apps/web/src/components/ui/collapsible.tsx` | 折叠组件 | + +--- + +## 六、使用指南 + +### 后端使用 + +#### 1. 保护接口 + +```typescript +@Controller('users') +@UseGuards(JwtAuthGuard, PermissionGuard) +@ApiBearerAuth() +export class UserController { + @Get() + @RequirePermission('user:read') + findAll() { ... } + + @Post() + @RequirePermission('user:create') + create() { ... } + + @Delete(':id') + @RequirePermission('user:delete') + delete() { ... } +} +``` + +#### 2. 获取当前用户权限 + +```typescript +@Get('profile') +getProfile(@CurrentUser() user: AuthUserWithPermissions) { + // user.isSuperAdmin - 是否超管 + // user.roles - 角色编码列表 + // user.permissions - 权限编码列表 +} +``` + +### 前端使用 + +#### 1. 权限守卫组件 + +```tsx +import { PermissionGuard } from '@/components/permission'; + +// 单个权限 + + + + +// 多个权限(OR 关系) + + + + +// 多个权限(AND 关系) + + + + +// 带 fallback +无权限}> + + +``` + +#### 2. usePermission Hook + +```tsx +import { usePermission } from '@/hooks'; + +function MyComponent() { + const { hasPermission, isSuperAdmin } = usePermission(); + + return ( +
+ {hasPermission('user:create') && } + {hasPermission(['user:update', 'user:delete']) && } +
+ ); +} +``` + +#### 3. 路由权限配置 + +```typescript +// config/route-permissions.ts +export const routePermissions: Record = { + '/users': 'user:read', + '/users/create': 'user:create', + '/roles': 'role:read', +}; + +export const publicRoutes = [ + '/dashboard', + '/profile', + '/settings', +]; +``` + +--- + +## 七、部署步骤 + +### 1. 数据库迁移 + +```bash +# 启动数据库 +docker compose -f deploy/docker-compose.yml up -d + +# 执行数据库迁移 +cd apps/api && pnpm db:migrate + +# 初始化种子数据 +cd apps/api && pnpm db:seed +``` + +### 2. 验证 + +1. 使用超级管理员账号登录 + - 邮箱: `admin@seclusion.dev` + - 密码: `admin123` + +2. 访问 Swagger 文档查看新增的权限管理 API + - `http://localhost:4000/api/docs` + +3. 验证菜单根据权限动态显示 + +4. 验证无权限路由跳转到 403 页面 + +--- + +## 八、预置数据 + +### 角色 + +| 编码 | 名称 | 说明 | +|------|------|------| +| `super_admin` | 超级管理员 | 系统内置,拥有所有权限 | +| `admin` | 管理员 | 系统内置,拥有大部分权限 | +| `user` | 普通用户 | 系统内置,基础权限 | + +### 权限 + +| 编码 | 名称 | 资源 | 操作 | +|------|------|------|------| +| `user:create` | 创建用户 | user | create | +| `user:read` | 查看用户 | user | read | +| `user:update` | 更新用户 | user | update | +| `user:delete` | 删除用户 | user | delete | +| `role:create` | 创建角色 | role | create | +| `role:read` | 查看角色 | role | read | +| `role:update` | 更新角色 | role | update | +| `role:delete` | 删除角色 | role | delete | +| `permission:read` | 查看权限 | permission | read | +| `menu:create` | 创建菜单 | menu | create | +| `menu:read` | 查看菜单 | menu | read | +| `menu:update` | 更新菜单 | menu | update | +| `menu:delete` | 删除菜单 | menu | delete | + +### 菜单 + +| 编码 | 名称 | 类型 | 路径 | 图标 | 权限 | +|------|------|------|------|------|------| +| `dashboard` | 仪表盘 | menu | /dashboard | LayoutDashboard | - | +| `user-management` | 用户管理 | menu | /users | Users | user:read | +| `system` | 系统管理 | dir | - | Settings | - | +| `role-management` | 角色管理 | menu | /roles | Shield | role:read | +| `menu-management` | 菜单管理 | menu | /menus | Menu | menu:read | +| `profile` | 个人中心 | menu | /profile | User | - | +| `settings` | 系统设置 | menu | /settings | Settings | - | + +--- + +## 九、阶段四:管理界面(已完成) + +### 角色管理 + +| 文件路径 | 说明 | +|----------|------| +| `apps/web/src/app/(dashboard)/roles/page.tsx` | 角色管理页面 | +| `apps/web/src/components/roles/RolesTable.tsx` | 角色列表表格组件 | +| `apps/web/src/components/roles/RoleEditDialog.tsx` | 角色编辑弹窗组件 | +| `apps/web/src/hooks/useRoles.ts` | 角色相关 Hooks | +| `apps/web/src/services/role.service.ts` | 角色服务层 | + +### 菜单管理 + +| 文件路径 | 说明 | +|----------|------| +| `apps/web/src/app/(dashboard)/menus/page.tsx` | 菜单管理页面 | +| `apps/web/src/components/menus/MenusTable.tsx` | 菜单列表表格组件 | +| `apps/web/src/components/menus/MenuEditDialog.tsx` | 菜单编辑弹窗组件 | +| `apps/web/src/hooks/useMenus.ts` | 菜单相关 Hooks | +| `apps/web/src/services/menu.service.ts` | 菜单服务层 | + +### 功能说明 + +1. **角色管理**: + - 角色列表展示(支持分页、搜索、排序) + - 新建/编辑角色 + - 为角色分配权限 + - 删除角色(系统内置角色不可删除) + +2. **菜单管理**: + - 菜单列表展示(支持分页、搜索、排序) + - 新建/编辑菜单 + - 支持目录、菜单、按钮三种类型 + - 删除菜单(静态菜单不可删除) + +--- + +## 十、后续扩展建议 + +1. **数据权限** - 支持数据行级权限控制 +2. **权限缓存** - Redis 缓存用户权限减少数据库查询 +3. **权限继承** - 支持角色继承 +4. **操作日志** - 记录权限变更日志 +5. **批量导入导出** - 支持权限配置的批量操作