antd 树型穿梭框

antd 树型穿梭 自定义

<template>
  <div>
    <a-transfer
      :target-keys="targetKeys"
      class="tree-transfer"
      :data-source="dataSource"
      :render="(item) => item.title"
      :show-search="false"
      :show-select-all="false"
      @change="onChange"
    >
      <template #leftSelectAllLabel>
        <div class="transfer-title">
          <div class="title">点位列表</div>
        </div>
      </template>
      <template #rightSelectAllLabel>
        <div class="transfer-title">
          <div class="title">选中点位</div>
        </div>
      </template>
      <template #children="{ direction, selectedKeys, onItemSelect }">
        <template v-if="direction === 'left'">
          <a-tree
            v-if="leftTreeData.length"
            block-node
            checkable
            :tree-data="leftTreeData"
            :checked-keys="leftCheckedKey"
            :field-names="{ title: 'title', key: 'id' }"
            default-expand-all
            @check="
              (_, props) => {
                handleLeftChecked(
                  _,
                  props,
                  [...selectedKeys, ...targetKeys],
                  onItemSelect
                );
              }
            "
          >
            <template #title="{ title }">
              <span v-if="title.indexOf(leftSearchValue) > -1">
                {{ title.substr(0, title.indexOf(leftSearchValue)) }}
                <span style="color: #f50">{{ leftSearchValue }}</span>
                {{
                  title.substr(
                    title.indexOf(leftSearchValue) + leftSearchValue.length
                  )
                }}
              </span>
              <span v-else>{{ title }}</span>
            </template>
          </a-tree>
          <a-empty v-else>
            <template #description>暂无数据</template>
          </a-empty>
        </template>
        <template v-else-if="direction === 'right'">
          <a-tree
            v-if="rightTreeData.length"
            block-node
            checkable
            default-expand-all
            :tree-data="rightTreeData"
            :checked-keys="rightCheckedKey"
            :field-names="{ title: 'title', key: 'id' }"
            @check="
              (_, props) => {
                handleRightChecked(
                  _,
                  props,
                  [...selectedKeys, ...targetKeys],
                  onItemSelect
                );
              }
            "
          >
            <template #title="{ title }">
              <span v-if="title.indexOf(rightSearchValue) > -1">
                {{ title.substr(0, title.indexOf(rightSearchValue)) }}
                <span style="color: #f50">{{ rightSearchValue }}</span>
                {{
                  title.substr(
                    title.indexOf(rightSearchValue) + rightSearchValue.length
                  )
                }}
              </span>
              <span v-else>{{ title }}</span>
            </template>
          </a-tree>
          <a-empty v-else>
            <template #description>暂无数据</template>
          </a-empty>
        </template>
      </template>
    </a-transfer>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import {
  cloneDeep,
  flatten,
  getDeepList,
  getTreeKeys,
  handleLeftTreeData,
  handleRightTreeData,
  isChecked,
  uniqueTree,
} from "@/utils/utils.js";

const treeData = ref([
  {
    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: "高级表单",
      },
    ],
  },
]);

const editKey = ref([]);
const targetKeys = ref([]); // 显示在右侧框数据的 key 集合
const dataSource = ref([]); // 数据源,其中的数据将会被渲染到左边一栏
const leftCheckedKey = ref([]); // 左侧树选中 key 集合
const leftHalfCheckedKeys = ref([]); // 左侧半选集合
const leftCheckedAllKey = ref([]); // 左侧树选中的 key 集合,包括半选与全选
const leftTreeData = ref([]); // 左侧树
const rightCheckedKey = ref([]); // 右侧树选中集合
const rightCheckedAllKey = ref([]); // 右侧树选中集合,包括半选与全选
const rightExpandedKey = ref([]); // 右侧展开数集合
const rightTreeData = ref([]); // 右侧树
const emitKeys = ref([]); // 往父级组件传递的数据
const deepList = ref([]); // 深层列表
const leftSearchValue = ref(); // 左边搜索关键字
const rightSearchValue = ref(); // 右边搜索关键字
const rightTreeDataHis = ref([]) // 历史右侧树
const pointTypeRight = ref() // 点位类型右边
const pointTypeLeft = ref() // 点位类型左边
const rightCheckedKeyHis = ref([]) // 左侧树选中 key 历史集合


onMounted(() => {
  processTreeData()
});

