当前位置: 首页 > news >正文

基于vue3和elementPlus的el-tree组件,实现树结构穿梭框,支持数据回显和懒加载

一、功能

功能描述

  • 数据双向穿梭:支持从左侧向右侧转移数据,以及从右侧向左侧转移数据。
  • 懒加载支持:支持懒加载数据,适用于大数据量的情况。
  • 多种展示形式:右侧列表支持以树形结构或列表形式展示。
  • 全选与反选:支持全选和全不选操作,以及保持树形结构的层级关系。
  • 搜索过滤:支持对左侧和右侧数据进行搜索过滤。
  • 自定义节点内容:支持自定义右侧列表中每个节点的内容

配置选项:
nodeKey:节点的主键,用于唯一标识每个节点。
leftTitlerightTitle:左侧和右侧树形列表的标题。
lazy:是否开启懒加载,当设置为 true 时,需要通过 loadMethod 方法加载数据。
loadMethod:懒加载时,用于加载数据的方法。
defaultProps:树节点的默认属性,包括标签、子节点和禁用状态。
leftDatarightData:左侧和右侧树形列表的数据。
defaultSelectionKeys:默认选中的数据的 ID 列表。
isSort:是否对右侧数据进行排序。
defaultExpandAll:是否默认展开所有节点。
checkOnClickNode:是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点。
expandOnClickNode:是否在点击节点时展开或收缩节点。
isToList:是否将右侧数据展示为列表形式。

事件:
checkVal:当选中数据发生变化时触发的事件,返回当前选中的数据。

二、使用

1、 tree to list

在这里插入图片描述
使用 :

