iview中Select組件v-model不支持Object數(shù)組的問題

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>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赦邻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子实檀,更是在濱河造成了極大的恐慌惶洲,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膳犹,死亡現(xiàn)場離奇詭異湃鹊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)镣奋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門币呵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侨颈,你說我怎么就攤上這事余赢。” “怎么了哈垢?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵妻柒,是天一觀的道長。 經(jīng)常有香客問我耘分,道長举塔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任求泰,我火速辦了婚禮央渣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘渴频。我一直安慰自己芽丹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布卜朗。 她就那樣靜靜地躺著拔第,像睡著了一般咕村。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蚊俺,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天懈涛,我揣著相機(jī)與錄音,去河邊找鬼泳猬。 笑死批钠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的暂殖。 我是一名探鬼主播价匠,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼当纱,長吁一口氣:“原來是場噩夢啊……” “哼呛每!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坡氯,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤晨横,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后箫柳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體手形,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年悯恍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了库糠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涮毫,死狀恐怖瞬欧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情罢防,我是刑警寧澤艘虎,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站咒吐,受9級特大地震影響野建,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恬叹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一候生、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绽昼,春花似錦陶舞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唠粥。三九已至,卻和暖如春停做,著一層夾襖步出監(jiān)牢的瞬間晤愧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工蛉腌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留官份,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓烙丛,卻偏偏與公主長得像舅巷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子河咽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容