ant-design-vue 实现树形穿梭框

1 篇文章 0 订阅

版本:“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
}

  • 13
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 54
    评论
评论 54
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值