vue3组件elementPlus组件改树形穿梭框

由于饿了么组件库没有树形穿梭框,我在网上找了很多的博客发现一个通过el-tree自己封装的组件代码。但是我实际使用了一下vue3 + element-plus 自定义树形穿梭框-CSDN博客这个代码有些地方有问题然后没有css代码。所以我就进行了问题的修正和css代码的添加。不多说直接上完整代码。

<!-- eslint-disable vue/no-unused-vars -->
​<template>
    <div class="treeTransfer">
        <!-- 左边 -->
        <div class="leftTree">
            <div class="list">
                <div class="left_lowline">
                    <el-checkbox
v-model="checkedLeft" :disabled="leftTreeData.length < 1" label="" size="large"
                        style="margin-right: 12px" @change="leftAllCheck" />
                    <p class="left_title">{{ props.title[0] }}</p>
                </div>
                <!-- 搜索 -->
                <div class="left_input">
                    <el-input
                   v-model="leftSearchText" class="w-50" placeholder="请输入通道或目录名称" clearable
                        prefix-icon="el-icon-search" @clear="onSearchLeft" @keyup.enter="onSearchLeft"
                        @change="onSearchLeft" />
                 </div>
               <div  style="overflow: auto; flex: 1;margin-top: 10px;">
                <el-tree
             
                ref="leftTreeRef" :filter-node-method="filterNode" :data="leftTreeData" show-checkbox
                    :node-key="props.nodeKey" :props="props.defaultProps" v-slot="{ node, data }" accordion
                    @check="onCheckLeft" default-expand-all>
                    <div>
                        {{ data.corgName }}
                    </div>
                    <div>
                        {{ data.label }}
                    </div>
                </el-tree>
            </div>
            </div>
        </div>
        <!-- 中间按钮 -->
        <div class="btnDiv">
            <div class="mg10">
                <el-button @click="toRight()" size="default" type="primary" :icon="ArrowRight"  :disabled="leftOperation.length < 1"/>
            </div>
            <div class="mg10">
                <el-button @click="toLeft()" size="default"  type="primary"  :icon="ArrowLeft"  :disabled="rightOperation.length < 1"/>
            </div>
        </div>
        <!-- 右边 -->
        <div class="rightTree">
            <div class="list">
                <div class="left_lowline">
                    <el-checkbox
v-model="checkedRight" :disabled="rightTreeData.length < 1" label="" size="large"
                        style="margin-right: 12px" @change="rightAllCheck" />
                    <p class="left_title">{{ props.title[1] }}</p>
                </div>
                <!-- 搜索 -->
                <div class="left_input">
                    <el-input
v-model="rightSearchText" class="w-50" placeholder="请输入已挂载的通道或目录名称" clearable
                        prefix-icon="el-icon-search" @clear="onSearchRight" @keyup.enter="onSearchRight"
                        @change="onSearchRight" />
                </div>
                <div    style="overflow: auto; flex: 1;margin-top: 10px;">
                <el-tree
                  :filter-node-method="filterNode"
                  ref="rightTreeRef" :data="rightTreeData" show-checkbox :node-key="props.nodeKey"
                    :props="props.defaultProps" v-slot="{ node, data }" accordion @check="onCheckRight" default-expand-all>
                    <div>
                        {{ data.corgName }}
                    </div>
                    <div>
                        {{ data.label }}
                    </div>
                </el-tree>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup lang="ts">
//@ts-nocheck    解决ts类型报红
import { ref, onMounted, watch } from 'vue'
import lodash from 'lodash'
const props = defineProps(['nodeKey', 'fromData', 'toData', 'defaultProps', 'title'])
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
const checkedLeft = ref(false)
const checkedRight = ref(false)
const leftSearchText = ref('')
const rightSearchText = ref('')
const leftOperation = ref<any[]>([])
const rightOperation = ref<any[]>([])

// 定义emit
const emits = defineEmits(['change'])
const leftTreeRef = ref()
const rightTreeRef = ref()

// 左侧数据
const leftTreeData = ref([])
// 右侧数据
const rightTreeData = ref([])

watch(
  props,
  (newVal) => {
    leftTreeData.value = lodash.cloneDeep(newVal.fromData)
    rightTreeData.value = lodash.cloneDeep(newVal.toData)
  },
  { immediate: true }
)

