基于Vue3的移动端多选下拉组件SelectMultiple详解

121 阅读3分钟

一、组件概述

SelectMultiple.vue 是一个功能强大的移动端多选下拉组件,基于Vue3 Composition API和Vant组件库构建。该组件支持以下核心特性:

  • 多种数据格式支持(字符串数组/对象数组)
  • 动态字段映射(label/value/disabled)
  • 智能搜索过滤 - 禁用状态处理
  • 双向数据绑定(支持Array/Object类型)

二、核心功能实现

1. 模板结构解析

<div
  :class="[
    'qis-form__control',
    'qis-form__select'
  ]"
  @click.stop="showPicker = true; selectedValues = value.slice()"
>
  <input :value="currentValue" type="hidden" />
  <span class="qis-form__select__value">
    {{ firstSelected || placeholder }}
  </span>
  <van-tag type="primary" v-if="currentValue.length > 1">
    +{{ currentValue.length - 1 }}
  </van-tag>
  <i class="iconfont icon-arrow"></i>
</div>
<van-popup
  v-model="showPicker"
  round
  position="bottom"
  get-container="#app"
  :style="{ height: '50%' }"
>
  <van-search
    autofocus
    placeholder="请输入关键字"
    v-model.trim="curVal"
  />
  <van-picker
    :columns="computedColumns"
    :default-index="0"
    show-toolbar
    :visible-item-count="5"
    ref="picker"
    @confirm="onConfirm"
    @cancel="onCancel"
  >
    <template slot="option" slot-scope="item">
      <div class="custom-picker-item" :class="{ disabled: item[disabledKey] }" @click="toggleItem(item)">
        <span
          class="checkbox"
          :class="{ checked: selectedValues.includes(getValue(item)), disabled: item[disabledKey] }"
        ></span>
        <span class="option-text">{{ getLabel(item) }}</span>
      </div>
    </template>
  </van-picker>
</van-popup>

2. 核心逻辑说明

// 响应式props定义 
const props = defineProps({
  value: {
    type: Array,
    required: true,
    default: () => []
  },
  placeholder: {
    type: String,
    default: '请选择'
  },
  options: {
    type: Array,
    required: true,
    default: () => []
  },
  // 新增字段映射配置
  labelKey: {
    type: String,
    default: 'label'
  },
  valueKey: {
    type: String,
    default: 'value'
  },
  // 禁用字段配置
  disabledKey: {
    type: String,
    default: 'disabled'
  }
})

const toggleItem = item => {
  // 跳过禁用项
  if (item[props.disabledKey]) return

  const itemValue = getValue(item)
  const index = selectedValues.value.findIndex(
    v => getValue(v) === itemValue
  )
  if (index > -1) {
    selectedValues.value.splice(index, 1)
  } else {
    selectedValues.value.push(item)
  }
}

三、核心特性解析

1. 数据格式兼容性

// 字符串数组示例 
const options1 = ['选项A', '选项B', '选项C'] 
// 对象数组示例 
const options2 = [ { id: 1, name: '选项A', status: 'active' }, { id: 2, name: '选项B', status: 'disabled' } ] 
// 使用方式 
<SelectMultiple :options="options2" label-key="name" value-key="id" disabled-key="status === 'disabled'" /> 

2. 智能搜索过滤

// 支持模糊匹配的搜索逻辑 
const computedColumns = computed(() => {
  const filterVal = curVal.value.trim()
  if (!filterVal) return props.options
  
  return props.options.filter(item => {
    const label = getLabel(item)
    return label.includes(filterVal)
  })
})

3. 状态管理

// 双向绑定处理 
const currentValue = useVModel(props, 'value', emit)
const onConfirm = () => {
  const newValue = selectedValues.value.map(v => getValue(v))
  currentValue.value = newValue
  showPicker.value = false
}

四、组件使用示例

1. 基础用法

<template> 
    <SelectMultiple v-model:value="selected" :options="['苹果', '香蕉', '橘子']" /> 
</template> 

2. 对象数组用法

<template> 
    <SelectMultiple v-model:value="selectedFruits" :options="fruitOptions" label-key="name" value-key="id" disabled-key="isDisabled" /> 
</template> 
<script setup> 
const fruitOptions = [
  { id: 1, name: '苹果', isDisabled: false },
  { id: 2, name: '香蕉', isDisabled: true },
  { id: 3, name: '橘子', isDisabled: false }
] 
</script> 

五、效果展示

image.png

image.png

六、扩展与优化建议

1. 性能优化

  • computedColumns添加防抖处理(建议500ms)
  • 大数据量时增加虚拟滚动支持
  • 添加搜索结果缓存机制

2. 功能增强

  • 支持全选/反选操作
  • 添加最大选择数量限制
  • 支持远程搜索数据加载
  • 增加清除选项功能

3. 交互改进

  • 添加加载状态指示
  • 支持键盘导航操作
  • 增加选中计数器显示
  • 支持自定义选项渲染插槽

七、注意事项

  1. 数据格式一致性:确保传入的options与字段映射配置匹配
  2. 性能考量:超过1000条数据时建议启用虚拟滚动
  3. 类型处理:避免在value中直接修改对象引用
  4. 样式隔离:组件样式使用scoped,需确保主题一致性 该组件通过灵活的配置和良好的交互设计,实现了移动端友好的多选下拉体验。通过字段映射和禁用状态支持,可适应各种复杂业务场景,是移动端表单开发的优选组件方案。