vite-plugin-pages 路由高级配置 参考

本文介绍了如何在 Vue 项目中利用 vite-plugin-pages 动态生成路由,以及结合 vuex 进行权限管理和路由过滤。通过设置 constantRoutes 和 asyncRoutes,根据文件系统、前端或后端动态生成路由,并在 beforeEach 钩子中处理权限,实现动态加载和权限校验。同时,展示了如何根据用户权限过滤路由,并提供了相应的 vuex 模块和 action 方法。
摘要由CSDN通过智能技术生成

vite-plugin-pages 路由高级配置 参考

简介

vue-router是来动态生成路由信息的插件,可以根据对应的文件结构来生成路由信息,不再需要手动的去配置路由.Nuxt中就是相似的方式来实现路由,可以通过nuxtStyle配置项来使用nuxt的方式来设置文件结构.

案例

例如我的vue目录如下:

image-20220301135354370

页面效果

image-20220301135443850

路由文件这样配置

image-20220301135502060

所有在 pages页面下的文件都会自动添加到generatedRoutes里,那怎么对generatedRoutes进行配置,比如添加常路由,异步路由,权限管理?

先来打印下 这个所谓的generatedRoutes

image-20220301140720925

发现和我们手动引用的区别不大,下一步就可以使用vuerouter方法进行处理路由了

参照的项目是fantastic-admin-master Fantastic-admin: ⭐⭐⭐⭐⭐)

1.区分 constantRoutesasyncRoutes

import { setupLayouts } from 'virtual:generated-layouts'
import generatedRoutes from 'virtual:generated-pages'

if (useSettingsOutsideStore().app.routeBaseOn === 'filesystem') {
    constantRoutes = generatedRoutes.filter(item => {
        return item.meta?.enabled !== false && item.meta?.constant === true
    })
    asyncRoutes = setupLayouts(generatedRoutes.filter(item => {
        return item.meta?.enabled !== false && item.meta?.constant !== true && item.meta?.layout !== false
    }))
}

2.router.beforeEach拦截

注意 routeOutsideStore.generateRoutesAtFront(asyncRoutes) 这个方法,动态加载

 switch (settingsOutsideStore.app.routeBaseOn) {
                case 'frontend':
                    await routeOutsideStore.generateRoutesAtFront(asyncRoutes)
                    break
                case 'backend':
                    await routeOutsideStore.generateRoutesAtBack()
                    break
                case 'filesystem':
                    await routeOutsideStore.generateRoutesAtFilesystem(asyncRoutes)
                    switch (settingsOutsideStore.menu.baseOn) {
                        case 'frontend':
                            await menuOutsideStore.generateMenusAtFront()
                            break
                        case 'backend':
                            await menuOutsideStore.generateMenusAtBack()
                            break
                    }
                    break
            }

在vuex实现,几个方法

    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()
                    })
                })
            },
            // 根据权限动态生成路由(文件系统生成)
            generateRoutesAtFilesystem(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()
                })
            },
            // 记录 accessRoutes 路由,用于登出时删除路由
            setCurrentRemoveRoutes(routes) {
                this.currentRemoveRoutes = routes
            },
            // 清空路由
            removeRoutes() {
                this.isGenerate = false
                this.routes = []
                this.currentRemoveRoutes.forEach(removeRoute => {
                    removeRoute()
                })
                this.currentRemoveRoutes = []
            }
        }

filterAsyncRoutes 方法

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
}

参考

1.src\store\modules\route.js

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 => {
                const settingsStore = useSettingsStore()
                let routes = []
                if (state.routes) {
                    if (settingsStore.app.routeBaseOn !== 'filesystem') {
                        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)
                            }
                        })
                    } else {
                        state.routes.map(item => {
                            routes.push(deepClone(item))
                        })
                    }
                }
                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()
                    })
                })
            },
            // 根据权限动态生成路由(文件系统生成)
            generateRoutesAtFilesystem(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()
                })
            },
            // 记录 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.src\router\index.js

import { createRouter, createWebHashHistory } from 'vue-router'
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'
const { isLoading } = useNProgress()

