mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-05-30 06:39:16 +08:00
feat(object): 修改根据返回路由表动态生成路由和菜单
This commit is contained in:
parent
c6aa06637a
commit
e4dc741844
@ -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);
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
22
src/types/business.d.ts
vendored
22
src/types/business.d.ts
vendored
@ -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;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
2
src/types/route.d.ts
vendored
2
src/types/route.d.ts
vendored
@ -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>);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user