結合el-tree和el-transfer搞一個樹形穿梭框

最近遇到個需求,穿梭框中的內容是樹形結構的數據。查看elementUI的transfer組件是不支持樹形結構的數據,也就不能直接使用了。可是el-tree組件支持啊,那若是讓tree組件和transfer組件結合一下豈不完美🤔。javascript

準備

clone一份element的源碼,找到package/tansfer/src文件夾,裏面的vue文件就是咱們的目標。複製一份出來到本身的項目(同時複製mixins裏的emitter.jsmigrating.js),接下來就對它進行改造。html

|-- components
|   |-- mixins
|   |   |-- emitter.js
|   |   |-- migraing.js
|   |-- main.vue
|   |-- transfer-panel.vue
複製代碼

使用

使用以前先對main.vuetransfer-panel.vue兩個文件進行簡單的修改。刪除locale相關代碼。emitter.jsmigrating.js由於路徑變化須要從新引入。而後在其它組件中引入main.vue就可使用啦(和直接使用el-transfer是同樣的)。vue

<i-el-transfer v-model="value" :data="data"></i-el-transfer>

...

import iElTransfer from '../components/main'

export default {
    name: 'home',
    components: {
        iElTransfer
    },
    data () {
        return {
            data: [
                { key: 1, label: '備選項1', disabled: false },
                { key: 2, label: '備選項2', disabled: false }
                ...
            ]
        }
    }
    ...
}
複製代碼

image

改造

將原來的單層結構轉換成樹形結構,咱們有現成的el-tree使用,因此將原來顯示內容的區域換成el-tree組件看看。java

# transfer-panel.vue
...
<el-checkbox-group v-model="checked" v-show="!hasNoMatch && data.length > 0" :class="{ 'is-filterable': filterable }" class="el-transfer-panel__list">
    <el-checkbox class="el-transfer-panel__item" :label="item[keyProp]" :disabled="item[disabledProp]" :key="item[keyProp]" v-for="item in filteredData">
        <option-content :option="item"></option-content>
    </el-checkbox>
</el-checkbox-group>
...
複製代碼

先無論那麼多,直接替換成el-treenode

...
<div v-show="data.length > 0" :class="{ 'is-filterable': filterable }" class="el-transfer-panel__list">

    <el-tree ref="tree" :data="filteredData" :node-key="keyProp" show-checkbox default-expand-all :default-checked-keys="checked">
    </el-tree>
</div>
...
複製代碼

data變爲樹形結構,回過來看頁面git

image

是咱們像要的結果,這時候雖然變成了咱們想要的樹形結構,可是操做一下就能發現問題。繼續看代碼。github

sourceData() {
    return this.data.filter(item => this.value.indexOf(item[this.props.key]) === -1);
},

targetData() {
    if (this.targetOrder === 'original') {
        return this.data.filter(item => this.value.indexOf(item[this.props.key]) > -1);
    } else {
        return this.value.reduce((arr, cur) => {
            const val = this.dataObj[cur];
            if (val) {
                arr.push(val);
            }
        return arr;
      }, []);
    }
},
複製代碼

sourceDatatargetData分別用來展現左右兩側的數據,this.value是使用組件時經過v-model綁定的數據。這裏經過this.value篩選出左右兩邊的數據。那咱們就在這裏作一下修改:數組

sourceData () {
    const data = deepCopy(this.data)

    // 有children繼續篩選
    const filterData = (list) => {
        return list.filter(item => {
            if (item[this.props.children] && item[this.props.children].length > 0) {
                item[this.props.children] = filterData(item.children)
            }
            return this.value.indexOf(item[this.props.key]) === -1
        })
    }

    return filterData(data)
},

targetData () {
    const data = deepCopy(this.data)

    const filterData = (list) => {
        const res = []
        list.forEach(item => {
            if (this.value.indexOf(item[this.props.key]) > -1) {
                res.push(item)
            }

            if (item[this.props.children] && item[this.props.children].length > 0) {
                const result = filterData(item[this.props.children])
                if (result.length > 0) {
                    item[this.props.children] = result
                    const find = res.find(i => i.key === item.key)

                    // res 中沒有找到 item,需將 item 加入 res。
                    // 避免當選中子元素,父元素未選中的時候出現問題
                    if (find === undefined) {
                        res.push(item)
                    }
                }
            }
        })
        return res
    }

    return filterData(data)
},
複製代碼

