create-iking-admin
Version:
金合技术中台脚手架
375 lines (365 loc) • 13.6 kB
text/typescript
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