feat(menu): base-layout-sider.vue
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 5m1s
CI/CD Pipeline / playwright (push) Successful in 5m36s

This commit is contained in:
严浩
2025-10-21 18:07:51 +08:00
parent 7892c1f019
commit 437727fdd4
9 changed files with 216 additions and 32 deletions

View File

@@ -57,7 +57,7 @@
"utils4u": "^4.2.3",
"vue": "^3.5.21",
"vue-i18n": "^11.1.12",
"vue-router": "^4.5.1"
"vue-router": "^4.6.3"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.13.2",

30
pnpm-lock.yaml generated
View File

@@ -52,7 +52,7 @@ importers:
version: 4.1.0
utils4u:
specifier: ^4.2.3
version: 4.2.3(@vueuse/core@13.9.0(vue@3.5.22(typescript@5.9.2)))(nprogress@0.2.0)(primevue@4.4.0(vue@3.5.22(typescript@5.9.2)))(vue-router@4.5.1(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2))
version: 4.2.3(@vueuse/core@13.9.0(vue@3.5.22(typescript@5.9.2)))(nprogress@0.2.0)(primevue@4.4.0(vue@3.5.22(typescript@5.9.2)))(vue-router@4.6.3(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2))
vue:
specifier: ^3.5.21
version: 3.5.22(typescript@5.9.2)
@@ -60,8 +60,8 @@ importers:
specifier: ^11.1.12
version: 11.1.12(vue@3.5.22(typescript@5.9.2))
vue-router:
specifier: ^4.5.1
version: 4.5.1(vue@3.5.22(typescript@5.9.2))
specifier: ^4.6.3
version: 4.6.3(vue@3.5.22(typescript@5.9.2))
devDependencies:
'@cloudflare/vite-plugin':
specifier: ^1.13.2
@@ -218,7 +218,7 @@ importers:
version: 29.2.0(vite@7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))
unplugin-vue-router:
specifier: ^0.16.0
version: 0.16.0(@vue/compiler-sfc@3.5.22)(typescript@5.9.2)(vue-router@4.5.1(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2))
version: 0.16.0(@vue/compiler-sfc@3.5.22)(typescript@5.9.2)(vue-router@4.6.3(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2))
vite:
specifier: ^7.1.5
version: 7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)
@@ -236,7 +236,7 @@ importers:
version: 8.0.3(vite@7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.2))
vite-plugin-vue-meta-layouts:
specifier: ^0.6.1
version: 0.6.1(vite@7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.22(typescript@5.9.2)))
version: 0.6.1(vite@7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.6.3(vue@3.5.22(typescript@5.9.2)))
vite-plugin-webfont-dl:
specifier: ^3.11.1
version: 3.11.1(vite@7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))
@@ -5243,10 +5243,10 @@ packages:
peerDependencies:
vue: ^2.7.0 || ^3.2.25
vue-router@4.5.1:
resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==}
vue-router@4.6.3:
resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==}
peerDependencies:
vue: ^3.2.0
vue: ^3.5.0
vue-tsc@3.1.0:
resolution: {integrity: sha512-fbMynMG7kXSnqZTRBSCh9ROYaVpXfCZbEO0gY3lqOjLbp361uuS88n6BDajiUriDIF+SGLWoinjvf6stS2J3Gg==}
@@ -10149,7 +10149,7 @@ snapshots:
unplugin-utils: 0.3.1
vite: 7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)
unplugin-vue-router@0.16.0(@vue/compiler-sfc@3.5.22)(typescript@5.9.2)(vue-router@4.5.1(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2)):
unplugin-vue-router@0.16.0(@vue/compiler-sfc@3.5.22)(typescript@5.9.2)(vue-router@4.6.3(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2)):
dependencies:
'@babel/generator': 7.28.3
'@vue-macros/common': 3.1.1(vue@3.5.22(typescript@5.9.2))
@@ -10170,7 +10170,7 @@ snapshots:
unplugin-utils: 0.3.1
yaml: 2.8.1
optionalDependencies:
vue-router: 4.5.1(vue@3.5.22(typescript@5.9.2))
vue-router: 4.6.3(vue@3.5.22(typescript@5.9.2))
transitivePeerDependencies:
- typescript
- vue
@@ -10194,13 +10194,13 @@ snapshots:
util-deprecate@1.0.2: {}
utils4u@4.2.3(@vueuse/core@13.9.0(vue@3.5.22(typescript@5.9.2)))(nprogress@0.2.0)(primevue@4.4.0(vue@3.5.22(typescript@5.9.2)))(vue-router@4.5.1(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2)):
utils4u@4.2.3(@vueuse/core@13.9.0(vue@3.5.22(typescript@5.9.2)))(nprogress@0.2.0)(primevue@4.4.0(vue@3.5.22(typescript@5.9.2)))(vue-router@4.6.3(vue@3.5.22(typescript@5.9.2)))(vue@3.5.22(typescript@5.9.2)):
optionalDependencies:
'@vueuse/core': 13.9.0(vue@3.5.22(typescript@5.9.2))
nprogress: 0.2.0
primevue: 4.4.0(vue@3.5.22(typescript@5.9.2))
vue: 3.5.22(typescript@5.9.2)
vue-router: 4.5.1(vue@3.5.22(typescript@5.9.2))
vue-router: 4.6.3(vue@3.5.22(typescript@5.9.2))
varint@6.0.0: {}
@@ -10318,11 +10318,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
vite-plugin-vue-meta-layouts@0.6.1(vite@7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.22(typescript@5.9.2))):
vite-plugin-vue-meta-layouts@0.6.1(vite@7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1))(vue-router@4.6.3(vue@3.5.22(typescript@5.9.2))):
dependencies:
local-pkg: 0.5.1
vite: 7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)
vue-router: 4.5.1(vue@3.5.22(typescript@5.9.2))
vue-router: 4.6.3(vue@3.5.22(typescript@5.9.2))
vite-plugin-webfont-dl@3.11.1(vite@7.1.10(@types/node@22.18.12)(jiti@2.6.1)(lightningcss@1.30.1)(sass-embedded@1.93.2)(sass@1.93.2)(terser@5.44.0)(yaml@2.8.1)):
dependencies:
@@ -10475,7 +10475,7 @@ snapshots:
- vue-tsc
- webpack
vue-router@4.5.1(vue@3.5.22(typescript@5.9.2)):
vue-router@4.6.3(vue@3.5.22(typescript@5.9.2)):
dependencies:
'@vue/devtools-api': 6.6.4
vue: 3.5.22(typescript@5.9.2)

