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

相关推荐
吃杠碰小鸡23 分钟前
commitlint校验git提交信息
前端
天天进步201543 分钟前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
虾球xz1 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇1 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员1 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐1 小时前
前端图像处理(一)
前端
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒2 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪2 小时前
AJAX的基本使用
前端·javascript·ajax