您现在的位置是:首页 > 技术教程 正文

Vue3+Vue-Router+Element-Plus根据后端数据实现前端动态路由——权限管理模块

admin 阅读: 2024-04-01
后台-插件-广告管理-内容页头部广告(手机)

提示:文章内容仔细看一些,或者直接粘贴复制,效果满满

文章目录

  • 前言
  • 一、技术栈
  • 二、项目结构
  • 三、菜单组件和数据
    • 1、AsideMenu.vue 组件
    • 2、LeftSubMenu.vue
    • 3、menuData.json 数据
  • 四、router 配置
    • 1、router/index.js
    • 2、permission.js (与main.js 同级)
    • 3、main.js
  • 五、效果
  • 六、给个点赞和收藏
  • 七、参考文献


前言

提示:文章大概

1、项目:前后端分离
2、前端:基于Vite创建的Vue3项目
3、后端:没有,模拟的后端数据
4、关于路径“@”符号——vite.config.js 文件里修改
在这里插入图片描述


提示:以下是本篇文章正文内容,下面案例可供复制粘贴使用,嘎嘎爽

一、技术栈

  1. Vite 创建 Vue3 项目
# 1.创建项目 npm create vite@latest # 2.下载依赖 npm install # 3.运行项目 npm run dev
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. Element-plus
# 1.下载 npm install element-plus --save # 2.main.js 引入 // main.ts import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(ElementPlus) app.mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  1. Vue-Router
# 1.安装 npm install vue-router@4
  • 1
  • 2
  1. nprogress (进度条——非必选,好看而已)
npm i nprogress -S
  • 1

在这里插入图片描述

二、项目结构

在这里插入图片描述

三、菜单组件和数据

说明:

  1. AsideMenu.vue 引用 LeftSubMenu.vue 组件,并父传子传入后端数据
  2. LeftSubMenu.vue 组件加载数据
  3. menuData.json 后端模拟数据文件

1、AsideMenu.vue 组件

代码如下(示例):

