diff --git a/admin/src/pages/menu/constants.tsx b/admin/src/pages/menu/constants.tsx index 9b607de..0384080 100644 --- a/admin/src/pages/menu/constants.tsx +++ b/admin/src/pages/menu/constants.tsx @@ -27,8 +27,8 @@ export function getColumns(callback: (type: TableOptions, record: any) => Promis align: 'center', }, { - title: '菜单url', - dataIndex: 'url', + title: '菜单path', + dataIndex: 'path', align: 'center', }, { diff --git a/admin/src/pages/menu/form.tsx b/admin/src/pages/menu/form.tsx index 85a6533..624ef2e 100644 --- a/admin/src/pages/menu/form.tsx +++ b/admin/src/pages/menu/form.tsx @@ -88,8 +88,8 @@ export default function FormComponent(props: Props) { - - + + diff --git a/admin/src/pages/role/grantPerm.tsx b/admin/src/pages/role/grantPerm.tsx index f301f8d..d2d5721 100644 --- a/admin/src/pages/role/grantPerm.tsx +++ b/admin/src/pages/role/grantPerm.tsx @@ -20,7 +20,7 @@ function GrantPermission(props: Props) { try { setConfirmLoading(true); const values = await form.validate(); - await grantPermission(values.id, checkedKeys.map(item=>Number(item))); + await grantPermission(values.id, checkedKeys); Message.success(`操作成功!`); handleConfirm(); } catch (e) { @@ -41,13 +41,12 @@ function GrantPermission(props: Props) { const [handleFinished, setHandleFinished] = useState(false); const handleMenuAndKeys = (menu, menuIds) => { - let newMenuIds = menuIds.map((menuId) => menuId.toString()); + let newMenuIds = menuIds.map((menuId) => menuId); function handleMenu(_menu) { _menu.forEach((item) => { // 组件需要的key必须为string - item.id = item.id.toString(); - item.parentId = item.id.toString(); + item.parentId = item.id; if (item.children?.length > 0) { if (item.children.some(child => !newMenuIds.includes(child.id))) { diff --git a/admin/src/routes.ts b/admin/src/routes.ts index aa4988c..cbf58c2 100644 --- a/admin/src/routes.ts +++ b/admin/src/routes.ts @@ -15,7 +15,7 @@ export type IRoute = AuthParams & { const generatorMenu = (data, parentId = '-') => { const menu = []; for (const item of data) { - item.key = item.url; + item.key = item.path; if ((item.parentId || '-') === parentId) { const children = generatorMenu(data, item.id); if (children.length) { diff --git a/server/README.md b/server/README.md index 6941be1..20ec267 100644 --- a/server/README.md +++ b/server/README.md @@ -2,3 +2,10 @@ # 种子文件 npx prisma db seed ``` +```json +{ + "prisma": { + "seed": "ts-node prisma/seed.ts" + } +} +``` diff --git a/server/prisma/migrations/20250308094457_/migration.sql b/server/prisma/migrations/20250310150829_/migration.sql similarity index 82% rename from server/prisma/migrations/20250308094457_/migration.sql rename to server/prisma/migrations/20250310150829_/migration.sql index bfee5d9..a2085c8 100644 --- a/server/prisma/migrations/20250308094457_/migration.sql +++ b/server/prisma/migrations/20250310150829_/migration.sql @@ -57,35 +57,11 @@ CREATE TABLE "sys_permission" ( "id" TEXT NOT NULL, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMP(3) NOT NULL, - "type" INTEGER NOT NULL, + "name" TEXT, CONSTRAINT "sys_permission_pkey" PRIMARY KEY ("id") ); --- CreateTable -CREATE TABLE "sys_menu" ( - "id" TEXT NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - "name" TEXT NOT NULL, - "url" TEXT NOT NULL, - "parent_id" TEXT, - "sort" INTEGER NOT NULL DEFAULT 0, - - CONSTRAINT "sys_menu_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "sys_menu_permission" ( - "id" TEXT NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - "menu_id" TEXT NOT NULL, - "permission_id" TEXT NOT NULL, - - CONSTRAINT "sys_menu_permission_pkey" PRIMARY KEY ("id") -); - -- CreateTable CREATE TABLE "sys_role_permission" ( "id" TEXT NOT NULL, @@ -97,6 +73,31 @@ CREATE TABLE "sys_role_permission" ( CONSTRAINT "sys_role_permission_pkey" PRIMARY KEY ("id") ); +-- CreateTable +CREATE TABLE "sys_resource" ( + "id" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "name" TEXT NOT NULL, + "path" TEXT, + "parent_id" TEXT, + "sort" INTEGER NOT NULL DEFAULT 0, + "type" INTEGER NOT NULL, + + CONSTRAINT "sys_resource_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "sys_resource_permission" ( + "id" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + "resource_id" TEXT NOT NULL, + "permission_id" TEXT NOT NULL, + + CONSTRAINT "sys_resource_permission_pkey" PRIMARY KEY ("id") +); + -- CreateTable CREATE TABLE "sys_config" ( "id" TEXT NOT NULL, @@ -125,10 +126,7 @@ CREATE INDEX "sys_user_role_user_id_idx" ON "sys_user_role"("user_id"); CREATE INDEX "sys_user_role_role_id_idx" ON "sys_user_role"("role_id"); -- CreateIndex -CREATE INDEX "sys_menu_permission_menu_id_idx" ON "sys_menu_permission"("menu_id"); - --- CreateIndex -CREATE INDEX "sys_menu_permission_permission_id_idx" ON "sys_menu_permission"("permission_id"); +CREATE UNIQUE INDEX "sys_permission_name_key" ON "sys_permission"("name"); -- CreateIndex CREATE INDEX "sys_role_permission_role_id_idx" ON "sys_role_permission"("role_id"); @@ -136,5 +134,17 @@ CREATE INDEX "sys_role_permission_role_id_idx" ON "sys_role_permission"("role_id -- CreateIndex CREATE INDEX "sys_role_permission_permission_id_idx" ON "sys_role_permission"("permission_id"); +-- CreateIndex +CREATE UNIQUE INDEX "sys_resource_name_type_key" ON "sys_resource"("name", "type"); + +-- CreateIndex +CREATE UNIQUE INDEX "sys_resource_path_key" ON "sys_resource"("path"); + +-- CreateIndex +CREATE INDEX "sys_resource_permission_resource_id_idx" ON "sys_resource_permission"("resource_id"); + +-- CreateIndex +CREATE INDEX "sys_resource_permission_permission_id_idx" ON "sys_resource_permission"("permission_id"); + -- CreateIndex CREATE UNIQUE INDEX "sys_config_key_key" ON "sys_config"("key"); diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 3312b2c..b3dcb98 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -77,45 +77,16 @@ model File { // 权限表 model SysPermission { - id String @id @default(cuid()) /// 主键id - createdAt DateTime @default(now()) @map("created_at") /// 创建时间 - updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间 - type Int /// 权限类型 - sysRolePermission SysRolePermission[] - sysMenuPermission SysMenuPermission[] + id String @id @default(cuid()) /// 主键id + createdAt DateTime @default(now()) @map("created_at") /// 创建时间 + updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间 + name String? @unique // 权限字符 + sysRolePermission SysRolePermission[] + sysResourcePermission SysResourcePermission[] @@map("sys_permission") } -// 系统菜单表 -model SysMenu { - id String @id @default(cuid()) /// 主键id - createdAt DateTime @default(now()) @map("created_at") /// 创建时间 - updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间 - name String - url String - parentId String? @map("parent_id") - sort Int @default(0) - sysMenuPermission SysMenuPermission[] - - @@map("sys_menu") -} - -// 菜单权限关联表 -model SysMenuPermission { - id String @id @default(cuid()) /// 主键id - createdAt DateTime @default(now()) @map("created_at") /// 创建时间 - updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间 - menuId String @map("menu_id") - permissionId String @map("permission_id") - menu SysMenu @relation(fields: [menuId], references: [id], onDelete: Cascade) - permission SysPermission @relation(fields: [permissionId], references: [id], onDelete: Cascade) - - @@index([menuId]) - @@index([permissionId]) - @@map("sys_menu_permission") -} - // 角色权限关联表 model SysRolePermission { id String @id @default(cuid()) /// 主键id @@ -131,6 +102,38 @@ model SysRolePermission { @@map("sys_role_permission") } +// 系统资源表 +model SysResource { + id String @id @default(cuid()) /// 主键id + createdAt DateTime @default(now()) @map("created_at") /// 创建时间 + updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间 + name String + path String? /// 菜单或api路径 + parentId String? @map("parent_id") + sort Int @default(0) + type Int /// 资源类型 0:菜单 1:api + sysResourcePermission SysResourcePermission[] + + @@unique([name, type]) + @@unique([path]) + @@map("sys_resource") +} + +// 系统资源权限表 +model SysResourcePermission { + id String @id @default(cuid()) /// 主键id + createdAt DateTime @default(now()) @map("created_at") /// 创建时间 + updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间 + resourceId String @map("resource_id") + resource SysResource @relation(fields: [resourceId], references: [id], onDelete: Cascade) + permissionId String @map("permission_id") + permission SysPermission @relation(fields: [permissionId], references: [id], onDelete: Cascade) + + @@index([resourceId]) + @@index([permissionId]) + @@map("sys_resource_permission") +} + // 系统配置 model SysConfig { id String @id @default(cuid()) /// 主键id diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts index 953e766..f96e000 100644 --- a/server/prisma/seed.ts +++ b/server/prisma/seed.ts @@ -3,6 +3,7 @@ const prisma = new PrismaClient(); enum PermissionType { Menu, + API, } async function main() { @@ -18,23 +19,25 @@ async function main() { }, }); const menus = [ - { name: '用户管理', url: 'user' }, - { name: '角色管理', url: 'role' }, - { name: '菜单管理', url: 'menu' }, - { name: '系统配置', url: 'config' }, + { name: '用户管理', path: 'user', type: PermissionType.Menu }, + { name: '角色管理', path: 'role', type: PermissionType.Menu }, + { name: '菜单管理', path: 'menu', type: PermissionType.Menu }, + { name: '系统配置', path: 'config', type: PermissionType.Menu }, ]; for (const menu of menus) { // 创建菜单 await prisma.$transaction(async (_prisma) => { - const { id: menuId } = await _prisma.sysMenu.create({ data: menu }); + const { id: resourceId } = await _prisma.sysResource.create({ + data: menu, + }); // 创建菜单权限 const { id: permissionId } = await _prisma.sysPermission.create({ - data: { type: PermissionType.Menu }, + data: {}, }); // 关联权限和菜单 - await _prisma.sysMenuPermission.create({ - data: { menuId, permissionId }, + await _prisma.sysResourcePermission.create({ + data: { resourceId, permissionId }, }); }); } diff --git a/server/src/common/enum/index.ts b/server/src/common/enum/index.ts index cbac3e5..47f8ef0 100644 --- a/server/src/common/enum/index.ts +++ b/server/src/common/enum/index.ts @@ -10,4 +10,5 @@ export enum RedisGroup { export enum PermissionType { Menu, + API, } diff --git a/server/src/modules/system/menu/dto/create-menu.dto.ts b/server/src/modules/system/menu/dto/create-menu.dto.ts index 0f130aa..e893b2b 100644 --- a/server/src/modules/system/menu/dto/create-menu.dto.ts +++ b/server/src/modules/system/menu/dto/create-menu.dto.ts @@ -7,7 +7,7 @@ export class CreateMenuDto { @IsNotEmpty() @IsString() - url: string; + path: string; @IsOptional() @IsString() @@ -16,4 +16,8 @@ export class CreateMenuDto { @IsOptional() @IsInt() sort?: number; + + @IsOptional() + @IsInt() + type: number; } diff --git a/server/src/modules/system/menu/menu.service.ts b/server/src/modules/system/menu/menu.service.ts index b61d98d..ba94779 100644 --- a/server/src/modules/system/menu/menu.service.ts +++ b/server/src/modules/system/menu/menu.service.ts @@ -11,8 +11,9 @@ export class MenuService { findOne(params: { id?: string; excludeId?: string }) { const { id, excludeId } = params; - return this.prisma.sysMenu.findFirst({ + return this.prisma.sysResource.findFirst({ where: { + type: PermissionType.Menu, ...(id ? { id } : {}), ...(excludeId ? { NOT: { id: excludeId } } : {}), }, @@ -20,18 +21,20 @@ export class MenuService { } findOneById(id: string) { - return this.prisma.sysMenu.findUnique({ where: { id } }); + return this.prisma.sysResource.findUnique({ where: { id } }); } async create(data: CreateMenuDto) { await this.prisma.$transaction(async (prisma) => { - const { id: menuId } = await prisma.sysMenu.create({ data }); + const { id: resourceId } = await prisma.sysResource.create({ data }); // 创建菜单权限 const { id: permissionId } = await prisma.sysPermission.create({ - data: { type: PermissionType.Menu }, + data: {}, + }); + // // 关联权限和菜单 + await prisma.sysResourcePermission.create({ + data: { resourceId, permissionId }, }); - // 关联权限和菜单 - await prisma.sysMenuPermission.create({ data: { menuId, permissionId } }); }); } @@ -50,23 +53,23 @@ export class MenuService { } async getTreeMenu() { - const query: Prisma.SysMenuFindManyArgs = { + const query: Prisma.SysResourceFindManyArgs = { orderBy: [{ sort: 'desc' }, { createdAt: 'desc' }], }; - const list = await this.prisma.sysMenu.findMany(query); + const list = await this.prisma.sysResource.findMany(query); return this.generatorMenu(list); } findAll() { - const query: Prisma.SysMenuFindManyArgs = { + const query: Prisma.SysResourceFindManyArgs = { select: { id: true, name: true, - url: true, + path: true, parentId: true, }, }; - return this.prisma.sysMenu.findMany(query); + return this.prisma.sysResource.findMany(query); } async update(id: string, data: UpdateMenuDto) { @@ -82,18 +85,18 @@ export class MenuService { ); } - await this.prisma.sysMenu.update({ data, where: { id } }); + await this.prisma.sysResource.update({ data, where: { id } }); } - async remove(menuId: string) { - const doesExists = await this.findOneById(menuId); + async remove(resourceId: string) { + const doesExists = await this.findOneById(resourceId); if (!doesExists) { throw new HttpException('菜单不存在!', HttpStatus.NOT_FOUND); } // 判断是否存在子菜单 - const doesExistsChildren = await this.prisma.sysMenu.findFirst({ - where: { parentId: menuId }, + const doesExistsChildren = await this.prisma.sysResource.findFirst({ + where: { parentId: resourceId }, }); if (doesExistsChildren) { @@ -108,14 +111,14 @@ export class MenuService { // 因为schema文件中设置了onDelete: Cascade 删除菜单会删除关联表数据 则无法找出对应权限 await prisma.sysPermission.deleteMany({ where: { - sysMenuPermission: { - some: { menuId }, + sysResourcePermission: { + some: { resourceId }, }, }, }); //step2 删除菜单数据 - await prisma.sysMenu.delete({ where: { id: menuId } }); + await prisma.sysResourcePermission.delete({ where: { id: resourceId } }); }); } } diff --git a/server/src/modules/system/permission/permission.service.ts b/server/src/modules/system/permission/permission.service.ts index 00d0871..65e09c3 100644 --- a/server/src/modules/system/permission/permission.service.ts +++ b/server/src/modules/system/permission/permission.service.ts @@ -7,9 +7,9 @@ export class PermissionService { constructor(private readonly prisma: PrismaService) {} async getMenu(id: string) { - const menuIds = await this.prisma.sysMenuPermission.findMany({ + const menuIds = await this.prisma.sysResourcePermission.findMany({ select: { - menuId: true, + resourceId: true, }, where: { permission: { @@ -20,7 +20,7 @@ export class PermissionService { }, }); - return menuIds.map((item) => item.menuId); + return menuIds.map((item) => item.resourceId); } async grantMenu(id: string, { menuIds }: GrantMenuDto) { @@ -30,9 +30,9 @@ export class PermissionService { where: { roleId: id }, }); // 通过菜单id 找出所有权限id - const rolePermIds = await prisma.sysMenuPermission.findMany({ + const rolePermIds = await prisma.sysResourcePermission.findMany({ select: { permissionId: true }, - where: { menuId: { in: menuIds } }, + where: { resourceId: { in: menuIds } }, }); // 根据权限id 关联菜单和权限 if (rolePermIds.length > 0) { diff --git a/server/src/modules/system/user/user.service.ts b/server/src/modules/system/user/user.service.ts index f5abbd7..d36c16e 100644 --- a/server/src/modules/system/user/user.service.ts +++ b/server/src/modules/system/user/user.service.ts @@ -13,7 +13,7 @@ import { QueryListDto } from './dto/query-list.dto'; import * as bcrypt from 'bcryptjs'; import { UpdatePasswordDto } from './dto/update-password.dto'; import { SkipAuth } from '@/common/decorators/skip-auth.decorator'; -import { RedisGroup } from '@/common/enum'; +import { PermissionType, RedisGroup } from '@/common/enum'; import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager'; @Injectable() @@ -126,11 +126,12 @@ export class UserService { }, }); - const query: Prisma.SysMenuFindManyArgs = { where: {} }; + const query: Prisma.SysResourceFindManyArgs = { where: {} }; if (!isAdmin) { query.where = { - sysMenuPermission: { + type: PermissionType.Menu, + sysResourcePermission: { some: { permission: { sysRolePermission: { @@ -142,7 +143,8 @@ export class UserService { }; } - const menus = await this.prisma.sysMenu.findMany(query); + const menus = await this.prisma.sysResource.findMany(); + return { ...user, menus,