vue-element-admin框架实现动态路由

902 阅读4分钟

想必开发过后端管理项目的同学都了解过vue-element-admin这个框架吧,那么我们今天将告诉大家如何把该框架的路由修改成动态的。

话不多说 直接开始!!!!

1.先到GitHub上下载该框架:github.com/PanJiaChen/…
注意:这是基础模板的下载路径。

2.在项目根目录下面输入 npm i 安装依赖,然后 npm run dev 把项目跑起来

3.我们先缕清思路,所谓的动态路由就是:从后端获取到路由数据=>处理成router能识别的格式=>然后通过 router.addRoutes方法将其添加到路由中即可。

我们需要修改的页面有store文件夹下的user.js getters.js、router文件夹下的index.js 、src下的permission.js、layout下的index.vue

一、修改user.js成下面这样

import {login,logout,getInfo} from '@/api/user'
import {getToken,setToken,removeToken} from '@/utils/auth'
import {resetRouter} from '@/router' // 引用重置路由的方法 非常重要!!!

const getDefaultState = () => {
  return {
    token: getToken(),
    menus: [], // 菜单权限
  }
}

const state = getDefaultState()

const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_MENUS: (state, menus) => {
    state.menus = menus // 菜单权限
  }
}

const actions = {
  // user login  调用登录接口获取到token  我这里先是写死的 具体看个人情况
  login({
    commit
  }, userInfo) {
    const {
      username,
      password
    } = userInfo
    return new Promise((resolve, reject) => {
      // login({ 
      //   username: username.trim(),
      //   password: password
      // }).then(response => {
      //   const {
      //     data
      //   } = response
      //   commit('SET_TOKEN', data.token)
      //   setToken(data.token)
      //   resolve()
      // }).catch(error => {
      //   reject(error)
      // })
      const toekn = 'Bearer 928|vjqDa6oj3pFAk0LPUKUvajTITUkCa7IdRBdfE6gK'
      commit('SET_TOKEN', toekn)
      setToken(toekn)
      resolve()
    })
  },

  // get user info 关键部分在这里:通过后端接口把路由数据获取到,并且存到store里面,方便其他文件读取。
  getInfo({
    commit,
    state
  }) {
    return new Promise((resolve, reject) => {
      getInfo().then(response => {
        console.log(response);
        const data = response.data
        if (!data) {
          return reject('Verification failed, please Login again.')
        }
        const menus = data.routers;
        commit('SET_MENUS', menus) // 获取到路由信息并且存进去
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // user logout
  logout({
    commit,
    state
  }) {
    return new Promise((resolve, reject) => {
      // logout(state.token).then(() => {
      removeToken() // must remove  token  first
      resetRouter()
      commit('RESET_STATE')
      resolve()
      // }).catch(error => {
      //   reject(error)
      // })
    })
  },

  // remove token
  resetToken({
    commit
  }) {
    return new Promise(resolve => {
      removeToken() // must remove  token  first
      commit('RESET_STATE')
      resolve()
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

二、修改getters.js成下面这样

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token,
  menus: state => state.user.menus, // 菜单权限
}
export default getters

三、修改router下的index.js成下面这样

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  },
  // 404 page must be placed at the end !!!
  // { path: '*', redirect: '/404', hidden: true }
]


const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export const error404= { path: '*', redirect: '/404', hidden: true };

export default router

把多余的路由删掉,只保留login、404和首页部分就可以,因为其他的页面我们是动态加进去的。 注意:这里把404的路由导出去:export const error404= { path: '*', redirect: '/404', hidden: true }; 后面permission.js文件要用到

三、修改src下的permission.js成下面这样

import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress"; // progress bar
import "nprogress/nprogress.css"; // progress bar style
import { getToken } from "@/utils/auth"; // get token from cookie
import getPageTitle from "@/utils/get-page-title";
import {error404} from './router'
import Layout from "@/layout"; // 引入Layout
const _import = require('@/router/_import_development'); // 引入获取组件的方法

NProgress.configure({ showSpinner: false }); // NProgress Configuration

const whiteList = ["/login"]; // no redirect whitelist

router.beforeEach(async (to, from, next) => {
  // start progress bar
  NProgress.start();

  // set page title
  document.title = getPageTitle(to.meta.title);

  // determine whether the user has logged in
  const hasToken = getToken();

  if (hasToken) {
    if (to.path === "/login") {
      // if is logged in, redirect to the home page
      next({ path: "/" });
      NProgress.done();
    } else {
      const hasGetUserInfo = store.getters.name;
      if (hasGetUserInfo) {
        next();
      } else {
        try {
          // get user info
          await store.dispatch("user/getInfo");
          // **在这里做动态路由**
          if (store.getters.menus.length < 1) {
            global.antRouter = [];
            next();
          }
          const menus = filterAsyncRouter(store.getters.menus); // 过滤路由
          router.addRoutes([...menus,error404]); // 动态添加路由 把404放到最后面 防止刷新页面的时候跳转404页面
          global.antRouter = menus; // 将路由数据传递给全局变量,做侧边栏菜单渲染工作
          next({ ...to, replace: true });
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch("user/resetToken");
          Message.error(error || "Has Error");
          next(`/login?redirect=${to.path}`);
          NProgress.done();
        }
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next();
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`);
      NProgress.done();
    }
  }
});

function filterAsyncRouter(asyncRouterMap) {
  let arr = [];
  let father = {};
  let children = {};
  asyncRouterMap.forEach((item) => {
    // 判断是否有子级
    if (item.children && item.children.length) {
      // 处理父级
      father = {
        path: "/" + item.path,
        component: Layout,
        redirect: "/" + item.path + "/" + item.children[0].path,
        name: item.meta.title,
        meta: { title: item.meta.title, icon: item.meta.icon },
        children: [],
      };
      // 处理子级
      item.children.forEach((ele) => {
        children = {
          path: ele.path,
          name: ele.meta.title,
          component: _import(ele.component), // 动态写法 避免无法进入到页面的情况
          meta: { title: ele.meta.title, icon: ele.meta.icon },
        };
        father.children.push(children);
      });
    } else {
      // 如果没有子级  直接处理父级
      father = {
        path: "/" + item.path,
        component: Layout,
        children: [
          {
            path: "",
            name: item.meta.title,
            // component: () => import('@/views/' + item.path + '/index'),
            component: _import(item.path), // 动态写法 避免无法进入到页面的情况
            meta: { title: item.meta.title, icon: item.meta.icon },
          },
        ],
      };
    }
    arr.push(father);
  });
  return arr;
}

router.afterEach(() => {
  // finish progress bar
  NProgress.done();
});

注意:在该文件夹下面引入了两个东西: import Layout from "@/layout"; const _import = require('@/router/_import_development'); 看到这里想必会有小伙伴疑惑这个_import是什么东东? 答:因为在循环处理路由数据的时候,路由的格式只能是动态的,如果我们使用常规的字符串拼接路由,那么将导致router识别不出来,继而报错。所以我们在router文件夹下面创建一个文件并且命名为:_import_development.js。循环处理路由的时候会调用该文件。该文件内容如下:

module.exports = file => {
    return (resolve) => require([`@/views/${file}`], resolve)
}

filterAsyncRouter()这个函数就是用来处理路由格式的,你们可以根据后端给的路由数据做调整

四、修改layout下的index.vue改成下面这样

<template>
  <div :class="{'has-logo':showLogo}">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="variables.menuBg"
        :text-color="variables.menuText"
        :unique-opened="false"
        :active-text-color="variables.menuActiveText"
        :collapse-transition="false"
        mode="vertical"
      >
        <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'

export default {
  components: { SidebarItem, Logo },
  computed: {
    ...mapGetters([
      'sidebar'
    ]),
    routes() {
      return this.$router.options.routes.concat(global.antRouter); 
    },
    activeMenu() {
      const route = this.$route
      const { meta, path } = route
      // if set path, the sidebar will highlight the path you set
      if (meta.activeMenu) {
        return meta.activeMenu
      }
      return path
    },
    showLogo() {
      return this.$store.state.settings.sidebarLogo
    },
    variables() {
      return variables
    },
    isCollapse() {
      return !this.sidebar.opened
    }
  }
}
</script>

其实这个文件只修改了return this.$router.options.routes.concat(global.antRouter); 一行代码。通过concat方法把我们在permission.js文件里面处理好的路由数组合并到一起

到这里就大功告成啦!!!!