<template> <el-menu router :default-active="activeMenu" :class="'menu-left'" :default-openeds="openedsArr" text-color="#fff"> <LeftSubMenu :menuData="treeMenu"></LeftSubMenu> </el-menu> </template> <script setup> import LeftSubMenu from "./LeftSubMenu.vue"; import { computed } from "vue"; import { useRouter } from "vue-router"; import treeMenu from './menuData.json'; const openedsArr = treeMenu.map((item) => { return item.path; }); const activeMenu = computed(() => { const router = useRouter(); const { meta, path } = router.currentRoute.value; if (meta.matchPath2) { return meta.matchPath2; } else { return path; } }); </script> <style scoped> .menu-left { flex: 1; padding: 0 8px; border-right: none; background: none; } .menu-left:deep(.el-menu), .menu-left:deep(.el-sub-menu__title:hover) { background: none; } .menu-left:deep(.el-menu-item), .menu-left:deep(.el-sub-menu__title) { height: 36px; margin-bottom: 4px; border-radius: 4px; color: var(--text-main-color) !important; } .menu-left:deep(.el-menu-item:hover .icon), .menu-left:deep(.el-menu-item.is-active .icon) { filter: invert(100%); -webkit-filter: invert(100%); } .menu-left:deep(.el-menu-item:hover), .menu-left:deep(.el-menu-item.is-active) { color: #ffffff !important; background-color: #eecece; } </style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

2、LeftSubMenu.vue

代码如下(示例):

<template> <template v-for="item in props.menuData"> <el-sub-menu :key="item.path" v-if="item.children && item.children.length > 0" :index="item.path"> <template #title> <el-icon> <component :is="item.icon"></component> </el-icon> <span>{{ item.meta.title }}</span> </template> <LeftSubMenu :menuData="item.children"></LeftSubMenu> </el-sub-menu> <el-menu-item :key="item.id" v-else :index="item.path" :disabled="item.disabled"> <template #title> <!-- <img class="icon pd-r-10" :src="item.icon" /> --> <el-icon> <component :is="item.icon"></component> </el-icon> <span>{{ item.meta.title }}</span> </template> </el-menu-item> </template> </template> <script setup> import LeftSubMenu from "./LeftSubMenu.vue"; import { computed, onMounted } from "vue"; import { useRouter } from "vue-router"; const props = defineProps({ menuData: { type: Array, default: [], }, }); onMounted(() => { console.log(props.menuData, "Item打印数据"); }); const curRoute = computed(() => { const router = useRouter(); const { path } = router.currentRoute.value; return path; }); </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

3、menuData.json 数据

数据参数说明:

  1. menuType: 0为菜单组,1为菜单(可跳转)
  2. children: 子路由
数据说明:不复制 { "id": "1", // 唯一id "name": "Home", // 组件名称 "path": "/home", // 路由 "component": "/home/index.vue", // 组件文件位置 "menuType": "1", // 组件类型 "icon": "Discount", // 图标 "sort": 0, // 排序规则 "meta": { "title": "系统首页", // 组件名称 "requiresAuth": null, // 是否需要身份验证 "roles": [], // 用户角色或权限 "breadcrumb": [ // 定义面包屑导航 {} ], "keepAlive": null // 是否需要缓存 }, "children": [] // 子路由 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

代码如下(示例):

[ { "id": "1", "name": "Home", "path": "/home", "component": "/home/index.vue", "menuType": "1", "icon": "Discount", "sort": 0, "meta": { "title": "系统首页", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [] }, { "id": "2", "name": "System", "path": "/system", "component": "/system/index.vue", "menuType": "0", "icon": "Operation", "sort": 0, "meta": { "title": "系统管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [ { "id": "211", "name": "User", "path": "/user", "component": "/user/index.vue", "menuType": "1", "icon": "user", "sort": 0, "meta": { "title": "用户管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [] }, { "id": "222", "name": "Menu", "path": "/menu", "component": "/menu/index.vue", "menuType": "1", "icon": "Menu", "sort": 0, "meta": { "title": "菜单管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [] }, { "id": "223", "name": "Role", "path": "/role", "component": "/role/index.vue", "menuType": "1", "icon": "Avatar", "sort": 0, "meta": { "title": "角色管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [] } ] }, { "id": "3", "name": "Log", "path": "/log", "component": "/log/index.vue", "menuType": "1", "icon": "Notebook", "sort": 0, "meta": { "title": "日志管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [] }, { "id": "4", "name": "Study", "path": "/study", "component": "/study/index.vue", "menuType": "0", "icon": "Notebook", "sort": 0, "meta": { "title": "学习管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [ { "id": "441", "name": "StudyUser", "path": "/studyUser", "component": "/study/user/index.vue", "menuType": "0", "icon": "Notebook", "sort": 0, "meta": { "title": "用户管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [ { "id": "4441", "name": "Student", "path": "/student", "component": "/study/user/student/index.vue", "menuType": "1", "icon": "Notebook", "sort": 0, "meta": { "title": "学生管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [] }, { "id": "4442", "name": "Teacher", "path": "/teacher", "component": "/study/user/teacher/index.vue", "menuType": "1", "icon": "Notebook", "sort": 0, "meta": { "title": "教师管理", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [] } ] }, { "id": "3", "name": "Log", "path": "/log", "component": "/log/index.vue", "menuType": "1", "icon": "Notebook", "sort": 0, "meta": { "title": "打卡记录", "requiresAuth": null, "roles": [], "breadcrumb": [ {} ], "keepAlive": null }, "children": [] } ] } ]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216

四、router 配置

说明:

  1. router.addRouter({}) 函数即动态路由,它是临时性的,就是一旦刷新就会清除掉添加的动态路由信息
  2. 需要重新定位到 localhost:8080 来刷新,重新获取路由信息,方便调试
  3. 因为是前端静态数据,所以正常,只要连接后端,请求数据后,缓存本地,每次刷新从本地获取即可
  4. 文章只是完成动态路由的实现,数据的持久性存储,各位根据自己项目自身完善

1、router/index.js

代码如下(示例):

import { createRouter, createWebHashHistory } from 'vue-router'; import NotFound from '@/pages/404/404.vue' // pages 文件下创建404文件,再创建一个404.vue const routes = [ { path: "/", component: () => import('@/pages/manage/ManageMain.vue') }, // 登录页 { path: "/manage", name: 'Manage', component: () => import('@/pages/manage/ManageMain.vue'), // 主页 }, { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }, ] const router = createRouter({ // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。 history: createWebHashHistory(), routes, // `routes: routes` 的缩写 }) // 导出实例, permission.js 引入 export default router
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

2、permission.js (与main.js 同级)

说明:

  1. 注意 NProgress 的引入、配置、使用
  2. 动态路由的添加(主要是 router.addRoute ,其他的都是根据后端 json 文件的参数来判断,不同的参数配置,不同的判断,这只是我喜欢的参数配置)
  3. 路径的拼接(component: () => import(/* @vite-ignore */ ./views${item.component}))
  4. 具体的根据自己的情况配置,打印就知道了,
  5. 按照我的配置,就不需要改动

代码如下(示例):

// 说明:路由守卫文件 // 引入 import router from "./router"; // 判断用户无token 返回登录页提示有用 import { ElMessage } from 'element-plus'; // 进度条 import NProgress from 'nprogress'; // 简单配置 进度条,可以不配置:在axios中我们不再做配置,以用来区分。 NProgress.inc(0.2) NProgress.configure({ easing: 'ease', speed: 500, showSpinner: false }) // 一、前置守卫 router.beforeEach((to, from, next) => { // 进度条 NProgress.start(); // 1、动态路由 addRoutes(); // 2、中间处理(token) // 3、最后放行 next(); }) // 动态路由获取:注:之后完善项目直接考虑在登录的时候直接获取 // 直接缓存在 pinia 里 // 这里直接取数据,不请求 import { getTreeMenu } from '@/api/index.js'; import menuData from '@/components/menu2/menuData.json'; function addRoutes() { // 1、后端数据 createRouters(menuData); console.log("router/index.js打印router已有的路由信息", router.getRoutes()); } // 拼接路由 function createRouters(result) { result.forEach((item) => { // 1、类型为0的菜单,子路由不为空,将子路由添加到manage里 if (item.menuType === '0' && item.children.length > 0) { item.children.forEach((children) => { createRouterTemplate('Manage', children); }) } // 2、menuType == 1, 子路由为空 if (item.menuType === '1' && item.children.length === 0) { createRouterTemplate('Manage', item); } // 3、递归层级 if (item.children.length > 0) { createRouters(item.children); } }); } // 把router 的动态路由进行封装 function createRouterTemplate(fatherRouter, item) { router.addRoute(fatherRouter, { path: item.path, name: item.name, meta: { title: item.meta.title, // 面包屑用 requiresAuth: item.meta.requiresAuth, roles: item.meta.roles, breadcrumb: item.meta.breadcrumb, keepAlive: item.meta.keepAlive }, // /* @vite-ignore */ :处理vite动态导入的警告 component: () => import(/* @vite-ignore */ `./views${item.component}`) }) } // 二、后置守卫 router.afterEach((to) => { // 标签抬头 document.title = to.meta.title; // 进度条 NProgress.done(); }) // main.js 导入的为这个router export default router
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

3、main.js

说明:

  • 1.注意 router 的引用文件
  • 2.注意 nprogress 的引用
  • 3.注意全局定义Element-Plus图标
  • 4.注意Vue3动态图标的使用
# Vue3 动态图标的使用
  • 1
  • 2

代码如下(示例):

import { createApp } from 'vue' import './style.css'; import ElementPlus from 'element-plus'; import 'element-plus/dist/index.css'; import App from './App.vue'; // import router from './router'; // 原router import router from './permission'; // 现router //Icon全局引入 import * as icons from "@element-plus/icons-vue"; // 进度条 import 'nprogress/nprogress.css'; const app = createApp(App); // ElementPlus app.use(ElementPlus); // Icon全局注册 Object.keys(icons).forEach(key => { app.component(key, icons[key]) }) app.use(router); app.mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

五、效果

在这里插入图片描述

在这里插入图片描述

删除menuData.json 文件的某一个路由,界面将不展示!!!

六、给个点赞和收藏

七、参考文献

参考文章 — https://www.cnblogs.com/lpkshuai/p/17346600.html

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索