自定义组件(移动端下拉多选)中使用 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="所属城市" />
相关推荐
子兮曰6 小时前
把 649 种文件格式塞进一个网页里:一个「文件不出浏览器」的预览器
前端·javascript·后端
paopaokaka_luck9 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
sm_926787059 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
IMPYLH9 小时前
HTML 的 <video> 元素
前端·html
溪语流沙10 小时前
【每天一个CSS | Day19】流光文字与一笔写成的手写签名
前端·javascript·css
逐光者93310 小时前
STM32——SPI 屏 · Flash · 高速
java·前端·stm32
To_OC10 小时前
同样是让 AI 写全栈,为什么别人一句话就够了
前端·前端框架·next.js
CDwenhuohuo11 小时前
electron pc项目打包成桌面端
前端·javascript·electron
前端大斗师11 小时前
「大于 1000」把 1000 元那单也算进去了:我在 Vue3 订单页对了 10 句话
前端·人工智能·typescript·大模型·原力计划
Jmyd012311 小时前
用 Web VR 引擎搭建 3D 虚拟展厅:从素材上云到多端实时渲染的完整数据链路
前端·3d·vr·三维数字化