最近遇到個需求,穿梭框中的內容是樹形結構的數據。查看elementUI的transfer組件是不支持樹形結構的數據,也就不能直接使用了。可是el-tree組件支持啊,那若是讓tree組件和transfer組件結合一下豈不完美🤔。javascript
clone
一份element
的源碼,找到package/tansfer/src
文件夾,裏面的vue文件就是咱們的目標。複製一份出來到本身的項目(同時複製mixins
裏的emitter.js
和migrating.js
),接下來就對它進行改造。html
|-- components
| |-- mixins
| | |-- emitter.js
| | |-- migraing.js
| |-- main.vue
| |-- transfer-panel.vue
複製代碼
使用以前先對main.vue
,transfer-panel.vue
兩個文件進行簡單的修改。刪除locale
相關代碼。emitter.js
和migrating.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 }
...
]
}
}
...
}
複製代碼
將原來的單層結構轉換成樹形結構,咱們有現成的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
是咱們像要的結果,這時候雖然變成了咱們想要的樹形結構,可是操做一下就能發現問題。繼續看代碼。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;
}, []);
}
},
複製代碼
sourceData
和targetData
分別用來展現左右兩側的數據,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
有那感受了,可是如今向左向右添加的功能仍是不行。下面就修改這部分的功能。找到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
文件的修改基本就算完了。咱們發現還須要獲取到rightChecked
、leftChecked
、rightFalfChecke
的值。修改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
組件,這裏去掉了targetOrder
,format
屬性,其它的則作了保留或修改。但願這篇文章能幫到您,您能夠在下面的連接查看完整代碼。歡迎交流🎉️。
Github:連接