mirror of
https://gitee.com/fantastic-admin/basic.git
synced 2024-12-04 21:18:16 +08:00
优化全局状态,将导航和路由拆分成menu和route分别管理
This commit is contained in:
parent
c8e2d48f3a
commit
151e510e2c
@ -6,7 +6,7 @@
|
||||
<Logo />
|
||||
<!-- 顶部模式 -->
|
||||
<div class="nav">
|
||||
<template v-for="(item, index) in menuStore.transformRoutes">
|
||||
<template v-for="(item, index) in menuStore.allMenus">
|
||||
<div v-if="item.children && item.children.length !== 0" :key="index" class="item" :class="{'active': index == menuStore.headerActived}" @click="switchMenu(index)">
|
||||
<svg-icon v-if="item.meta.icon" :name="item.meta.icon" />
|
||||
<span v-if="item.meta.title">{{ item.meta.title }}</span>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<Logo :show-title="false" class="sidebar-logo" />
|
||||
<!-- 侧边栏模式(含主导航) -->
|
||||
<div class="nav">
|
||||
<template v-for="(item, index) in menuStore.transformRoutes">
|
||||
<template v-for="(item, index) in menuStore.allMenus">
|
||||
<div
|
||||
v-if="item.children && item.children.length !== 0" :key="index" :class="{
|
||||
'item': true,
|
||||
|
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<div class="breadcrumb">
|
||||
<span v-for="(bc, bcIndex) in item.breadcrumb" :key="bcIndex">
|
||||
{{ bc }}
|
||||
{{ bc.title }}
|
||||
<svg-icon name="el-icon-arrow-right" />
|
||||
</span>
|
||||
</div>
|
||||
@ -52,20 +52,50 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { deepClone, isExternalLink } from '@/util'
|
||||
import { isExternalLink } from '@/util'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
import { useSettingsStore } from '@/store/modules/settings'
|
||||
const settingsStore = useSettingsStore()
|
||||
import { useMenuStore } from '@/store/modules/menu'
|
||||
const menuStore = useMenuStore()
|
||||
import { useRouteStore } from '@/store/modules/route'
|
||||
const routeStore = useRouteStore()
|
||||
|
||||
const isShow = ref(false)
|
||||
const searchInput = ref('')
|
||||
const sourceList = ref([])
|
||||
const actived = ref(-1)
|
||||
|
||||
const sourceList = computed(() => {
|
||||
let list = []
|
||||
routeStore.flatRoutes.map(item => {
|
||||
if (item.children) {
|
||||
item.children.map(child => {
|
||||
list.push({
|
||||
icon: child.meta.icon || item.meta.icon,
|
||||
title: child.meta.title,
|
||||
i18n: child.meta.i18n,
|
||||
breadcrumb: child.meta.breadcrumbNeste,
|
||||
path: child.path,
|
||||
isExternalLink: isExternalLink(child.path)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
list.push({
|
||||
icon: item.meta.icon,
|
||||
title: item.meta.title,
|
||||
i18n: item.meta.i18n,
|
||||
breadcrumb: [{
|
||||
title: item.meta.title,
|
||||
path: item.path
|
||||
}],
|
||||
path: item.path,
|
||||
isExternalLink: isExternalLink(item.path)
|
||||
})
|
||||
}
|
||||
})
|
||||
return list
|
||||
})
|
||||
|
||||
const resultList = computed(() => {
|
||||
let result = []
|
||||
result = sourceList.value.filter(item => {
|
||||
@ -76,7 +106,7 @@ const resultList = computed(() => {
|
||||
if (item.path.indexOf(searchInput.value) >= 0) {
|
||||
flag = true
|
||||
}
|
||||
if (item.breadcrumb.some(b => b.indexOf(searchInput.value) >= 0)) {
|
||||
if (item.breadcrumb.some(b => b.title.indexOf(searchInput.value) >= 0)) {
|
||||
flag = true
|
||||
}
|
||||
return flag
|
||||
@ -121,59 +151,8 @@ onMounted(() => {
|
||||
isShow.value = true
|
||||
}
|
||||
})
|
||||
menuStore.routes.map(item => {
|
||||
getSourceList(item.children)
|
||||
})
|
||||
})
|
||||
|
||||
function hasChildren(item) {
|
||||
let flag = true
|
||||
if (item.children) {
|
||||
if (item.children.every(i => i.meta.sidebar === false)) {
|
||||
flag = false
|
||||
}
|
||||
} else {
|
||||
flag = false
|
||||
}
|
||||
return flag
|
||||
}
|
||||
function getSourceList(arr) {
|
||||
arr.map(item => {
|
||||
if (item.meta.sidebar !== false) {
|
||||
if (hasChildren(item)) {
|
||||
let baseBreadcrumb = item.meta.baseBreadcrumb ? deepClone(item.meta.baseBreadcrumb) : []
|
||||
baseBreadcrumb.push(item.meta.title)
|
||||
let child = deepClone(item.children)
|
||||
child.map(c => {
|
||||
c.meta.baseIcon = item.meta.icon || item.meta.baseIcon
|
||||
c.meta.baseBreadcrumb = baseBreadcrumb
|
||||
c.meta.basePath = item.meta.basePath ? [item.meta.basePath, item.path].join('/') : item.path
|
||||
})
|
||||
getSourceList(child)
|
||||
} else {
|
||||
let breadcrumb = []
|
||||
if (item.meta.baseBreadcrumb) {
|
||||
breadcrumb = deepClone(item.meta.baseBreadcrumb)
|
||||
}
|
||||
breadcrumb.push(item.meta.title)
|
||||
let path = ''
|
||||
if (isExternalLink(item.path)) {
|
||||
path = item.path
|
||||
} else {
|
||||
path = item.meta.basePath ? [item.meta.basePath, item.path].join('/') : item.path
|
||||
}
|
||||
sourceList.value.push({
|
||||
icon: item.meta.icon || item.meta.baseIcon,
|
||||
title: item.meta.title,
|
||||
i18n: item.meta.i18n,
|
||||
breadcrumb: breadcrumb,
|
||||
path: path,
|
||||
isExternalLink: isExternalLink(item.path)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
function keyUp() {
|
||||
if (resultList.value.length) {
|
||||
actived.value -= 1
|
||||
|
@ -14,7 +14,7 @@
|
||||
}"
|
||||
>
|
||||
<transition-group name="sub-sidebar">
|
||||
<template v-for="route in menuStore.sidebarRoutes">
|
||||
<template v-for="route in menuStore.sidebarMenus">
|
||||
<SidebarItem v-if="route.meta.sidebar !== false" :key="route.path" :item="route" :base-path="route.path" />
|
||||
</template>
|
||||
</transition-group>
|
||||
|
@ -92,10 +92,10 @@ provide('switchMenu', switchMenu)
|
||||
function switchMenu(index) {
|
||||
menuStore.switchHeaderActived(index)
|
||||
if (settingsStore.menu.switchMainMenuAndPageJump) {
|
||||
if (isExternalLink(menuStore.sidebarRoutesFirstDeepestPath)) {
|
||||
window.open(menuStore.sidebarRoutesFirstDeepestPath, '_blank')
|
||||
if (isExternalLink(menuStore.sidebarMenusFirstDeepestPath)) {
|
||||
window.open(menuStore.sidebarMenusFirstDeepestPath, '_blank')
|
||||
} else {
|
||||
router.push(menuStore.sidebarRoutesFirstDeepestPath)
|
||||
router.push(menuStore.sidebarMenusFirstDeepestPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { useSettingsOutsideStore } from '@/store/modules/settings'
|
||||
import { useKeepAliveOutsideStore } from '@/store/modules/keepAlive'
|
||||
import { useUserOutsideStore } from '@/store/modules/user'
|
||||
import { useMenuOutsideStore } from '@/store/modules/menu'
|
||||
import { useRouteOutsideStore } from '@/store/modules/route'
|
||||
|
||||
import '@/assets/styles/nprogress.scss'
|
||||
import { useNProgress } from '@vueuse/integrations/useNProgress'
|
||||
@ -144,11 +145,12 @@ router.beforeEach(async(to, from, next) => {
|
||||
const settingsOutsideStore = useSettingsOutsideStore()
|
||||
const userOutsideStore = useUserOutsideStore()
|
||||
const menuOutsideStore = useMenuOutsideStore()
|
||||
const routeOutsideStore = useRouteOutsideStore()
|
||||
settingsOutsideStore.app.enableProgress && (isLoading.value = true)
|
||||
// 是否已登录
|
||||
if (userOutsideStore.isLogin) {
|
||||
// 是否已根据权限动态生成并挂载路由
|
||||
if (menuOutsideStore.isGenerate) {
|
||||
if (routeOutsideStore.isGenerate) {
|
||||
// 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态
|
||||
settingsOutsideStore.menu.menuMode !== 'single' && menuOutsideStore.setHeaderActived(to.path)
|
||||
if (to.name) {
|
||||
@ -161,9 +163,9 @@ router.beforeEach(async(to, from, next) => {
|
||||
})
|
||||
} else if (!settingsOutsideStore.dashboard.enable && to.name == 'dashboard') {
|
||||
// 如果未开启控制台页面,则默认进入侧边栏导航第一个模块
|
||||
if (menuOutsideStore.sidebarRoutes.length > 0) {
|
||||
if (menuOutsideStore.sidebarMenus.length > 0) {
|
||||
next({
|
||||
path: menuOutsideStore.sidebarRoutesFirstDeepestPath,
|
||||
path: menuOutsideStore.sidebarMenusFirstDeepestPath,
|
||||
replace: true
|
||||
})
|
||||
} else {
|
||||
@ -182,21 +184,20 @@ router.beforeEach(async(to, from, next) => {
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
let accessRoutes = []
|
||||
if (!settingsOutsideStore.app.enableBackendReturnRoute) {
|
||||
accessRoutes = await menuOutsideStore.generateRoutesAtFront(asyncRoutes)
|
||||
await routeOutsideStore.generateRoutesAtFront(asyncRoutes)
|
||||
} else {
|
||||
accessRoutes = await menuOutsideStore.generateRoutesAtBack()
|
||||
await routeOutsideStore.generateRoutesAtBack()
|
||||
}
|
||||
accessRoutes.push(lastRoute)
|
||||
let removeRoutes = []
|
||||
accessRoutes.forEach(route => {
|
||||
routeOutsideStore.flatRoutes.forEach(route => {
|
||||
if (!/^(https?:|mailto:|tel:)/.test(route.path)) {
|
||||
removeRoutes.push(router.addRoute(route))
|
||||
}
|
||||
})
|
||||
removeRoutes.push(router.addRoute(lastRoute))
|
||||
// 记录的 accessRoutes 路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
|
||||
menuOutsideStore.setCurrentRemoveRoutes(removeRoutes)
|
||||
routeOutsideStore.setCurrentRemoveRoutes(removeRoutes)
|
||||
next({ ...to, replace: true })
|
||||
}
|
||||
} else {
|
||||
|
@ -1,123 +1,10 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { piniaStore } from '@/store'
|
||||
import { resolveRoutePath } from '@/util'
|
||||
import path from 'path-browserify'
|
||||
import { deepClone, resolveRoutePath } from '@/util'
|
||||
import api from '@/api'
|
||||
|
||||
import { useSettingsStore } from './settings'
|
||||
import { useUserStore } from './user'
|
||||
|
||||
function hasPermission(permissions, route) {
|
||||
let isAuth = false
|
||||
if (route.meta && route.meta.auth) {
|
||||
isAuth = permissions.some(auth => {
|
||||
if (typeof route.meta.auth == 'string') {
|
||||
return route.meta.auth === auth
|
||||
} else {
|
||||
return route.meta.auth.some(routeAuth => {
|
||||
return routeAuth === auth
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
isAuth = true
|
||||
}
|
||||
return isAuth
|
||||
}
|
||||
|
||||
function filterAsyncRoutes(routes, permissions) {
|
||||
const res = []
|
||||
routes.forEach(route => {
|
||||
let tmpRoute = deepClone(route)
|
||||
if (hasPermission(permissions, tmpRoute)) {
|
||||
if (tmpRoute.children) {
|
||||
tmpRoute.children = filterAsyncRoutes(tmpRoute.children, permissions)
|
||||
tmpRoute.children.length && res.push(tmpRoute)
|
||||
} else {
|
||||
res.push(tmpRoute)
|
||||
}
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
function formatBackRoutes(routes, views = import.meta.glob('../../views/**/*.vue')) {
|
||||
return routes.map(route => {
|
||||
switch (route.component) {
|
||||
case 'Layout':
|
||||
route.component = () => import('@/layout/index.vue')
|
||||
break
|
||||
default:
|
||||
if (route.component) {
|
||||
route.component = views[`../../views/${route.component}`]
|
||||
}
|
||||
}
|
||||
if (route.children) {
|
||||
route.children = formatBackRoutes(route.children, views)
|
||||
}
|
||||
return route
|
||||
})
|
||||
}
|
||||
|
||||
// 将多层嵌套路由处理成平级
|
||||
function flatAsyncRoutes(routes, breadcrumb, baseUrl = '') {
|
||||
let res = []
|
||||
routes.forEach(route => {
|
||||
if (route.children) {
|
||||
let childrenBaseUrl = ''
|
||||
if (baseUrl == '') {
|
||||
childrenBaseUrl = route.path
|
||||
} else if (route.path != '') {
|
||||
childrenBaseUrl = `${baseUrl}/${route.path}`
|
||||
}
|
||||
let childrenBreadcrumb = deepClone(breadcrumb)
|
||||
if (route.meta.breadcrumb !== false) {
|
||||
childrenBreadcrumb.push({
|
||||
path: childrenBaseUrl,
|
||||
title: route.meta.title
|
||||
})
|
||||
}
|
||||
let tmpRoute = deepClone(route)
|
||||
tmpRoute.path = childrenBaseUrl
|
||||
tmpRoute.meta.breadcrumbNeste = childrenBreadcrumb
|
||||
delete tmpRoute.children
|
||||
res.push(tmpRoute)
|
||||
let childrenRoutes = flatAsyncRoutes(route.children, childrenBreadcrumb, childrenBaseUrl)
|
||||
childrenRoutes.map(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 {
|
||||
let tmpRoute = deepClone(route)
|
||||
if (baseUrl != '') {
|
||||
if (tmpRoute.path != '') {
|
||||
tmpRoute.path = `${baseUrl}/${tmpRoute.path}`
|
||||
} else {
|
||||
tmpRoute.path = baseUrl
|
||||
}
|
||||
}
|
||||
// 处理面包屑导航
|
||||
let tmpBreadcrumb = deepClone(breadcrumb)
|
||||
if (tmpRoute.meta.breadcrumb !== false) {
|
||||
tmpBreadcrumb.push({
|
||||
path: tmpRoute.path,
|
||||
title: tmpRoute.meta.title
|
||||
})
|
||||
}
|
||||
tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
|
||||
res.push(tmpRoute)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
import { useRouteStore } from './route'
|
||||
|
||||
function getDeepestPath(routes, rootPath = '') {
|
||||
let retnPath
|
||||
@ -147,137 +34,49 @@ export const useMenuStore = defineStore(
|
||||
'menu',
|
||||
{
|
||||
state: () => ({
|
||||
isGenerate: false,
|
||||
routes: [],
|
||||
defaultOpenedPaths: [],
|
||||
headerActived: 0,
|
||||
currentRemoveRoutes: []
|
||||
headerActived: 0
|
||||
}),
|
||||
getters: {
|
||||
transformRoutes: state => {
|
||||
let routes
|
||||
// 完整导航数据
|
||||
allMenus() {
|
||||
const settingsStore = useSettingsStore()
|
||||
const routeStore = useRouteStore()
|
||||
let routes
|
||||
if (settingsStore.menu.menuMode === 'single') {
|
||||
routes = [{ children: [] }]
|
||||
state.routes.map(item => {
|
||||
routeStore.routes.map(item => {
|
||||
routes[0].children.push(...item.children)
|
||||
})
|
||||
} else {
|
||||
routes = state.routes
|
||||
routes = routeStore.routes
|
||||
}
|
||||
return routes
|
||||
},
|
||||
sidebarRoutes() {
|
||||
return this.transformRoutes.length > 0 ? this.transformRoutes[this.headerActived].children : []
|
||||
// 次导航数据
|
||||
sidebarMenus() {
|
||||
return this.allMenus.length > 0 ? this.allMenus[this.headerActived].children : []
|
||||
},
|
||||
sidebarRoutesFirstDeepestPath() {
|
||||
return this.transformRoutes.length > 0 ? getDeepestPath(this.sidebarRoutes[0]) : '/'
|
||||
// 次导航里第一个导航的路径
|
||||
sidebarMenusFirstDeepestPath() {
|
||||
return this.allMenus.length > 0 ? getDeepestPath(this.sidebarMenus[0]) : '/'
|
||||
},
|
||||
defaultOpenedPaths() {
|
||||
const routeStore = useRouteStore()
|
||||
let defaultOpenedPaths = []
|
||||
routeStore.routes.map(item => {
|
||||
item.meta.defaultOpened && defaultOpenedPaths.push(item.path)
|
||||
item.children && item.children.map(child => {
|
||||
child.meta.defaultOpened && defaultOpenedPaths.push(path.resolve(item.path, child.path))
|
||||
})
|
||||
})
|
||||
return defaultOpenedPaths
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// 根据权限动态生成路由(前端生成)
|
||||
generateRoutesAtFront(asyncRoutes) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async resolve => {
|
||||
const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
let accessedRoutes
|
||||
// 如果权限功能开启,则需要对路由数据进行筛选过滤
|
||||
if (settingsStore.app.enablePermission) {
|
||||
const permissions = await userStore.getPermissions()
|
||||
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
|
||||
} else {
|
||||
accessedRoutes = deepClone(asyncRoutes)
|
||||
}
|
||||
// 设置 routes 数据
|
||||
this.isGenerate = true
|
||||
let newRoutes = deepClone(accessedRoutes)
|
||||
this.routes = newRoutes.filter(item => {
|
||||
return item.children.length != 0
|
||||
})
|
||||
// 将三级及以上路由数据拍平成二级
|
||||
let routes = []
|
||||
accessedRoutes.map(item => {
|
||||
routes.push(...item.children)
|
||||
})
|
||||
routes.map(item => {
|
||||
if (item.children) {
|
||||
item.children = flatAsyncRoutes(item.children, [{
|
||||
path: item.path,
|
||||
title: item.meta.title
|
||||
}], item.path)
|
||||
}
|
||||
})
|
||||
// 设置 defaultOpenedPaths 数据
|
||||
let defaultOpenedPaths = []
|
||||
routes.map(item => {
|
||||
item.meta.defaultOpened && defaultOpenedPaths.push(item.path)
|
||||
item.children && item.children.map(child => {
|
||||
child.meta.defaultOpened && defaultOpenedPaths.push(path.resolve(item.path, child.path))
|
||||
})
|
||||
})
|
||||
this.defaultOpenedPaths = defaultOpenedPaths
|
||||
resolve(routes)
|
||||
})
|
||||
},
|
||||
// 生成路由(后端获取)
|
||||
generateRoutesAtBack() {
|
||||
return new Promise(resolve => {
|
||||
api.get('route/list', {
|
||||
baseURL: '/mock/'
|
||||
}).then(async res => {
|
||||
const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
let asyncRoutes = formatBackRoutes(res.data)
|
||||
let accessedRoutes
|
||||
// 如果权限功能开启,则需要对路由数据进行筛选过滤
|
||||
if (settingsStore.app.enablePermission) {
|
||||
const permissions = await userStore.getPermissions()
|
||||
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
|
||||
} else {
|
||||
accessedRoutes = deepClone(asyncRoutes)
|
||||
}
|
||||
// 设置 routes 数据
|
||||
this.isGenerate = true
|
||||
let newRoutes = deepClone(accessedRoutes)
|
||||
this.routes = newRoutes.filter(item => {
|
||||
return item.children.length != 0
|
||||
})
|
||||
// 将三级及以上路由数据拍平成二级
|
||||
let routes = []
|
||||
accessedRoutes.map(item => {
|
||||
routes.push(...item.children)
|
||||
})
|
||||
routes.map(item => {
|
||||
if (item.children) {
|
||||
item.children = flatAsyncRoutes(item.children, [{
|
||||
path: item.path,
|
||||
title: item.meta.title
|
||||
}], item.path)
|
||||
}
|
||||
})
|
||||
// 设置 defaultOpenedPaths 数据
|
||||
let defaultOpenedPaths = []
|
||||
routes.map(item => {
|
||||
item.meta.defaultOpened && defaultOpenedPaths.push(item.path)
|
||||
item.children && item.children.map(child => {
|
||||
child.meta.defaultOpened && defaultOpenedPaths.push(path.resolve(item.path, child.path))
|
||||
})
|
||||
})
|
||||
this.defaultOpenedPaths = defaultOpenedPaths
|
||||
resolve(routes)
|
||||
})
|
||||
})
|
||||
},
|
||||
invalidRoutes() {
|
||||
this.isGenerate = false
|
||||
this.routes = []
|
||||
this.defaultOpenedPaths = []
|
||||
this.headerActived = 0
|
||||
},
|
||||
// 根据路由判断属于哪个头部导航
|
||||
setHeaderActived(path) {
|
||||
this.routes.map((item, index) => {
|
||||
const routeStore = useRouteStore()
|
||||
routeStore.routes.map((item, index) => {
|
||||
if (
|
||||
item.children.some(r => {
|
||||
return path.indexOf(r.path + '/') === 0 || path == r.path
|
||||
@ -290,17 +89,6 @@ export const useMenuStore = defineStore(
|
||||
// 切换头部导航
|
||||
switchHeaderActived(index) {
|
||||
this.headerActived = index
|
||||
},
|
||||
// 记录 accessRoutes 路由,用于登出时删除路由
|
||||
setCurrentRemoveRoutes(routes) {
|
||||
this.currentRemoveRoutes = routes
|
||||
},
|
||||
// 清空动态路由
|
||||
removeRoutes() {
|
||||
this.currentRemoveRoutes.forEach(removeRoute => {
|
||||
removeRoute()
|
||||
})
|
||||
this.currentRemoveRoutes = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
214
src/store/modules/route.js
Normal file
214
src/store/modules/route.js
Normal file
@ -0,0 +1,214 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { piniaStore } from '@/store'
|
||||
import { deepClone, isExternalLink } from '@/util'
|
||||
import api from '@/api'
|
||||
|
||||
import { useSettingsStore } from './settings'
|
||||
import { useUserStore } from './user'
|
||||
|
||||
function hasPermission(permissions, route) {
|
||||
let isAuth = false
|
||||
if (route.meta && route.meta.auth) {
|
||||
isAuth = permissions.some(auth => {
|
||||
if (typeof route.meta.auth == 'string') {
|
||||
return route.meta.auth === auth
|
||||
} else {
|
||||
return route.meta.auth.some(routeAuth => {
|
||||
return routeAuth === auth
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
isAuth = true
|
||||
}
|
||||
return isAuth
|
||||
}
|
||||
|
||||
function filterAsyncRoutes(routes, permissions) {
|
||||
const res = []
|
||||
routes.forEach(route => {
|
||||
let tmpRoute = deepClone(route)
|
||||
if (hasPermission(permissions, tmpRoute)) {
|
||||
if (tmpRoute.children) {
|
||||
tmpRoute.children = filterAsyncRoutes(tmpRoute.children, permissions)
|
||||
tmpRoute.children.length && res.push(tmpRoute)
|
||||
} else {
|
||||
res.push(tmpRoute)
|
||||
}
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
function formatBackRoutes(routes, views = import.meta.glob('../../views/**/*.vue')) {
|
||||
return routes.map(route => {
|
||||
switch (route.component) {
|
||||
case 'Layout':
|
||||
route.component = () => import('@/layout/index.vue')
|
||||
break
|
||||
default:
|
||||
if (route.component) {
|
||||
route.component = views[`../../views/${route.component}`]
|
||||
}
|
||||
}
|
||||
if (route.children) {
|
||||
route.children = formatBackRoutes(route.children, views)
|
||||
}
|
||||
return route
|
||||
})
|
||||
}
|
||||
|
||||
// 将多层嵌套路由处理成平级
|
||||
function flatAsyncRoutes(routes, breadcrumb, baseUrl = '') {
|
||||
let res = []
|
||||
routes.forEach(route => {
|
||||
if (route.children) {
|
||||
let childrenBaseUrl = ''
|
||||
if (baseUrl == '') {
|
||||
childrenBaseUrl = route.path
|
||||
} else if (route.path != '') {
|
||||
childrenBaseUrl = `${baseUrl}/${route.path}`
|
||||
}
|
||||
let childrenBreadcrumb = deepClone(breadcrumb)
|
||||
if (route.meta.breadcrumb !== false) {
|
||||
childrenBreadcrumb.push({
|
||||
path: childrenBaseUrl,
|
||||
title: route.meta.title
|
||||
})
|
||||
}
|
||||
let tmpRoute = deepClone(route)
|
||||
tmpRoute.path = childrenBaseUrl
|
||||
tmpRoute.meta.breadcrumbNeste = childrenBreadcrumb
|
||||
delete tmpRoute.children
|
||||
res.push(tmpRoute)
|
||||
let childrenRoutes = flatAsyncRoutes(route.children, childrenBreadcrumb, childrenBaseUrl)
|
||||
childrenRoutes.map(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 {
|
||||
let tmpRoute = deepClone(route)
|
||||
if (baseUrl != '' && !isExternalLink(tmpRoute.path)) {
|
||||
if (tmpRoute.path != '') {
|
||||
tmpRoute.path = `${baseUrl}/${tmpRoute.path}`
|
||||
} else {
|
||||
tmpRoute.path = baseUrl
|
||||
}
|
||||
}
|
||||
// 处理面包屑导航
|
||||
let tmpBreadcrumb = deepClone(breadcrumb)
|
||||
if (tmpRoute.meta.breadcrumb !== false) {
|
||||
tmpBreadcrumb.push({
|
||||
path: tmpRoute.path,
|
||||
title: tmpRoute.meta.title
|
||||
})
|
||||
}
|
||||
tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
|
||||
res.push(tmpRoute)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
export const useRouteStore = defineStore(
|
||||
// 唯一ID
|
||||
'route',
|
||||
{
|
||||
state: () => ({
|
||||
isGenerate: false,
|
||||
routes: [],
|
||||
currentRemoveRoutes: []
|
||||
}),
|
||||
getters: {
|
||||
// 扁平化路由(将三级及以上路由数据拍平成二级)
|
||||
flatRoutes: state => {
|
||||
let routes = []
|
||||
if (state.routes) {
|
||||
state.routes.map(item => {
|
||||
routes.push(...deepClone(item.children))
|
||||
})
|
||||
routes.map(item => {
|
||||
if (item.children) {
|
||||
item.children = flatAsyncRoutes(item.children, [{
|
||||
path: item.path,
|
||||
title: item.meta.title
|
||||
}], item.path)
|
||||
}
|
||||
})
|
||||
}
|
||||
return routes
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// 根据权限动态生成路由(前端生成)
|
||||
generateRoutesAtFront(asyncRoutes) {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async resolve => {
|
||||
const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
let accessedRoutes
|
||||
// 如果权限功能开启,则需要对路由数据进行筛选过滤
|
||||
if (settingsStore.app.enablePermission) {
|
||||
const permissions = await userStore.getPermissions()
|
||||
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
|
||||
} else {
|
||||
accessedRoutes = deepClone(asyncRoutes)
|
||||
}
|
||||
// 设置 routes 数据
|
||||
this.isGenerate = true
|
||||
this.routes = accessedRoutes.filter(item => item.children.length != 0)
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
// 根据权限动态生成路由(后端获取)
|
||||
generateRoutesAtBack() {
|
||||
return new Promise(resolve => {
|
||||
api.get('route/list', {
|
||||
baseURL: '/mock/'
|
||||
}).then(async res => {
|
||||
const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
let asyncRoutes = formatBackRoutes(res.data)
|
||||
let accessedRoutes
|
||||
// 如果权限功能开启,则需要对路由数据进行筛选过滤
|
||||
if (settingsStore.app.enablePermission) {
|
||||
const permissions = await userStore.getPermissions()
|
||||
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
|
||||
} else {
|
||||
accessedRoutes = deepClone(asyncRoutes)
|
||||
}
|
||||
// 设置 routes 数据
|
||||
this.isGenerate = true
|
||||
this.routes = accessedRoutes.filter(item => item.children.length != 0)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 记录 accessRoutes 路由,用于登出时删除路由
|
||||
setCurrentRemoveRoutes(routes) {
|
||||
this.currentRemoveRoutes = routes
|
||||
},
|
||||
// 清空路由
|
||||
removeRoutes() {
|
||||
this.isGenerate = false
|
||||
this.routes = []
|
||||
this.currentRemoveRoutes.forEach(removeRoute => {
|
||||
removeRoute()
|
||||
})
|
||||
this.currentRemoveRoutes = []
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export function useRouteOutsideStore() {
|
||||
return useRouteStore(piniaStore)
|
||||
}
|
@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import { piniaStore } from '@/store'
|
||||
import api from '@/api'
|
||||
|
||||
import { useRouteStore } from './route'
|
||||
import { useMenuStore } from './menu'
|
||||
|
||||
export const useUserStore = defineStore(
|
||||
@ -46,6 +47,7 @@ export const useUserStore = defineStore(
|
||||
},
|
||||
logout() {
|
||||
return new Promise(resolve => {
|
||||
const routeStore = useRouteStore()
|
||||
const menuStore = useMenuStore()
|
||||
localStorage.removeItem('account')
|
||||
localStorage.removeItem('token')
|
||||
@ -53,8 +55,8 @@ export const useUserStore = defineStore(
|
||||
this.account = ''
|
||||
this.token = ''
|
||||
this.failure_time = ''
|
||||
menuStore.invalidRoutes()
|
||||
menuStore.removeRoutes()
|
||||
routeStore.removeRoutes()
|
||||
menuStore.switchHeaderActived(0)
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user