From af75cf70c7d6c50b24aa1cdd9ba65fe016d71a0d Mon Sep 17 00:00:00 2001 From: GaoNeng-wWw Date: Mon, 20 Jul 2026 14:30:28 +0800 Subject: [PATCH 01/28] fix(menu): remove menu --- .../shared/src/.generate/i18n.generated.ts | 1 + .../libs/shared/src/guard/reject.guard.ts | 17 +++-- template/nest-next/src/menu/menu.module.ts | 11 +++- template/nest-next/src/menu/menu.service.ts | 17 +---- .../src/menu/queries/get-user-menu.query.ts | 63 +++++++++++++++++++ template/nest-next/src/menu/queries/index.ts | 1 + .../permission/dto/create-permission.dto.ts | 2 +- .../queries/user-has-permission.query.ts | 12 ++-- .../nest-next/src/role/assembler/find-role.ts | 2 +- .../nest-next/src/role/dto/find-role.dto.ts | 2 +- .../src/role/dto/get-role-detail.dto.ts | 4 +- .../views/menu/info/components/info-tab.vue | 3 +- 12 files changed, 99 insertions(+), 36 deletions(-) create mode 100644 template/nest-next/src/menu/queries/get-user-menu.query.ts diff --git a/template/nest-next/libs/shared/src/.generate/i18n.generated.ts b/template/nest-next/libs/shared/src/.generate/i18n.generated.ts index 53bb5764..565241d5 100644 --- a/template/nest-next/libs/shared/src/.generate/i18n.generated.ts +++ b/template/nest-next/libs/shared/src/.generate/i18n.generated.ts @@ -1,5 +1,6 @@ /* DO NOT EDIT, file generated by nestjs-i18n */ +/* eslint-disable */ /* prettier-ignore */ import type { Path } from "nestjs-i18n"; /* prettier-ignore */ diff --git a/template/nest-next/libs/shared/src/guard/reject.guard.ts b/template/nest-next/libs/shared/src/guard/reject.guard.ts index e02fd5c0..1c9664fc 100644 --- a/template/nest-next/libs/shared/src/guard/reject.guard.ts +++ b/template/nest-next/libs/shared/src/guard/reject.guard.ts @@ -9,6 +9,7 @@ import { Reflector } from '@nestjs/core'; import { I18nTranslations } from '../.generate/i18n.generated'; import { I18nContext } from 'nestjs-i18n'; import { ConfigureService } from '@app/configure'; +import { DomainError } from '../error.base'; @Injectable() export class RejectRequestGuard implements CanActivate { @@ -31,11 +32,15 @@ export class RejectRequestGuard implements CanActivate { if (!i18n) { return Promise.resolve(false); } - throw new HttpException( - i18n.t('exception.preview.REJECT_THIS_REQUEST', { - lang: I18nContext?.current()?.lang || 'enUS', - }), - HttpStatus.BAD_REQUEST, - ); + throw new RejectRequest(); + } +} + +export class RejectRequest extends DomainError { + constructor() { + super({ + message: 'exception.preview.REJECT_THIS_REQUEST', + code: HttpStatus.BAD_REQUEST, + }) } } diff --git a/template/nest-next/src/menu/menu.module.ts b/template/nest-next/src/menu/menu.module.ts index 63c4d76f..ccacd648 100644 --- a/template/nest-next/src/menu/menu.module.ts +++ b/template/nest-next/src/menu/menu.module.ts @@ -6,12 +6,18 @@ import { RemoveMenuCommandHandler, UpdateMenuCommandHandler, } from './commands'; -import { FindAllMenuQueryHandler, FindMenuHandler } from './queries'; +import { + FindAllMenuQueryHandler, + FindMenuHandler, + GetUserMenuService, +} from './queries'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { Menu } from './menu.entity'; +import { User } from '../user'; +import { RoleMenu } from '../role'; @Module({ - imports: [MikroOrmModule.forFeature([Menu])], + imports: [MikroOrmModule.forFeature([Menu, User, RoleMenu])], controllers: [MenuController], providers: [ MenuService, @@ -20,6 +26,7 @@ import { Menu } from './menu.entity'; UpdateMenuCommandHandler, FindAllMenuQueryHandler, FindMenuHandler, + GetUserMenuService, ], }) export class MenuModule {} diff --git a/template/nest-next/src/menu/menu.service.ts b/template/nest-next/src/menu/menu.service.ts index dccace11..026d7314 100644 --- a/template/nest-next/src/menu/menu.service.ts +++ b/template/nest-next/src/menu/menu.service.ts @@ -3,10 +3,8 @@ import { CreateMenuDto } from './dto/create-menu.dto'; import { UpdateMenuDto } from './dto/update-menu.dto'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { CreateMenu, RemoveMenu, UpdateMenu } from './commands'; -import { convertToTree, FindAllMenu } from './queries'; +import { FindAllMenu, GetUserMenu } from './queries'; import { MenuId } from './menu.entity'; -import { GetUserInfo } from '../user/query'; -import { UserNotFound } from '../user'; @Injectable() export class MenuService { @@ -15,18 +13,7 @@ export class MenuService { private readonly cb: CommandBus, ) {} async findUserMenu(email: string) { - const [userInfo] = await this.cb.execute( - new GetUserInfo({ email: [email] }), - ); - if (!userInfo) { - throw new UserNotFound(); - } - const menus = userInfo.role.flatMap((role) => role.menus); - const maps = {}; - menus.forEach((menu) => { - maps[menu.id] = menu; - }); - return convertToTree(Object.values(maps)); + return this.qb.execute(new GetUserMenu({ email })); } create(createMenuDto: CreateMenuDto) { return this.cb.execute(new CreateMenu(createMenuDto)); diff --git a/template/nest-next/src/menu/queries/get-user-menu.query.ts b/template/nest-next/src/menu/queries/get-user-menu.query.ts new file mode 100644 index 00000000..8e0eb687 --- /dev/null +++ b/template/nest-next/src/menu/queries/get-user-menu.query.ts @@ -0,0 +1,63 @@ +import { IQueryHandler, Query, QueryHandler } from '@nestjs/cqrs'; +import { InjectRepository } from '@mikro-orm/nestjs'; +import { EntityRepository, FilterQuery } from '@mikro-orm/core'; +import { convertToTree, Menu } from '../../menu'; +import { User, UserId, UserNotFound } from '../../user'; +import { TreeNode } from '../types'; +import { RoleMenu } from '../../role'; + +export type GetUserInfoRequest = + | { + email: string; + id?: never; + } + | { + email?: never; + id: UserId; + }; + +export class GetUserMenu extends Query { + constructor(public readonly request: GetUserInfoRequest) { + super(); + } +} + +@QueryHandler(GetUserMenu) +export class GetUserMenuService implements IQueryHandler { + constructor( + @InjectRepository(User) + private readonly userRepository: EntityRepository, + @InjectRepository(RoleMenu) + private readonly roleMenuRepo: EntityRepository, + @InjectRepository(Menu) + private readonly menuRepo: EntityRepository, + ) {} + async execute(query: GetUserMenu): Promise { + const whereCondition: FilterQuery = {}; + if (query.request.email) { + whereCondition['email'] = query.request.email; + } + if (query.request.id) { + whereCondition['id'] = query.request.id; + } + const user = await this.userRepository.findOne( + { + ...whereCondition, + }, + { populate: ['role'] }, + ); + if (!user) { + throw new UserNotFound(); + } + const roles = user.role; + const roleIds = roles.map((role) => role.roleId); + const roleMenus = await this.roleMenuRepo.find({ + roleId: { $in: roleIds }, + }); + const menuIds = roleMenus.map((menu) => menu.menuId); + const menus = await this.menuRepo.find({ + id: { $in: menuIds }, + }); + return convertToTree(menus); + } +} diff --git a/template/nest-next/src/menu/queries/index.ts b/template/nest-next/src/menu/queries/index.ts index 714848d0..ff003e03 100644 --- a/template/nest-next/src/menu/queries/index.ts +++ b/template/nest-next/src/menu/queries/index.ts @@ -1,2 +1,3 @@ export * from './find-all-menu.query'; export * from './find-menu.query'; +export * from './get-user-menu.query'; diff --git a/template/nest-next/src/permission/dto/create-permission.dto.ts b/template/nest-next/src/permission/dto/create-permission.dto.ts index 23a64c14..b5ec82f1 100644 --- a/template/nest-next/src/permission/dto/create-permission.dto.ts +++ b/template/nest-next/src/permission/dto/create-permission.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty } from 'class-validator'; +import { IsNotEmpty, ValidateIf } from 'class-validator'; import { i18nValidationMessage } from 'nestjs-i18n'; import { I18nTranslations } from '../../.generate/i18n.generated'; import { ApiProperty } from '@nestjs/swagger'; diff --git a/template/nest-next/src/permission/queries/user-has-permission.query.ts b/template/nest-next/src/permission/queries/user-has-permission.query.ts index 981c9900..8d571e3e 100644 --- a/template/nest-next/src/permission/queries/user-has-permission.query.ts +++ b/template/nest-next/src/permission/queries/user-has-permission.query.ts @@ -40,24 +40,24 @@ export class UserHasPermissionService implements IQueryHandler toRoleId(item.id)) }, - }); + const rolePermissions = await this.rolePermissionRepository.find( + { + roleId: { $in: roles.map((item) => toRoleId(item.roleId)) }, + }, + { logging: { enabled: true, debugMode: ['query'] } }, + ); const rolePermissionIds = rolePermissions.map((item) => item.permissionId); const permissions = await this.permissionRepository.find({ id: { $in: rolePermissionIds }, }); - if (!permissions.length) { return false; } - if (permissions.find((perm) => perm.name === '*')) { return true; } return permissionName.every( (name) => permissions.find((perm) => perm.name === name) !== undefined, ); - // return permissions.every((perm) => permissionName.includes(perm.name)); } } diff --git a/template/nest-next/src/role/assembler/find-role.ts b/template/nest-next/src/role/assembler/find-role.ts index 3d14245d..327524cc 100644 --- a/template/nest-next/src/role/assembler/find-role.ts +++ b/template/nest-next/src/role/assembler/find-role.ts @@ -12,7 +12,7 @@ export const assembleFindRole = ( return new FindRoleResponse({ id: role.id, name: role.name, - permissions: [...permission], + permission: [...permission], menus: menuTree, }); }; diff --git a/template/nest-next/src/role/dto/find-role.dto.ts b/template/nest-next/src/role/dto/find-role.dto.ts index d0700ae7..25910e6b 100644 --- a/template/nest-next/src/role/dto/find-role.dto.ts +++ b/template/nest-next/src/role/dto/find-role.dto.ts @@ -48,7 +48,7 @@ export class FindRoleResponse { @ApiProperty({ description: '角色名称' }) name: string; @ApiProperty({ description: '角色所拥有的权限', type: [FindRolePermission] }) - permissions: FindRolePermission[]; + permission: FindRolePermission[]; @ApiProperty({ description: '角色所拥有的菜单', type: [FindRoleMenu] }) menus: FindRoleMenu[]; constructor(props: FindRoleResponse) { diff --git a/template/nest-next/src/role/dto/get-role-detail.dto.ts b/template/nest-next/src/role/dto/get-role-detail.dto.ts index 99b4eec4..b896e6a9 100644 --- a/template/nest-next/src/role/dto/get-role-detail.dto.ts +++ b/template/nest-next/src/role/dto/get-role-detail.dto.ts @@ -55,13 +55,13 @@ export class GetRoleInfo { @ApiProperty({ type: [RoleInfoItem], }) - item: RoleInfoItem[]; + items: RoleInfoItem[]; @ApiProperty({ type: PaginationMeta, }) meta: PaginationMeta; constructor(item: RoleInfoItem[], meta: PaginationMeta) { - this.item = item; + this.items = item; this.meta = meta; } } diff --git a/template/tinyvue/src/views/menu/info/components/info-tab.vue b/template/tinyvue/src/views/menu/info/components/info-tab.vue index c617eaf6..cfe8cbb2 100644 --- a/template/tinyvue/src/views/menu/info/components/info-tab.vue +++ b/template/tinyvue/src/views/menu/info/components/info-tab.vue @@ -122,8 +122,7 @@ function onDelete(data: Node) { if (node.parentId === null) { node.parentId = -1 } - - deleteMenu(Number(node.id.toString()), node.parentId) + deleteMenu(node.id.toString(), node.parentId) .then(() => { TinyModal.message({ message: '删除成功', From fe9daf88cbaf759de84580808af78e409322008b Mon Sep 17 00:00:00 2001 From: GaoNeng-wWw Date: Mon, 20 Jul 2026 14:41:00 +0800 Subject: [PATCH 02/28] fix(menu): update menu --- template/nest-next/src/auth/auth.guard.ts | 25 ++++++++++--------- .../nest-next/src/menu/menu.controller.ts | 2 +- .../views/menu/info/components/add-menu.vue | 4 +-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/template/nest-next/src/auth/auth.guard.ts b/template/nest-next/src/auth/auth.guard.ts index 2f973e71..e1befa22 100644 --- a/template/nest-next/src/auth/auth.guard.ts +++ b/template/nest-next/src/auth/auth.guard.ts @@ -29,19 +29,20 @@ export class AuthGuard implements CanActivate { return false; } try { - const payload = this.jwtService.verify(token); + const payload = this.jwtService.decode(token); req['user'] = payload; - if ('type' in payload && payload.type === 'api') { - const isValidApiToken = await this.apiTokenService.validateApiToken( - payload.email, - token, - ); - if (!isValidApiToken) { - throw new InvalidToken(); - } - return true; - } - await this.authService.tokenAlive(token); + // if ('type' in payload && payload.type === 'api') { + // const isValidApiToken = await this.apiTokenService.validateApiToken( + // payload.email, + // token, + // ); + // if (!isValidApiToken) { + // throw new InvalidToken(); + // } + // return true; + // } + // await this.authService.tokenAlive(token); + return true; } catch { throw new InvalidToken(); } diff --git a/template/nest-next/src/menu/menu.controller.ts b/template/nest-next/src/menu/menu.controller.ts index 81782c13..8ac02519 100644 --- a/template/nest-next/src/menu/menu.controller.ts +++ b/template/nest-next/src/menu/menu.controller.ts @@ -53,7 +53,7 @@ export class MenuController { @ApiOperation({ summary: '菜单修改' }) @ApiOkResponse({ type: UpdateMenuResponse }) - @Patch(':id') + @Patch('') @Reject() @Permission('menu::update') update(@Body() updateMenuDto: UpdateMenuDto) { diff --git a/template/tinyvue/src/views/menu/info/components/add-menu.vue b/template/tinyvue/src/views/menu/info/components/add-menu.vue index 73a3c739..2dbbf024 100644 --- a/template/tinyvue/src/views/menu/info/components/add-menu.vue +++ b/template/tinyvue/src/views/menu/info/components/add-menu.vue @@ -20,7 +20,7 @@ const props = defineProps<{ interface TreeSelectMenu { label: string - id: number + id: string; children: TreeSelectMenu[] } @@ -52,7 +52,7 @@ function cover(data: ITreeNodeData[]) { const dfs = (menu: ITreeNodeData) => { const ret: TreeSelectMenu = { label: menu.label, - id: Number(menu.id), + id: menu.id.toString(), children: [], } for (let i = 0; i < menu.children.length; i += 1) { From cfcaac8301496c565c9ce81ae9c50d6b064eeb23 Mon Sep 17 00:00:00 2001 From: GaoNeng-wWw Date: Mon, 20 Jul 2026 20:05:28 +0800 Subject: [PATCH 03/28] fix(role): permission update --- .../queries/get-all-permissions.query.ts | 10 ++++++---- .../src/role/commands/update-role.command.ts | 14 ++++++++++++-- template/nest-next/src/role/dto/create-role.dto.ts | 4 ++-- .../src/role/queries/find-all-role.query.ts | 2 +- .../src/role/queries/get-role-total.query.ts | 11 ++++++++--- template/nest-next/src/role/role.service.ts | 1 + template/tinyvue/src/api/role.ts | 2 +- template/tinyvue/src/store/modules/user/types.ts | 6 +++--- .../src/views/role/info/components/add-role.vue | 4 ++-- .../src/views/role/info/components/info-tab.vue | 2 +- .../src/views/role/info/components/role-table.vue | 13 +++++++++---- 11 files changed, 46 insertions(+), 23 deletions(-) diff --git a/template/nest-next/src/permission/queries/get-all-permissions.query.ts b/template/nest-next/src/permission/queries/get-all-permissions.query.ts index 8475bab7..e62dee9c 100644 --- a/template/nest-next/src/permission/queries/get-all-permissions.query.ts +++ b/template/nest-next/src/permission/queries/get-all-permissions.query.ts @@ -31,10 +31,12 @@ export class GetAllPermissionQueryHandler implements IQueryHandler { ) {} async execute(command: UpdateRoleCommand): Promise { const { id, name } = command; - const role = await this.role.findOne(id); + const role = await this.role.findOne({ id }); if (!role) { throw new RoleNotFound(); } @@ -39,13 +39,23 @@ export class UpdateRoleHandler implements ICommandHandler { role.name = name; } if (command.permissionIds) { + await this.rolePermission.nativeDelete( + { + roleId: id, + }, + { logging: { debugMode: ['info'], enabled: true } }, + ); command.permissionIds .map((pm) => { return this.rolePermission.create({ roleId: id, permissionId: pm }); }) - .forEach((perm) => this.em.persist(perm)); + .map((pm) => this.em.persist(pm)); } if (command.menuIds) { + await this.roleMenu.nativeDelete( + { roleId: id }, + { logging: { debugMode: ['info'], enabled: true } }, + ); command.menuIds .map((menuId) => this.roleMenu.create({ roleId: id, menuId })) .forEach((menu) => this.em.persist(menu)); diff --git a/template/nest-next/src/role/dto/create-role.dto.ts b/template/nest-next/src/role/dto/create-role.dto.ts index c30ba029..30aad845 100644 --- a/template/nest-next/src/role/dto/create-role.dto.ts +++ b/template/nest-next/src/role/dto/create-role.dto.ts @@ -15,12 +15,12 @@ export class CreateRoleDto { }) @IsArray() @IsNotEmpty() - permissionIds: PermissionId[] = []; + permissionIds: PermissionId[]; @ApiProperty({ description: '菜单ID', type: [String], }) @IsArray() @IsNotEmpty() - menuIds: MenuId[] = []; + menuIds: MenuId[]; } diff --git a/template/nest-next/src/role/queries/find-all-role.query.ts b/template/nest-next/src/role/queries/find-all-role.query.ts index 949a857c..ebddfd34 100644 --- a/template/nest-next/src/role/queries/find-all-role.query.ts +++ b/template/nest-next/src/role/queries/find-all-role.query.ts @@ -23,7 +23,7 @@ export class FindAllRoleHandler implements IQueryHandler { const filter = query.page && query.limit ? { - page: query.page, + offset: (query.page - 1) * query.limit, limit: query.limit, } : {}; diff --git a/template/nest-next/src/role/queries/get-role-total.query.ts b/template/nest-next/src/role/queries/get-role-total.query.ts index 396f1621..f86e61e7 100644 --- a/template/nest-next/src/role/queries/get-role-total.query.ts +++ b/template/nest-next/src/role/queries/get-role-total.query.ts @@ -24,9 +24,14 @@ export class GetRoleTotalHandler implements IQueryHandler { } async execute(query: GetRoleTotal): Promise { if (!query.name) { - const cnt = await this.roleRepo.count({ - name: query.name ? { $like: `${query.name}` } : undefined, - }); + const cnt = await this.roleRepo.count( + query.name + ? { + name: { $like: `${query.name}` }, + } + : {}, + ); + await this.redis.set(roleTotal(), cnt); return cnt; } return this.redis.get(roleTotal()).then((val) => (val ? Number(val) : 1)); diff --git a/template/nest-next/src/role/role.service.ts b/template/nest-next/src/role/role.service.ts index 66781c50..763d6653 100644 --- a/template/nest-next/src/role/role.service.ts +++ b/template/nest-next/src/role/role.service.ts @@ -91,6 +91,7 @@ export class RoleService { ]); const permMap = new Map(permissions.map((p) => [p.id, p])); const menuMap = new Map(menus.map((m) => [m.id, m])); + const roleDetail = assembleGetDetail( roles, permissionIdMap, diff --git a/template/tinyvue/src/api/role.ts b/template/tinyvue/src/api/role.ts index fcff5a26..4d9bbd76 100644 --- a/template/tinyvue/src/api/role.ts +++ b/template/tinyvue/src/api/role.ts @@ -32,7 +32,7 @@ export function updateRole(data: any) { return axios.patch(`${import.meta.env.VITE_BASE_API}/role`, data) } -export function deleteRole(id: number) { +export function deleteRole(id: string) { return axios.delete(`${import.meta.env.VITE_BASE_API}/role/${id}`) } diff --git a/template/tinyvue/src/store/modules/user/types.ts b/template/tinyvue/src/store/modules/user/types.ts index 0ea7c9c9..dc9499d8 100644 --- a/template/tinyvue/src/store/modules/user/types.ts +++ b/template/tinyvue/src/store/modules/user/types.ts @@ -1,13 +1,13 @@ export type RoleType = '' | '*' | 'admin' | 'user' export interface Role { - id: number + id: string name: string permission: { name: string desc: string - id: number + id: string }[] - menus: { id: number, name: string }[] + menus: { id: string, name: string }[] } export interface UserInfo { id: string diff --git a/template/tinyvue/src/views/role/info/components/add-role.vue b/template/tinyvue/src/views/role/info/components/add-role.vue index 0c0c13e1..42fbf9e0 100644 --- a/template/tinyvue/src/views/role/info/components/add-role.vue +++ b/template/tinyvue/src/views/role/info/components/add-role.vue @@ -76,6 +76,7 @@ defineExpose({ }, onConfirm, }) + - diff --git a/template/tinyvue/src/views/role/info/components/info-tab.vue b/template/tinyvue/src/views/role/info/components/info-tab.vue index 0f62da48..4a6b2621 100644 --- a/template/tinyvue/src/views/role/info/components/info-tab.vue +++ b/template/tinyvue/src/views/role/info/components/info-tab.vue @@ -58,7 +58,7 @@ getAllMenu() setLoading(false) }) getAllPermission().then(({ data }) => { - permissions.value = data + permissions.value = data.items }) const pagerConfigSm = { component: TinyPager, diff --git a/template/tinyvue/src/views/role/info/components/role-table.vue b/template/tinyvue/src/views/role/info/components/role-table.vue index 90e4a542..154169ae 100644 --- a/template/tinyvue/src/views/role/info/components/role-table.vue +++ b/template/tinyvue/src/views/role/info/components/role-table.vue @@ -49,7 +49,7 @@ function onMenuUpdate(data: ITreeNodeData[], roldId: number, role: Role) { emits('menuUpdate', data, roldId, role) } -function onRoleDelete(id: number, row) { +function onRoleDelete(id: string, row) { setLoading(true) deleteRole(id) .then(() => { @@ -86,8 +86,12 @@ function getPermission(row: any) { } function onUpdate(args: any) { const menuIds = args.row.menus.map(menu => menu.id) + const permissionIds = args.row.permissionIds.map(item => item) + console.log(permissionIds); updateRole({ - ...args.row, + id: args.row.id, + name: args.row.name, + permissionIds, menuIds, }) .then(() => { @@ -154,9 +158,10 @@ defineExpose({ 'collapse-tags': true, 'value-key': 'id', 'options': props.permissions, - 'textField': 'name', - 'valueField': 'id', + 'text-field': 'name', + 'value-field': 'id', }, + events:{change: onPermissionChange} }" >