版本:“ant-design-vue”: “^1.7.6”
参考大佬 注:大佬的为ts+vue3版,下面本人改动而来,方便vue2.0的小伙伴直接拿来用
父组件:
<template>
<div class="transfer-wrapper">
<transfer
ref="transferRef"
:tree-data="treeData"
:edit-key="editKey"
class="tree"
/>
</div>
</template>
<script>
import Transfer from "@/components/transfer.vue";
export default {
name: "Options",
components: {
Transfer,
},
data() {
return {
treeData: [
{
id: 1,
parentId: 0,
key: "0",
title: "工作台",
},
{
id: 2,
parentId: 0,
key: "2",
title: "仪表盘",
children: [
{
id: 3,
parentId: 2,
key: "3",
title: "欢迎页",
},
{
id: 4,
parentId: 2,
key: "4",
title: "分析页",
},
{
id: 5,
parentId: 2,
key: "5",
title: "监控页",
},
],
},
{
id: 6,
parentId: 0,
key: "6",
title: "表单页",
children: [
{
id: 7,
parentId: 6,
key: "7",
title: "基础表单",
},
{
id: 8,
parentId: 6,
key: "8",
title: "分步表单",
},
{
id: 9,
parentId: 6,
key: "9",
title: "高级表单",
},
],
},
],
editKey: ["2"],
};
},
methods: {
// 获取值
getValue() {
const transferRef = this.$refs.transferRef;
if (transferRef) console.log(transferRef.emitKeys);
},
// 设置值
setValue() {
this.editKey = data.editKey;
},
},
};
</script>
<style lang="scss">
.transfer-wrapper {
display: flex;
justify-content: center;
margin-top: 80px;
margin-bottom: 24px;
text-align: left;
width: 1200px;
.tree {
width: 1000px;
}
}
</style>
子组件:
transfer.vue
<template>
<a-transfer
class="tree-transfer"
:data-source="dataSource"
:target-keys="targetKeys"
:render="(item) => item.title"
:show-select-all="false"
@change="onChange"
:titles="['源数据', '调用数据']"
>
<template
slot="children"
slot-scope="{ props: { direction, selectedKeys }, on: { itemSelect } }"
>
<template v-if="direction === 'left'">
<a-tree
v-if="leftTreeData.length"
blockNode
checkable
defaultExpandAll
:tree-data="leftTreeData"
:checked-keys="leftCheckedKey"
@check="
(_, props) => {
handleLeftChecked(
_,
props,
[...selectedKeys, ...targetKeys],
itemSelect
);
}
"
/>
<a-empty v-else>
<template #description>暂无数据</template>
</a-empty>
</template>
<template v-else-if="direction === 'right'">
<a-tree
v-if="rightTreeData.length"
blockNode
checkable
defaultExpandAll
:tree-data="rightTreeData"
v-model:checked-keys="rightCheckedKey"
v-model:expanded-keys="rightExpandedKey"
@check="
(_, props) => {
handleRightChecked(
_,
props,
[...selectedKeys, ...targetKeys],
itemSelect
);
}
"
/>
<a-empty v-else>
<template #description>暂无数据</template>
</a-empty>
</template>
</template>
</a-transfer>
</template>
<script>
import {
cloneDeep,
flatten,
getDeepList,
getTreeKeys,
handleLeftTreeData,
handleRightTreeData,
isChecked,
uniqueTree,
} from "@/utils";
export default {
name: "OptionsTransfer",
props: {
/** 树数据 */
treeData: {
type: Array,
default: () => [],
},
/** 编辑 key */
editKey: {
type: Array,
default: () => [],
},
},
data() {
return {
targetKeys: [], // 显示在右侧框数据的 key 集合
dataSource: [], // 数据源,其中的数据将会被渲染到左边一栏
leftCheckedKey: [], // 左侧树选中 key 集合
leftHalfCheckedKeys: [], // 左侧半选集合
leftCheckedAllKey: [], // 左侧树选中的 key 集合,包括半选与全选
leftTreeData: [], // 左侧树
rightCheckedKey: [], // 右侧树选中集合
rightCheckedAllKey: [], // 右侧树选中集合,包括半选与全选
rightExpandedKey: [], // 右侧展开数集合
rightTreeData: [], // 右侧树
emitKeys: [], // 往父级组件传递的数据
deepList: [], // 深层列表
};
},
watch: {
treeData: {
deep: true,
handler(val) {
// console.log("123123");
this.processTreeData();
},
},
editKey: {
deep: true,
handler(val) {
this.processTreeData();
},
},
},
created() {
this.processTreeData();
},
methods: {
// 处理树数据
processTreeData() {
this.dataSource = []
flatten(cloneDeep(this.treeData), this.dataSource);
console.log(this.dataSource);
if (this.editKey.length) {
this.processEditData();
} else {
this.leftTreeData = handleLeftTreeData(
cloneDeep(this.treeData),
this.leftCheckedKey
);
}
},
// 处理编辑数据
processEditData() {
this.leftCheckedAllKey = this.editKey;
this.rightExpandedKey = this.editKey;
this.targetKeys = this.editKey;
this.rightTreeData = handleRightTreeData(
cloneDeep(this.treeData),
this.editKey
);
getDeepList(this.deepList, this.treeData);
this.leftCheckedKey = uniqueTree(this.editKey, this.deepList);
this.leftHalfCheckedKeys = this.leftCheckedAllKey.filter(
(item) => this.leftCheckedKey.indexOf(item) === -1
);
this.leftTreeData = handleLeftTreeData(
cloneDeep(this.treeData),
this.leftCheckedKey
);
this.emitKeys = this.rightExpandedKey;
},
// 穿梭更改
onChange(targetKeys, direction) {
console.log(targetKeys, direction);
if (direction === "right") {
this.targetKeys = this.leftCheckedAllKey;
this.rightCheckedKey = [];
this.rightTreeData = handleRightTreeData(
cloneDeep(this.treeData),
this.leftCheckedAllKey,
"right"
);
console.log(this.leftTreeData, "左边1");
console.log(this.leftCheckedAllKey, "左边");
this.leftTreeData = handleLeftTreeData(
cloneDeep(this.treeData),
this.leftCheckedKey,
"right"
);
console.log(this.leftTreeData, "左边2");
} else if (direction === "left") {
this.rightTreeData = handleRightTreeData(
this.rightTreeData,
this.rightCheckedKey,
"left"
);
this.leftTreeData = handleLeftTreeData(
this.leftTreeData,
this.rightCheckedKey,
"left"
);
this.leftCheckedKey = this.leftCheckedKey.filter(
(item) => this.rightCheckedKey.indexOf(item) === -1
);
this.targetKeys = this.targetKeys.filter(
(item) => this.rightCheckedKey.indexOf(item) === -1
);
this.leftHalfCheckedKeys = this.leftHalfCheckedKeys.filter(
(item) => this.rightCheckedKey.indexOf(item) === -1
);
this.rightCheckedKey = [];
}
this.rightExpandedKey = getTreeKeys(this.rightTreeData);
this.emitKeys = this.rightExpandedKey;
},
// 左侧选择
// 左侧选择
handleLeftChecked(_, { node, halfCheckedKeys }, checkedKeys, itemSelect) {
console.log(_, { node, halfCheckedKeys }, checkedKeys, itemSelect, "c");
this.leftCheckedKey = _;
this.leftHalfCheckedKeys = [
...new Set([...this.leftHalfCheckedKeys, ...halfCheckedKeys]),
];
this.leftCheckedAllKey = [
...new Set([...this.leftHalfCheckedKeys, ...halfCheckedKeys, ..._]),
];
const { eventKey } = node;
itemSelect(eventKey, !isChecked(checkedKeys, eventKey));
},
// 右侧选择
handleRightChecked(_, { node, halfCheckedKeys }, checkedKeys, itemSelect) {
this.rightCheckedKey = _;
this.rightCheckedAllKey = [...halfCheckedKeys, ..._];
const { eventKey } = node;
itemSelect(eventKey, isChecked(_, eventKey));
},
},
};
</script>
<style lang="scss">
.ant-transfer-list {
width: 800px;
}
</style>
配置文件:
index.js
// import type { TreeDataItem } from '@/types'
/**
* 深拷贝
* @param data
*/
export function cloneDeep(data) {
return JSON.parse(JSON.stringify(data))
}
/**
* 树转数组
* @param tree
* @param hasChildren
*/
export function treeToList(tree = [], hasChildren = false) {
let queen = []
const out = []
queen = queen.concat(JSON.parse(JSON.stringify(tree)))
while (queen.length) {
const first = queen.shift()
if (first?.children) {
queen = queen.concat(first.children)
if (!hasChildren) delete first.children
}
out.push(first)
}
return out
}
/**
* 数组转树
* @param list
* @param tree
* @param parentId
* @param key
*/
export function listToTree(list = [], tree = [], parentId = 0, key = 'parentId') {
list.forEach(item => {
if (item[key] === parentId) {
const child = {
...item,
children: []
}
listToTree(list, child.children, item.id, key)
if (!child.children?.length) delete child.children
tree.push(child)
}
})
return tree
}
/**
* 获取树节点 key 列表
* @param treeData
*/
export function getTreeKeys(treeData) {
const list = treeToList(treeData)
return list.map(item => item.key)
}
/**
* 循环遍历出最深层子节点,存放在一个数组中
* @param deepList
* @param treeData
*/
export function getDeepList(deepList, treeData) {
treeData?.forEach(item => {
if (item?.children?.length) {
getDeepList(deepList, item.children)
} else {
deepList.push(item.key)
}
})
return deepList
}
/**
* 将后台返回的含有父节点的数组和第一步骤遍历的数组做比较,如果有相同值,将相同值取出来,push到一个新数组中
* @param uniqueArr
* @param arr
*/
export function uniqueTree(uniqueArr, arr) {
const uniqueChild = []
for (const i in arr) {
for (const k in uniqueArr) {
if (uniqueArr[k] === arr[i]) {
uniqueChild.push(uniqueArr[k])
}
}
}
return uniqueChild
}
/**
* 是否选中
* @param selectedKeys
* @param eventKey
*/
export function isChecked(selectedKeys, eventKey = "") {
return selectedKeys.indexOf(eventKey) !== -1
}
/**
* 处理左侧树数据
* @param data
* @param targetKeys
* @param direction
*/
export function handleLeftTreeData(data, targetKeys, direction = 'right') {
data.forEach(item => {
if (direction === 'right') {
item.disabled = targetKeys.includes(item.key)
} else if (direction === 'left') {
if (item.disabled && targetKeys.includes(item.key)) item.disabled = false
}
if (item.children) handleLeftTreeData(item.children, targetKeys, direction)
})
return data
}
/**
* 处理右侧树数据
* @param data
* @param targetKeys
* @param direction
*/
export function handleRightTreeData(data, targetKeys, direction = 'right') {
const list = treeToList(data)
const arr = []
const tree = []
list.forEach(item => {
if (direction === 'right') {
if (targetKeys.includes(item.key)) {
const content = { ...item }
if (content.children) delete content.children
arr.push({ ...content })
}
} else if (direction === 'left') {
if (!targetKeys.includes(item.key)) {
const content = { ...item }
if (content.children) delete content.children
arr.push({ ...content })
}
}
})
listToTree(arr, tree, 0)
return tree
}
/**
* 树数据展平
* @param list
* @param dataSource
*/
export function flatten(list, dataSource) {
list.forEach(item => {
dataSource.push(item)
if (item.children) flatten(item.children, dataSource)
})
return dataSource
}