自定义树形穿梭框组件
因项目需求是库表结构,这里做的是动态加载子选项,默认不显示表名选项,点击库名再去动态加载并显示该库中的所有表,效果如下:
.ts代码:
// 自定义树形穿梭框 import React, { useEffect, useState } from 'react'; import { Tree, Checkbox, Button, Input, message } from 'antd'; import { CaretDownOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons'; import './TreeSelectTransfer.less'; import { ApiRes } from '@/api/config'; const { Search } = Input; const { TreeNode } = Tree; const getLeftAndRightKey = (data, rightDataKey = {}) => { let len = data.length; let leftData: any = new Array(len).fill('').map(() => { return { children: [] }; }); let rightData: any = new Array(len).fill('').map(() => { return { children: [] }; }); let leftTotalCount = 0; let rightTotalCount = 0; data.map((item, index) => { if (!item.children) { leftData[index].name = item.name; leftData[index].key = index + ''; } else { item.children.map((childItem, childIndex) => { let nowKey = `${childItem.id}`; if (nowKey in rightDataKey) { rightData[index].name = item.name; rightData[index].key = index + ''; rightData[index].children.push(childItem); rightTotalCount += 1; } else { leftTotalCount += 1; leftData[index].name = item.name; leftData[index].key = index + ''; leftData[index].children.push(childItem); } }); } }); return { leftData, rightData, leftTotalCount, rightTotalCount }; }; // 搜索过滤 const filterData = (data, filterValue) => { let filterTrimV = filterValue.trim(); if (!filterTrimV) { return data; } data = data.filter((item) => { if (item.name && item.name.includes(filterTrimV)) { return true; } else if (item.children && item.children.length) { item.children = item.children.filter((childItem) => { if (childItem.name.includes(filterTrimV)) { return true; } else { return false; } }); return item.children.length > 0; } return true; }); return data; }; function TreeSelectTransfer(props) { const [leftSelectKey, setLeftSelectKey] = useState({}); const [rightDataKey, setRightDataKey] = useState({}); const [rightSelectKey, setRightSelectKey] = useState({}); const [leftFValue, setLeftFValue] = useState(''); const [rightFValue, setRightFValue] = useState(''); const [selectKeys, setSelectKeys] = useState([]); const [dataSource, setDataSource] = useState([]); const [data, setData] = useState([]); const [childrenData, setChildrenData] = useState([]); const [fLeftData, setFLeftData] = useState([]); const [leftData, setLeftData] = useState([]); const [rightData, setRightData] = useState([]); const [fRightData, setFRightData] = useState([]); const [leftSelectAll, setLeftSelectAll] = useState(true); const [rightSelectAll, setRightSelectAll] = useState(true); const [leftTotalCount, setLeftTotalCount] = useState(null); const [rightTotalCount, setRightTotalCount] = useState(null); const { titles = [] } = props; // 全选 const selectOrCancelAll = (type, selectKeys, data, isSelectAll) => () => { if (isSelectAll) { if (type === 'leftSelectKey') { setLeftSelectKey({}); } else { setRightSelectKey({}); } } else { let newSelectKeys = { ...selectKeys }; data.map((item) => { item.children.map((childItem) => { if (!(childItem.key in newSelectKeys)) { newSelectKeys[childItem.id] = childItem.name; } }); }); if (type === 'leftSelectKey') { setLeftSelectKey(newSelectKeys); } else { setRightSelectKey(newSelectKeys); } } }; const selectGoToRight = () => { const { handleChangeTable } = props; const dataKey = { ...rightDataKey, ...leftSelectKey }; let targetKeys = Object.keys(dataKey); setLeftSelectKey({}); setRightDataKey(dataKey); if (typeof handleChangeTable == 'function') { handleChangeTable(targetKeys, 'right', selectKeys); } }; const selectGoToLeft = () => { const { handleChangeTable } = props; for (let key in rightSelectKey) { delete rightDataKey[key]; } let targetKeys = Object.keys(rightDataKey); setRightSelectKey({}); setRightDataKey(rightDataKey); if (typeof handleChangeTable == 'function') { handleChangeTable(targetKeys, 'left', selectKeys); } }; const bindCheckLeft = (checkedKeys, e) => { if (!checkedKeys.length) { setLeftSelectKey({}); } if (e.checkedNodes.length) { const temp = {}; const keys = []; e.checkedNodes.map((item) => { if (!item.children) { temp[item.key] = item.title; keys.push(item.key); } }); setLeftSelectKey(temp); setSelectKeys(keys); } }; const bindCheckRight = (checkedKeys, e) => { if (!checkedKeys.length) { setRightSelectKey({}); } if (e.checkedNodes.length) { const temp = {}; const keys = []; e.checkedNodes.map((item) => { if (!item.children) { temp[item.key] = item.title; keys.push(item.key); } }); setRightSelectKey(temp); setSelectKeys(keys); } }; const searchOnChange = (type, e) => { const { value } = e.target; if (type == 'left') { setLeftFValue(value); } else { setRightFValue(value); } }; const onSelectNode = (selectedKeys, node) => { const { Api, dbId } = props.childrenParams; Api({ dbId, schemaName: node.node.title }).then((res: ApiRes) => { if (res.code === 200) { const temp = res.data.length && res.data.map((item, index) => { return { id: item.tableId, key: `${selectedKeys}-${index}`, name: item.tableName }; }); dataSource.length && dataSource.map((item) => { if (item.name === node.node.title) { item.children = temp; } }); setChildrenData(temp); setData(dataSource); } else { message.error(res.message); } }); }; useEffect(() => { if (props.tableCacheMap.length) { setDataSource(props.tableCacheMap); setData(props.tableCacheMap); } }, [props.tableCacheMap]); useEffect(() => { let { leftData, rightData, leftTotalCount, rightTotalCount } = getLeftAndRightKey(data, rightDataKey); setLeftData(leftData); setRightData(rightData); setLeftTotalCount(leftTotalCount); setRightTotalCount(rightTotalCount); setLeftSelectAll(leftSelectCount == leftTotalCount); setRightSelectAll(rightSelectCount == rightTotalCount); const leftList = filterData(leftData, leftFValue); const rightList = filterData(rightData, rightFValue); setFLeftData(leftList); setFRightData(rightList); if (leftSelectCount === 0) { setLeftSelectAll(false); } if (rightSelectCount === 0) { setRightSelectAll(false); } }, [data, childrenData, Object.keys(rightDataKey).length, leftFValue, rightFValue, leftSelectKey, rightSelectKey]); useEffect(() => {}, [leftData, rightData, leftFValue]); let leftSelectCount = Object.keys(leftSelectKey).length; let rightSelectCount = Object.keys(rightSelectKey).length; let isHaveLeftChecked = leftSelectCount > 0; let isHaveRightChecked = rightSelectCount > 0; return ( <div className="tree-select-transfer"> <div className="tst-l"> <div className="tst-header"> <Checkbox indeterminate={!leftSelectAll && isHaveLeftChecked} onChange={selectOrCancelAll('leftSelectKey', leftSelectKey, leftData, leftSelectAll)} checked={leftSelectAll} > {`${leftSelectCount ? leftSelectCount + '/' : ''}${leftTotalCount} 项`} </Checkbox> {titles[0] ? <span style={{ float: 'right' }}>{titles[0]}</span> : null} </div> <Search style={{ marginBottom: 8 }} placeholder="Search" onChange={(e) => searchOnChange('left', e)} /> <div style={{ overflow: 'auto', height: '300px' }}> <Tree checkable showIcon defaultExpandAll onCheck={bindCheckLeft} checkedKeys={Object.keys(leftSelectKey)} switcherIcon={<CaretDownOutlined />} onSelect={onSelectNode} > {fLeftData.map((item) => { if (!!item.name) { return ( <TreeNode title={item.name} key={item.key} checkable={!!item.children.length}> {item.children.map((childItem) => { return <TreeNode key={childItem.id} title={childItem.name} />; })} </TreeNode> ); } })} </Tree> </div> </div> <div className="tst-m "> <Button type="primary" style={{ marginBottom: 4 }} disabled={!isHaveLeftChecked} onClick={selectGoToRight} > <RightOutlined /> </Button> <Button type="primary" disabled={!isHaveRightChecked} onClick={selectGoToLeft}> <LeftOutlined /> </Button> </div> <div className="tst-r"> <div className="tst-header"> <Checkbox indeterminate={!rightSelectAll && isHaveRightChecked} checked={rightSelectAll} onChange={selectOrCancelAll('rightSelectKey', rightSelectKey, rightData, rightSelectAll)} > {`${rightSelectCount ? rightSelectCount + '/' : ''}${rightTotalCount} 项`} </Checkbox> {titles[1] ? <span style={{ float: 'right' }}>{titles[1]}</span> : null} </div> <Search style={{ marginBottom: 8 }} placeholder="Search" onChange={(e) => searchOnChange('right', e)} /> <div style={{ overflow: 'auto', height: '300px' }}> <Tree checkable showIcon defaultExpandAll onCheck={bindCheckRight} checkedKeys={Object.keys(rightSelectKey)} switcherIcon={<CaretDownOutlined />} > {fRightData.map((item) => { if (!!item.name) { return ( <TreeNode title={item.name} key={item.key}> {item.children.map((childItem) => { let isChecked = childItem.key in rightSelectKey; return ( <TreeNode icon={<Checkbox checked={isChecked} />} key={childItem.id} title={childItem.name} /> ); })} </TreeNode> ); } })} </Tree> </div> </div> </div> ); } export default TreeSelectTransfer;
样式 .less:
.tree-select-transfer { overflow: hidden; width: 832px; height: 400px; display: flex; align-items: center; vertical-align: middle; .tst-l, .tst-r { background-color: #fff; flex: 1; border: 1px solid #d9d9d9; display: inline-block; border-radius: 4px; position: relative; height: 100%; padding: 0 8px; padding-top: 40px; width: 0; .tst-header { height: 34px; padding: 6px 12px; border-radius: 4px 4px 0 0; color: #000; border-bottom: 1px solid #e8e8e8; overflow: hidden; position: absolute; top: 0; left: 0; width: 100%; } .ant-tree-node-content-wrapper .ant-tree-iconEle { display: none; } .ant-tree-child-tree .ant-tree-node-content-wrapper .ant-tree-iconEle { display: inline-block; } .ant-tree-child-tree .ant-tree-switcher { display: none; } .ant-input-suffix { color: rgba(0,0,0,0.45); cursor: pointer; &:hover { color: rgba(0,0,0,0.8); } } } .tst-m { display: grid; height: 60px; margin: 0 20px; bottom { width: 32px; } } }
页面引入:
import React, { useState, useEffect } from 'react'; import { listDsSchemaName, listTablesByDsIdAndSchemaName } from '@/api/offlineTask/index'; import TreeSelectTransfer from '@/components/treeSelectTransfer'; function BigTask(props) { const [tableCacheMap, setTableCacheMap] = useState([]); // 获取所有库名 function getListDsSchemaName(dbId) { ... } // 数据表API接口及参数 const childrenParams = { Api: listTablesByDsIdAndSchemaName, dbId: props.syncForm.dbId }; // 点击库名获取改库下所有数据表 function handleChangeTable(targetKeys, direction, moveKeys) { ... } return ( <TreeSelectTransfer tableCacheMap={tableCacheMap} childrenParams={childrenParams} handleChangeTable={handleChangeTable} /> ) } export default BigTask;