在这里插入图片描述

表单里除了输入框,用得最多的就是选择器了。当选项是固定的几个值时,让用户从下拉列表里选比让他手动输入要靠谱得多,既能避免输入错误,也能提升操作效率。这篇文章来聊聊在 Electron for OpenHarmony 项目中如何实现 Select 选择器组件。

选择器的应用场景

在实际项目中,选择器的使用场景非常多:性别选择、省市区级联、状态筛选、分类选择等等。相比原生的 select 标签,Element Plus 的 el-select 组件功能强大很多,支持搜索过滤、多选、远程加载、自定义模板等高级特性,而且样式统一美观,不会出现不同浏览器显示效果不一致的问题。

数据准备

选择器需要一组选项数据,通常是一个对象数组,每个对象包含 value 和 label 两个字段:

<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const value1 = ref('')
const value2 = ref([])
const options = [
  { value: '1', label: '选项一' },
  { value: '2', label: '选项二' },
  { value: '3', label: '选项三' },
  { value: '4', label: '选项四' },
  { value: '5', label: '选项五' },
]
</script>

value 是选项的实际值,会绑定到 v-model 上;label 是显示给用户看的文本。这两个字段名是 Element Plus 的默认配置,如果你的数据结构不一样,可以通过 value-keylabel 属性来指定。

value1 用于单选场景,初始值是空字符串;value2 用于多选场景,初始值是空数组。这个初始值的类型很重要,如果多选场景给了字符串初始值,组件会报错。

基础选择器

最简单的用法就是 v-model 绑定一个值,然后用 v-for 渲染选项:

<el-card class="demo-card">
  <template #header>基础用法</template>
  <el-select v-model="value1" placeholder="请选择" style="width: 100%">
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</el-card>

el-select 是选择器容器,el-option 是每个选项。v-for 遍历 options 数组,:key 绑定唯一标识,:label 绑定显示文本,:value 绑定实际值。

placeholder 在没有选中任何选项时显示,提示用户这个选择器是干什么的。style="width: 100%" 让选择器撑满父容器宽度,默认情况下 el-select 有一个固定宽度,在响应式布局中不太好用。

用户点击选择器会弹出下拉面板,选中某个选项后面板收起,选中的 label 显示在选择器中,对应的 value 赋值给 v-model 绑定的变量。

可清空选择器

有时候用户选错了想重新选,或者这个字段本身就是可选的,需要能清空已选的值:

<el-card class="demo-card">
  <template #header>可清空</template>
  <el-select v-model="value1" clearable placeholder="请选择" style="width: 100%">
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</el-card>

加上 clearable 属性后,当选择器有值时,鼠标悬停会在右侧显示一个小叉号,点击就能清空选中的值。这个交互和 Input 组件的 clearable 是一致的,用户学习成本低。

清空后 v-model 绑定的变量会变成空字符串(单选)或空数组(多选),placeholder 会重新显示出来。

多选模式

单选只能选一个,但有些场景需要选多个,比如给文章打标签、选择多个收件人:

<el-card class="demo-card">
  <template #header>多选</template>
  <el-select v-model="value2" multiple placeholder="请选择" style="width: 100%">
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</el-card>

multiple 属性开启多选模式。多选时,选中的选项会以标签(Tag)的形式显示在选择器中,每个标签右侧有删除按钮可以单独移除。v-model 绑定的变量是一个数组,包含所有选中项的 value。

多选模式下点击已选中的选项会取消选中,点击未选中的选项会添加到已选列表。下拉面板不会自动收起,方便用户连续选择多个。

如果选项很多,选中的标签可能会把选择器撑得很高。可以用 collapse-tags 属性折叠标签:

<el-select v-model="value2" multiple collapse-tags placeholder="请选择" style="width: 100%">
  <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>

折叠后只显示第一个标签,后面的用 “+N” 的形式表示还有多少个。鼠标悬停在 “+N” 上会显示完整列表。

