Antd Desgin 穿梭框
- 普通用法
- 高级用法-表格穿梭框组件
- 高级用法-树穿梭框组件
普通用法
/* eslint-disable no-unused-vars */
import React, { useEffect, useState } from 'react'
import { Space, Transfer } from 'antd'// Antd的穿梭框组件Mock数据
const mockData = Array.from({length: 20
}).map((_, i) => ({key: i.toString(),title: `content${i + 1}`,description: `description of content${i + 1}`,disabled: i % 3 < 1 // 禁用某项
}))// 筛选出ID数组
const initialTargetKeys = mockData.filter(item => Number(item.key) > 10).map(item => item.key)const App = () => {// 设置目标键数组const [targetKeys, setTargetKeys] = useState(initialTargetKeys)// 设置选中的键数组const [selectedKeys, setSelectedKeys] = useState([])useEffect(() => {console.log('模拟数据', mockData)}, [])const onChange = (nextTargetKeys, direction, moveKeys) => {console.log('==========Start Change==========')console.log('targetKeys:', nextTargetKeys) // 下一次的目标键数组,即移动后的目标列表console.log('direction:', direction) // 移动的方向,可以是'left'或'right',表示从左侧列表移动到右侧列表或从右侧列表移动到左侧列表console.log('moveKeys:', moveKeys) // 移动的键数组,即移动的项console.log('==========End Change==========')setTargetKeys(nextTargetKeys)}const onSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {console.log('==========Start SelectChange==========')console.log('sourceSelectedKeys:', sourceSelectedKeys) // 源列表中选中的键数组console.log('targetSelectedKeys:', targetSelectedKeys) // 目标列表中选中的键数组console.log('==========End SelectChange==========')setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys])}const onScroll = (direction, e) => {console.log('==========Start Scroll==========')console.log('direction:', direction) // 滚动的方向,可以是'left'或'right',表示向左滚动或向右滚动console.log('target:', e.target) // 滚动事件对象,包含了滚动的相关信息,如滚动的目标等console.log('==========End Scroll==========')}console.log('==========Start Search==========')const handleSearch = (dir, value) => {// dir 表示搜索框所在的列表,可以是'left'或'right',表示在源列表或目标列表中搜索\// value 表示搜索框中的值console.log('search:', dir, value)}console.log('==========End Search==========')return (<div className="App"><Space><TransferdataSource={mockData} // 数据源,即需要在两个列表之间移动的数据列表titles={['Source', 'Target']} // 列表的标题,包括源列表和目标列表的标题targetKeys={targetKeys} // 目标列表中的键数组,表示当前已经选中的项的键数组selectedKeys={selectedKeys} // 当前选中的项的键数组,用于在两个列表之间移动项时的状态管理onChange={onChange} // 当目标列表中的键数组改变时触发的事件回调函数onSelectChange={onSelectChange} // 当源列表和目标列表中的选中项改变时触发的事件回调函数onScroll={onScroll} // 当滚动时触发的事件回调函数onSearch={handleSearch} // 当搜索框中的值改变时触发的事件回调函数render={item => item.title} // 定义如何渲染每个数据项,返回一个React元素oneWay // 是否只允许从左侧列表向右侧列表移动数据,默认为falseshowSearch // 是否显示搜索框,默认为falsepagination // 是否显示分页,默认为false,一般在大数据量下使用/>{/* 自定义状态 */}<Transfer status="error" /><Transfer status="warning" showSearch /></Space></div>)
}export default App
高级用法-表格穿梭框组件
/* eslint-disable react/prop-types */
/* eslint-disable no-unused-vars */
import React, { useState } from 'react'
import { Space, Switch, Table, Tag, Transfer } from 'antd'// leftColumns 表示左侧表格的列,rightColumns表示右侧表格的列,restProps表示其他属性
const TableTransfer = ({ leftColumns, rightColumns, ...restProps }) => (// 渲染Transfer组件<Transfer {...restProps}>{({direction, // 数据传输方向(左或右)filteredItems, // 经过搜索过滤后的项onItemSelect, // 选中项时的回调函数onItemSelectAll, // 全选项时的回调函数selectedKeys: listSelectedKeys, // 已选中项的键数组disabled: listDisabled // 列表是否被禁用的标志}) => {const columns = direction === 'left' ? leftColumns : rightColumns // 根据传输方向选择表格列const rowSelection = {getCheckboxProps: () => ({disabled: listDisabled // 设置复选框是否禁用}),onChange(selectedRowKeys) {onItemSelectAll(selectedRowKeys, 'replace') // 全选/取消全选时的操作},selectedRowKeys: listSelectedKeys, // 已选中项的键数组selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT, Table.SELECTION_NONE] // 表格行选择器}return (<TablerowSelection={rowSelection} // 表格行选择器配置columns={columns} // 表格列配置dataSource={filteredItems} // 数据源size="small" // 表格尺寸style={{pointerEvents: listDisabled ? 'none' : undefined // 根据列表是否禁用设置CSS样式}}onRow={({ key, disabled: itemDisabled }) => ({// 表格行的事件处理函数onClick: () => {if (itemDisabled || listDisabled) {// 如果项被禁用或列表被禁用,则不执行操作return}onItemSelect(key, !listSelectedKeys.includes(key)) // 选中/取消选中项时的操作}})}/>)}}</Transfer>
)const mockTags = ['cat', 'dog', 'bird'] // 模拟标签数据
const mockData = Array.from({// 生成模拟数据length: 20
}).map((_, i) => ({key: i.toString(), // 唯一键title: `content${i + 1}`, // 标题description: `description of content${i + 1}`, // 描述tag: mockTags[i % 3] // 标签
}))
// 表格列配置
const columns = [{dataIndex: 'title',title: 'Name'},{dataIndex: 'tag',title: 'Tag',render: tag => (<Tagstyle={{marginInlineEnd: 0}}color="cyan">{tag.toUpperCase()}</Tag>)},{dataIndex: 'description',title: 'Description'}
]
const Default = () => {const [targetKeys, setTargetKeys] = useState([]) // 目标键数组的状态及其更新函数const [disabled, setDisabled] = useState(false) // 禁用状态及其更新函数const onChange = nextTargetKeys => {// 目标键数组变化时的处理函数setTargetKeys(nextTargetKeys) // 更新目标键数组}const toggleDisabled = checked => {// 切换禁用状态的处理函数setDisabled(checked) // 更新禁用状态}return (<><TableTransfer // 表格数据传输组件dataSource={mockData} // 数据源targetKeys={targetKeys} // 目标键数组disabled={disabled} // 是否禁用showSearch // 是否显示搜索框showSelectAll={false} // 是否显示全选按钮onChange={onChange} // 目标键数组变化时的回调函数filterOption={(inputValue,item // 自定义搜索过滤函数) => item.title.indexOf(inputValue) !== -1 || item.tag.indexOf(inputValue) !== -1}leftColumns={columns} // 左侧表格列配置rightColumns={columns} // 右侧表格列配置/><Spacestyle={{marginTop: 16}}>{/* 开关组件,用于切换禁用状态 */}<Switch unCheckedChildren="disabled" checkedChildren="disabled" checked={disabled} onChange={toggleDisabled} /></Space></>)
}
export default Default
高级用法-树穿梭框组件
未完善
TreeTransfer.jsx
树穿梭框组件
/* eslint-disable no-unused-vars */
/* eslint-disable react/prop-types */
import React, { useEffect, useState } from 'react'
import { Transfer, Tree } from 'antd'const generateTree = (treeNodes = [], checkedKeys = [], parentKeys = []) =>treeNodes.map(({ children, ...props }) => {const updatedProps = {...props,disabled: checkedKeys.includes(props.key),children: generateTree(children, checkedKeys, parentKeys.concat(props.key))}// 父节点如果被选中,则添加所有子节点到 checkedKeysif (checkedKeys.includes(props.key)) {updatedProps.children.forEach(child => {if (!checkedKeys.includes(child.key)) {checkedKeys.push(child.key)}})}return updatedProps})const TreeTransfer = ({ dataSource, ...restProps }) => {const [selectedKeys, setSelectedKeys] = useState([])const [targetKeys, setTargetKeys] = useState([])useEffect(() => {console.log(selectedKeys, 'selectedKeys')}, [selectedKeys, setSelectedKeys])// 子节点全部选中时,让父节点也会被选中// key 表示当前节点,checkedKeys 是当前目标源keys数组,dataSource 是数据数组const updateParentKeys = (key, checkedKeys, dataSource) => {console.log(key, '当前节点', checkedKeys, '当前目标源keys数组', dataSource, '数据数组')// 对 checkedKeys 做浅拷贝,以避免直接修改原数组const updatedKeys = [...checkedKeys]// 查找包含指定子节点键 key 的父节点const parentNode = dataSource.find(item => item.children && item.children.some(child => child.key === key))if (parentNode) {// 如果找到了父节点// 检查父节点的所有子节点是否都在 updatedKeys 中const allChildrenChecked = parentNode.children.every(child => updatedKeys.includes(child.key))// 如果所有子节点都被选中且父节点未被选中,则将父节点添加到 updatedKeys 中if (allChildrenChecked && !updatedKeys.includes(parentNode.key)) {updatedKeys.push(parentNode.key)} else if (!allChildrenChecked && updatedKeys.includes(parentNode.key)) {// 如果存在未被选中的子节点且父节点被选中,则从 updatedKeys 中移除父节点updatedKeys.splice(updatedKeys.indexOf(parentNode.key), 1)}// 递归更新父节点的父节点,确保所有相关节点的选中状态都被正确更新return updateParentKeys(parentNode.key, updatedKeys, dataSource)}// 如果没有找到父节点,则直接返回 updatedKeysreturn updatedKeys}const handleCheck = (checkedKeys, { node: { key, children } }) => {let cKeys = [...selectedKeys] // 复制当前已选择的键数组// 如果点击的节点已经在已选择数组中,则从数组中移除if (cKeys.includes(key)) {cKeys = cKeys.filter(item => item !== key)if (children && children.length > 0) {const checkList = dataSource.filter(item => item.key === key).map(item => {return [key, ...item.children.map(child => child.key)]}).flat()console.log(checkList, '取消选择的父节点')// 使用 Array.prototype.filter() 来移除整个节点数组cKeys = cKeys.filter(item => !checkList.includes(item))} else {// 如果点击的是子节点,则检查父节点是否需要从已选择数组中移除cKeys = updateParentKeys(key, cKeys, dataSource)}} else {// 将当前节点添加到已选择数组中cKeys.push(key)// 如果点击的是父节点,则同时将子节点也添加到已选择数组中if (children && children.length > 0) {children.forEach(child => {cKeys.push(child.key)})} else {// 如果点击的是子节点,则检查父节点是否需要添加到已选择数组中cKeys = updateParentKeys(key, cKeys, dataSource)}}setSelectedKeys(cKeys)}const onChange = (t, d, m) => {setTargetKeys(selectedKeys)}return (<Transfer{...restProps}targetKeys={targetKeys}selectedKeys={selectedKeys}dataSource={dataSource}onChange={onChange}render={item => item.title}showSelectAll={false}oneWay>{({ direction }) => {if (direction === 'left') {const checkedKeys = [...selectedKeys, ...targetKeys]return (<TreeblockNodecheckablecheckStrictlydefaultExpandAllcheckedKeys={checkedKeys}treeData={generateTree(dataSource, targetKeys)}onCheck={handleCheck}onSelect={handleCheck}/>)}}}</Transfer>)
}export default TreeTransfer
Index.jsx
/* eslint-disable no-unused-vars */
/* eslint-disable react/prop-types */
import React, { useState } from 'react'
import TreeTransfer from './dom';const treeData = [{key: '0-0',title: '0-0'},{key: '0-1',title: '0-1',children: [{key: '0-1-0',title: '0-1-0'},{key: '0-1-1',title: '0-1-1'}]},{key: '0-2',title: '0-2'},{key: '0-3',title: '0-3'},{key: '0-4',title: '0-4'}
]const Index = () => {return <TreeTransfer dataSource={treeData} />
}
export default Index