vue动态路由实践
🧐🧐🧐 vue动态路由(约定路由),听起来好像很玄乎的样子😲 但是你要是理解了实现思路,你会发现,没有想象中的那么难😌
在没有亲自实现功能前,永远不要低估自己的个人实力和潜力😛😛😛
🔥🔥🔥下面是本人一个刚从服务端开发转职前端开发的程序猿的实现过程🔥🔥🔥
实现思路
思路其实很简单,也很明确:
1、将路由分为静态路由(staticRouters)、动态路由
2、静态路由初始化时正常加载
3、用户登陆后,获取相关动态路由数据,
4、然后利用vue:addRoute追加到vue实例中即可。
实现思路虽然很简单,但是过程并不是一帆风顺,需要注意的细节还是很多的
环境介绍
- vue-cli: v4.x.x
- vue: v2.6.11
- vuex: v3.4.0
- vue-router: v3.2.0
实现过程
- 路由文件处理(router/index.js):
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeLayout from '../layouts/HomeLayout'
import store from '@/store/index'
Vue.use(VueRouter)
// 解决重复点击路由报错的BUG
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch((err) => err)
}
const routes = [
{
path: '/',
name: 'homeBase',
component: HomeLayout,
redirect: {
name: 'home'
},
children: [
// 门户路由
{
path: 'home',
name: 'home',
component: () => import('../views/portal/Home.vue'),
},
{
path: 'lists',
name: 'lists',
component: () => import('../views/portal/Lists.vue'),
},
{
path: 'detail',
name: 'detail',
component: () => import('../views/portal/Detail.vue'),
},
]
},
]
// 定义静态路由集合
const staticRouterMap = [
'home',
'lists',
'detail'
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
})
// 路由全局拦截
// 以下可根据业务逻辑自行在拦截路由中进行处理,此处仅以本人业务作为示例展示
// 本示例以 vuex+sessionStorage相互配合完成动态路由的数据存储
// 仅以vuex存储获取到的动态路由信息后,在刷新页面时,动态路由信息是会丢失,
// 从而导致页面404
router.beforeEach((to, from, next) => {
const userState = JSON.parse(sessionStorage.getItem('userState'))
if (!userState || !userState.isLogin) {
// 没有登录
// 如果前往页面非公共路由,则跳转至首页
if (staticRouterMap.indexOf(to.name) < 0) {
next({name: 'home'})
} else {
next()
}
} else {
// 登录
// 已经存在路由列表: 注意刚登陆成功第一次调转route时相应store数据还未更新
const hasGetRoute = store.getters['user/hasGetRoute']
const routeMap = JSON.parse(sessionStorage.getItem('routeMap'))
if(!hasGetRoute && routeMap) {
// 刷新页面且有route记录数据,可再次追加动态路由
store.dispatch('user/updateRouteOfUser', routeMap)
next({...to, replace: true})
} else {
next()
}
}
})
export default router
- view数据处理
<template>
<div class="home">
<div>
这是demo
</div>
<div>
<div v-show="!isLogin">
<a-divider>调用接口: 模拟登陆</a-divider>
<div style="margin-top:5px;">
<a-space :size="size">
<a-button type="primary" @click="login()">
用户登陆
</a-button>
</a-space>
<p>{{loading}}</p>
</div>
</div>
</div>
</div>
</template>
<script>
// @ is an alias to /src
import {
Base64
} from 'js-base64'
import User from '../../api/user'
import {
mapGetters,
mapMutations,
mapActions
} from 'vuex'
export default {
name: 'home',
data() {
return {
size: "middle",
user: {
'name': 'xxxx',
'pass': Base64.encode('xxxx')
},
}
},
components: {},
computed: {
...mapGetters('user', ['isLogin', 'userInfo', 'hasGetRoute'])
},
methods: {
...mapMutations('user', ['setUserState']),
...mapActions('user', ['getUserInfo', 'getDynamicRouteOfUser']),
login() {
if (this.isLogin) {
this.$router.push({
path: '/user'
})
} else {
// 模拟用户
User.login(this.user).then(res => {
this.setUserState({
'isLogin': true,
'ut': res.data.user_token,
'userType': 1
})
this.getUserInfo()
//以下就是根据用户登陆信息,获取动态路由信息操作
this.getDynamicRouteOfUser(type).then(() => {
this.$router.push({
path: '/user'
})
})
}).catch(() => {
})
}
},
},
}
</script>
<style lang="scss" scoped>
.home {
padding: 20px;
}
</style>
- vuex
import VueRouter from '../../router'
import UserApi from '../../api/user'
import axios from 'axios'
import TeacherLayout from '@/layouts/Layout'
import NotFound from '@/layouts/404'
const user = {
namespaced: true,
state: {
// 用户状态相关
userState: JSON.parse(sessionStorage.getItem('userState')) || {ut: '', isLogin: false, userType: null},
// 用户信息相关
userInfo: JSON.parse(sessionStorage.getItem('userInfo')) || {},
// 是否获取route
hasGetRoute: false,
// routeMap
routeMap: JSON.parse(sessionStorage.getItem('routeMap')) || [],
},
getters: {
ut : state => state.userState.ut,
isLogin: state => !!state.userState.isLogin,
userInfo: state => state.userInfo,
hasGetRoute: state => state.hasGetRoute,
routeMap: state => state.routeMap[0].children,
},
mutations: {
setUserState(state, playload) {
state.userState = playload
sessionStorage.setItem('userState', JSON.stringify(state.userState))
},
setUserInfo(state, playload) {
state.userInfo = playload
sessionStorage.setItem('userInfo', JSON.stringify(state.userInfo))
},
setRouteMap(state, routers) {
state.routeMap = routers
// 为了防止用户刷新页面导致动态创建的路由失效,将其存储在本地中
sessionStorage.setItem('routeMap', JSON.stringify(routers));
},
setDynamicRouteMap(state, routers) {
state.hasGetRoute = true
let routerMaps = filterRouter(routers)
// 最后追加404路由
routerMaps.push({
path: '*',
component: NotFound
})
// 追加路由
// 这块是重点,如果直接使用addRoute是无效的
routerMaps.forEach(item => {
VueRouter.addRoute(item);
})
},
resetLogin() {
sessionStorage.clear()
}
},
actions: {
// 获取用户信息
async getUserInfo({commit}) {
await UserApi.user().then(res => {
commit('setUserInfo', res)
}).catch(error => {
console.log(error)
})
},
// 获取用户授权动态路由
async getDynamicRouteOfUser({commit}, type) {
let flag = false
// mock api
mockRouter().then(res => {
commit('setRouteMap', res.data)
commit('setDynamicRouteMap', res.data)
flag = true
}).catch(err => {
console.log(err)
})
return flag
},
// 刷新重置路由
updateRouteOfUser({commit}, routerMap) {
commit('setDynamicRouteMap', routerMap)
},
}
}
// handle views
const loadView = (viewPath) => {
return () => import('@/views/' + viewPath)
}
// Handle routers
const filterRouter = (routers) => {
return routers.filter((router) => {
// 区分布局与视图文件,因为加载方式不同
if (router.component === 'Layout') {
router.component = Layout
}else {
// view
router.component = loadView(router.component)
}
// 删除路由记录中的无用字段:这段是本示例与后台协商的,但在vue-router中不被支持的字段信息,可忽略
if (!router.redirect || !router.redirect.length) { delete router.redirect }
// 判断是否存在子路由,并递归调用自己
if(router.children && router.children.length) {
router.children = filterRouter(router.children)
}
return true
})
}
// mock 数据
async function mockRouter() {
const url = 'http://localhost:8080/t.json'
let routerData
await axios.get(url).then(res => {
routerData = res.data
}).catch(err => {
console.log(err)
})
return routerData
}
export default user;
路由数据(demo)
贡献本人于服务端约定的路由数据结构,仅供参考
{
"data":[
{
"title":"demo",
"name":"x",
"pname":"",
"path": "/x",
"type": 1,
"component": "Layout",
"redirect": {"name": "xx"},
"children": [
{
"title":"child1",
"name":"xx",
"pname":"x",
"path": "",
"type": 2,
"icon": "desktop",
"component": "xx.vue",
"redirect": {}
},
{
"title":"child1",
"name":"xx",
"pname":"tBase",
"path": "xx",
"type": 2,
"icon": "container",
"component": "xx.vue",
"redirect": {"name": "xxx"},
"children": [
{
"title":"child2",
"name":"xx",
"pname":"xx",
"path": "xx",
"type": 2,
"icon": "unordered-list",
"component": "xx.vue",
"redirect": {}
}
]
},
]
}
]
}