主要分为几个步骤
- 登录页面登录完获取用户个人信息 然后获取其相应的权限信息
- 编写处理后端返回的路由信息的方法,处理成路由可使用的格式.
- vuex里边写GET_INFO 和 GENERATE_ROUTES方法 用户登录的时候调用, 调用GET_INFO获取个人信息,然后拿到token再去调用GENERATE_ROUTES获取权限信息. 在获取权限信息后调用编写好的方法处理路由信息然后动态挂载,在vuex里存储处理好的路由信息
- 路由守卫处理 挂载动态路由
- Layout布局使用处理好的路由信息
以下只是提供思路 具体项目的实施还需看具体情况 我这属于简化版
需要做404页面 防止用户跳转没有权限的页面
后端返回路由信息分两种情况
一种是后端只返回当前用户有权限的路由 一种是返回所有路由 然后根据权限过滤路由
第一种根据后端返回路由信息处理一下就好了 第二种需要根据角色权限跟后端返回路由信息做对比
以下是示例代码:
1. 用户登录获取个人信息 和 路由信息
// store.js
import { createStore } from 'vuex';
import { constantRoutes } from '@/router'
import AuthAPI from "@/api/auth";
import { generateRoutesFromMenu } from '@/utils/menu-util';
export const store = createStore({
state: {
token: '',
menuData: null,
},
mutations: {
setToken(state, token) {
state.token = token;
},
setMenuData(state, menuData) {
state.menuData = constantRoutes.concat(menuData);
},
},
actions: {
//登录
async login({ commit }, { username, password }) {
try {
const token = await AuthAPI.login(username, password);
commit('setToken', 'token');
} catch (error) {
console.error('Login failed:', error);
throw error;
}
},
//获取菜单
async getMenuList() {
try {
// 调用接口进行登录,获取菜单信息
const menuData = await AuthAPI.getMenu(username, password);
//处理后端返回信息
const menuList = generateRoutesFromMenu(menuData);
commit('setMenuData', menuList);
} catch (error) {
console.error('Login failed:', error);
throw error;
}
},
},
getters: {
isAuthenticated: state => !!state.token
// 其他可能需要的 getter
}
});
2. 登录页面
// Login.vue
<template>
<form @submit.prevent="login">
<input v-model="username" type="text" placeholder="Username">
<input v-model="password" type="password" placeholder="Password">
<button type="submit">Login</button>
</form>
</template>
<script>
import { ref } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
const username = ref('');
const password = ref('');
const store = useStore();
const login = async () => {
try {
await store.dispatch('login', { username: username.value, password: password.value });
// 登录成功后跳转到首页或其他页面
router.push('/');
} catch (error) {
console.error('Login failed:', error);
}
};
return { username, password, login };
},
};
</script>
3.处理后端返回的路由信息
这个方法可以写在untils里 也可以写在vuex里 这个方法的写法也可以写成只处理component的形式 参考下边第二种情况的处理
// 处理后端返回的路由信息并将其转换为路由配置的函数
export function generateRoutesFromMenu(menuItems) {
const routes = [];
menuItems.forEach(item => {
const route = {
path: item.path,
name: item.name,
component: item.component?.toString() == 'Layout' ?
Layout : () => import(`./views/${item.component}`),
};
if (item.children && item.children.length > 0) {
route.children = generateRoutesFromMenu(item.children);
}
routes.push(route);
});
return routes;
}
4.router.js
// router.js
import { createRouter, createWebHistory } from 'vue-router';
import { GENERATE_ROUTES, GET_INFO } from '@/store/modules/user/actions';
const routes = [
{
path: '/',
name: 'Login',
component: () => import('./views/Login.vue'),
},
// 其他静态路由...
];
const router = createRouter({
history: createWebHistory(),
routes,
});
// 全局前置守卫,用于处理动态路由的加载
router.beforeEach(async (to, from, next) => {
/ check login user.role is null
if (store.state.menuData && store.state.menuData.length > 0) {
return true;
} else {
//分两种情况 一种是后端只返回当前用户有权限的路由 一种是返回所有路由 然后根据权限过滤路由
//第一种情况
//const info = await store.dispatch(`user/${GET_INFO}`);
// 使用当前用户的 权限信息 生成 对应权限的路由表
//const allowRouters = await store.dispatch('getMenuList', info);
//第二种情况
const allowRouters = await store.dispatch('getMenuList');
allowRouters.forEach((route) => {
router.addRoute(route);
});
if (allowRouters) {
return { ...to, replace: true };
}
return false;
}
next(); // 继续导航
});
export default router;
这里有另外一种方法 路由文件里面只写静态路由 然后封装一个文件 该文件要引入到main.js文件中使用
import router from './router'
import store from './store'
import { ElMessage } from 'element-plus'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate'
NProgress.configure({ showSpinner: false });
//白名单,不进行拦截处理
const whiteList = ['/login', '/auth-redirect', '/bind', '/register'];
//路由拦截
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && store.dispatch('setTitle', to.meta.title)
/* has token*/
if (to.path === '/login') {
console.log(111)
next({ path: '' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
// isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
// isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
//捕捉错误,退出登录
store.dispatch('LogOut').then(() => {
ElMessage.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
另一种情况 如果需要根据权限过滤路由信息
const modules = import.meta.glob("../../views/**/**.vue");
const filterAsyncRoutes = (routes: RouteVO[], roles: string[]) => {
const asyncRoutes: RouteRecordRaw[] = [];
routes.forEach((route) => {
const tmpRoute = { ...route } as RouteRecordRaw; // 深拷贝 route 对象 避免污染
if (hasPermission(roles, tmpRoute)) {
// 如果是顶级目录,替换为 Layout 组件
if (tmpRoute.component?.toString() == "Layout") {
tmpRoute.component = Layout;
} else {
// 如果是子目录,动态加载组件
const component = modules[`../../views/${tmpRoute.component}.vue`];
if (component) {
tmpRoute.component = component;
} else {
tmpRoute.component = modules[`../../views/error-page/404.vue`];
}
}
if (tmpRoute.children) {
tmpRoute.children = filterAsyncRoutes(route.children, roles);
}
asyncRoutes.push(tmpRoute);
}
});
return asyncRoutes;
};