| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- import type { SystemModel, TransformData } from '#/api';
- import accessMenuRoutes from '../../../public/database/menu.json';
- export interface TreeSelectMenuNode {
- id: number | string;
- label: string;
- children?: TreeSelectMenuNode[];
- }
- /** 标准化菜单下拉树节点,供权限树组件使用 */
- export function normalizeMenuTreeSelect(
- nodes?: TreeSelectMenuNode[],
- ): TreeSelectMenuNode[] {
- if (!Array.isArray(nodes)) return [];
- return nodes.map((node) => ({
- id: String(node.id),
- label: node.label ?? '',
- children: node.children?.length
- ? normalizeMenuTreeSelect(node.children)
- : undefined,
- }));
- }
- type AccessMenuRoute = TransformData & {
- id?: string;
- children?: AccessMenuRoute[];
- };
- const accessMenuRouteMap = buildAccessMenuRouteMap(
- accessMenuRoutes as AccessMenuRoute[],
- );
- /**
- * 本地写死的菜单树节点(后端 treeselect 未返回时合并进侧栏)。
- * 后期后端配置相同 id 的菜单后,将优先使用接口返回的 label。
- */
- export const HARDCODED_MENU_TREE_SELECT: TreeSelectMenuNode[] = [
- {
- id: '1',
- label: '系统管理',
- children: [{ id: '2415', label: '岗位人员资质管理' }],
- },
- {
- id: '2500',
- label: '处方点评',
- children: [
- { id: '2501', label: '点评专家' },
- { id: '2502', label: '点评指标库' },
- ],
- },
- {
- id: '2600',
- label: '患者评价',
- children: [
- { id: '2601', label: '满意度评价' },
- { id: '2602', label: '处方疗效评价' },
- ],
- },
- ];
- const HARDCODED_MENU_ROOT_IDS = ['2500', '2600'];
- const HARDCODED_MENU_LEAF_IDS = ['2415'];
- /** 将本地写死菜单合并进后端 treeselect 结果 */
- export function mergeHardcodedMenuTree(
- nodes?: TreeSelectMenuNode[],
- ): TreeSelectMenuNode[] {
- const backend = Array.isArray(nodes) ? [...nodes] : [];
- for (const hard of HARDCODED_MENU_TREE_SELECT) {
- const existing = backend.find((n) => String(n.id) === String(hard.id));
- if (!existing) {
- backend.push({ ...hard, children: hard.children ? [...hard.children] : undefined });
- continue;
- }
- if (!hard.children?.length) continue;
- existing.children ??= [];
- for (const child of hard.children) {
- if (
- !existing.children.some((c) => String(c.id) === String(child.id))
- ) {
- existing.children.push(child);
- }
- }
- }
- return backend;
- }
- function findMenuInTree(
- menus: SystemModel.Menu[],
- id: string,
- ): SystemModel.Menu | null {
- for (const menu of menus) {
- if (String(menu.id) === id) return menu;
- if (menu.children?.length) {
- const found = findMenuInTree(menu.children, id);
- if (found) return found;
- }
- }
- return null;
- }
- function findParentOfMenu(
- menus: SystemModel.Menu[],
- id: string,
- ): SystemModel.Menu | null {
- for (const menu of menus) {
- if (menu.children?.some((child) => String(child.id) === id)) return menu;
- if (menu.children?.length) {
- const found = findParentOfMenu(menu.children, id);
- if (found) return found;
- }
- }
- return null;
- }
- /** 父级菜单可见时,补全本地写死的子菜单(便于前期联调) */
- function ensureHardcodedMenuLeavesVisible(
- filtered: SystemModel.Menu[],
- all: SystemModel.Menu[],
- ): SystemModel.Menu[] {
- const result = filtered.map((menu) => ({
- ...menu,
- children: menu.children ? [...menu.children] : undefined,
- }));
- for (const leafId of HARDCODED_MENU_LEAF_IDS) {
- if (findMenuInTree(result, leafId)) continue;
- const leaf = findMenuInTree(all, leafId);
- const parentInAll = findParentOfMenu(all, leafId);
- if (!leaf || !parentInAll) continue;
- const parentInFiltered = findMenuInTree(result, String(parentInAll.id));
- if (!parentInFiltered) continue;
- parentInFiltered.children ??= [];
- if (
- !parentInFiltered.children.some((child) => String(child.id) === leafId)
- ) {
- parentInFiltered.children.push(leaf);
- }
- }
- return result;
- }
- /** 角色权限未包含写死菜单 id 时,仍保留本地菜单(便于前期联调) */
- export function ensureHardcodedMenusVisible(
- filtered: SystemModel.Menu[],
- all: SystemModel.Menu[],
- ): SystemModel.Menu[] {
- let result = [...filtered];
- for (const rootId of HARDCODED_MENU_ROOT_IDS) {
- const hardRoot = all.find((menu) => String(menu.id) === rootId);
- if (!hardRoot) continue;
- if (result.some((menu) => String(menu.id) === rootId)) continue;
- result = [...result, hardRoot];
- }
- result = ensureHardcodedMenuLeavesVisible(result, all);
- return result.sort(
- (a, b) => (a.meta.order ?? 999_999) - (b.meta.order ?? 999_999),
- );
- }
- function buildAccessMenuRouteMap(
- menus: AccessMenuRoute[],
- map = new Map<string, AccessMenuRoute>(),
- ) {
- for (const menu of menus) {
- if (menu.id != null) map.set(String(menu.id), menu);
- if (menu.children?.length) buildAccessMenuRouteMap(menu.children, map);
- }
- return map;
- }
- /** 将 treeselect 接口数据与本地路由配置合并为可生成路由的菜单 */
- export function fromTreeSelectMenus(
- nodes: TreeSelectMenuNode[],
- ): SystemModel.Menu[] {
- if (!Array.isArray(nodes)) return [];
- return nodes
- .map((node) => toAccessMenu(node))
- .filter((menu): menu is SystemModel.Menu => menu != null)
- .sort((a, b) => (a.meta.order ?? 999_999) - (b.meta.order ?? 999_999));
- }
- function toAccessMenu(node: TreeSelectMenuNode): SystemModel.Menu | null {
- const route = accessMenuRouteMap.get(String(node.id));
- if (!route) return null;
- const children = node.children?.length
- ? fromTreeSelectMenus(node.children)
- : undefined;
- const hasChildren = !!children?.length;
- return {
- id: String(node.id),
- type: hasChildren ? 'catalog' : 'menu',
- name: route.name,
- path: route.path,
- component: route.component,
- meta: fromMenuMeta({
- ...route.meta,
- title: node.label || route.meta?.title,
- }),
- children: hasChildren ? children : undefined,
- } satisfies SystemModel.Menu;
- }
- export function filterMenusByPermissions(
- permissions: Array<number | string>,
- menus: SystemModel.Menu[],
- ): SystemModel.Menu[] {
- const permissionSet = new Set(permissions.map(String));
- const walk = (
- items: SystemModel.Menu[],
- parentMenu: SystemModel.Menu[] = [],
- ) => {
- for (const menu of items) {
- const id = String(menu.id);
- if (permissionSet.has(id)) {
- permissionSet.delete(id);
- if (menu.type === 'menu') parentMenu.push(menu);
- else if (menu.type === 'catalog' && menu.children?.length) {
- const parent = { ...menu, children: [] as SystemModel.Menu[] };
- parentMenu.push(parent);
- walk(menu.children, parent.children);
- }
- } else if (menu.type === 'catalog' && menu.children?.length) {
- walk(menu.children, parentMenu);
- }
- if (permissionSet.size === 0) break;
- }
- return parentMenu;
- };
- return walk(menus);
- }
- export function fromMenus(menus: TransformData[]): SystemModel.Menu[] {
- const getType = (menu: TransformData): SystemModel.Menu['type'] => {
- if (menu.type) return menu.type;
- if (menu.component && menu.children === null) return 'menu';
- return menu.component ? 'catalog' : 'button';
- };
- return Array.isArray(menus)
- ? menus
- .map((menu: TransformData) => {
- menu.meta ??= {};
- // 使用后端的 orderNum 优先;其可能为字符串,这里转为数字;没有则回退到已有 meta.order;仍无则给很大默认值
- const computedOrder = Number(
- menu?.orderNum ?? (menu.meta as any)?.order ?? 999_999,
- );
- menu.meta.order = Number.isFinite(computedOrder)
- ? computedOrder
- : 999_999;
- return {
- type: getType(menu),
- id: menu.id ?? menu.meta.id,
- pid: menu.parentId,
- name: menu.name,
- path: menu.path,
- component: menu.component,
- meta: fromMenuMeta(menu.meta),
- children: fromMenus(menu.children),
- } satisfies SystemModel.Menu;
- })
- // 未设置排序的项默认放到最后
- .sort((a, b) => (a.meta.order ?? 999_999) - (b.meta.order ?? 999_999))
- : [];
- }
- type MenuMeta = SystemModel.Menu['meta'];
- export function getDefaultMenuMeta(meta?: MenuMeta): MenuMeta {
- return Object.assign(
- {
- keepAlive: true,
- },
- meta,
- );
- }
- function fromMenuMeta(meta: TransformData): MenuMeta {
- return getDefaultMenuMeta(meta satisfies MenuMeta);
- }
|