fix(system): 修复菜单树半选父节点权限丢失

- 回显时还原完整选中节点,提交时补齐祖先菜单
- 去重 Tree 级联选择产生的重复节点
- 对齐五端租户套餐菜单接口与详情回填
- 修复五端租户套餐及角色菜单授权
- 补全租户套餐详情 API 返回类型
- 增加 Tree 工具和组件回归测试
This commit is contained in:
YunaiV
2026-07-11 19:07:37 +08:00
parent c96cf0ead9
commit b5029a0b67
19 changed files with 532 additions and 47 deletions

View File

@@ -27,7 +27,9 @@ export function getTenantPackagePage(params: PageParam) {
/** 查询租户套餐详情 */
export function getTenantPackage(id: number) {
return requestClient.get(`/system/tenant-package/get?id=${id}`);
return requestClient.get<SystemTenantPackageApi.TenantPackage>(
`/system/tenant-package/get?id=${id}`,
);
}
/** 新增租户套餐 */

View File

@@ -8,7 +8,11 @@ import { nextTick, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { SystemMenuTypeEnum } from '@vben/constants';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { Checkbox, message, Spin } from 'ant-design-vue';
@@ -52,7 +56,11 @@ const [Modal, modalApi] = useVbenModal({
try {
await assignRoleMenu({
roleId: data.id,
menuIds: data.menuIds,
menuIds: getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
),
});
// 关闭并提示
await modalApi.close();
@@ -76,7 +84,10 @@ const [Modal, modalApi] = useVbenModal({
try {
// 加载角色菜单
const menuIds = await getRoleMenuList(data.id);
await formApi.setFieldValue('menuIds', menuIds);
await formApi.setFieldValue(
'menuIds',
getTreeCheckedValues(menuTree.value, menuIds, (menu) => menu.id),
);
await formApi.setValues(data);
} finally {

View File

@@ -5,12 +5,16 @@ import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
import { computed, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { Checkbox, message, Spin } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { getMenuList } from '#/api/system/menu';
import { getSimpleMenusList } from '#/api/system/menu';
import {
createTenantPackage,
getTenantPackage,
@@ -56,6 +60,11 @@ const [Modal, modalApi] = useVbenModal({
// 提交表单
const data =
(await formApi.getValues()) as SystemTenantPackageApi.TenantPackage;
data.menuIds = getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
);
try {
await (formData.value
? updateTenantPackage(data)
@@ -82,8 +91,16 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
try {
formData.value = await getTenantPackage(data.id);
await formApi.setValues(data);
const detail = await getTenantPackage(data.id);
formData.value = detail;
await formApi.setValues({
...detail,
menuIds: getTreeCheckedValues(
menuTree.value,
detail.menuIds ?? [],
(menu) => menu.id,
),
});
} finally {
modalApi.unlock();
}
@@ -94,7 +111,7 @@ const [Modal, modalApi] = useVbenModal({
async function loadMenuTree() {
menuLoading.value = true;
try {
const data = await getMenuList();
const data = await getSimpleMenusList();
menuTree.value = handleTree(data) as SystemMenuApi.Menu[];
} finally {
menuLoading.value = false;

View File

@@ -27,7 +27,9 @@ export function getTenantPackagePage(params: PageParam) {
/** 查询租户套餐详情 */
export function getTenantPackage(id: number) {
return requestClient.get(`/system/tenant-package/get?id=${id}`);
return requestClient.get<SystemTenantPackageApi.TenantPackage>(
`/system/tenant-package/get?id=${id}`,
);
}
/** 新增租户套餐 */

View File

@@ -8,7 +8,11 @@ import { nextTick, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { SystemMenuTypeEnum } from '@vben/constants';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { Checkbox, message, Spin } from 'antdv-next';
@@ -52,7 +56,11 @@ const [Modal, modalApi] = useVbenModal({
try {
await assignRoleMenu({
roleId: data.id,
menuIds: data.menuIds,
menuIds: getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
),
});
// 关闭并提示
await modalApi.close();
@@ -76,7 +84,10 @@ const [Modal, modalApi] = useVbenModal({
try {
// 加载角色菜单
const menuIds = await getRoleMenuList(data.id);
await formApi.setFieldValue('menuIds', menuIds);
await formApi.setFieldValue(
'menuIds',
getTreeCheckedValues(menuTree.value, menuIds, (menu) => menu.id),
);
await formApi.setValues(data);
} finally {

View File

@@ -5,12 +5,16 @@ import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
import { computed, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { Checkbox, message, Spin } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';
import { getMenuList } from '#/api/system/menu';
import { getSimpleMenusList } from '#/api/system/menu';
import {
createTenantPackage,
getTenantPackage,
@@ -56,6 +60,11 @@ const [Modal, modalApi] = useVbenModal({
// 提交表单
const data =
(await formApi.getValues()) as SystemTenantPackageApi.TenantPackage;
data.menuIds = getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
);
try {
await (formData.value
? updateTenantPackage(data)
@@ -82,8 +91,16 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
try {
formData.value = await getTenantPackage(data.id);
await formApi.setValues(data);
const detail = await getTenantPackage(data.id);
formData.value = detail;
await formApi.setValues({
...detail,
menuIds: getTreeCheckedValues(
menuTree.value,
detail.menuIds ?? [],
(menu) => menu.id,
),
});
} finally {
modalApi.unlock();
}
@@ -94,7 +111,7 @@ const [Modal, modalApi] = useVbenModal({
async function loadMenuTree() {
menuLoading.value = true;
try {
const data = await getMenuList();
const data = await getSimpleMenusList();
menuTree.value = handleTree(data) as SystemMenuApi.Menu[];
} finally {
menuLoading.value = false;

View File

@@ -27,7 +27,9 @@ export function getTenantPackagePage(params: PageParam) {
/** 查询租户套餐详情 */
export function getTenantPackage(id: number) {
return requestClient.get(`/system/tenant-package/get?id=${id}`);
return requestClient.get<SystemTenantPackageApi.TenantPackage>(
`/system/tenant-package/get?id=${id}`,
);
}
/** 新增租户套餐 */

View File

@@ -8,7 +8,11 @@ import { nextTick, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { SystemMenuTypeEnum } from '@vben/constants';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { ElCheckbox, ElMessage } from 'element-plus';
@@ -52,7 +56,11 @@ const [Modal, modalApi] = useVbenModal({
try {
await assignRoleMenu({
roleId: data.id,
menuIds: data.menuIds,
menuIds: getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
),
});
// 关闭并提示
await modalApi.close();
@@ -76,7 +84,10 @@ const [Modal, modalApi] = useVbenModal({
try {
// 加载角色菜单
const menuIds = await getRoleMenuList(data.id);
await formApi.setFieldValue('menuIds', menuIds);
await formApi.setFieldValue(
'menuIds',
getTreeCheckedValues(menuTree.value, menuIds, (menu) => menu.id),
);
await formApi.setValues(data);
} finally {

View File

@@ -5,12 +5,16 @@ import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
import { computed, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { ElCheckbox, ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { getMenuList } from '#/api/system/menu';
import { getSimpleMenusList } from '#/api/system/menu';
import {
createTenantPackage,
getTenantPackage,
@@ -56,6 +60,11 @@ const [Modal, modalApi] = useVbenModal({
// 提交表单
const data =
(await formApi.getValues()) as SystemTenantPackageApi.TenantPackage;
data.menuIds = getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
);
try {
await (formData.value
? updateTenantPackage(data)
@@ -82,8 +91,16 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
try {
formData.value = await getTenantPackage(data.id);
await formApi.setValues(data);
const detail = await getTenantPackage(data.id);
formData.value = detail;
await formApi.setValues({
...detail,
menuIds: getTreeCheckedValues(
menuTree.value,
detail.menuIds ?? [],
(menu) => menu.id,
),
});
} finally {
modalApi.unlock();
}
@@ -94,7 +111,7 @@ const [Modal, modalApi] = useVbenModal({
async function loadMenuTree() {
menuLoading.value = true;
try {
const data = await getMenuList();
const data = await getSimpleMenusList();
menuTree.value = handleTree(data) as SystemMenuApi.Menu[];
} finally {
menuLoading.value = false;

View File

@@ -27,7 +27,9 @@ export function getTenantPackagePage(params: PageParam) {
/** 查询租户套餐详情 */
export function getTenantPackage(id: number) {
return requestClient.get(`/system/tenant-package/get?id=${id}`);
return requestClient.get<SystemTenantPackageApi.TenantPackage>(
`/system/tenant-package/get?id=${id}`,
);
}
/** 新增租户套餐 */

View File

@@ -8,7 +8,11 @@ import { nextTick, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { SystemMenuTypeEnum } from '@vben/constants';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { NCheckbox, NSpin } from 'naive-ui';
@@ -53,7 +57,11 @@ const [Modal, modalApi] = useVbenModal({
try {
await assignRoleMenu({
roleId: data.id,
menuIds: data.menuIds,
menuIds: getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
),
});
// 关闭并提示
await modalApi.close();
@@ -77,7 +85,10 @@ const [Modal, modalApi] = useVbenModal({
try {
// 加载角色菜单
const menuIds = await getRoleMenuList(data.id);
await formApi.setFieldValue('menuIds', menuIds);
await formApi.setFieldValue(
'menuIds',
getTreeCheckedValues(menuTree.value, menuIds, (menu) => menu.id),
);
await formApi.setValues(data);
} finally {

View File

@@ -5,13 +5,17 @@ import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
import { computed, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { NCheckbox, NSpin } from 'naive-ui';
import { useVbenForm } from '#/adapter/form';
import { message } from '#/adapter/naive';
import { getMenuList } from '#/api/system/menu';
import { getSimpleMenusList } from '#/api/system/menu';
import {
createTenantPackage,
getTenantPackage,
@@ -57,6 +61,11 @@ const [Modal, modalApi] = useVbenModal({
// 提交表单
const data =
(await formApi.getValues()) as SystemTenantPackageApi.TenantPackage;
data.menuIds = getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
);
try {
await (formData.value
? updateTenantPackage(data)
@@ -83,8 +92,16 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
try {
formData.value = await getTenantPackage(data.id);
await formApi.setValues(data);
const detail = await getTenantPackage(data.id);
formData.value = detail;
await formApi.setValues({
...detail,
menuIds: getTreeCheckedValues(
menuTree.value,
detail.menuIds ?? [],
(menu) => menu.id,
),
});
} finally {
modalApi.unlock();
}
@@ -95,7 +112,7 @@ const [Modal, modalApi] = useVbenModal({
async function loadMenuTree() {
menuLoading.value = true;
try {
const data = await getMenuList();
const data = await getSimpleMenusList();
menuTree.value = handleTree(data) as SystemMenuApi.Menu[];
} finally {
menuLoading.value = false;

View File

@@ -27,7 +27,9 @@ export function getTenantPackagePage(params: PageParam) {
/** 查询租户套餐详情 */
export function getTenantPackage(id: number) {
return requestClient.get(`/system/tenant-package/get?id=${id}`);
return requestClient.get<SystemTenantPackageApi.TenantPackage>(
`/system/tenant-package/get?id=${id}`,
);
}
/** 新增租户套餐 */

View File

@@ -8,7 +8,11 @@ import { nextTick, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { SystemMenuTypeEnum } from '@vben/constants';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { Checkbox } from 'tdesign-vue-next';
@@ -53,7 +57,11 @@ const [Modal, modalApi] = useVbenModal({
try {
await assignRoleMenu({
roleId: data.id,
menuIds: data.menuIds,
menuIds: getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
),
});
// 关闭并提示
await modalApi.close();
@@ -77,7 +85,10 @@ const [Modal, modalApi] = useVbenModal({
try {
// 加载角色菜单
const menuIds = await getRoleMenuList(data.id);
await formApi.setFieldValue('menuIds', menuIds);
await formApi.setFieldValue(
'menuIds',
getTreeCheckedValues(menuTree.value, menuIds, (menu) => menu.id),
);
await formApi.setValues(data);
} finally {

View File

@@ -5,13 +5,17 @@ import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
import { computed, ref } from 'vue';
import { Tree, useVbenModal } from '@vben/common-ui';
import { handleTree } from '@vben/utils';
import {
getTreeCheckedValues,
getTreeValuesWithAncestors,
handleTree,
} from '@vben/utils';
import { Checkbox } from 'tdesign-vue-next';
import { useVbenForm } from '#/adapter/form';
import { message } from '#/adapter/tdesign';
import { getMenuList } from '#/api/system/menu';
import { getSimpleMenusList } from '#/api/system/menu';
import {
createTenantPackage,
getTenantPackage,
@@ -57,6 +61,11 @@ const [Modal, modalApi] = useVbenModal({
// 提交表单
const data =
(await formApi.getValues()) as SystemTenantPackageApi.TenantPackage;
data.menuIds = getTreeValuesWithAncestors(
menuTree.value,
data.menuIds ?? [],
(menu) => menu.id,
);
try {
await (formData.value
? updateTenantPackage(data)
@@ -83,8 +92,16 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
try {
formData.value = await getTenantPackage(data.id);
await formApi.setValues(data);
const detail = await getTenantPackage(data.id);
formData.value = detail;
await formApi.setValues({
...detail,
menuIds: getTreeCheckedValues(
menuTree.value,
detail.menuIds ?? [],
(menu) => menu.id,
),
});
} finally {
modalApi.unlock();
}
@@ -95,7 +112,7 @@ const [Modal, modalApi] = useVbenModal({
async function loadMenuTree() {
menuLoading.value = true;
try {
const data = await getMenuList();
const data = await getSimpleMenusList();
menuTree.value = handleTree(data) as SystemMenuApi.Menu[];
} finally {
menuLoading.value = false;