Nav 导航
用于 App 主导航:纵向当侧栏、横向当底栏。激活项由一个可滑动的实心块标记,键盘方向键可以在项之间移动。与 MTabs 一样是纯 items 数据驱动,不通过插槽收集子组件。
代码演示
基础用法
items 决定有哪些项,v-model 决定哪一项激活。横向排列适合做底部标签栏。
<script setup lang="ts">
import { defineComponent, h, ref, resolveComponent, type Component } from 'vue'
import { mdiHomeVariantOutline, mdiCompassOutline, mdiAccountOutline } from '@mdi/js'
const MIcon = resolveComponent('MIcon') as Component
/** items[].icon 需要的是一个组件,这里把 mdi 路径包成接收 size 的小组件 */
function icon(path: string) {
return defineComponent({
name: 'DemoNavIcon',
props: { size: { type: [Number, String], default: 24 } },
setup(props) {
return () => h(MIcon, { path, size: props.size })
},
})
}
const active = ref('home')
const items = [
{ name: 'home', label: '首页', icon: icon(mdiHomeVariantOutline) },
{ name: 'explore', label: '发现', icon: icon(mdiCompassOutline) },
{ name: 'mine', label: '我的', icon: icon(mdiAccountOutline) },
]
</script>
<template>
<MNav v-model="active" :items="items" direction="horizontal" size="small" />
</template>纵向侧栏
direction="vertical" 为默认值,配合 align 可以控制整列在容器内的对齐位置。
左边 align="middle"、右边 size="small" + align="top"。
<script setup lang="ts">
import { defineComponent, h, ref, resolveComponent, type Component } from 'vue'
import {
mdiViewDashboardOutline,
mdiChartTimelineVariant,
mdiAccountGroupOutline,
mdiCogOutline,
} from '@mdi/js'
const MIcon = resolveComponent('MIcon') as Component
function icon(path: string) {
return defineComponent({
name: 'DemoNavIcon',
props: { size: { type: [Number, String], default: 24 } },
setup(props) {
return () => h(MIcon, { path, size: props.size })
},
})
}
const active = ref('dashboard')
const items = [
{ name: 'dashboard', label: '总览', icon: icon(mdiViewDashboardOutline) },
{ name: 'analysis', label: '分析', icon: icon(mdiChartTimelineVariant) },
{ name: 'members', label: '成员', icon: icon(mdiAccountGroupOutline) },
{ name: 'settings', label: '设置', icon: icon(mdiCogOutline) },
]
</script>
<template>
<div class="m-row-demo">
<MNav v-model="active" :items="items" direction="vertical" align="middle" />
<MNav v-model="active" :items="items" direction="vertical" size="small" align="top" />
</div>
</template>徽标
badge 传数字显示在图标右上角,传 true 显示小红点;数字超过 badgeMax 时显示为 {badgeMax}+。
第二排把 badgeMax 调成 9,120 就变成了 9+。
<script setup lang="ts">
import { defineComponent, h, ref, resolveComponent, type Component } from 'vue'
import {
mdiInboxOutline,
mdiBellOutline,
mdiEmailOutline,
mdiAlertCircleOutline,
} from '@mdi/js'
const MIcon = resolveComponent('MIcon') as Component
function icon(path: string) {
return defineComponent({
name: 'DemoNavIcon',
props: { size: { type: [Number, String], default: 24 } },
setup(props) {
return () => h(MIcon, { path, size: props.size })
},
})
}
const active = ref('inbox')
const items = [
{ name: 'inbox', label: '收件箱', icon: icon(mdiInboxOutline), badge: 8 },
{ name: 'notice', label: '通知', icon: icon(mdiBellOutline), badge: true },
{ name: 'mail', label: '邮件', icon: icon(mdiEmailOutline), badge: 120 },
{ name: 'alert', label: '告警', icon: icon(mdiAlertCircleOutline) },
]
</script>
<template>
<div class="m-row-demo--stretch">
<MNav v-model="active" :items="items" direction="horizontal" :badge-max="99" />
<MNav v-model="active" :items="items" direction="horizontal" size="small" :badge-max="9" />
</div>
</template>尺寸与对齐
size 影响图标大小、字号与项宽高;align 在横向下取 left / center / right。
<script setup lang="ts">
import { defineComponent, h, ref, resolveComponent, type Component } from 'vue'
import { mdiHomeVariantOutline, mdiCompassOutline, mdiAccountOutline } from '@mdi/js'
const MIcon = resolveComponent('MIcon') as Component
function icon(path: string) {
return defineComponent({
name: 'DemoNavIcon',
props: { size: { type: [Number, String], default: 24 } },
setup(props) {
return () => h(MIcon, { path, size: props.size })
},
})
}
const active = ref('home')
const items = [
{ name: 'home', label: '首页', icon: icon(mdiHomeVariantOutline) },
{ name: 'explore', label: '发现', icon: icon(mdiCompassOutline) },
{ name: 'mine', label: '我的', icon: icon(mdiAccountOutline) },
]
</script>
<template>
<div class="m-row-demo--stretch">
<MNav v-model="active" :items="items" direction="horizontal" size="small" align="left" />
<MNav v-model="active" :items="items" direction="horizontal" size="default" align="center" />
<MNav v-model="active" :items="items" direction="horizontal" size="large" align="right" />
</div>
</template>语义色
color 决定激活块的底色与 hover 底色。
<script setup lang="ts">
import { defineComponent, h, ref, resolveComponent, type Component } from 'vue'
import { mdiHomeVariantOutline, mdiCompassOutline, mdiAccountOutline } from '@mdi/js'
const MIcon = resolveComponent('MIcon') as Component
function icon(path: string) {
return defineComponent({
name: 'DemoNavIcon',
props: { size: { type: [Number, String], default: 24 } },
setup(props) {
return () => h(MIcon, { path, size: props.size })
},
})
}
const colors = ['primary', 'secondary', 'success', 'warning', 'error', 'info'] as const
const active = ref('home')
const items = [
{ name: 'home', label: '首页', icon: icon(mdiHomeVariantOutline) },
{ name: 'explore', label: '发现', icon: icon(mdiCompassOutline) },
{ name: 'mine', label: '我的', icon: icon(mdiAccountOutline) },
]
</script>
<template>
<div class="m-row-demo--stretch">
<MNav
v-for="color in colors"
:key="color"
v-model="active"
:items="items"
direction="horizontal"
size="small"
align="left"
:color="color" />
</div>
</template>禁用与受控激活
单项 disabled 只锁住那一项,顶层 disabled 锁住全部。激活值指向禁用项或不存在项时,组件会自动回退到第一个可用项并回写。
点按钮把 active 设成禁用项,状态会被组件改回来。
<script setup lang="ts">
import { ref } from 'vue'
const active = ref<string | number>('home')
const items = [
{ name: 'home', label: '首页' },
{ name: 'locked', label: '受限项', disabled: true },
{ name: 'mine', label: '我的' },
]
/** 主动把激活项指到一个禁用项上,观察组件回退 */
function pickDisabled() {
active.value = 'locked'
}
</script>
<template>
<div class="m-row-demo--stretch">
<MNav v-model="active" :items="items" direction="horizontal" size="small" align="left" />
<MNav :items="items" direction="horizontal" size="small" align="left" disabled />
<div class="m-row-demo">
<MButton variant="outlined" color="primary" size="small" @click="pickDisabled">
把 active 设为禁用项
</MButton>
<span>当前激活:{{ active }}</span>
</div>
</div>
</template>API
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue | string | number | undefined | 当前激活项的 name(v-model) |
items | NavItem[] | [] | 导航项数据 |
direction | 'vertical' | 'horizontal' | 'vertical' | 排列方向:纵向侧栏 / 横向底栏 |
align | 'top' | 'middle' | 'bottom' | 'left' | 'center' | 'right' | 'center' | 沿主轴对齐;纵向取 top / middle / bottom,横向取 left / center / right |
color | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'primary' | 主题色,决定激活实心块与 hover 底色 |
size | 'small' | 'default' | 'large' | 'default' | 尺寸,影响图标大小(20 / 24 / 28)、字号与项宽高 |
disabled | boolean | false | 是否禁用全部导航项 |
badgeMax | number | 99 | 数字徽标上限,超过时显示为 {badgeMax}+ |
ariaLabel | string | '导航' | 绑定到 nav 元素的 aria-label |
Events
| 事件 | 参数 | 说明 |
|---|---|---|
update:modelValue | value: string | number | 激活项变化,包含内部回退时的回写 |
change | name: string | number | 用户切换激活项(点击或键盘) |
Slots
无。
类型定义
export interface NavItem {
name: string | number;
label: string;
icon?: Component;
badge?: string | number | boolean;
disabled?: boolean;
}使用建议
items[].icon 是组件而不是字符串
icon 的类型是 Component,组件会被渲染成 <component :is="icon" :size="iconSize" /> 并接收当前尺寸。直接用 MIcon 需要先把路径绑定进去:
import { defineComponent, h, resolveComponent } from 'vue';
import { mdiHome } from '@mdi/js';
const MIcon = resolveComponent('MIcon');
const HomeIcon = defineComponent({
props: { size: { type: [Number, String], default: 24 } },
setup: (props) => () => h(MIcon, { path: mdiHome, size: props.size }),
});激活值失效会被自动改写
modelValue 不存在于 items 中,或指向一个被禁用的项时,watchEffect 会把激活值改成第一个可用项,并通过 update:modelValue 回写。所以外部持有的激活变量会被组件改掉,别把它当成只读的展示值。
徽标的三种取值语义不同
badge: true 渲染为小红点(无文字);数字或字符串渲染为文字徽标;badge 为 null / false 时不渲染。数字超过 badgeMax 会显示成 {badgeMax}+。
方向键随 direction 变化
纵向导航用 ↑ / ↓,横向导航用 ← / →,Home / End 跳到首尾,Enter / Space 确认。焦点在项之间走 roving tabindex,只有激活项是 tabindex="0"。