SwSelect

SwSelect 是下拉选择器:选项收进弹层、触发器收起态。单 / 多选一个组件(multiple 开关),支持可清空(clearable)、分组(选项 group 字段)、可搜索(searchable)与输入即建(creatable)。受控 v-model,单选为单值、多选为数组。

薄壳套 reka-ui:默认基于 Select 原语;searchable 时内部切换 Combobox 原语(触发器变输入框),对外仍是同一套 props / v-model / 插槽。弹层自渲染(不是原生 <select> 的 OS 弹层),故完全可定制;键盘导航 / 弹层定位 / a11y 开箱自带。视觉走「最小必要基础样式」——触发器 / 弹层 / 选中 / 高亮 / 禁用态默认都肉眼可辨,颜色 / 圆角等由 applier 覆盖。

选型:选项多、要省横向空间时用本组件(下拉)。选项少(2–5)、要全部可见、省一次点击时用 SwSegmented(平铺)。二者互补,如 antd 同时有 Select 与 Segmented。

单选

单选(状态筛选)

加载中...
<script setup lang="ts">
const status = ref()
</script>

<template>
  <SwSelect
    v-model="status"
    :options="[
      { value: 'all', label: '全部状态' },
      { value: 'ongoing', label: '连载中' },
      { value: 'completed', label: '已完结' },
    ]"
    placeholder="选择状态"
    aria-label="状态筛选"
  />

  <!-- 整体禁用:父表单 pending / 无权限时锁住选择器 -->
  <SwSelect v-model="status" :options="options" disabled />
</template>

多选 + 可清空

multiple 让 v-model 变数组;clearable 在有选中值时显示一键清空按钮。多选时触发器默认显示「已选 N 项」,要展示 tags / chips 走 #trigger 插槽:

多选 + 可清空(触发器显示计数)

加载中...

分组

选项带 group 字段时,连续同名归一组、组首渲染分组标题;不带 group 则扁平:

选项分组(直辖市 / 浙江)

加载中...
<template>
  <SwSelect
    v-model="city"
    :options="[
      { value: 'bj', label: '北京', group: '直辖市' },
      { value: 'sh', label: '上海', group: '直辖市' },
      { value: 'hz', label: '杭州', group: '浙江' },
    ]"
  />
</template>

可搜索(searchable)

searchable 让触发器变输入框:输入按 label 本地过滤(大小写不敏感包含匹配,组内滤空整组隐藏),↑↓ + 回车可选。选项几十上百(演职员 / 标签 / 城市)时用它;集合大到不能一次拉全(作品库 / 全站用户)时用下面的远程搜索。

可搜索单选(输入过滤)

加载中...

单选时输入框常显选中项 label(弹层打开后未输入前不过滤,避免被选中值锁死列表);多选时已选项渲染为 chips(label + ✕ 移除,Backspace 空输入删末位)。

远程搜索(remote + @search + loading)

三件套组合出「从大集合里搜一个选一个」:remote 关掉本地 label 过滤(options 本身就是远程结果集——服务端按拼音 / 别名 / id 命中的项 label 未必含关键词,本地再滤一遍会把它们藏掉);@search(term) 把用户输入交给业务(按 searchDebounce 防抖,默认 300ms;点清空立即发 '',据此复位列表);loading 在下拉里显示「加载中…」并压掉「无匹配」空态,当前列表保留不清空(防每次击键闪烁)。

结果陈旧期(用户已敲字、结果尚未回来:防抖窗口 ∪ loading)当前列表保留但不可选(选项 disabled + 变淡)——保留防塌陷跳动,不可选防「手快点下去选了与当前查询无关的对象」;creatable 的「创建」项同期藏掉。options 被整换后,已选项即使不在新结果集里,单选回显 / 多选 chips 的 label 也不会丢——组件内部缓存曾被选中值的 label(只记选过的,有界),同 value 的 label 后来变了以最新为准。

两条语义归业务:@search 收到的 '' 只表示「搜索词已清空」——复位 / 回初始列表 / 不搜由你定,不等于搜全部;remote 下 creatable 的「精确命中」只挡已加载页里的重复,真正的去重保证在服务端(唯一约束)。

远程搜索(模拟 500ms 接口,按导演名也能命中)

加载中...
<SwSelect v-model="mediaId" searchable remote :loading="pending" :options="mediaOptions" @search="onSearch" />
async function onSearch(term: string) {
  pending.value = true;
  mediaOptions.value = await api.media.options({ keyword: term }); // 整换结果集
  pending.value = false;
}

输入即建(creatable,标签选择场景)

creatable(仅 searchable 下生效)在输入无精确命中(大小写不敏感)时于列表尾部追加「创建 "xxx"」项。选中它不改 v-model,只发 @create(payload 为 trim 后输入文本)——业务调后端建项后自行把新 option 推进 options + v-model。multiple + searchable + creatable 组合即 tag-input 形态:

标签选择:多选 + 搜索 + 输入即建

加载中...
<script setup lang="ts">
async function onCreateTag(name: string) {
  const tag = await createTag(name) // 业务调后端建项
  tagOptions.value.push({ value: tag.id, label: tag.name })
  tagIds.value = [...tagIds.value, tag.id] // 推进 options + v-model
}
</script>

<template>
  <SwSelect v-model="tagIds" multiple searchable creatable :options="tagOptions" @create="onCreateTag" />