可搜索选择器

选项太多的时候,让用户一个个找太费劲了。加上搜索功能,用户输入关键字就能快速定位:

<el-card class="demo-card">
  <template #header>可搜索</template>
  <el-select v-model="value1" filterable placeholder="请选择" style="width: 100%">
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</el-card>

filterable 属性开启搜索功能。开启后选择器变成可输入状态,用户输入的内容会和选项的 label 进行匹配,只显示匹配的选项。匹配规则默认是包含匹配,不区分大小写。

如果默认的匹配规则不满足需求,可以通过 filter-method 属性自定义过滤函数:

<script setup lang="ts">
const filterMethod = (query: string) => {
  // 自定义过滤逻辑,比如拼音匹配
  if (query) {
    // 返回过滤后的选项
  }
}
</script>

<template>
  <el-select v-model="value1" filterable :filter-method="filterMethod" placeholder="请选择">
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</template>

禁用状态

某些情况下需要禁止用户操作选择器,比如没有权限或者正在加载数据:

<el-card class="demo-card">
  <template #header>禁用状态</template>
  <el-select v-model="value1" disabled placeholder="禁用状态" style="width: 100%">
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</el-card>

disabled 属性禁用整个选择器,视觉上会变灰,点击没有任何反应。这个属性通常会动态绑定:

<el-select v-model="value1" :disabled="loading" placeholder="请选择">

除了禁用整个选择器,还可以禁用单个选项:

<el-option 
  v-for="item in options" 
  :key="item.value" 
  :label="item.label" 
  :value="item.value"
  :disabled="item.disabled"
/>

禁用的选项会变灰,可以看到但不能选中。这在某些选项暂时不可用的场景很有用,比如库存为 0 的商品规格。

分组选项

选项很多的时候,可以按类别分组显示,让用户更容易找到想要的:

<script setup lang="ts">
const groupOptions = [
  {
    label: '热门城市',
    options: [
      { value: 'beijing', label: '北京' },
      { value: 'shanghai', label: '上海' },
      { value: 'guangzhou', label: '广州' },
    ]
  },
  {
    label: '其他城市',
    options: [
      { value: 'chengdu', label: '成都' },
      { value: 'hangzhou', label: '杭州' },
      { value: 'wuhan', label: '武汉' },
    ]
  }
]
</script>

<template>
  <el-select v-model="value1" placeholder="请选择城市" style="width: 100%">
    <el-option-group v-for="group in groupOptions" :key="group.label" :label="group.label">
      <el-option v-for="item in group.options" :key="item.value" :label="item.label" :value="item.value" />
    </el-option-group>
  </el-select>
</template>

el-option-group 组件用来包裹一组选项,:label 设置分组标题。分组标题不可选中,只是起到分类提示的作用。

远程搜索

当选项数据量很大,不适合一次性加载到前端时,可以用远程搜索,用户输入关键字后从服务端获取匹配的选项:

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

const value = ref('')
const options = ref([])
const loading = ref(false)

const remoteSearch = async (query: string) => {
  if (!query) {
    options.value = []
    return
  }
  loading.value = true
  // 模拟接口请求
  setTimeout(() => {
    options.value = [
      { value: '1', label: `搜索结果 1 - ${query}` },
      { value: '2', label: `搜索结果 2 - ${query}` },
      { value: '3', label: `搜索结果 3 - ${query}` },
    ]
    loading.value = false
  }, 500)
}
</script>

<template>
  <el-select 
    v-model="value" 
    filterable 
    remote 
    :remote-method="remoteSearch"
    :loading="loading"
    placeholder="请输入关键字搜索"
    style="width: 100%"
  >
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</template>

remote 属性开启远程搜索模式,remote-method 指定搜索函数,用户输入时会调用这个函数并传入输入的内容。:loading 绑定加载状态,在请求过程中显示 loading 图标。

