Vue实现动态路由,请求服务端菜单数据,封装并添加到Vue前端路由中
permission里面通过请求数据接口加载封装为vue路由数据,通过store存储,定义一个Mutation,把封装的路由存到store里面,在router/index.js里通过先拿到store的路由数据惊醒判断,如果没有就先进行获取刷新和添加动态路由。减少前端的操作,只需在后端进行添加新的路由菜单,存储到数据库,还可以增加安全性和实用性!本文场景十用户访问网站,请求后端菜单目录的数据接口,然
1、什么是动态路由?
动态路由,动态即不是写死的,是可变的。我们可以根据自己不同的需求加载不同的路由,做到不同的实现及页面的渲染。动态的路由存储可分为两种,一种是将路由存储到前端。另一种则是将路由存储到数据库。动态路由的使用一般结合后端的角色权限控制一起使用。通过用户登录后的橘色去家在不同的菜单路由:
思路其实很简单,也很明确:
1、将路由分为静态路由(staticRouters)、动态路由
2、静态路由初始化时正常加载
3、用户登陆后,获取相关动态路由数据,
4、然后利用vue:addRoute追加到vue实例中即可。
实现思路虽然很简单,但是过程并不是一帆风顺,需要注意的细节还是很多的
2、动态路由的好处
使用动态路由可以跟灵活,无需手工维护,我们可以使用一个页面对路由进行维护。减少前端的操作,只需在后端进行添加新的路由菜单,存储到数据库,还可以增加安全性和实用性!而且系统的整体可维护性提高了。
3、动态路由如何实现
本文场景十用户访问网站,请求后端菜单目录的数据接口,然后菜单数据封装为前端路由结构,通过和路由文件中定义的静态路由做拼接,组成最终的路由,在前端显示。
1)此为我的router目录,index.js对路由添加,守卫拦截等处理。
import Vue from 'vue'
import { ref } from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Error404 from '../views/errorpage/ErrorPage404.vue'
import LeaveMessage from '../views/comment/LeaveMessage.vue'
import Write4Me from "@/views/write/Write4Me.vue"
import store from "../store";
Vue.use(VueRouter)
/* Layout */
import Layout from '@/layout'
// 公共静态路由
export const constantRoutes = [
{
path: '',
//name: 'layout',
component: Layout,
meta: { title: '极客普拉斯', icon: '' },
type: 'page',
hidden:true,
children:[
{
path: '/',
name: 'home',
component: HomeView,
meta: { title: '首页', icon: '' },
type: 'menu',
children: []
},
{
path: '/leaveMessage',
name: 'leaveMessage',
meta: { title: '留言给我', icon: 'fa-home' },
component: LeaveMessage,//() => import(/* webpackChunkName: "about" */''),
type: 'menu',
children: []
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: (resolve) => require(['../views/about/AboutUS.vue'], resolve),
//component: () => import(/* webpackChunkName: "about" */ '../views/about/AboutUS.vue'),
meta: { title: '关于', icon: '' },
type: 'menu',
children: []
},
{
path: '/article/:articleId',
name: 'article',
meta: { title: '文章详情', icon: 'fa-home' },
component: (resolve) => require(['@/views/article/ArticleContent.vue'], resolve),
type: 'generalPage',
hidden: true
},
{
path: '/general',
name: 'generalArticle',
meta: { title: '文章详情', icon: 'fa-home' },
component: (resolve) => require(['@/views/article/GeneralContent.vue'], resolve),
type: 'generalPage',
hidden: true
},
{
path: '/write4me',
name: 'write4me',
meta: { title: '给我投稿', icon: 'fa-home' },
component: Write4Me,
//component: (resolve) => require(['@/views/write/Write4Me.vue'],resolve),
type: 'page',
hidden: true
},
{
path: '/articleListForTag',
name: 'articleListForTag',
meta: { title: '标签文章列表', icon: 'fa-home' },
component: (resolve) => require(['@/views/categorypage/ArticleListPageForTag.vue'], resolve),
type: 'menu',
children: []
},
{
path: '/search',
name: 'search',
meta: { title: '搜索详情页', icon: 'fa-home' },
component: (resolve) => require(['@/views/categorypage/SearchResult.vue'], resolve),
type: 'page',
hidden: true
},
]
},
{
path: '/chatgpt',
name: 'ChatGPT',
meta: { title: 'ChatGPT智能助手', icon: 'fa-home' },
component: (resolve) => require(['@/views/chatgpt/index.vue'], resolve),
type: 'page',
children: [],
hidden: true
},
{
path: '/404',
name: 'error404',
component: Error404,
meta: { title: '404', icon: '' },
type: 'error',
hidden: true
},
//这个*匹配必须放在最后,将改路由配置放到所有路由的配置信息的最后,否则会其他路由path匹配造成影响。因为是动态路由,所以这里需要注释掉
// {
// path: '*',
// redirect: '/404',
// type: 'error',
// hidden: true
// }
]
const router = new VueRouter({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
//base: process.env.VUE_BASE_API,
routes:constantRoutes
})
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = VueRouter()
router.matcher = newRouter.matcher // reset router
}
const VueRouterPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(to) {
return VueRouterPush.call(this, to).catch(err => err)
}
// 全局后置钩子-常用于结束动画等,beforeEach每次进行路由跳转时都会执行
router.beforeEach(async(to, from, next) => {
document.title = to.meta.title || "极客普拉斯&梦极客园 - geekplus.xyz";
if (store.getters.routes.length==0 || store.getters.addRoutes==0) {
//拿到store存储中动态路由的数据 重新添加
//可以不用再判断一次store.getters.addRoutes.length == 0
if (store.getters.addRoutes.length == 0) {
await store.dispatch('permission/generateRoutes').then(accessRoutes => {
let asyncRouter = accessRoutes
// 根据后端请求的数据生成可访问的路由表
router.addRoute(accessRoutes) // 动态添加可访问路由表
router.options.routes=store.getters.routes;
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}else{
next({ ...to, replace: true }) //添加完成后再次进入
}
} else {
next() //如果登录页或首页 或 vuex中有动态路由数据 直接通过
}
});
export default router
constantRoutes为前端定义的静态路由,不需要动态加载的,如登陆页面,忘记密码页面,404页面等。路由守卫router.beforeEach里面判断我们登录时是否拿到请求后端生成的动态路由,是存储在store里面的。后面就是通过permission.js里面进行后台数据请求然后封装成循环嵌套路由结构!关于store的使用,我也会再出一篇文章!
2)permmison.js文件,store/modules/permission.js
import router from "../../router"
import {constantRoutes} from "../../router";
import store from "../../store";
import HomeView from '@/views/HomeView.vue'
import { listSubParentCategory } from "@/api/geekplus/geekplus";
//import router from 'vue-router'
/* Layout */
import Layout from '@/layout'
// 全局变量state,routes和addRoutes数组
const state = {
routes: [],
addRoutes: [],
menuRouters: [],
}
// Mutation 用户变更Store数据
const mutations = {
SET_ROUTES: (state, routes) => {
state.menuRouters = routes
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }) {
return new Promise(resolve => {
let routerList = new Array();
//这里是调用后端请求的接口函数,我这里返回的是一个彩蛋数据,里面潜逃children子菜单的数据结构,你也可以不用这样,直接返回一条一条的菜单数据,不用后端封装,不过下面就需要改变setChild的方法了,使用别的方法
listSubParentCategory().then(response => { //调用后端接口获取路由列表
let menus = response.data;
menus.forEach(item => {
setChild(item, routerList, '', '')
})
let layoutRouter = {
path: '',
name: 'layout',
component: Layout,
meta: { title: '框架布局页面', icon: '' },
type: 'layout',
hidden: true,
children: routerList,
}
routerList.push({ path: '*', redirect: '/404',type:'error', hidden: true })
commit('SET_ROUTES', routerList);
resolve(layoutRouter);
})
})
}
}
function setChild(item, routerList, rootName, rootPath) {
// rootName = rootName+'/'+item.categoryName
// rootPath = rootPath+'/'+item.path
rootName = item.categoryName
let routerName = item.path
rootPath = item.path
if (item.children != null && item.children != [] && item.children.length > 0) {
//有下层则继续递归路由
let router = {
name: routerName.replace('/', ''),
path: rootPath,
component: () => import('@/views/categorypage/ArticleListPage.vue'),// + item.component
meta: { title: rootName, icon: item.icon, id: item.id},
type: 'servermenu',
children: []
}
routerList.push(router)
//如果有下层
item.children.forEach(node => {
setChild(node, router.children, rootName, rootPath)
})
} else {
//没有下层则说明这是一个路由
let router = {
name: routerName.replace('/', ''),
path: rootPath,
component: () => import('@/views/categorypage/ArticleListPage.vue'),
meta: { title: rootName, icon: item.icon, id: item.id},
type: 'servermenu',
children: []
}
//console.log(router)
routerList.push(router)
}
}
//可以通过这个方法加载不同的路由页面,这里因为我只是封装的同一个页面,所以没有用,这里view参数为上面的categorypage/ArticleListPage.vue
export const loadView = (view) => { // 路由懒加载
return (resolve) => require([`@/views/${view}`], resolve)
}
export default {
namespaced: true,
state,
mutations,
actions
}
permission里面通过请求数据接口加载封装为vue路由数据,通过store存储,定义一个Mutation,把封装的路由存到store里面,在router/index.js里通过先拿到store的路由数据惊醒判断,如果没有就先进行获取刷新和添加动态路由。
下面还有store存储用到的的一些文件:
store/getters.js
const getters = {
//permission_routes: state => state.permission.routes,
routes: state => state.permission.routes,
addRoutes: state => state.permission.addRoutes,
menuRouters: state => state.permission.menuRouters,
}
export default getters;
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import permission from './modules/permission'
Vue.use(Vuex)
//export default store
export default new Vuex.Store({
state: {
routes:[],
addRoutes:[],
menuRouters:[]
},
getters,
mutations: {
},
actions: {
},
modules: {
permission
}
})
讲一讲遇到的坑及注意点
在进行路由数据封装时,把404页面的路由添加到最后
routerList.push({ path: '*', redirect: '/404',type:'error', hidden: true })
此处为重要的一点,直接用next()不行
next({
...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
replace: true, // 重进一次, 不保留重复历史
})
3)由于添加完路由还会重复执行一遍路由守卫,所有必须确保不要一直死循环添加路由。否则直接崩溃。这里我用的是store.getters.addRoutes和store.getters.routes全局变量是否存在确保不循环。由于vue-router3.0舍弃了addRoutes(),只有使用addRoute(),addRoutes添加的是多条路有数组,addRoute是添加一个路由对象。
更多推荐
所有评论(0)