feat(object): 修改根据返回路由表动态生成路由和菜单

This commit is contained in:
Coffee-crocodile 2022-08-17 16:16:51 +08:00
parent c6aa06637a
commit e4dc741844
11 changed files with 176 additions and 191 deletions

View File

@ -85,85 +85,102 @@ const userInfo = {
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg', avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
role: 'admin', role: 'admin',
password: '123456', password: '123456',
token,
userRoutes: [
{
name: 'dashboard',
path: '/dashboard',
meta: {
title: '分析页',
requiresAuth: true,
icon: 'icon-park-outline:analysis',
},
children: [
{
name: 'dashboard_workbench',
path: '/dashboard/workbench',
meta: {
title: '工作台',
requiresAuth: true,
icon: 'icon-park-outline:alarm',
},
},
{
name: 'dashboard_monitor',
path: '/dashboard/monitor',
meta: {
title: '监控页',
requiresAuth: true,
icon: 'icon-park-outline:anchor',
},
},
],
},
{
name: 'test',
path: '/test',
meta: {
title: '测试专题',
requiresAuth: true,
icon: 'icon-park-outline:ambulance',
},
children: [
{
name: 'test1',
path: '/test/test1',
meta: {
title: '测试专题1',
requiresAuth: true,
icon: 'icon-park-outline:alarm',
},
},
{
name: 'test2',
path: '/test/test2',
meta: {
title: '测试专题2',
requiresAuth: true,
icon: 'icon-park-outline:pic',
},
},
{
name: 'test3',
path: '/test/test3',
meta: {
title: '测试专题3',
requiresAuth: true,
icon: 'icon-park-outline:tool',
},
},
],
},
],
}; };
const userRoutes = [
{
name: 'dashboard',
path: '/dashboard',
redirect: '/dashboard/workbench',
meta: {
title: '分析页',
requiresAuth: true,
icon: 'icon-park-outline:analysis',
},
children: [
{
name: 'dashboard_workbench',
path: '/dashboard/workbench',
meta: {
title: '工作台',
requiresAuth: true,
icon: 'icon-park-outline:alarm',
},
},
{
name: 'dashboard_monitor',
path: '/dashboard/monitor',
meta: {
title: '监控页',
requiresAuth: true,
icon: 'icon-park-outline:anchor',
},
},
],
},
{
name: 'test',
path: '/test',
redirect: '/test/test1',
meta: {
title: '测试专题',
requiresAuth: true,
icon: 'icon-park-outline:ambulance',
},
children: [
{
name: 'test1',
path: '/test/test1',
meta: {
title: '测试专题1',
requiresAuth: true,
icon: 'icon-park-outline:alarm',
},
},
{
name: 'test2',
path: '/test/test2',
meta: {
title: '测试专题2',
requiresAuth: true,
icon: 'icon-park-outline:pic',
},
},
{
name: 'test3',
path: '/test/test3',
meta: {
title: '测试专题3',
requiresAuth: true,
icon: 'icon-park-outline:tool',
},
},
],
},
];
export default [ export default [
{ {
url: '/mock/login', url: '/mock/login',
timeout: 1000, timeout: 1000,
method: 'post', method: 'post',
response: () => {
return resultSuccess({ token });
},
},
{
url: '/mock/getUserInfo',
timeout: 1000,
method: 'get',
response: () => { response: () => {
return resultSuccess(userInfo); return resultSuccess(userInfo);
}, },
}, },
{
url: '/mock/getUserRoutes',
timeout: 1000,
method: 'post',
response: () => {
return resultSuccess(userRoutes);
},
},
]; ];

View File