View File

@@ -0,0 +1,106 @@
<script setup lang="ts">
import type { MenuOption } from 'naive-ui';
import type { RouteRecordNormalized } from 'vue-router';
import { createGetRoutes, router } from '@/plugins/router-plugin';
// 路由转换为菜单树的辅助函数
function convertRoutesToMenuOptions(routes: RouteRecordNormalized[]): MenuOption[] {
const menuMap = new Map<string, MenuOption>();
const rootMenus: MenuOption[] = [];
// 过滤和排序路由
const validRoutes = routes
.filter((route) => {
// 过滤掉不需要显示的路由
if (!route.name || route.meta?.hidden === true || route.meta?.layout === false) {
return false;
}
// 过滤掉根路径和通配符路径
if (route.path === '/' || route.path.includes('*')) {
return false;
}
return true;
})
.sort((a, b) => a.path.localeCompare(b.path));
// 构建菜单树
for (const route of validRoutes) {
const pathSegments = route.path.split('/').filter(Boolean);
const menuOption: MenuOption = {
label: route.meta?.title || (route.name as string),
key: route.path,
};
// 如果只有一级路径,直接添加到根菜单
if (pathSegments.length === 1) {
rootMenus.push(menuOption);
menuMap.set(route.path, menuOption);
} else {
// 多级路径,需要创建或找到父菜单
let currentPath = '';
for (let i = 0; i < pathSegments.length - 1; 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);
}
}
// 将当前菜单项添加到父菜单
const parentPath = currentPath;
const parent = menuMap.get(parentPath);
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(menuOption);
}
menuMap.set(route.path, menuOption);
}
}
return rootMenus;
}
const routes = createGetRoutes(router)();
const menuOptions = computed(() => convertRoutesToMenuOptions(routes));
console.debug('原始路由:', JSON.stringify(routes, null, 2));
console.debug('转换后的菜单:', JSON.stringify(menuOptions.value, null, 2));
// 处理菜单点击,导航到对应路由
const handleMenuUpdate = (key: string) => {
// 只有当 key 对应一个实际路由时才导航
const route = routes.find((r) => r.path === key);
if (route && !route.children?.length) {
router.push(key);
}
};
</script>
<template>
<NMenu :options="menuOptions" @update:value="handleMenuUpdate" />
</template>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { AdminLayout } from '@sa/materials';
import BaseLayoutHeader from './base-layout-header.vue';
import BaseLayoutSider from './base-layout-sider.vue';
const siderCollapse = ref(false);
const appStore = useAppStore();
@@ -18,9 +19,9 @@ const appStore = useAppStore();
</template>
<template #sider>
<div
class="bg-purple-100/28 dark:bg-purple-900/28 text-purple-900 dark:text-purple-100 p-4 h-full overflow-hidden"
class="bg-purple-100/28 dark:bg-purple-900/28 text-purple-900 dark:text-purple-100 h-full overflow-hidden"
>
3#GlobalSider
<BaseLayoutSider />
</div>
</template>
<div class="bg-yellow-100/28 dark:bg-yellow-900/28 text-yellow-900 dark:text-yellow-100 p-4">