<script lang="ts" setup>
import { ref } from 'vue'
const transferProps = ref({label: 'name',children: 'children',disabled: 'disabled',isLeaf: 'leaf',
})const checkVal = (val: any) => {console.log('checkVal  ; ', val)
}const loadNode = async (pid = 0) => {return new Promise((resolve) => {// 模拟网络请求延迟setTimeout(() => {// 假数据,树结构let dataif (pid === 0) {data = [{ pid: 0, id: 1, name: 'region' }]} else if (pid === 1) {data = [{ pid: 1, id: 2, name: 'region1-1' }, { pid: 1, id: 3, name: 'region1-2', leaf: true }]} else if (pid === 2) {data = [{ pid: 2, id: 4, name: 'region2-1' }, { pid: 2, id: 5, name: 'region2-2', leaf: true }, { pid: 2, id: 6, name: 'region2-3', leaf: true }]} else {data = []}// 返回对应父节点的子节点resolve(data || [])}, 300) // 模拟延迟})
}
</script><template><div><ZtTreeTransfer:default-props="transferProps":load-method="loadNode"node-key="id"is-select-all-nodesis-sortis-to-listlazy@check-val="checkVal"/></div>
</template>

2、 tree to tree

可以配置默认选中的数据的ids,显示在右侧列表,以实现数据回显
在这里插入图片描述
使用 :

<script lang="ts" setup>
import { ZtTreeTransfer } from '@zt-components/components'import { ref } from 'vue'const fromData = ref([{id: 1,label: '1Level one 1',children: [{id: 4,label: '1-1',children: [{id: 9,label: '1-1-1',},{id: 10,label: '1-1-2',},],},],},{id: 2,label: '2Level one 2',children: [{id: 5,label: '2-1',},{id: 6,label: '2-2',},],},{id: 3,label: '3Level one 31111111',children: [{id: 7,label: '3-111111111111111111',disabled: true,},{id: 8,label: 'Level two 3-21111111',disabled: true,children: [{id: 11,label: '4-111111111111111111111',},{id: 12,label: '4-211111111111111111111',},],},],},
]) // 树形数据
const toData = ref([9, 10]) // 选中的ids数据
const transferProps = ref({label: 'label',children: 'children',disabled: 'disabled',
})const checkVal = (val: any) => {console.log(val)
}
</script><template><div><ZtTreeTransfer:default-props="transferProps":default-selection-keys="toData":left-data="fromData"node-key="id"default-expand-allis-select-all-nodesis-sort@check-val="checkVal"/></div>
</template>

三、代码实现

<script lang="ts" setup>
import { computed, nextTick, ref, watch } from 'vue'
import { ArrowLeft, ArrowRight, Search } from '@element-plus/icons-vue'/* 定义props */
const props: TreeTransferProps = defineProps({// 主键nodeKey: {type: String,default: 'id',},// 左侧标题leftTitle: {type: String,default: () => {return '全部列表'},},// 右侧标题rightTitle: {type: String,default: () => {return '已选列表'},},// 是否开启懒加载lazy: { type: Boolean, default: false },// 懒加载时,加载数据的方法loadMethod: { type: Function, required: false },// tree绑定的propsdefaultProps: {type: Object,default: () => ({label: 'label',children: 'children',disabled: 'disabled',}),},// 左侧树结构数据leftData: {type: Array,default: () => {return []},},// 默认选中的数据的ids,显示在右侧列表defaultSelectionKeys: {type: Array,default: () => {return []},},// 右侧数据是否按顺序排序 仅在平铺展开是有效  只支持按住键正序排序isSort: {type: Boolean,},defaultExpandAll: {type: Boolean,default: false,},// 是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点。checkOnClickNode: {type: Boolean,default: false,},// 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。expandOnClickNode: {type: Boolean,default: true,},// 选择右侧所选数据的展示类型,默认是tree,true时为listisToList: {type: Boolean,default: false,},
}) // 又侧筛选条件/* 定义emit */
const emit = defineEmits(['checkVal'])/*** 定义props类型*/
export interface TreeTransferProps {nodeKey: anyleftTitle: anyrightTitle: anydefaultProps: anyleftData: anydefaultSelectionKeys: anyisSort: booleandefaultExpandAll: Array<any>checkOnClickNode: booleanexpandOnClickNode: booleanisToList: anyloadMethod: Functionlazy: boolean
}const isCheckedAllLeft = ref(false) // 左侧全选框是否选中
const isCheckedAllRight = ref(false) // 右侧全选框是否选中const isLeftCheckAllBoxDisabled = ref(false) // 左侧全选框是否禁用
const isRightCheckAllBoxDisabled = ref(false) // 右侧全选框是否禁用const leftTreeRef = ref() // 左侧树ref
const rightTreeRef = ref() // 右侧树refconst leftFilterText = ref('') // 左侧筛选条件
const rightFilterText = ref('')
const leftTreeData = ref([]) // 左侧tree数据
// 用于在右侧显示的数据列表
const rightData = ref([]) // 右侧列表结构数据
const rightTreeData = ref([]) // 右侧树结构数据// 数组打平
const flattenTree = (treeData: any[], defaultProps: any): any[] => {let flatData: any[] = []treeData.forEach((node) => {flatData.push(node)if (node[defaultProps.children] && node[defaultProps.children].length) {flatData = flatData.concat(flattenTree(node[defaultProps.children], defaultProps))}})return flatData
}// 校验树是否全选
const checkedAllTrue = (treeRef: any, treeData: any[], nodeKey: any, defaultProps: any): boolean => {// 校验是否全选const allKeys: string[] = treeRef.getCheckedKeys()const allNodes: any[] = flattenTree(treeData, defaultProps)const allKeysSet: Set<string> = new Set(allKeys)const allNodesSet: Set<string> = new Set(allNodes.map(node => node[nodeKey]))return allKeysSet.size === allNodesSet.size && [...allKeysSet].every(key => allNodesSet.has(key))
}// 深拷贝
const deepClone = (obj: any): any => {// 判断拷贝的obj是对象还是数组const objClone: any = Array.isArray(obj) ? [] : {}if (obj && typeof obj === 'object') {// obj不能为空,并且是对象或者是数组 因为null也是objectfor (const key in obj) {if (obj.hasOwnProperty(key)) {if (obj[key] && typeof obj[key] === 'object') {// obj里面属性值不为空并且还是对象,进行深度拷贝objClone[key] = deepClone(obj[key]) // 递归进行深度的拷贝} else {objClone[key] = obj[key] // 直接拷贝}}}}return objClone
}// 校验是否树节点是否全部禁用 nodes: []
const checkAllDisabled = (nodes: any[], defaultProps: any): boolean => {if (!(nodes && Array.isArray(nodes))) return falsefor (const node of nodes) {// 如果当前节点的disabled不是true,则直接返回falseif (!node[defaultProps.disabled]) {return false}// 如果当前节点有子节点,则递归检查子节点if (node[defaultProps.children]?.length) {const childrenAreDisabled = checkAllDisabled(node[defaultProps.children], defaultProps)// 如果子节点中有任何disabled不是true,则返回falseif (!childrenAreDisabled) {return false}}}// 如果所有节点的disabled都是true,则返回truereturn true
}// 设置数组的某个字段值为某个参数
const setFieldValue = (array: any[], field: string, value: any, defaultProps: any) => {// 遍历数组中的每个元素array.forEach((item) => {// 如果元素是对象且有属性,则设置字段值if (typeof item === 'object' && item !== null) {item[field] = value// 如果元素有子数组,递归调用函数if (Array.isArray(item[defaultProps.children])) {setFieldValue(item[defaultProps.children], field, value, defaultProps)}}})
}// 设置禁用
const setTreeIsDisabled = (data: any[], nodeKeysToDisable: string[], nodeKey: string, defaultProps: any, flag = true) => {if (!data || !data.length) returndata.forEach((item) => {if (nodeKeysToDisable && nodeKeysToDisable.length && nodeKeysToDisable.includes(item[nodeKey])) {// 如果当前节点的id主键在要禁用的id主键列表中,设置disabled为trueitem[defaultProps.disabled] = flag}// 如果当前节点有children,递归调用函数const itemChildren = item[defaultProps.children]if (itemChildren && Array.isArray(itemChildren)) {setTreeIsDisabled(itemChildren, nodeKeysToDisable, nodeKey, defaultProps, flag)}})
}// 获取数组中disabled的节点的Ids
const getDisabledNodeIds = (nodes: any[], nodeKey: string, defaultProps: any): string[] => {const disabledIds: string[] = []function traverse(node: any) {if (node.disabled) {disabledIds.push(node[nodeKey])}if (node[defaultProps.children]?.length) {node[defaultProps.children].forEach((child: any) => traverse(child))}}nodes.forEach(node => traverse(node))return disabledIds
}// 递归校验 当子节点全部被禁用时 ,则设置其父节点也禁用
const updateDisabledStatus = (nodes: any[], defaultProps: any) => {nodes.forEach((node) => {// 首先检查当前节点是否有子节点if (node[defaultProps.children]?.length) {// 假设当前节点的所有子节点都是禁用的let allChildrenDisabled = true// 递归检查所有子节点的disabled状态node[defaultProps.children].forEach((child: any) => {// 如果子节点有子节点,递归调用if (child[defaultProps.children]?.length) {updateDisabledStatus([child], defaultProps) // 递归更新子节点状态}// 更新子节点的disabled状态child[defaultProps.disabled] = child[defaultProps.children].length > 0? child[defaultProps.children].every((c: any) => c[defaultProps.disabled]): child[defaultProps.disabled]// 如果发现任何一个子节点没有被禁用,更新假设if (!child[defaultProps.disabled]) {allChildrenDisabled = false}})// 更新当前节点的disabled状态node[defaultProps.disabled] = allChildrenDisabled}})
}// 左侧输入框过滤事件
const filterLeftNode = (value, data) => {if (!value) return truereturn data[props.defaultProps.label].includes(value)
}// 右侧输入框过滤事件
const filterRightNode = (value, data) => {if (!value) return truereturn data[props.defaultProps.label].includes(value)
}// 右侧数据按顺序排序
const sortRightListByKey = () => {if (!props.isSort) return rightData.valuereturn rightData.value.sort((a, b) => a[props.nodeKey] - b[props.nodeKey])
}// 递归函数,用于构建只包含 ids 数组中 id 的树结构
const filterTreeByIds = (treeData, ids) => {return treeData.map((node) => {// 创建一个新节点对象,避免直接修改原始数据const newNode = { ...node }newNode[props.defaultProps.disabled] = false// 如果当前节点的 id 在 ids 中,保留这个节点及其子节点if (ids.includes(node[props.nodeKey])) {// 递归地过滤子节点newNode[props.defaultProps.children] = filterTreeByIds(node[props.defaultProps.children] || [], ids)} else {// 如果当前节点的 id 不在 ids 中,但有子节点,递归地过滤子节点// 同时,如果子节点中有至少一个节点的 id 在 ids 中,保留当前节点newNode[props.defaultProps.children] = filterTreeByIds(node[props.defaultProps.children] || [], ids).filter(child => child !== null)}// 如果当前节点的 id 不在 ids 中,且没有子节点或子节点都不在 ids 中,则不保留这个节点if (!ids.includes(node[props.nodeKey]) && (!newNode[props.defaultProps.children] || newNode[props.defaultProps.children].length === 0)) {return null}// 返回新的节点对象return newNode}).filter(node => node !== null) // 过滤掉 null 节点
}// 去右边
const toRight = () => {/*  右侧显示的数据获取 */rightTreeData.value = getRightTreeData()rightData.value = getRightListData()// 给父组件抛出已选择的数据checkVal()/**  更新移动后的左侧树的节点状态 和全选按钮状态*    先给所有已右移的节点设置禁用*    再通过递归计算是否将子节点的父节点也设置禁用(子节点全部禁用时,将其父节点也禁用)** */const rids = rightData.value.map(item => item[props.nodeKey])setTreeIsDisabled(leftTreeData.value, rids, props.nodeKey, props.defaultProps)updateDisabledStatus(leftTreeData.value, props.defaultProps)isLeftCheckAllBoxDisabled.value = checkAllDisabled(leftTreeData.value, props.defaultProps)
}
// 去左边
const toLeft = async () => {if (props.isToList) {// 获取当前右侧选中的数据,没有就returnconst listToLeftIds = rightData.value.filter(item => item.checked).map(item => item[props.nodeKey])if (!listToLeftIds.length) return// 从右侧去掉选中的数据,并将所有数据的checked设为false,避免由索引变更导致的异常选中const unselectedList = rightData.value.filter(item => !item.checked)rightData.value.map(item => (item.checked = false))rightData.value = unselectedList// 恢复选中数据在左侧的可选状态,并清除选中状态listToLeftIds.forEach(item => leftTreeRef.value.setChecked(item, false))setTreeIsDisabled(leftTreeData.value, listToLeftIds, props.nodeKey, props.defaultProps, false)updateDisabledStatus(leftTreeData.value, props.defaultProps)checkVal()isLeftCheckAllBoxDisabled.value = checkAllDisabled(leftTreeData.value, props.defaultProps)} else {// 获取当前右侧选中的数据,没有就returnconst treeToLeftIds = getRightTReeCheckedNodeIds()if (!treeToLeftIds.length) return// 恢复选中数据在左侧的可选状态,并清除选中状态setTreeIsDisabled(leftTreeData.value, treeToLeftIds, props.nodeKey, props.defaultProps, false)treeToLeftIds.forEach(item => leftTreeRef.value.setChecked(item, false))updateDisabledStatus(leftTreeData.value, props.defaultProps)checkVal()isLeftCheckAllBoxDisabled.value = checkAllDisabled(leftTreeData.value, props.defaultProps)rightTreeData.value = []rightTreeData.value = getRightTreeData()isCheckedAllRight.value = checkedAllTrue(rightTreeRef.value, rightTreeData.value, props.nodeKey, props.defaultProps)}
}// 获取右侧树中选中节点的Ids
const getRightTReeCheckedNodeIds = () => {// 返回全部节点填false, false ;返回叶子结点填true,trueconst checkNodeIds = rightTreeRef.value.getCheckedKeys(true)if (!checkNodeIds.length) return []return checkNodeIds
}// 左侧数据全选操作(全不选)
const handleLeftAllCheck = () => {const leftTree = leftTreeRef.valueconst disabledIds = getDisabledNodeIds(leftTreeData.value, props.nodeKey, props.defaultProps)if (isCheckedAllLeft.value) {/** 操作 : 设置全选* 逻辑 : 已经设置了disable的节点无法编辑选中,所以先获取所有设置了disable的节点的ids,然后将所有数据放开disable,设置全部选中,选中后再将ids中的节点设置禁用* */setFieldValue(leftTreeData.value, props.defaultProps.disabled, false, props.defaultProps)leftTree?.setCheckedNodes(leftTreeData.value)setTreeIsDisabled(leftTreeData.value, disabledIds, props.nodeKey, props.defaultProps)isCheckedAllLeft.value = true} else {/** 操作 : 设置全不选* 逻辑 : 已经设置disabled的节点不应该改变其选中和禁用状态 ,所以先获取所有禁用数据的ids(也就是checked=true的所有当前选中状态的数据),然后取消全部的选中状态,再将ids中的节点设置为选中状态* */leftTree?.setCheckedNodes([])disabledIds.forEach(item => leftTreeRef.value.setChecked(item, true))isCheckedAllLeft.value = false}
}
// 左侧树节点checkbox被点击
const handleLeftCheckChange = () => {isCheckedAllLeft.value = checkedAllTrue(leftTreeRef.value, leftTreeData.value, props.nodeKey, props.defaultProps)
}// 右侧树节点checkbox被点击
const handleRightCheckChange = () => {isCheckedAllRight.value = checkedAllTrue(rightTreeRef.value, rightTreeData.value, props.nodeKey, props.defaultProps)
}// 右侧数据全选操作(全不选)
const handleRightAllCheck = () => {// listsetFieldValue(rightData.value, 'checked', isCheckedAllRight.value, props.defaultProps)// treerightTreeRef.value.setCheckedNodes(isCheckedAllRight.value ? rightTreeData.value : [])
}// 返回已选数据给父组件
const checkVal = () => {emit('checkVal', props.isToList ? rightData.value : leftTreeRef.value.getCheckedNodes(true))
}const walkTreeData = (nodes, selectedKeys) => {const ret = []nodes.forEach((node) => {const newNode = { ...node }newNode[props.defaultProps.disabled] = falsedelete newNode[props.defaultProps.children]node[props.defaultProps.children] && (newNode[props.defaultProps.children] = walkTreeData(node[props.defaultProps.children], selectedKeys))if (selectedKeys.includes(newNode[props.nodeKey]) || (newNode[props.defaultProps.children] && newNode[props.defaultProps.children].length)) {ret.push(newNode)}})return ret
}// 获取右侧list结构数据
const getRightListData = () => {/*  右侧list结构数据获取 */if (!currentLeftUseableNodes.value.length) return []const newArr = rightData.value.concat(currentLeftUseableNodes.value)const obj: any = {}// 去重const peon: any = newArr.reduce((cur, next) => {obj[next[props.nodeKey]] ? '' : (obj[next[props.nodeKey]] = true && cur.push(next))cur.checked = falsereturn cur}, []) // 设置cur默认类型为数组,并且初始值为空的数组return peon
}// 获取右侧树结构数据
const getRightTreeData = () => {if (!leftTreeRef.value || !rightTreeRef.value) return []const checkedKeys = leftTreeRef.value.getCheckedKeys(false) // 当前选中节点 key 的数组const halfCheckedKeys = leftTreeRef.value.getHalfCheckedKeys() // 目前半选中的节点的 key 所组成的数组const allCheckedKeys = halfCheckedKeys.concat(checkedKeys)if (allCheckedKeys && allCheckedKeys.length) {return walkTreeData(leftTreeData.value, allCheckedKeys)} else {return []}
}// 获取左侧树当前所选的可进行右移操作的数据
const currentLeftUseableNodes = computed(() => {if (!leftTreeRef.value) return []// 返回全部节点填false ;返回叶子结点填trueconst checkNodes = leftTreeRef.value.getCheckedNodes(true) // 将返回当前选中节点的节点数组if (!checkNodes.length) return []// 过滤当前已选,如果没有选择新的数据就returnconst useableNodes = checkNodes.filter(item => !item[props.defaultProps.disabled])if (!useableNodes.length) return []return useableNodes
})// 左移按钮disabled计算
const isToLeftBtnDisabled = computed(() => {let checkNodes = []rightTreeRef.value && (checkNodes = rightTreeRef.value.getCheckedNodes(false, false)) // tree选择的节点const listToLeftIds = rightData.value.filter(item => item.checked).map(item => item[props.nodeKey]) // list选择的节点return !(listToLeftIds.length || checkNodes.length)
})// 更新 treeData 中的指定节点,添加子节点
const updateTreeData = (targetNode: any, childNodes: any) => {const recursiveUpdate = (nodes: any) => {for (const node of nodes) {if (node[props.nodeKey] === targetNode[props.nodeKey]) {node[props.defaultProps.children] = childNodes // 将子节点添加到目标节点} else if (node[props.defaultProps.children]) {recursiveUpdate(node[props.defaultProps.children]) // 递归查找目标节点}}}if (!Object.keys(leftTreeData.value).length) {leftTreeData.value = childNodesreturn}recursiveUpdate(leftTreeData.value)
}//  懒加载方法
const handleLoadNode = (node: any, resolve: any) => {if (props.lazy) {const pid = node.level === 0 ? 0 : node.data[props.nodeKey]props.loadMethod(pid).then((res: any) => {if (res || Array.isArray(res)) {// 更新 treeData,确保包含懒加载的节点// 在节点展开时,确保 treeData 是最新的完整结构resolve(res)} else {resolve([])}updateTreeData(node.data, res)}).catch((err: any) => {console.error('Failed to load node data:', err)resolve([])})} else {resolve(node.data[props.defaultProps.children] || [])}
}// 监听右侧数据变化,判断右侧全选框是否选中
watch(() => rightData.value,(newData) => {if (!newData || !props.isToList) returnisCheckedAllRight.value = newData.length && newData.every(item => item.checked)},{deep: true,immediate: true,},
)// 初始化操作,将传参的默认选中节点传递并显示到右侧
watch(() => props.defaultSelectionKeys,(newKeys) => {if (props.lazy && props.loadMethod) returnif (!newKeys?.length) returnnextTick(async () => {// 设置目前选中的节点await leftTreeRef.value.setCheckedKeys(newKeys)toRight()})},{deep: true,immediate: true,},
)// 初始化操作,将传参的默认选中节点传递并显示到右侧
watch(() => props.leftData,(newData) => {// 如果是懒加载,并且有loadMethod方法,直接returnif (props.lazy && props.loadMethod) return// 没有数据就returnif (!newData?.length) returnleftTreeData.value = deepClone(newData)setFieldValue(leftTreeData.value, props.defaultProps.disabled, false, props.defaultProps)},{deep: true,immediate: true,},
)watch(leftFilterText, (val) => {leftTreeRef.value!.filter(val)
})
</script><template><div class="zt-tree-transfer"><!-- 左边 --><div class="left-content"><div class="list"><div class="left_lowline"><el-checkboxv-model="isCheckedAllLeft":disabled="isLeftCheckAllBoxDisabled"label=""size="large"@change="handleLeftAllCheck"/><p class="left_title">{{ leftTitle }}</p></div><!-- 搜索 --><div class="left_input"><el-inputv-model="leftFilterText":prefix-icon="Search"class="w-50 m-2"placeholder="搜索"clearable/></div><div class="left-tree"><el-treeref="leftTreeRef"v-slot="{ node, data }":check-on-click-node="checkOnClickNode":data="leftTreeData":default-expand-all="defaultExpandAll":expand-on-click-node="expandOnClickNode":filter-node-method="filterLeftNode":lazy="lazy":load="handleLoadNode":node-key="nodeKey":props="defaultProps"highlight-currentshow-checkbox@check-change="handleLeftCheckChange"/></div></div></div><!-- 中间按钮 --><div class="btn-div"><div class="btn-item" @click="toRight()"><el-button:disabled="!currentLeftUseableNodes.length":icon="ArrowRight"size="large"type="primary"/></div><div class="btn-item" @click="toLeft()"><el-button:disabled="isToLeftBtnDisabled":icon="ArrowLeft"size="large"type="primary"/></div></div><!-- 右边 --><div class="righ-content"><div class="list"><div class="left_lowline"><el-checkboxv-model="isCheckedAllRight":disabled="isRightCheckAllBoxDisabled"label=""size="large"@change="handleRightAllCheck"/><p class="left_title">{{ rightTitle }}</p></div><!-- 搜索 --><div class="left_input"><el-inputv-model="rightFilterText":prefix-icon="Search"class="w-50 m-2"placeholder="搜索"clearable/></div><!--    右侧数据展示格式为list时    --><div v-if="isToList"><!--   根据[props.nodeKey]排序  ;  根据rightFilterText进行过滤显示    --><divv-for="(item, index) in sortRightListByKey().filter((item) => item[defaultProps.label].includes(rightFilterText))"v-if="sortRightListByKey().filter((item) => item[defaultProps.label].includes(rightFilterText)).length":key="index"class="right_item"><!-- 检查是否有名为 "right-item" 的插槽内容 --><slotv-if="$slots['right-item']":index="index":item="item"name="right-item"></slot><!-- 如果没有,则显示默认内容 --><div v-else><el-checkboxv-model="item.checked":false-label="false":true-label="true":value="item[nodeKey]">{{ item[defaultProps.label] }}</el-checkbox></div></div><div v-else style="padding: 10px"><el-text type="info">暂无数据</el-text></div></div><!--    右侧数据展示格式为tree时    --><div v-else class="right-tree"><el-treeref="rightTreeRef"v-slot="{ node, data }":check-on-click-node="checkOnClickNode":data="rightTreeData":default-expand-all="defaultExpandAll":expand-on-click-node="expandOnClickNode":filter-node-method="filterRightNode":node-key="nodeKey":props="defaultProps"highlight-currentshow-checkbox@check-change="handleRightCheckChange"/></div></div></div></div>
</template><style lang="less" scoped>
.zt-tree-transfer {display: flex;height: 500px;width: 800px;box-sizing: border-box;.btn-div {flex: 1;height: 60%;margin: auto;display: flex;flex-direction: column;justify-content: space-evenly;align-items: center;.btn-item {:deep(svg),:deep(.el-icon) {height: 1.6em !important;width: 1.6em !important;}}}.left-content {width: 45%;border: 1px solid #dcdfe6;box-sizing: border-box;padding: 5px 10px;.list {width: 100%;height: 100%;display: flex;flex-direction: column;overflow: hidden;.left-tree {width: calc(100% - 5px);height: 100%;overflow: auto;margin-top: 10px;padding-right: 5px;}}}.righ-content {box-sizing: border-box;border: 1px solid #dcdfe6;padding: 5px 10px;width: 45%;overflow: auto;.right_item {text-align: left;}.list {height: 100%;display: flex;flex-direction: column;}}.left_lowline {display: flex;align-items: center;}.right_lowline {display: flex;align-items: center;}:deep(.el-input__wrapper) {position: relative;.el-input__inner {padding-right: 18px;}.el-input__suffix {position: absolute;right: 8px;top: 50%;transform: translateY(-50%);}}// 滚动条宽度::-webkit-scrollbar {width: 6px;height: 6px;}// 滚动条轨道::-webkit-scrollbar-track {background: rgb(239, 239, 239);border-radius: 2px;}// 小滑块::-webkit-scrollbar-thumb {background: #40a0ff49;border-radius: 2px;}::-webkit-scrollbar-thumb:hover {background: #40a0ff;}:deep(.el-button:focus) {outline: none;}:deep(.el-tree) {display: inline-block;min-width: 100%;.el-tree-node__content {//margin-right: 5px;}}
}
</style>

相关文章:

基于vue3和elementPlus的el-tree组件,实现树结构穿梭框,支持数据回显和懒加载

一、功能 功能描述 数据双向穿梭&#xff1a;支持从左侧向右侧转移数据&#xff0c;以及从右侧向左侧转移数据。懒加载支持&#xff1a;支持懒加载数据&#xff0c;适用于大数据量的情况。多种展示形式&#xff1a;右侧列表支持以树形结构或列表形式展示。全选与反选&#xf…...

彻底理解链表(LinkedList)结构

目录 比较操作结构封装单向链表实现面试题 循环链表实现 双向链表实现 链表&#xff08;Linked List&#xff09;是一种线性数据结构&#xff0c;由一组节点&#xff08;Node&#xff09;组成&#xff0c;每个节点包含两个部分&#xff1a;数据域&#xff08;存储数据&#xff…...

TON 区块链开发的深入概述#TON链开发#DAPP开发#交易平台#NFT#Gamefi链游

区块链开发领域发展迅速&#xff0c;各种平台为开发人员提供不同的生态系统。其中一个更有趣且越来越相关的区块链是TON&#xff08;开放网络&#xff09;区块链。TON 区块链最初由 Telegram 构思&#xff0c;旨在提供快速、安全且可扩展的去中心化应用程序 (dApp)。凭借其独特…...

Hive专栏概述

Hive专栏概述 Hive“出身名门”&#xff0c;是最初由Facebook公司开发的数据仓库工具。它简单且容易上手&#xff0c;是深入学习Hadoop技术的一个很好的切入点。专栏内容包括&#xff1a;Hive的安装和配置&#xff0c;其核心组件和架构&#xff0c;Hive数据操作语言&#xff0c…...

鼠标悬停后出现小提示框实现方法

大家在网页上会经常看到某些图标或文字&#xff0c;当鼠标悬停后会在四周某个位置出现一个简短的文字提示&#xff0c;这种提示分为两种&#xff0c;一种是提示固定的文字&#xff0c;例如放在qq图标上&#xff0c;会显示固定的文字“QQ”&#xff1b;第二种是显示鼠标所在标签…...

计算机视觉常用数据集Foggy Cityscapes的介绍、下载、转为YOLO格式进行训练

我在寻找Foggy Cityscapes数据集的时候花了一番功夫&#xff0c;因为官网下载需要用公司或学校邮箱邮箱注册账号&#xff0c;等待审核通过后才能进行下载数据集。并且一开始我也并不了解Foggy Cityscapes的格式和内容是什么样的&#xff0c;现在我弄明白后写下这篇文章&#xf…...

css中的样式穿透

1. >>> 操作符 <style scoped> /* 影响子组件的样式 */ .parent >>> .child {color: red; } </style>注意&#xff1a;>>> 操作符在某些预处理器&#xff08;如Sass&#xff09;中可能无法识别&#xff0c;因为它不是标准的CSS语法。 …...

MMCA:多模态动态权重更新,视觉定位新SOTA | ACM MM‘24 Oral

来源&#xff1a;晓飞的算法工程笔记 公众号&#xff0c;转载请注明出处 论文: Visual Grounding with Multi-modal Conditional Adaptation 论文地址&#xff1a;https://arxiv.org/abs/2409.04999论文代码&#xff1a;https://github.com/Mr-Bigworth/MMCA 创新点 提出了多模…...

linux同步执行命令脚本 (xcall)

linux同步执行命令脚本 (xcall) 1、在/usr/local/bin目录下 创建xcall文件 vim /usr/local/bin/xcall2、输入内容 #!/bin/bash # 获取控制台指令 判断指令是否为空 pcount$# if((pcount0)); thenecho "command can not be null !"exit fifor host in bigdata01 …...

opencv - py_imgproc - py_grabcut GrabCut 算法提取前景

文章目录 使用 GrabCut 算法进行交互式前景提取目标理论演示 使用 GrabCut 算法进行交互式前景提取 目标 在本章中 我们将了解 GrabCut 算法如何提取图像中的前景我们将为此创建一个交互式应用程序。 理论 GrabCut 算法由英国剑桥微软研究院的 Carsten Rother、Vladimir K…...

ChatGPT多模态命名实体识别

ChatGPT多模态命名实体识别 ChatGPT辅助细化知识增强&#xff01;![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/025e651de3ef440a90cbe05fa9971409.png)一、研究背景二、模型结构和代码任务流程第一阶段&#xff1a;辅助精炼知识启发式生成第二阶段&#xff1a;基于…...

04-Dubbo的通信协议

04-Dubbo的通信协议 Dubbo 支持的通信协议 Dubbo 框架提供了自定义的高性能 RPC 通信协议&#xff1a; 基于 TCP 的 Dubbo2 协议 基于 HTTP/2 的 Triple 协议 Dubbo 框架是不和任何通信协议绑定的&#xff0c;对通信协议的支持非常灵活&#xff0c;支持任意的第三方协议&#x…...

开源数据库 - mysql - innodb源码阅读 - 线程启动

线程启动源码 /** Start up the InnoDB service threads which are independent of DDL recovery.*/void srv_start_threads() {if (!srv_read_only_mode) {/* Before 8.0, it was master thread that was doing periodicalcheckpoints (every 7s). Since 8.0, it is the log …...

在美团外卖上抢券 Python来实现

在美团外卖上抢券的 Python 实现 在如今的互联网时代&#xff0c;自动化脚本已经成为了许多用户生活中不可或缺的工具。尤其是在购物、抢券等场景中&#xff0c;自动化脚本能够帮助我们节省大量的时间和精力。今天&#xff0c;我们将一起探索如何使用 Python 编写一个简单的脚…...

【ONLYOFFICE 文档 8.2 版本深度测评】功能革新与用户体验的双重飞跃

引言 在数字化办公的浪潮中&#xff0c;ONLYOFFICE 文档以其强大的在线协作功能和全面的办公套件解决方案&#xff0c;赢得了全球用户的青睐。随着 8.2 版本的发布&#xff0c;ONLYOFFICE 再次证明了其在办公软件领域的创新能力和技术实力。 一.协作编辑 PDF&#xff1a;团队合…...

npm入门教程18:npm发布npm包

一、准备工作 注册npm账号&#xff1a; 前往npm官网注册一个账号。注册过程中需要填写个人信息&#xff0c;并完成邮箱验证。 安装Node.js和npm&#xff1a; 确保你的计算机上已安装Node.js和npm。Node.js的安装包中通常包含了npm。你可以通过运行node -v和npm -v命令来检查它…...

VueSSR详解 VueServerRenderer Nutx

SSR Vue中的SSR&#xff08;Server-Side Rendering&#xff0c;服务器端渲染&#xff09;是一种将页面的渲染工作从客户端转移到服务器端的技术。以下是对Vue中SSR的详细解释&#xff1a; 一、SSR的工作原理 在传统的客户端渲染&#xff08;CSR&#xff09;中&#xff0c;页面的…...

构建您自己的 RAG 应用程序:使用 Ollama、Python 和 ChromaDB 在本地设置 LLM 的分步指南

在数据隐私至关重要的时代&#xff0c;建立自己的本地语言模型 &#xff08;LLM&#xff09; 为公司和个人都提供了至关重要的解决方案。本教程旨在指导您完成使用 Ollama、Python 3 和 ChromaDB 创建自定义聊天机器人的过程&#xff0c;所有这些机器人都托管在您的系统本地。以…...

谷歌浏览器安装axure插件

1.在生成静态原型页面的路径下&#xff0c;找到resources\chrome\axure-chrome-extension.crx&#xff0c;这就是需要的插件了。 2.将axure-chrome-extension.crx重命名成axure-chrome-extension.zip然后解压到指定的文件夹&#xff08;这个文件夹不能删除, 例如解压到了扩展程…...

Java唯一键实现方案

数据唯一性 1、生成UUID1.1 代码中实现1.2 数据库中实现优点缺点 2、数据库递增主键优点 3、数据库递增序列3.1 创建序列3.2 使用序列优点缺点 在Java项目开发中&#xff0c;对数据的唯一性要求&#xff0c;业务数据入库的时候保持单表只有一条记录&#xff0c;因此对记录中要求…...

opencv - py_imgproc - py_canny Canny边缘检测

文章目录 Canny 边缘检测目标理论OpenCV 中的 Canny 边缘检测其他资源 Canny 边缘检测 目标 在本章中&#xff0c;我们将学习 Canny 边缘检测的概念用于该目的的 OpenCV 函数&#xff1a;cv.Canny() 理论 Canny 边缘检测是一种流行的边缘检测算法。它由 John F. Canny 于1…...

Spring Boot 创建项目详细介绍

上篇文章简单介绍了 Spring Boot&#xff08;Spring Boot 详细简介&#xff01;&#xff09;&#xff0c;还没看到的读者&#xff0c;建议看看。 下面&#xff0c;介绍一下如何创建一个 Spring Boot 项目&#xff0c;以及自动生成的目录文件作用。 Maven 构建项目 访问 http…...

70B的模型需要多少张A10的卡可以部署成功,如果使用vLLM

部署一个 70B 的模型&#xff08;如 defog/sqlcoder-70b-alpha&#xff09;通常需要考虑多个因素&#xff0c;包括模型的内存需求和你的 GPU 配置。 1. 模型内存需求 大约计算&#xff0c;一个 70B 参数的模型在使用 FP16 精度时大约需要 280 GB 的 GPU 内存。对于 A10 GPU&a…...

clickhouse配置用户角色与权限

首先找到user.xml文件&#xff0c;默认在/etc/clickhouse-server路径下 一、配置角色 找到标签定义 <aaaa><readonly>1</readonly><allow_dll>0</allow_dll> </aaaa>其中aaaa为角色名称&#xff0c;readonly为只读权限&#xff08;0–代表…...

面试题整理 4

总结整理了某公司面试中值得记录的笔试和问到的问题和答案。 目录 PHP传值和传引用区别&#xff1f;什么情况下用传值&#xff1f;什么情况下用传引用&#xff1f; 传值 传引用 区别 选择传值还是传引用时 简述PHP的垃圾回收机制 二维数组排序 什么是CSRF攻击&#xff…...

React基础大全

文章目录 一、React基本介绍1.虚拟DOM优化1.1 原生JS渲染页面1.2 React渲染页面 2.需要提前掌握的JS知识 二、入门1.React基本使用2.创建DOM的两种方式2.1 使用js创建&#xff08;一般不用&#xff09;2.2 使用jsx创建 3.React JSX3.1 JSX常见语法规则3.2 for循环渲染数据 4.模…...

51c大模型~合集10

我自己的原文哦~ https://blog.51cto.com/whaosoft/11547799 #Llama 3.1 美国太平洋时间 7 月 23 日&#xff0c;Meta 公司发布了其最新的 AI 模型 Llama 3.1&#xff0c;这是一个里程碑时刻。Llama 3.1 的发布让我们看到了开源 LLM 有与闭源 LLM 一较高下的能力。 Meta 表…...

【已解决】element-plus配置主题色后,sass兼容问题。set-color-mix-level() is...in Dart Sass 3

项目&#xff1a;vue3vite "scripts": {"dev": "vite","build": "vite build","preview": "vite preview"},"dependencies": {"element-plus/icons-vue": "^2.3.1",&quo…...

JavaWeb——Web入门(4/9)-HTTP协议:请求协议(请求行、请求头、请求体、演示 )

目录 请求协议概述 请求行 请求头 请求体 演示 GET POST 请求协议概述 介绍完了 HTTP 协议的概念以及特点之后&#xff0c;接下来介绍 HTTP 当中的请求协议。 请求协议指的就是请求数据的格式。 HTTP 请求协议在整个 Web 通信中起着至关重要的作用。当用户在浏览器…...

软考:数据库考点总结

结构冲突 在数据库领域&#xff0c;冲突主要指的是在并发操作中&#xff0c;多个事务试图同时访问或修改相同的数据资源&#xff0c;导致数据一致性、完整性和隔离性受到威胁。以下是数据库中常见的几种冲突类型&#xff1a; 读写冲突&#xff08;Read-Write Conflict&#xf…...