Vue Router搭配Vuex动态路由

1. 定义基础路由:定义应用的基础路由,通常包含静态页面如登录页、404 页等。
2. 异步获取路由数据:从后台 API 获取用户或者角色对应的路由数据。
3. 动态添加路由:根据获取的路由数据搭配Vuex动态生成 Vue Router 实例,并添加到当前路由配置中。

1. 定义基础路由

基础路由通常是一些不变的页面,比如登录页、404 错误页等。

// src/router/staticRoutes.js

import Layout from '@/layout/index.vue';

export const staticRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index.vue'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/404.vue'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index.vue'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  }
];

2. 异步获取路由数据

通过 API 获取用户或角色对应的路由数据。

// src/api/user.js

import axios from 'axios';

export function getRoleMenuList(roleId) {
  return axios.get(`/api/role/${roleId}/menu`);
}

3. 动态添加路由

根据后端返回的数据动态生成路由并添加到路由实例中。

3.1 动态路由生成函数

把后端的菜单数据转为 Vue Router 格式。

// src/utils/asyncRoutes.js

import Layout from '@/layout/index.vue';

const _import = require('@/router/_import_' + process.env.NODE_ENV);

export function parseAsyncRoutes(routes) {
  const res = [];

  routes.forEach(route => {
    const tmp = { ...route };
    if (tmp.component) {
      if (tmp.component === 'Layout') {
        tmp.component = Layout;
      } else {
        tmp.component = _import(tmp.component);
      }
    }
    if (tmp.children) {
      tmp.children = parseAsyncRoutes(tmp.children);
    }
    res.push(tmp);
  });

  return res;
}

3.2 动态添加路由

在 Vuex 中通过 action 动态获取和添加路由。(permission模块)

// src/store/modules/permission.js

import { getRoleMenuList } from '@/api/user';
import { parseAsyncRoutes } from '@/utils/asyncRoutes';
import { staticRoutes } from '@/router/staticRoutes';

const state = {
  routes: [],
  addRoutes: []
};

const mutations = {
  SET_ROUTES: (state, routes) => {
    // 清空之前的动态路由
    state.addRoutes.forEach(route => {
      if (router.hasRoute(route.name)) {
        router.removeRoute(route.name);
      }
    });

    state.addRoutes = routes;
    state.routes = staticRoutes.concat(routes);

    // 添加新路由
    routes.forEach(route => {
      router.addRoute(route);
    });
  }
};

const actions = {
  async generateRoutes({ commit }, roleId) {
    try {
      const response = await getRoleMenuList(roleId);
      const { data } = response;
      const asyncRoutes = parseAsyncRoutes(data);
      commit('SET_ROUTES', asyncRoutes);
      return asyncRoutes;
    } catch (error) {
      throw new Error(`Failed to generate routes: ${error.message}`);
    }
  }
};

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

在退出or登录时重置路由信息。(user模块)

// src/store/modules/user.js

import router, { resetRouter } from '@/router'; // 确保引入路由实例
import { login, getRoleMenuList } from '@/api/user'; // 确保引入login、getRoleMenuList 方法
import { parseAsyncRoutes } from '@/utils/asyncRoutes';
import { removeToken, setToken } from '@/utils/auth'; // 假设有 token 管理工具

const getDefaultState = () => {
  return {
    token: '',
    name: '',
    roles: []
  };
};

const state = getDefaultState();

const mutations = {
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState());
  },
  SET_TOKEN: (state, token) => {
    state.token = token;
  },
  SET_NAME: (state, name) => {
    state.name = name;
  },
  SET_ROLES: (state, roles) => {
    state.roles = roles;
  }
};

const actions = {
  async login({ commit, dispatch }, userInfo) {
    const { username, password } = userInfo;
    try {
      // 登录请求
      const response = await login({ username, password });
      const { token, roleId } = response.data;

      commit('SET_TOKEN', token);
      setToken(token); // 保存 token

      // 添加新的动态路由
      await dispatch('permission/generateRoutes', roleId, { root: true });

      // 重置路由
      resetRouter();
    } catch (error) {
      throw new Error('Login failed');
    }
  },
  logout({ commit }) {
    return new Promise((resolve) => {
      removeToken(); // 移除 token
      resetRouter(); // 重置路由
      commit('RESET_STATE');
      resolve();
    });
  }
};

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

4. 在 Vue 应用中使用

在 Vuex 的主模块整合 permission 和 user 子模块

// src/store/index.js

import { createStore } from 'vuex';
import permission from './modules/permission';
import user from './modules/user';

const state = {
  // 全局 state
};

const mutations = {
  // 全局 mutations
};

const actions = {
  // 全局 actions
};

// 创建 Vuex store 实例
const store = createStore({
  state,
  mutations,
  actions,
  modules: {
    permission,
    user
  }
});

export default store;

在 router 主文件中引入这些配置,并在创建 Vue 实例前加载动态路由。

// src/router/index.js

import Vue from 'vue';
import Router from 'vue-router';
import { staticRoutes } from './staticRoutes';

Vue.use(Router);

const createRouter = () => new Router({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes: staticRoutes
});

const router = createRouter();

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

export default router;

在 main.js 中处理动态路由加载。

// src/main.js
 
import Vue from 'vue';
import App from './App.vue';
import store from './store';
import router from './router';
import { getToken, getCurrentUserRoleId } from '@/utils/auth'; // 获取 token 和角色 ID 的工具函数
import { resetRouter } from '@/router';
 
//当 Vue.config.productionTip 设置为 false 时,Vue 在启动时不会在控制台输出这条提示信息。
//这对生产环境是有意义的,因为在生产环境中,你不需要这些开发相关的提示信息。
//Vue.config.productionTip = false;
 
async function initializeApplication() {
  try {
    const token = getToken();
    if (token) {
      const roleId = getCurrentUserRoleId();
      if (roleId) {
        await store.dispatch('permission/generateRoutes', roleId);
        router.addRoutes(store.state.permission.addRoutes);
      }
    }
  } catch (error) {
    console.error('Failed to initialize application:', error);
  } finally {
    // 无论是否成功初始化,都要创建 Vue 实例
    createVueInstance();
  }
}
 
function createVueInstance() {
  new Vue({
    el: '#app',
    router,
    store,
    render: h => h(App)
  });
}
 
initializeApplication();
posted @ 2024-06-14 14:57  苏沐~  阅读(98)  评论(0编辑  收藏  举报