fix: 删除菜单包括子级

This commit is contained in:
2026-07-09 13:52:53 +08:00
parent 5749b95327
commit 5486f6e79d
11 changed files with 75 additions and 22 deletions

View File

@@ -40,7 +40,7 @@
<!-- RPC 相关 -->
<!-- Config 配置中心相关 -->
<!-- Job 定时任务相关 -->
<xxl-job.version>2.4.0</xxl-job.version>
<xxl-job.version>2.4.2</xxl-job.version>
<!-- 服务保障相关 -->
<lock4j.version>2.2.7</lock4j.version>
<!-- 监控相关 -->

View File

@@ -95,7 +95,10 @@ spring:
xxl:
job:
admin:
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
addresses: http://192.168.10.14:8080/xxl-job-admin # 调度中心部署跟地址
executor:
ip: 192.168.10.14
port: 9991
--- #################### 服务保障相关配置 ####################

View File

@@ -124,7 +124,7 @@ xxl:
executor:
appname: ${spring.application.name} # 执行器 AppName
logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
accessToken: default_token # 执行器通讯TOKEN
accessToken: 123456 # 执行器通讯TOKEN
--- #################### 芋道相关配置 ####################

View File

@@ -52,8 +52,9 @@ public class MenuController {
@Operation(summary = "删除菜单")
@Parameter(name = "id", description = "菜单编号", required= true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:menu:delete')")
public CommonResult<Boolean> deleteMenu(@RequestParam("id") Long id) {
menuService.deleteMenu(id);
public CommonResult<Boolean> deleteMenu(@RequestParam("id") Long id,
@RequestParam(value = "cascade", defaultValue = "false") boolean cascade) {
menuService.deleteMenu(id, cascade);
return success(true);
}

View File

@@ -32,9 +32,10 @@ public interface MenuService {
/**
* 删除菜单
*
* @param id 菜单编号
* @param id 菜单编号
* @param cascade 是否级联删除子菜单
*/
void deleteMenu(Long id);
void deleteMenu(Long id, boolean cascade);
/**
* 批量删除菜单

View File

@@ -89,21 +89,43 @@ public class MenuServiceImpl implements MenuService {
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
allEntries = true) // allEntries 清空所有缓存,因为此时不知道 id 对应的 permission 是多少。直接清理,简单有效
public void deleteMenu(Long id) {
// 校验是否还有子菜单
if (menuMapper.selectCountByParentId(id) > 0) {
throw exception(MENU_EXISTS_CHILDREN);
}
public void deleteMenu(Long id, boolean cascade) {
// 校验删除的菜单是否存在
if (menuMapper.selectById(id) == null) {
throw exception(MENU_NOT_EXISTS);
}
// 校验是否还有子菜单
if (menuMapper.selectCountByParentId(id) > 0) {
if (!cascade) {
throw exception(MENU_EXISTS_CHILDREN);
}
// 级联删除:递归收集所有子菜单 ID
List<Long> childIds = new ArrayList<>();
collectChildMenuIds(id, childIds);
// 先删除子菜单,再删父菜单
menuMapper.deleteByIds(childIds);
childIds.forEach(childId -> permissionService.processMenuDeleted(childId));
}
// 标记删除
menuMapper.deleteById(id);
// 删除授予给角色的权限
permissionService.processMenuDeleted(id);
}
/**
* 递归收集所有子菜单 ID
*
* @param parentId 父菜单 ID
* @param result 收集结果
*/
private void collectChildMenuIds(Long parentId, List<Long> result) {
List<MenuDO> children = menuMapper.selectList(MenuDO::getParentId, parentId);
for (MenuDO child : children) {
result.add(child.getId());
collectChildMenuIds(child.getId(), result);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,

View File

@@ -96,6 +96,9 @@ xxl:
job:
admin:
addresses: http://192.168.10.14:8080/xxl-job-admin # 调度中心部署跟地址
executor:
ip: 192.168.10.14
port: 9992
--- #################### 服务保障相关配置 ####################

View File

@@ -124,7 +124,7 @@ xxl:
executor:
appname: ${spring.application.name} # 执行器 AppName
logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
accessToken: default_token # 执行器通讯TOKEN
accessToken: 123456 # 执行器通讯TOKEN
--- #################### 验证码相关配置 ####################

View File

@@ -71,6 +71,10 @@ xxl:
enabled: false # 是否开启调度中心,默认为 true 开启
admin:
addresses: http://192.168.10.14:8080/xxl-job-admin # 调度中心部署跟地址
executor:
appname: nl-lms-server
ip: 192.168.10.14
port: 9992
--- #################### 消息队列相关 ####################

View File

@@ -49,8 +49,8 @@ export async function updateMenu(data: SystemMenuApi.Menu) {
}
/** 删除菜单 */
export async function deleteMenu(id: number) {
return requestClient.delete(`/system/menu/delete?id=${id}`);
export async function deleteMenu(id: number, cascade?: boolean) {
return requestClient.delete(`/system/menu/delete?id=${id}&cascade=${cascade || false}`);
}
/** 批量删除菜单 */

View File

@@ -8,7 +8,7 @@ import { Page, useVbenModal } from '@vben/common-ui';
import { SystemMenuTypeEnum } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
import { message } from 'antdv-next';
import { message, Modal } from 'antdv-next';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteMenu, getMenuList } from '#/api/system/menu';
@@ -43,13 +43,35 @@ function handleEdit(row: SystemMenuApi.Menu) {
}
/** 删除菜单 */
async function handleDelete(row: SystemMenuApi.Menu) {
function handleDelete(row: SystemMenuApi.Menu) {
const hasChildren = (row as any).children?.length > 0;
if (hasChildren) {
Modal.confirm({
title: '提示',
content: '该菜单存在子菜单,是否连子级一起删除?',
okText: '是',
cancelText: '否',
onOk: () => doDelete(row, true),
});
} else {
Modal.confirm({
title: '提示',
content: $t('ui.actionMessage.deleteConfirm', [row.name]),
okText: '确认',
cancelText: '取消',
onOk: () => doDelete(row, false),
});
}
}
/** 执行菜单删除 */
async function doDelete(row: SystemMenuApi.Menu, cascade: boolean) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
});
try {
await deleteMenu(row.id!);
await deleteMenu(row.id!, cascade);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
@@ -161,10 +183,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['system:menu:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
onClick: handleDelete.bind(null, row),
},
]"
/>