</template>

Props

名称类型必填默认值说明
modelValue(v-model)string | number | (string | number)[]undefined受控值。单选为单值,多选(multiple)为数组
optionsSwSelectOption[]✓—选项列表,每项 { value, label, disabled?, group? }
multiplebooleanfalse多选模式(v-model 为数组)
placeholderstring'请选择'未选时触发器占位文案
disabledbooleanfalse整体禁用
clearablebooleanfalse有选中值时显示一键清空按钮
searchablebooleanfalse可搜索形态:触发器变输入框,输入按 label 本地过滤(大小写不敏感)
creatablebooleanfalse输入无精确命中时显示「创建 "xxx"」项并 emit create;仅 searchable 下生效
remotebooleanfalse远程搜索:关掉本地 label 过滤(options 即远程结果集),配合 @search;仅 searchable 下有意义
loadingbooleanfalse远程搜索进行中:下拉显示「加载中…」并压掉空态,当前列表保留不清空
searchDebouncenumber300@search 防抖毫秒数,0 = 不防抖

SwSelectOption:{ value: string \| number; label: string; disabled?: boolean; group?: string }(可从包名导入:import type { SwSelectOption } from '@swift-web/framework')

Events

名称携带说明
update:modelValuevalue | value[] | undefined选中变化(清空时单选为 undefined、多选为 [])
update:openopen: boolean弹层开 / 合
clear—点击清空按钮触发
createlabel: string(searchable + creatable)选中「创建 "xxx"」项时触发;组件不改 v-model,业务建项后自行推 options + v-model
searchterm: string(searchable)用户输入即发,payload 为 trim 后的搜索词,按 searchDebounce 防抖;点清空立即发 ''。单选回显 label 的程序性同步不发;非 remote 下也发(本地过滤照旧),便于埋点 / 联动

Slots

名称作用域参数说明
trigger{ selectedLabel, selectedOptions, count, hasValue }自定义触发器内容。不传则裸文案。仅非 searchable 分支有效(searchable 触发器是输入框)
option{ option, selected }自定义单个选项视觉。不传则渲染 option.label

样式定制 Token

触发器与弹层皮肤经 --sw-select-* token 覆盖(fallback 即默认视觉)。弹层 Portal 到 body,token 在 :root(或任意 body 祖先)覆盖即可,无需 :global 穿透:

Token默认值作用点
--sw-select-trigger-min-width8rem触发器最小宽
--sw-select-trigger-min-heightvar(--control-height)触发器最小高
--sw-select-trigger-padding0.25rem 0.75rem触发器内边距
--sw-select-trigger-bordervar(--input)触发器描边色
--sw-select-trigger-radiusvar(--radius)触发器圆角
--sw-select-trigger-bgvar(--background)触发器底色
--sw-select-trigger-colorvar(--foreground)触发器文字色
--sw-select-trigger-font-sizevar(--control-font-size)触发器字号
--sw-select-chip-bgvar(--muted)多选 chips 底色(multiple + searchable)
--sw-select-chip-colorvar(--foreground)多选 chips 文字色
--sw-select-chip-radiuscalc(var(--radius) - 0.25rem)多选 chips 圆角
--sw-select-content-max-height18rem弹层最大高
--sw-select-content-bgvar(--popover)弹层底色
--sw-select-content-bordervar(--border)弹层描边色
--sw-select-content-radiusvar(--radius)弹层圆角
--sw-select-content-shadowvar(--shadow-md)弹层阴影
--sw-select-content-font-sizevar(--control-font-size)弹层字号
--sw-select-content-z1100弹层层级(fw#332 z 层旋钮)。弹层 Portal 到 body,与其它浮层撞层时改这个

高度 / 字号默认吃表单控件族语义 token --control-height(32px)/ --control-font-size(14px)——SwInput / SwSelect / SwSegmented 同行混排默认等高;覆盖这两个 token 全站控件同步缩放,组件级 token 优先级更高。

注意事项

  • 弹层经 Portal 传送到 body:applier 要改弹层样式(如字号)须用全局 / :global 选择器(scoped :deep 够不到),命中 .sw-select-content。
  • 无内置视觉品牌:组件只给最小必要基础样式;颜色 / 圆角等由 applier 覆盖。
  • 多选触发器默认显示「已选 N 项」:要展示 tags / chips 等用 #trigger 插槽自定义。
  • searchable 默认是本地过滤:只按 label 大小写不敏感包含匹配;远程搜索必须加 remote 关掉它,否则服务端按别名 / 拼音命中的项会被本地过滤藏掉。数据源仍归业务(组件只交出 @search 的搜索词、承接 options)。
  • @create 不改 v-model:选中「创建 "xxx"」项只发事件——业务建项后必须自己把新 option 推进 options 并更新 v-model,否则界面无变化。
  • searchable 时 #trigger 插槽不生效:搜索形态的触发器是输入框(多选为 chips + 输入框),无自定义触发器插槽。
  • multiple 时 v-model 必须是数组:多选把 v-model 初始化为单值(如 ref(''))会让选中态、「已选 N 项」计数全错且类型崩——多选用 ref([])、单选用单值。
  • 分组按「连续同名」归组:同名 group 的选项必须相邻排列,散落的同名 group 会渲染成多个同名分组块——options 传入前先按 group 排序。

相关