?功能點:
- 點擊節點前的箭頭,可以手動展開或折疊該節點的子節點。
- 在搜索框中輸入關鍵詞,匹配的節點及其父節點會自動展開。
- 清空搜索框后,恢復到用戶手動控制的展開狀態。
- 勾選節點時仍然遵守 "最多勾選 6 個節點" 和 "只能選擇同級節點" 的限制。
import { Transfer, Tree, Input, message } from 'antd';
import React, { useState } from 'react';// 判斷是否已選中
const isChecked = (selectedKeys, eventKey) => selectedKeys.includes(eventKey);// 生成樹結構,并標記禁用狀態
const generateTree = (treeNodes = [], checkedKeys = []) =>treeNodes.map(({ children, ...props }) => ({...props,disabled: checkedKeys.includes(props.key),children: generateTree(children, checkedKeys),}));// 搜索功能:過濾樹節點并展開到匹配節點
const filterTreeData = (treeData, searchValue) => {const loop = (data) =>data.map((item) => {const match = item.title.toLowerCase().includes(searchValue.toLowerCase());const children = item.children ? loop(item.children) : [];return {...item,children,isMatch: match || children.some((child) => child.isMatch),};}).filter((item) => item.isMatch);return loop(treeData);
};// 自定義 TreeTransfer 組件
const TreeTransfer = ({ dataSource, targetKeys, ...restProps }) => {const transferDataSource = [];const [searchValue, setSearchValue] = useState('');const [expandedKeys, setExpandedKeys] = useState([]); // 控制展開的節點// 展平樹結構為一維數組function flatten(list = []) {list.forEach((item) => {transferDataSource.push(item);flatten(item.children);});}flatten(dataSource);// 動態計算需要展開的節點(用于搜索)const calculateExpandedKeys = (treeData) => {const keys = [];const loop = (data) =>data.forEach((item) => {if (item.isMatch) {keys.push(item.key);if (item.children) loop(item.children);}});loop(treeData);return keys;};// 過濾后的樹數據const filteredTreeData = filterTreeData(dataSource, searchValue);const autoExpandedKeys = calculateExpandedKeys(filteredTreeData);// 更新展開狀態const handleExpand = (newExpandedKeys) => {setExpandedKeys(newExpandedKeys);};return (<Transfer{...restProps}targetKeys={targetKeys}dataSource={transferDataSource}className="tree-transfer"render={(item) => item.title}showSelectAll={false}>{({ direction, onItemSelect, selectedKeys }) => {if (direction === 'left') {const checkedKeys = [...selectedKeys, ...targetKeys];return (<>{/* 搜索框 */}<Inputplaceholder="搜索節點"value={searchValue}onChange={(e) => {setSearchValue(e.target.value);setExpandedKeys(autoExpandedKeys); // 搜索時自動展開匹配節點}}style={{ marginBottom: 16 }}/><TreeblockNodecheckablecheckStrictlydefaultExpandAllcheckedKeys={checkedKeys}treeData={filteredTreeData}expandedKeys={searchValue ? autoExpandedKeys : expandedKeys} // 根據搜索狀態決定展開哪些節點onExpand={handleExpand} // 手動控制展開/折疊onCheck={(_, { node }) => {const { key, parentKey } = node;// 檢查是否在同一層級const isSameLevel = checkedKeys.every((k) => {const checkedNode = transferDataSource.find((item) => item.key === k);return !checkedNode || checkedNode.parentKey === parentKey;});// 檢查是否超過最大勾選數量限制const isWithinLimit = checkedKeys.length < 6 || checkedKeys.includes(key);if (isSameLevel && isWithinLimit) {onItemSelect(key, !isChecked(checkedKeys, key));} else {message.warn(!isSameLevel? '只能選擇同級節點': '最多只能同時勾選 6 個節點');}}}onSelect={(_, { node }) => {const { key, parentKey } = node;// 檢查是否在同一層級const isSameLevel = checkedKeys.every((k) => {const checkedNode = transferDataSource.find((item) => item.key === k);return !checkedNode || checkedNode.parentKey === parentKey;});// 檢查是否超過最大勾選數量限制const isWithinLimit = checkedKeys.length < 6 || checkedKeys.includes(key);if (isSameLevel && isWithinLimit) {onItemSelect(key, !isChecked(checkedKeys, key));} else {message.warn(!isSameLevel? '只能選擇同級節點': '最多只能同時勾選 6 個節點');}}}/></>);}}}</Transfer>);
};// 測試數據
const treeData = [{key: '0-0',title: 'Node 0-0',parentKey: null, // 根節點沒有父節點},{key: '0-1',title: 'Node 0-1',parentKey: null,children: [{key: '0-1-0',title: 'Node 0-1-0',parentKey: '0-1', // 父節點是 '0-1'},{key: '0-1-1',title: 'Node 0-1-1',parentKey: '0-1', // 父節點是 '0-1'},],},{key: '0-2',title: 'Node 0-3',parentKey: null,},
];// 主應用組件
const App = () => {const [targetKeys, setTargetKeys] = useState([]);const onChange = (keys) => {setTargetKeys(keys);};return <TreeTransfer dataSource={treeData} targetKeys={targetKeys} onChange={onChange} />;
};export default App;