Compare commits

..

3 Commits

Author SHA1 Message Date
liuyun
56056ec3d0 Merge remote-tracking branch 'upstream/main' 2026-10-14 14:00:42 +08:00
liuyun
06e5ba4515 feat(layout): 顶部导航 top 布局菜单也支持无限级
- top 布局横向菜单改用 ty-menu-node 递归渲染
- 统一 icon 间距样式(:deep(.icon) 替代 .menu-icon)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-14 13:59:13 +08:00
liuyun
4874a23a61 feat(layout): 左侧菜单支持无限级(递归菜单节点组件)
- 新增 ty-menu-node 自引用递归组件
- ty-sidebar 改递归渲染,不再限于两层

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-14 13:56:48 +08:00
4 changed files with 27 additions and 27 deletions

2
components.d.ts vendored
View File

@ -60,6 +60,7 @@ declare module 'vue' {
TyFooter: typeof import('./src/components/ty-footer.vue')['default']
TyForm: typeof import('./src/components/ty-form.vue')['default']
TyHeader: typeof import('./src/components/ty-header.vue')['default']
TyMenuNode: typeof import('./src/components/ty-menu-node.vue')['default']
TyMixbar: typeof import('./src/components/ty-mixbar.vue')['default']
TyNotice: typeof import('./src/components/ty-notice.vue')['default']
TySearchForm: typeof import('./src/components/ty-search-form.vue')['default']
@ -125,6 +126,7 @@ declare global {
const TyFooter: typeof import('./src/components/ty-footer.vue')['default']
const TyForm: typeof import('./src/components/ty-form.vue')['default']
const TyHeader: typeof import('./src/components/ty-header.vue')['default']
const TyMenuNode: typeof import('./src/components/ty-menu-node.vue')['default']
const TyMixbar: typeof import('./src/components/ty-mixbar.vue')['default']
const TyNotice: typeof import('./src/components/ty-notice.vue')['default']
const TySearchForm: typeof import('./src/components/ty-search-form.vue')['default']

View File

@ -20,17 +20,9 @@
@select="onNavSelect"
>
<template v-for="item in menus" :key="item.code">
<el-sub-menu v-if="layout === 'top' && item.children?.length" :index="item.code">
<template #title>
<icon type="iconify" :icon="item.icon ?? 'mdi:menu'" class="menu-icon" />
<span>{{ item.name }}</span>
</template>
<el-menu-item v-for="child in item.children" :key="child.code" :index="child.path ?? child.code">
{{ child.name }}
</el-menu-item>
</el-sub-menu>
<ty-menu-node v-if="layout === 'top'" :node="item" />
<el-menu-item v-else :index="item.code">
<icon type="iconify" :icon="item.icon ?? 'mdi:menu'" class="menu-icon" />
<icon type="iconify" :icon="item.icon ?? 'mdi:menu'" />
<span>{{ item.name }}</span>
</el-menu-item>
</template>
@ -219,7 +211,7 @@ function onUserCommand(command: string): void {
color: var(--el-color-primary);
}
:deep(.menu-icon) {
:deep(.icon) {
@apply mr-1 text-base;
}
}

View File

@ -0,0 +1,21 @@
<template>
<el-sub-menu v-if="node.children?.length" :index="node.code">
<template #title>
<icon type="iconify" :icon="node.icon ?? 'mdi:menu'" />
<span>{{ node.name }}</span>
</template>
<TyMenuNode v-for="child in node.children" :key="child.code" :node="child" />
</el-sub-menu>
<el-menu-item v-else :index="node.path ?? node.code">
<icon type="iconify" :icon="node.icon ?? 'mdi:menu'" />
<span>{{ node.name }}</span>
</el-menu-item>
</template>
<script setup lang="ts">
import type { ResourceNode } from '@/types/resource'
defineOptions({ name: 'TyMenuNode' })
defineProps<{ node: ResourceNode }>()
</script>

View File

@ -9,22 +9,7 @@
popper-class="ty-menu-popper"
@select="onSelect"
>
<template v-for="item in menus" :key="item.code">
<el-sub-menu v-if="item.children?.length" :index="item.code">
<template #title>
<icon type="iconify" :icon="item.icon ?? 'mdi:menu'" />
<span>{{ item.name }}</span>
</template>
<el-menu-item v-for="child in item.children" :key="child.code" :index="child.path ?? child.code">
<icon v-if="child.icon" type="iconify" :icon="child.icon" />
<span>{{ child.name }}</span>
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="item.path ?? item.code">
<icon type="iconify" :icon="item.icon ?? 'mdi:menu'" />
<span>{{ item.name }}</span>
</el-menu-item>
</template>
<ty-menu-node v-for="item in menus" :key="item.code" :node="item" />
</el-menu>
</el-scrollbar>
</aside>