由于业务需求需要在穿梭框里使用树形结构,但是本身element里并不支持,于是参考了别的大佬发的文章作为思路及后续自己新增了一些处理功能。
目录
1.拷贝代码放到自己的项目目录中
2.改造el-transfer的源码
3.修改tree-transfer-panel.vue文件
4.修改index.vue文件
5.对tree-transfer-panel.vue文件功能进行完善
1.拷贝代码放到自己的项目目录中
在github上搜索element,拷贝packages/transfer/src中的vue文件,放到项目的components/tree-transfer目录。把main.vue改成index.vue方便操作。
在需要用到穿梭框的页面中拷贝transfer源码的组件,效果和使用el-transfer效果一致
<my-el-tree-transfer
v-model="treeChecked"
:data="treeData"
:titles="['请选择功能', '已选择功能']"
@change="transferChange"
target-order='push'>
</my-el-tree-transfer>
...
import myElTreeTransfer from '@/components/treeTransfer'
export default {
data(){
// 初始的数据源
functionSourceArray: [
{ key: 1, label: '功能1' },
{ key: 2, label: '功能2' },
{ key: 3, label: '功能3' },
{ key: 4, label: '功能4' },
],
treeChecked: [],
// 改造后数据源需要tree结构的数据
treeData: [
{
label: '一级 1', children: [
{ key: 2, label: '二级 1-1' },
{ key: 3, label: '二级 1-2' },
]
},
{
label: '一级 2', children: [
{ key: 5, label: '二级 2-1' },
{ key: 6, label: '二级 2-2' },
]
},
]
}
}
2.改造el-transfer的源码
index.vue的dom由三部分组成,左边的panel、中间的操作按钮,右边的panel,只需要修改左边的panel就可以,接下来拷贝一份transfer-panel.vue并改名为tree-transfer-panel.vue,修改index.vue的引用
// index.vue
<div class="el-transfer">
<tree-transfer-panel ...></tree-transfer-panel>
<div class="el-transfer__buttons">...</div>
<transfer-panel ...></transfer-panel>
</div>
...
import TreeTransferPanel from './tree-transfer-panel.vue';
3.修改tree-transfer-panel.vue文件
先将中间部分的checked-group替换为el-tree
// tree-transfer-panel.vue
<div :class="['el-transfer-panel__body', hasFooter ? 'is-with-footer' : '']">
<el-input class="el-transfer-panel__filter" v-model="query" size="small" :placeholder="placeholder"
@mouseenter.native="inputHover = true" @mouseleave.native="inputHover = false" v-if="filterable">
<i slot="prefix" :class="['el-input__icon', 'el-icon-' + inputIcon]" @click="clearQuery"></i>
</el-input>
<!-- 原先的el-checkbox-group -->
<!-- <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-tree -->
<el-tree ref="tree" :data="filteredData" node-key="key" default-expand-all show-checkbox :default-checked-keys="checked" @check="treeCheckChange"></el-tree>
<p class="el-transfer-panel__empty" v-show="hasNoMatch">{{ t('el.transfer.noMatch') }}</p>
<p class="el-transfer-panel__empty" v-show="data.length === 0 && !hasNoMatch">{{ t('el.transfer.noData') }}
</p>
</div>
添加el-tree
选择的event
@check
当复选框被点击的时候触发 共两个参数,依次为:传递给 data
属性的数组中该节点所对应的对象、树目前的选中状态对象,包含 checkedNodes、checkedKeys、halfCheckedNodes、halfCheckedKeys 四个属性
checkedKeys选中的key
halfCheckedKeys半选中的key
这时候看页面,结构已经出来了
treeCheckChange(cur,checkedInfo){
const {checkedKeys} = checkedInfo
this.checked = checkedKeys
}
4.修改index.vue文件
先把dom结构的第一个panel换为tree-transfer-panel.vue
<div class="el-transfer">
<tree-transfer-panel ...
左边的panel及右边的panel:data
数据都是计算属性,右边的结构没有改变,左边的结构变为了tree,所以只需要把左边的:data
改变即可。原来的数据结构是一维数组,现在的是二维数组,数据源需要根据数据结构做相应的改变。
这个sourceData
作用是每次把左边的数据添加到右边以后需要把添加的数据从左边去掉。
// index.vue
sourceData() {
let temp = []
const originalData = this.data
for(let i=0; i<originalData.length;i++){
temp.push({label: originalData[i].label})
temp[i].children = []
for(let j=0; j<originalData[i].children.length; j++){
let tempKey = originalData[i].children[j].key
if(this.value.indexOf(tempKey) === -1){
temp[i].children.push(originalData[i].children[j])
}
}
}
return temp
// return this.data.filter(item => this.value.indexOf(item[this.props.key]) === -1);
},
修改完以后每次往右边添加左边的该数据就会消失。但是此时右边并没有显示出左边添加过去的数据,此时需要修改addToRight
方法
addToRight() {
let currentValue = this.value.slice();
const itemsToBeMoved = [];
const key = this.props.key;
let dataTemp = []
// 该处是修改部分,数据结构变为二维以后,需要把第二层的数据放到数据源中
this.data.forEach(item=>{
dataTemp = dataTemp.concat(item.children)
})
dataTemp.forEach(item => {
const itemKey = item[key];
if (
this.leftChecked.indexOf(itemKey) > -1 &&
this.value.indexOf(itemKey) === -1
) {
itemsToBeMoved.push(itemKey);
}
});
currentValue = this.targetOrder === 'unshift'
? itemsToBeMoved.concat(currentValue)
: currentValue.concat(itemsToBeMoved);
this.$emit('input', currentValue);
this.$emit('change', currentValue, 'right', this.leftChecked);
},
右边panel展示的数据computed
由于用到的dataObj
,所以计算属性dataObj也要做相应调整
dataObj() {
const key = this.props.key;
let temp = []
// 获取第二层的数据
this.data.forEach(item=>{
temp = temp.concat(item.children)
})
return temp.reduce((o, cur) => (o[cur[key]] = cur) && o, {});
// return this.data.reduce((o, cur) => (o[cur[key]] = cur) && o, {});
},
这时候正常结构和功能都已经出来了,那么接下来我又新增了全选及当没有子节点的情况下禁止选中的功能。
5.对tree-transfer-panel.vue文件功能进行完善
对复选框点击事件进行修改,可以对树级选择器进行全选及取消操作
//tree-transfer-panel.vue
<p class="el-transfer-panel__header">
<el-checkbox v-model="allChecked" @change="handleAllCheckedChange" :indeterminate="isIndeterminate">
{{ title }}
<span>{{ checkedSummary }}</span>
</el-checkbox>
</p>
handleAllCheckedChange(isChecked) {
// this.checked = value
// ? this.checkableData.map(item => item[this.keyProp])
// : [];
const allKeys = this.filteredData.reduce((acc, node) => {
acc.push(node.key);
if (node.children) {
acc.push(...this.getAllKeys(node.children));
}
return acc;
}, []);
this.checked = isChecked ? allKeys : [];
this.$refs.tree.setCheckedKeys(this.checked);
},
getAllKeys(nodes) {
return nodes.reduce((acc, node) => {
acc.push(node.key);
if (node.children) {
acc.push(...this.getAllKeys(node.children));
}
return acc;
}, []);
},
对于没有子节点的父级进行禁止选中功能,通过监听树级结构data里的数据,来给父节点是否增加disabled禁止选中
<el-tree ref="tree" :data="filteredData" node-key="key" show-checkbox
:class="{ 'is-filterable': filterable }" class="el-transfer-panel__list "
:default-checked-keys="checked" @check="treeCheckChange">
</el-tree>
watch:{
filteredData(newVal) {
this.disableNodesWithoutChildren(newVal);
},
},
methods:{
disableNodesWithoutChildren(nodes) {
nodes.forEach((node) => {
if (node.children && node.children.length === 0) {
node.disabled = true; // 如果没有子节点,禁用该节点
} else if (node.children) {
// 如果有子节点,递归处理子节点
this.disableNodesWithoutChildren(node.children);
}
});
},
}
这样所有功能都已经完成。在把树级结构的数据替换成自己的后端接口返回的数据及就可以了。
需要注意一点,我自己引进的时候由于我使用的是vue2语法,有一处地方提示我使用了jsx语法而报错
于是我改变了它形式,具体功能和展示效果与原来一样
最后我自己的效果展示
参考文章 https://juejin.cn/post/7066079104742719525?searchId=20240522133658C46B56C6941369684F86
github地址 GitHub - ElemeFE/element: A Vue.js 2.0 UI Toolkit for Web