使用 npm:
$ npm i -g npm
$ npm i @tc-utils/mc-tree
直接引用
import console from '@tc-utils/mc-tree'
import Tree, { TreeActionType, TreeProps } from '@tc-utils/mc-tree'
import console from '@tc-utils/consoles'
import {
Button,
DotLoading,
SearchBar,
Space,
Toast,
Checkbox,
Radio,
} from 'antd-mobile'
import { TeamOutline, UserOutline } from 'antd-mobile-icons'
import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import './demo.less'
import {
initTreeData,
mockRequest,
mockSearchRequest,
updateTreeData,
} from './utils'
const App = () => {
const actionRef = useRef<TreeActionType>()
const [expandedKeys, setExpandedKeys] = useState<TreeProps['expandedKeys']>(
[]
)
const [checkedKeys, setCheckedKeys] = useState<TreeProps['checkedKeys']>([
'node_0',
])
const [selectedKeys, setSelectedKeys] = useState<TreeProps['selectedKeys']>([
'node_0',
])
const [treeData, setTreeData] = useState(initTreeData)
const [loading, setLoading] = useState<boolean>(false)
const [checkedAll, setCheckedAll] = useState<boolean>(false)
const ref = useRef<HTMLDivElement>()
const handlerReloadNode = () => {
actionRef.current?.reloadNode()
}
const handlerGoBackNode = () => {
actionRef.current?.goBackNode()
}
const handlerGoBackPointNode = () => {
actionRef.current?.goBackPointNode('node_0')
}
const handlerCheckAllNode = () => {
console.log('🚀🚀 ~ handlerCheckAllNode ~ expandedKeys', expandedKeys)
actionRef.current?.checkedAll(!checkedAll)
setCheckedAll(!checkedAll)
}
const handlerEmptySelectedNode = () => {
console.log('🚀🚀 ~ handlerEmptySelectedNode')
setCheckedKeys([])
setSelectedKeys([])
}
const handlerShowNodePath = () => {
console.log('🚀🚀 ~ handlerShowNodePath ~ expandedKeys', expandedKeys)
const entities = actionRef.current?.getKeyEntities(expandedKeys)
console.log('🚀🚀 ~ handlerShowNodePath ~ entities', entities)
}
const handlerScrollToTop = () => {
actionRef.current?.scrollTo(0)
}
const onExpand: TreeProps['onExpand'] = (expandedKeysValue, info) => {
console.log('🚀🚀 ~ onExpand expandedKeysValue', expandedKeysValue)
console.log('🚀🚀 ~ onExpand info', info)
setExpandedKeys(expandedKeysValue)
}
const onSelect: TreeProps['onSelect'] = (checkedKeysValue, info) => {
console.log('🚀🚀 ~ info', info)
console.log('🚀🚀 ~ checked', checkedKeysValue)
setSelectedKeys(checkedKeysValue)
}
const onCheck: TreeProps['onCheck'] = (checkedKeysValue, info) => {
console.log('🚀🚀 ~ info', info)
console.log('🚀🚀 ~ checked', checkedKeysValue)
setCheckedKeys(checkedKeysValue)
}
const onCheckAll: TreeProps['onCheckAll'] = (checkedKeysValue, info) => {
console.log('🚀🚀 ~ info', info)
console.log('🚀🚀 ~ onCheckAll', checkedKeysValue)
setCheckedKeys(checkedKeysValue)
}
const onBack: TreeProps['onBack'] = (expandedKeysValue, info) => {
console.log('🚀🚀 ~ onBack expandedKeysValue', expandedKeysValue)
console.log('🚀🚀 ~ onBack info', info)
setExpandedKeys(expandedKeysValue)
}
useEffect(() => {
console.log('🚀🚀 ~ checkedKeys', checkedKeys)
}, [checkedKeys])
return (
<div className="demo-wrapper">
<div className="demo-searchbar">
<SearchBar
placeholder="请输入内容"
defaultValue="node_0_0"
onSearch={async (keyword) => {
setLoading(true)
if (keyword) {
const result = await mockSearchRequest(keyword)
console.log('🚀🚀 ~ result', result)
setTreeData(result)
} else {
setTreeData(initTreeData)
}
setLoading(false)
}}
/>
</div>
<div className="demo-toolbar">
<Space wrap>
<Button color="primary" onClick={() => handlerReloadNode()}>
重置节点
</Button>
<Button color="primary" onClick={() => handlerGoBackNode()}>
返回节点
</Button>
<Button color="primary" onClick={() => handlerGoBackPointNode()}>
返回指定节点
</Button>
<Button color="primary" onClick={() => handlerCheckAllNode()}>
{checkedAll ? '全不选' : '全选'}
</Button>
<Button color="primary" onClick={() => handlerEmptySelectedNode()}>
清空全部选中
</Button>
<Button color="primary" onClick={() => handlerShowNodePath()}>
节点路径
</Button>
<Button color="primary" onClick={() => handlerScrollToTop()}>
回到顶部
</Button>
</Space>
</div>
<div
className="demo-toolbar"
style={{
display: loading ? 'block' : 'none',
}}
>
<DotLoading color="primary" /> 数据加载中
</div>
<div className="demo-tree" ref={ref}>
<Tree
treeData={treeData}
loadData={async (node, props) => {
setLoading(true)
const children = await mockRequest(node)
// const newTreeData = [...treeData, ...children]
// setTreeData(newTreeData)
setTreeData((origin) => updateTreeData(origin, node.key, children))
setLoading(false)
}}
actionRef={actionRef}
// virtualRoot={{
// key: 'root',
// title: '根节点',
// icon: 'Ico',
// switcherIcon: '下级',
// }}
expandAction="click"
onlySelectLeaf
// showLeafIcon={false}
expandedKeys={expandedKeys}
checkedKeys={checkedKeys}
// multiple={false}
selectedKeys={selectedKeys}
// checkStrictly={false}
// maxCount={2}
icon={(props) => {
if (props.isLeaf) {
return <UserOutline />
}
return <TeamOutline />
}}
switcherIcon={() => {
return '下级'
}}
onOverMaxCount={(maxCount, info) => {
console.log('🚀🚀 ~ App ~ info', info)
Toast.show(`${maxCount}`)
}}
onExpand={onExpand}
onSelect={onSelect}
onCheck={onCheck}
onCheckAll={onCheckAll}
onBack={onBack}
style={{
height: '100%',
}}
renderCheckbox={(info) => {
return (
<Checkbox
checked={info.checked}
value={info.value}
disabled={info.disabled}
indeterminate={info.halfChecked}
/>
)
}}
renderRadio={(info) => {
return (
<Radio
checked={info.checked}
value={info.value}
disabled={info.disabled}
/>
)
}}
/>
</div>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
demo.less 文件
html,
body,
#root {
height: 100%;
padding: 0;
margin: 0;
}
.demo-wrapper {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
.demo-searchbar {
flex: 0 0 auto;
padding: 10px;
}
.demo-toolbar {
flex: 0 0 auto;
padding: 10px;
}
.demo-tree {
position: relative;
flex: 1 1 auto;
overflow: auto;
padding: 10px;
font-size: 18px;
}
}
::-webkit-scrollbar-track-piece {
background-color: transparent;
}
::-webkit-scrollbar {
width: 3px;
height: 10px;
}
::-webkit-scrollbar-thumb {
height: 50px;
background-color: #b8b8b8;
border-radius: 6px;
outline: 2px solid #fff;
outline-offset: -2px;
filter: alpha(opacity=50);
-moz-opacity: 0.5;
-khtml-opacity: 0.5;
opacity: 0.5;
}
::-webkit-scrollbar-thumb:hover {
height: 50px;
background-color: #878987;
border-radius: 6px;
}
* {
scrollbar-color: #b8b8b8 transparent;
scrollbar-width: thin;
}
utils.ts
import { TreeDataNode, TreeEventDataNode, TreeNodeKey } from '@tc-utils/mc-tree'
// import { httpRequest } from "../../demos/utils";
const TREE_LENGTH = 20
const TREE_LEVEL = 4
const sleep = (time: number) =>
new Promise((resolve) => setTimeout(resolve, time))
const genTreeNode = (
index: number,
length: number,
level: number,
parentId: TreeNodeKey
) => {
const itemId = `${parentId ?? 'node'}_${index}`
let item: TreeDataNode = {
key: itemId,
title: `文件_${itemId}`,
level,
parentId,
description: `描述内容`,
// icon: 'Ico',
// switcherIcon: '下级',
}
if (level !== 0 && index < length / 3) {
// item.children = genStaticTreeData(length, itemId, level - 1);
// item.selectable = false;
// item.disabled = true;
// item.checkable = false;
// item.disableCheckbox = true;
item.title = `文件夹_${itemId}(${level ?? 0} Level)`
} else {
item.disabled = index === 6
item.isLeaf = true
}
return item
}
// It's just a simple demo. You can use tree map to optimize update perf.
export const updateTreeData = (
list: TreeDataNode[],
key: TreeNodeKey,
children: TreeDataNode[]
): TreeDataNode[] =>
list.map((node) => {
if (node.key === key) {
return {
...node,
children,
}
}
if (node.children) {
return {
...node,
children: updateTreeData(node.children, key, children),
}
}
return node
})
const genStaticTreeData = (
length: number,
level = 2,
parentId?: TreeNodeKey
) => {
return Array.from({ length }, (val, index) => {
const item = genTreeNode(index, length, level, parentId)
if (!item.isLeaf) {
item.children = genStaticTreeData(length, level - 1, item.key)
}
return item
})
}
const genLazyTreeData = (length: number, level = 2, parentId?: TreeNodeKey) => {
return Array.from({ length }, (val, index) => {
const item = genTreeNode(index, length, level, parentId)
return item
})
}
export const treeData = genStaticTreeData(TREE_LENGTH, TREE_LEVEL)
export const initTreeData = genLazyTreeData(TREE_LENGTH, TREE_LEVEL)
const buildTreeSelectedNodes = (keys: TreeNodeKey[]): TreeDataNode[] => {
console.log('🚀🚀 ~ file: utils.ts ~ line 109 ~ keys', keys)
return keys.map((key) => {
const parentKey = `${key}`.match(/(\S*)_/)[1]
return {
key: key,
title: `默认选中节点${key}`,
level: 2,
parentKey,
isLeaf: true,
}
})
}
export const mockRequest = async (
treeNode: TreeEventDataNode
): Promise<TreeDataNode[] | null | undefined> => {
await sleep(1000)
console.group('🚀🚀 Mock Request')
console.log('🚀🚀 ~ file: utils.ts ~ line 105 ~ treeNode', treeNode)
// console.log("🚀🚀 ~ file: utils.ts ~ line 105 ~ props", props)
let childrenData: TreeDataNode[] | null = null
const level = treeNode?.level ?? 0
if (level > 0) {
childrenData = genLazyTreeData(
Math.round(Math.random() * 100 + 1),
level - 1,
treeNode.key
)
}
console.log('🚀🚀 ~ childrenData', childrenData)
console.groupEnd()
return Promise.resolve(childrenData)
}
export const mockNodesRequest = async (
keys: TreeNodeKey[]
): Promise<TreeDataNode[] | null | undefined> => {
await sleep(5000)
console.group('🚀🚀 Mock Nodes Request')
console.log('🚀🚀 ~ file: utils.ts ~ line 105 ~ keys', keys)
// console.log("🚀🚀 ~ file: utils.ts ~ line 105 ~ props", props)
let nodeItems: TreeDataNode[] = buildTreeSelectedNodes(keys)
console.groupEnd()
return Promise.resolve(nodeItems)
}
// 字符串包含
const isInclude = (str: string, matchStr: string) => {
if (matchStr) {
return str.toLowerCase().indexOf(matchStr.toLowerCase()) >= 0
}
return true
}
const findTreeNodeByKeyWord = (treeNodes: TreeDataNode[], keyWord: string) => {
const nodes: TreeDataNode[] = []
if (treeNodes) {
treeNodes.forEach(({ children, ...treeNode }) => {
if (isInclude(treeNode.title as string, keyWord)) {
nodes.push(treeNode)
}
if (children) {
const childNodes = findTreeNodeByKeyWord(children, keyWord)
nodes.push(...childNodes)
}
})
}
return nodes
}
export const mockSearchRequest = async (keyWord: string) => {
let tree = genStaticTreeData(TREE_LENGTH, TREE_LEVEL)
const searchNodes = findTreeNodeByKeyWord(tree, keyWord)
return searchNodes
}