vue el-select 如何选中整个item,实现多选,单选来回切换

概述:el-select选中整个item,组件本身可支持,但是会有很多坑,并且在多选和单选之前来回切换,也会有很多bug和需要注意的地方,如果是第一次遇到,会耗费很多时间,本文将一一解析,废话不多说,直接上代码!

需求描述:

  1. 单选只能选中一个值,多选可选中多个值
  2. 单选多选来回切换,需清空之前的值
  3. 查看弹框详情,需根据门票类型,进行多选多选的切换+值得回显

实际效果图展示:

接口数据结构

下拉选项optionList数据结构

提示:组件是单选还是单选,可通过multiple属性控制,为true多选,为false单选,在切换的时候,可以动态的赋值multiple为true和false,从而实现单选和多选的来回切换,但是本文不讲改方法,具体内容如下

源码解析

  1. 多选实现:加上multiple属性,单需要绑定value-key="itemMark",否则在选中某一个后,下了列表会被全选中
  2. 选择整个item: :value="item"
ini 复制代码
<el-calendar v-model="calendarValue">
    <template slot="dateCell" slot-scope="{date, data}">
    </template>
</el-calendar>

bug解析

  1. 绑定:value="item"后,详情值不回显

    原因:v-model绑定的接口数据结构,是以逗号拼接的字符串,和本身需要的数据结构不匹配

    解决:将v-model绑定的数据结构进行处理成对象的格式,保证里面有el-option需要的id,name,value等属性

ini 复制代码
<el-select multiple value-key="itemMark" v-model="formData.multipleItem">
    <el-option
      v-for="item in optionList"
      :key="item.itemMark"
      :label="item.itemName"
      :value="item">
    </el-option>
  </el-select>
  
  created() {
     getDetail() {
        // 获取接口详情数据
        detailTicketInfo(this.rowData.id).then(response => {
          // 将接口的id集合转换为以逗号拼接的数组
          let markValue=response.itemId.split(',')
          var detialArr = []
          // 将接口的数据进行清洗,组装成v-model需要的数据结构
          this.optionList.forEach(val=>{
            // this.optionList的数据为整个下拉选项的数据,markValue为详情接口,el-select返回已选的ID集合
            // 将markValue中的id和this.optionList中的id进行比对,如果相等,则将该数据添加到detialArr中
          if(markValue.some((item)=>item==val.id)){
              detialArr.push(val)    
            }    
          })
          // 比对完成后,将detialArr赋值给el-select的v-model,此时v-model绑定的数据结构,
          // 就和this.optionList的数据结构保持一致,就可实现详情回显
          response.multipleItem = detialArr
        })
      },
  }
  1. 绑定:value="item",详情值回显后,无法进行多选

原因:多选后将数组进行组装成字符串拼接,直接等于了数组push返回值,而数组push后返回长度,所有一直是1 解决:从新声明一个数组变量,让这个变量取值push后的值

  1. 回显后,再次重新选择,却点不动

原因:是由于v-model绑定了对象,在选择后,数据存在多层嵌套,导致试图更新不及时

解决:在el-select的change事件里加上:this.$forceUpdate()

  1. 详情中,单选切换为多选,会报 Error in render: "TypeError: arr.some is not a function"

原因:是因为getDetail方法中,没有对多选和单选进行区分,在单选时接口之会返回一个字符串,无法将其处理成数组形式 解决: 通过条件判断,对数据进行单选和多选区分

  1. 单选详情不回显

原因:接口返回的是字符串,而组件需要number

解决:将字符串转换为number类型

  1. 打开弹框后,多选或单选显示object对象,如图

原因:是因为多选和单选,在弹框打开初始化的时候,没有正确通过类型回显,此时返回的是多选数据,而选择框还是单选的选择框

解决: 通过条件判断,对数据进行单选和多选区分

  1. 选择框显示id,不显示名字

原因:是v-model绑定的id,和下拉列表的id对应不上,导致无法匹配上对应的名字

解决:将下拉框列表的数据和v-model的数据一一对应

  1. 通过v-if在多选和单选切换时对其影藏,如果触发了必填校验,即使选了值,也一直触发必填校验,无法提交,但是在提交时,将下拉框绑定的值打印,是有值的

