Select 选择器
从固定选项集合中取值。单选用单值 v-model,多选改用数组;面板定位自带视口夹紧、上下翻转与滚动跟随,长列表在面板内部滚动,不会把页面顶开。
代码演示
基础用法
options 是 { label, value } 组成的数组,未选中时显示 placeholder。
点击触发区展开面板,选择后单选面板自动收起。
<script setup lang="ts">
import { ref } from 'vue'
const city = ref<string | number | (string | number)[] | undefined>(undefined)
const options = [
{ label: '北京', value: 'beijing' },
{ label: '上海', value: 'shanghai' },
{ label: '广州', value: 'guangzhou' },
{ label: '深圳', value: 'shenzhen' },
]
</script>
<template>
<div class="m-row-demo">
<MSelect v-model="city" :options="options" placeholder="请选择城市" clearable width="200px" />
</div>
</template>多选
multiple 打开后 modelValue 必须改为数组,触发区把已选项的 label 用逗号拼接展示。
<script setup lang="ts">
import { ref } from 'vue'
const skills = ref<(string | number)[]>(['vue'])
const options = [
{ label: 'Vue', value: 'vue' },
{ label: 'React', value: 'react' },
{ label: 'Svelte', value: 'svelte' },
{ label: 'Solid', value: 'solid' },
]
</script>
<template>
<div class="m-row-demo">
<MSelect v-model="skills" :options="options" multiple clearable placeholder="可多选" width="240px" />
</div>
</template>选项图标与禁用项
SelectOption.icon 是文本图标(emoji / 字符),选中后会一并出现在触发区;disabled 的选项不可点击,键盘导航也会跳过。
<script setup lang="ts">
import { ref } from 'vue'
const status = ref<string | number | (string | number)[] | undefined>('running')
const options = [
{ label: '运行中', value: 'running', icon: '🚀' },
{ label: '排队中', value: 'pending', icon: '⏳' },
{ label: '已失败', value: 'failed', icon: '⛔' },
{ label: '已归档(不可选)', value: 'archived', disabled: true },
]
</script>
<template>
<div class="m-row-demo">
<MSelect v-model="status" :options="options" width="240px" />
</div>
</template>尺寸与圆角
size 控制触发区高度,radius 只作用于触发区(面板圆角是 panelRadius)。
<script setup lang="ts">
import { ref } from 'vue'
const small = ref<string | number | (string | number)[] | undefined>('small')
const normal = ref<string | number | (string | number)[] | undefined>('default')
const large = ref<string | number | (string | number)[] | undefined>('large')
const pill = ref<string | number | (string | number)[] | undefined>(undefined)
const options = [
{ label: 'small 28px', value: 'small' },
{ label: 'default 36px', value: 'default' },
{ label: 'large 48px', value: 'large' },
]
</script>
<template>
<div class="m-row-demo--col">
<MSelect v-model="small" :options="options" size="small" width="240px" />
<MSelect v-model="normal" :options="options" size="default" width="240px" />
<MSelect v-model="large" :options="options" size="large" width="240px" />
<MSelect
v-model="pill"
:options="options"
radius="999px"
placeholder="radius=999px"
width="240px" />
</div>
</template>面板宽度与圆角
面板宽度以触发区为下限、随最长选项自动撑开,视口右侧放不下时会压缩;高度上限 280px,超出后内部滚动。
左侧 14 个选项触发内部滚动,右侧的长 label 会把面板撑得比触发区宽。
<script setup lang="ts">
import { ref } from 'vue'
const short = ref<string | number | (string | number)[] | undefined>(undefined)
const long = ref<string | number | (string | number)[] | undefined>(undefined)
const many = Array.from({ length: 14 }, (_, i) => ({
label: `选项 ${i + 1}`,
value: i + 1,
}))
const verbose = [
{ label: '只在工作日 09:00 - 18:00 接收通知', value: 'workday' },
{ label: '任何时间都接收通知(含节假日)', value: 'always' },
{ label: '完全不接收通知', value: 'never' },
]
</script>
<template>
<div class="m-row-demo">
<MSelect
v-model="short"
:options="many"
panel-radius="0"
placeholder="panelRadius=0"
width="140px" />
<MSelect
v-model="long"
:options="verbose"
panel-radius="18px"
placeholder="label 比触发区宽"
width="180px" />
</div>
</template>清空与禁用
清除按钮在「有值、未禁用」时出现;单选清除抛 undefined,多选清除抛 []。
两个 change 处理器会把抛出的值弹成 toast。
<script setup lang="ts">
import { ref } from 'vue'
import { toast } from 'miao-design'
const options = [
{ label: '北京', value: 'beijing' },
{ label: '上海', value: 'shanghai' },
{ label: '广州', value: 'guangzhou' },
]
const single = ref<string | number | (string | number)[] | undefined>('beijing')
const multi = ref<(string | number)[]>(['beijing'])
function onSingle(value: string | number | (string | number)[] | undefined) {
toast.info(value === undefined ? '清除后收到 undefined' : `选中 ${String(value)}`)
}
function onMulti(value: string | number | (string | number)[] | undefined) {
const empty = Array.isArray(value) && value.length === 0
toast.info(empty ? '清除后收到空数组 []' : `选中 ${String(value)}`)
}
</script>
<template>
<div class="m-row-demo--col">
<MSelect
v-model="single"
:options="options"
clearable
width="240px"
@change="onSingle" />
<MSelect
v-model="multi"
:options="options"
multiple
clearable
width="240px"
@change="onMulti" />
<MSelect
:model-value="'beijing'"
:options="options"
disabled
width="240px" />
</div>
</template>API
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue | string | number | (string | number)[] | undefined | undefined(未提供) | 当前值(单选为单值,多选为数组) |
options | SelectOption[] | [](工厂函数) | 选项列表 |
placeholder | string | undefined(未提供) | 占位文本 |
disabled | boolean | false | 是否禁用 |
clearable | boolean | false | 有值时显示清除按钮 |
multiple | boolean | false | 多选模式:modelValue 为数组 |
size | 'small' | 'default' | 'large' | 'default' | 高度取全局 --m-size-* 令牌:small 28px / default 36px / large 48px |
width | string | undefined(未提供;样式默认 220px) | 触发区宽度 |
radius | string | undefined(未提供;默认 8px) | 触发区圆角(任意 CSS 长度,如 '8px') |
panelRadius | string | undefined(未提供;默认 8px) | 弹出面板圆角(任意 CSS 长度) |
popupClass | string | undefined(未提供) | 弹出面板额外类名(面板 Teleport 到 body,需配合全局样式使用) |
appendToBody | boolean | true | 是否将面板挂载到 body;在 Chrome 扩展 popup 等受限容器中建议设为 false |
lockScroll | boolean | true | 打开面板时是否锁定 body 滚动 |
Events
| 事件 | 参数 | 说明 |
|---|---|---|
update:modelValue | value: SelectValue | 选中变化(多选为数组;单选清除为 undefined,多选清除为 []) |
change | value: SelectValue | 选中变化,与 update:modelValue 同值同刻抛出 |
Slots
无。
类型定义
export interface SelectOption {
label: string;
value: string | number;
icon?: string;
iconUrl?: string;
disabled?: boolean;
}
export type SelectValue = string | number | (string | number)[] | undefined;
export type SelectSize = InputSize; // 'small' | 'default' | 'large'Expose:源码未暴露实例方法。
面板行为
- 定位:默认 Teleport 到
body,用fixed定位;下方空间不足时向上翻转,两侧都不够时选空间较大的一侧并把高度锁在该侧空间内(面板始终完整落在视口内)。 - 跟随:滚动或缩放时重新读取触发区位置并重算定位;触发区整体滚出视口后面板收起。
- inline 模式:
appendToBody="false"时面板absolute定位在组件根节点内,宽度固定等于触发区宽度,且不转移焦点,适合受限容器。 - 键盘:未展开时
Enter/Space/ArrowUp/ArrowDown展开;展开后上下键移动、Home/End跳首尾、Enter选中、Escape关闭并归还焦点、Tab关闭面板继续走焦。
使用建议
多选时 modelValue 必须是数组
multiple 与 modelValue 的类型必须成对出现。多选时传了单值,组件会按空数组处理,表现为「选了不显示」。
面板内容不可自定义
面板由 options 数据驱动,没有默认插槽、选项插槽或空状态插槽,也不支持选项分组与多列布局。空列表会渲染内置的「无匹配选项」文案。需要完全自定义的下拉内容时请改用 Dropdown 下拉菜单。
icon 与 iconUrl 的区别
icon 是直接渲染在选项里的文本(emoji、字符),不是 MIcon 的 path;要放图片请用 iconUrl(作为 background-image),两者同时存在时 iconUrl 优先。
相关组件
- Dropdown 下拉菜单:需要自定义面板内容时使用。
- Radio 单选框 / Checkbox 复选框:选项数量少且需要全量可见时,比下拉更省一次点击。
- Input 输入框:候选项不固定、需要自由输入时使用。