О addRoutes, axios динамически добавляют маршруты

Vue.js
О addRoutes, axios динамически добавляют маршруты

После того, как бэкенд сгенерирует соответствующий маршрут для текущего пользователя, фронтенд addRoutes динамически подгружает маршрут.Я написал каштан, может поможет друзьям.Отправил на гитхаб.Интерфейс запроса vue динамически добавляет маршрут

39Yo1x.th.gif

О некоторых проблемах, с которыми я столкнулся, я писал в комментариях к коду.

Сначала посмотрите код

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

обо мне