// 固定路由
let constantRoutes = [
    {
        path: '/login',
        name: 'login',
        component: () => import('@/views/login.vue'),
        meta: {
            title: '登录'
        }
    },
    {
        path: '/',
        component: () => import('@/layout/index.vue'),
        redirect: '/dashboard',
        children: [
            {
                path: 'dashboard',
                name: 'dashboard',
                component: () => import('@/views/index.vue'),
                meta: {
                    title: () => {
                        const settingsOutsideStore = useSettingsOutsideStore()
                        return settingsOutsideStore.dashboard.title
                    }
                }
            },
            {
                path: 'personal/setting',
                name: 'personalSetting',
                component: () => import('@/views/personal/setting.vue'),
                meta: {
                    title: '个人设置',
                    cache: 'personalEditPassword',
                    breadcrumbNeste: [
                        { title: '个人设置', path: '/personal/setting' }
                    ]
                }
            },
            {
                path: 'personal/edit/password',
                name: 'personalEditPassword',
                component: () => import('@/views/personal/edit.password.vue'),
                meta: {
                    title: '修改密码',
                    breadcrumbNeste: [
                        { title: '修改密码', path: '/personal/edit/password' }
                    ]
                }
            },
            {
                path: 'reload',
                name: 'reload',
                component: () => import('@/views/reload.vue')
            }
        ]
    }
]

import MultilevelMenuExample from './modules/multilevel.menu.example'
import BreadcrumbExample from './modules/breadcrumb.example'
import KeepAliveExample from './modules/keep.alive.example'
import ComponentBasicExample from './modules/component.basic.example'
import ComponentExtendExample from './modules/component.extend.example'
import PermissionExample from './modules/permission.example'
import MockExample from './modules/mock.example'
import JsxExample from './modules/jsx.example'
import ExternalLinkExample from './modules/external.link.example'
// import VideosExample from './modules/videos.example'
import EcologyExample from './modules/ecology.example'
import CooperationExample from './modules/cooperation.example'

// 动态路由(异步路由、导航栏路由)
let asyncRoutes = [
    {
        meta: {
            title: '演示',
            icon: 'sidebar-default'
        },
        children: [
            MultilevelMenuExample,
            BreadcrumbExample,
            KeepAliveExample,
            ComponentBasicExample,
            ComponentExtendExample,
            PermissionExample,
            MockExample,
            JsxExample,
            ExternalLinkExample
        ]
    },
    // {
    //     meta: {
    //         title: '教程',
    //         icon: 'sidebar-videos'
    //     },
    //     children: [
    //         ...VideosExample
    //     ]
    // },
    {
        meta: {
            title: '生态',
            icon: 'sidebar-ecology'
        },
        children: [
            ...EcologyExample
        ]
    },
    {
        meta: {
            title: '战略合作',
            icon: 'sidebar-cooperation'
        },
        children: [
            ...CooperationExample
        ]
    }
]

const lastRoute = {
    path: '/:all(.*)*',
    name: 'notFound',
    component: () => import('@/views/[...all].vue'),
    meta: {
        title: '找不到页面'
    }
}

import { setupLayouts } from 'virtual:generated-layouts'
import generatedRoutes from 'virtual:generated-pages'

if (useSettingsOutsideStore().app.routeBaseOn === 'filesystem') {
    constantRoutes = generatedRoutes.filter(item => {
        return item.meta?.enabled !== false && item.meta?.constant === true
    })
    asyncRoutes = setupLayouts(generatedRoutes.filter(item => {
        return item.meta?.enabled !== false && item.meta?.constant !== true && item.meta?.layout !== false
    }))
}

