From 1737563516a9049ab5e40582ca2e524a7071190f Mon Sep 17 00:00:00 2001 From: xiong Date: Thu, 16 Jul 2026 10:16:56 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/admin/src/pages/Users/index.tsx | 21 +---------------- apps/admin/src/test/fixtures.ts | 2 +- apps/server/src/rbac/rbac.controller.ts | 22 ------------------ apps/server/src/rbac/rbac.service.ts | 30 ++++++++++++++++--------- 技术文档.md | 3 ++- 5 files changed, 23 insertions(+), 55 deletions(-) diff --git a/apps/admin/src/pages/Users/index.tsx b/apps/admin/src/pages/Users/index.tsx index b6684d1..a4802b2 100644 --- a/apps/admin/src/pages/Users/index.tsx +++ b/apps/admin/src/pages/Users/index.tsx @@ -10,7 +10,7 @@ import { Tag, Popconfirm, } from 'antd'; -import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, IdcardOutlined, InboxOutlined } from '@ant-design/icons'; +import { PlusOutlined, EditOutlined, KeyOutlined, IdcardOutlined, InboxOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import api from '../../api'; import PermissionButton from '../../components/PermissionButton'; @@ -141,18 +141,6 @@ const UsersPage: React.FC = () => { } }; - const handleDelete = async (id: number) => { - try { - await api.delete(`/rbac/users/${id}`); - message.success('已删除'); - fetchData(); - } catch (e: unknown) { - const err = e as { message?: string }; - message.error(err?.message || '删除失败'); - } - }; - - const handleResetPwd = (record: any) => { setResetTarget(record); pwdForm.resetFields(); @@ -253,13 +241,6 @@ const UsersPage: React.FC = () => { 归档 )} - {record.username !== 'admin' && ( - handleDelete(record.id)}> - }> - 删除 - - - )} ), }, diff --git a/apps/admin/src/test/fixtures.ts b/apps/admin/src/test/fixtures.ts index 9d54a01..fb0aa9f 100644 --- a/apps/admin/src/test/fixtures.ts +++ b/apps/admin/src/test/fixtures.ts @@ -220,6 +220,6 @@ export const PERMISSION_NODES = [ 'report:generate', 'log:view', 'role:view', 'role:add', 'role:update', 'role:delete', - 'user:view', 'user:add', 'user:update', 'user:delete', + 'user:view', 'user:add', 'user:update', 'dashboard:view', ] as const; diff --git a/apps/server/src/rbac/rbac.controller.ts b/apps/server/src/rbac/rbac.controller.ts index 95b022d..6aa95e2 100644 --- a/apps/server/src/rbac/rbac.controller.ts +++ b/apps/server/src/rbac/rbac.controller.ts @@ -198,28 +198,6 @@ export class RbacController { } } - @Delete('users/:id') - @RequirePermission('user:delete') - async deleteUser(@Param('id') id: string, @Request() req: any) { - const { ipAddress, userAgent } = extractRequestInfo(req); - try { - const result = await this.rbacService.deleteUser(+id); - await this.logService.log({ - userId: req.user?.id, - username: req.user?.username, - module: '账号', - action: '删除账号', - targetId: +id, - targetType: 'user', - ipAddress, - userAgent, - }); - return result; - } catch (e: any) { - throw new BadRequestException(e.message); - } - } - @Put('users/:id/archive') @RequirePermission('user:edit') async archiveUser(@Param('id') id: string) { diff --git a/apps/server/src/rbac/rbac.service.ts b/apps/server/src/rbac/rbac.service.ts index 4343dda..efc3e79 100644 --- a/apps/server/src/rbac/rbac.service.ts +++ b/apps/server/src/rbac/rbac.service.ts @@ -70,7 +70,6 @@ const PRESET_PERMISSIONS: Array<{ code: string; name: string; group: string }> = { code: 'user:view', name: '查看用户', group: 'user' }, { code: 'user:create', name: '创建用户', group: 'user' }, { code: 'user:edit', name: '编辑用户', group: 'user' }, - { code: 'user:delete', name: '删除用户', group: 'user' }, { code: 'user:reset-password', name: '重置密码', group: 'user' }, { code: 'role:view', name: '查看角色', group: 'role' }, { code: 'role:create', name: '创建角色', group: 'role' }, @@ -250,7 +249,12 @@ export class RbacService { await this.permRepo.save(this.permRepo.create(p)); } } - const allPerms = await this.permRepo.find(); + const deprecatedUserDeletePermission = await this.permRepo.findOne({ + where: { code: 'user:delete' }, + }); + const allPerms = (await this.permRepo.find()).filter( + (permission) => permission.code !== 'user:delete', + ); // Step 2: 幂等插入预置角色 for (const r of PRESET_ROLES) { @@ -271,6 +275,19 @@ export class RbacService { } const allRoles = await this.roleRepo.find({ relations: ['permissions', 'users'] }); + if (deprecatedUserDeletePermission) { + for (const role of allRoles) { + const permissions = role.permissions ?? []; + if (permissions.some((permission) => permission.id === deprecatedUserDeletePermission.id)) { + role.permissions = permissions.filter( + (permission) => permission.id !== deprecatedUserDeletePermission.id, + ); + await this.roleRepo.save(role); + } + } + await this.permRepo.remove(deprecatedUserDeletePermission); + } + // Step 3: 合并旧角色并构建新的职责权限矩阵 for (const preset of PRESET_ROLES) { const matchesPreset = (role: Role) => @@ -564,15 +581,6 @@ export class RbacService { return { message: '已恢复为学员' }; } - async deleteUser(id: number) { - const user = await this.userRepo.findOne({ where: { id } }); - if (!user) throw new Error('用户不存在'); - if (user.username === 'admin') throw new Error('不能删除默认管理员'); - if (!user.isArchived) throw new Error('请先归档再删除'); - await this.userRepo.remove(user); - return { message: '用户已删除' }; - } - // ---- 用户资料 ---- async getUserProfile(id: number) { diff --git a/技术文档.md b/技术文档.md index cdf614c..13c3d69 100644 --- a/技术文档.md +++ b/技术文档.md @@ -169,7 +169,8 @@ | GET | /api/auth/users | 用户列表 | | PUT | /api/auth/users/:id | 编辑用户 | | PUT | /api/auth/users/:id/password | 重置密码 | -| DELETE | /api/auth/users/:id | 删除用户 | +| PUT | /api/rbac/users/:id/archive | 归档用户 | +| PUT | /api/rbac/users/:id/restore | 恢复用户 | ### 业务模块 | 方法 | 路径 | 说明 | From 200d2e423b68cb76f4347dfd9e570b07e793a187 Mon Sep 17 00:00:00 2001 From: xiong Date: Thu, 16 Jul 2026 10:44:21 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=BD=92=E6=A1=A3?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/admin/src/pages/Classes/index.tsx | 15 ----- apps/admin/src/pages/Organizations/index.tsx | 60 ++++++++++++++------ 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/apps/admin/src/pages/Classes/index.tsx b/apps/admin/src/pages/Classes/index.tsx index 85d15f1..a815c2d 100644 --- a/apps/admin/src/pages/Classes/index.tsx +++ b/apps/admin/src/pages/Classes/index.tsx @@ -154,16 +154,6 @@ const ClassesPage: React.FC = () => { } }; - const handleDelete = async (id: number) => { - try { - await api.delete(`/classes/${id}`); - message.success('已删除'); - fetchData(); - } catch (e: any) { - message.error(e?.message || '删除失败'); - } - }; - const columns: ColumnsType = useMemo(() => [ { title: '班级名称', dataIndex: 'name', width: 120, @@ -208,11 +198,6 @@ const ClassesPage: React.FC = () => { 归档 )} - handleDelete(r.id)}> - - 删除 - - ), }, diff --git a/apps/admin/src/pages/Organizations/index.tsx b/apps/admin/src/pages/Organizations/index.tsx index 0ba4775..f449de8 100644 --- a/apps/admin/src/pages/Organizations/index.tsx +++ b/apps/admin/src/pages/Organizations/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { Alert, Empty, Form, Input, Modal, Popconfirm, Select, Space, Table, Tag } from 'antd'; -import { BankOutlined, InboxOutlined, PlusOutlined } from '@ant-design/icons'; +import { BankOutlined, InboxOutlined, PlusOutlined, UndoOutlined } from '@ant-design/icons'; import api from '../../api'; import PermissionButton from '../../components/PermissionButton'; import { message } from '../../ui/app-message'; @@ -148,35 +148,61 @@ const OrganizationsPage: React.FC = () => { width: 160, render: (_: unknown, record: OrganizationItem) => ( - openEditor(record)} - > - 编辑 - - {!record.isHost && record.status === 'active' ? ( + {record.status === 'archived' ? ( { try { - await api.delete(`/organizations/${record.id}`); - message.success('机构已归档'); + await api.put(`/organizations/${record.id}`, { status: 'active' }); + message.success('机构已恢复'); await fetchData(); } catch (error: any) { - message.error(error?.message || '归档失败'); + message.error(error?.message || '恢复失败'); } }} > } + type="link" + icon={} > - 归档 + 恢复 - ) : null} + ) : ( + <> + openEditor(record)} + > + 编辑 + + {!record.isHost ? ( + { + try { + await api.delete(`/organizations/${record.id}`); + message.success('机构已归档'); + await fetchData(); + } catch (error: any) { + message.error(error?.message || '归档失败'); + } + }} + > + } + > + 归档 + + + ) : null} + + )} ), },