原因: 1. 组件在来回切换,虽然通过v-if对其进行了DOM元素的销毁,但是组件并不能及时更新,导致校验提示还存在,此时在切换的时候,可以手动清除校验提示

  1. 必填校验依然存在,是因为虽然DOM元素上的选择框销毁了,但是V-model绑定值得校验必填依旧为true,可以在切换得时候,动态将绑定值的required设置为false

  2. 也可以通过样式的形式取控制,模拟表单的必填规则及样式,在触发必填校验的时候,给表单加一个必填class,通过样式取控制,代码如下:

ini 复制代码
<div class="valiteContainer customeValite" v-if="canMultiple">
    <div class="lableFlex">
      <span class="symbol">*</span>
      <span class="lableTit">项目名称</span>
      <el-select popper-class="dialog-select" key="itemMark" filterable v-model="formData.customerName" placeholder="请选择项目名称" @change="getSelect" :disabled="disabled">
        <el-option
          v-for="item in optionList"
          :key="item.id"
          :label="item.itemName"
          :value="item.id">
        </el-option>
      </el-select>
    </div>
    <div v-show="CustomerNameFlag" class="customerErrorTip">请选择项目名称</div>
  </div>

完整代码如下:

ini 复制代码
<template>
  <el-dialog :title="title" :visible.sync="dialogVisible " width="500px" append-to-body :before-close="handleClose">
    <el-form ref="formData" :model="formData" :rules="rules" label-width="96px">
      <el-form-item label="门票类型">
        <el-radio-group v-model="formData.ticketType" @change="selectTicket">
          <el-radio
            :disabled="disabled"
            v-for="item in options"
            :key="item.value"
            :label="item.value">
            {{ item.label }}
          </el-radio>
        </el-radio-group>
      </el-form-item>
      <div class="valiteContainer customeValite" v-if="canMultiple">
        <div class="lableFlex">
          <span class="symbol">*</span>
          <span class="lableTit">项目名称</span>
          <el-select popper-class="dialog-select" key="itemMark" filterable v-model="formData.customerName" placeholder="请选择项目名称" @change="getSelect" :disabled="disabled">
            <el-option
              v-for="item in optionList"
              :key="item.id"
              :label="item.itemName"
              :value="item.id">
            </el-option>
          </el-select>
        </div>
        <div v-show="CustomerNameFlag" class="customerErrorTip">请选择项目名称</div>
      </div>

      <div class="valiteContainer customeValite" v-else>
        <div class="lableFlex">
          <span class="symbol">*</span>
          <span class="lableTit">项目名称</span>
          <el-select filterable popper-class="dialog-select" multiple value-key="itemMark" v-model="formData.multipleItem" placeholder="请选择项目名称" @change="getSelect" :disabled="disabled">
            <el-option
              v-for="item in optionList"
              :key="item.itemMark"
              :label="item.itemName"
              :value="item">
            </el-option>
          </el-select>
        </div>
        <div v-show="MultipleItemFlag" class="customerErrorTip">{{ multipleErrorFlag ? '套票必须选择2个以上的项目' : '请选择项目名称' }}</div>
      </div>

      <el-form-item label="门票名称" prop="ticketName">
        <el-input v-model="formData.ticketName" placeholder="请输入项目名称" maxlength="64" :disabled="disabled" />
      </el-form-item>
      <el-form-item label="最大购买数" prop="buyMax">
        <el-input-number v-model="formData.buyMax" :min="1" :max="999" :disabled="disabled"></el-input-number>
      </el-form-item>
      <el-form-item label="最小购买数" prop="buyMin">
        <el-input-number v-model="formData.buyMin" :min="1" :max="formData.buyMax" :disabled="disabled"></el-input-number>
      </el-form-item>
      <!-- <el-form-item v-if="PackageFlag" label="套票名称" prop="PackageName">
        <el-select multiple v-model="formData.PackageName" placeholder="请选择套票" @change="getPackage" :disabled="disabled">
          <el-option
            v-for="item in PackageTickList"
            :key="item.id"
            :label="item.ticketName"
            :value="item.id">
          </el-option>
        </el-select>
      </el-form-item> -->
      <div class="valiteContainer">
        <div class="lableFlex">
          <span class="symbol">*</span>
          <span class="lableTit">门票图片</span>
        </div>
        <div :class="imgArr.length >= 4 ? 'uploadImg' : ''">
          <ImageUpload ref="ImageUpload" :value="formData.ticketImage" :unloadType="unloadType" :pageType="pageType" :limit="9999" :errorType="bannerImageErrorType" @imgUploadMultipleSuccess="imgUploadMultipleSuccess" @imgUploadMultipleRemove="handMultipleleRemove"></ImageUpload>
        </div>
      </div>
    </el-form>
    <div slot="footer" class="dialog-footer" v-show="pageType !== 'detail'">
      <el-button type="primary" @click="submitForm" :loading="loading">确 定</el-button>
      <el-button @click="handleClose">取 消</el-button>
    </div>
  </el-dialog>
