vue3+vite+ts+Ant Design Vue实现左右树穿梭框

在工作中难免会碰到各种奇葩的问题。最近遇到一个需求,要实现树左右树穿梭的功能。对于有经验的小伙伴这个需求不难,而且Ant Design Vue 官网中有穿梭框 Transfer 组件。但提供的示例是左侧是树,右侧则是平铺列表。只能在其基础上进行修改实现了在。。。。在但是在工作当中后台给的数据往往满足不了我们的要求(理想很丰满,现实很骨感),这时就要让我们勤劳的小手动起来了,不再是ctrl+c和ctrl+v了。

效果图

在这里插入图片描述

1、先创建一个父组件,文件名看自己喜好

<template>
  <div class="about transfer-wrapper">
    <transfer ref="transferRef" class="tree" :tree-data="treeData" :edit-key="editKey" />
  </div>
</template>
<script lang="ts" setup>
import { computed, ref } from "vue";

import transfer from "@/components/transfer-plugin/transfer.vue";

let editKey = ref([]);
const treeData = ref([
  {
    key: "1002",
    title: "海上平台",
    children: [
      {
        key: "100201",
        title: "航母",
        children: [
          {
            key: "10020101",
            mblb: "100201",
            title: "尼米兹号",
          },
          {
            key: "10020102",
            mblb: "100201",
            title: "阿波罗号",
          },
          {
            key: "10020103",
            mblb: "100201",
            title: "里根号",
          },
        ],
      },
      {
        key: "100202",
        title: "舰队",
        children: [
          {
            key: "10020201",
            mblb: "100202",
            title: "罗斯福驱逐舰",
          },
          {
            key: "10020202",
            mblb: "100202",
            title: "奥巴马驱逐舰",
          },
          {
            key: "10020203",
            mblb: "100202",
            title: "拜登驱逐舰",
          },
        ],
      },
      {
        key: "100203",
        title: "潜艇"
      },
      {
        key: "100204",
        title: "蛟龙号"
      }
    ],
  },
  {
    key: "1003",
    title: "空中平台",
    children: [
      {
        key: "100301",
        title: "战机",
        children: [
          {
            key: "10030101",
            mblb: "100301",
            title: "米格-21",
          },
          {
            key: "10030102",
            mblb: "100301",
            title: "歼-20",
          },
          {
            key: "10030103",
            mblb: "100301",
            title: "f-15",
          },
        ],
      },
      {
        key: "100302",
        title: "无人机",
        children: [
          {
            key: "10030201",
            mblb: "100302",
            title: "座山雕",
          },
          {
            key: "10030202",
            mblb: "100302",
            title: "夜鹰号",
          },
          {
            key: "10030203",
            mblb: "100302",
            title: "猫头鹰号",
          },
        ],
      },
    ],
  },
  {
    key: "1004",
    title: "陆地平台",
    children: [
      {
        key: "100401",
        title: "坦克",
        children: [
          {
            key: "10040101",
            mblb: "100401",
            title: "700-MinT",
          },
          {
            key: "10040102",
            mblb: "100401",
            title: "T26-轻型坦克",
          },
          {
            key: "10040103",
            mblb: "100401",
            title: "T90",
          },
        ],
      },
      {
        key: "100402",
        title: "装甲车",
        children: [
          {
            key: "10040201",
            mblb: "100402",
            title: "比亚迪",
          },
          {
            key: "10040202",
            mblb: "100402",
            title: "吉普",
          },
          {
            key: "10040203",
            mblb: "100402",
            title: "猛虎",
          },
        ],
      },
    ],
  },
]);
</script>

<style>
@media (min-width: 1024px) {
  .about {
    /* min-height: 100vh; */
    /* display: flex; */
    align-items: center;
  }
}
</style>

2、创建穿梭框组件的文件名transfer.vue

