Skip to content

Select 选择器 ​

用于从选项集合中选择单个或多个值,适合枚举、状态、人员、城市、分类等字段。

基础用法 ​

当前值:pending,清除次数:0
vue
<template>
  <x-select v-model="value" placeholder="请选择" allow-clear @clear="clearCount++">
    <x-select-option value="pending">待处理</x-select-option>
    <x-select-option value="processing">处理中</x-select-option>
    <x-select-option value="done">已完成</x-select-option>
    <x-select-option value="disabled" disabled>禁用项</x-select-option>
  </x-select>
  <p>当前值:{{ value || '未选择' }},清除次数:{{ clearCount }}</p>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const value = ref('pending');
const clearCount = ref(0);
</script>

多选 ​

请选择成员当前值:design, frontend
vue
<template>
  <x-select
    v-model="values"
    multiple
    allow-clear
    :max-tag-count="2"
    placeholder="请选择成员"
  >
    <x-select-option value="design" :tag-props="{ color: 'blue' }">设计</x-select-option>
    <x-select-option value="frontend" :tag-props="{ color: 'green' }">前端</x-select-option>
    <x-select-option value="backend">后端</x-select-option>
    <x-select-option value="qa">测试</x-select-option>
  </x-select>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const values = ref(['design', 'frontend']);
</script>

搜索和自定义过滤 ​

支持中文和英文拼写过滤
vue
<template>
  <x-select
    v-model="city"
    allow-search
    :options="cities"
    :filter-option="filterCity"
    placeholder="搜索城市"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const city = ref('');
const cities = [
  { label: '北京 Beijing', value: 'beijing' },
  { label: '上海 Shanghai', value: 'shanghai' },
  { label: '广州 Guangzhou', value: 'guangzhou' },
  { label: '深圳 Shenzhen', value: 'shenzhen' },
];

const filterCity = (input: string, option: { label?: string }) =>
  option.label?.toLowerCase().includes(input.toLowerCase()) ?? false;
</script>

允许创建 ​

输入后选择新标签当前标签:vue
vue
<template>
  <x-select v-model="tags" multiple allow-create placeholder="输入后选择新标签">
    <x-select-option value="vue">Vue</x-select-option>
    <x-select-option value="typescript">TypeScript</x-select-option>
    <x-select-option value="vite">Vite</x-select-option>
  </x-select>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['vue']);
</script>

选择数量限制 ​

产品尝试选择第 3 项触发限制
vue
<template>
  <x-select
    v-model="members"
    multiple
    :limit="2"
    :options="memberOptions"
    placeholder="最多选择 2 项"
    @exceed-limit="message = '最多只能选择 2 项'"
  />
  <p>{{ message }}</p>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const members = ref(['pm']);
const message = ref('');
const memberOptions = [
  { label: '产品', value: 'pm' },
  { label: '设计', value: 'design' },
  { label: '前端', value: 'frontend' },
  { label: '后端', value: 'backend' },
];
</script>

通过 options 渲染 ​

北京 Beijing当前值:beijing
vue
<template>
  <x-select v-model="value" :options="options" placeholder="请选择城市" />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const value = ref('beijing');
const options = [
  { label: '北京', value: 'beijing' },
  { label: '上海', value: 'shanghai' },
  { label: '禁用项', value: 'disabled', disabled: true },
];
</script>

自定义字段名 ​

北京
vue
<template>
  <x-select
    v-model="city"
    :options="options"
    :field-names="{ value: 'id', label: 'name', disabled: 'locked' }"
    placeholder="请选择城市"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const city = ref('bj');
const options = [
  { id: 'bj', name: '北京' },
  { id: 'sh', name: '上海' },
  { id: 'gz', name: '广州', locked: true },
];
</script>

选项分组 ​

