vite+vue+ts+element-plus从零开发管理后台框架(06)-菜单和路由动态生成

因为权限的问题,角色不同显示的菜单也不一样,所以需要动态生成。

图标组件

安装

npm install @element-plus/icons-vue@2.3.1

编辑src/main.ts,注册所有图标。

import './style.css'

import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)

for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

app.use(ElementPlus).use(router)

配置和生成

新建src/types/index.d.ts,内容如下,主要定义菜单的数据类型。

interface IMenu {
    name: string
    desc: string

    key?: number[] //菜单权限(1:管理员|11:游客),没有配置或者为空数组则所有角色都有权限
    route?: string //路由
    children?: IMenu[] //子菜单
    redirect?: string //如果有子菜单,要重定向到第一个子菜单
    icon?: string //菜单图标(一级菜单才有)
}

新建src/data/menu.ts,内容如下。

const menus: IMenu[] = [
    {
        name: 'Home',
        desc: '首页',
        icon: 'House',
    },
    {
        name: 'sys',
        desc: '系统',
        children: [
            {
                name: 'AdmUserPassword',
                desc: '密码更新',
                key: [1, 11]
            },
            {
                name: 'AdmUser',
                desc: '管理员',
                key: [1]
            },
        ],
        icon: 'User',
    },
    {
        name: 'log',
        desc: '日志',
        children: [
            {
                name: 'AdmUserLogin',
                desc: '管理员登录',
                key: [1],
            },
        ],
        icon: 'Notification',
    },
]


export default menus

新建src/util/menu.ts,内容如下,用来生成菜单和路由数据。

import menus from "@/data/menu"
import { RouteRecordRaw } from "vue-router"


const views = import.meta.glob('@/views/**/*.vue')


export const gen = (userType: number): [IMenu[], RouteRecordRaw[]] => {
    return gen2(userType, menus, '/', [], [])
}


const gen2 = (userType: number, menus: IMenu[], parentPath: string, genMenus: IMenu[], genRoutes: RouteRecordRaw[]): [IMenu[], RouteRecordRaw[]] => {
    for (let i = 0; i < menus.length; i++) {
        const menuTmp = menus[i]

        if (menuTmp.key && menuTmp.key.length > 0 && menuTmp.key.indexOf(userType) < 0) continue

        const menu = { ...menuTmp }
        menu.route = parentPath + menu.name

        if (menu.children) {
            const [genMenusChild, genRoutesChild] = gen2(userType, menu.children, menu.route + '/', [], [])
            if (genMenusChild.length > 0) {
                menu.children = genMenusChild

                genMenus.push(menu)
                genRoutes.push({
                    path: menu.route,
                    name: menu.name,
                    redirect: genMenusChild[0].route,
                    children: genRoutesChild,
                })
            }
        } else {
            genMenus.push(menu)
            genRoutes.push({
                path: menu.route,
                name: menu.name,
                component: views[`/src/views${menu.route}.vue`],
                meta: {
                    desc: menu.desc
                },
            })
        }
    }

    return [genMenus, genRoutes]
}

验证

编辑src/views/Main.vuescript段修改如下,这里是获取管理员角色的数据。

<script setup lang="ts">
import { useRoute } from 'vue-router'

import * as MenuUtil from '@/util/menu'


const route = useRoute()

const [menus, routes] = MenuUtil.gen(1)
console.log('menus:', menus)
console.log('routes:', routes)
</script>

浏览器查看菜单和路由数据

管理员菜单数据

管理员路由数据

MenuUtil.gen(1)改成MenuUtil.gen(11)获取游客数据并在浏览器查看

游客菜单数据

发现不同角色显示的菜单数据是不一样的,说明动态生成没问题了。

使用动态生成的菜单和路由数据

菜单

编辑src/views/Main.vuescript段修改如下,这里使用管理员角色。

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'

import * as MenuUtil from '@/util/menu'


const route = useRoute()


const menus = ref<IMenu[]>([])


onMounted(() => {
    const [genMenus, _genRoutes] = MenuUtil.gen(1)

    menus.value = genMenus
})
</script>

templateel-menu修改后如下

<el-menu router :default-active="route.path">
    <template v-for="menu in menus">
        <el-sub-menu v-if="menu.children" :key="menu.route" :index="menu.name">
            <template #title>
                <el-icon>
                    <component :is="menu.icon"></component>
                </el-icon>
                <span>{{ menu.desc }}</span>
            </template>
            <el-menu-item v-for="child in menu.children" :key="child.route" :index="child.route"
                :route="child.route">{{
                    child.desc }}</el-menu-item>
        </el-sub-menu>

        <el-menu-item v-else :key="menu.route + 'xx'" :index="menu.route" :route="menu.route">
            <el-icon>
                <component :is="menu.icon"></component>
            </el-icon>
            <template #title>{{ menu.desc }}</template>
        </el-menu-item>
    </template>
</el-menu>

浏览器查看菜单和点击菜单跳转都是正常的

菜单渲染

路由

编辑src/router/index.ts,删除之前的菜单相关的路由。

const routes: RouteRecordRaw[] = [
    {
        path: '/login',
        name: 'Login',
        component: () => import('@/views/Login.vue'),
    },
    {
        path: '/',
        name: 'Main',
        component: () => import('@/views/Main.vue'),
    },
    {
        path: '/:catchAll(.*)',
        component: () => import('@/views/errors/404.vue'),
    },
]

添加路由前置守卫,在这里判断Main是否有子路由,没有就添加。

import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router'

import * as MenuUtil from '@/util/menu'
const router = createRouter({
    routes,
    history: createWebHashHistory()
})


let isAddRoute = false
router.beforeEach((to, _from, next) => {
    if (isAddRoute) {
        next()
        return
    }


    // 添加主页子路由
    isAddRoute = true

    const mainRoute = router.options.routes.find((v) => v.path == '/')!

    const [_genMenus, genRoutes] = MenuUtil.gen(1)

    mainRoute.redirect = genRoutes[0].path
    mainRoute.children = genRoutes

    router.addRoute(mainRoute)

    next({ ...to, replace: true })
})

浏览器点击菜单测试,发现路由跳转都是正常的。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!