自定义组件(移动端下拉多选)中使用 v-model


一、Vue 3 示例

v-model 的本质

v-model 默认绑定 modelValue 属性并监听 update:modelValue 事件。

MultipleSelect.vue

html 复制代码
<template>
  <van-dropdown-menu ref="menuRef">
    <van-dropdown-item :title="placeholder" ref="itemRef">
      <van-cell v-for="item in options" :key="item.value" :title="item.label" center @click="handleClick(item)">
        <template #right-icon>
          <van-icon v-show="modelValue.includes(item.value)" name="success" color="rgb(0, 122, 255)" />
        </template>
      </van-cell>
    </van-dropdown-item>
  </van-dropdown-menu>
</template>
<script setup>
const props = defineProps({
  modelValue: {
    type: Array,
    default: () => []
  },
  placeholder: {
    type: String,
    default: '请选择'
  },
  options: {
    type: Array,
    default: () => []
  }
})

const emit = defineEmits(['update:modelValue'])

const handleClick = (val) => {
  const selected = [...props.modelValue]
  if (selected.includes(val.value)) {
    selected.splice(selected.indexOf(val.value), 1)
  } else {
    selected.push(val.value)
  }
  emit('update:modelValue', selected)
  console.log(selected)
}
</script>

使用

html 复制代码
<template>
  <div class="home-page">
    <MultipleSelect v-model="selected" placeholder="请选择" :options="options" />
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'
import MultipleSelect from '@/components/MultipleSelect.vue'

const options = [
  {
    label: '北京',
    value: 1,
  },
  {
    label: '上海',
    value: 2,
  },
  {
    label: '广州',
    value: 3,
  },
  {
    label: '深圳',
    value: 4,
  },
]

const selected = ref([])

watch(selected, (newVal) => {
  console.log('选中的值', newVal)
})

</script>

<style lang="scss" scoped>
.home-page {}
</style>

组件上的 v-model 也可以接受一个参数:

html 复制代码
<MultipleSelect v-model:value="selected" placeholder="请选择" :options="options" />
html 复制代码
<template>
  <van-dropdown-menu ref="menuRef">
    <van-dropdown-item :title="placeholder" ref="itemRef">
      <van-cell v-for="item in options" :key="item.value" :title="item.label" center @click="handleClick(item)">
        <template #right-icon>
          <van-icon v-show="value.includes(item.value)" name="success" color="rgb(0, 122, 255)" />
        </template>
      </van-cell>
    </van-dropdown-item>
  </van-dropdown-menu>
</template>
<script setup>
const props = defineProps({
  value: {
    type: Array,
    default: () => []
  },
  placeholder: {
    type: String,
    default: '请选择'
  },
  options: {
    type: Array,
    default: () => []
  }
})

const emit = defineEmits(['update:value'])

const handleClick = (val) => {
  const selected = [...props.value]
  if (selected.includes(val.value)) {
    selected.splice(selected.indexOf(val.value), 1)
  } else {
    selected.push(val.value)
  }
  emit('update:value', selected)
  console.log(selected)
}
</script>

二、vue3.4+示例

html 复制代码
<template>
  <van-dropdown-menu ref="menuRef">
    <van-dropdown-item :title="placeholder" ref="itemRef">
      <van-cell v-for="item in options" :key="item.value" :title="item.label" center @click="handleClick(item)">
        <template #right-icon>
          <van-icon v-show="model.includes(item.value)" name="success" color="rgb(0, 122, 255)" />
        </template>
      </van-cell>
    </van-dropdown-item>
  </van-dropdown-menu>
</template>
<script setup>
/**
 * defineModel 是一个便利宏。编译器将其展开为以下内容:
    一个名为 modelValue 的 prop,本地 ref 的值与其同步;
    一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。
 */
const model = defineModel({ default: () => [] })
const props = defineProps({
  placeholder: {
    type: String,
    default: '请选择'
  },
  options: {
    type: Array,
    default: () => []
  }
})

const handleClick = (val) => {
  const selected = [...model.value]
  if (selected.includes(val.value)) {
    selected.splice(selected.indexOf(val.value), 1)
  } else {
    selected.push(val.value)
  }
  // 直接赋值给 model.value
  model.value = selected
  console.log(selected)
}
</script>

<style lang="scss" scoped></style>
html 复制代码
<MultipleSelect v-model="selected" placeholder="请选择" :options="options" />

组件上的 v-model 也可以接受一个参数:

html 复制代码
<MultipleSelect v-model:value="selected" placeholder="请选择" :options="options" />
html 复制代码
<template>
  <van-dropdown-menu ref="menuRef">
    <van-dropdown-item :title="placeholder" ref="itemRef">
      <van-cell v-for="item in options" :key="item.value" :title="item.label" center @click="handleClick(item)">
        <template #right-icon>
          <van-icon v-show="value.includes(item.value)" name="success" color="rgb(0, 122, 255)" />
        </template>
      </van-cell>
    </van-dropdown-item>
  </van-dropdown-menu>
</template>
<script setup>
/**
 * defineModel 是一个便利宏。编译器将其展开为以下内容:
    一个名为 modelValue 的 prop,本地 ref 的值与其同步;
    一个名为 update:modelValue 的事件,当本地 ref 的值发生变更时触发。
 */
const value = defineModel({ default: () => [] })
const props = defineProps({
  placeholder: {
    type: String,
    default: '请选择'
  },
  options: {
    type: Array,
    default: () => []
  }
})