@ -1,77 +1,45 @@
import { RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw } from 'vue-router';
import { router } from '@/router';
import { BasicLayout } from '@/layouts/index'; import { BasicLayout } from '@/layouts/index';
import { useAuthStore } from '@/store';
export async function setDynamicRoutes() { // 引入所有页面
const authStore = useAuthStore(); const modules = import.meta.glob('../../views/**/*.vue');
const vueRootRoute: RouteRecordRaw = { /* 将路由树转换成一维数组 */
path: '/test', function FlatAuthRoutes(routes: AppRoute.Route[]) {
name: 'test', let result: AppRoute.Route[] = [];
redirect: '/test/test1', // 浅拷贝一层去降维,否则影响原数组
const _routes = JSON.parse(JSON.stringify(routes));
_routes.forEach((item: any) => {
if (item.children) {
const temp = item.children || [];
delete item.children;
result.push(item);
result = [...result, ...FlatAuthRoutes(temp)];
} else {
result.push(item);
}
});
return result;
}
export async function createDynamicRoutes(routes: AppRoute.Route[]) {
// 数组降维成一维数组,然后删除所有的childen
const flatRoutes = FlatAuthRoutes(routes);
// 生成路由有redirect的不需要引入文件
const mapRoutes = flatRoutes.map((item) => {
if (!item.redirect) {
// 动态加载对应页面
item['component'] = modules[`../../views${item.path}/index.vue`];
}
return item;
});
const appRootRoute: RouteRecordRaw = {
path: '/',
name: 'appRoot',
redirect: '/dashboard/workbench',
component: BasicLayout, component: BasicLayout,
children: [], children: [],
}; };
const dynamicRoutes = [
{
path: '/dashboard/workbench',
name: 'workbench',
component: () => import('@/views/dashboard/workbench/index.vue'),
meta: {
title: '工作台',
icon: 'icon-park-outline:music-list',
requiresAuth: true,
role: ['admin'],
},
},
{
path: '/dashboard/monitor',
name: 'monitor',
component: () => import('@/views/dashboard/monitor/index.vue'),
meta: {
title: '控制页',
icon: 'icon-park-outline:music-list',
requiresAuth: true,
role: ['admin'],
},
},
{
path: '/test/test1',
name: 'test1',
component: () => import(`@/views/test/test1.vue`),
meta: {
title: '测试1',
icon: 'icon-park-outline:game-three',
requiresAuth: true,
role: ['admin'],
},
},
{
path: '/test/test2',
name: 'test2',
component: () => import('@/views/test/test2.vue'),
meta: {
title: '测试2',
icon: 'carbon:aperture',
requiresAuth: true,
role: ['admin'],
},
},
{
path: '/test/test3',
name: 'test3',
component: () => import('@/views/test/test3.vue'),
meta: {
title: '测试3',
icon: 'icon-park-outline:music-list',
requiresAuth: true,
role: ['admin'],
},
},
];
// 根据角色过滤后的插入根路由中 // 根据角色过滤后的插入根路由中
vueRootRoute.children = dynamicRoutes.filter((route) => { appRootRoute.children = mapRoutes as unknown as RouteRecordRaw[];
return route.meta.role.includes(authStore.userInfo.role); return appRootRoute;
});
router.addRoute(vueRootRoute);
} }

View File

@ -1,7 +1,7 @@
import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router'; import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { useRouteStore } from '@/store'; import { useRouteStore } from '@/store';
import { setDynamicRoutes } from './dynamic'; // import { setDynamicRoutes } from './dynamic';
export async function createPermissionGuard( export async function createPermissionGuard(
to: RouteLocationNormalized, to: RouteLocationNormalized,
@ -25,7 +25,6 @@ export async function createPermissionGuard(
} }
return false; return false;
} }
// debugger;
// 有登录但是没有路由,初始化路由、侧边菜单等 // 有登录但是没有路由,初始化路由、侧边菜单等
// await setDynamicRoutes(); // await setDynamicRoutes();
await routeStore.initAuthRoute(); await routeStore.initAuthRoute();

View File

