自定义组件(移动端下拉多选)中使用 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="所属城市" />
相关推荐
GreenTea1 小时前
一文搞懂Harness Engineering与Meta-Harness
前端·人工智能·后端
killerbasd3 小时前
牧苏苏传 我不装了 4/7
前端·javascript·vue.js
吴声子夜歌3 小时前
ES6——二进制数组详解
前端·ecmascript·es6
码事漫谈3 小时前
手把手带你部署本地模型,让你Token自由(小白专属)
前端·后端
ZC跨境爬虫3 小时前
【爬虫实战对比】Requests vs Scrapy 笔趣阁小说爬虫,从单线程到高效并发的全方位升级
前端·爬虫·scrapy·html
爱上好庆祝3 小时前
svg图片
前端·css·学习·html·css3
橘子编程4 小时前
JavaScript与TypeScript终极指南
javascript·ubuntu·typescript
王夏奇4 小时前
python中的__all__ 具体用法
java·前端·python
叫我一声阿雷吧4 小时前
JS 入门通关手册(45):浏览器渲染原理与重绘重排(性能优化核心,面试必考
javascript·前端面试·前端性能优化·浏览器渲染·浏览器渲染原理,重排重绘·reflow·repaint
大家的林语冰4 小时前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js