一、新增页面
- 新增三级菜单页
- @/views/Menu/Menu2目录下新增Menu2-1目录
- @/views/Menu/Menu2/Menu2-1目录新增index.vue
- @/views/Menu/Menu2/Menu2-1/index.vue
<template>
<h1>Menu2-1页面!</h1>
</template>
<script lang="ts" setup></script>
- 修改Menu2页面
- @/views/Menu/Menu2/index.vue
<template>
<router-view />
</template>
二、修改Mock动态路由数据
- mock/data/routes.ts
interface routeConfig {
path: string;
name: string;
component?: string;
redirect?: string;
meta: {
title: string;
layout: boolean;
menu: boolean;
icon: string;
sort?: number;
};
children?: routeConfig[];
}
const rootRoutes: routeConfig[] = [
{
path: '/root',
name: 'root',
component: 'views/Root/index.vue',
meta: {
title: 'Root权限页',
layout: true,
menu: true,
icon: 'KeyOutlined',
sort: 2,
},
},
{
path: '/menu',
name: 'menu',
component: 'views/Menu/index.vue',
redirect: '/menu/menu1',
meta: {
title: '多级菜单页',
layout: true,
menu: true,
icon: 'MenuOutlined',
sort: 3,
},
children: [
{
path: 'menu1',
name: 'menu1',
component: 'views/Menu/Menu1/index.vue',
meta: {
title: 'Menu1页',
layout: true,
menu: true,
icon: 'MenuOutlined',
sort: 1,
},
},
{
path: 'menu2',
name: 'menu2',
component: 'views/Menu/Menu2/index.vue',
redirect: 'menu2/menu2-1',
meta: {
title: 'Menu2页',
layout: true,
menu: true,
icon: 'MenuOutlined',
sort: 2,
},
children: [
{
path: 'menu2-1',
name: 'menu2-1',
component: 'views/Menu/Menu2/Menu2-1/index.vue',
meta: {
title: 'Menu2-1页',
layout: true,
menu: true,
icon: 'MenuOutlined',
sort: 3,
},
},
],
},
],
},
];
const adminRoutes: routeConfig[] = [
{
path: '/admin',
name: 'admin',
component: 'views/Admin/index.vue',
meta: {
title: 'Admin权限页',
layout: true,
menu: true,
icon: 'KeyOutlined',
sort: 2,
},
},
];
const userRoutes: routeConfig[] = [];
export { rootRoutes, adminRoutes, userRoutes, routeConfig };
三、修改静态路由数据
- @/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
import { useUserStore } from '@/store';
import { addRoutes, clerRoutes } from '@/util/anyncRoutes';
const routes = [
{
path: '/',
name: 'home',
component: () => import('@/views/Home/index.vue'),
meta: {
title: '首页',
layout: true,
menu: true,
icon: 'DashboardOutlined',
sort: 1,
},
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login/index.vue'),
meta: {
title: '登录',
layout: false,
menu: false,
icon: 'LoginOutlined',
},
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
let registerRouteFresh = true;
router.beforeEach((to, from, next) => {
const userStore = useUserStore();
if (to.meta.title) {
document.title = to.meta.title as string;
}
if (to.name !== 'Login' && !userStore.token) {
next({ name: 'Login' });
} else {
if (to.name === 'Login') {
userStore.clearToken();
userStore.clearUser();
clerRoutes(userStore, router);
}
if (!from.name && registerRouteFresh) {
addRoutes(userStore, router);
next({ ...to, replace: true });
registerRouteFresh = false;
} else {
next();
}
}
});
export default router;
- 修改@/util/anyncRoutes.ts
......
// 添加动态路由
const addRoutes = (userStore, router) => {
......
router.addRoute({
path: '/:pathMatch(.*)*',
name: '404',
component: () => import('@/views/404/index.vue'),
meta: {
title: '无法找到该页面',
layout: false,
menu: false,
},
});
};
......
四、修改网站标题
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台基础模板</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
五、新建图标动态引入的方法
- @/util目录下新建icon.ts文件
- @/util/icon.ts
import { createVNode } from 'vue';
import { DashboardOutlined, MenuOutlined, KeyOutlined } from '@ant-design/icons-vue';
// import * as $Icon from '@ant-design/icons-vue';
const $Icon = {
DashboardOutlined,
MenuOutlined,
KeyOutlined,
};
const Icon = (props: { icon: string }) => {
const { icon } = props;
const antIcon: { [key: string]: any } = $Icon;
return createVNode(antIcon[icon]);
};
export default Icon;
六、抽离菜单模块
- @/layout/modules目录下新建文件Menu.vue
<template>
<a-menu theme="dark" mode="inline" v-model:selectedKeys="routeKey" @select="menuSelect">
<template v-for="item in routesData">
<template v-if="item.meta.menu">
<template v-if="item.children && item.children.length > 0">
<sub-menu :routes-sub-data="item" :key="item.name" />
</template>
<template v-else>
<a-menu-item :key="item.name">
<template #icon v-if="item.meta.icon">
<Icon :icon="item.meta.icon" />
</template>
<span>{{ item.meta.title }}</span>
</a-menu-item>
</template>
</template>
</template>
</a-menu>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } from 'vue';
import { useRouter, useRoute, RouteRecordName } from 'vue-router';
import Icon from '@/util/icon';
import SubMenu from './SubMenu.vue';
const route = useRoute();
const router = useRouter();
const routeKey = ref<(RouteRecordName | null | undefined)[]>([]);
const routesData = ref<any[]>([]);
const menuSelect = (item: { key: string }) => {
router.push({ name: item.key });
};
const setSortRouter = (tree: any[]) => {
tree.sort((aItem, bItem) => {
return aItem.meta.sort - bItem.meta.sort;
});
return tree.map((node) => {
const tempNode = node;
if (tempNode.children && tempNode.children.length > 0) {
setSortRouter(tempNode.children);
}
return tempNode;
});
};
const getMenu = () => {
let menuRouters: any[] = router.getRoutes();
menuRouters = menuRouters.filter((item: any) => {
return item.meta.menu && item.path.split('/').length === 2;
});
setSortRouter(menuRouters);
routesData.value = menuRouters;
routeKey.value = [route.name];
};
onMounted(() => {
getMenu();
});
watch(
() => route.name,
(val) => {
if (val) {
routeKey.value = [route.name];
}
}
);
</script>
<style lang="less" scoped></style>
- @/layout/modules目录下新建文件SubMenu.vue
<template>
<a-sub-menu :key="routesSubData.name">
<template #icon v-if="routesSubData.meta.icon">
<Icon :icon="routesSubData.meta.icon" />
</template>
<template #title>{{ routesSubData.meta.title }}</template>
<template v-for="item in routesSubData.children">
<template v-if="item.children && item.children.length > 0">
<sub-menu :routes-sub-data="item" :key="item.name" />
</template>
<template v-else>
<a-menu-item :key="item.name">
<template #icon v-if="item.meta.icon">
<Icon :icon="item.meta.icon" />
</template>
<span>{{ item.meta.title }}</span>
</a-menu-item>
</template>
</template>
</a-sub-menu>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
import Icon from '@/util/icon';
defineProps({
routesSubData: {
type: Object,
default: () => {},
},
});
</script>
<style lang="less" scoped></style>
- @/layout/index.vue
<template>
<a-layout class="layout">
<a-layout-sider v-model:collapsed="layoutStore.menuCollapsed" :trigger="null" collapsible>
<div class="logo" />
<Menu />
</a-layout-sider>
<a-layout>
<Header />
<a-layout-content style="margin: 24px 16px; padding: 24px; background: #fff; min-height: 280px">
<a-spin :spinning="loadingStore.loadingState" :delay="300" size="large">
<router-view />
</a-spin>
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script lang="ts" setup>
import { useLoadingStore, useLayoutStore } from '@/store';
import Menu from './modules/Menu.vue';
import Header from './modules/Header.vue';
const loadingStore = useLoadingStore();
const layoutStore = useLayoutStore();
</script>
<style lang="less" scoped>
......
</style>
七、源代码地址
https://github.com/jiangzetian/vue3-admin-template
八、视频演示及源码
本文演示视频:点击浏览
更多前端内容欢迎关注公众号:天小天个人网