const router = createRouter({
    history: createWebHashHistory(),
    routes: constantRoutes
})

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 (routeOutsideStore.isGenerate) {
            // 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态
            settingsOutsideStore.menu.menuMode !== 'single' && menuOutsideStore.setActived(to.path)
            if (to.name) {
                if (to.matched.length !== 0) {
                    // 如果已登录状态下,进入登录页会强制跳转到控制台页面
                    if (to.name == 'login') {
                        next({
                            name: 'dashboard',
                            replace: true
                        })
                    } else if (!settingsOutsideStore.dashboard.enable && to.name == 'dashboard') {
                        // 如果未开启控制台页面,则默认进入侧边栏导航第一个模块
                        if (menuOutsideStore.sidebarMenus.length > 0) {
                            next({
                                path: menuOutsideStore.sidebarMenusFirstDeepestPath,
                                replace: true
                            })
                        } else {
                            next()
                        }
                    } else {
                        next()
                    }
                } else {
                    // 如果是通过 name 跳转,并且 name 对应的路由没有权限时,需要做这步处理,手动指向到 404 页面
                    next({
                        path: '/404'
                    })
                }
            } else {
                next()
            }
        } else {
            switch (settingsOutsideStore.app.routeBaseOn) {
                case 'frontend':
                    await routeOutsideStore.generateRoutesAtFront(asyncRoutes)
                    break
                case 'backend':
                    await routeOutsideStore.generateRoutesAtBack()
                    break
                case 'filesystem':
                    await routeOutsideStore.generateRoutesAtFilesystem(asyncRoutes)
                    switch (settingsOutsideStore.menu.baseOn) {
                        case 'frontend':
                            await menuOutsideStore.generateMenusAtFront()
                            break
                        case 'backend':
                            await menuOutsideStore.generateMenusAtBack()
                            break
                    }
                    break
            }
            let removeRoutes = []
            routeOutsideStore.flatRoutes.forEach(route => {
                if (!/^(https?:|mailto:|tel:)/.test(route.path)) {
                    removeRoutes.push(router.addRoute(route))
                }
            })
            if (settingsOutsideStore.app.routeBaseOn === 'filesystem') {
                const otherRoutes = generatedRoutes.filter(item => item.meta?.constant !== true && item.meta?.layout === false)
                otherRoutes.length && removeRoutes.push(router.addRoute(...otherRoutes))
            } else {
                removeRoutes.push(router.addRoute(lastRoute))
            }
            // 记录的 accessRoutes 路由数据,在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
            routeOutsideStore.setCurrentRemoveRoutes(removeRoutes)
            next({ ...to, replace: true })
        }
    } else {
        if (to.name != 'login') {
            next({
                name: 'login',
                query: {
                    redirect: to.fullPath
                }
            })
        } else {
            next()
        }
    }
})

router.afterEach((to, from) => {
    const settingsOutsideStore = useSettingsOutsideStore()
    const keepAliveOutsideStore = useKeepAliveOutsideStore()
    settingsOutsideStore.app.enableProgress && (isLoading.value = false)
    // 设置页面 title
    to.meta.title && settingsOutsideStore.setTitle(typeof to.meta.title === 'function' ? to.meta.title() : to.meta.title)
    // 判断当前页面是否开启缓存,如果开启,则将当前页面的 name 信息存入 keep-alive 全局状态
    if (to.meta.cache) {
        let componentName = to.matched[to.matched.length - 1].components.default.name
        if (componentName) {
            keepAliveOutsideStore.add(componentName)
        } else {
            console.warn('该页面组件未设置组件名,会导致缓存失效,请检查')
        }
    }
    // 判断离开页面是否开启缓存,如果开启,则根据缓存规则判断是否需要清空 keep-alive 全局状态里离开页面的 name 信息
    if (from.meta.cache) {
        let componentName = from.matched[from.matched.length - 1].components.default.name
        // 通过 meta.cache 判断针对哪些页面进行缓存
        switch (typeof from.meta.cache) {
            case 'string':
                if (from.meta.cache != to.name) {
                    keepAliveOutsideStore.remove(componentName)
                }
                break
            case 'object':
                if (!from.meta.cache.includes(to.name)) {
                    keepAliveOutsideStore.remove(componentName)
                }
                break
        }
        // 如果进入的是 reload 页面,则也将离开页面的缓存清空
        if (to.name == 'reload') {
            keepAliveOutsideStore.remove(componentName)
        }
    }
})

export default router

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值