// 处理树数据
function processTreeData() {
  dataSource.value = [];
  flatten(cloneDeep(treeData.value), dataSource.value);
  if (editKey.value.length) {
    processEditData();
  } else {
    leftTreeData.value = handleLeftTreeData(
      cloneDeep(treeData.value),
      leftCheckedKey.value
    );
  }
}
// 处理编辑数据
function processEditData() {
  leftCheckedAllKey.value = editKey.value;
  rightExpandedKey.value = editKey.value;
  targetKeys.value = editKey.value;
  rightTreeData.value = handleRightTreeData(
    cloneDeep(treeData.value),
    editKey.value
  );

  getDeepList(deepList.value, treeData.value);

  leftCheckedKey.value = uniqueTree(editKey.value, deepList.value);
  leftHalfCheckedKeys.value = leftCheckedAllKey.value.filter(
    (item) => leftCheckedKey.value.indexOf(item) === -1
  );
  leftTreeData.value = handleLeftTreeData(
    cloneDeep(treeData.value),
    leftCheckedKey.value
  );

  emitKeys.value = rightExpandedKey.value;
}
// 穿梭更改
function onChange(targetKeyList, direction) {
  if (direction === 'right') {
    pointTypeLeft.value = null
    rightCheckedKey.value = []
    let rightTreeKeys = getTreeKeys(rightTreeData.value)
    let checkedKey = [...new Set([...rightTreeKeys, ...leftCheckedAllKey.value])]
    leftCheckedKey.value = checkedKey
    targetKeys.value = checkedKey
    rightTreeData.value = handleRightTreeData(cloneDeep(treeData.value), checkedKey, 'right')
    rightTreeDataHis.value = rightTreeData.value
    leftTreeData.value = handleLeftTreeData(cloneDeep(treeData.value), checkedKey, 'right')
  } else if (direction === 'left') {
    pointTypeRight.value = null
    rightTreeData.value = handleRightTreeData(cloneDeep(rightTreeDataHis.value), rightCheckedKey.value, 'left')
    rightTreeDataHis.value = rightTreeData.value
    leftTreeData.value = handleLeftTreeData(cloneDeep(leftTreeData.value), rightCheckedKey.value, 'left')
    leftCheckedKey.value = leftCheckedKey.value.filter(item => rightCheckedKey.value.indexOf(item) === -1)
    targetKeys.value = targetKeyList.filter(item => rightCheckedKey.value.indexOf(item) === -1)
    leftHalfCheckedKeys.value = leftHalfCheckedKeys.value.filter(item => rightCheckedKey.value.indexOf(item) === -1)
    rightCheckedKey.value = []
    leftCheckedAllKey.value = []
  }
  rightExpandedKey.value = getTreeKeys(rightTreeData.value)
  emitKeys.value = rightExpandedKey.value
}

// 左侧选择
function handleLeftChecked(list, { node, halfCheckedKeys }, checkedKeys, itemSelect) {
  leftCheckedKey.value = list
  leftHalfCheckedKeys.value = [...new Set([...leftHalfCheckedKeys.value, ...halfCheckedKeys])]
  leftCheckedAllKey.value = [...new Set([...leftHalfCheckedKeys.value, ...halfCheckedKeys, ...list])]
  const { eventKey } = node
  itemSelect(eventKey, !isChecked(checkedKeys, eventKey))
}
// 右侧选择
function handleRightChecked(list, { node, halfCheckedKeys }, checkedKeys, itemSelect) {
  let rightTreeKeys = [...new Set(getTreeKeys(rightTreeDataHis.value))]
  rightCheckedKeyHis.value = rightCheckedKey.value
  rightCheckedKey.value = list
  rightCheckedAllKey.value = [...halfCheckedKeys, ...list]
  // let arr = sameArr(rightTreeKeys, rightCheckedAllKey.value)
  // if (arr.length !== rightTreeKeys.length) {
  //   rightCheckedAllKey.value = rightCheckedAllKey.value.filter(x => x !== '510000')
  //   rightCheckedKey.value = rightCheckedKey.value.filter(x => x !== '510000')
  // }
  const { eventKey } = node
  itemSelect(eventKey, isChecked(list, eventKey))
}
</script>

<style lang="less">
.tree-transfer {
  :deep {
    .ant-transfer-list-body {
      max-height: 500px;
      overflow: hidden;
      overflow-y: auto;
    }
  }
  .ant-transfer-list:first-child {
    width: 50%;
    flex: none;
  }
}
</style>

utils代码


```javascript
// 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.id)
}

/**
 * 循环遍历出最深层子节点,存放在一个数组中
 * @param deepList
 * @param treeData
 */
export function getDeepList (deepList, treeData) {
  treeData?.forEach(item => {
    if (item?.children?.length) {
      getDeepList(deepList, item.children)
    } else {
      deepList.push(item.id)
    }
  })
  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.id)
    } else if (direction === 'left') {
      if (item.disabled && targetKeys.includes(item.id)) 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.id)) {
        const content = { ...item }
        if (content.children) delete content.children
        arr.push({ ...content })
      }
    } else if (direction === 'left') {
      if (!targetKeys.includes(item.id)) {
        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
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Antd TreeSelect组件提供了搜索功能,可以方便地筛选出符合条件的选项。你可以在TreeSelect组件中添加`showSearch`属性,即可开启搜索功能。 以下是一个示例代码: ```jsx import React, { useState } from 'react'; import { TreeSelect } from 'antd'; const { TreeNode } = TreeSelect; const treeData = [ { title: 'Node1', value: '0-0', key: '0-0', children: [ { title: 'Child Node1', value: '0-0-1', key: '0-0-1', }, { title: 'Child Node2', value: '0-0-2', key: '0-0-2', }, ], }, { title: 'Node2', value: '0-1', key: '0-1', }, ]; const TreeSelectDemo = () => { const [value, setValue] = useState(undefined); const onChange = (newValue) => { setValue(newValue); }; return ( <TreeSelect showSearch value={value} placeholder="Please select" style={{ width: '100%' }} dropdownStyle={{ maxHeight: 400, overflow: 'auto' }} onChange={onChange} treeDefaultExpandAll > {treeData.map((item) => ( <TreeNode key={item.key} value={item.value} title={item.title}> {item.children && item.children.map((child) => ( <TreeNode key={child.key} value={child.value} title={child.title} /> ))} </TreeNode> ))} </TreeSelect> ); }; export default TreeSelectDemo; ``` 在上面的示例中,我们添加了`showSearch`属性,并设置了一些其他的属性,如`value`,`placeholder`等。此外,我们还需要为TreeSelect组件提供一些数据,这里使用了一个简单的treeData数组。 当用户在TreeSelect中输入关键字时,Antd会自动筛选出匹配的选项,并将它们展示在下拉菜单中。 希望这个例子能够帮助到你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值