watch(leftSearchText, (val) => {
  leftTreeRef.value?.filter(val)
})
watch(rightSearchText, (val) => {
  rightTreeRef.value?.filter(val)
})
defineExpose({
  leftTreeData,
  rightTreeData
})

onMounted(() => {
  leftSearchText.value = ''
  rightSearchText.value = ''
})

const removeChecked = (rightList, allCheckedKeys) => {
  rightList.forEach((e, index) => {
    if (allCheckedKeys.indexOf(e.id) == -1) {
      rightList.splice(index, 1)
    }
    if (e.children) {
      removeChecked(e.children, allCheckedKeys)
    }
  })
  return rightList
}
const recursionTree = (parents, allCheckedKeys) => {
  parents.forEach((e) => {
    const list = lodash.filter(e.children, (child) => allCheckedKeys.indexOf(child.id) >= 0)
    e.children = list
    if (e.children.length !== 0) {
      recursionTree(e.children, allCheckedKeys)
    }
  })
  return parents
}
const handleData = (rightNodes, leftTree, allCheckedKeys) => {
  rightNodes.forEach((item) => {
    if (item.children) {
      let parent = leftTree.getNode(item.pid)
      let node = item
      if (!leftTree.getNode(item.id) && allCheckedKeys.indexOf(item.id) > 0) {
        const copyNode = lodash.cloneDeep(node)
        copyNode.children = []
        leftTree.append(copyNode, parent)
      }
      handleData(item.children, leftTree, allCheckedKeys)
    } else {
      let parent = leftTree.getNode(item.pid)
      let node = item
      if (!leftTree.getNode(item.id) && allCheckedKeys.indexOf(item.id) > 0) {
        leftTree.append(node, parent)
      }
    }
  })
}
// // 去右边
const toRight = async () => {
  const leftTree = leftTreeRef.value
  checkedLeft.value = false
  if (!leftTree) {
    return
  }
  const leftNodes = leftTree.getCheckedNodes(false, true)
  const parents = leftNodes.filter((el) => el.children && el.children.length > 0 && el.pid == '0')
  const checkedKeys = leftTree.getCheckedKeys(false)
  const halfCheckedKeys = leftTree.getHalfCheckedKeys()
  const allCheckedKeys = halfCheckedKeys.concat(checkedKeys)
  const rightTree = rightTreeRef.value
  if (!rightTree) {
    let rightList = parents.map((parent) => {
      const obj = lodash.omit(parent, ['children'])
      obj.children = lodash.filter(
        parent.children,
        (child) => allCheckedKeys.indexOf(child.id) >= 0
      )
      return obj
    })

    let copyRightList = lodash.cloneDeep(rightList)
    removeChecked(copyRightList, allCheckedKeys)
    rightTreeData.value.push(...copyRightList)
  } else {
    let isExist = false
    rightTree.data.forEach((e) => {
      if (parents[0].id === (e as any).id) isExist = true
    })
    if (!isExist) {
      const overall = lodash.cloneDeep(parents)
      let rightList = await recursionTree(overall, allCheckedKeys)
      let copyRightList = lodash.cloneDeep(rightList)
      removeChecked(copyRightList, allCheckedKeys)
      rightTreeData.value.push(...copyRightList)
    } else {
      handleData(leftNodes, rightTree, allCheckedKeys)
    }
  }
  // 移过去之后要删除原本的值
  leftNodes.forEach((node) => {
    leftTree.setChecked(node, false, false)
    if (checkedKeys.indexOf(node.id) >= 0) {
      leftTree.remove(node)
    }
  })

  leftOperation.value = formatTree(leftTree.getCheckedNodes(false) || [], 'pid', 'id')
  // emits('change', leftTreeData.value, rightTreeData.value)
  emits('change', checkedKeys)
  setTimeout(() => {
    rightTree?.setCheckedNodes(leftNodes)
    rightOperation.value = formatTree(rightTree.getCheckedNodes(false) || [], 'pid', 'id')
    if (lodash.isEqual(rightOperation.value, rightTreeData.value)) {
      checkedRight.value = true
    } else {
      checkedRight.value = false
    }
  }, 500)
}
// // 去左边
const toLeft = async () => {
  checkedRight.value = false
  const rightTree = rightTreeRef.value
  if (!rightTree) {
    return
  }
  const rightNodes = rightTree.getCheckedNodes(false, true)
  const checkedKeys = rightTree.getCheckedKeys(false)
  const halfCheckedKeys = rightTree.getHalfCheckedKeys()
  const allCheckedKeys = halfCheckedKeys.concat(checkedKeys)

  const parents = lodash.filter(
    rightNodes,
    (item) => item.children && item.children.length > 0 && item.pid == '0'
  )
  const leftTree = leftTreeData.value.length ? leftTreeRef.value : null
  if (!leftTree) {
    let leftList = parents.map((parent) => {
      const obj = lodash.omit(parent, ['children'])
      obj.children = lodash.filter(
        parent.children,
        (child) => allCheckedKeys.indexOf(child.id) >= 0
      )
      return obj
    })
    let copyLeftList = lodash.cloneDeep(leftList)
    removeChecked(copyLeftList, allCheckedKeys)
    leftTreeData.value.push(...copyLeftList)
  } else {
    let isExist = false
    leftTree.data.forEach((e) => {
      if (parents[0].id === (e as any).id) isExist = true
    })
    if (!isExist) {
      const overall = lodash.cloneDeep(parents)
      let leftList = await recursionTree(overall, allCheckedKeys)
      let copyLeftList = lodash.cloneDeep(leftList)
      removeChecked(copyLeftList, allCheckedKeys)
      rightTreeData.value.push(...copyLeftList)
    } else {
      handleData(rightNodes, leftTree, allCheckedKeys)
    }
  }
  console.log(leftTreeData.value, 'slf-leftTreeData')

  rightNodes.forEach((node) => {
    rightTree.setChecked(node, false, false)
    if (checkedKeys.indexOf(node.id) >= 0) {
      rightTree.remove(node)
    }
  })

  rightOperation.value = formatTree(rightTree.getCheckedNodes(false) || [], 'pid', 'id')
  // emits('change', leftTreeData.value, rightTreeData.value)
  emits('change', checkedKeys)
}

