概述:el-select选中整个item,组件本身可支持,但是会有很多坑,并且在多选和单选之前来回切换,也会有很多bug和需要注意的地方,如果是第一次遇到,会耗费很多时间,本文将一一解析,废话不多说,直接上代码!
需求描述:
- 单选只能选中一个值,多选可选中多个值
- 单选多选来回切换,需清空之前的值
- 查看弹框详情,需根据门票类型,进行多选多选的切换+值得回显
实际效果图展示:
接口数据结构
下拉选项optionList数据结构
提示:组件是单选还是单选,可通过multiple属性控制,为true多选,为false单选,在切换的时候,可以动态的赋值multiple为true和false,从而实现单选和多选的来回切换,但是本文不讲改方法,具体内容如下
源码解析
- 多选实现:加上multiple属性,单需要绑定value-key="itemMark",否则在选中某一个后,下了列表会被全选中
- 选择整个item: :value="item"
ini
<el-calendar v-model="calendarValue">
<template slot="dateCell" slot-scope="{date, data}">
</template>
</el-calendar>
bug解析
-
绑定: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
})
},
}
- 绑定:value="item",详情值回显后,无法进行多选
原因:多选后将数组进行组装成字符串拼接,直接等于了数组push返回值,而数组push后返回长度,所有一直是1 解决:从新声明一个数组变量,让这个变量取值push后的值
- 回显后,再次重新选择,却点不动
原因:是由于v-model绑定了对象,在选择后,数据存在多层嵌套,导致试图更新不及时
解决:在el-select的change事件里加上:this.$forceUpdate()
- 详情中,单选切换为多选,会报 Error in render: "TypeError: arr.some is not a function"
原因:是因为getDetail方法中,没有对多选和单选进行区分,在单选时接口之会返回一个字符串,无法将其处理成数组形式 解决: 通过条件判断,对数据进行单选和多选区分
- 单选详情不回显
原因:接口返回的是字符串,而组件需要number
解决:将字符串转换为number类型
- 打开弹框后,多选或单选显示object对象,如图
原因:是因为多选和单选,在弹框打开初始化的时候,没有正确通过类型回显,此时返回的是多选数据,而选择框还是单选的选择框
解决: 通过条件判断,对数据进行单选和多选区分
- 选择框显示id,不显示名字
原因:是v-model绑定的id,和下拉列表的id对应不上,导致无法匹配上对应的名字
解决:将下拉框列表的数据和v-model的数据一一对应
- 通过v-if在多选和单选切换时对其影藏,如果触发了必填校验,即使选了值,也一直触发必填校验,无法提交,但是在提交时,将下拉框绑定的值打印,是有值的
原因: 1. 组件在来回切换,虽然通过v-if对其进行了DOM元素的销毁,但是组件并不能及时更新,导致校验提示还存在,此时在切换的时候,可以手动清除校验提示
-
必填校验依然存在,是因为虽然DOM元素上的选择框销毁了,但是V-model绑定值得校验必填依旧为true,可以在切换得时候,动态将绑定值的required设置为false
-
也可以通过样式的形式取控制,模拟表单的必填规则及样式,在触发必填校验的时候,给表单加一个必填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>