После того, как бэкенд сгенерирует соответствующий маршрут для текущего пользователя, фронтенд addRoutes динамически подгружает маршрут.Я написал каштан, может поможет друзьям.Отправил на гитхаб.Интерфейс запроса vue динамически добавляет маршрут
О некоторых проблемах, с которыми я столкнулся, я писал в комментариях к коду.
Сначала посмотрите код
main.js
import Vue from 'vue'
import App from './admin.vue'
import store from './store'
import router from './router'
import '@/style/reset.css'
// 把请求方法也挂在到原型上
import * as types from './http/http.js'
Vue.prototype.$http = types
// 将自定义工具扩展到原型上
import utils from '@/util/utils.js'
Vue.prototype.$utils = utils
// 使用ElementUI
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue.config.productionTip = false
new Vue({
store,
router,
render: h => h(App),
}).$mount('#app')
Авторизоваться
Начните с входа в систему, начните с входа в систему
<template>
<div>
<el-input v-model="name" placeholder="请输入内容"></el-input>
<el-input v-model="pas" placeholder="请输入内容"></el-input>
<el-button type="primary" @click="btnLogin">登录</el-button>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
data(){
return {
name:'123456',
pas:'123456'
}
},
methods:{
...mapActions([
'getMenuList',
'getUserInfo'
]),
// async await 同步写法
async btnLogin(){
// 获取用户信息
let userInfo = await this.getUserInfo();
if(userInfo && userInfo.name){
// 获取后台返回路由
let menuList = await this.getMenuList();
if(menuList){
// 跳转动态添加后的动态路由
this.$router.push({path:'/home'})
}else{
console.log('出错提示')
}
}
}
}
}
</script>
<style lang="less" scoped>
button{
width:100%;
}
</style>
vue-router
import Vue from "vue";
import VueRouter from "vue-router";
import store from '../store'
Vue.use(VueRouter);
// 默认路由
const defaultRouter = [{
path: "/",
name: "index",
redirect: "/main",
meta: { title: "main", icon: '', shows: false },
},
{
path: "/main",
name: "main",
component: resolve => require(["@/views/admin/src/layout/main.vue"], resolve),
meta: { title: "主页", icon: '', shows: true },
children: []
},
{
path: "/login",
name: "login",
component: resolve => require(["@/views/admin/src/login/index.vue"], resolve),
meta: { title: "登录", icon: '', shows: true },
}
]
// 创建实例
const router = new VueRouter({
routes: defaultRouter,
mode: "hash",
base: __dirname, //比如设置 base: test 路由链接解析后 test/#/views-b
strict: process.env.NODE_ENV !== "production"
});
// 路由全局拦截
router.beforeEach( async (to, from, next) => {
let hasLogin = localStorage.getItem("hasLogin")
if (hasLogin) {
if (!store.state.menuList.length) {
// 进入到这一步用户已经登录过,但是又刷新了浏览器,导致路由清空了
// 所以要重新请求路由,其实也阔以把路由存在路由localStorage中,我这里为了演示同步的写法,也可以达到同样的目的
await store.dispatch('getMenuList')
// router.addRoutes是异步的,所以把全局的跳转 *也动态添加了,同时使用 next({ ...to, replace: true })重新载入
next({...to, replace: true })
}
// 已经登录过访问的是login,跳转至home
if (to.name === 'login') {
next({
path: '/home',
})
} else {
next()
}
} else {
// 没有登录想访问其他页面,跳转至
if (to.name !== 'login') {
next({
path: '/login',
})
} else {
next()
}
}
})
export default router;
vuex
import Vuex from 'vuex'
import Vue from 'vue'
import VueRouter from '../router'
import { defaultRouter } from '../router/admin'
import { getRole, getUserinfo } from '@/views/admin/http/api'
import app from '../config/app'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
userInfo: {},
hasLogin: false, // 表示没有获取过权限,获取完毕后,把状态改成true
menuList: [], // 存放菜单数据
},
getters: {
},
mutations: {
setMenuList(state, menus) {
state.menuList = menus;
// 把请求的接口放在mian下面
defaultRouter[1].children = menus;
// 添加到router当中
VueRouter.addRoutes(defaultRouter);
// VueRouter.options.routes = VueRouter.options.routes.concat(defaultRouter) // 解决组件中 this.$router.options.routes 获取不到新添加的router
},
setUserInfo(state, user) {
// 更新用户信息
state.userInfo = user;
state.hasLogin = true;
localStorage.setItem("hasLogin", state.hasLogin); // 存储登录过权限
}
},
actions: {
// 会调用setMenuList 生成路由并添加路由
async getMenuList({ commit }) {
let { data } = await getRole(); // 请求后端接口拉取路由
let menus = needRoutes(data.menus) // 生成路由信息
commit('setMenuList', menus) // actions调用mutations
return menus;
},
async getUserInfo({ dispatch, commit }) {
let { data } = await getUserinfo(); // 获取用户信息
commit('setUserInfo', data); // 更新Vuex-setUserInfo
return data
}
}
})
// 生成路由数据
function needRoutes(data) {
// 判断是否是数组
if (!Array.isArray(data)) {
return new TypeError('arr must be an array.');
}
let arr = [];
for (let obj of data) {
const component = obj.component
// 把后台返回的路由参数,拼接路径
obj.component = resolve => { require(['@/' + component + '.vue'], resolve) }
arr.push(obj)
}
// 考虑到后台排序 进行排序 compare 是我封装的数组对象排序的方法
arr.sort(Vue.prototype.$utils.compare('sort')) // 根据后台返回数据sort字段进行排序
arr = Vue.prototype.$utils.toTree(arr) // 生成树状结构,为后面生成左侧菜单准备
arr.push(app.defaultErr) // 404路由需要最后添加,不然访问动态的路由会出现404
return arr;
}
main.vue
<template>
<div class="main">
<section class="main-left">
<!-- 左边菜单 -->
<sidebar></sidebar>
</section>
<section class="main-right">
<!-- 右边头部 -->
<div class="main-right-head">
<rightHead></rightHead>
</div>
<!-- 右边内容 -->
<div class="main-right-container">
<div class="app-main">
<router-view></router-view>
</div>
</div>
</section>
</div>
</template>
<script>
// 可以看github
import sidebar from './sidebar.vue' // https://github.com/hangjob/vue-admin/blob/master/src/views/admin/src/layout/sidebar.vue
import rightHead from './header.vue' // https://github.com/hangjob/vue-admin/blob/master/src/views/admin/src/layout/treeMenus.vue
export default {
components:{
sidebar,
rightHead
}
}
</script>
<style lang="less" scoped>
</style>
За кулисами
let express = require('express');
let app = express();
//在后端配置,cors跨域
app.use('*', function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
app.get('/role', (req, res) => {
res.json({
menus: [
{ id: 1, sort: 1, path: '/home', component: 'views/admin/src/home/index', meta: { title: "首页", icon: 'el-icon-grape', shows: true }, pid: '' },
{ id: 2, sort: 8, path: '/financial', component: 'views/admin/src/financial/index', meta: { title: "财务管理", icon: 'el-icon-refrigerator', shows: true }, pid: '' },
{ id: 3, sort: 1, path: '/certification', component: 'views/admin/src/certification/index', meta: { title: "认证资质", icon: 'el-icon-watermelon', shows: true }, pid: '' },
{ id: 4, sort: 1, path: '/integrated', component: 'views/admin/src/integrated/index', meta: { title: "综合管理", icon: 'el-icon-cherry', shows: true }, pid: '' } ,
{ id: 5, sort: 6, path: '/project', component: 'views/admin/src/project/index', meta: { title: "项目管理", icon: 'el-icon-apple', shows: true }, pid: '' }
]
})
})
app.get('/userinfo', (req, res) => {
res.json({
name: '羊先生',
mesg: 'admin'
})
})
//监听3000端口
app.listen(3001); // 启动node 执行该文件
Суммировать
Сначала определите общедоступную таблицу маршрутизации, которая содержит только некоторые общедоступные маршруты, такие как вход в систему.
После успешного входа
Получите список маршрутизации пользователя. Поскольку это обратный маршрут серверной части, серверная часть знает разрешения пользователя на информацию о маршрутизации и возвращает соответствующий список маршрутизации.
Получите список маршрутов, таблицу динамической маршрутизации, чтобы добавить это в маршрутизатор.
На конец необходимо добавить 404 страницы, в противном случае асинхронная маршрутизация запроса страницы будет обновляться, и появится 404
После обновления пользователя, если маршрутvuexисчезнуть, существоватьlocalStorage, который очищается, когда пользователь теряет разрешенияlocalStorageДля связанной информации здесь я использую синхронизацию, чтобы снова получить список маршрутов.
Код был загруженgithub