我這裏但願添加到右邊的數據和左邊的數據保持一樣的結構,因此刪除了targetOrder選項。咱們試着改變v-model綁定的值來看下效果。bash

image

有那感受了,可是如今向左向右添加的功能仍是不行。下面就修改這部分的功能。找到addToRight函數,該函數篩選出用戶左側選中要添加到右側的數據和已經存在右側的數據,並經過$emit()修改this.value的值。函數

addToRight () {
    let currentValue = this.value.slice()
    const itemsToBeMoved = []
    const findSelectkey = (list) => {
        const key = this.props.key
        const itemsToBeMoved = []

        list.forEach(item => {
            const itemKey = item[key]
            if (
                this.leftChecked.indexOf(itemKey) > -1 &&
                this.value.indexOf(itemKey) === -1
            ) {
                itemsToBeMoved.push(itemKey)
            }

            if (item[this.props.children] && item[this.props.children].length > 0) {
                itemsToBeMoved.push(...findSelectkey(item[this.props.children]))
            }
        })
        return itemsToBeMoved
    }

    itemsToBeMoved.push(...findSelectkey(this.data))

    currentValue = currentValue.concat(itemsToBeMoved)

    this.$emit('input', currentValue)
    this.$emit('change', currentValue, 'right', this.leftChecked)
},

複製代碼

其實思路都是同樣的,若是存在children就繼續查找下層數據。接着修改addToLeft函數。

addToLeft () {
    let currentValue = this.value.slice()
    const list = this.rightChecked.concat(this.rightFalfChecked)
    list.forEach(item => {
        const index = currentValue.indexOf(item)
        if (index > -1) {
            currentValue.splice(index, 1)
        }
    })
    this.$emit('input', currentValue)
    this.$emit('change', currentValue, 'left', this.rightChecked)
},
複製代碼

這裏我想的是若是元素的子元素沒有被全選,那麼該元素不是選中狀態,因此要從右側刪除該元素的key。這裏新加了一個rightFalfChecked用來表示右側半選狀態的數據。

上面對main.vue文件的修改基本就算完了。咱們發現還須要獲取到rightCheckedleftCheckedrightFalfChecke的值。修改transfer-panel.vue文件來獲取它們。el-tree組件有個check事件,經過它咱們能夠獲取當前el-tree哪些被選中了。

check:當複選框被點擊的時候觸發;共兩個參數,依次爲:傳遞給 data 屬性的數組中該節點所對應的對象、樹目前的選中狀態對象,包含 checkedNodes、checkedKeys、halfCheckedNodes、halfCheckedKeys 四個屬性

<el-tree ref="tree" :data="filteredData"
    :node-key="keyProp"
    show-checkbox
    default-expand-all
    :default-checked-keys="checked"
    @check="handleCheck">
</el-tree>

...
watch: {
    checked (val, oldVal) {
        this.updateAllChecked()
        if (this.checkChangeByUser) {
            const movedKeys = val.concat(oldVal)
                .filter(v => val.indexOf(v) === -1 || oldVal.indexOf(v) === -1)
            this.$emit('checked-change', val, movedKeys, this.halfChecked)
        } else {
            this.$emit('checked-change', val)
            this.checkChangeByUser = true
        }
    }
}

...

handleCheck (cur, checkedInfo) {
    const { checkedKeys, halfCheckedKeys } = checkedInfo
    this.checked = checkedKeys
    this.halfChecked = halfCheckedKeys
}
複製代碼

總結

到這裏基本功能實現都已經完成了,可是還有一些細節問題,好比全選、篩選等,這裏就再也不描述。相比原來的transfer組件,這裏去掉了targetOrderformat屬性,其它的則作了保留或修改。但願這篇文章能幫到您,您能夠在下面的連接查看完整代碼。歡迎交流🎉️。

Github:連接

相關文章
相關標籤/搜索