import React, {
ReactNode,
FC,
useState,
useEffect,
useContext,
createContext,
useCallback,
useMemo
} from 'react'
import styled from 'styled-components'
import { Checkbox, Input, Tree, TreeProps } from 'antd'
import { LeftOutlined, RightOutlined } from '@ant-design/icons'
const { Search } = Input
export type treeDataType = Required<Pick<TreeProps, 'treeData'>>
export type onCheckType = Required<Pick<TreeProps, 'onCheck'>>
export interface FunEntryType {
treeData: any[]
callback: (
item: {
title: string
key: string
children: any[]
[key: string]: any
},
index: number,
parentId: string | null | number
) => void
replaceName?: string
parentId?: string | null | number
}
export interface FlatEntryType extends treeDataType {
treeData: any[]
callback?: (
item: {
title: string
key: string
children: any[]
[key: string]: any
},
index: number
) => void
replaceName?: string
}
export interface reorganizeEntryType {
treeArr: Array<any>
id?: string | null | number
link?: string
idName?: string
}
export interface TreeShuttleBoxRightPropsType {}
export interface shuttleLeftProps {
handleChange?: (value: string[]) => void
}
const treeData_ = [
{
key: '1',
title: '1',
children: [
{
key: '89083790301',
title: '(Newogbed',
children: [
{
key: '2553780501',
title: 'VH-Dogobile',
children: null
},
{
key: '30140750601',
title: 'VH-Dtop',
children: [
{
key: '255120501',
title: 'VH-Dogbed',
children: null
},
{
key: '30140601',
title: 'VH-Dktop',
children: null
}
]
}
]
}
]
},
{
key: '60941980901',
title: 'VHktop',
children: null
},
{
key: '6624220001',
title: 'VMobile',
children: null
},
{
key: '72095520101',
title: 'bile',
children: null
},
{
key: '98673349201',
title: 'Vktop',
children: null
}
]
const TreeShuttleContainer = styled.div`
width: ${({ isOnlyLeft }: { isOnlyLeft: boolean }) =>
isOnlyLeft ? 360 : 792}px;
height: 400px;
display: flex;
justify-content: center;
align-items: center;
`
const CustomTransferBox = styled.div`
width: 358px;
height: 398px;
background: #ffffff;
border: 1px solid #dddddd;
border-radius: 5px;
`
const CustomTransferButtonBox = styled.div`
width: 24px;
margin: 0 24px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.active {
background: #ffaa00;
border: 1px solid #ffaa00;
}
.icon-color {
color: rgba(0, 0, 0, 0.25);
}
.icon-color-active {
color: #ffffff;
}
`
const CustomTransferButton = styled.div`
width: 23px;
height: 23px;
background: rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 3px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
& + & {
margin-top: 12px;
}
`
const CustomTransferHead = styled.div`
height: 39px;
border-bottom: 1px solid #dddddd;
padding: 13px 0 12px 8px;
display: flex;
justify-content: flex-start;
align-items: center;
line-height: 14px;
.select-count {
font-size: 12px;
font-family: Helvetica, Helvetica-Regular;
font-weight: 400;
text-align: left;
color: #555555;
padding: 0;
margin: 0 0 0 8px;
}
`
const CustomTransferBody = styled.div`
padding: 8px;
`
const CustomTransferTree = styled.div`
height: 300px;
overflow: auto;
margin-top: 6px;
/* 最多显示两行,超过就隐藏 */
.ant-tree-title {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-all;
}
`
interface TreeShuttleBoxType {
({
children,
defaultValue,
handleChange,
isOnlyLeft
}: {
children: ReactNode
defaultValue?: Array<string>
handleChange?: (value: string[]) => void
isOnlyLeft?: boolean
}): JSX.Element
TreeShuttleBoxLeft: FC<shuttleLeftProps & treeDataType>
TreeShuttleBoxRight: FC<TreeShuttleBoxRightPropsType>
TreeShuttleButton: FC
}
type contextType = ReturnType<typeof useTreeShuttle>
const TreeShuttleContext = createContext<contextType | undefined>(undefined)
interface TreeProviderProps {
children: ReactNode
value: any
}
function TreeProvider({ children, value }: TreeProviderProps) {
return (
<TreeShuttleContext.Provider value={value}>
{children}
</TreeShuttleContext.Provider>
)
}
const useTreeShuttle = () => {
const [finallyValue, setFinallyValue] = useState<Array<string>>([]) // 最终返回给用户的key数组
const handleFinallyValueChange = (value: string[]) => {
setFinallyValue(value)
}
const [defaultValue, setDefaultValue] = useState<string[]>([]) // 主要用于保存默认值
const handleDefaultValueChange = (arr: string[]) => {
setDefaultValue(arr)
}
const [sonParMap, setSonParMap] = useState<Map<string, string | null>>(
new Map()
) // 保存子节点key和父节点key的Map,主要用于子级查找父级
const handleSonParMapChange = (map: Map<string, string | null>) => {
setSonParMap(map)
}
const [flatArray, setFlatArray] = useState<any[]>([]) // 保存展平的树数据,用于重组树形结构
const handleFlatArrayChange = (value: any[]) => {
setFlatArray(value)
}
// 左边的树形选择框
const [checkListLeft, setCheckListLeft] = useState<string[]>([]) // 存储左侧选择框选中的key
const handleCheckListLeftChange = (value: string[]) => {
setCheckListLeft(value)
}
const [isBrightLeft, setIsBrightLeft] = useState<boolean>(false) // 控制左侧按钮是否高亮
const handleIsBrightLeftChange = (isBright: boolean) => {
setIsBrightLeft(isBright)
}
const [readOnlyAllKeyLeft, setReadOnlyAllKeyLeft] = useState<string[]>([]) // readonly 左边全部的key
const handleReadOnlyAllKeyLeftChange = (arr: string[]) => {
setReadOnlyAllKeyLeft(arr)
}
const [indeterminateLeft, setIndeterminateLeft] = useState<boolean>(false) // 主要处理左侧的半选
const handleIndeterminateLeftChange = (boo: boolean) => {
setIndeterminateLeft(boo)
}
const [dataSourceLeft, setDataSourceLeft] = useState<treeDataType>({
treeData: []
}) // 左侧树形选择框的数据源
const handleDataSourceLeftChange = ({ treeData }: treeDataType) => {
setDataSourceLeft({ treeData })
}
const [readOnlyTreeDataSource, setReadOnlyTreeDataSource] =
useState<treeDataType>({
treeData: []
}) // 保存左侧树的数据源,用于只读,搜索
const handleReadOnlyTreeDataSourceChange = ({ treeData }: treeDataType) => {
setReadOnlyTreeDataSource({ treeData })
}
// 右边的树形选择框
const [checkListRight, setCheckListRight] = useState<string[]>([]) // 存储在右侧选择框中的key
const handleCheckListRightChange = (value: string[]) => {
setCheckListRight(value)
}
const [dataSourceRight, setDataSourceRight] = useState<treeDataType>({
treeData: []
}) // 右侧树形选择框的数据源
const handleDataSourceRightChange = ({ treeData }: treeDataType) => {
setDataSourceRight({ treeData })
}
const [readOnlyAllKeyRight, setReadOnlyAllKeyRight] = useState<string[]>([]) // 右侧所有的key
const handleReadOnlyAllKeyRightChange = (arr: string[]) => {
setReadOnlyAllKeyRight(arr)
}
const [isBrightRight, setIsBrightRight] = useState<boolean>(false) // 控制右侧按钮是否高亮
const handleIsBrightRightChange = (isBright: boolean) => {
setIsBrightRight(isBright)
}
const [readOnlyDataSourceRight, setReadOnlyDataSourceRight] =
useState<treeDataType>({
treeData: []
}) // 右侧只读的树形数据,主要用于搜索
const handleReadOnlyDataSourceRightChange = ({ treeData }: treeDataType) => {
setReadOnlyDataSourceRight({ treeData })
}
return {
finallyValue,
checkListLeft,
checkListRight,
dataSourceRight,
dataSourceLeft,
defaultValue,
flatArray,
sonParMap,
isBrightLeft,
isBrightRight,
indeterminateLeft,
readOnlyAllKeyRight,
readOnlyDataSourceRight,
readOnlyAllKeyLeft,
readOnlyTreeDataSource,
handleFinallyValueChange,
handleCheckListLeftChange,
handleCheckListRightChange,
handleDataSourceRightChange,
handleDataSourceLeftChange,
handleFlatArrayChange,
handleSonParMapChange,
handleReadOnlyAllKeyRightChange,
handleReadOnlyDataSourceRightChange,
handleIsBrightLeftChange,
handleIsBrightRightChange,
handleIndeterminateLeftChange,
handleDefaultValueChange,
handleReadOnlyAllKeyLeftChange,
handleReadOnlyTreeDataSourceChange
}
}
const useTreeContext = () => {
const context = useContext(TreeShuttleContext)
if (!context) {
throw new Error('context必须在Provider中使用!!')
}
return context
}
const traverseTreeData = ({
treeData = [],
callback = () => {},
parentId = null,
replaceName = 'children'
}: FunEntryType) => {
// 遍历树形数据
treeData.forEach((item: any, index) => {
callback(item, index, parentId)
if (item[replaceName] && item[replaceName].length > 0) {
traverseTreeData({
treeData: item[replaceName],
callback,
parentId: item.key,
replaceName
})
}
})
}
const flatTree = ({
treeData = [],
callback = () => {},
replaceName = 'children'
}: FlatEntryType) => {
// 扁平化树形数据结构
let flatArr: any[] = []
treeData.forEach((item, index) => {
const copyItem = { ...item }
if (item[replaceName]) {
delete copyItem[replaceName]
}
callback(copyItem, index)
flatArr.push(copyItem)
if (item[replaceName] && item[replaceName].length > 0) {
flatArr = flatArr.concat(
flatTree({ treeData: item[replaceName], replaceName, callback })
)
}
})
return flatArr
}
const searchTree = (searchValue = '', treeArr: any[] = []) => {
// 树形数据搜索
let searTreeArr: any[] = []
treeArr?.forEach((treeItem: { title: string; children: any[] }) => {
if (treeItem.title.includes(searchValue)) {
searTreeArr.push(treeItem)
} else {
if (treeItem.children && treeItem.children.length) {
const chr = searchTree(searchValue, treeItem.children)
const obj = {
...treeItem,
children: chr
}
if (chr && chr.length) {
searTreeArr.push(obj)
}
}
}
})
return searTreeArr
}
const reorganizeTree = ({
treeArr = [],
id = null,
link = 'parentId',
idName = 'key'
}: reorganizeEntryType): any[] =>
// 重组树形数据
treeArr
.filter((itemF) => itemF[link] === id)
.map((itemM) => ({
...itemM,
children: reorganizeTree({
treeArr,
id: itemM[idName],
link,
idName
})
}))
const searchAllParents = (
arr: string[],
sonParMap: Map<string, string | null>
): string[] => {
let allKey: string[] = []
function search(key: string) {
allKey.push(key)
const nextKey = sonParMap.get(key)
if (nextKey) {
search(nextKey)
}
}
arr.forEach((key) => {
search(key)
})
return Array.from(new Set(allKey))
}
const TreeShuttleButton: FC = () => {
const {
checkListLeft,
checkListRight,
flatArray,
sonParMap,
dataSourceLeft,
finallyValue,
handleDataSourceRightChange,
handleReadOnlyAllKeyRightChange,
handleFinallyValueChange,
handleDataSourceLeftChange,
handleReadOnlyDataSourceRightChange,
isBrightRight,
handleIsBrightRightChange,
isBrightLeft,
handleIsBrightLeftChange,
handleCheckListRightChange,
handleCheckListLeftChange,
indeterminateLeft,
handleIndeterminateLeftChange,
defaultValue,
readOnlyAllKeyLeft,
readOnlyAllKeyRight,
readOnlyTreeDataSource,
readOnlyDataSourceRight
} = useTreeContext()
const [handleClickToRight, handleClickToLeft] = useMemo(() => {
const handleClickToRight = () => {
if (checkListLeft.length === 0 && !isBrightRight) {
return
}
// 取消左侧的半选
handleIndeterminateLeftChange(false)
if (readOnlyAllKeyLeft.length === checkListLeft.length) {
// 左侧全选逻辑
// 右侧数据源保存
handleDataSourceRightChange(
JSON.parse(JSON.stringify(readOnlyTreeDataSource))
)
// 保存右侧只读的数据源所有的key
handleReadOnlyAllKeyRightChange([...checkListLeft])
// 保存最终值的key
handleFinallyValueChange([...checkListLeft])
// 保存右侧只读的数据源用于搜索
handleReadOnlyDataSourceRightChange({
treeData: JSON.parse(JSON.stringify(checkListLeft))
})
// 禁用左侧树已经选择的节点
const treeData_ = JSON.parse(
JSON.stringify(readOnlyTreeDataSource.treeData)
)
traverseTreeData({
treeData: treeData_,
callback(item) {
item['disabled'] = true
}
})
handleDataSourceLeftChange({
treeData: treeData_
})
} else {
// 左侧不全选逻辑
// 得到子级的key 和 半选状态的 父级的key 用于重组数组
let allKey = searchAllParents(checkListLeft, sonParMap)
allKey = Array.from(new Set(allKey.concat(finallyValue)))
// 得到选中的key 所对应的节点
const selectNode = flatArray.filter(({ key }) => allKey.includes(key))
// 重组树形数据结构
const treeData = reorganizeTree({
treeArr: selectNode
})
// 右侧数据源保存
handleDataSourceRightChange({ treeData })
// 保存右侧只读的数据源所有的key
handleReadOnlyAllKeyRightChange(allKey)
// 保存最终值的key
handleFinallyValueChange(allKey)
// 保存右侧只读的数据源用于搜索
handleReadOnlyDataSourceRightChange({
treeData: JSON.parse(JSON.stringify(treeData))
})
// 禁用左侧树已经选择的节点
traverseTreeData({
treeData: dataSourceLeft.treeData,
callback(item) {
item['disabled'] = allKey.includes(item.key)
}
})
handleDataSourceLeftChange({
treeData: [...dataSourceLeft.treeData]
})
}
// 右侧按钮取消高亮
handleIsBrightRightChange(false)
}
const handleClickToLeft = () => {
if (checkListRight.length === 0 && !isBrightLeft) {
return
}
if (checkListRight.length === readOnlyAllKeyRight.length) {
// 右侧全选逻辑
// 保存左侧选择的key
handleCheckListLeftChange([])
// 保存右侧的数据源
handleDataSourceRightChange({ treeData: [] })
// 保存右侧所有的key
handleReadOnlyAllKeyRightChange([])
// 更改最终的key值
handleFinallyValueChange([])
// 更改右侧选中的key值
handleCheckListRightChange([])
// 保存右侧只读的数据源
handleReadOnlyDataSourceRightChange({
treeData: []
})
// 保存左侧的数据源
handleDataSourceLeftChange(
JSON.parse(JSON.stringify(readOnlyTreeDataSource))
)
} else {
// 右侧不全选逻辑
// 在最终值里删除右边勾选的key
const finallyArr = finallyValue.filter(
(key) => !checkListRight.includes(key)
)
// 筛选右侧的树形数据
const selectNode = flatArray.filter(({ key }) =>
finallyArr.includes(key)
)
// 重组右侧树形数据
const treeData = reorganizeTree({
treeArr: selectNode
})
// 更新左侧选择的key
const selectKeyLeft = checkListLeft.filter(
(key) => !checkListRight.includes(key)
)
// 保存左侧选择的key
handleCheckListLeftChange(selectKeyLeft)
// 保存右侧的数据源
handleDataSourceRightChange({ treeData })
// 保存右侧所有的key
handleReadOnlyAllKeyRightChange(finallyArr)
// 更改最终的key值
handleFinallyValueChange(finallyArr)
// 更改右侧选中的key值
handleCheckListRightChange([])
// 保存右侧只读的数据源
handleReadOnlyDataSourceRightChange({
treeData: JSON.parse(JSON.stringify(treeData))
})
// 取消左侧树数据源的禁用
traverseTreeData({
treeData: dataSourceLeft.treeData,
callback(item) {
item['disabled'] = finallyArr.includes(item.key)
}
})
// 保存左侧的数据源
handleDataSourceLeftChange({
treeData: JSON.parse(JSON.stringify(dataSourceLeft.treeData))
})
}
// 取消左侧高亮显示
handleIsBrightLeftChange(false)
}
return [handleClickToRight, handleClickToLeft]
}, [
checkListLeft,
checkListRight,
flatArray,
dataSourceLeft,
finallyValue,
sonParMap,
indeterminateLeft,
defaultValue,
readOnlyAllKeyLeft,
readOnlyAllKeyRight,
readOnlyTreeDataSource,
readOnlyDataSourceRight
])
useEffect(() => {
if (defaultValue.length) {
handleClickToRight()
}
}, [defaultValue])
return (
<CustomTransferButtonBox>
{useMemo(
() => (
<CustomTransferButton
className={isBrightRight ? 'active' : ''}
onClick={handleClickToRight}
>
<RightOutlined
className={isBrightRight ? 'icon-color-active' : 'icon-color'}
/>
</CustomTransferButton>
),
[
isBrightRight,
checkListLeft,
indeterminateLeft,
defaultValue,
readOnlyAllKeyLeft,
readOnlyAllKeyRight,
readOnlyTreeDataSource,
readOnlyDataSourceRight
]
)}
{useMemo(
() => (
<CustomTransferButton
className={isBrightLeft ? 'active' : ''}
onClick={handleClickToLeft}
>
<LeftOutlined
className={isBrightLeft ? 'icon-color-active' : 'icon-color'}
/>
</CustomTransferButton>
),
[
isBrightLeft,
checkListRight,
indeterminateLeft,
defaultValue,
readOnlyAllKeyLeft,
readOnlyAllKeyRight,
readOnlyTreeDataSource,
readOnlyDataSourceRight
]
)}
</CustomTransferButtonBox>
)
}
const TreeShuttleBoxRight: FC<TreeShuttleBoxRightPropsType> = ({}) => {
const {
dataSourceRight,
checkListRight,
readOnlyAllKeyRight,
handleCheckListRightChange,
handleDataSourceRightChange,
readOnlyDataSourceRight,
handleIsBrightLeftChange
} = useTreeContext()
const [indeterminate, setIndeterminate] = useState<boolean>(false) // 控制半选
const [checkAll, setCheckAll] = useState<boolean>(false) // 控制全选
const [searchText, setSearchText] = useState<string>('') // 用于搜索title
useEffect(() => {
if (checkListRight.length === 0) {
// 无选择数据是 全选置空
setCheckAll(false)
// 无选择数据是 半选选置空
setIndeterminate(false)
}
}, [checkListRight])
const [handleCheckBoxChange, handleSelectNode] = useMemo(() => {
const handleCheckBoxChange = (e: any) => {
// 处理全选和半选
handleCheckListRightChange(e.target.checked ? readOnlyAllKeyRight : [])
// 设置全选状态
setCheckAll(e.target.checked)
// 设置半选状态
setIndeterminate(false)
// 设置to Left 按钮高亮
handleIsBrightLeftChange(e.target.checked)
}
const handleSelectNode = (keyArr: any) => {
// 设置右侧选项
handleCheckListRightChange(keyArr)
// 设置全选状态
setCheckAll(keyArr.length && readOnlyAllKeyRight.length === keyArr.length)
// 设置半选状态
setIndeterminate(
keyArr.length && readOnlyAllKeyRight.length !== keyArr.length
)
// 设置to Left 按钮高亮
handleIsBrightLeftChange(!!keyArr.length)
}
return [handleCheckBoxChange, handleSelectNode]
}, [checkAll, readOnlyAllKeyRight])
const [handleSearchTree, handleSearchChange] = useMemo(() => {
const handleSearchTree = (value: string) => {
if (value === '') {
// 点击清除搜索或者手动删除文字按下回车
const treeData = JSON.parse(JSON.stringify(readOnlyDataSourceRight))
// 保存右侧数据源
handleDataSourceRightChange(treeData)
} else {
// 搜索树
handleDataSourceRightChange({
treeData: searchTree(value, dataSourceRight.treeData)
})
}
}
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// 搜索树形结构数据
setSearchText(e.target.value.trim())
}
return [handleSearchTree, handleSearchChange]
}, [searchText, readOnlyDataSourceRight, dataSourceRight.treeData])
return (
<CustomTransferBox>
<CustomTransferHead>
{useMemo(
() => (
<Checkbox
indeterminate={indeterminate}
checked={checkAll}
onChange={handleCheckBoxChange}
/>
),
[indeterminate, checkAll, readOnlyAllKeyRight]
)}
{useMemo(
() => (
<p className="select-count">
{checkListRight.length}/{readOnlyAllKeyRight.length}items
</p>
),
[readOnlyAllKeyRight, checkListRight]
)}
</CustomTransferHead>
<CustomTransferBody>
<Search
placeholder="search here"
value={searchText}
onSearch={handleSearchTree}
onChange={handleSearchChange}
allowClear
enterButton
/>
<CustomTransferTree>
<Tree
height={300}
selectable={false}
checkable
checkedKeys={checkListRight}
onCheck={handleSelectNode}
treeData={dataSourceRight.treeData}
/>
</CustomTransferTree>
</CustomTransferBody>
</CustomTransferBox>
)
}
const TreeShuttleBoxLeft: FC<shuttleLeftProps & treeDataType> = ({
treeData,
handleChange // 当只需要左侧选择框时就可以传递此回调函数,获取所选择的值
}) => {
const {
checkListLeft,
handleCheckListLeftChange,
dataSourceLeft,
handleDataSourceLeftChange,
handleFlatArrayChange,
handleSonParMapChange,
finallyValue,
handleIsBrightRightChange,
indeterminateLeft,
handleIndeterminateLeftChange,
readOnlyAllKeyLeft,
handleReadOnlyAllKeyLeftChange,
readOnlyTreeDataSource,
handleReadOnlyTreeDataSourceChange
} = useTreeContext()
const [checkAll, setCheckAll] = useState<boolean>(false) // 控制全选
const [searchText, setSearchText] = useState<string>('') // 用于搜索title
useEffect(() => {
// 遍历树形数据设置parentId
const sonParMap: Map<string, string | null> = new Map()
traverseTreeData({
treeData,
callback(item, index, parentId) {
item.parentId = parentId
sonParMap.set(item.key, item.parentId)
}
})
handleSonParMapChange(sonParMap)
// 将所以树形数据拉平,为后续组装右侧的树形树形使用
const flatArray = flatTree({
treeData: JSON.parse(JSON.stringify(treeData))
})
handleFlatArrayChange(flatArray)
// 得到左侧所有的key,主要用于全选
const allKey: string[] = flatArray.map(({ key }) => key)
// 保存左侧数据源所有的key
handleReadOnlyAllKeyLeftChange(allKey)
// 保存到左侧的数据源
handleDataSourceLeftChange({
treeData: JSON.parse(JSON.stringify(treeData))
})
// 保存左侧树的数据源,只读,用于搜索
handleReadOnlyTreeDataSourceChange({
treeData: JSON.parse(JSON.stringify(treeData))
})
}, [treeData])
useEffect(() => {
// 左侧选择数据时回调change
handleChange && handleChange(checkListLeft)
}, [checkListLeft])
useEffect(() => {
if (checkListLeft.length !== readOnlyAllKeyLeft.length) {
setCheckAll(false)
}
}, [checkListLeft, readOnlyAllKeyLeft])
const [handleCheckBoxChange, handleSelectNode] = useMemo(() => {
const handleCheckBoxChange = (e: any) => {
// 处理全选和半选
handleCheckListLeftChange(e.target.checked ? readOnlyAllKeyLeft : [])
// 处理全选状态
setCheckAll(e.target.checked)
// 处理半选状态
handleIndeterminateLeftChange(false)
// 处理to Right 高亮
handleIsBrightRightChange(e.target.checked)
}
const handleSelectNode = (keyArr: any) => {
// 处理左侧选中的key
handleCheckListLeftChange(keyArr)
// 处理全选状态
setCheckAll(readOnlyAllKeyLeft.length === keyArr.length)
// 处理半选状态
handleIndeterminateLeftChange(
keyArr.length && readOnlyAllKeyLeft.length !== keyArr.length
)
// 处理 to Right 高亮
handleIsBrightRightChange(!!keyArr.length)
}
return [handleCheckBoxChange, handleSelectNode]
}, [readOnlyAllKeyLeft, checkAll, checkListLeft])
const [handleSearchTree, handleSearchChange] = useMemo(() => {
const handleSearchTree = (value: string) => {
if (value === '') {
// 点击清除搜索或者手动删除文字按下回车
const treeData = JSON.parse(
JSON.stringify(readOnlyTreeDataSource.treeData)
)
// 处理左侧数据源选中数据的禁用
traverseTreeData({
treeData,
callback(item) {
item['disabled'] = finallyValue.includes(item.key)
}
})
// 保存左侧的数据源
handleDataSourceLeftChange({
treeData
})
} else {
// 搜索树
handleDataSourceLeftChange({
treeData: searchTree(value, dataSourceLeft.treeData)
})
}
}
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value.trim())
}
return [handleSearchTree, handleSearchChange]
}, [
searchText,
readOnlyTreeDataSource.treeData,
dataSourceLeft.treeData,
finallyValue
])
return (
<CustomTransferBox>
<CustomTransferHead>
{useMemo(
() => (
<Checkbox
indeterminate={indeterminateLeft}
checked={checkAll}
onChange={handleCheckBoxChange}
/>
),
[indeterminateLeft, checkAll, readOnlyAllKeyLeft]
)}
{useMemo(
() => (
<p className="select-count">
{checkListLeft.length}/{readOnlyAllKeyLeft.length}items
</p>
),
[checkListLeft, readOnlyAllKeyLeft]
)}
</CustomTransferHead>
<CustomTransferBody>
<Search
placeholder="search here"
value={searchText}
onSearch={handleSearchTree}
onChange={handleSearchChange}
allowClear
enterButton
/>
<CustomTransferTree className="homeScrollBar">
<Tree
height={300}
selectable={false}
checkable
checkedKeys={checkListLeft}
onCheck={handleSelectNode}
treeData={dataSourceLeft.treeData}
/>
</CustomTransferTree>
</CustomTransferBody>
</CustomTransferBox>
)
}
const TreeShuttleBox: TreeShuttleBoxType = ({
children,
defaultValue = [],
handleChange,
isOnlyLeft = false // 主要控制是否是需要左侧树形选择框
}) => {
const {
finallyValue,
handleDefaultValueChange,
handleCheckListLeftChange,
handleIsBrightRightChange,
...rest
} = useTreeShuttle()
useEffect(() => {
if (defaultValue.length) {
// 有默认数据时
// 保存默认数据
handleDefaultValueChange(defaultValue)
// 保存左侧选中的数据
handleCheckListLeftChange(defaultValue)
// to Right 高亮
handleIsBrightRightChange(true)
}
}, [defaultValue])
useEffect(() => {
handleChange && handleChange(finallyValue)
}, [finallyValue])
return (
<TreeProvider
value={{
finallyValue,
handleDefaultValueChange,
handleCheckListLeftChange,
handleIsBrightRightChange,
...rest
}}
>
<TreeShuttleContainer isOnlyLeft={isOnlyLeft}>
{children}
</TreeShuttleContainer>
</TreeProvider>
)
}
TreeShuttleBox.TreeShuttleBoxLeft = TreeShuttleBoxLeft
TreeShuttleBox.TreeShuttleBoxRight = TreeShuttleBoxRight
TreeShuttleBox.TreeShuttleButton = TreeShuttleButton
function App() {
const [treeData, settreeData] = useState<any[]>([])
const [defaultArr, setdefaultArr] = useState<any[]>([])
useEffect(() => {
settreeData(treeData_)
let defaultArr = [
'9745585390301',
'8908033790301',
'2551253780501',
'3014087750601',
'6093241980901',
'6624581220001',
'7209552490101',
'9867334920901'
]
setdefaultArr(defaultArr)
}, [])
const handleChange = useCallback((finallyValue: string[]) => {
console.log('handleChange -> finallyValue', finallyValue)
}, [])
return (
<div>
<TreeShuttleBox defaultValue={defaultArr} handleChange={handleChange}>
<TreeShuttleBox.TreeShuttleBoxLeft treeData={treeData} />
<TreeShuttleBox.TreeShuttleButton />
<TreeShuttleBox.TreeShuttleBoxRight />
</TreeShuttleBox>
</div>
)
}
export default App