feat: 租户套餐的菜单支持微调

This commit is contained in:
Ray.Hao
2026-01-26 09:21:59 +08:00
parent 452e1361cf
commit 092e51544f
2 changed files with 94 additions and 29 deletions

View File

@@ -98,6 +98,23 @@ const TenantAPI = {
params: { status }, params: { status },
}); });
}, },
/** 获取租户菜单ID集合 */
getTenantMenuIds(tenantId: number) {
return request<any, number[]>({
url: `${TENANT_BASE_URL}/${tenantId}/menuIds`,
method: "get",
});
},
/** 更新租户菜单 */
updateTenantMenus(tenantId: number, menuIds: number[]) {
return request({
url: `${TENANT_BASE_URL}/${tenantId}/menus`,
method: "put",
data: menuIds,
});
},
}; };
export default TenantAPI; export default TenantAPI;

View File

@@ -96,14 +96,14 @@
<template #default="scope"> <template #default="scope">
<el-button <el-button
v-if="!isPlatformTenantId(scope.row.id)" v-if="!isPlatformTenantId(scope.row.id)"
v-hasPerm="['sys:tenant:update']" v-hasPerm="['sys:tenant:assign']"
type="primary" type="primary"
size="small" size="small"
link link
icon="menu" icon="menu"
@click="handleOpenPlanMenuDialog(scope.row)" @click="handleOpenTenantMenuDialog(scope.row)"
> >
方案菜单 租户菜单
</el-button> </el-button>
<el-button <el-button
v-hasPerm="['sys:tenant:update']" v-hasPerm="['sys:tenant:update']"
@@ -230,10 +230,10 @@
<!-- 方案菜单配置 --> <!-- 方案菜单配置 -->
<el-drawer <el-drawer
v-model="planMenuDialogVisible" v-model="tenantMenuDialogVisible"
:title="'【' + checkedPlan.name + '】方案菜单配置'" :title="'【' + checkedTenant.name + '】租户菜单配置'"
size="600px" size="600px"
@close="handleClosePlanMenuDialog" @close="handleCloseTenantMenuDialog"
> >
<div class="flex-x-between"> <div class="flex-x-between">
<el-input v-model="menuKeywords" clearable class="w-[150px]" placeholder="菜单名称"> <el-input v-model="menuKeywords" clearable class="w-[150px]" placeholder="菜单名称">
@@ -282,13 +282,13 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button <el-button
v-hasPerm="['sys:tenant-plan:assign']" v-hasPerm="['sys:tenant:assign']"
type="primary" type="primary"
@click="handlePlanMenuSubmit" @click="handleTenantMenuSubmit"
> >
确定 确定
</el-button> </el-button>
<el-button @click="planMenuDialogVisible = false">取消</el-button> <el-button @click="tenantMenuDialogVisible = false">取消</el-button>
</div> </div>
</template> </template>
</el-drawer> </el-drawer>
@@ -308,7 +308,13 @@ import { hasPerm } from "@/utils/auth";
import TenantAPI from "@/api/system/tenant"; import TenantAPI from "@/api/system/tenant";
import TenantPlanAPI from "@/api/system/tenant-plan"; import TenantPlanAPI from "@/api/system/tenant-plan";
import MenuAPI from "@/api/system/menu"; import MenuAPI from "@/api/system/menu";
import type { TenantCreateForm, TenantForm, TenantQueryParams, TenantItem } from "@/types/api"; import type {
OptionItem,
TenantCreateForm,
TenantForm,
TenantQueryParams,
TenantItem,
} from "@/types/api";
import { MenuScopeEnum } from "@/enums/business"; import { MenuScopeEnum } from "@/enums/business";
import { isPlatformTenantId } from "@/utils/tenant"; import { isPlatformTenantId } from "@/utils/tenant";
@@ -335,8 +341,8 @@ const dialog = reactive({
visible: false, visible: false,
}); });
const planMenuDialogVisible = ref(false); const tenantMenuDialogVisible = ref(false);
const checkedPlan = ref<{ id?: number; name?: string }>({}); const checkedTenant = ref<{ id?: number; name?: string; planId?: number }>({});
const menuKeywords = ref(""); const menuKeywords = ref("");
const menuExpanded = ref(true); const menuExpanded = ref(true);
const menuParentChildLinked = ref(true); const menuParentChildLinked = ref(true);
@@ -409,8 +415,10 @@ function fetchData() {
}); });
} }
async function handleOpenPlanMenuDialog(row: TenantItem) { async function handleOpenTenantMenuDialog(row: TenantItem) {
if (isPlatformTenantId(row.id)) { const tenantId = row.id;
if (tenantId == null || tenantId === "") return;
if (isPlatformTenantId(tenantId)) {
return; return;
} }
const planId = row.planId; const planId = row.planId;
@@ -419,29 +427,48 @@ async function handleOpenPlanMenuDialog(row: TenantItem) {
return; return;
} }
planMenuDialogVisible.value = true; tenantMenuDialogVisible.value = true;
loading.value = true; loading.value = true;
checkedPlan.value = { id: planId, name: resolvePlanLabel(planId) }; checkedTenant.value = {
id: Number(tenantId),
name: row.name || String(tenantId),
planId,
};
try { try {
// 套餐菜单只允许配置业务菜单 const [menuOptions, planMenuIds, menuIds] = await Promise.all([
const menuOptions = await MenuAPI.getOptions(false, MenuScopeEnum.TENANT); MenuAPI.getOptions(false, MenuScopeEnum.TENANT),
menuPermOptions.value = menuOptions; TenantPlanAPI.getPlanMenuIds(planId),
const menuIds = await TenantPlanAPI.getPlanMenuIds(planId); TenantAPI.getTenantMenuIds(Number(tenantId)),
]);
const normalizedPlanMenuIds = planMenuIds
.map((menuId) => Number(menuId))
.filter((menuId) => !Number.isNaN(menuId));
const allowedMenuIdSet = new Set(normalizedPlanMenuIds);
menuPermOptions.value = allowedMenuIdSet.size
? filterMenuOptionsByIds(menuOptions, allowedMenuIdSet)
: menuOptions;
const normalizedMenuIds = menuIds
.map((menuId) => Number(menuId))
.filter((menuId) => !Number.isNaN(menuId));
await nextTick(); await nextTick();
menuTreeRef.value?.setCheckedKeys([], false); menuTreeRef.value?.setCheckedKeys([], false);
menuIds.forEach((menuId) => menuTreeRef.value?.setChecked(menuId, true, false)); const checkedMenuIds = allowedMenuIdSet.size
? normalizedMenuIds.filter((menuId) => allowedMenuIdSet.has(menuId))
: normalizedMenuIds;
checkedMenuIds.forEach((menuId) => menuTreeRef.value?.setChecked(menuId, true, false));
} finally { } finally {
loading.value = false; loading.value = false;
} }
} }
function handleClosePlanMenuDialog() { function handleCloseTenantMenuDialog() {
planMenuDialogVisible.value = false; tenantMenuDialogVisible.value = false;
menuKeywords.value = ""; menuKeywords.value = "";
menuExpanded.value = true; menuExpanded.value = true;
menuParentChildLinked.value = true; menuParentChildLinked.value = true;
menuTreeRef.value?.setCheckedKeys([], false); menuTreeRef.value?.setCheckedKeys([], false);
checkedTenant.value = {};
} }
function toggleMenuTree() { function toggleMenuTree() {
@@ -470,9 +497,28 @@ function handleMenuFilter(value: string, data: { [key: string]: any }) {
return data.label.includes(value); return data.label.includes(value);
} }
async function handlePlanMenuSubmit() { function filterMenuOptionsByIds(
const planId = checkedPlan.value.id; options: OptionItem[],
if (!planId) return; allowedMenuIdSet: Set<number>
): OptionItem[] {
return options.reduce<OptionItem[]>((acc, option) => {
const children = option.children
? filterMenuOptionsByIds(option.children, allowedMenuIdSet)
: [];
const allowed = allowedMenuIdSet.has(Number(option.value));
if (allowed || children.length > 0) {
acc.push({
...option,
children: children.length > 0 ? children : undefined,
});
}
return acc;
}, []);
}
async function handleTenantMenuSubmit() {
const tenantId = checkedTenant.value.id;
if (!tenantId) return;
const checkedMenuIds: number[] = menuTreeRef const checkedMenuIds: number[] = menuTreeRef
.value!.getCheckedNodes(false, true) .value!.getCheckedNodes(false, true)
@@ -480,9 +526,11 @@ async function handlePlanMenuSubmit() {
loading.value = true; loading.value = true;
try { try {
await TenantPlanAPI.updatePlanMenus(planId, checkedMenuIds); await TenantAPI.updateTenantMenus(tenantId, checkedMenuIds);
ElMessage.success("方案菜单配置成功"); ElMessage.success("租户菜单配置成功");
planMenuDialogVisible.value = false; tenantMenuDialogVisible.value = false;
} catch {
ElMessage.error("租户菜单配置失败");
} finally { } finally {
loading.value = false; loading.value = false;
} }