vue
<template>
  <x-select v-model="city" placeholder="请选择城市">
    <x-select-option-group label="华北">
      <x-select-option value="beijing">北京</x-select-option>
      <x-select-option value="tianjin">天津</x-select-option>
    </x-select-option-group>
    <x-select-option-group label="华东">
      <x-select-option value="shanghai">上海</x-select-option>
      <x-select-option value="hangzhou">杭州</x-select-option>
    </x-select-option-group>
  </x-select>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const city = ref('');
</script>

状态和尺寸 ​

vue
<template>
  <x-space direction="vertical">
    <x-select v-model="value" size="small" :options="options" placeholder="小尺寸" />
    <x-select v-model="value" :options="options" disabled placeholder="禁用状态" />
    <x-select v-model="value" :options="options" error placeholder="错误状态" />
    <x-select v-model="value" :options="options" loading placeholder="加载中" />
    <x-select v-model="value" :options="options" :bordered="false" placeholder="无边框" />
  </x-space>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const value = ref('');
const options = [
  { label: '选项 A', value: 'a' },
  { label: '选项 B', value: 'b' },
];
</script>

自定义内容和插槽 ​

状态
vue
<template>
  <x-select v-model="value" placeholder="请选择">
    <template #prefix>状态</template>
    <template #header>
      <div style="padding: 8px 12px">常用状态</div>
    </template>
    <template #footer>
      <div style="padding: 8px 12px">没有合适选项时可联系管理员</div>
    </template>
    <template #label="{ data }">
      {{ data.label }} / {{ data.value }}
    </template>
    <x-select-option value="pending" label="待处理">
      待处理
      <template #suffix>3</template>
    </x-select-option>
    <x-select-option value="done" label="已完成">
      已完成
      <template #suffix>8</template>
    </x-select-option>
  </x-select>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const value = ref('pending');
</script>

虚拟列表和滚动事件 ​

虚拟与非虚拟列表共用 scrollbar 配置,并触发相同的滚动事件;传 scrollbar=false 可使用原生滚动条。

继续滚动查看更多
vue
<template>
  <x-select
    v-model="value"
    :options="options"
    :virtual-list-props="{ height: 200 }"
    placeholder="大量数据"
    @dropdown-reach-bottom="reached = true"
  />
  <p>{{ reached ? '已滚动到底部' : '继续滚动查看更多' }}</p>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const value = ref('');
const reached = ref(false);
const options = Array.from({ length: 100 }, (_, index) => ({
  label: `选项 ${index + 1}`,
  value: index + 1,
}));
</script>

局部容器滚动 ​

打开下拉后滚动下面的容器:默认跟随触发器。开启「滚动关闭」后,从打开时的位置滚动 24px 即收起;不需要同时设置 updateAtScroll。若需要固定在原位置,可传 triggerProps: { updateAtScroll: false }。

滚动关闭 · 下拉已关闭
选项 A
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Select, Switch } from 'x-next';

const value = ref('a');
const popup = ref(false);
const closeOnScroll = ref(false);
const host = ref<HTMLElement>();
const switchAttrs = { 'aria-label': '滚动关闭' };
const options = [{ label: '选项 A', value: 'a' }, { label: '选项 B', value: 'b' }];
</script>

<template>
  <section class="scroll-scope">
    <Switch v-model="closeOnScroll" v-bind="switchAttrs" />
    <span>{{ popup ? '下拉已打开' : '下拉已关闭' }}</span>
    <div class="scroll-region">
      <div class="scroll-content">
        <Select
          v-if="host"
          v-model="value"
          v-model:popup-visible="popup"
          :options="options"
          :popup-container="host"
          :trigger-props="{ scrollToClose: closeOnScroll, scrollToCloseDistance: 24 }"
          style="width: 220px"
        />
      </div>
    </div>
    <div ref="host" class="scroll-overlay" />
  </section>
</template>

<style scoped>
.scroll-region { height: 200px; overflow: auto; }
.scroll-content { min-height: 480px; padding: 80px 16px 0; }
.scroll-overlay { position: fixed; inset: 0; pointer-events: none; }
</style>

键盘、焦点与父级 Escape ​