<template>
  <div>
    <a-transfer
      v-model:target-keys="targetKeys"
      class="tree-transfer"
      :data-source="dataSource"
      :render="(item) => item.title"
      :show-select-all="false"
      @change="onChange"
      :titles="['source', 'target']"
    >
      <template #children="{ direction, selectedKeys, onItemSelect }">
        <template v-if="direction === 'left'">
          <a-tree
            block-node
            checkable
            default-expand-all
            :checked-keys="leftCheckedKey"
            :tree-data="leftTreeData"
            @check="
              (_, props) => {
                handleLeftChecked(
                  _,
                  props,
                  [...selectedKeys, ...targetKeys],
                  onItemSelect
                );
              }
            "
          />
        </template>
        <template v-if="direction === 'right'">
          <a-tree
            block-node
            checkable
            default-expand-all
            :checked-keys="rightCheckedKey"
            :tree-data="rightTreeData"
            @check="
              (_, props) => {
                handleRightChecked(
                  _,
                  props,
                  [...selectedKeys, ...targetKeys],
                  onItemSelect
                );
              }
            "
          />
        </template>
      </template>
    </a-transfer>
  </div>
</template>
<script lang="ts" setup>
import { computed, ref, watch, onMounted } from "vue";
import type { TransferProps, TreeProps } from "ant-design-vue";
import type { title } from "process";

import {
  cloneDeep,
  addParentIdTreeData,
  isChecked,
  flatten,
  handleLeftTreeData,
  handleRightTreeData,
	getTreeKeys
} from "./index.ts";

const props = defineProps({
  /** 树数据 */
  treeData: {
    type: Array as PropType<TreeDataItem[]>,
    default: () => [],
  },
  /** 编辑 key */
  editKey: {
    type: Array as PropType<string[]>,
    default: () => [],
  },
});
const tData: TransferProps["dataSource"] = [];

const transferDataSource: TransferProps["dataSource"] = [];

const dataSource = ref(transferDataSource);
const targetKeys = ref<string[]>([]);

let leftTreeData = ref<any>([]); // 左侧树
let leftCheckedKey = ref([]); // 左侧树选中key的集合
let leftHalfCheckedKeys = ref([]); // 左侧半选集合
let leftCheckedAllKey = ref([]); // 左侧树选中的key集合,包括半选和全选

let rightTreeData = ref<any>([]); // 右侧树
let rightCheckedKey = ref([]); // 右侧树选中key的集合
let rightCheckedAllKey = ref([]); // 右侧树选中的key集合,包括半选和全选
let rightExpandedKey = ref([]); // 右侧展开树集合

let emitKeys = ref([]); // 往父级组件传递的数据
let deepList = ref([]); // 深层列表

// 处理树数据
function processTreeData() {
  flatten(addParentIdTreeData(cloneDeep(props.treeData)), dataSource.value);
  if (props.editKey.length) {
    processEditData();
  } else {
    leftTreeData.value = handleLeftTreeData(
      addParentIdTreeData(cloneDeep(props.treeData)),
      leftCheckedKey.value
    );
  }
}
/** 处理编辑数据 */
function processEditData(): void {
  debugger;
}
// 穿梭更改
function onChange(targetKeys: string[], direction: string) {
  console.log(targetKeys, direction, "穿梭更改");
  if (direction === "right") {
    targetKeys.value = leftCheckedAllKey.value;
    rightCheckedKey.value = [];
    rightTreeData.value = handleRightTreeData(
      addParentIdTreeData(cloneDeep(props.treeData)),
      leftCheckedAllKey.value,
      "right"
    );
		leftTreeData.value = handleLeftTreeData(addParentIdTreeData(cloneDeep(props.treeData)), leftCheckedKey.value, 'right')
		console.log(rightTreeData.value,'rightTreeData.value');
		
  } else if (direction === "left") {
		rightTreeData.value = handleRightTreeData(rightTreeData.value, rightCheckedKey.value, 'left')
		leftTreeData.value = handleLeftTreeData(leftTreeData.value, rightCheckedKey.value, 'left')
		leftCheckedKey.value = leftCheckedKey.value.filter(item => rightCheckedKey.value.indexOf(item) === -1)
		targetKeys.value = targetKeys.value.filter(item => rightCheckedKey.value.indexOf(item) === -1)
		leftHalfCheckedKeys.value = leftHalfCheckedKeys.value.filter(item => rightCheckedKey.value.indexOf(item) === -1)
		rightCheckedKey.value = []
  }
	rightExpandedKey.value = getTreeKeys(rightTreeData.value)
  emitKeys.value = rightExpandedKey.value
}