远程搜索通常会配合防抖使用,避免用户快速输入时频繁请求接口:

<script setup lang="ts">
import { ref } from 'vue'
import { useDebounceFn } from '@vueuse/core'

const remoteSearch = useDebounceFn(async (query: string) => {
  // 搜索逻辑
}, 300)
</script>

与鸿蒙原生能力结合

在 Electron for OpenHarmony 项目中,选择器的选项数据可能来自原生层。比如获取系统支持的语言列表:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useOhos } from '@/composables/useOhos'

const language = ref('')
const languages = ref([])
const { getSystemInfo } = useOhos()

onMounted(async () => {
  const info = await getSystemInfo()
  // 假设系统信息中包含支持的语言列表
  languages.value = [
    { value: 'zh-CN', label: '简体中文' },
    { value: 'en-US', label: 'English' },
  ]
})
</script>

<template>
  <el-select v-model="language" placeholder="选择语言" style="width: 100%">
    <el-option v-for="item in languages" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</template>

选择器的值变化后,也可以调用原生能力来响应:

<script setup lang="ts">
import { ref, watch } from 'vue'
import { useOhos } from '@/composables/useOhos'

const theme = ref('light')
const { setWindowTitle } = useOhos()

watch(theme, (newTheme) => {
  setWindowTitle(`当前主题: ${newTheme}`)
})
</script>

<template>
  <el-select v-model="theme" placeholder="选择主题" style="width: 100%">
    <el-option value="light" label="浅色主题" />
    <el-option value="dark" label="深色主题" />
    <el-option value="auto" label="跟随系统" />
  </el-select>
</template>

watch 监听 theme 变量的变化,变化时调用 setWindowTitle 更新窗口标题。这只是一个简单示例,实际项目中可能会调用更复杂的原生能力,比如切换系统主题、修改系统设置等。

自定义选项模板

默认的选项只显示文本,有时候需要更丰富的展示,比如带图标或者多行信息:

<script setup lang="ts">
const cities = [
  { value: 'beijing', label: '北京', desc: '首都,政治文化中心' },
  { value: 'shanghai', label: '上海', desc: '经济金融中心' },
  { value: 'shenzhen', label: '深圳', desc: '科技创新之城' },
]
</script>

<template>
  <el-select v-model="value1" placeholder="请选择城市" style="width: 100%">
    <el-option v-for="item in cities" :key="item.value" :label="item.label" :value="item.value">
      <div style="display: flex; justify-content: space-between; align-items: center;">
        <span>{{ item.label }}</span>
        <span style="color: #909399; font-size: 12px;">{{ item.desc }}</span>
      </div>
    </el-option>
  </el-select>
</template>

el-option 的默认插槽可以自定义选项的显示内容。注意 :label 属性还是要设置的,因为选中后选择器中显示的是 label 的值,不是插槽内容。

样式定制

Element Plus 的选择器样式可以通过 CSS 变量定制:

.demo-page {
  --el-select-border-color-hover: #409eff;
  --el-select-disabled-border: #e4e7ed;
  --el-select-font-size: 14px;
  --el-select-input-color: #606266;
  --el-select-input-focus-border-color: #409eff;
}

下拉面板的样式需要用全局样式来覆盖,因为面板是挂载在 body 下的,不在组件的 scoped 范围内:

/* 全局样式文件 */
.el-select-dropdown {
  --el-bg-color-overlay: #fff;
  --el-border-color-light: #e4e7ed;
}

.el-select-dropdown__item.hover {
  background-color: #f5f7fa;
}

小结

Select 选择器是表单中非常重要的组件,这篇文章介绍了它的各种用法:基础单选、可清空、多选、可搜索、禁用状态、分组选项、远程搜索,以及与鸿蒙原生能力的结合。选择器的核心是把用户的选择限定在预设的范围内,既能保证数据的准确性,又能提升操作效率。根据实际场景选择合适的配置,才能发挥选择器的最大价值。


欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