refactor: 路由多级缓存调整为扁平化方案

This commit is contained in:
2024-05-04 21:45:12 +08:00
parent 308938a0f6
commit 5f3dd93376
5 changed files with 55 additions and 36 deletions

View File

@@ -13,19 +13,32 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { RouteLocationMatched } from 'vue-router' import type { RouteLocationMatched } from 'vue-router'
import { useRouteStore } from '@/stores'
import { findTree } from 'xe-utils'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const { routes } = useRouteStore()
let home: RouteLocationMatched | null = null
const getHome = () => {
if (!home) {
const cloneRoutes = JSON.parse(JSON.stringify(routes)) as RouteLocationMatched[]
const obj = findTree(cloneRoutes, (i) => i.path === '/home')
home = obj.item
}
}
const breadcrumbList = ref<RouteLocationMatched[]>([]) const breadcrumbList = ref<RouteLocationMatched[]>([])
function getBreadcrumbList() { function getBreadcrumbList() {
// 只显示有title标题的 getHome()
const matched = route.matched.filter((item) => item.meta && item.meta.title) const cloneRoutes = JSON.parse(JSON.stringify(routes)) as RouteLocationMatched[]
const first = matched[0] const obj = findTree(cloneRoutes, (i) => i.path === route.path)
if (!isHome(first)) { // 获取当前节点的所有上级节点集合,包含当前节点
matched.unshift({ path: '/', meta: { title: '首页' } } as RouteLocationMatched) const arr = obj.nodes.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false)
if (home) {
breadcrumbList.value = [home, ...arr]
} }
breadcrumbList.value = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false)
} }
getBreadcrumbList() getBreadcrumbList()
@@ -34,13 +47,6 @@ watchEffect(() => {
getBreadcrumbList() getBreadcrumbList()
}) })
// 判断是否为首页
function isHome(route: RouteLocationMatched) {
const name = (route?.name as string) || ''
if (!name) return false
return name.trim() === 'Home'
}
// 路由跳转 // 路由跳转
function handleLink(item: RouteLocationMatched) { function handleLink(item: RouteLocationMatched) {
const { redirect, path } = item const { redirect, path } = item

View File

@@ -1,9 +1,9 @@
<template> <template>
<a-layout class="main"> <a-layout class="main">
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
<transition :name="transitionName(route)" mode="out-in" appear> <transition :name="appStore.transitionName" mode="out-in" appear>
<keep-alive :include="(tabsStore.cacheList as string[])"> <keep-alive :include="(tabsStore.cacheList as string[])">
<component :is="Component" :key="route.matched?.[1]?.path" /> <component :is="Component" :key="route.path" />
</keep-alive> </keep-alive>
</transition> </transition>
</router-view> </router-view>
@@ -11,22 +11,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useAppStore, useTabsStore } from '@/stores' import { useAppStore, useTabsStore } from '@/stores'
defineOptions({ name: 'Main' }) defineOptions({ name: 'Main' })
const appStore = useAppStore() const appStore = useAppStore()
const tabsStore = useTabsStore() const tabsStore = useTabsStore()
// 过渡动画
const transitionName = computed(() => {
return function (route: RouteLocationNormalizedLoaded) {
if (route?.matched?.[1]?.meta?.animation === false) {
return ''
}
return appStore.transitionName
}
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,13 +1,15 @@
import { ref, toRaw } from 'vue' import { ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import { constantRoutes } from '@/router' import { constantRoutes } from '@/router'
import Layout from '@/layout/index.vue'
import ParentView from '@/components/ParentView/index.vue' import ParentView from '@/components/ParentView/index.vue'
import { getUserRoute, type RouteItem } from '@/apis' import { getUserRoute, type RouteItem } from '@/apis'
import { mapTree } from 'xe-utils' import { mapTree, toTreeArray } from 'xe-utils'
import { cloneDeep, omit } from 'lodash-es'
import { transformPathToName } from '@/utils' import { transformPathToName } from '@/utils'
const Layout = () => import('@/layout/index.vue')
// 匹配views里面所有的.vue文件 // 匹配views里面所有的.vue文件
const modules = import.meta.glob('@/views/**/*.vue') const modules = import.meta.glob('@/views/**/*.vue')
@@ -64,6 +66,28 @@ const formatAsyncRoutes = (menus: RouteItem[]) => {
return routes as RouteRecordRaw[] return routes as RouteRecordRaw[]
} }
/** 判断路由层级是否大于 2 */
export const isMultipleRoute = (route: RouteRecordRaw) => {
const children = route.children
if (children?.length) {
// 只要有一个子路由的 children 长度大于 0就说明是三级及其以上路由
return children.some((child) => child.children?.length)
}
return false
}
/** 路由降级(把三级及其以上的路由转化为二级路由) */
export const flatMultiLevelRoutes = (routes: RouteRecordRaw[]) => {
const cloneRoutes = cloneDeep(routes)
cloneRoutes.forEach((route) => {
if (isMultipleRoute(route)) {
const flatRoutes = toTreeArray(route.children)
route.children = flatRoutes.map((i) => omit(i, 'children')) as RouteRecordRaw[]
}
})
return cloneRoutes
}
const storeSetup = () => { const storeSetup = () => {
// 所有路由(常驻路由 + 动态路由) // 所有路由(常驻路由 + 动态路由)
const routes = ref<RouteRecordRaw[]>([]) const routes = ref<RouteRecordRaw[]>([])
@@ -74,7 +98,6 @@ const storeSetup = () => {
const setRoutes = (data: RouteRecordRaw[]) => { const setRoutes = (data: RouteRecordRaw[]) => {
routes.value = constantRoutes.concat(data) routes.value = constantRoutes.concat(data)
asyncRoutes.value = data asyncRoutes.value = data
console.log('路由', toRaw(routes.value))
} }
// 生成路由 // 生成路由
@@ -84,7 +107,9 @@ const storeSetup = () => {
getUserRoute().then((res) => { getUserRoute().then((res) => {
const asyncRoutes = formatAsyncRoutes(res.data) const asyncRoutes = formatAsyncRoutes(res.data)
setRoutes(asyncRoutes) setRoutes(asyncRoutes)
resolve(asyncRoutes) const cloneRoutes = cloneDeep(asyncRoutes)
const flatRoutes = flatMultiLevelRoutes(cloneRoutes as RouteRecordRaw[])
resolve(flatRoutes)
}) })
}) })
} }

View File

@@ -10,7 +10,8 @@ const storeSetup = () => {
const cacheList = ref<RouteRecordName[]>([]) // keep-alive缓存的数组, 元素是组件名 const cacheList = ref<RouteRecordName[]>([]) // keep-alive缓存的数组, 元素是组件名
// 添加一个页签, 如果当前路由已经打开, 则不再重复添加 // 添加一个页签, 如果当前路由已经打开, 则不再重复添加
const addTagItem = (item: RouteRecordRaw) => { const addTagItem = (route: RouteRecordRaw) => {
const item = JSON.parse(JSON.stringify(route))
if (tagList.value.some((i) => i.path === item.path)) return if (tagList.value.some((i) => i.path === item.path)) return
if (item.meta?.showInTabs ?? true) { if (item.meta?.showInTabs ?? true) {
tagList.value.push(item) tagList.value.push(item)
@@ -35,7 +36,7 @@ const storeSetup = () => {
const arr: RouteRecordRaw[] = [] const arr: RouteRecordRaw[] = []
_XEUtils_.eachTree(routeStore.routes, (item) => { _XEUtils_.eachTree(routeStore.routes, (item) => {
if (item.meta?.affix ?? false) { if (item.meta?.affix ?? false) {
arr.push(item) arr.push(JSON.parse(JSON.stringify(item)))
} }
}) })
tagList.value = arr tagList.value = arr

View File

@@ -11,7 +11,7 @@ declare module 'vue-router' {
/** 默认false, 设置true的时候该路由不会在侧边栏出现 */ /** 默认false, 设置true的时候该路由不会在侧边栏出现 */
hidden?: boolean hidden?: boolean
/** 默认true, 如果设置为false, 则不会在面包屑中显示 */ /** 默认true, 如果设置为false, 则不会在面包屑中显示 */
breadcrumb?: false breadcrumb?: boolean
/** 默认true, 如果设置为false, 它则不会显示在Tab栏中 */ /** 默认true, 如果设置为false, 它则不会显示在Tab栏中 */
showInTabs?: boolean showInTabs?: boolean
/** 默认false, 如果设置为true, 它则会固定在Tab栏中, 例如首页 */ /** 默认false, 如果设置为true, 它则会固定在Tab栏中, 例如首页 */
@@ -39,8 +39,6 @@ declare module 'vue-router' {
noShowingChildren?: boolean noShowingChildren?: boolean
/** 设置该路由进入的权限, 支持多个权限叠加 */ /** 设置该路由进入的权限, 支持多个权限叠加 */
roles?: string[] roles?: string[]
/** 路由切换是否使用动画 */
animation?: boolean
/** 排序 */ /** 排序 */
sort?: number sort?: number
} }