mirror of
https://gitee.com/fantastic-admin/basic.git
synced 2024-12-05 13:37:45 +08:00
优化全局状态,将导航和路由拆分成menu和route分别管理
This commit is contained in:
parent
c8e2d48f3a
commit
151e510e2c
@ -6,7 +6,7 @@
|
|||||||
<Logo />
|
<Logo />
|
||||||
<!-- 顶部模式 -->
|
<!-- 顶部模式 -->
|
||||||
<div class="nav">
|
<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)">
|
<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" />
|
<svg-icon v-if="item.meta.icon" :name="item.meta.icon" />
|
||||||
<span v-if="item.meta.title">{{ item.meta.title }}</span>
|
<span v-if="item.meta.title">{{ item.meta.title }}</span>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<Logo :show-title="false" class="sidebar-logo" />
|
<Logo :show-title="false" class="sidebar-logo" />
|
||||||
<!-- 侧边栏模式(含主导航) -->
|
<!-- 侧边栏模式(含主导航) -->
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<template v-for="(item, index) in menuStore.transformRoutes">
|
<template v-for="(item, index) in menuStore.allMenus">
|
||||||
<div
|
<div
|
||||||
v-if="item.children && item.children.length !== 0" :key="index" :class="{
|
v-if="item.children && item.children.length !== 0" :key="index" :class="{
|
||||||
'item': true,
|
'item': true,
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="breadcrumb">
|
<div class="breadcrumb">
|
||||||
<span v-for="(bc, bcIndex) in item.breadcrumb" :key="bcIndex">
|
<span v-for="(bc, bcIndex) in item.breadcrumb" :key="bcIndex">
|
||||||
{{ bc }}
|
{{ bc.title }}
|
||||||
<svg-icon name="el-icon-arrow-right" />
|
<svg-icon name="el-icon-arrow-right" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -52,20 +52,50 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { deepClone, isExternalLink } from '@/util'
|
import { isExternalLink } from '@/util'
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance()
|
const { proxy } = getCurrentInstance()
|
||||||
|
|
||||||
import { useSettingsStore } from '@/store/modules/settings'
|
import { useSettingsStore } from '@/store/modules/settings'
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
import { useMenuStore } from '@/store/modules/menu'
|
import { useRouteStore } from '@/store/modules/route'
|
||||||
const menuStore = useMenuStore()
|
const routeStore = useRouteStore()
|
||||||
|
|
||||||
const isShow = ref(false)
|
const isShow = ref(false)
|
||||||
const searchInput = ref('')
|
const searchInput = ref('')
|
||||||
const sourceList = ref([])
|
|
||||||
const actived = ref(-1)
|
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(() => {
|
const resultList = computed(() => {
|
||||||
let result = []
|
let result = []
|
||||||
result = sourceList.value.filter(item => {
|
result = sourceList.value.filter(item => {
|
||||||
@ -76,7 +106,7 @@ const resultList = computed(() => {
|
|||||||
if (item.path.indexOf(searchInput.value) >= 0) {
|
if (item.path.indexOf(searchInput.value) >= 0) {
|
||||||
flag = true
|
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
|
flag = true
|
||||||
}
|
}
|
||||||
return flag
|
return flag
|
||||||
@ -121,59 +151,8 @@ onMounted(() => {
|
|||||||
isShow.value = true
|
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() {
|
function keyUp() {
|
||||||
if (resultList.value.length) {
|
if (resultList.value.length) {
|
||||||
actived.value -= 1
|
actived.value -= 1
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<transition-group name="sub-sidebar">
|
<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" />
|
<SidebarItem v-if="route.meta.sidebar !== false" :key="route.path" :item="route" :base-path="route.path" />
|
||||||
</template>
|
</template>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
|
@ -92,10 +92,10 @@ provide('switchMenu', switchMenu)
|
|||||||
function switchMenu(index) {
|
function switchMenu(index) {
|
||||||
menuStore.switchHeaderActived(index)
|
menuStore.switchHeaderActived(index)
|
||||||
if (settingsStore.menu.switchMainMenuAndPageJump) {
|
if (settingsStore.menu.switchMainMenuAndPageJump) {
|
||||||
if (isExternalLink(menuStore.sidebarRoutesFirstDeepestPath)) {
|
if (isExternalLink(menuStore.sidebarMenusFirstDeepestPath)) {
|
||||||
window.open(menuStore.sidebarRoutesFirstDeepestPath, '_blank')
|
window.open(menuStore.sidebarMenusFirstDeepestPath, '_blank')
|
||||||
} else {
|
} 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 { useKeepAliveOutsideStore } from '@/store/modules/keepAlive'
|
||||||
import { useUserOutsideStore } from '@/store/modules/user'
|
import { useUserOutsideStore } from '@/store/modules/user'
|
||||||
import { useMenuOutsideStore } from '@/store/modules/menu'
|
import { useMenuOutsideStore } from '@/store/modules/menu'
|
||||||
|
import { useRouteOutsideStore } from '@/store/modules/route'
|
||||||
|
|
||||||
import '@/assets/styles/nprogress.scss'
|
import '@/assets/styles/nprogress.scss'
|
||||||
import { useNProgress } from '@vueuse/integrations/useNProgress'
|
import { useNProgress } from '@vueuse/integrations/useNProgress'
|
||||||
@ -144,11 +145,12 @@ router.beforeEach(async(to, from, next) => {
|
|||||||
const settingsOutsideStore = useSettingsOutsideStore()
|
const settingsOutsideStore = useSettingsOutsideStore()
|
||||||
const userOutsideStore = useUserOutsideStore()
|
const userOutsideStore = useUserOutsideStore()
|
||||||
const menuOutsideStore = useMenuOutsideStore()
|
const menuOutsideStore = useMenuOutsideStore()
|
||||||
|
const routeOutsideStore = useRouteOutsideStore()
|
||||||
settingsOutsideStore.app.enableProgress && (isLoading.value = true)
|
settingsOutsideStore.app.enableProgress && (isLoading.value = true)
|
||||||
// 是否已登录
|
// 是否已登录
|
||||||
if (userOutsideStore.isLogin) {
|
if (userOutsideStore.isLogin) {
|
||||||
// 是否已根据权限动态生成并挂载路由
|
// 是否已根据权限动态生成并挂载路由
|
||||||
if (menuOutsideStore.isGenerate) {
|
if (routeOutsideStore.isGenerate) {
|
||||||
// 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态
|
// 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态
|
||||||
settingsOutsideStore.menu.menuMode !== 'single' && menuOutsideStore.setHeaderActived(to.path)
|
settingsOutsideStore.menu.menuMode !== 'single' && menuOutsideStore.setHeaderActived(to.path)
|
||||||
if (to.name) {
|
if (to.name) {
|
||||||
@ -161,9 +163,9 @@ router.beforeEach(async(to, from, next) => {
|
|||||||
})
|
})
|
||||||
} else if (!settingsOutsideStore.dashboard.enable && to.name == 'dashboard') {
|
} else if (!settingsOutsideStore.dashboard.enable && to.name == 'dashboard') {
|
||||||
// 如果未开启控制台页面,则默认进入侧边栏导航第一个模块
|
// 如果未开启控制台页面,则默认进入侧边栏导航第一个模块
|
||||||
if (menuOutsideStore.sidebarRoutes.length > 0) {
|
if (menuOutsideStore.sidebarMenus.length > 0) {
|
||||||
next({
|
next({
|
||||||
path: menuOutsideStore.sidebarRoutesFirstDeepestPath,
|
path: menuOutsideStore.sidebarMenusFirstDeepestPath,
|
||||||
replace: true
|
replace: true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -182,21 +184,20 @@ router.beforeEach(async(to, from, next) => {
|
|||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let accessRoutes = []
|
|
||||||
if (!settingsOutsideStore.app.enableBackendReturnRoute) {
|
if (!settingsOutsideStore.app.enableBackendReturnRoute) {
|
||||||
accessRoutes = await menuOutsideStore.generateRoutesAtFront(asyncRoutes)
|
await routeOutsideStore.generateRoutesAtFront(asyncRoutes)
|
||||||
} else {
|
} else {
|
||||||
accessRoutes = await menuOutsideStore.generateRoutesAtBack()
|
await routeOutsideStore.generateRoutesAtBack()
|
||||||
}
|
}
|
||||||
accessRoutes.push(lastRoute)
|
|
||||||
let removeRoutes = []
|
let removeRoutes = []
|
||||||
accessRoutes.forEach(route => {
|
routeOutsideStore.flatRoutes.forEach(route => {
|
||||||
if (!/^(https?:|mailto:|tel:)/.test(route.path)) {
|
if (!/^(https?:|mailto:|tel:)/.test(route.path)) {
|
||||||
removeRoutes.push(router.addRoute(route))
|
removeRoutes.push(router.addRoute(route))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
removeRoutes.push(router.addRoute(lastRoute))
|
||||||
// 记录的 accessRoutes 路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
|
// 记录的 accessRoutes 路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
|
||||||
menuOutsideStore.setCurrentRemoveRoutes(removeRoutes)
|
routeOutsideStore.setCurrentRemoveRoutes(removeRoutes)
|
||||||
next({ ...to, replace: true })
|
next({ ...to, replace: true })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,123 +1,10 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { piniaStore } from '@/store'
|
import { piniaStore } from '@/store'
|
||||||
|
import { resolveRoutePath } from '@/util'
|
||||||
import path from 'path-browserify'
|
import path from 'path-browserify'
|
||||||
import { deepClone, resolveRoutePath } from '@/util'
|
|
||||||
import api from '@/api'
|
|
||||||
|
|
||||||
import { useSettingsStore } from './settings'
|
import { useSettingsStore } from './settings'
|
||||||
import { useUserStore } from './user'
|
import { useRouteStore } from './route'
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeepestPath(routes, rootPath = '') {
|
function getDeepestPath(routes, rootPath = '') {
|
||||||
let retnPath
|
let retnPath
|
||||||
@ -147,137 +34,49 @@ export const useMenuStore = defineStore(
|
|||||||
'menu',
|
'menu',
|
||||||
{
|
{
|
||||||
state: () => ({
|
state: () => ({
|
||||||
isGenerate: false,
|
headerActived: 0
|
||||||
routes: [],
|
|
||||||
defaultOpenedPaths: [],
|
|
||||||
headerActived: 0,
|
|
||||||
currentRemoveRoutes: []
|
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
transformRoutes: state => {
|
// 完整导航数据
|
||||||
let routes
|
allMenus() {
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
const routeStore = useRouteStore()
|
||||||
|
let routes
|
||||||
if (settingsStore.menu.menuMode === 'single') {
|
if (settingsStore.menu.menuMode === 'single') {
|
||||||
routes = [{ children: [] }]
|
routes = [{ children: [] }]
|
||||||
state.routes.map(item => {
|
routeStore.routes.map(item => {
|
||||||
routes[0].children.push(...item.children)
|
routes[0].children.push(...item.children)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
routes = state.routes
|
routes = routeStore.routes
|
||||||
}
|
}
|
||||||
return 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: {
|
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) {
|
setHeaderActived(path) {
|
||||||
this.routes.map((item, index) => {
|
const routeStore = useRouteStore()
|
||||||
|
routeStore.routes.map((item, index) => {
|
||||||
if (
|
if (
|
||||||
item.children.some(r => {
|
item.children.some(r => {
|
||||||
return path.indexOf(r.path + '/') === 0 || path == r.path
|
return path.indexOf(r.path + '/') === 0 || path == r.path
|
||||||
@ -290,17 +89,6 @@ export const useMenuStore = defineStore(
|
|||||||
// 切换头部导航
|
// 切换头部导航
|
||||||
switchHeaderActived(index) {
|
switchHeaderActived(index) {
|
||||||
this.headerActived = 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 { piniaStore } from '@/store'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
|
||||||
|
import { useRouteStore } from './route'
|
||||||
import { useMenuStore } from './menu'
|
import { useMenuStore } from './menu'
|
||||||
|
|
||||||
export const useUserStore = defineStore(
|
export const useUserStore = defineStore(
|
||||||
@ -46,6 +47,7 @@ export const useUserStore = defineStore(
|
|||||||
},
|
},
|
||||||
logout() {
|
logout() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
const routeStore = useRouteStore()
|
||||||
const menuStore = useMenuStore()
|
const menuStore = useMenuStore()
|
||||||
localStorage.removeItem('account')
|
localStorage.removeItem('account')
|
||||||
localStorage.removeItem('token')
|
localStorage.removeItem('token')
|
||||||
@ -53,8 +55,8 @@ export const useUserStore = defineStore(
|
|||||||
this.account = ''
|
this.account = ''
|
||||||
this.token = ''
|
this.token = ''
|
||||||
this.failure_time = ''
|
this.failure_time = ''
|
||||||
menuStore.invalidRoutes()
|
routeStore.removeRoutes()
|
||||||
menuStore.removeRoutes()
|
menuStore.switchHeaderActived(0)
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user