@ -7,3 +7,9 @@ interface Ilogin {
export function fetchLogin(params: Ilogin) { export function fetchLogin(params: Ilogin) {
return mockRequest.post('/login', params); return mockRequest.post('/login', params);
} }
export function fetchUserInfo() {
return mockRequest.get('/getUserInfo');
}
export function fetchUserRoutes(params: string) {
return mockRequest.post('/getUserRoutes', params);
}

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { fetchLogin } from '@/service'; import { fetchLogin, fetchUserInfo } from '@/service';
import { setUserInfo, getUserInfo, getToken, setToken, clearAuthStorage } from '@/utils/auth'; import { setUserInfo, getUserInfo, getToken, setToken, clearAuthStorage } from '@/utils/auth';
import { router } from '@/router'; import { router } from '@/router';
import { useAppRouter } from '@/hook'; import { useAppRouter } from '@/hook';
@ -40,20 +40,19 @@ export const useAuthStore = defineStore('auth-store', {
async login(userName: string, password: string) { async login(userName: string, password: string) {
this.loginLoading = true; this.loginLoading = true;
const { data } = await fetchLogin({ userName, password }); const { data } = await fetchLogin({ userName, password });
// 处理登录信息 // 处理登录信息
await this.handleAfterLogin(data as any); // TODO 避免any await this.handleAfterLogin(data); // TODO 避免any
this.loginLoading = false; this.loginLoading = false;
}, },
/* 登录后的处理函数 */ /* 登录后的处理函数 */
async handleAfterLogin(data: Auth.UserInfo) { async handleAfterLogin(data: Auth.loginToken) {
// 等待数据写入完成 // 将token和userInfo保存下来
const catchSuccess = await this.catchUserInfo(data); const catchSuccess = await this.catchUserInfo(data);
// 初始化侧边菜单 // 初始化侧边菜单
const { initAuthRoute } = useRouteStore(); // const { initAuthRoute } = useRouteStore();
await initAuthRoute(); // await initAuthRoute();
// 登录写入信息成功 // 登录写入信息成功
if (catchSuccess) { if (catchSuccess) {
@ -75,14 +74,18 @@ export const useAuthStore = defineStore('auth-store', {
}, },
/* 缓存用户信息 */ /* 缓存用户信息 */
async catchUserInfo(data: Auth.UserInfo) { async catchUserInfo(userToken: Auth.loginToken) {
let catchSuccess = false; let catchSuccess = false;
// 先存储token
const { token } = userToken;
setToken(token);
// 存储用户信息 // 请求/存储用户信息
const { data } = await fetchUserInfo();
setUserInfo(data); setUserInfo(data);
setToken(data.token); // 再将token和userInfo初始化
this.userInfo = data; this.userInfo = data;
this.token = data.token; this.token = token;
catchSuccess = true; catchSuccess = true;

View File

@ -1,8 +1,9 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { renderIcon, getUserInfo } from '@/utils'; import { renderIcon, getUserInfo } from '@/utils';
import { MenuOption, radioGroupProps } from 'naive-ui'; import { MenuOption } from 'naive-ui';
import { setDynamicRoutes } from '@/router/guard/dynamic'; import { createDynamicRoutes } from '@/router/guard/dynamic';
import { router } from '@/router'; import { router } from '@/router';
import { fetchUserRoutes } from '@/service';
interface RoutesStatus { interface RoutesStatus {
isInitAuthRoute: boolean; isInitAuthRoute: boolean;
@ -24,17 +25,25 @@ export const useRouteStore = defineStore('route-store', {
}, },
resetRoutes() { resetRoutes() {
/* 删除后面添加的路由 */ /* 删除后面添加的路由 */
router.removeRoute('test'); router.removeRoute('appRoot');
}, },
async setUserRoutes() { createMenus(userRoutes: AppRoute.Route[]) {
this.userRoutes = getUserInfo().userRoutes; this.userRoutes = userRoutes;
this.menus = this.transformAuthRoutesToMenus(userRoutes);
}, },
async setMenus() { async initDynamicRoute() {
this.setUserRoutes(); // 根据用户id来获取用户的路由
this.menus = this.transformAuthRoutesToMenus(this.userRoutes); const { userId } = getUserInfo();
const { data } = await fetchUserRoutes(userId);
// 根据用户返回的路由表来生成真实路由
const appRoutes = await createDynamicRoutes(data);
// 更具返回的生成侧边菜单
await this.createMenus(data);
// 插入路由表
router.addRoute(appRoutes);
}, },
// 将返回的路由表渲染成侧边栏 // 将返回的路由表渲染成侧边栏
transformAuthRoutesToMenus(userRoutes: Auth.UserInfoPermissions[]): MenuOption[] { transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] {
return userRoutes.map((item) => { return userRoutes.map((item) => {
const target: MenuOption = { const target: MenuOption = {
label: item.meta.title, label: item.meta.title,
@ -52,25 +61,8 @@ export const useRouteStore = defineStore('route-store', {
}); });
}, },
/* 将路由树转换成一维数组 */
FlatAuthRoutes(routes: AppRoute.Route[]) {
let result: AppRoute.Route[] = [];
routes.forEach((item) => {
if (Object.prototype.hasOwnProperty.call(item, 'children')) {
const temp = item.children || [];
delete item.children;
result.push(item);
result = [...result, ...this.FlatAuthRoutes(temp)];
} else {
result.push(item);
}
});
return result;
},
async initAuthRoute() { async initAuthRoute() {
await this.setMenus(); await this.initDynamicRoute();
await setDynamicRoutes();
this.isInitAuthRoute = true; this.isInitAuthRoute = true;
}, },
}, },

View File

@ -10,6 +10,10 @@ declare namespace Auth {
// type RoleType = keyof typeof import('@/enum').EnumUserRole; // type RoleType = keyof typeof import('@/enum').EnumUserRole;
/** 用户信息 */ /** 用户信息 */
interface loginToken {
token: string;
}
interface UserInfo { interface UserInfo {
/** 用户id */ /** 用户id */
userId: string; userId: string;
@ -23,16 +27,12 @@ declare namespace Auth {
role: RoleType; role: RoleType;
/* 密码 */ /* 密码 */
password: string; password: string;
/* token */
token: string;
/* 权限路由 */
userRoutes: UserInfoPermissions[];
}
interface UserInfoPermissions {
name: string;
path: string;
meta: AppRoute.RouteMeta;
children?: UserInfoPermissions[];
redirect: string;
} }
// interface userRoutes {
// name: string;
// path: string;
// meta: AppRoute.RouteMeta;
// children?: userRoutes[];
// redirect: string;
// }
} }

View File

@ -18,7 +18,7 @@ declare namespace AppRoute {
/** 子路由 */ /** 子路由 */
children?: Route[]; children?: Route[];
/** 路由描述 */ /** 路由描述 */
meta?: RouteMeta; meta: RouteMeta;
/** 路由属性 */ /** 路由属性 */
// props?: boolean | Record<string, any> | ((to: any) => Record<string, any>); // props?: boolean | Record<string, any> | ((to: any) => Record<string, any>);
} }