用 Tab 聚焦下面的选择器,再按 ↓ / ↑ 打开面板。没有已选的启用项时,↓ 激活首个启用项,↑ 激活最后一个启用项;已有选中项时优先激活该项。展开后上下键跳过禁用项并循环,Enter 选择活动项。通过方向键打开时会激活选项,即使设置了 defaultActiveFirstOption=false。

Enter 单选或 Esc 关闭面板后,焦点保留在选择器上。展开时第一次 Esc 只关闭选项面板,继续按 Esc 才到达父级;闭合选择器不会拦截 Esc,可供外层 Dialog 关闭。Tab / Shift+Tab 关闭面板并保留浏览器的焦点移动,点击外部也可正常转移焦点。禁用状态和输入法组合输入期间不执行上述键盘操作;加载期间不通过 Enter 或上下键选择 / 移动选项,仍可用 Escape 或 Tab 关闭已打开面板。

当前值:未选择;面板:关闭; Select 聚焦:否;父级收到 Escape:0 次

vue
<template>
  <div @keydown="handleParentEscape">
    <x-select
      v-model="value"
      v-model:popup-visible="popupVisible"
      :options="options"
      aria-label="键盘选择城市"
      placeholder="Tab 聚焦后按 ↑ / ↓"
      @focus="focused = true"
      @blur="focused = false"
    />
    <x-button>下一个焦点目标</x-button>
    <p role="status">
      当前值:{{ value || '未选择' }};面板:{{ popupVisible ? '展开' : '关闭' }};
      Select 聚焦:{{ focused ? '是' : '否' }};父级收到 Escape:{{ parentEscapeCount }} 次
    </p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const value = ref('');
const popupVisible = ref(false);
const focused = ref(false);
const parentEscapeCount = ref(0);
const options = [
  { label: '不可用的起始项', value: 'disabled-first', disabled: true },
  { label: '北京', value: 'beijing' },
  { label: '上海', value: 'shanghai' },
  { label: '不可用的末尾项', value: 'disabled-last', disabled: true },
];
const handleParentEscape = (event: KeyboardEvent) => {
  if (event.key === 'Escape' && !event.isComposing && event.keyCode !== 229) {
    parentEscapeCount.value++;
  }
};
</script>

默认触发器会自动关联 combobox、listbox 和 option 的 ARIA 状态。使用 trigger 自定义触发器时,插槽会提供 popupVisible、disabled、listboxId 和 activeDescendant;自定义节点需自行实现键盘事件、保持可聚焦,并设置对应的 aria-expanded、aria-controls 与 aria-activedescendant。

在 Form 中校验 ​

组件放进 x-form-item 后会自动继承 Form 的尺寸与禁用状态,并按 FormItem 的校验规则展示错误。默认 validateTrigger 为 change,即值变化时触发校验。

vue
<template>
  <x-form ref="formRef" :model="formState" :rules="formRules">
    <x-form-item field="city" label="城市">
      <x-select v-model="formState.city" :options="cityOptions" placeholder="请选择城市" allow-clear />
    </x-form-item>
    <x-form-item>
      <x-space>
        <x-button type="primary" @click="handleValidate">校验</x-button>
        <x-button @click="handleReset">重置</x-button>
      </x-space>
    </x-form-item>
  </x-form>
  <p role="status">{{ validateResult }}</p>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';
import type { FormInstance, FieldRule } from 'x-next';

const formState = reactive({ city: undefined });
const formRules: Record<string, FieldRule[]> = {
  city: [{ required: true, message: '请选择城市' }],
};
const formRef = ref<FormInstance>();
const validateResult = ref('');

const handleValidate = async () => {
  const errors = await formRef.value!.validate();
  validateResult.value = errors
    ? `校验失败:${Object.values(errors).map((error) => error.message).join(';')}`
    : '校验通过';
};
const handleReset = () => {
  formRef.value!.resetFields();
  validateResult.value = '已重置';
};
</script>

按需导入 ​