// 左侧选择
function handleLeftChecked(
  _: string[],
  { node, halfCheckedKeys }: any,
  checkedKeys: any,
  itemSelect: (arg0: any, arg1: boolean) => void
): void {
  console.log(
    _,
    { node, halfCheckedKeys },
    checkedKeys,
    itemSelect,
    "左侧选择"
  );
  leftCheckedKey.value = _;
  leftHalfCheckedKeys.value = [
    ...new Set([...leftHalfCheckedKeys.value, ...halfCheckedKeys]),
  ];
  leftCheckedAllKey.value = [
    ...new Set([...leftHalfCheckedKeys.value, ...halfCheckedKeys, ..._]),
  ];
  const { eventKey } = node;
  itemSelect(eventKey, !isChecked(checkedKeys, eventKey));
}
// 右侧选择
function handleRightChecked(_: string[], { node, halfCheckedKeys }: any, checkedKeys: any, itemSelect: (arg0: any, arg1: boolean) => void): void {
	rightCheckedKey.value = _
	rightCheckedAllKey.value = [...halfCheckedKeys, ..._]
	const { eventKey } = node
	itemSelect(eventKey, isChecked(_, eventKey))
}


onMounted(() => {
  processTreeData();
});
</script>
<style scoped>
.tree-transfer .ant-transfer-list:first-child {
  width: 50%;
  flex: none;
}
</style>

3、创建index.ts文件,放一些工具类

/**
 * 深拷贝
 * @param data
 */
export function cloneDeep(data) {
  return JSON.parse(JSON.stringify(data));
}

/**
 * 给每一级添加parentId
 * parentId
 */
export function addParentIdTreeData(data: any) {
  data.forEach((item) => {
		if(item.key.length === 4) {
			item.parentId = Number(0)
		}else if(item.key.length === 6) {
			item.parentId = Number(item.key.slice(0,4))
		}else {
			item.parentId = Number(item.mblb)
		}
		if (item.children) addParentIdTreeData(item.children)
  });
	// console.log(data,'添加pid');
  return data;
}

/**
 * 是否选中
 * @param selectedKeys
 * @param eventKey
 */
export function isChecked (selectedKeys: string[], eventKey: string): boolean {
  return selectedKeys.indexOf(eventKey) !== -1
}

/**
 * 树数据展平
 * @param list
 * @param dataSource
 */
export function flatten (list: TreeDataItem[], dataSource: TreeDataItem[]): TreeDataItem[] {
  list.forEach(item => {
    dataSource.push(item)
    if (item.children) flatten(item.children, dataSource)
  })
  return dataSource
}

/**
 * 树转数组
 * @param tree
 * @param hasChildren
 */
export function treeToList (tree: TreeDataItem[], hasChildren = false): TreeDataItem[] {
  let queen: TreeDataItem[] = []
  const out: TreeDataItem[] = []
  queen = queen.concat(JSON.parse(JSON.stringify(tree)))
  while (queen.length) {
    const first = queen.shift() as TreeDataItem
    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: TreeDataItem[], tree: TreeDataItem[], parentId, key = 'parentId'): TreeDataItem[] {
  list.forEach(item => {
		if (item[key] === parentId) {
      const child: TreeDataItem = {
        ...item,
        children: []
      }
      listToTree(list, child.children as TreeDataItem[], Number(item.key), key)
      if (!child.children?.length) delete child.children
      tree.push(child)
    }
  })
  return tree
}

/**
 * 获取树节点 key 列表
 * @param treeData
 */
export function getTreeKeys (treeData: TreeDataItem[]): string[] {
  const list = treeToList(treeData)
  return list.map(item => item.key)
}

/**
 * 处理左侧树数据
 * @param data
 * @param targetKeys
 * @param direction
 */
export function handleLeftTreeData (data: TreeDataItem[], targetKeys: string[], direction = 'right'): TreeDataItem[] {
  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: TreeDataItem[], targetKeys: string[], direction = 'right'): TreeDataItem[] {
	console.log(data, targetKeys, direction, `处理${direction}侧树数据`);
  const list = treeToList(data)
  const arr: TreeDataItem[] = []
  const tree: TreeDataItem[] = []
  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
}
  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值