Commit f8d8c2ff authored by 前端-许佳敏's avatar 前端-许佳敏

新增tabTree组件

parent 8259463b
......@@ -20,7 +20,8 @@ const config:any = {
routes,
extraBabelPlugins: [
// ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }, 'antd'],
['import', { libraryName: 'god', libraryDirectory: 'es', style: true }, 'god']
['import', { libraryName: 'god', libraryDirectory: 'es', style: true }, 'god'],
['import', { libraryName: '@umijs/hooks', libraryDirectory: 'lib', camel2DashComponentName: false }, '@umijs/hooks'],// 将下划线转化关闭
],
history: {
type: 'browser', // 'brower' | 'hash'
......
......@@ -28,6 +28,7 @@
"@ant-design/pro-layout": "^5.0.16",
"@formily/antd": "^1.2.10",
"@formily/antd-components": "^1.2.10",
"@umijs/hooks": "^1.9.3",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.2.0",
"bizcharts": "^4.0.7",
......
@import '../../constants/styles/_var.less';
.god-tabtree-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 54px;
background: #fff;
font-size: 18px;
font-weight: 700;
color: @main-font-bold-color;
}
#root .ant-tree.god-tabtree {
.ant-tree-treenode {
cursor: pointer;
&:hover {
.god-tabtree-icons {
opacity: 1;
}
}
}
.god-tabtree-icons {
opacity: 0;
transition: .3s;
&.show {
opacity: 1;
}
}
.god-tabtree-select {
background: none;
border-color: @tree-node-border;
&.show {
border-color: @main-color;
background: @tree-node_hover;
.tree-node-circle {
background: @main-color;
}
}
&.hide {
.tree-node-circle {
background: @status-stop;
}
}
}
}
import React, { useState, ReactText, useImperativeHandle } from 'react'
import { Tree, Space, Tooltip, Button } from 'antd'
import { findItemAndDelete, findTreeKeys } from '@/utils'
import './index.less'
import deepClone from 'clone'
import { TreeProps } from 'antd/lib/tree'
import { PlusOutlined, DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons'
import cx from 'classnames'
import { EventDataNode } from 'rc-tree/lib/interface'
import { useSelections } from '@umijs/hooks'
export interface TabTreeActions {
getExpandedKeys: () => ReactText[],
getSelectKey: () => ReactText,
getSelectKeys: () => ReactText[],
setExpandedKeys: (keys: ReactText[]) => void,
setSelectKey: (key: ReactText) => void
setSelectKeys: (keys: ReactText[]) => void
}
export interface toolsRenderProps {
addNode?(node),
addChildNode?(node),
deleteNode?(node)
}
export interface TabTreeProps extends TreeProps {
treeData: any[],
fetchData(params?): Promise<any>,
actions?: TabTreeActions,
title?: React.ReactNode,
// 若传入该字段, 则会作为tree识别的节点, 默认是`key`, 传入后原有的key值将无效
customKey?: string | number,
customTitle?: string | number,
handleSelect?: (key: ReactText, node: EventDataNode) => void | Promise<any>,
toolsRender?: toolsRenderProps
}
export interface InnermostTreeNodeProps {
}
export interface RenderIconsProps {
node: any,
nowKey: any,
toolsRender?: toolsRenderProps
}
export const useTreeActions = (): TabTreeActions => {
const actions: TabTreeActions = {
getExpandedKeys(){ return [] },
getSelectKey(){ return '' },
getSelectKeys(){ return [] },
setSelectKey(){},
setSelectKeys(){},
setExpandedKeys(){},
}
return actions
}
const InnermostTreeNode: React.FC<InnermostTreeNodeProps> = (props) => {
return <span style={{display: 'flex', alignItems: 'center'}}>
<span className='tree-node-circle'></span>
<span>{props.children}</span>
</span>
}
const RenderIcons: React.FC<RenderIconsProps> = (props) => {
const {
toolsRender
} = props
// @todo 去掉点击active时, 保持icon显示
// return <Space className={cx('god-tabtree-icons', props.nowKey === props.node.key ? 'show' : 'hide')}>
return <Space className={cx('god-tabtree-icons')}>
<Tooltip title='新增节点'>
<PlusCircleOutlined onClick={(e) => {
e.stopPropagation()
toolsRender && toolsRender.addNode && toolsRender.addNode(props.node)
}}/>
</Tooltip>
<Tooltip title='新增子节点'>
<PlusCircleOutlined onClick={(e) => {
e.stopPropagation()
toolsRender && toolsRender.addChildNode && toolsRender.addChildNode(props.node)
}}/>
</Tooltip>
<Tooltip title='删除当前节点'>
<DeleteOutlined onClick={(e) => {
e.stopPropagation()
toolsRender && toolsRender.deleteNode && toolsRender.deleteNode(props.node)
}}/>
</Tooltip>
</Space>
}
// 将无children的叶子节点中的title 转化为带有样式的title, 由于每次render 都需要重新deepClone深拷贝,可以优化
// 在多选模式下无需转化
function transformSingleTitle(data, nowKey, checkable, disabled, toolsRender, customKey?, customTitle?) {
if (Array.isArray(data) && data.length > 0) {
for (let item = 0; item < data.length; item++) {
if (data[item].children) {
transformSingleTitle(data[item].children, nowKey, checkable, disabled, toolsRender, customKey, customTitle)
}
data[item].title = <span className='god-tabtree-title' style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
{ (checkable || data[item].children) ? data[item].title : <InnermostTreeNode>{data[item].title}</InnermostTreeNode> }
<div>
{ toolsRender && <RenderIcons node={data[item]} nowKey={nowKey} toolsRender={toolsRender}/> }
</div>
</span>
// 使选中样式受控
data[item].className= cx('god-tabtree-select', nowKey === data[item].key ? 'show' : 'hide')
if (disabled) {
data[item].disableCheckbox = disabled
}
// 指定默认key
if (customKey) {
data[item]._key = data[item].key
data[item].key = data[item][customKey]
}
if (customTitle) {
data[item]._title = data[item].title
data[item].title = data[item][customTitle]
}
}
}
return data;
}
const TabTree:React.FC<TabTreeProps> = (props) => {
const {
title,
treeData,
actions,
checkable,
customKey,
customTitle,
toolsRender,
disabled
} = props
// 需展开的key
const [expandkeys, setExpandkeys] = useState<ReactText[]>([])
// 当前选中的node
const [selectKey, setSelectKey] = useState<string | number>('')
const data = transformSingleTitle(deepClone(treeData), selectKey, checkable, disabled, toolsRender, customKey, customTitle)
// 重写选择方法, 只有在开启多选的时候才会启用
const checkedKeys = findTreeKeys(treeData)
const { selected, select,setSelected, unSelect, allSelected, unSelectAll, selectAll } = useSelections(
checkedKeys,
[]
);
const toggleSelectAll = () => {
if (allSelected) {
unSelectAll()
} else {
selectAll()
}
}
if (actions) {
actions.getExpandedKeys = () => expandkeys
actions.getSelectKey = () => selectKey
actions.getSelectKeys = () => selected
actions.setSelectKeys = (keys: ReactText[]) => {
setSelected(keys)
}
actions.setExpandedKeys = (keys: ReactText[]) => {
setExpandkeys(keys)
}
actions.setSelectKey = (key: ReactText) => {
setSelectKey(key)
}
}
const batchSelect = (items: any[]) => {
items.forEach(v => select(v))
}
return (
<div>
{title &&
<div className='god-tabtree-header'>
<div>{title}</div>
{checkable && <Button onClick={toggleSelectAll} disabled={disabled} type='link'>{ allSelected ? '取消全选' : '全选'}</Button>}
</div>
}
<Tree
className="god-tabtree"
treeData={data}
blockNode
checkable={checkable}
checkedKeys={selected}
expandedKeys={expandkeys}
onCheck={(keys, nodes) => {
const { node, checked, checkedNodes } = nodes
checked ? batchSelect(keys as any) : setSelected(checkedNodes)
}}
onSelect={(keys, e) => {
// 控制点击node时可以展开
const { node, selected } = e
// 用户自定义的选择后触发事件
if (props.handleSelect) {
const result = props.handleSelect(node.key, node)
// 存在返回值则不执行选中事件, 一般用于切换node时,不希望离开当前页面
if (result !== undefined) {
result.then(() => {
// 若promise 是resolve状态, 说明确认离开了当前页面
setSelectKey(selectKey === node.key ? '' : node.key)
setExpandkeys(expandkeys.includes(node.key) ? findItemAndDelete(expandkeys, node.key) : [...expandkeys, node.key])
}).catch(() => {
})
return false
}
}
// 如果重复点击 需要取消选中
setSelectKey(selectKey === node.key ? '' : node.key)
setExpandkeys(expandkeys.includes(node.key) ? findItemAndDelete(expandkeys, node.key) : [...expandkeys, node.key])
}}
>
</Tree>
</div>
)
}
TabTree.defaultProps = {}
export default TabTree
\ No newline at end of file
import moment from 'moment';
import deepClone from 'clone'
function isArray(arr: any) {
return Array.isArray(arr)
......@@ -126,6 +127,20 @@ export const findItemAndDelete = (arr: any[], target) => {
}
}
// 遍历树拿到所有key的集合
export const findTreeKeys = (arr: any[], keyword?: string) => {
const copyArr: any[] = deepClone(arr)
const results: any[] = []
while(copyArr.length > 0) {
const item = copyArr.shift()
results.push(keyword ? item[keyword] : item.key)
if (item.children) {
copyArr.push(...item.children)
}
}
return results
}
export default {
isArray,
isObject,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment