iview Select 設(shè)置可多選時(shí),v-model的變量對應(yīng)的是數(shù)組,但是該數(shù)組的元素不能是對象形式耸棒,只能是數(shù)字或字符串等類型;
本篇正是為了解決這一問題报辱,探討幾種方案榆纽。
iview Option源碼
<template>
<li
:class="classes"
@click.stop="select"
@mousedown.prevent
><slot>{{ showLabel }}</slot></li>
</template>
<script>
import Emitter from '../../mixins/emitter';
import { findComponentUpward } from '../../utils/assist';
const prefixCls = 'ivu-select-item';
export default {
name: 'option-ex',
componentName: 'select-item',
mixins: [ Emitter ],
props: {
value: {
type: [String, Number],
required: true
},
...
</script>
從源碼中可以看出value被限定類型了,無法傳入Object類型
解決辦法
- 封裝Select
<template>
<Select
v-model="newValue"
@on-change="handleChange">
<Option>
{{}}
</Option>
</Select>
</template>
<script>
export default {
methods: {
handleChange(data){
let arr = []
// 對data進(jìn)行攔截處理捏肢,輸出結(jié)果不再是iview Select的格式奈籽,根據(jù)key獲取到原數(shù)據(jù),傳入input鸵赫,change事件中
this.$emit('input',arr);
this.$emit('change',arr);
}
}
</script>
- 重寫Option(不建議)
復(fù)制Option源碼到重寫的組件中衣屏,同時(shí)把value的類型增加一個(gè)Object即可;
麻煩的地方在于兩點(diǎn):
a. js代碼中Emitter,findComponentUpward的引入路徑改為iview源碼路徑
b.componentName必須保持不變辩棒,因?yàn)楦附M件Select中會(huì)據(jù)此判斷
c.無法呈現(xiàn)勾選狀態(tài)狼忱,這是因?yàn)楦附M件Select的processOption方法中是用includes判斷是否選中膨疏,includes不能用于元素是對象的數(shù)組:
// iview select.vue
processOption(option, values, isFocused){
if (!option.componentOptions) return option;
...
const isSelected = values.includes(optionValue);
const propsData = {
...option.componentOptions.propsData,
selected: isSelected,
...
};
return {
...option,
componentOptions: {
...option.componentOptions,
propsData: propsData
}
};
},
- HOC(高階)方式
支持iview Select OptionGroup
option上需要傳入data,存儲原數(shù)據(jù)
select-x上要傳入idKey钻弄,設(shè)置唯一標(biāo)識符
基于第二佃却、三點(diǎn),組件實(shí)現(xiàn)了回顯
iview Select功能保持不變
效果
https://codesandbox.io/embed/vue-template-rybgu?fontsize=14&hidenavigation=1&theme=dark
selectX.js在iview Select基礎(chǔ)上進(jìn)行封裝
// selectX.js
/*
* iview Select 設(shè)置可多選時(shí)窘俺,v-model的變量對應(yīng)的是數(shù)組饲帅,但是該數(shù)組的元素不能是對象形式,只能是數(shù)字或字符串等類型瘤泪;
*/
export default {
props: {
value: {
type: [String, Number, Array],
default: ''
},
idKey: {
type: String,
default: 'id'
},
},
name: 'select-x',
render(h) {
const uid = this.idKey || 'id'
const slots = Object.keys(this.$slots)
.reduce((arr, key) => arr.concat(this.$slots[key]), [])
.map(vnode => {
vnode.context = this._self
return vnode
})
const optionGroup = slots.filter(slot => slot.componentOptions && slot.componentOptions.tag === 'OptionGroup').map(slot => slot.componentOptions.children).flat()
const optionNoneGroup = slots.filter(slot => slot.componentOptions && slot.componentOptions.tag === 'Option')
const allOldOptions = this.allOldOptions || []
const allOptions = optionGroup.concat(optionNoneGroup).map(child => child.data.attrs.data).filter(Boolean)
const allOptionIds = allOptions.map(option => option[uid]).filter(Boolean)
this.allOldOptions = (allOptions.push(...allOldOptions.filter(option => !allOptionIds.includes(option.id))) && allOptions)
// 特殊情況處理
const isArrayFlag = Array.isArray(this.value)
let ArrayContainsObject = isArrayFlag ? this.value.some(v => Object.prototype.toString.apply(v) === '[object Object]') : false
let props = {
...this.$props,
...this.$attrs,
value: ArrayContainsObject ? this.value.map(v => v[uid]) : this.value // 關(guān)鍵 支持字符串或數(shù)字
}
const isMultiple = ['', true].includes(this.$attrs.multiple) || !!this.$attrs.multiple
/*
* 如果v-model傳入值不是數(shù)組灶泵,但組件已被設(shè)置成多選的話
*/
if (isMultiple && !isArrayFlag) {
let values = [this.value]
ArrayContainsObject = isArrayFlag ? values.some(v => Object.prototype.toString.apply(v) === '[object Object]') : false
props = {
...this.$props,
...this.$attrs,
value: ArrayContainsObject ? values.map(v => v[uid]) : values // 關(guān)鍵 支持字符串或數(shù)字
}
}
/*
* 如果v-model傳入值為數(shù)組,則無論是否設(shè)置多選对途,都強(qiáng)制設(shè)置成多選
*/
if (isArrayFlag) {
this.$attrs.multiple = props.multiple = true
}
const listeners = Object.fromEntries(Object.entries(this.$listeners).filter(e => !['input', 'on-change', 'on-open-change'].includes(e[0])))
return h('Select', {
on: {
...listeners,
'on-change': (params) => {
if (Array.isArray(params) && params.length) {
let res = []
ArrayContainsObject = isArrayFlag
? this.params.some(
v => Object.prototype.toString.apply(v) === "[object Object]"
)
: false
if (Object.prototype.toString.apply(params[0]) === '[object Object]') {
res = ArrayContainsObject
? params.map(param => allOptions.find(v => v[uid] === param.value)).filter(Boolean)
: params.map(param => param.value)
} else {
res = ArrayContainsObject
? params.map(param => allOptions.find(v => v[uid] === param)).filter(Boolean)
: params
}
this.$emit('on-change', res)
this.$emit('input', res)
} else {
this.$emit('on-change', params)
this.$emit('input', params)
}
},
'input': (params) => {
let res = Array.isArray(params)
? params.map(param => allOptions.find(v => v[uid] === param)).filter(Boolean)
: params
if (Array.isArray(params) && params.length) {
this.$emit('input', res)
this.$emit('on-change', res)
} else {
this.$emit('input', res)
this.$emit('on-change', res)
}
},
'on-open-change': (params) => {
this.$emit('on-open-change', params)
}
},
props,
// 透傳 scopedSlots
scopedSlots: this.$scopedSlots,
// attrs: this.$attrs
}, slots)
}
}
使用方法
<template>
<div id="app">
<select-x
id-key="uid"
v-model="modelData"
filterable
multiple
:loading="selectConfig.loading"
:loading-text="selectConfig.loadingText"
:not-found-text="selectConfig.notFoundText"
placeholder="請點(diǎn)擊此處"
@on-open-change="handleOpenChange"
>
<Option
v-for="opt in (list || modelData)"
:key="opt.uid"
:value="opt.uid"
:data="opt"
>{{opt.label}}</Option>
</select-x>
已選城市:
<ul>
<li v-for="(item, index) of modelData" :key="index">{{item.label}}</li>
</ul>
</div>
</template>
<script>
import selectX from "./components/selectX";
export default {
name: "App",
components: {
selectX
},
data() {
return {
modelData: [{ uid: 11, label: "西安" }, { uid: 12, label: "北京" }], // 回顯的值
list: null,
selectConfig: {
loading: false,
loadingText: "正在查詢中",
notFoundText: ""
}
};
},
methods: {
handleOpenChange(bl) {
// this.$Message.success('打開:', bl)
this.selectConfig = {
loading: true,
loadingText: "正在查詢",
notFoundText: ""
};
let self = this;
setTimeout(() => {
self.list = [
{ uid: 11, label: "西安" },
{ uid: 12, label: "北京" },
{ uid: 13, label: "南京" },
{ uid: 14, label: "洛陽" },
{ uid: 15, label: "武昌" }
];
console.log("---");
self.selectConfig = {
loading: false,
loadingText: "",
notFoundText: ""
};
}, 2000);
},
mounted() {
console.log("mounted!!!");
}
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>