何时使用
导航菜单是一个网站的灵魂,用户依赖导航在各个页面中进行跳转。一般分为顶部导航和侧边导航,顶部导航提供全局性的类目和功能,侧边导航提供多级结构来收纳和排列网站架构。
更多布局和导航的使用可以参考:通用布局。
开发者注意事项
- Menu 元素为
ul
,因而仅支持li
以及script-supporting
子元素。因而你的子节点元素应该都在Menu.Item
内使用。 - Menu 需要计算节点结构,因而其子元素仅支持
Menu.*
以及对此进行封装的 HOC 组件。 - 必须为 SubMenu 设置唯一 key
案例:顶部导航
核心代码:
<template>
<a-menu v-model:selectedKeys="current" mode="horizontal" :items="items" />
</template>
<script lang="ts" setup>
import { h, ref } from 'vue';
import { MailOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons-vue';
import { MenuProps } from 'ant-design-vue';
const current = ref<string[]>(['mail']);
const items = ref<MenuProps['items']>([
{
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: [
{
type: 'group',
label: 'Item 1',
children: [
{
label: 'Option 1',
key: 'setting:1',
},
{
label: 'Option 2',
key: 'setting:2',
},
],
},
{
type: 'group',
label: 'Item 2',
children: [
{
label: 'Option 3',
key: 'setting:3',
},
{
label: 'Option 4',
key: 'setting:4',
},
],
},
],
},
{
key: 'alipay',
label: h('a', { href: 'https://antdv.com', target: '_blank' }, 'Navigation Four - Link'),
title: 'Navigation Four - Link',
},
]);
</script>
如何定义一个水平的菜单:<a-menu v-model:selectedKeys="current" mode="horizontal" :items="items" />
items 的核心结构是什么:
- key:菜单的唯一标记
- icon:图标
- label:显示的文本
- title:描述文本
- children:子菜单。子菜单中的内容和父菜单中的内容一样。
[
{
key: 'mail',
icon: () => h(MailOutlined),
label: 'Navigation One',
title: 'Navigation One',
}
{
key: 'sub1',
icon: () => h(SettingOutlined),
label: 'Navigation Three - Submenu',
title: 'Navigation Three - Submenu',
children: [
{
type: 'group',
label: 'Item 1',
children: [
{
label: 'Option 1',
key: 'setting:1',
},
{
label: 'Option 2',
key: 'setting:2',
},
],
}
],
}
]
vue3示例:
<script setup>
import {h} from "vue";
import {HomeOutlined} from "@ant-design/icons-vue";
const items = [
{
key: "index",
icon: () => h(HomeOutlined),
label: "首页",
title: "首页"
},
{
key: "dashboard",
label: "面板",
},
{
key: "analysis",
label: "数据分析平台",
children: [
{
key: "analysis-sale",
label: "销量分析",
},
]
},
]
</script>
<template>
<a-menu mode="horizontal" :items="items"/>
</template>
案例:垂直导航
核心代码:
<template>
<a-menu
id="dddddd"
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
style="width: 256px"
mode="inline"
:items="items"
@click="handleClick"
></a-menu>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, VueElement, h } from 'vue';
import { MailOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons-vue';
import type { MenuProps, ItemType } from 'ant-design-vue';
const selectedKeys = ref<string[]>(['1']);
const openKeys = ref<string[]>(['sub1']);
function getItem(
label: VueElement | string,
key: string,
icon?: any,
children?: ItemType[],
type?: 'group',
): ItemType {
return {
key,
icon,
children,
label,
type,
} as ItemType;
}
const items: ItemType[] = reactive([
getItem('Navigation One', 'sub1', () => h(MailOutlined), [
getItem('Item 1', 'g1', null, [getItem('Option 1', '1'), getItem('Option 2', '2')], 'group'),
getItem('Item 2', 'g2', null, [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', null, [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', null, [getItem('Option 13', '13'), getItem('Option 14', '14')], 'group'),
]);
const handleClick: MenuProps['onClick'] = e => {
console.log('click', e);
};
watch(openKeys, val => {
console.log('openKeys', val);
});
</script>
vue3示例:
<script setup>
import {reactive, ref, watch, h} from 'vue';
import {MailOutlined, AppstoreOutlined, SettingOutlined} from '@ant-design/icons-vue';
// 被选中的子菜单,可以有多个
const selectedKeys = ref(['1']);
// 被打开的子菜单,可以有多个
const openKeys = ref(['sub1']);
function getItem(
label,
key,
icon,
children,
type,
) {
return {
key,
icon,
children,
label,
type,
}
}
// 菜单的内容
const items = reactive([
getItem('Navigation One', 'sub1', () => h(MailOutlined), [
getItem('Item 1', 'g1', null, [getItem('Option 1', '1'), getItem('Option 2', '2')], 'group'),
getItem('Item 2', 'g2', null, [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', null, [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', null, [getItem('Option 13', '13'), getItem('Option 14', '14')], 'group'),
]);
const handleClick = e => {
console.log('click', e);
};
watch(openKeys, val => {
console.log('openKeys', val);
});
</script>
<template>
<a-menu
id="dddddd"
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
style="width: 256px"
mode="inline"
:items="items"
@click="handleClick"
></a-menu>
</template>
案例:垂直导航的展开和合并
核心代码:
<template>
<div style="width: 256px">
<a-button type="primary" style="margin-bottom: 16px" @click="toggleCollapsed">
<MenuUnfoldOutlined v-if="state.collapsed" />
<MenuFoldOutlined v-else />
</a-button>
<a-menu
v-model:openKeys="state.openKeys"
v-model:selectedKeys="state.selectedKeys"
mode="inline"
theme="dark"
:inline-collapsed="state.collapsed"
:items="items"
></a-menu>
</div>
</template>
<script lang="ts" setup>
import { reactive, watch, h } from 'vue';
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
PieChartOutlined,
MailOutlined,
DesktopOutlined,
InboxOutlined,
AppstoreOutlined,
} from '@ant-design/icons-vue';
const state = reactive({
collapsed: false,
selectedKeys: ['1'],
openKeys: ['sub1'],
preOpenKeys: ['sub1'],
});
const items = reactive([
{
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',
},
],
},
],
},
]);
watch(
() => state.openKeys,
(_val, oldVal) => {
state.preOpenKeys = oldVal;
},
);
const toggleCollapsed = () => {
state.collapsed = !state.collapsed;
state.openKeys = state.collapsed ? [] : state.preOpenKeys;
};
</script>
vue3示例:
<script setup>
import {reactive, watch, h} from 'vue';
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
PieChartOutlined,
MailOutlined,
DesktopOutlined,
InboxOutlined,
AppstoreOutlined,
} from '@ant-design/icons-vue';
// 菜单状态
const state = reactive({
collapsed: false, // 是否展开
selectedKeys: ['1'], // 选中的 key
openKeys: ['sub1'], // 打开的 key
preOpenKeys: ['sub1'], // 之前打开的 key
});
// 菜单列表
const items = reactive([
{
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',
},
],
},
],
},
]);
// 监听器:监听打开的key
watch(
() => state.openKeys,
(_val, oldVal) => {
state.preOpenKeys = oldVal;
},
);
// 切换展开状态
const toggleCollapsed = () => {
state.collapsed = !state.collapsed;
state.openKeys = state.collapsed ? [] : state.preOpenKeys;
};
</script>
<template>
<div style="width: 256px">
<!--控制展开和合并的按钮-->
<a-button type="primary" style="margin-bottom: 16px" @click="toggleCollapsed">
<MenuUnfoldOutlined v-if="state.collapsed"/>
<MenuFoldOutlined v-else/>
</a-button>
<!--
菜单
:inline-collapsed="state.collapsed":控制是否合并
-->
<a-menu
v-model:openKeys="state.openKeys"
v-model:selectedKeys="state.selectedKeys"
mode="inline"
theme="dark"
:inline-collapsed="state.collapsed"
:items="items"
></a-menu>
</div>
</template>
案例:弹出菜单
核心代码:
<template>
<a-menu
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
style="width: 256px"
mode="vertical"
:items="items"
@click="handleClick"
/>
</template>
<script lang="ts" setup>
import { h, ref } from 'vue';
import {
MailOutlined,
CalendarOutlined,
AppstoreOutlined,
SettingOutlined,
} from '@ant-design/icons-vue';
import type { MenuProps } from 'ant-design-vue';
const selectedKeys = ref([]);
const openKeys = ref([]);
const items = ref([
{
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',
},
],
},
]);
const handleClick: MenuProps['onClick'] = menuInfo => {
console.log('click ', menuInfo);
};
</script>
通过添加 mode="inline"
让菜单变成内嵌的形式。但是如果不加这个属性,那么菜单默认就是弹出菜单。
vue3示例:
<script setup>
import {h, ref} from 'vue';
import {
MailOutlined,
CalendarOutlined,
AppstoreOutlined,
SettingOutlined,
} from '@ant-design/icons-vue';
const selectedKeys = ref([]);
const openKeys = ref([]);
const items = ref([
{
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',
},
],
},
]);
const handleClick = menuInfo => {
console.log('click ', menuInfo);
};
</script>
<template>
<a-menu
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
style="width: 256px"
mode="vertical"
:items="items"
@click="handleClick"
/>
</template>
案例:切换菜单主题
内建了两套主题 light 和 dark,默认 light。
核心代码:
<template>
<div>
<a-switch
:checked="theme === 'dark'"
checked-children="Dark"
un-checked-children="Light"
@change="changeTheme"
/>
<br />
<br />
<a-menu
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
style="width: 256px"
mode="inline"
:theme="theme"
:items="items"
/>
</div>
</template>
<script lang="ts" setup>
import { h, ref } from 'vue';
import {
MailOutlined,
CalendarOutlined,
AppstoreOutlined,
SettingOutlined,
} from '@ant-design/icons-vue';
import type { MenuTheme } from 'ant-design-vue';
const theme = ref<MenuTheme>('dark');
const selectedKeys = ref(['1']);
const openKeys = ref(['sub1']);
const items = ref([
{
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',
},
],
},
]);
const changeTheme = (checked: boolean) => {
theme.value = checked ? 'dark' : 'light';
};
</script>
vue3示例:
<script setup>
import { h, ref } from 'vue';
import {
MailOutlined,
CalendarOutlined,
AppstoreOutlined,
SettingOutlined,
} from '@ant-design/icons-vue';
const theme = ref('dark');
const selectedKeys = ref(['1']);
const openKeys = ref(['sub1']);
const items = ref([
{
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',
},
],
},
]);
// 切换主题的函数
const changeTheme = (checked) => {
theme.value = checked ? 'dark' : 'light';
};
</script>
<template>
<div>
<a-switch
:checked="theme === 'dark'"
checked-children="Dark"
un-checked-children="Light"
@change="changeTheme"
/>
<br />
<br />
<a-menu
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
style="width: 256px"
mode="inline"
:theme="theme"
:items="items"
/>
</div>
</template>
案例:切换按钮
核心代码:
<a-switch
:checked="theme === 'dark'"
checked-children="Dark"
un-checked-children="Light"
@change="changeTheme"
/>
vue3示例:
<script setup>
import {h, ref} from 'vue';
const theme = ref('dark');
// 切换主题的函数
const changeTheme = (checked) => {
theme.value = checked ? 'dark' : 'light';
};
</script>
<template>
<div>
<!--
checked:选中状态,参数是布尔值
checked-children:选中时的文本
un-checked-children:未选中时的文本
@change="changeTheme":当选中状态切换的时候,会触发的事件
-->
<a-switch
:checked="theme === 'dark'"
checked-children="暗黑"
un-checked-children="明亮"
@change="changeTheme"
/>
<a-divider/>
<a-typography-title>{{ theme }}</a-typography-title>
</div>
</template>
属性
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
forceSubMenuRender | 在子菜单展示之前就渲染进 DOM | boolean | false |
inlineCollapsed | inline 时菜单是否收起状态 | boolean | - |
inlineIndent | inline 模式的菜单缩进宽度 | number | 24 |
items | 菜单内容 | [ItemType] | - |
mode | 菜单类型,现在支持垂直、水平、和内嵌模式三种 | vertical |
horizontal |
multiple | 是否允许多选 | boolean | false |
openKeys(v-model) | 当前展开的 SubMenu 菜单项 key 数组 | (string | number)[] | |
overflowedIndicator | 用于自定义 Menu 水平空间不足时的省略收缩的图标 | slot | <EllipsisOutlined /> |
selectable | 是否允许选中 | boolean | true |
selectedKeys(v-model) | 当前选中的菜单项 key 数组 | (string | number)[] | |
subMenuCloseDelay | 用户鼠标离开子菜单后关闭延时,单位:秒 | number | 0.1 |
subMenuOpenDelay | 用户鼠标进入子菜单后开启延时,单位:秒 | number | 0 |
theme | 主题颜色 | light |
dark |
triggerSubMenuAction | 修改 Menu 子菜单的触发方式 | click |
hover |
事件
事件名称 | 说明 | 回调参数 |
---|---|---|
click | 点击 MenuItem 调用此函数 | function({ item, key, keyPath }) |
deselect | 取消选中时调用,仅在 multiple 生效 | function({ item, key, selectedKeys }) |
openChange | SubMenu 展开/关闭的回调 | function(openKeys: (string | number)[]) |
select | 被选中时调用 | function({ item, key, selectedKeys }) |