12
src/pages/Home.page.vue Normal file
View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
definePage({
meta: {
title: '首页',
hidden: false,
},
});
</script>
<template>
<div>Home Page</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>Demo-API-Page</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>Demo-Icons-Page</div>
</template>

76
typed-router.d.ts vendored
View File

@@ -1,10 +1,15 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection ES6UnusedImports
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
declare module 'vue-router/auto-resolver' {
export type ParamParserCustom = never
}
declare module 'vue-router/auto-routes' {
import type {
RouteRecordInfo,
@@ -18,13 +23,46 @@ declare module 'vue-router/auto-routes' {
* Route name map generated by unplugin-vue-router
*/
export interface RouteNamedMap {
'Root': RouteRecordInfo<'Root', '/', Record<never, never>, Record<never, never>>,
'$Path': RouteRecordInfo<'$Path', '/:path(.*)', { path: ParamValue<true> }, { path: ParamValue<false> }>,
'Root': RouteRecordInfo<
'Root',
'/',
Record<never, never>,
Record<never, never>,
| never
>,
'$Path': RouteRecordInfo<
'$Path',
'/:path(.*)',
{ path: ParamValue<true> },
{ path: ParamValue<false> },
| never
>,
'Home': RouteRecordInfo<
'Home',
'/Home',
Record<never, never>,
Record<never, never>,
| never
>,
'PageDemoApi': RouteRecordInfo<
'PageDemoApi',
'/page/demo-api',
Record<never, never>,
Record<never, never>,
| never
>,
'PageDemoIcons': RouteRecordInfo<
'PageDemoIcons',
'/page/demo-icons',
Record<never, never>,
Record<never, never>,
| never
>,
}
/**
* Route file to route info map by unplugin-vue-router.
* Used by the volar plugin to automatically type useRoute()
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
*
* Each key is a file path relative to the project root with 2 properties:
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
@@ -34,18 +72,40 @@ declare module 'vue-router/auto-routes' {
*/
export interface _RouteFileInfoMap {
'src/pages/index.page.vue': {
routes: 'Root'
views: never
routes:
| 'Root'
views:
| never
}
'src/pages/[...path].page.vue': {
routes: '$Path'
views: never
routes:
| '$Path'
views:
| never
}
'src/pages/Home.page.vue': {
routes:
| 'Home'
views:
| never
}
'src/pages/page/demo-api.page.vue': {
routes:
| 'PageDemoApi'
views:
| never
}
'src/pages/page/demo-icons.page.vue': {
routes:
| 'PageDemoIcons'
views:
| never
}
}
/**
* Get a union of possible route names in a certain route component file.
* Used by the volar plugin to automatically type useRoute()
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
*
* @internal
*/

View File

@@ -1,5 +1,5 @@
/* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: c8d566f9236103c3d936718f23f1bb71)
// Generated by Wrangler by running `wrangler types` (hash: 7094267439eea3789640d49ba1e25377)
// Runtime types generated with workerd@1.20251008.0 2025-09-09
declare namespace Cloudflare {
interface GlobalProps {
@@ -7,11 +7,6 @@ declare namespace Cloudflare {
}
interface Env {
KV: KVNamespace;
VITE_APP_TITLE: string;
VITE_APP_BASE: string;
VITE_APP_BUILD_SOURCE_MAP: string;
VITE_APP_BUILD_COMMIT: string;
VITE_APP_BUILD_TIME: string;
}
}
interface Env extends Cloudflare.Env {}