导航菜单 Menu
为页面和功能提供导航的菜单列表
何时使用
- 需要为页面提供水平方向或垂直方向的导航入口时
- 需要以展开 / 收起的方式组织带层级关系的菜单项时
顶部导航
水平的顶部导航菜单
- Navigation One
- Navigation Two
- Navigation Three - Submenu
- Navigation Four - Link
- Navigation One
- Navigation Two
- Navigation Three - Submenu
- Navigation Four - Link
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuIcon, MenuKey } from 'vue-amazing-ui'
function getItem(label: string, key: string, icon?: MenuIcon, children?: ItemType[], type?: 'group'): ItemType {
return { key, icon, children, label, type } as ItemType
}
const horizontalSelected = ref<MenuKey[]>(['mail'])
const horizontalItems: ItemType[] = [
{ key: 'mail', icon: () => h(MailOutlined), label: 'Navigation One', title: 'Navigation One' },
{ key: 'app', icon: () => h(AppstoreOutlined), label: 'Navigation Two', title: 'Navigation Two' },
{
key: 'sub1',
icon: () => h(SettingOutlined),
label: 'Navigation Three - Submenu',
title: 'Navigation Three - Submenu',
children: [
getItem('Item 1', 'g1', undefined, [getItem('Option 1', 'setting:1'), getItem('Option 2', 'setting:2')], 'group'),
getItem('Item 2', 'g2', undefined, [getItem('Option 3', 'setting:3'), getItem('Option 4', 'setting:4')], 'group')
]
},
{ key: 'alipay', label: 'Navigation Four - Link', title: 'Navigation Four - Link' }
]
</script>
<template>
<Menu v-model:selectedKeys="horizontalSelected" mode="horizontal" :items="horizontalItems" />
</template>内嵌菜单
子菜单内嵌在菜单区域内,可逐级展开
- Navigation One
- Item 1
- Option 1
- Option 2
- Item 2
- Option 3
- Option 4
- Navigation Two
- Option 5
- Option 6
- Submenu
- Option 7
- Option 8
- Navigation Three
- Option 9
- Option 10
- Option 11
- Option 12
- Group
- Option 13
- Option 14
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuIcon, MenuKey } from 'vue-amazing-ui'
function getItem(label: string, key: string, icon?: MenuIcon, children?: ItemType[], type?: 'group'): ItemType {
return { key, icon, children, label, type } as ItemType
}
const inlineSelected = ref<MenuKey[]>(['1'])
const inlineOpenKeys = ref<MenuKey[]>(['sub1'])
const inlineItems: ItemType[] = [
getItem('Navigation One', 'sub1', () => h(MailOutlined), [
getItem('Item 1', 'g1', undefined, [getItem('Option 1', '1'), getItem('Option 2', '2')], 'group'),
getItem('Item 2', 'g2', undefined, [getItem('Option 3', '3'), getItem('Option 4', '4')], 'group')
]),
getItem('Navigation Two', 'sub2', () => h(AppstoreOutlined), [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Submenu', 'sub3', undefined, [getItem('Option 7', '7'), getItem('Option 8', '8')])
]),
{ type: 'divider' },
getItem('Navigation Three', 'sub4', () => h(SettingOutlined), [
getItem('Option 9', '9'),
getItem('Option 10', '10'),
getItem('Option 11', '11'),
getItem('Option 12', '12')
]),
getItem('Group', 'grp', undefined, [getItem('Option 13', '13'), getItem('Option 14', '14')], 'group')
]
</script>
<template>
<Menu
v-model:openKeys="inlineOpenKeys"
v-model:selectedKeys="inlineSelected"
style="width: 256px"
mode="inline"
:items="inlineItems"
/>
</template>垂直菜单
子菜单以弹出形式展示,支持多级嵌套
- Navigation One
- Navigation Two
- Navigation Three
- Navigation Four
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, CalendarOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuKey } from 'vue-amazing-ui'
const verticalSelected = ref<MenuKey[]>(['1'])
const verticalOpenKeys = ref<MenuKey[]>(['sub1'])
const verticalItems: ItemType[] = [
{ key: '1', icon: () => h(MailOutlined), label: 'Navigation One', title: 'Navigation One' },
{ key: '2', icon: () => h(CalendarOutlined), label: 'Navigation Two', title: 'Navigation Two' },
{
key: 'sub1',
icon: () => h(AppstoreOutlined),
label: 'Navigation Three',
title: 'Navigation Three',
children: [
{ key: '3', label: 'Option 3', title: 'Option 3' },
{ key: '4', label: 'Option 4', title: 'Option 4' },
{
key: 'sub1-2',
label: 'Submenu',
title: 'Submenu',
children: [
{ key: '5', label: 'Option 5', title: 'Option 5' },
{ key: '6', label: 'Option 6', title: 'Option 6' }
]
}
]
},
{
key: 'sub2',
icon: () => h(SettingOutlined),
label: 'Navigation Four',
title: 'Navigation Four',
children: [
{ key: '7', label: 'Option 7', title: 'Option 7' },
{ key: '8', label: 'Option 8', title: 'Option 8' },
{ key: '9', label: 'Option 9', title: 'Option 9' },
{ key: '10', label: 'Option 10', title: 'Option 10' }
]
}
]
</script>
<template>
<Menu
v-model:openKeys="verticalOpenKeys"
v-model:selectedKeys="verticalSelected"
style="width: 256px"
mode="vertical"
:items="verticalItems"
/>
</template>切换菜单类型
动态切换菜单模式与主题
- Navigation One
- Navigation Two
- Navigation Three
- Option 3
- Option 4
- Submenu
- Option 5
- Option 6
- Navigation Four
- Option 7
- Option 8
- Option 9
- Option 10
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, CalendarOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuIcon, MenuKey } from 'vue-amazing-ui'
function getItem(label: string, key: string, icon?: MenuIcon, children?: ItemType[]): ItemType {
return { key, icon, children, label } as ItemType
}
const switchMode = ref(false)
const switchTheme = ref(false)
const switchSelected = ref<MenuKey[]>(['1'])
const switchOpenKeys = ref<MenuKey[]>(['sub1'])
const switchItems: ItemType[] = [
getItem('Navigation One', '1', () => h(MailOutlined)),
getItem('Navigation Two', '2', () => h(CalendarOutlined)),
getItem('Navigation Three', 'sub1', () => h(AppstoreOutlined), [
getItem('Option 3', '3'),
getItem('Option 4', '4'),
getItem('Submenu', 'sub1-2', undefined, [getItem('Option 5', '5'), getItem('Option 6', '6')])
]),
getItem('Navigation Four', 'sub2', () => h(SettingOutlined), [
getItem('Option 7', '7'),
getItem('Option 8', '8'),
getItem('Option 9', '9'),
getItem('Option 10', '10')
])
]
</script>
<template>
<Switch v-model:value="switchMode" checked="vertical" unchecked="inline" />
<span class="demo-divider" />
<Switch v-model:value="switchTheme" checked="dark" unchecked="light" />
<Menu
v-model:openKeys="switchOpenKeys"
v-model:selectedKeys="switchSelected"
style="width: 256px; margin-top: 16px"
:mode="switchMode ? 'vertical' : 'inline'"
:theme="switchTheme ? 'dark' : 'light'"
:items="switchItems"
/>
</template>子菜单展开触发与延时
切换子菜单的展开触发方式;hover 触发时,subMenuOpenDelay / subMenuCloseDelay 分别决定展开与收起的延时
- Navigation One
- Navigation Two
- Navigation Three
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuIcon, MenuKey } from 'vue-amazing-ui'
function getItem(label: string, key: string, icon?: MenuIcon, children?: ItemType[]): ItemType {
return { key, icon, children, label } as ItemType
}
const triggerHover = ref(true)
const triggerDelaySelected = ref<MenuKey[]>(['1'])
const triggerDelayItems: ItemType[] = [
getItem('Navigation One', '1', () => h(MailOutlined)),
getItem('Navigation Two', 'sub1', () => h(AppstoreOutlined), [
getItem('Option 1', '1-1'),
getItem('Option 2', '1-2')
]),
getItem('Navigation Three', 'sub2', () => h(SettingOutlined), [
getItem('Option 3', '2-1'),
getItem('Option 4', '2-2')
])
]
</script>
<template>
<Switch v-model:value="triggerHover" checked="hover" unchecked="click" />
<span class="demo-divider" />
<Menu
v-model:selectedKeys="triggerDelaySelected"
style="width: 256px; margin-top: 16px"
mode="vertical"
:trigger-sub-menu-action="triggerHover ? 'hover' : 'click'"
:sub-menu-open-delay="0.3"
:sub-menu-close-delay="0.8"
:items="triggerDelayItems"
/>
</template>自定义缩进
inline 模式下 inlineIndent 决定每一级菜单项的缩进宽度,左侧为默认 24、右侧为 32
- Navigation One
- Option 1
- Submenu
- Option 2
- Navigation Two
- Navigation One
- Option 1
- Submenu
- Option 2
- Navigation Two
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, MailOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuIcon, MenuKey } from 'vue-amazing-ui'
function getItem(label: string, key: string, icon?: MenuIcon, children?: ItemType[]): ItemType {
return { key, icon, children, label } as ItemType
}
const indentDefaultOpenKeys = ref<MenuKey[]>(['sub1', 'sub1-2'])
const indentWideOpenKeys = ref<MenuKey[]>(['sub1', 'sub1-2'])
const indentItems: ItemType[] = [
getItem('Navigation One', 'sub1', () => h(MailOutlined), [
getItem('Option 1', '1'),
getItem('Submenu', 'sub1-2', undefined, [getItem('Option 2', '2')])
]),
getItem('Navigation Two', '2', () => h(AppstoreOutlined))
]
</script>
<template>
<Flex wrap="wrap" :gap="24">
<Menu v-model:openKeys="indentDefaultOpenKeys" style="width: 256px" mode="inline" :items="indentItems" />
<Menu
v-model:openKeys="indentWideOpenKeys"
style="width: 256px"
mode="inline"
:inline-indent="32"
:items="indentItems"
/>
</Flex>
</template>缩起内嵌菜单
收起时只显示图标,子菜单以浮层展示,悬浮菜单项可查看完整标题
- Option 1
- Option 2
- Option 3
- Navigation One
- Option 5
- Option 6
- Option 7
- Option 8
- Navigation Two
- Option 9
- Option 10
- Submenu
- Option 11
- Option 12
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import {
AppstoreOutlined,
DesktopOutlined,
InboxOutlined,
MailOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
PieChartOutlined
} from '@ant-design/icons-vue'
import type { ItemType, MenuKey } from 'vue-amazing-ui'
const collapsed = ref(false)
const collapsedSelected = ref<MenuKey[]>(['1'])
const collapsedOpenKeys = ref<MenuKey[]>(['sub1'])
const collapsedItems: ItemType[] = [
{ key: '1', icon: () => h(PieChartOutlined), label: 'Option 1', title: 'Option 1' },
{ key: '2', icon: () => h(DesktopOutlined), label: 'Option 2', title: 'Option 2' },
{ key: '3', icon: () => h(InboxOutlined), label: 'Option 3', title: 'Option 3' },
{
key: 'sub1',
icon: () => h(MailOutlined),
label: 'Navigation One',
title: 'Navigation One',
children: [
{ key: '5', label: 'Option 5', title: 'Option 5' },
{ key: '6', label: 'Option 6', title: 'Option 6' },
{ key: '7', label: 'Option 7', title: 'Option 7' },
{ key: '8', label: 'Option 8', title: 'Option 8' }
]
},
{
key: 'sub2',
icon: () => h(AppstoreOutlined),
label: 'Navigation Two',
title: 'Navigation Two',
children: [
{ key: '9', label: 'Option 9', title: 'Option 9' },
{ key: '10', label: 'Option 10', title: 'Option 10' },
{
key: 'sub3',
label: 'Submenu',
title: 'Submenu',
children: [
{ key: '11', label: 'Option 11', title: 'Option 11' },
{ key: '12', label: 'Option 12', title: 'Option 12' }
]
}
]
}
]
</script>
<template>
<Space vertical style="width: 256px">
<Button type="primary" @click="collapsed = !collapsed">
<MenuUnfoldOutlined v-if="collapsed" />
<MenuFoldOutlined v-else />
</Button>
<Menu
v-model:openKeys="collapsedOpenKeys"
v-model:selectedKeys="collapsedSelected"
mode="inline"
theme="dark"
:inline-collapsed="collapsed"
:items="collapsedItems"
/>
</Space>
</template>收起态长标题提示
提示取 title,长标题会让提示折成多行(面板高于菜单项);把某项滚动到窗口上/下缘附近再悬浮,提示面板会贴边完整显示,箭头依然对准该项
- 很长的标题 One:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果
- 较长的标题 Two:这段文案也写得较长,悬浮提示会折成多行,展示提示折成两行时的形态
- 很长的标题 Three:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果
- 较长的标题 Four:这段文案也写得较长,悬浮提示会折成多行,展示提示折成两行时的形态
- 很长的标题 Five:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, DesktopOutlined, InboxOutlined, MailOutlined, PieChartOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuKey } from 'vue-amazing-ui'
const longTitleSelected = ref<MenuKey[]>(['1'])
const longTitleItems: ItemType[] = [
{
key: '1',
icon: () => h(PieChartOutlined),
label: '很长的标题 One:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果',
title: '很长的标题 One:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果'
},
{
key: '2',
icon: () => h(DesktopOutlined),
label: '较长的标题 Two:这段文案也写得较长,悬浮提示会折成多行,展示提示折成两行时的形态',
title: '较长的标题 Two:这段文案也写得较长,悬浮提示会折成多行,展示提示折成两行时的形态'
},
{
key: '3',
icon: () => h(InboxOutlined),
label: '很长的标题 Three:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果',
title: '很长的标题 Three:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果'
},
{
key: '4',
icon: () => h(MailOutlined),
label: '较长的标题 Four:这段文案也写得较长,悬浮提示会折成多行,展示提示折成两行时的形态',
title: '较长的标题 Four:这段文案也写得较长,悬浮提示会折成多行,展示提示折成两行时的形态'
},
{
key: '5',
icon: () => h(AppstoreOutlined),
label: '很长的标题 Five:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果',
title: '很长的标题 Five:这段文案刻意写长,用于让收起态的悬浮提示折成多行,展示提示内容超出菜单项时的换行效果'
}
]
</script>
<template>
<div style="width: 256px">
<Menu
v-model:selectedKeys="longTitleSelected"
mode="inline"
theme="dark"
:inline-collapsed="true"
:items="longTitleItems"
/>
</div>
</template>只展开当前父级菜单
点击菜单时收起其他已展开的菜单,保持菜单聚焦简洁
- Navigation One
- Option 1
- Option 2
- Option 3
- Option 4
- Navigation Two
- Option 5
- Option 6
- Submenu
- Option 7
- Option 8
- Navigation Three
- Option 9
- Option 10
- Option 11
- Option 12
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuIcon, MenuKey } from 'vue-amazing-ui'
function getItem(label: string, key: string, icon?: MenuIcon, children?: ItemType[]): ItemType {
return { key, icon, children, label } as ItemType
}
const rootSubmenuKeys: MenuKey[] = ['sub1', 'sub2', 'sub4']
const parentOpenKeys = ref<MenuKey[]>(['sub1'])
const parentSelected = ref<MenuKey[]>([])
const parentItems: ItemType[] = [
getItem('Navigation One', 'sub1', () => h(MailOutlined), [
getItem('Option 1', '1'),
getItem('Option 2', '2'),
getItem('Option 3', '3'),
getItem('Option 4', '4')
]),
getItem('Navigation Two', 'sub2', () => h(AppstoreOutlined), [
getItem('Option 5', '5'),
getItem('Option 6', '6'),
getItem('Submenu', 'sub3', undefined, [getItem('Option 7', '7'), getItem('Option 8', '8')])
]),
getItem('Navigation Three', 'sub4', () => h(SettingOutlined), [
getItem('Option 9', '9'),
getItem('Option 10', '10'),
getItem('Option 11', '11'),
getItem('Option 12', '12')
])
]
// 展开集合受控:仅保留最新展开的顶层子菜单,其余一并收起
function onParentOpenChange(openKeys: MenuKey[]) {
const latestOpenKey = openKeys.find((key) => !parentOpenKeys.value.includes(key))
if (latestOpenKey === undefined || !rootSubmenuKeys.includes(latestOpenKey)) {
parentOpenKeys.value = openKeys
} else {
parentOpenKeys.value = [latestOpenKey]
}
}
</script>
<template>
<Menu
style="width: 256px"
mode="inline"
:items="parentItems"
:open-keys="parentOpenKeys"
v-model:selectedKeys="parentSelected"
@open-change="onParentOpenChange"
/>
</template>多选
通过 multiple 允许多选,再次点击已选中项会触发取消选中
- Option 1
- Option 2
- Option 3
- Navigation
- Option 4
- Option 5
Show Code
<script setup lang="ts">
import { ref } from 'vue'
import type { ItemType, MenuKey } from 'vue-amazing-ui'
const multipleSelected = ref<MenuKey[]>(['1'])
const multipleItems: ItemType[] = [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2' },
{ key: '3', label: 'Option 3' },
{
key: 'sub1',
label: 'Navigation',
children: [
{ key: '4', label: 'Option 4' },
{ key: '5', label: 'Option 5' }
]
}
]
</script>
<template>
<Menu v-model:selectedKeys="multipleSelected" style="width: 256px" mode="inline" multiple :items="multipleItems" />
</template>禁用
通过 disabled 禁用整个菜单;菜单项自身的 disabled 只禁用该项,danger 用于呈现错误状态样式
- Option 1
- Option 2(disabled)
- Option 3(danger)
- Option 4
Show Code
<script setup lang="ts">
import { ref } from 'vue'
import type { ItemType, MenuKey } from 'vue-amazing-ui'
const disabledSelected = ref<MenuKey[]>(['1'])
const wholeDisabled = ref(false)
const disabledItems: ItemType[] = [
{ key: '1', label: 'Option 1' },
{ key: '2', label: 'Option 2(disabled)', disabled: true },
{ key: '3', label: 'Option 3(danger)', danger: true },
{ key: '4', label: 'Option 4', disabled: true }
]
</script>
<template>
<Switch v-model:value="wholeDisabled" checked="禁用" unchecked="可用" />
<Menu
v-model:selectedKeys="disabledSelected"
style="width: 256px; margin-top: 16px"
mode="inline"
:disabled="wholeDisabled"
:items="disabledItems"
/>
</template>空数据
菜单内容为空时渲染空列表,不报错也不残留占位
Show Code
<script setup lang="ts">
import type { ItemType } from 'vue-amazing-ui'
const emptyItems: ItemType[] = []
</script>
<template>
<Menu style="width: 256px" mode="inline" :items="emptyItems" />
</template>自定义展开图标
通过 expandIcon 属性或同名插槽自定义子菜单的展开收起图标:上方为插槽形态、下方为属性形态,两者等价
- Navigation One
- Option 1
- Option 2
- Submenu
- Option 3
- Option 4
- Navigation Two
- Option 5
- Option 6
- Navigation One
- Option 1
- Option 2
- Submenu
- Option 3
- Option 4
- Navigation Two
- Option 5
- Option 6
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, MailOutlined, RightOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuExpandIconInfo, MenuIcon, MenuKey } from 'vue-amazing-ui'
function getItem(label: string, key: string, icon?: MenuIcon, children?: ItemType[]): ItemType {
return { key, icon, children, label } as ItemType
}
const expandIconSelected = ref<MenuKey[]>(['1'])
const expandIconOpenKeys = ref<MenuKey[]>(['sub1'])
const expandIconItems: ItemType[] = [
getItem('Navigation One', 'sub1', () => h(MailOutlined), [
getItem('Option 1', '1'),
getItem('Option 2', '2'),
getItem('Submenu', 'sub2', undefined, [getItem('Option 3', '3'), getItem('Option 4', '4')])
]),
getItem('Navigation Two', 'sub3', () => h(AppstoreOutlined), [getItem('Option 5', '5'), getItem('Option 6', '6')])
]
// 属性形态:与插槽形态等价,渲染函数入参含所在子菜单配置与 isOpen
const expandIconByProp = (info: MenuExpandIconInfo) =>
h(RightOutlined, { class: ['demo-expand-icon', { 'demo-expand-icon-open': info.isOpen }] })
const expandIconPropOpenKeys = ref<MenuKey[]>(['sub1'])
</script>
<template>
<Menu
v-model:openKeys="expandIconOpenKeys"
v-model:selectedKeys="expandIconSelected"
style="width: 256px"
mode="inline"
:items="expandIconItems"
>
<template #expandIcon="{ isOpen }">
<RightOutlined class="demo-expand-icon" :class="{ 'demo-expand-icon-open': isOpen }" />
</template>
</Menu>
<Menu
v-model:openKeys="expandIconPropOpenKeys"
style="width: 256px; margin-top: 16px"
mode="inline"
:items="expandIconItems"
:expand-icon="expandIconByProp"
/>
</template>
<style lang="less" scoped>
/* 展开图标用「单个箭头旋转 90°」表达展开态:直接切换图标会有一次瞬跳,旋转则连续过渡 */
.demo-expand-icon {
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
&.demo-expand-icon-open {
transform: rotate(90deg);
}
}
</style>组件式用法
以 MenuItem / MenuSubMenu / MenuItemGroup / MenuDivider 子组件描述菜单结构,与 items 配置等价;两者同时提供时以子组件为准
- Option 1
- Navigation Two
- Option 2
- Option 3
- Submenu
- Option 4
- Option 5
- Group
- Option 6
- Option 7
- Option 8
Show Code
<script setup lang="ts">
import { ref } from 'vue'
import { AppstoreOutlined, MailOutlined } from '@ant-design/icons-vue'
import type { MenuKey } from 'vue-amazing-ui'
// MenuItem / MenuSubMenu / MenuItemGroup / MenuDivider 为全局注册的子组件,无需引入
const componentSelected = ref<MenuKey[]>(['1'])
const componentOpenKeys = ref<MenuKey[]>(['sub2'])
</script>
<template>
<Menu
v-model:openKeys="componentOpenKeys"
v-model:selectedKeys="componentSelected"
style="width: 256px"
mode="inline"
>
<MenuItem key="1">
<template #icon><MailOutlined /></template>
Option 1
</MenuItem>
<MenuSubMenu key="sub2">
<template #icon><AppstoreOutlined /></template>
<template #title>Navigation Two</template>
<MenuItem key="2">Option 2</MenuItem>
<MenuItem key="3">Option 3</MenuItem>
<MenuSubMenu key="sub2-1" title="Submenu">
<MenuItem key="4">Option 4</MenuItem>
<MenuItem key="5">Option 5</MenuItem>
</MenuSubMenu>
</MenuSubMenu>
<MenuDivider />
<MenuItemGroup title="Group">
<MenuItem key="6">Option 6</MenuItem>
<MenuItem key="7">Option 7</MenuItem>
</MenuItemGroup>
<MenuItem key="8" disabled>Option 8</MenuItem>
</Menu>
</template>组件式自定义展开图标
子菜单的 expandIcon 插槽优先于 Menu 的同名插槽,未提供时回落到 Menu 级图标
- Navigation One
- Option 1
- Option 2
- Navigation Three
- Option 3
- Option 4
Show Code
<script setup lang="ts">
import { ref } from 'vue'
import { MailOutlined, MinusOutlined, PlusOutlined, RightOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { MenuKey } from 'vue-amazing-ui'
const componentExpandIconSelected = ref<MenuKey[]>(['1'])
const componentExpandIconOpenKeys = ref<MenuKey[]>(['sub1'])
</script>
<template>
<Menu
v-model:openKeys="componentExpandIconOpenKeys"
v-model:selectedKeys="componentExpandIconSelected"
style="width: 256px"
mode="inline"
>
<template #expandIcon="{ isOpen }">
<RightOutlined class="demo-expand-icon" :class="{ 'demo-expand-icon-open': isOpen }" />
</template>
<MenuSubMenu key="sub1" title="Navigation One">
<template #icon><MailOutlined /></template>
<template #expandIcon="{ isOpen }">
<span class="demo-expand-switch">
<PlusOutlined :class="{ 'demo-expand-switch-hidden': isOpen }" />
<MinusOutlined :class="{ 'demo-expand-switch-hidden': !isOpen }" />
</span>
</template>
<MenuItem key="1">Option 1</MenuItem>
<MenuItem key="2">Option 2</MenuItem>
</MenuSubMenu>
<MenuSubMenu key="sub3" title="Navigation Three">
<template #icon><SettingOutlined /></template>
<MenuItem key="3">Option 3</MenuItem>
<MenuItem key="4">Option 4</MenuItem>
</MenuSubMenu>
</Menu>
</template>
<style lang="less" scoped>
/* 展开图标用「单个箭头旋转 90°」表达展开态;加号 / 减号叠放在同一位置,靠「旋转 + 淡出」互换 */
.demo-expand-icon {
transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
&.demo-expand-icon-open {
transform: rotate(90deg);
}
}
.demo-expand-switch {
position: relative;
display: inline-block;
width: 1em;
height: 1em;
> * {
position: absolute;
top: 0;
left: 0;
transition:
opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
&.demo-expand-switch-hidden {
opacity: 0;
transform: rotate(-90deg);
}
}
}
</style>水平溢出指示器
水平空间不足时,放不下的菜单项会收进省略子菜单;其指示器由 overflowedIndicator 插槽自定义(未提供时使用内置三点图标)
- Navigation One
- Navigation Two
- Navigation Three
- Navigation Four
- Navigation Five
- Navigation One
- Navigation Two
- Navigation Three
- Navigation Four
- Navigation Five
Show Code
<script setup lang="ts">
import { DownOutlined } from '@ant-design/icons-vue'
import type { ItemType } from 'vue-amazing-ui'
const overflowItems: ItemType[] = [
{ key: 'mail', label: 'Navigation One', title: 'Navigation One' },
{ key: 'app', label: 'Navigation Two', title: 'Navigation Two' },
{ key: 'calendar', label: 'Navigation Three', title: 'Navigation Three' },
{ key: 'setting', label: 'Navigation Four', title: 'Navigation Four' },
{ key: 'desktop', label: 'Navigation Five', title: 'Navigation Five' }
]
</script>
<template>
<div class="demo-overflow-box">
<Menu mode="horizontal" :items="overflowItems">
<template #overflowedIndicator>
<DownOutlined />
</template>
</Menu>
</div>
</template>
<style lang="less" scoped>
/* 限宽容器:宽度不足以容纳全部菜单项时才会触发溢出省略 */
.demo-overflow-box {
width: 480px;
max-width: 100%;
}
</style>子菜单预渲染
开启 forceSubMenuRender 后,子菜单面板在首次展开前就已渲染进 DOM(保持不可见),首次展开因此无需等待挂载;可用开发者工具查看未展开的子菜单下是否已存在 .menu-submenu-popup 面板
- Navigation One
- Navigation Two
- Navigation Three
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { ItemType } from 'vue-amazing-ui'
const forceRenderEnabled = ref(false)
const forceRenderItems: ItemType[] = [
{ key: '1', icon: () => h(MailOutlined), label: 'Navigation One', title: 'Navigation One' },
{
key: 'sub1',
icon: () => h(AppstoreOutlined),
label: 'Navigation Two',
title: 'Navigation Two',
children: [
{ key: '2', label: 'Option 2', title: 'Option 2' },
{ key: '3', label: 'Option 3', title: 'Option 3' }
]
},
{
key: 'sub2',
icon: () => h(SettingOutlined),
label: 'Navigation Three',
title: 'Navigation Three',
children: [
{ key: '4', label: 'Option 4', title: 'Option 4' },
{ key: '5', label: 'Option 5', title: 'Option 5' }
]
}
]
</script>
<template>
<Switch v-model:value="forceRenderEnabled" checked="On" unchecked="Off" />
<Menu
style="width: 256px; margin-top: 16px"
mode="vertical"
:force-sub-menu-render="forceRenderEnabled"
:items="forceRenderItems"
/>
</template>主题
内建 light 与 dark 两套主题,默认为 light
- Navigation One
- Navigation Two
- Navigation Three
- Option 3
- Option 4
- Submenu
- Option 5
- Option 6
- Navigation Four
- Option 7
- Option 8
- Option 9
- Option 10
Show Code
<script setup lang="ts">
import { h, ref } from 'vue'
import { AppstoreOutlined, CalendarOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuKey } from 'vue-amazing-ui'
const themeDark = ref(true)
const themeSelected = ref<MenuKey[]>(['1'])
const themeOpenKeys = ref<MenuKey[]>(['sub1'])
const themeItems: ItemType[] = [
{ key: '1', icon: () => h(MailOutlined), label: 'Navigation One', title: 'Navigation One' },
{ key: '2', icon: () => h(CalendarOutlined), label: 'Navigation Two', title: 'Navigation Two' },
{
key: 'sub1',
icon: () => h(AppstoreOutlined),
label: 'Navigation Three',
title: 'Navigation Three',
children: [
{ key: '3', label: 'Option 3', title: 'Option 3' },
{ key: '4', label: 'Option 4', title: 'Option 4' },
{
key: 'sub1-2',
label: 'Submenu',
title: 'Submenu',
children: [
{ key: '5', label: 'Option 5', title: 'Option 5' },
{ key: '6', label: 'Option 6', title: 'Option 6' }
]
}
]
},
{
key: 'sub2',
icon: () => h(SettingOutlined),
label: 'Navigation Four',
title: 'Navigation Four',
children: [
{ key: '7', label: 'Option 7', title: 'Option 7' },
{ key: '8', label: 'Option 8', title: 'Option 8' },
{ key: '9', label: 'Option 9', title: 'Option 9' },
{ key: '10', label: 'Option 10', title: 'Option 10' }
]
}
]
</script>
<template>
<Switch v-model:value="themeDark" checked="Dark" unchecked="Light" />
<Menu
v-model:openKeys="themeOpenKeys"
v-model:selectedKeys="themeSelected"
style="width: 256px; margin-top: 16px"
mode="inline"
:theme="themeDark ? 'dark' : 'light'"
:items="themeItems"
/>
</template>子菜单主题
通过子菜单的 theme 属性设置其主题,可实现根目录深色、子目录浅色的效果
- Navigation One
- Option 5
- Option 6
Show Code
<script setup lang="ts">
import { computed, h, ref } from 'vue'
import { MailOutlined } from '@ant-design/icons-vue'
import type { ItemType, MenuKey } from 'vue-amazing-ui'
const submenuDark = ref(false)
const submenuSelected = ref<MenuKey[]>(['1'])
const submenuOpenKeys = ref<MenuKey[]>(['sub1'])
const submenuItems = computed<ItemType[]>(() => [
{
key: 'sub1',
icon: () => h(MailOutlined),
label: 'Navigation One',
title: 'Navigation One',
theme: submenuDark.value ? 'dark' : 'light',
children: [
{ key: '1', label: 'Option 1', title: 'Option 1' },
{ key: '2', label: 'Option 2', title: 'Option 2' },
{ key: '3', label: 'Option 3', title: 'Option 3' }
]
},
{ key: '5', label: 'Option 5', title: 'Option 5' },
{ key: '6', label: 'Option 6', title: 'Option 6' }
])
</script>
<template>
<Switch v-model:value="submenuDark" checked="Dark" unchecked="Light" />
<Menu
style="width: 256px; margin-top: 16px"
mode="vertical"
theme="dark"
:open-keys="submenuOpenKeys"
v-model:selectedKeys="submenuSelected"
:items="submenuItems"
/>
</template>APIs
Menu
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| openKeys v-model | 当前展开的子菜单 key 数组,不传时为非受控 | MenuKey[] | undefined |
| selectedKeys v-model | 当前选中的菜单项 key 数组,不传时为非受控 | MenuKey[] | undefined |
| items | 菜单内容 | ItemType[] | [] |
| mode | 菜单类型 | 'horizontal' | 'vertical' | 'inline' | 'vertical' |
| theme | 主题颜色 | 'light' | 'dark' | 'light' |
| inlineCollapsed | inline / vertical 模式下是否收起,收起时子菜单改为浮层展开;菜单位于侧边栏内时以侧边栏的收起状态为准 | boolean | false |
| inlineIndent | inline 模式每一级菜单项的缩进宽度 | number | 24 |
| expandIcon | 自定义子菜单的展开收起图标 | (info: MenuExpandIconInfo) => VNodeChild | undefined |
| disabled | 是否禁用整个菜单 | boolean | false |
| selectable | 是否允许选中 | boolean | true |
| multiple | 是否允许多选 | boolean | false |
| triggerSubMenuAction | 子菜单的展开触发方式;inline 模式未收起时子菜单就地展开,不受此属性影响 | 'click' | 'hover' | 'hover' |
| subMenuOpenDelay | 鼠标进入子菜单后开启的延时,单位秒 | number | 0 |
| subMenuCloseDelay | 鼠标离开子菜单后关闭的延时,单位秒 | number | 0.1 |
| forceSubMenuRender | 是否在子菜单展示之前就渲染进 DOM(首次展开因而无需等待挂载) | boolean | false |
MenuKey Type
| 名称 | 值 |
|---|---|
| MenuKey | string | number |
MenuNode Type
| 名称 | 值 |
|---|---|
| MenuNode | VNodeChild | (() => VNodeChild) |
MenuIcon Type
| 名称 | 值 |
|---|---|
| MenuIcon | VNodeChild | ((item: SubMenuType | MenuItemType) => VNodeChild) |
ItemType Type
| 名称 | 值 |
|---|---|
| ItemType | SubMenuType | MenuItemType | MenuItemGroupType | MenuDividerType | null | undefined |
MenuItemType Type
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| key | 唯一标志 | MenuKey | — |
| label | 菜单项标题,支持渲染函数 | MenuNode | undefined |
| icon | 菜单图标,渲染函数入参为所在菜单项的配置 | MenuIcon | undefined |
| title | 收起时展示的悬浮标题,支持渲染函数;显式 false 表示不展示 | MenuNode | undefined |
| disabled | 是否禁用 | boolean | false |
| danger | 是否展示错误状态样式 | boolean | false |
| style | 菜单项自定义样式 | CSSProperties | undefined |
| class | 菜单项自定义类名 | string | undefined |
SubMenuType Type
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| key | 唯一标志 | MenuKey | — |
| label | 子菜单标题,支持渲染函数 | MenuNode | undefined |
| children | 子菜单的菜单项 | ItemType[] | — |
| icon | 菜单图标,渲染函数入参为所在子菜单的配置 | MenuIcon | undefined |
| expandIcon | 子菜单级展开收起图标,优先于 Menu 的 expandIcon | (info: MenuExpandIconInfo) => VNodeChild | undefined |
| theme | 子菜单主题,不传则继承 Menu 的 theme | 'light' | 'dark' | undefined |
| title | 收起时展示的悬浮标题,支持渲染函数;显式 false 表示不展示 | MenuNode | undefined |
| popupClassName | 弹出子菜单的自定义类名,inline 模式下无效 | string | undefined |
| popupOffset | 弹出子菜单与锚点的偏移,inline 模式下无效 | [number, number] | undefined |
| onTitleClick | 点击子菜单标题 | (info: MenuTitleInfo) => void | undefined |
| disabled | 是否禁用 | boolean | false |
| style | 子菜单自定义样式 | CSSProperties | undefined |
| class | 子菜单自定义类名 | string | undefined |
MenuItemGroupType Type
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 固定为 group,标记该项为分组 | 'group' | — |
| label | 分组标题,支持渲染函数 | MenuNode | undefined |
| children | 分组的菜单项 | ItemType[] | undefined |
| style | 分组自定义样式(作用于分组容器) | CSSProperties | undefined |
| class | 分组自定义类名(作用于分组容器) | string | undefined |
MenuDividerType Type
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 固定为 divider,标记该项为分割线 | 'divider' | — |
| dashed | 是否虚线 | boolean | false |
| style | 分割线自定义样式 | CSSProperties | undefined |
| class | 分割线自定义类名 | string | undefined |
MenuExpandIconInfo Type
expandIcon 属性与 #expandIcon 插槽的入参类型,为 SubMenuType 与以下字段的交叉类型:
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| isOpen | 所在子菜单当前是否展开 | boolean | undefined |
MenuInfo Type
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| key | 触发项的 key | MenuKey | — |
| keyPath | 由根到触发项的 key 路径 | MenuKey[] | — |
| item | 触发项的原始配置 | SubMenuType | MenuItemType | — |
| domEvent | 触发的原生事件 | MouseEvent | KeyboardEvent | — |
SelectInfo Type
为 MenuInfo 与以下字段的交叉类型:
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| selectedKeys | 变化后的选中项 key 数组 | MenuKey[] | — |
MenuTitleInfo Type
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| key | 子菜单的 key | MenuKey | — |
| domEvent | 触发的原生事件 | MouseEvent | KeyboardEvent | — |
MenuItem
<Menu> 默认插槽中的菜单项子组件(配置式写法请用 MenuItemType 数据):
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| title | 收起时展示的悬浮标题 | string | undefined |
| disabled | 是否禁用 | boolean | false |
| danger | 是否展示错误状态样式 | boolean | false |
MenuSubMenu
<Menu> 默认插槽中的子菜单子组件(配置式写法请用 SubMenuType 数据),默认插槽内书写 MenuItem / MenuSubMenu:
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| title | 子菜单标题,收起态下同时作为悬浮标题 | string | undefined |
| theme | 子菜单主题,不传则继承 Menu 的 theme | 'light' | 'dark' | undefined |
| popupClassName | 弹出子菜单的自定义类名,inline 模式下无效 | string | undefined |
| popupOffset | 弹出子菜单与锚点的偏移,inline 模式下无效 | [number, number] | undefined |
| disabled | 是否禁用 | boolean | false |
MenuItemGroup
<Menu> 默认插槽中的分组子组件(配置式写法请用 MenuItemGroupType 数据),组内书写 MenuItem:
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| title | 分组标题 | string | undefined |
MenuDivider
<Menu> 默认插槽中的分割线子组件(配置式写法请用 MenuDividerType 数据):
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| dashed | 是否虚线 | boolean | false |
ℹ️ 组件式写法下菜单项与子菜单的
key取子组件的key属性,未设置时按其所在层级的下标兜底(分组与分割线不参与选中,其key由渲染时按位置合成)。items与默认插槽同时提供时,本库以默认插槽为准,沿用本库「插槽优先于prop」的统一约定。
Slots
Menu Slots
| 名称 | 说明 | 用法 |
|---|---|---|
| default | 子组件式菜单内容,直接书写 MenuItem / MenuSubMenu / MenuItemGroup / MenuDivider;与 items 同时提供时以此为准 | v-slot:default |
| expandIcon | 自定义子菜单的展开收起图标,存在时优先于 expandIcon 属性 | v-slot:expandIcon="{ isOpen, ...item }" |
| overflowedIndicator | 水平空间不足时收进省略子菜单的指示器,缺省为内置三点图标 | v-slot:overflowedIndicator |
MenuItem Slots
| 名称 | 说明 | 用法 |
|---|---|---|
| default | 菜单项标题 | v-slot:default |
| icon | 菜单图标 | v-slot:icon |
| title | 收起时展示的悬浮标题 | v-slot:title |
MenuSubMenu Slots
| 名称 | 说明 | 用法 |
|---|---|---|
| default | 子菜单的菜单项,书写 MenuItem / MenuSubMenu | v-slot:default |
| title | 子菜单标题 | v-slot:title |
| icon | 菜单图标 | v-slot:icon |
| expandIcon | 子菜单的展开收起图标,存在时优先于 Menu 的 expandIcon | v-slot:expandIcon="{ isOpen, ...item }" |
MenuItemGroup Slots
| 名称 | 说明 | 用法 |
|---|---|---|
| default | 分组的菜单项,书写 MenuItem | v-slot:default |
| title | 分组标题 | v-slot:title |
Events
| 名称 | 说明 | 类型 |
|---|---|---|
| click | 点击菜单项时调用 | (info: MenuInfo) => void |
| select | 被选中时调用 | (info: SelectInfo) => void |
| deselect | 取消选中时调用,仅在 multiple 生效 | (info: SelectInfo) => void |
| openChange | 子菜单展开 / 收起时调用 | (openKeys: MenuKey[]) => void |
| titleClick | 点击子菜单标题时调用(MenuSubMenu 的事件,需在 <MenuSubMenu> 上监听,而非 <Menu>) | (info: MenuTitleInfo) => void |