vant组件封装

1.下拉多选1

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

MultipleSelect.vue

html 复制代码
<template>
  <van-dropdown-menu ref="menuRef">
    <van-dropdown-item :title="displayTitle" 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>
import { computed } from 'vue'

const props = defineProps({
  modelValue: {
    type: Array,
    default: () => []
  },
  placeholder: {
    type: String,
    default: '请选择'
  },
  options: {
    type: Array,
    default: () => []
  }
})

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

// 使用计算属性动态计算显示标题
const displayTitle = computed(() => {
  if (props.modelValue.length === 0) {
    return props.placeholder
  }
  const selectedLabels = props.options
    .filter(item => props.modelValue.includes(item.value))
    .map(item => item.label)
  return selectedLabels.join('、')
})

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)
}


</script>

2.下拉多选二

html 复制代码
<MultipleSelect2 v-model="selected" placeholder="请选择" :options="options" label="所属城市" />

MultipleSelect2.vue

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" 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>

3.带快捷选项的日期范围选择器

html 复制代码
<CustomDatePicker v-model="dateArr" :shortcuts="shortcuts" />

// 快捷选项
const shortcuts = ref([
  {
    text: '近一周',
    start: ['2025', '12', '02'],
    end: ['2025', '12', '08']
  },
  {
    text: '近一个月',
    start: ['2025', '11', '09'],
    end: ['2025', '12', '08']
  },
  {
    text: '近三个月',
    start: ['2025', '10', '09'],
    end: ['2025', '12', '08']
  },
])

const dateArr = ref([])

CustomDatePicker.vue

html 复制代码
<template>
  <van-field readonly clickable name="picker" v-model="fieldValue" label="日期范围" placeholder="请选择日期范围"
    input-align="right" is-link @click="showDatePicker = true" />

  <van-popup v-model:show="showDatePicker" round position="bottom" class="date-popup">
    <div class="popup-header">
      <span class="btn-cancel" @click="handleReset">重置</span>
      <span class="title">选择日期范围</span>
      <span class="btn-confirm" @click="handleConfirm">确定</span>
    </div>
    <div v-if="shortcuts?.length" class="tab-list">
      <div v-for="(item, index) in shortcuts" :key="index" class="tab-item" :class="{ active: activeTab === index }"
        @click="activeTanChange(index, item)">
        {{ item.text }}
      </div>
    </div>
    <div class="tab-list tab-list2">
      <div class="tab-item" :class="{ active: activeTab === 'start' }" @click="activeTanChange('start')">
        {{ startDateLabel }}
      </div>
      <div class="line"></div>
      <div class="tab-item" :class="{ active: activeTab === 'end' }" @click="activeTanChange('end')">
        {{ endDateLabel }}
      </div>
    </div>
    <van-date-picker v-show="activeTab === 'start'" v-model="startDate" title="选择日期" :min-date="minDate"
      :max-date="maxDate" :show-toolbar="false" />
    <van-date-picker v-show="activeTab === 'end'" v-model="endDate" title="选择日期" :min-date="minDate" :max-date="maxDate"
      :show-toolbar="false" />
  </van-popup>
</template>

<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
  modelValue: {
    type: Array,
    default: () => []
  },
  shortcuts: {
    type: Array,
    default: () => [],
  }
})

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

const showDatePicker = ref(false) // 是否显示日期筛选弹窗

const activeTab = ref(null) // 当前激活的日期筛选标签
// 开始日期标签
const startDateLabel = computed(() => {
  if (activeTab.value !== null) {
    return startDate.value.join('-')
  } else {
    return '开始日期'
  }
})
// 结束日期标签
const endDateLabel = computed(() => {
  if (activeTab.value !== null) {
    return endDate.value.join('-')
  } else {
    return '结束日期'
  }
})

const fieldValue = computed(() => {
  if (activeTab.value !== null) {
    return `${startDateLabel.value} 至 ${endDateLabel.value}`
  } else {
    return ''
  }
})

const startDate = ref(['2025', '12', '08']) // 开始日期
const endDate = ref(['2025', '12', '08']) // 结束日期
const minDate = ref(new Date(2020, 0, 1)) // 最小日期
const maxDate = ref(new Date()) // 最大日期

const activeTanChange = (index, item) => {
  activeTab.value = index
  if (index === 'start') {

  } else if (index === 'end') {

  } else {
    const { start, end } = item
    startDate.value = start
    endDate.value = end
  }
}


// 切换日期
const handleConfirm = () => {
  console.log(fieldValue.value)
  if (activeTab.value !== null) {
    emit('update:modelValue', fieldValue.value.split(' 至 '))
  } else {
    emit('update:modelValue', ['', ''])
  }
  showDatePicker.value = false
}

// 重置
const handleReset = () => {
  activeTab.value = null
  startDate.value = ['2025', '12', '08']
  endDate.value = ['2025', '12', '08']
}
</script>

<style lang="scss" scoped>
.date-popup {
  padding-bottom: 20px;

  .popup-header {
    height: 48px;
    padding: 12px 16px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 24px;

    .btn-cancel,
    .btn-confirm {
      color: rgba(0, 0, 0, 0.85);
      font-size: 14px;
      font-weight: 400;
      line-height: 20px;
    }

    .btn-confirm {
      color: rgba(23, 108, 255, 1);
    }

    .title {
      color: rgba(0, 0, 0, 0.95);
      font-size: 18px;
      font-weight: 500;
      line-height: 24px;
    }
  }

  .tab-list,
  .tab-list2 {
    padding: 0 16px;
    display: grid;
    grid-template-columns: repeat(auto-fill, 106px);
    gap: 12px;
    margin-bottom: 12px;

    .tab-item {
      width: 106px;
      height: 32px;
      display: flex;
      justify-content: center;
      align-items: center;
      border: 1px solid rgba(0, 0, 0, 0.12);
      border-radius: 4px;
      font-size: 14px;
      font-weight: 400;
      color: rgba(0, 0, 0, 0.85);

      &.active {
        border: 1px solid rgba(32, 128, 247, 1);
        color: rgba(32, 128, 247, 1);
      }
    }


  }

  .tab-list2 {
    display: flex;
    justify-content: space-between;
    align-items: center;

    .tab-item {
      width: 156px;
    }

    .line {
      width: 8px;
      height: 1px;
      background: rgba(0, 0, 0, 1);
    }
  }
}
</style>
相关推荐
cz追天之路5 小时前
华为机考--- 字符串最后一个单词的长度
javascript·css·华为·less
Light606 小时前
CSS逻辑革命:原生if()函数如何重塑我们的样式编写思维
前端·css·响应式设计·组件化开发·css if函数·声明式ui·现代css
蜡笔小嘟6 小时前
宝塔安装dify,更新最新版本--代码版
前端·ai编程·dify
Irene19916 小时前
Vue:useSlots 和 useAttrs 深度解析
vue.js·useslots·useattrs
ModyQyW7 小时前
HBuilderX 4.87 无法正常读取 macOS 环境配置的解决方案
前端·uni-app
bitbitDown7 小时前
我的2025年终总结
前端
五颜六色的黑7 小时前
vue3+elementPlus实现循环列表内容超出时展开收起功能
前端·javascript·vue.js
wscats8 小时前
Markdown 编辑器技术调研
前端·人工智能·markdown
EnoYao8 小时前
Markdown 编辑器技术调研
前端·javascript·人工智能
JIngJaneIL8 小时前
基于java+ vue医院管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot