import type { ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types' import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router' import { useSvgIcon } from '@/hooks/common/icon' import { $t } from '@/locales' /** * 根据角色过滤权限路由 * * @param routes 权限路由 * @param roles 角色 */ export function filterAuthRoutesByRoles(routes: ElegantConstRoute[], roles: string[]) { return routes.flatMap(route => filterAuthRouteByRoles(route, roles)) } /** * 根据角色过滤权限路由 * * @param route 权限路由 * @param roles 角色 */ function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]): ElegantConstRoute[] { const routeRoles = (route.meta && route.meta.roles) || [] // 如果路由的 "roles" 为空,则允许访问 const isEmptyRoles = !routeRoles.length // 如果用户的角色包含在路由的 "roles" 中,则允许访问 const hasPermission = routeRoles.some(role => roles.includes(role)) const filterRoute = { ...route } if (filterRoute.children?.length) { filterRoute.children = filterRoute.children.flatMap(item => filterAuthRouteByRoles(item, roles)) } // 如果过滤后没有子路由,则排除该路由 if (filterRoute.children?.length === 0) { return [] } return hasPermission || isEmptyRoles ? [filterRoute] : [] } /** * 根据 order 对路由进行排序 * * @param route 路由 */ function sortRouteByOrder(route: ElegantConstRoute) { if (route.children?.length) { route.children.sort((next, prev) => (Number(next.meta?.order) || 0) - (Number(prev.meta?.order) || 0)) route.children.forEach(sortRouteByOrder) } return route } /** * 根据 order 对路由进行排序 * * @param routes 路由 */ export function sortRoutesByOrder(routes: ElegantConstRoute[]) { routes.sort((next, prev) => (Number(next.meta?.order) || 0) - (Number(prev.meta?.order) || 0)) routes.forEach(sortRouteByOrder) return routes } /** * 根据权限路由获取全局菜单 * 当嵌套路由里面,有且仅有一个子路由时,将其提升到一级菜单,点击一级菜单时,跳转到子路由 * @param routes 权限路由 * @param shouldHoist 是否提升 * @param includeHidden 是否包含隐藏路由 */ export function getGlobalMenusByAuthRoutes(routes: ElegantConstRoute[], shouldHoist = false, includeHidden = false) { const menus: App.Global.Menu[] = [] routes.forEach((route) => { if (includeHidden || !route.meta?.hideInMenu) { const menu = getGlobalMenuByBaseRoute(route) if (route.children?.some(child => includeHidden || !child.meta?.hideInMenu)) { menu.children = getGlobalMenusByAuthRoutes(route.children, shouldHoist, includeHidden) } // 如果只有一个子菜单,将其提升 if (shouldHoist && menu.children?.length === 1) { const singleChild = menu.children[0] menu.key = singleChild.key menu.routeKey = singleChild.routeKey menu.routePath = singleChild.routePath menu.children = singleChild.children } menus.push(menu) } }) return menus } /** * 更新全局菜单的国际化 * * @param menus */ export function updateLocaleOfGlobalMenus(menus: App.Global.Menu[]) { const result: App.Global.Menu[] = [] menus.forEach((menu) => { const { i18nKey, label, children } = menu const newLabel = i18nKey ? $t(i18nKey) : label const newMenu: App.Global.Menu = { ...menu, label: newLabel, } if (children?.length) { newMenu.children = updateLocaleOfGlobalMenus(children) } result.push(newMenu) }) return result } /** * 根据路由获取全局菜单 * * @param route */ function getGlobalMenuByBaseRoute(route: RouteLocationNormalizedLoaded | ElegantConstRoute) { const { SvgIconVNode } = useSvgIcon() const { name, path } = route const { title, i18nKey, icon = import.meta.env.VITE_MENU_ICON, localIcon, iconFontSize } = route.meta ?? {} const label = i18nKey ? $t(i18nKey) : title! const menu: App.Global.Menu = { key: name as string, label, i18nKey, routeKey: name as RouteKey, routePath: path as RouteMap[RouteKey], icon: SvgIconVNode({ icon, localIcon, fontSize: iconFontSize || 20 }), } return menu } /** * 获取缓存路由名称 * * @param routes Vue 路由 (两级) */ export function getCacheRouteNames(routes: RouteRecordRaw[]) { const cacheNames: LastLevelRouteKey[] = [] routes.forEach((route) => { // 只获取最后两级有组件的路由 route.children?.forEach((child) => { if (child.component && child.meta?.keepAlive) { cacheNames.push(child.name as LastLevelRouteKey) } }) }) return cacheNames } /** * 根据路由名称判断路由是否存在 * * @param routeName * @param routes */ export function isRouteExistByRouteName(routeName: RouteKey, routes: ElegantConstRoute[]) { return routes.some(route => recursiveGetIsRouteExistByRouteName(route, routeName)) } /** * 递归判断路由名称是否存在 * * @param route * @param routeName */ function recursiveGetIsRouteExistByRouteName(route: ElegantConstRoute, routeName: RouteKey) { let isExist = route.name === routeName if (isExist) { return true } if (route.children && route.children.length) { isExist = route.children.some(item => recursiveGetIsRouteExistByRouteName(item, routeName)) } return isExist } /** * 获取选中的菜单 key 路径 * * @param selectedKey * @param menus */ export function getSelectedMenuKeyPathByKey(selectedKey: string, menus: App.Global.Menu[]) { const keyPath: string[] = [] menus.some((menu) => { const path = findMenuPath(selectedKey, menu) const find = Boolean(path?.length) if (find) { keyPath.push(...path!) } return find }) return keyPath } /** * 查找菜单路径 * * @param targetKey 目标菜单 key * @param menu 菜单 */ function findMenuPath(targetKey: string, menu: App.Global.Menu): string[] | null { const path: string[] = [] function dfs(item: App.Global.Menu): boolean { path.push(item.key) if (item.key === targetKey) { return true } if (item.children) { for (const child of item.children) { if (dfs(child)) { return true } } } path.pop() return false } if (dfs(menu)) { return path } return null } /** * 将菜单转换为面包屑 * * @param menu */ function transformMenuToBreadcrumb(menu: App.Global.Menu) { const { children, ...rest } = menu const breadcrumb: App.Global.Breadcrumb = { ...rest, } if (children?.length) { breadcrumb.options = children.map(transformMenuToBreadcrumb) } return breadcrumb } /** * 根据路由获取面包屑 * * @param route * @param menus */ export function getBreadcrumbsByRoute( route: RouteLocationNormalizedLoaded, menus: App.Global.Menu[], ): App.Global.Breadcrumb[] { const key = route.name as string const activeKey = route.meta?.activeMenu for (const menu of menus) { if (menu.key === key) { return [transformMenuToBreadcrumb(menu)] } if (menu.key === activeKey) { const ROUTE_DEGREE_SPLITTER = '_' const parentKey = key.split(ROUTE_DEGREE_SPLITTER).slice(0, -1).join(ROUTE_DEGREE_SPLITTER) const breadcrumbMenu = getGlobalMenuByBaseRoute(route) if (parentKey !== activeKey) { return [transformMenuToBreadcrumb(breadcrumbMenu)] } return [transformMenuToBreadcrumb(menu), transformMenuToBreadcrumb(breadcrumbMenu)] } if (menu.children?.length) { const result = getBreadcrumbsByRoute(route, menu.children) if (result.length > 0) { return [transformMenuToBreadcrumb(menu), ...result] } } } return [] } /** * 将菜单转换为搜索菜单 * * @param menus - 菜单 * @param treeMap */ export function transformMenuToSearchMenus(menus: App.Global.Menu[], treeMap: App.Global.Menu[] = []) { if (menus && menus.length === 0) return [] return menus.reduce((acc, cur) => { if (!cur.children) { acc.push(cur) } if (cur.children && cur.children.length > 0) { transformMenuToSearchMenus(cur.children, treeMap) } return acc }, treeMap) }