ts
import { Select, SelectOption, SelectOptionGroup, SelectDropdown } from 'x-next';

样式按需引入(base.css 为共享基础层,多个组件只需引入一次):

ts
import 'x-next/style/base.css';
import 'x-next/style/form-select.css';

Select Props ​

参数说明类型默认值
multiple是否多选booleanfalse
modelValue绑定值string | number | boolean | object | arrayundefined
defaultValue默认值,非受控模式string | number | boolean | object | array单选 '',多选 []
inputValue搜索输入值string-
defaultInputValue默认搜索输入值string''
size选择器尺寸'mini' | 'small' | 'medium' | 'large'跟随全局配置
placeholder占位提示string-
loading是否加载中booleanfalse
disabled是否禁用booleanfalse
error是否错误状态booleanfalse
allowClear是否允许清空booleanfalse
allowSearch是否允许搜索boolean | { retainInputValue?: boolean }单选 false,多选 true
allowCreate是否允许创建新选项booleanfalse
maxTagCount多选最多显示标签数量,0 不限制number0
popupContainer弹出层挂载容器string | HTMLElement-
bordered是否显示边框booleantrue
defaultActiveFirstOption无有效选中项时是否默认激活首个选项;方向键展开时按方向激活首 / 末项booleantrue
popupVisible弹出层显示状态booleanundefined
defaultPopupVisible默认弹出层显示状态booleanfalse
unmountOnClose关闭时是否销毁弹出层booleanfalse
filterOption是否过滤选项或自定义过滤方法boolean | functiontrue
options选项数据Array<string | number | boolean | SelectOptionData>[]
virtualListProps虚拟列表配置,传入后开启虚拟滚动object-
triggerProps下拉触发器配置;updateAtScroll 默认开启,可显式关闭object{ updateAtScroll: true }
formatLabel格式化显示内容(data) => string-
fallbackOption自定义值不存在时的兜底选项boolean | functiontrue
showExtraOptions是否显示额外选项booleantrue
valueKey对象值的唯一键字段string'value'
searchDelay搜索事件触发延迟number500
limit多选最多选择数量,0 不限制number0
fieldNames自定义选项字段名object-
scrollbar是否开启滚动条或滚动条配置boolean | objecttrue
showHeaderOnEmpty空状态时是否显示 headerbooleanfalse
showFooterOnEmpty空状态时是否显示 footerbooleanfalse
tagNowrap多选标签内容是否不换行booleanfalse

Select Events ​

事件名说明回调参数
update:modelValue值更新时触发value
update:inputValue搜索输入值更新时触发inputValue
update:popupVisible弹出层显示状态更新时触发visible
change值变化时触发value
input-value-change输入值变化时触发inputValue
popup-visible-change弹出层显示状态变化时触发visible
clear点击清除按钮时触发event
remove删除多选标签时触发removed
search用户搜索时触发inputValue
dropdown-scroll下拉菜单滚动时触发event
dropdown-reach-bottom下拉菜单滚动到底部时触发event
exceed-limit多选超过数量限制时触发value, event

Select Slots ​

插槽名说明
empty空状态内容
option自定义选项内容
label自定义选择框显示内容
header下拉框页头
footer下拉框页脚
arrow-icon箭头图标
loading-icon加载中图标
search-icon搜索图标
prefix前缀元素
trigger自定义触发元素;参数:{ popupVisible, disabled, listboxId, activeDescendant }

SelectOption Props ​

参数说明类型默认值
value选项值,不填时从内容获取string | number | boolean | objectundefined
label选项标签,不填时从内容获取string-
disabled是否禁用booleanfalse
tagProps多选时展示标签的属性object-
index手动指定选项 indexnumber-

SelectOption Slots ​

插槽名说明
default选项内容
icon选项图标
suffix选项后缀

SelectOptionGroup Props ​

参数说明类型默认值
label选项组标题string-

SelectOptionGroup Slots ​

插槽名说明
default选项组内容
label自定义选项组标题