UNPKG

create-iking-admin

Version:

金合技术中台脚手架

375 lines (365 loc) 13.6 kB
import { cloneDeep } from 'lodash-es' import type { RouteMeta, RouteRecordRaw } from 'vue-router' import { resolveRoutePath } from '@main/utils' import { systemRoutes } from '@main/router/routes' import { menuApi } from '@main/api/modules/menu' import useSettingsStore from './settings' import useUserStore from './user' import useTabbarStore from './tabbar' import type { Route } from '#/global' const useRouteStore = defineStore( // 唯一ID 'route', () => { const settingsStore = useSettingsStore() const userStore = useUserStore() const tabbarStore = useTabbarStore() const isGenerate = ref(false) const routesRaw = ref<Route.recordMainRaw[]>([]) const filesystemRoutesRaw = ref<RouteRecordRaw[]>([]) const currentRemoveRoutes = ref<(() => void)[]>([]) const systemMenuTree = ref<RouteRecordRaw[]>([]) const systemMenuList = ref<RouteRecordRaw[]>([]) const systemRouteMenu = ref<RouteRecordRaw[]>([]) // 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平 function flatAsyncRoutes<T extends RouteRecordRaw>(route: T): T { if (route.children) { route.children = flatAsyncRoutesRecursive(route.children, [{ path: route.path, title: route.meta?.title, i18n: route.meta?.i18n, icon: route.meta?.icon, activeIcon: route.meta?.activeIcon, hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false, }], route.path, route.meta?.auth) } return route } function flatAsyncRoutesRecursive(routes: RouteRecordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = '', baseAuth: RouteMeta['auth']): RouteRecordRaw[] { const res: RouteRecordRaw[] = [] routes.forEach((route) => { if (route.children) { const childrenBaseUrl = resolveRoutePath(baseUrl, route.path) const childrenBaseAuth = baseAuth ?? route.meta?.auth const tmpBreadcrumb = cloneDeep(breadcrumb) tmpBreadcrumb.push({ path: childrenBaseUrl, title: route.meta?.title, i18n: route.meta?.i18n, icon: route.meta?.icon, activeIcon: route.meta?.activeIcon, hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false, }) const tmpRoute = cloneDeep(route) tmpRoute.path = childrenBaseUrl if (!tmpRoute.meta) { tmpRoute.meta = {} } tmpRoute.meta.auth = childrenBaseAuth tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb delete tmpRoute.children res.push(tmpRoute) const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl, childrenBaseAuth) childrenRoutes.forEach((item) => { // 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复 if (res.some(v => v.path === item.path)) { res.forEach((v, i) => { if (v.path === item.path) { res[i] = item } }) } else { res.push(item) } }) } else { const tmpRoute = cloneDeep(route) tmpRoute.path = resolveRoutePath(baseUrl, tmpRoute.path) // 处理面包屑导航 const tmpBreadcrumb = cloneDeep(breadcrumb) tmpBreadcrumb.push({ path: tmpRoute.path, title: tmpRoute.meta?.title, i18n: tmpRoute.meta?.i18n, icon: tmpRoute.meta?.icon, activeIcon: tmpRoute.meta?.activeIcon, hide: !tmpRoute.meta?.breadcrumb && tmpRoute.meta?.breadcrumb === false, }) if (!tmpRoute.meta) { tmpRoute.meta = {} } tmpRoute.meta.auth = baseAuth ?? tmpRoute.meta?.auth tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb res.push(tmpRoute) } }) return res } // 扁平化路由(将三级及以上路由数据拍平成二级) const flatRoutes = computed(() => { const returnRoutes: RouteRecordRaw[] = [] if (settingsStore.settings.app.routeBaseOn !== 'filesystem') { if (routesRaw.value) { routesRaw.value.forEach((item) => { const tmpRoutes = cloneDeep(item.children || [item]) as RouteRecordRaw[] tmpRoutes.map((v) => { if (!v.meta) { v.meta = {} } return v }) returnRoutes.push(...tmpRoutes) }) returnRoutes.forEach(item => flatAsyncRoutes(item)) } } else { returnRoutes.push(...cloneDeep(filesystemRoutesRaw.value) as RouteRecordRaw[]) } return returnRoutes }) const flatSystemRoutes = computed(() => { const routes = [...systemRoutes] routes.forEach(item => flatAsyncRoutes(item)) return routes }) // 判断是否有权限 function hasPermission(permissions: string[], route: Route.recordMainRaw | RouteRecordRaw) { let isAuth = false if (route.meta?.auth) { isAuth = permissions.some((auth) => { if (typeof route.meta?.auth === 'string') { return route.meta.auth !== '' ? route.meta.auth === auth : true } else if (typeof route.meta?.auth === 'object') { return route.meta.auth.length > 0 ? route.meta.auth.includes(auth) : true } else { return false } }) } else { isAuth = true } return isAuth } // 根据权限过滤路由 function filterAsyncRoutes<T extends Route.recordMainRaw[] | RouteRecordRaw[]>(routes: T, permissions: string[]): T { const res: any = [] routes.forEach((route) => { if (hasPermission(permissions, route)) { const tmpRoute = cloneDeep(route) if (tmpRoute.children) { tmpRoute.children = filterAsyncRoutes(tmpRoute.children, permissions) tmpRoute.children.length && res.push(tmpRoute) } else { res.push(tmpRoute) } } }) return res } const routes = computed(() => { let returnRoutes: Route.recordMainRaw[] // 如果权限功能开启,则需要对路由数据进行筛选过滤 if (settingsStore.settings.app.enablePermission) { returnRoutes = filterAsyncRoutes(routesRaw.value as any, userStore.permissions) } else { returnRoutes = cloneDeep(routesRaw.value) as any } return returnRoutes }) // 将设置 meta.singleMenu 的一级路由转换成二级路由 function convertSingleRoutes<T extends Route.recordMainRaw[]>(routes: T): T { routes.map((route) => { if (route.children) { route.children.forEach((item, index, arr) => { if (item.meta?.singleMenu) { arr[index] = { ...item, component: () => import('@main/layouts/index.vue'), children: [ { path: '', component: item.component, meta: { title: item.meta.title, i18n: item.meta.i18n, sidebar: false, breadcrumb: false, }, }, ], } as RouteRecordRaw delete arr[index].meta!.singleMenu } }) } return route }) return routes } // 根据权限动态生成路由(前端生成) async function generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) { // 设置 routes 数据 routesRaw.value = convertSingleRoutes(cloneDeep(asyncRoutes) as any) isGenerate.value = true // 加载常驻标签页 if (settingsStore.settings.tabbar.enable) { tabbarStore.initPermanentTab() } } // 格式化后端路由数据 function formatBackRoutes(routes: any): Route.recordMainRaw[] { const mainViews = import.meta.glob('@main/views/**/*.vue') const microViews = import.meta.glob('@micro/views/**/*.vue') return routes.map((route: any) => { switch (route.component) { case 'Layout': route.component = () => import('@main/layouts/index.vue') break default: if (route.component) { route.component = route?.framework ? mainViews[`/main-app/views/${route.component}`] : microViews[`/micro-app/views/${route.component}`] } else { delete route.component } } if (route.children?.length) { route.children = formatBackRoutes(route.children) } // if (route.jumpType === MenuJumpEnum.NONE) { // route.component = () => import('@main/layouts/index.vue') // } return route }) } // 根据权限动态生成路由(后端获取) async function generateRoutesAtBack() { await menuApi.getUserMenuList(false).then(async (res) => { const routesMenu = res.data.filter((v: { menuType: keyof typeof MenuBtnEnum }) => v.menuType === MenuBtnEnum.MENU) const _routes = routesMenu.map((v: any) => { const linkStartWidth = v.link.startsWith('/') if (v.parentId && linkStartWidth) { v.link = v.link.substring(1) } const fPath = v.link.startsWith('/') ? v.link : `/${v.link}` return { ...v, name: v.name, id: v.id, parentId: v.parentId, component: v.component, path: fPath, meta: { id: v.id, parentId: v.parentId, cache: v.keepAlive, title: v.name, i18n: '', icon: v.icon, isFirstLevel: !v.parentId, link: v.jumpType === MenuJumpEnum.EXTERNAL_LINK ? v.link : undefined, path: fPath, iframe: v.iframe, visible: v.visible, // 指定高亮侧边栏路由,需要设置完整路由地址 activeMenu: v.activeMenu, // 该路由是否在侧边栏导航中展示 sidebar: v.sidebar, // 该路由是否在面包屑导航中展示 breadcrumb: v.breadcrumb, // 次导航是否默认展开 defaultOpened: v.defaultOpened, activeIcon: v.activeIcon, badge: v.badge ? v.badge === 'point' ? true : v.badge : false, // boolean | string | number }, } }) const routeTree = ikTree.listToTree(_routes, { needSort: true }) routeTree.forEach((route: any) => { if (!route.children) { route.singleMenu = true // route.children = [] } }) // 设置 routes 数据 routesRaw.value = await formatBackRoutes(routeTree) as any// convertSingleRoutes(formatBackRoutes(menuList) as any) isGenerate.value = true // 初始化常驻标签页 if (settingsStore.settings.tabbar.enable) { tabbarStore.initPermanentTab() } systemMenuTree.value = ikTree.listToTree(res.data, { needSort: true }) systemMenuList.value = res.data systemRouteMenu.value = routesMenu // 所有按钮 const just_btn = await res.data.filter( (item: any) => item.menuType === MenuBtnEnum.PAGE_BUTTON, ).map(({ menuType, name, permissionCode, parentId, remark, badge }: any) => { return { menuType, name, permissionCode, parentId, remark, badge, } }) ikStore.session.setItem( MenuStorageEnum.BUTTON, just_btn, ) ikStore.session.setItem( MenuStorageEnum.MENU, routesMenu, ) // 所有菜单、按钮 ikStore.forage.setItem(MenuStorageEnum.ALL, res.data) // 所有菜单 ikStore.forage.setItem(MenuStorageEnum.MENU, routesMenu) }).catch(() => {}) } // 根据权限动态生成路由(文件系统生成) async function generateRoutesAtFilesystem(asyncRoutes: RouteRecordRaw[]) { // 设置 routes 数据 filesystemRoutesRaw.value = convertSingleRoutes(cloneDeep(asyncRoutes) as any) isGenerate.value = true // 加载常驻标签页 if (settingsStore.settings.tabbar.enable) { tabbarStore.initPermanentTab() } } // 记录 accessRoutes 路由,用于登出时删除路由 function setCurrentRemoveRoutes(routes: (() => void)[]) { currentRemoveRoutes.value = routes } // 清空动态路由 function removeRoutes() { isGenerate.value = false routesRaw.value = [] filesystemRoutesRaw.value = [] currentRemoveRoutes.value.forEach((removeRoute) => { removeRoute() }) currentRemoveRoutes.value = [] } return { isGenerate, routes, currentRemoveRoutes, flatRoutes, flatSystemRoutes, systemMenuTree, systemMenuList, systemRouteMenu, generateRoutesAtFront, generateRoutesAtBack, generateRoutesAtFilesystem, setCurrentRemoveRoutes, removeRoutes, } }, ) export default useRouteStore