const handleClick = (val) => {
  const selected = [...value.value]
  if (selected.includes(val.value)) {
    selected.splice(selected.indexOf(val.value), 1)
  } else {
    selected.push(val.value)
  }
  // 直接赋值给 value.value
  value.value = selected
  console.log(selected)
}
</script>

<style lang="scss" scoped></style>

三、vue2示例

html 复制代码
<template>
  <van-dropdown-menu ref="menuRef">
    <van-dropdown-item :title="placeholder" ref="itemRef">
      <van-cell v-for="item in options" :key="item.value" :title="item.label" center @click="handleClick(item)">
        <template #right-icon>
          <van-icon v-show="value.includes(item.value)" name="success" color="rgb(0, 122, 255)" />
        </template>
      </van-cell>
    </van-dropdown-item>
  </van-dropdown-menu>
</template>

<script>
export default {
  props: {
    value: {
      type: Array,
      default: () => []
    },
    placeholder: {
      type: String,
      default: '请选择'
    },
    options: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {

    }
  },

  methods: {
    handleClick(val) {
      const selected = [...this.value]
      if (selected.includes(val.value)) {
        selected.splice(selected.indexOf(val.value), 1)
      } else {
        selected.push(val.value)
      }
      this.$emit('update:value', selected)
      console.log(selected)
    }
  }
}

</script>
html 复制代码
<template>
  <div class="home-page">
    <MultipleSelect v-model:value="selected" placeholder="请选择" :options="options" />
  </div>
</template>

<script>
import MultipleSelect from '@/components/MultipleSelect.vue'
export default {
  components: {
    MultipleSelect
  },
  data() {
    return {
      options: [
        {
          label: '北京',
          value: 1,
        },
        {
          label: '上海',
          value: 2,
        },
        {
          label: '广州',
          value: 3,
        },
        {
          label: '深圳',
          value: 4,
        },
      ],
      selected: []
    }
  },

  watch: {
    selected(newVal) {
      console.log('选中的值', newVal)
    }
  }

}

</script>

<style lang="scss" scoped>
.home-page {
  width: 100%;
  height: 100%;
}
</style>

下拉多选2

html 复制代码
<template>
  <van-field readonly clickable name="picker" v-model="fieldValue" :label="label" :placeholder="placeholder"
    input-align="right" is-link @click="popupVisible = true" />

  <!-- <van-popup v-model:show="popupVisible" position="bottom" :style="{ height: '30%' }">
    <van-cell v-for="item in options" :key="item.value" :title="item.label" center @click="handleClick(item)">
      <template #right-icon>
        <van-icon v-show="modelValue.includes(item.value)" name="success" color="rgb(0, 122, 255)" />
      </template>
</van-cell>
</van-popup> -->

  <van-popup v-model:show="popupVisible" round position="bottom" closeable class="custom-popup">
    <van-cell-group class="list">
      <van-cell v-for="item in options" :key="item.value" :title="item.label" center @click="handleClick(item)">
        <template #right-icon>
          <van-icon v-show="modelValue.includes(item.value)" name="success" color="rgb(0, 122, 255)" />
        </template>
      </van-cell>
    </van-cell-group>
  </van-popup>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
  modelValue: {
    type: Array,
    default: () => []
  },
  label: {
    type: String,
    default: ''
  },
  placeholder: {
    type: String,
    default: '请选择'
  },
  options: {
    type: Array,
    default: () => []
  }
})

const emit = defineEmits(['update:modelValue'])

const fieldValue = ref('')
const popupVisible = ref(false)

const handleClick = (val) => {
  const selected = [...props.modelValue]
  if (selected.includes(val.value)) {
    selected.splice(selected.indexOf(val.value), 1)
  } else {
    selected.push(val.value)
  }
  emit('update:modelValue', selected)
  let selectedText = []
  props.options.forEach((item) => {
    if (selected.includes(item.value)) {
      selectedText.push(item.label)
    }
  })
  fieldValue.value = selectedText.join('、')

  console.log(selected)
}
</script>

<style>
.custom-popup {
  padding: 52px 0 16px 0;

  .list {
    max-height: 70vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
}
</style>
html 复制代码
<MultipleSelect2 v-model="selected" placeholder="请选择" :options="options" label="所属城市" />
相关推荐
Jutick1 分钟前
揭秘低延迟:WebSocket 实时行情如何拯救你的量化策略?——Python 生产级实现
前端
~欲买桂花同载酒~2 分钟前
项目优化-vite打包优化
前端·javascript·vue.js
林夕sama4 分钟前
多线程基础(五)
java·开发语言·前端
我叫蒙奇7 分钟前
husky 和 lint-staged
前端
kyriewen9 分钟前
JavaScript 继承的七种姿势:从“原型链”到“class”的进化史
前端·javascript·ecmascript 6
穷鱼子酱11 分钟前
ElSelect二次封装组件-实现分页(下拉加载、缓存)、回显
前端
科科睡不着12 分钟前
拆解iOS实况照片📷 - 附React web实现
前端
前端老兵AI13 分钟前
Electron 桌面应用开发入门:前端工程师的跨平台利器
前端·electron
胖子不胖15 分钟前
浅析cubic-bezier
前端
reasonsummer19 分钟前
【办公类-133-02】20260319_学区化展示PPT_02_python(图片合并文件夹、提取同名图片归类文件夹、图片编号、图片GIF)
前端·数据库·powerpoint