</template>

<script>
  import { addTicketInfo, editTicketInfo, detailTicketInfo, itemList } from "@/api/scenice/attractions";
  export default {
    data() {
      return {
        canMultiple: true,
        MultipleItemFlag: false,
        CustomerNameFlag: false,
        title: '',
        imgArr: "",
        rowData: {},
        formData: {
          ticketImage: ''
        },
        pageType: "",
        optionList: [],
        multipleImgArr: [],
        loading: false,  
        disabled: false,
        multipleErrorFlag: false,
        unloadType: 'ticket',
        PackageFlag: false,
        PackageTickList: [],
        bannerImageErrorType: true,
        pickData: {
          selectableRange: '00:00:00 - 23:59:00'
        },
        dialogVisible: false,
        rules: {
          customerName: [
          { required: true, message: "项目名称不能为空", trigger: "blur" }
          ],
          multipleItem: [
            { required: true, message: "项目名称不能为空", trigger: "blur" }
          ],
          ticketName: [
            { required: true, message: "门票名称不能为空", trigger: "blur" }
          ],
          PackageName: [
            { required: true, message: "套票名称不能为空", trigger: "blur" }
          ]
        },
        options: [
          { value: 0, label: '单票' },
          { value: 1, label: '套票' },
        ],
        size: 10,
        page: 0,
        queryParams: {},
      }
    },
    methods: {
      show(type, data) {
        this.optionList = []
        this.disabled = false;
        this.pageType = type;
        this.rowData = data;
        this.loading = false
        this.PackageFlag = false;
        this.MultipleItemFlag = false;
        this.CustomerNameFlag = false;
        this.multipleErrorFlag = false
        switch(type) {
          case 'add':
            this.canMultiple = true
            this.title = '新增门票'
            this.formData = {
              buyMax: 999,
              buyMin: 1,
              ticketType: 0,
              ticketName: '',
              customerName: '',
              multipleItem: []
            }
            this.imgArr = []
          break
          case 'detail':
            this.disabled = true
            this.title = '门票详情'
          break
            case 'edit':
            this.title = '修改门票'
          break
        }
        this.getScenicNameList()
        this.dialogVisible = true;
      },
      getScenicNameList() {
        itemList(this.queryParams).then(response => {  
          this.optionList = response.data.filter(item => item.priceMode === 1 && item.status === 1)
          if (this.pageType !== 'add') {
            this.getDetail()
          }
        });
      },
      getDetail() {
        detailTicketInfo(this.rowData.id).then(response => {
          this.formData = response
          this.canMultiple = this.formData.ticketType === 0
          // >-1 和 ticketType = 1  为多选,否则为单选  
          if (response.itemId.indexOf(',') > -1 && this.formData.ticketType === 1) {
            this.canMultiple = false
            let markValue=response.itemId.split(',')
            var detialArr = []
            this.optionList.forEach(val=>{
            if(markValue.some((item)=>item==val.id)){
                detialArr.push(val)    
              }    
            })
            this.formData.multipleItem = detialArr
          } else {
            this.canMultiple = true
            this.formData.customerName = Number(response.itemId)
          }
        })
        this.$forceUpdate()
      },
      getPackage(data) {
        this.formData.ticketsId = data.join(',')
      },
       // 多图添加
       imgUploadMultipleSuccess(imgData) {
        this.multipleImgArr = imgData
      },
      // 多图移除
      handMultipleleRemove(imgData) {
        this.multipleImgArr = imgData
      },
      selectTicket(selectId) {
        this.canMultiple = selectId === 0
        if (this.canMultiple) {
          this.CustomerNameFlag = false
          this.MultipleItemFlag = false
          this.formData.customerName = ''
          this.formData.multipleItem = []
        } else {
          this.MultipleItemFlag = false
          this.CustomerNameFlag = false
          this.formData.customerName = ''
          this.formData.multipleItem = []
        }
        
      },
      getSelect(data) {
        let itemIdArr = []
        let itemNameArr = []
        // 单选
        if (this.formData.ticketType === 0) {
          this.optionList.forEach(item => {
            if (data === item.id) {
              this.formData.itemName = item.itemName
              this.formData.itemId = item.id.toString()
            }
          })
        // 多选
        } else {
          this.formData.multipleItem.forEach((item) => {
            itemIdArr.push(item.id)
            itemNameArr.push(item.itemName)
          })
          this.formData.itemId = itemIdArr.toString()
          this.formData.itemName = itemNameArr.toString()
        }
        this.$forceUpdate()
      },
      submitForm() {
        if (this.formData.ticketType === 0) {
          this.CustomerNameFlag = this.formData.customerName === ''
          if (this.CustomerNameFlag) return
        } else {
          if (this.formData.multipleItem.length === 0) {
            this.MultipleItemFlag = true
            return
          } else if (this.formData.multipleItem.length < 2) {
            this.MultipleItemFlag = true
            this.multipleErrorFlag = true
            return
          }
        }
        this.$refs["formData"].validate(valid => {
          if (valid) {
            let apiUrl = ''
            let messageTips = ''
            // 对表单数据进行深拷贝,防止customerName在删除后,界面表单显示为空
            let sumbitDataCopy = JSON.parse(JSON.stringify(this.formData))
            delete sumbitDataCopy.customerName
            delete sumbitDataCopy.multipleItem
            delete sumbitDataCopy.PackageName
            sumbitDataCopy.ticketImage = this.listToString(this.multipleImgArr)
            this.bannerImageErrorType = this.multipleImgArr.length > 0
            if (!this.bannerImageErrorType) return
            // this.loading = true
            if (this.pageType === 'add') {
              apiUrl = addTicketInfo
              messageTips = '新增成功'
            } else {
              apiUrl = editTicketInfo
              messageTips = '修改成功'
              sumbitDataCopy.id = this.rowData.id
            }
            apiUrl(sumbitDataCopy).then(response => {
              this.formData.ticketImage = ''
              this.$modal.msgSuccess(messageTips)
              this.dialogVisible = false
              this.loading = false;
              this.$emit('getInit')
              this.$refs.ImageUpload.clearFiles(); // 清除上传图片
            });
            this.loading = false;
          }
        })
      },
      handleClose() {
        this.formData = {}
        this.resetForm("formData")
        this.dialogVisible = false
        this.$refs.ImageUpload.clearFiles(); // 清除上传图片
      }
    }
  }
