feat(layout): 优化侧边栏菜单生成逻辑并增强类型安全
This commit is contained in:
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -16,5 +16,8 @@
|
|||||||
"[vue]": {
|
"[vue]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"typescript.preferences.autoImportFileExcludePatterns": ["vue-router/auto$"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="tsx">
|
||||||
import type { MenuOption } from 'naive-ui';
|
|
||||||
import type { RouteRecordNormalized } from 'vue-router';
|
|
||||||
import { createGetRoutes, router } from '@/plugins/router-plugin';
|
import { createGetRoutes, router } from '@/plugins/router-plugin';
|
||||||
|
import type { MenuOption } from 'naive-ui';
|
||||||
|
import { RouterLink, type RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
// 路由转换为菜单树的辅助函数
|
// 路由转换为菜单树的辅助函数
|
||||||
function convertRoutesToMenuOptions(routes: RouteRecordNormalized[]): MenuOption[] {
|
function convertRoutesToMenuOptions(routes: Readonly<RouteRecordRaw[]>): MenuOption[] {
|
||||||
const menuMap = new Map<string, MenuOption>();
|
const menuMap = new Map<string, MenuOption>();
|
||||||
const rootMenus: MenuOption[] = [];
|
const rootMenus: MenuOption[] = [];
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ function convertRoutesToMenuOptions(routes: RouteRecordNormalized[]): MenuOption
|
|||||||
const validRoutes = routes
|
const validRoutes = routes
|
||||||
.filter((route) => {
|
.filter((route) => {
|
||||||
// 过滤掉不需要显示的路由
|
// 过滤掉不需要显示的路由
|
||||||
if (!route.name || route.meta?.hidden === true || route.meta?.layout === false) {
|
if (route.meta?.hidden === true || route.meta?.layout === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 过滤掉通配符路径
|
// 过滤掉通配符路径
|
||||||
@@ -27,7 +27,9 @@ function convertRoutesToMenuOptions(routes: RouteRecordNormalized[]): MenuOption
|
|||||||
for (const route of validRoutes) {
|
for (const route of validRoutes) {
|
||||||
const pathSegments = route.path.split('/').filter(Boolean);
|
const pathSegments = route.path.split('/').filter(Boolean);
|
||||||
const menuOption: MenuOption = {
|
const menuOption: MenuOption = {
|
||||||
label: route.meta?.title || (route.name as string),
|
label: () => (
|
||||||
|
<RouterLink to={route}>{route.meta?.title || (route.name as string)}</RouterLink>
|
||||||
|
),
|
||||||
key: route.path,
|
key: route.path,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,32 +42,6 @@ function convertRoutesToMenuOptions(routes: RouteRecordNormalized[]): MenuOption
|
|||||||
let currentPath = '';
|
let currentPath = '';
|
||||||
for (let i = 0; i < pathSegments.length - 1; i++) {
|
for (let i = 0; i < pathSegments.length - 1; i++) {
|
||||||
currentPath += `/${pathSegments[i]}`;
|
currentPath += `/${pathSegments[i]}`;
|
||||||
|
|
||||||
if (!menuMap.has(currentPath)) {
|
|
||||||
// 创建父菜单节点
|
|
||||||
const parentMenu: MenuOption = {
|
|
||||||
label: pathSegments[i],
|
|
||||||
key: currentPath,
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
// 顶级父节点
|
|
||||||
rootMenus.push(parentMenu);
|
|
||||||
} else {
|
|
||||||
// 找到祖父节点并添加
|
|
||||||
const grandParentPath = currentPath.substring(0, currentPath.lastIndexOf('/'));
|
|
||||||
const grandParent = menuMap.get(grandParentPath);
|
|
||||||
if (grandParent) {
|
|
||||||
if (!grandParent.children) {
|
|
||||||
grandParent.children = [];
|
|
||||||
}
|
|
||||||
grandParent.children.push(parentMenu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menuMap.set(currentPath, parentMenu);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将当前菜单项添加到父菜单
|
// 将当前菜单项添加到父菜单
|
||||||
@@ -85,22 +61,33 @@ function convertRoutesToMenuOptions(routes: RouteRecordNormalized[]): MenuOption
|
|||||||
return rootMenus;
|
return rootMenus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取路由表但是不包含布局路由
|
||||||
const routes = createGetRoutes(router)();
|
const routes = createGetRoutes(router)();
|
||||||
const menuOptions = computed(() => convertRoutesToMenuOptions(routes));
|
const menuOptions = computed(() => convertRoutesToMenuOptions(routes));
|
||||||
|
|
||||||
console.debug('原始路由:', JSON.stringify(routes, null, 2));
|
console.debug('原始路由:', JSON.stringify(routes, null, 0));
|
||||||
console.debug('转换后的菜单:', JSON.stringify(menuOptions.value, null, 2));
|
console.debug('转换后的菜单:', JSON.stringify(menuOptions.value, null, 0));
|
||||||
|
|
||||||
// 处理菜单点击,导航到对应路由
|
const menuInstRef = useTemplateRef('menuInstRef');
|
||||||
const handleMenuUpdate = (key: string) => {
|
const selectedKey = ref('');
|
||||||
// 只有当 key 对应一个实际路由时才导航
|
|
||||||
const route = routes.find((r) => r.path === key);
|
watch(
|
||||||
if (route && !route.children?.length) {
|
() => router.currentRoute.value.path,
|
||||||
router.push(key);
|
(newPath) => {
|
||||||
}
|
menuInstRef.value?.showOption(newPath);
|
||||||
};
|
selectedKey.value = newPath;
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NMenu :options="menuOptions" @update:value="handleMenuUpdate" />
|
<!-- @update:value="handleMenuUpdate" -->
|
||||||
|
<NMenu
|
||||||
|
v-model:value="selectedKey"
|
||||||
|
ref="menuInstRef"
|
||||||
|
:options="menuOptions"
|
||||||
|
:root-indent="32"
|
||||||
|
:indent="32"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePage({ meta: { title: '示例演示' } });
|
definePage({ meta: { title: '示例演示' } });
|
||||||
</script>
|
</script>
|
||||||
<template>这个文件只是为了给菜单渲染标题</template>
|
<template><div>此页面文件仅用于在侧边栏菜单中显示示例演示分组标题</div></template>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
|
|||||||
import { setupLayouts } from 'virtual:meta-layouts';
|
import { setupLayouts } from 'virtual:meta-layouts';
|
||||||
// import { createGetRoutes, setupLayouts } from 'virtual:generated-layouts';
|
// import { createGetRoutes, setupLayouts } from 'virtual:generated-layouts';
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
import { handleHotUpdate, routes } from 'vue-router/auto-routes';
|
import { routes, handleHotUpdate } from 'vue-router/auto-routes';
|
||||||
|
|
||||||
const setupLayoutsResult = setupLayouts(routes);
|
const setupLayoutsResult = setupLayouts(routes);
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -14,12 +14,10 @@ const router = createRouter({
|
|||||||
strict: true,
|
strict: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (__DEV__) Object.assign(globalThis, { router });
|
|
||||||
router.onError((error) => {
|
router.onError((error) => {
|
||||||
console.debug('🚨 [router error]:', error);
|
console.debug('🚨 [router error]:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { router, setupLayoutsResult };
|
|
||||||
export function install({ app }: { app: import('vue').App<Element> }) {
|
export function install({ app }: { app: import('vue').App<Element> }) {
|
||||||
app
|
app
|
||||||
// 在路由之前注册插件
|
// 在路由之前注册插件
|
||||||
@@ -37,12 +35,8 @@ export function install({ app }: { app: import('vue').App<Element> }) {
|
|||||||
Object.assign(globalThis, { stack: createStackGuard(router) });
|
Object.assign(globalThis, { stack: createStackGuard(router) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
definePage({
|
|
||||||
meta: { },
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
declare module 'vue-router' {
|
declare module 'vue-router' {
|
||||||
|
/* definePage({ meta: { title: '示例演示' } }); */
|
||||||
interface RouteMeta {
|
interface RouteMeta {
|
||||||
/**
|
/**
|
||||||
* @description 是否在菜单中隐藏
|
* @description 是否在菜单中隐藏
|
||||||
@@ -60,7 +54,11 @@ declare module 'vue-router' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { router, setupLayoutsResult };
|
||||||
export { createGetRoutes } from 'virtual:meta-layouts';
|
export { createGetRoutes } from 'virtual:meta-layouts';
|
||||||
|
|
||||||
|
if (__DEV__) Object.assign(globalThis, { router });
|
||||||
// This will update routes at runtime without reloading the page
|
// This will update routes at runtime without reloading the page
|
||||||
if (import.meta.hot) handleHotUpdate(router);
|
if (import.meta.hot) {
|
||||||
|
handleHotUpdate(router);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user