mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-04-05 19:41:59 +08:00
feat(router): 完善路由显示和meta字段功能
This commit is contained in:
parent
dd408611de
commit
d0108abc9e
@ -35,7 +35,6 @@ const userRoutes = [
|
||||
{
|
||||
name: 'dashboard',
|
||||
path: '/dashboard',
|
||||
redirect: '/dashboard/workbench',
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
requiresAuth: true,
|
||||
@ -65,7 +64,6 @@ const userRoutes = [
|
||||
{
|
||||
name: 'test',
|
||||
path: '/test',
|
||||
redirect: '/test/test1',
|
||||
meta: {
|
||||
title: '多级菜单演示',
|
||||
requiresAuth: true,
|
||||
@ -76,7 +74,7 @@ const userRoutes = [
|
||||
name: 'test1',
|
||||
path: '/test/test1',
|
||||
meta: {
|
||||
title: '多级菜单1',
|
||||
title: '接口功能测试',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:list',
|
||||
},
|
||||
@ -85,7 +83,7 @@ const userRoutes = [
|
||||
name: 'test2',
|
||||
path: '/test/test2',
|
||||
meta: {
|
||||
title: '多级菜单2',
|
||||
title: '多级菜单子页',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:list',
|
||||
},
|
||||
@ -94,7 +92,7 @@ const userRoutes = [
|
||||
name: 'test2_detail',
|
||||
path: '/test/test2/detail',
|
||||
meta: {
|
||||
title: '多级菜单2的详情页',
|
||||
title: '多级菜单的详情页',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:list',
|
||||
hide: true,
|
||||
@ -107,7 +105,7 @@ const userRoutes = [
|
||||
name: 'test3',
|
||||
path: '/test/test3',
|
||||
meta: {
|
||||
title: '多级菜单3',
|
||||
title: '多级菜单',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:list',
|
||||
},
|
||||
@ -128,7 +126,6 @@ const userRoutes = [
|
||||
{
|
||||
name: 'list',
|
||||
path: '/list',
|
||||
redirect: '/list/commonList',
|
||||
meta: {
|
||||
title: '列表页',
|
||||
requiresAuth: true,
|
||||
@ -158,7 +155,6 @@ const userRoutes = [
|
||||
{
|
||||
name: 'plugin',
|
||||
path: '/plugin',
|
||||
redirect: '/plugin/charts',
|
||||
meta: {
|
||||
title: '组件示例',
|
||||
requiresAuth: true,
|
||||
@ -265,7 +261,6 @@ const userRoutes = [
|
||||
{
|
||||
name: 'docments',
|
||||
path: '/docments',
|
||||
redirect: '/docments/vue',
|
||||
meta: {
|
||||
title: '外链文档',
|
||||
requiresAuth: true,
|
||||
@ -305,7 +300,6 @@ const userRoutes = [
|
||||
{
|
||||
name: 'permission',
|
||||
path: '/permission',
|
||||
redirect: '/permission/permission',
|
||||
meta: {
|
||||
title: '权限示例',
|
||||
requiresAuth: true,
|
||||
@ -336,7 +330,6 @@ const userRoutes = [
|
||||
{
|
||||
name: 'error',
|
||||
path: '/error',
|
||||
redirect: '/error/403',
|
||||
meta: {
|
||||
title: '异常页',
|
||||
requiresAuth: true,
|
||||
@ -350,6 +343,7 @@ const userRoutes = [
|
||||
title: '403页',
|
||||
requiresAuth: true,
|
||||
icon: 'carbon:error',
|
||||
order: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -359,6 +353,7 @@ const userRoutes = [
|
||||
title: '404页',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:error',
|
||||
order: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -368,6 +363,7 @@ const userRoutes = [
|
||||
title: '500页',
|
||||
requiresAuth: true,
|
||||
icon: 'carbon:data-error',
|
||||
order: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -375,7 +371,6 @@ const userRoutes = [
|
||||
{
|
||||
name: 'setting',
|
||||
path: '/setting',
|
||||
redirect: '/setting/account',
|
||||
meta: {
|
||||
title: '系统设置',
|
||||
requiresAuth: true,
|
||||
|
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<n-breadcrumb class="px-4">
|
||||
<n-breadcrumb-item v-for="(item, index) in routes" :key="index" @click="routerPush(item.path)">
|
||||
<n-breadcrumb-item
|
||||
v-for="(item, index) in routes"
|
||||
:key="index"
|
||||
@click="routerPush(item.path)"
|
||||
>
|
||||
<e-icon :icon="item.meta.icon" />
|
||||
{{ item.meta.title }}
|
||||
</n-breadcrumb-item>
|
||||
@ -17,7 +21,7 @@ const router = useRouter();
|
||||
const routeStore = useRouteStore();
|
||||
const { routerPush } = useAppRouter();
|
||||
const routes = computed(() => {
|
||||
return routeStore.createBreadcrumbFromRoutes(router.currentRoute.value.name as string, routeStore.userRoutes);
|
||||
return routeStore.createBreadcrumbFromRoutes(router.currentRoute.value.name as string);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -1,16 +1,27 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { BasicLayout } from '@/layouts/index';
|
||||
import { useRouteStore, useAuthStore } from '@/store';
|
||||
import { usePermission } from '@/hooks'
|
||||
import { useRouteStore } from '@/store';
|
||||
import { usePermission } from '@/hooks';
|
||||
|
||||
// 引入所有页面
|
||||
const modules = import.meta.glob('../../views/**/*.vue');
|
||||
|
||||
/* 含有子级的路由重定向到第一个子级 */
|
||||
function setRedirect(routes: AppRoute.Route[]) {
|
||||
routes.forEach((route) => {
|
||||
if (route.children) {
|
||||
const nonHiddenChild = route.children.find((child) => !child.meta || !child.meta.hide);
|
||||
if (nonHiddenChild) {
|
||||
route.redirect = nonHiddenChild.path;
|
||||
}
|
||||
setRedirect(route.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
/* 路由树转换成一维数组 */
|
||||
function FlatAuthRoutes(routes: AppRoute.Route[]) {
|
||||
let result: AppRoute.Route[] = [];
|
||||
// 浅拷贝一层去降维,否则影响原数组
|
||||
const _routes = JSON.parse(JSON.stringify(routes));
|
||||
_routes.forEach((item: any) => {
|
||||
routes.forEach((item: AppRoute.Route) => {
|
||||
if (item.children) {
|
||||
const temp = item.children || [];
|
||||
delete item.children;
|
||||
@ -26,8 +37,8 @@ function FlatAuthRoutes(routes: AppRoute.Route[]) {
|
||||
function filterPermissionRoutes(routes: AppRoute.Route[]) {
|
||||
const { hasPermission } = usePermission();
|
||||
return routes.filter((route) => {
|
||||
return hasPermission(route.meta.roles)
|
||||
})
|
||||
return hasPermission(route.meta.roles);
|
||||
});
|
||||
}
|
||||
|
||||
function createCatheRoutes(routes: AppRoute.Route[]) {
|
||||
@ -38,15 +49,19 @@ function createCatheRoutes(routes: AppRoute.Route[]) {
|
||||
.map((item) => item.name);
|
||||
}
|
||||
export async function createDynamicRoutes(routes: AppRoute.Route[]) {
|
||||
/* 复制一层 */
|
||||
let resultRouter = JSON.parse(JSON.stringify(routes));
|
||||
/* 设置路由重定向到子级第一个 */
|
||||
setRedirect(resultRouter);
|
||||
// 数组降维成一维数组,然后删除所有的childen
|
||||
const flatRoutes = FlatAuthRoutes(routes);
|
||||
resultRouter = FlatAuthRoutes(resultRouter);
|
||||
/* 路由权限过滤 */
|
||||
const permissionRoutes = filterPermissionRoutes(flatRoutes)
|
||||
resultRouter = filterPermissionRoutes(resultRouter);
|
||||
// 过滤需要缓存的路由name数组
|
||||
const routeStore = useRouteStore();
|
||||
routeStore.cacheRoutes = createCatheRoutes(permissionRoutes);
|
||||
routeStore.cacheRoutes = createCatheRoutes(resultRouter);
|
||||
// 生成路由,有redirect的不需要引入文件
|
||||
const mapRoutes = permissionRoutes.map((item: any) => {
|
||||
resultRouter = resultRouter.map((item: any) => {
|
||||
if (!item.redirect) {
|
||||
// 动态加载对应页面
|
||||
item['component'] = modules[`../../views${item.path}/index.vue`];
|
||||
@ -66,6 +81,6 @@ export async function createDynamicRoutes(routes: AppRoute.Route[]) {
|
||||
children: [],
|
||||
};
|
||||
// 根据角色过滤后的插入根路由中
|
||||
appRootRoute.children = mapRoutes as unknown as RouteRecordRaw[];
|
||||
appRootRoute.children = resultRouter as unknown as RouteRecordRaw[];
|
||||
return appRootRoute;
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { renderIcon,local } from '@/utils';
|
||||
import { renderIcon, local } from '@/utils';
|
||||
import { MenuOption } from 'naive-ui';
|
||||
import { createDynamicRoutes } from '@/router/guard/dynamic';
|
||||
import { router } from '@/router';
|
||||
import { fetchUserRoutes } from '@/service';
|
||||
import { staticRoutes } from '@/router/modules';
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { usePermission } from '@/hooks'
|
||||
import { h } from 'vue'
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { usePermission } from '@/hooks';
|
||||
import { h } from 'vue';
|
||||
|
||||
interface RoutesStatus {
|
||||
isInitAuthRoute: boolean;
|
||||
@ -38,7 +38,7 @@ export const useRouteStore = defineStore('route-store', {
|
||||
router.removeRoute('appRoot');
|
||||
},
|
||||
/* 根据当前路由的name生成面包屑数据 */
|
||||
createBreadcrumbFromRoutes(routeName = '/', userRoutes: AppRoute.Route[]) {
|
||||
createBreadcrumbFromRoutes(routeName = '/') {
|
||||
const path: AppRoute.Route[] = [];
|
||||
// 筛选所有包含目标的各级路由组合成一维数组
|
||||
const getPathfromRoutes = (routeName: string, userRoutes: AppRoute.Route[]) => {
|
||||
@ -51,7 +51,7 @@ export const useRouteStore = defineStore('route-store', {
|
||||
}
|
||||
});
|
||||
};
|
||||
getPathfromRoutes(routeName, userRoutes);
|
||||
getPathfromRoutes(routeName, this.userRoutes);
|
||||
return path;
|
||||
},
|
||||
/* 判断当前路由和子路由中是否存在为routeName的路由 */
|
||||
@ -77,57 +77,87 @@ export const useRouteStore = defineStore('route-store', {
|
||||
/* 生成侧边菜单的数据 */
|
||||
createMenus(userRoutes: AppRoute.Route[]) {
|
||||
this.userRoutes = userRoutes;
|
||||
this.menus = this.transformAuthRoutesToMenus(userRoutes);
|
||||
|
||||
let resultMenus = JSON.parse(JSON.stringify(userRoutes));
|
||||
resultMenus = this.removeHiddenRoutes(resultMenus);
|
||||
this.menus = this.transformAuthRoutesToMenus(resultMenus);
|
||||
},
|
||||
/** 过滤不需要显示的菜单 */
|
||||
removeHiddenRoutes(routes: AppRoute.Route[]) {
|
||||
return routes.filter((route) => {
|
||||
if (route.meta && route.meta.hide) {
|
||||
return false;
|
||||
} else if (route.children) {
|
||||
route.children = this.removeHiddenRoutes(route.children);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
//* 将返回的路由表渲染成侧边栏 */
|
||||
transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] {
|
||||
|
||||
return userRoutes
|
||||
/** 隐藏不需要显示的菜单 */
|
||||
.filter((item) => {
|
||||
return !item.meta.hide;
|
||||
})
|
||||
.filter((item: AppRoute.Route) => {
|
||||
const { hasPermission } = usePermission();
|
||||
return hasPermission(item.meta.roles)
|
||||
})
|
||||
/** 转换为侧边菜单数据结构 */
|
||||
.map((item) => {
|
||||
const target: MenuOption = {
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
path: item.path
|
||||
}
|
||||
},
|
||||
{ default: () => item.meta.title }
|
||||
),
|
||||
key: item.path,
|
||||
icon: renderIcon(item.meta.icon)
|
||||
};
|
||||
/** 判断子元素 */
|
||||
if (item.children) {
|
||||
const children = this.transformAuthRoutesToMenus(item.children);
|
||||
// 只有子元素有且不为空时才添加
|
||||
if (children.length !== 0) {
|
||||
target.children = children;
|
||||
return (
|
||||
userRoutes
|
||||
/** 过滤没有权限的侧边菜单 */
|
||||
.filter((item: AppRoute.Route) => {
|
||||
const { hasPermission } = usePermission();
|
||||
return hasPermission(item.meta.roles);
|
||||
})
|
||||
/** 根据order大小菜单排序 */
|
||||
.sort((a, b) => {
|
||||
if (a.meta && a.meta.order && b.meta && b.meta.order) {
|
||||
return a.meta.order - b.meta.order;
|
||||
} else if (a.meta && a.meta.order) {
|
||||
return -1;
|
||||
} else if (b.meta && b.meta.order) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
});
|
||||
})
|
||||
/** 转换为侧边菜单数据结构 */
|
||||
.map((item) => {
|
||||
const target: MenuOption = {
|
||||
label:
|
||||
!item.children || item.children.length == 0
|
||||
? () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
path: item.path,
|
||||
},
|
||||
},
|
||||
{ default: () => item.meta.title }
|
||||
)
|
||||
: item.meta.title,
|
||||
key: item.path,
|
||||
icon: renderIcon(item.meta.icon),
|
||||
};
|
||||
/** 判断子元素 */
|
||||
if (item.children) {
|
||||
const children = this.transformAuthRoutesToMenus(item.children);
|
||||
// 只有子元素有且不为空时才添加
|
||||
if (children.length !== 0) {
|
||||
target.children = children;
|
||||
} else {
|
||||
target.children = undefined;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
})
|
||||
);
|
||||
},
|
||||
/* 初始化动态路由 */
|
||||
async initDynamicRoute() {
|
||||
// 根据用户id来获取用户的路由
|
||||
const userInfo = local.get('userInfo')
|
||||
const userInfo = local.get('userInfo');
|
||||
|
||||
if (!userInfo||!userInfo.userId) {
|
||||
return
|
||||
if (!userInfo || !userInfo.userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: routes } = await fetchUserRoutes({ userId: userInfo.userId});
|
||||
const { data: routes } = await fetchUserRoutes({ userId: userInfo.userId });
|
||||
// 根据用户返回的路由表来生成真实路由
|
||||
const appRoutes = await createDynamicRoutes(routes);
|
||||
// 生成侧边菜单
|
||||
|
@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<n-card title="地图示例(keepalive缓存)">
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane v-for="item in maps" :key="item.id" :name="item.id" :tab="item.label" class="h-600px">
|
||||
<n-tabs
|
||||
type="line"
|
||||
animated
|
||||
>
|
||||
<n-tab-pane
|
||||
v-for="item in maps"
|
||||
:key="item.id"
|
||||
:name="item.id"
|
||||
:tab="item.label"
|
||||
class="h-600px"
|
||||
>
|
||||
<component :is="item.component" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-h1>接口功能测试</n-h1>
|
||||
<n-space>
|
||||
<n-button
|
||||
strong
|
||||
|
@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div text-center>
|
||||
I prove that you have made the ju mp test2.
|
||||
<n-button @click="routerPush('/test/test2/detail')">跳转详情子页</n-button>
|
||||
这个页面包含了一个不在侧边菜单的详情页面
|
||||
<n-button @click="routerPush('/test/test2/detail')">
|
||||
跳转详情子页
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped></style>
|
@ -1,7 +1,17 @@
|
||||
<template>
|
||||
<div text-center c-red>
|
||||
I prove that you have made the jump test4.
|
||||
<n-button strong secondary type="success" @click="testMsg">testMsg</n-button>
|
||||
<div
|
||||
text-center
|
||||
c-red
|
||||
>
|
||||
三级菜单页
|
||||
<n-button
|
||||
strong
|
||||
secondary
|
||||
type="success"
|
||||
@click="testMsg"
|
||||
>
|
||||
testMsg
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user