//左侧选中(移除原本值用)
const onCheckLeft = () => {
  leftOperation.value = formatTree(leftTreeRef.value?.getCheckedNodes(false) || [], 'pid', 'id')

  if (lodash.isEqual(leftOperation.value, leftTreeData.value)) {
    checkedLeft.value = true
  } else {
    checkedLeft.value = false
  }
}
// 右侧选中
const onCheckRight = () => {
  rightOperation.value = formatTree(rightTreeRef.value!.getCheckedNodes(false) || [], 'pid', 'id')
  if (lodash.isEqual(rightOperation.value, rightTreeData.value)) {
    checkedRight.value = true
  } else {
    checkedRight.value = false
  }
}

const formatTree = (tree: any[], parentKey: string = 'pid', idKey: string = 'id') => {
  // 格式化选择的树:清除全选下面的子节点
  let swap,
    parentIds: string[] = []
  // 先找出有children的id集合,再把所有的数据做对比,只要pid和其中一个对上,就把该数据删除;
  tree.forEach((item, index) => {
    if (item.children) {
      parentIds.push(item[idKey])
    }
  })
  swap = tree.filter((item, index) => {
    if (parentIds.indexOf(item[parentKey]) == -1) {
      return item
    }
  })
  return swap
}

const leftAllCheck = () => {
  const leftTree = leftTreeRef.value
  if (checkedLeft.value) {
    leftTree?.setCheckedNodes(leftTreeData.value)

    checkedLeft.value = true
  } else {
    leftTree?.setCheckedNodes([])
    checkedLeft.value = false
  }
  onCheckLeft()
}
const onSearchLeft = () => {
  leftTreeRef.value?.filter(leftSearchText.value)
}