</script>

<style lang="scss" scoped>
 .customeValite ::v-deep .el-input__inner {
    width: 363px;
  }
  .customeValite ::v-deep .el-input {
    width: 363px;
  }
  .CustomerNameFlag ::v-deep .el-input__inner {
    border-color: #ff4949;
  }
  .uploadImg {
    height: 320px;
    overflow-y: scroll;
  }
  .lableFlex {
    margin-left: 19px;
  }
  .customeValite {
    display: block;
    .lableFlex {
      width: 100%;
      .lableTit {
        width: 80px;
      }
     
    }
    .customerErrorTip {
      position: absolute;
      font-size: 12px;
      color: #ff4949;
      margin: 4px 0 0 97px;
    }
  }
  .valiteContainer {
  display: flex;
  margin-bottom: 22px;
  .lableFlex {
    width: 78px;
    display: flex;
    margin-left: 3px;
    .symbol {
      color: #ff4949;
      margin-right: 4px;
    }
    .lableTit {
      width: 70px;
      font-weight: 700;
      font-size: 14px;
      color: #606266;
      padding-right: 12px;
    }
  }
}
</style>

END...

相关推荐
nppe62 分钟前
sequlize操作mysql小记
前端·后端
Moment11 分钟前
面试官:一个接口使用postman这些测试很快,但是页面加载很慢怎么回事 😤😤😤
前端·后端·面试
诗书画唱15 分钟前
【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
开发语言·前端·javascript
excel21 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子28 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构35 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep36 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss40 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风41 分钟前
html二次作业
前端·html
江城开朗的豌豆44 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