自定义组件(移动端下拉多选)中使用 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="所属城市" />
相关推荐
你的电影很有趣4 小时前
lesson74:Vue条件渲染与列表优化:v-if/v-show深度对比及v-for key最佳实践
前端·javascript·vue.js
颜酱5 小时前
了解 Cypress 测试框架,给已有项目加上 Cypress 测试
前端·javascript·e2e
技术小丁5 小时前
uni-app 广告弹窗最佳实践:不扰民、可控制频次、含完整源码
前端·uni-app·1024程序员节
quan26315 小时前
日常开发20251022,传统HTML表格实现图片+视频+预览
前端·javascript·html·html列表实现图片+视频
陶甜也5 小时前
ThreeJS曲线动画:打造炫酷3D路径运动
前端·vue·threejs
楊无好5 小时前
react中的受控组件与非受控组件
前端·react.js
菠萝+冰5 小时前
react虚拟滚动
前端·javascript·react.js
落一落,掉一掉5 小时前
第十三周前端加密绕过
前端
前端初见6 小时前
快速上手TypeScript,TS速通
javascript·ubuntu·typescript