const rightAllCheck = () => {
  const rightTree = rightTreeRef.value
  if (checkedRight.value) {
    rightTree?.setCheckedNodes(rightTreeData.value)
    checkedRight.value = true
  } else {
    rightTree?.setCheckedNodes([])
    checkedRight.value = false
  }
  onCheckRight()
}
const onSearchRight = () => {
  rightTreeRef.value?.filter(rightSearchText.value)
}
const filterNode = (value: string, data: any) => {
  if (!value) return true
  return data.label.includes(value)
}
</script>
<style lang="scss" scoped>
.treeTransfer{
    display: flex;
    height: 400px;
    width: 100%;
    
    .btnDiv{
        flex:1;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
     
      .mg10{
        padding: 5px;
        :deep(svg) , :deep(.el-icon) {
            height: 2em !important;
            width: 2em !important;
        }
      
      }
    }
    .leftTree{
        width: 45%;
        border: 1px solid rgb(182, 182, 182);
    box-sizing: border-box;
    padding: 5px 10px;
        .list{
            height: 100%;
            display: flex;
          flex-direction: column;
        }
    }
    .rightTree{
    border: 1px solid rgb(182, 182, 182);
    box-sizing: border-box;
    padding: 5px 10px;
        width: 45%;
        .list{
            height: 100%;
            display: flex;
          flex-direction: column;
        }
    }
    .left_lowline{
        display: flex;
        align-items: center;
    }
    .right_lowline{
        display: flex;
        align-items: center;
    }
}
</style>

用法如下:

  <TreeTransfer
   style="margin-top: 20px;"
   ref="treeTransferRef" 
   node-key="id" 
 :fromData="fromData"
   :toData="toData"
   :defaultProps="transferProps" 
   :title="['请选择需要移动的通道或目录', '已挂载的通道或目录']"/>
let treeTransferRef = ref(); // 树形穿梭框
let fromData = ref([
      {
        id: "1",
        pid: 0,    //自定义pid的参数名,默认为"pid" 必填:false
        label: "一级 1",
        children: [
          {
            id: '1-2-1',
            pid: '1-2',
            children: [],
            label: '二级 1-2-1'
          },
          {
            id: '1-2-2',
            pid: '1-2',
            children: [],
            label: '二级 1-2-2'
          },
          {
            id: '1-2-3',
            pid: '1-2',
            children: [],
            label: '二级 1-2-3'
          }
        ]
      },
    ]); // 树形数据
let toData = ref([]); // 选中的ids数据
const transferProps = ref({
      label: 'label',
      children: 'children',
      disabled: 'disabled',
});

  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
可以使用Element Plus的Transfer组件结合Table组件来实现镶嵌表格穿梭组件。具体步骤如下: 1. 引入Transfer和Table组件 ```javascript import { Transfer, TableColumn } from 'element-plus'; ``` 2. 定义需要穿梭的数据和列配置 ```javascript // 穿梭左侧表格数据 const tableData = [ { id: 1, name: 'John Brown', age: 18, address: 'New York No.1 Lake Park' }, { id: 2, name: 'Jim Green', age: 24, address: 'London No.1 Lake Park' }, { id: 3, name: 'Joe Black', age: 30, address: 'Sydney No.1 Lake Park' }, { id: 4, name: 'Jon Snow', age: 26, address: 'Winterfell No.1 Castle' } ]; // 穿梭左侧表格列配置 const tableColumns = [ { label: '姓名', prop: 'name' }, { label: '年龄', prop: 'age' }, { label: '地址', prop: 'address' } ]; ``` 3. 在模板中使用Transfer和Table组件 ```html <el-transfer v-model="selectedData" :data="tableData" :titles="['可选数据', '已选数据']" > <el-table-column label="姓名" prop="name"></el-table-column> <el-table-column label="年龄" prop="age"></el-table-column> <el-table-column label="地址" prop="address"></el-table-column> </el-transfer> ``` 4. 完整示例代码 ```html <template> <div> <el-transfer v-model="selectedData" :data="tableData" :titles="['可选数据', '已选数据']" > <el-table-column label="姓名" prop="name"></el-table-column> <el-table-column label="年龄" prop="age"></el-table-column> <el-table-column label="地址" prop="address"></el-table-column> </el-transfer> </div> </template> <script> import { Transfer, TableColumn } from 'element-plus'; export default { components: { Transfer, TableColumn }, data() { return { tableData: [ { id: 1, name: 'John Brown', age: 18, address: 'New York No.1 Lake Park' }, { id: 2, name: 'Jim Green', age: 24, address: 'London No.1 Lake Park' }, { id: 3, name: 'Joe Black', age: 30, address: 'Sydney No.1 Lake Park' }, { id: 4, name: 'Jon Snow', age: 26, address: 'Winterfell No.1 Castle' } ], selectedData: [] }; } }; </script> ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云巅上的妖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值