HTML
<template>
<div class='tree-transfer'>
<div class="left-tree">
<div class="tree-tit">{<!-- -->{leftTit || 'Left column'}}</div>
<div class="list">
<el-tree
ref="treeRefL"
v-if="reLoad"
:data="leftData"
show-checkbox
default-expand-all
:node-key="nodeKey"
highlight-current
:props="defaultProps"
/>
</div>
</div>
<div class="btn-div">
<el-button :icon="Back" type="primary" :disabled="disabled" @click="toLeft()" />
<el-button :icon="Right" type="primary" :disabled="disabled" @click="toRight()" />
</div>
<div class="right-tree">
<div class="tree-tit">{<!-- -->{rightTit || 'right column'}}</div>
<div class="list">
<el-tree
ref="treeRefR"
v-if="reLoad"
:data="rightData"
show-checkbox
default-expand-all
:node-key="nodeKey"
highlight-current
:props="defaultProps"
/>
</div>
</div>
</div>
</template>
Shuttle box control logic
import { ref, nextTick, defineExpose } from 'vue'
import { Right, Back } from '@element-plus/icons-vue';
const props = defineProps({
nodeKey: String,
fromData: Array,
toData: Array,
defaultProps: {},
leftTit: String,
rightTit: String,
disabled: {
type: Boolean,
default: false
}
})
//Define emit
const emit = defineEmits(['checkVal'])
const treeRefL = ref([])
const treeRefR = ref([])
const leftData = ref([])
const rightData = ref([])
const reLoad = ref(true)
//Data on the right
const toData = ref([])
// Data to be removed on the right
const removeData = ref([])
defineExpose({
/**
*Clear data
*/
clearData() {
toData.value = []
},
/**
* Initialization data
*/
initData() {
const originalLeft = JSON.parse(JSON.stringify(props.fromData))
const originalRight = JSON.parse(JSON.stringify(props.fromData))
if (props.toData.length > 0) {
leftData.value = sortData(originalLeft, props.toData, 'left')
rightData.value = sortData(originalRight, props.toData, 'right')
}else{
leftData.value = originalLeft
rightData.value = []
}
}
})
//method
//Go to the right
const toRight = () =>{
// Save the selected data to toData
const checkNodes = treeRefL.value.getCheckedNodes(false, false)
const newArr = toData.value.concat(checkNodes)
const obj = {};
const peon = newArr.reduce((cur, next) => {
obj[next[props.nodeKey]] ? "" : obj[next[props.nodeKey]] = true & amp; & amp; cur.push(next);
return cur;
},[]) //Set the default type of cur to an array, and the initial value is an empty array
toData.value = peon
reLoad.value = false
const originalLeft = JSON.parse(JSON.stringify(props.fromData))
const originalRight = JSON.parse(JSON.stringify(props.fromData))
//Extract the id from the selected data
const ids = extractId(toData.value)
//Reorganize the data in the trees on both sides
leftData.value = sortData(originalLeft, ids, 'left')
rightData.value = sortData(originalRight, ids, 'right')
nextTick(() => {
reLoad.value = true
})
checkVal()
}
//Go to the left
const toLeft = () =>{
// Save the selected data to toData
const checkNodes = treeRefR.value.getCheckedNodes(false, false)
const newArr = removeData.value.concat(checkNodes)
const obj = {};
const peon = newArr.reduce((cur, next) => {
obj[next[props.nodeKey]] ? "" : obj[next[props.nodeKey]] = true & amp; & amp; cur.push(next);
return cur;
},[]) //Set the default type of cur to an array, and the initial value is an empty array
const dataNeedRemove = peon
reLoad.value = false
const originalLeft = JSON.parse(JSON.stringify(props.fromData))
const originalRight = JSON.parse(JSON.stringify(props.fromData))
//Extract the id from the selected data
const idsNeedRemove = extractId(dataNeedRemove)
// Delete the same id
const oldData = removeId(toData.value, idsNeedRemove)
toData.value = oldData
//The id of the data that needs to be retained in the right list
const ids = extractId(oldData)
//Reorganize the data in the trees on both sides
leftData.value = sortData(originalLeft, ids, 'left')
rightData.value = sortData(originalRight, ids, 'right')
nextTick(() => {
reLoad.value = true
})
checkVal()
}
/**
* Organize the data in the tree and determine whether the data is displayed in the tree.
* @param data tree data
* @param condition selected data
* @param leftRight Whether to sort the data in the tree on the left or in the tree on the right
*/
const sortData = (data: any, condition: Array<string>, leftRight: string) => {
if(leftRight === 'left'){
const result = [];
for (const item of data) {
// Determine whether the item's id is in the condition. If not, it means no need to delete it.
if (!condition.includes(item.id)) {
// If the item has the children attribute, call this function recursively and pass in the item's children and condition.
if (item.children) {
item.children = sortData(item.children, condition, leftRight);
}
// If the item's children is an empty array, delete the item's children attribute.
if (item.children & amp; & amp; item.children.length === 0) {
delete item.children;
}
result.push(item);
}
}
return result;
}else{
const result = [];
for (const item of data) {
// If the item's id is in condition, it means that the data needs to be retained
if (condition.includes(item.id)) {
result.push(item);
} else {
// Otherwise, determine whether the item has the children attribute
if (item.children) {
const subResult = sortData(item.children, condition, leftRight);
// If the returned result array is not empty, it means there is sub-data that meets the conditions.
if (subResult. length > 0) {
//Update the children attribute of item to the returned result array
item.children = subResult;
result.push(item);
}
}
}
}
return result;
}
}
/**
* If the id in the new array exists in the old array, delete the id in the original array
* @param oldIds original id
* @param newIds new id
*/
const removeId = (data: any, newIds: Array<string>) => {
const ids = []
for (const item of data) {
if(!newIds.includes(item.id)){
ids.push(item)
}
}
return ids
}
/**
* Take out the id from the data in the candidate
* @param arr The selected data in the tree
*/
const extractId = (arr: any) => {
const newArr = []
for(const i in arr){
newArr.push(arr[i].id)
}
return newArr
}
//Return to parent component
const checkVal = () =>{
emit('checkVal', toData.value)
}
CSS
.tree-transfer{
width: 100%;
display: flex;
justify-content: space-between;
.left-tree,.right-tree{
flex-grow: 1;
width: calc((100% - 60px) / 2);
.tree-tit{
margin-bottom: 10px;
}
.list{
overflow: auto;
height: 300px;
border: 1px solid #ddd;
border-radius: 4px;
.item{
padding: 0 10px;
font-size: 14px;
line-height: 26px;
cursor: pointer;
&.active{
background: #b9d7fa;
}
}
.item-checkbox{
height: 26px;
padding: 0 10px;
font-size: 14px;
line-height: 26px;
&>.el-checkbox{
height: 26px;
}
}
}
}
.btn-div{
width: 120px;
flex-shrink: 0;
display: flex;
// flex-direction: column;
align-items: center;
justify-content: center;
}
.el-checkbox__input.is-disabled .el-checkbox__inner{
display: none;
}
}
Parent component call
<tree-transfer ref="treeTransfer" :nodeKey="'id'" :fromData="menuList" :toData="ruleForm.menuIds"
:defaultProps="transferProps" :leftTit="'Optional menu'" :rightTit="'Selected menu'" @checkVal="checkVal"/>
/**
* Save the selected menu into the form
* @param val subcomponent shuttle box return
*/
const checkVal = (val: any) => {
const arr = []
for(const i in val){
arr.push(val[i].id)
}
ruleForm.menuIds = arr
}
Shuttle box parameter document
Properties
List
Parameter description
Field |
Description |
Type |
Whether it must be passed |
nodeKey |
The unique id value corresponding to the item in the tree |
string |
true |
fromData |
Menu tree (must contain id name) |
array[object] |
true |
toData |
Selected value |
array[string] |
true |
defaultProps |
Column width of list |
object |
true |
leftTit |
Left menu name |
string |
false |
rightTit |
Right menu name |
string |
false |
disabled |
Whether to disable the left and right buttons of the shuttle box |
boolean |
false |
fromData description in dataLabel
Field |
Description |
Type |
Whether it must be passed |
id |
Unique id value |
string |
true |
name |
id corresponds to the display rendering value |
string |
true |
children |
Sublevel of tree |
string |
false |
Description of defaultProps in dataLabel
Field |
Description |
Type |
Whether it must be passed |
label |
Specify the node label as an attribute value of the node object |
string, function(data, node) |
true |
children |
Specify the subtree as a certain attribute value of the node object |
string |
true |
disabled |
Specifies whether the node selection box is disabled as a property value of the node object |
string, function(data, node) |
true |
Expose
Field |
Description |
Type |
initData |
Initialize the data in the shuttle box |
function |
clearData |
Clear the data in the shuttle box |
function |