1,核心是利用好tree组件的titleRender属性,该属性可以将tree已有数据进行二次更改,将原本的title更换为(nodeData) => ReactNode
const renderTitle = (node: any) => {
const isHovered = node.key === treeEditStatusKey;
const regionWidth = getTextWidth(node.title, '14px');
return (
<div
className="tree-span"
onMouseEnter={() => {
setTreeEditStatusKey(node.key);
}}
onMouseLeave={() => {
setTreeEditStatusKey(null);
}}
>
<Tooltip title={regionWidth > 80 ? node.title : ''}>
<div className="hidden-box" ref={divRef}>
{node.title}
</div>
</Tooltip>
{isHovered && (
<>
<div>
{node?.parentId !== '0' && (
<Space>
<div
className="del-icon"
onClick={() => {
Modal.confirm({
title: t.formatMessage({
id: `columns.handel.delete`,
defaultMessage: '删除',
}),
icon: <ExclamationCircleOutlined />,
content: t.formatMessage({
id: 'device.deviceManage.delAreaReminds',
}),
onOk() {
regionDel({ id: node.key }).then(() => {
setReloadNumTree(reloadNumTree + 1);
setReloadNum(reloadNum + 1);
});
},
onCancel() {},
okText: t.formatMessage({
id: `form.handel.ok`,
defaultMessage: '确认',
}),
cancelText: t.formatMessage({
id: `form.handel.cancel`,
defaultMessage: '取消',
}),
});
}}
/>
<div
className="edit-icon"
onClick={() => {
setTreeAddOrEdit('edit');
setDeviceAddModal(true);
}}
/>
</Space>
)}
</div>
</>
)}
</div>
);
};
2,我们通过在每一项renderTitle上面定义onMouseEnter和onMouseLeave,enter的时候保存该项的nodeKey,移除的时候删除nodeKey,再将其与renderTitle的node项进行唯一值匹配,可控制该项的编辑和删除图标的显示和隐藏,
const [treeEditStatusKey, setTreeEditStatusKey] = useState<any>();
onMouseEnter={() => {
setTreeEditStatusKey(node.key);
}}
onMouseLeave={() => {
setTreeEditStatusKey(null);
}}
3,并且通过getTextWidth函数,对title的长度进行校验,过长的文字隐藏,并使用Tooltip展示,
//判断regionName长度隐藏
const getTextWidth = (text: any, fontSize: any) => {
const span = document.createElement('span');
span.style.fontSize = fontSize;
span.style.visibility = 'hidden';
span.style.position = 'absolute';
span.style.whiteSpace = 'nowrap';
span.innerText = text;
document.body.appendChild(span);
const width = span.offsetWidth;
document.body.removeChild(span);
return width;
};
4,选中拉通:选中项拉通功能需要使用tree组件的 const { DirectoryTree } = Tree;属性,即可像文件列表一样选中拉通,无需更改样式
5,效果:
6,全部代码:
import { regionDel } from '@/services/common/device';
import { ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import {
Col,
Empty,
Input,
Modal,
Row,
Space,
Spin,
Tooltip,
Tree,
} from 'antd';
import type { TreeProps } from 'antd/es/tree';
import { useRef, useState } from 'react';
import './index.less';
const { Search } = Input;
type FormType = {
treeData: any[];
reloadNumTree: number;
reloadNum: number;
treeSpinLoading: boolean;
setReloadNumTree: (values: any) => void;
setInitialValuesArea: (values: any) => void;
setSubmitAreaForm: (values: any) => void;
setDeviceAddModal: (values: any) => void;
setTreeAddOrEdit: (values: any) => void;
setReloadNum: (values: any) => void;
setTreeCheckedId: (values: any) => void;
treeCheckedId?: any;
};
const { DirectoryTree } = Tree;
const LeftTree = ({
treeData,
setInitialValuesArea,
setSubmitAreaForm,
setTreeAddOrEdit,
setDeviceAddModal,
reloadNum,
reloadNumTree,
setReloadNum,
setReloadNumTree,
treeSpinLoading,
setTreeCheckedId,
}: // treeCheckedId,
FormType) => {
const t = useIntl();
const divRef: any = useRef(null);
const [onInputChange, setOnInputChange] = useState<string>('');
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const [treeEditStatusKey, setTreeEditStatusKey] = useState<any>();
//判断regionName长度隐藏
const getTextWidth = (text: any, fontSize: any) => {
const span = document.createElement('span');
span.style.fontSize = fontSize;
span.style.visibility = 'hidden';
span.style.position = 'absolute';
span.style.whiteSpace = 'nowrap';
span.innerText = text;
document.body.appendChild(span);
const width = span.offsetWidth;
document.body.removeChild(span);
return width;
};
// // 递归筛选
const filterTree = (treeData: any[], searchValue: string) => {
if (!searchValue) {
return treeData;
}
return treeData.reduce((acc, { title, children, ...rest }) => {
const isMatch = title.toLowerCase().includes(searchValue.toLowerCase());
const childNodes = filterTree(children || [], searchValue);
if (isMatch || childNodes.length > 0) {
acc.push({
title: isMatch ? (
<span style={{ color: '#1890ff' }}>{title}</span>
) : (
title
),
children: childNodes,
...rest,
});
}
return acc;
}, []);
};
const filteredTreeData = filterTree(treeData, onInputChange);
// 扁平化filter数组,用于筛选后展开项
const getFlatArr = (treeArr: any) => {
let res: any[] = [];
// eslint-disable-next-line
treeArr.map((item: any) => {
let data = {};
data = { children: [], child: item.children };
const finItem = { ...item, ...data };
res.push(finItem);
if (item.children && Array.isArray(item.children)) {
res = res.concat(getFlatArr(item.children));
}
});
return res;
};
const flattenTree = getFlatArr(filteredTreeData);
// 树选择
const onTreeSelect: TreeProps['onSelect'] = (
selectedKeys: any,
info: any,
) => {
setTreeCheckedId(info?.node.key);
if (selectedKeys.length > 0) {
setReloadNum(() => reloadNum + 1);
setInitialValuesArea({
id: info?.node.key,
parentId: info?.node?.parentId,
regionName: info?.node?.label,
version: info?.node?.version,
});
} else {
setReloadNum(() => reloadNum + 1);
}
};
const onExpand = (expandedKeysValue: any[]) => {
setExpandedKeys(expandedKeysValue);
};
const renderTitle = (node: any) => {
const isHovered = node.key === treeEditStatusKey;
const regionWidth = getTextWidth(node.title, '14px');
return (
<div
className="tree-span"
onMouseEnter={() => {
setTreeEditStatusKey(node.key);
}}
onMouseLeave={() => {
setTreeEditStatusKey(null);
}}
>
<Tooltip title={regionWidth > 80 ? node.title : ''}>
<div className="hidden-box" ref={divRef}>
{node.title}
</div>
</Tooltip>
{isHovered && (
<>
<div>
{node?.parentId !== '0' && (
<Space>
<div
className="del-icon"
onClick={() => {
Modal.confirm({
title: t.formatMessage({
id: `columns.handel.delete`,
defaultMessage: '删除',
}),
icon: <ExclamationCircleOutlined />,
content: t.formatMessage({
id: 'device.deviceManage.delAreaReminds',
}),
onOk() {
regionDel({ id: node.key }).then(() => {
setReloadNumTree(reloadNumTree + 1);
setReloadNum(reloadNum + 1);
});
},
onCancel() {},
okText: t.formatMessage({
id: `form.handel.ok`,
defaultMessage: '确认',
}),
cancelText: t.formatMessage({
id: `form.handel.cancel`,
defaultMessage: '取消',
}),
});
}}
/>
<div
className="edit-icon"
onClick={() => {
setTreeAddOrEdit('edit');
setDeviceAddModal(true);
}}
/>
</Space>
)}
</div>
</>
)}
</div>
);
};
return (
<>
<div className="member-left-tree">
<div className="table-view-tree-device">
<div className="tree-container">
<div className="tree-title-box">
<div className="tree-title">区域</div>
</div>
<div className="tree-search-box">
<div className="search-box">
<Row className="zone-container">
<Col span={20} className="right-search">
<Search
maxLength={50}
size="middle"
allowClear
placeholder={t.formatMessage({
id: 'device.deviceManage.inputArea',
defaultMessage: '请输入',
})}
// addonAfter={<SearchOutlined />}
onChange={(e) => {
setOnInputChange(e.target.value);
}}
/>
</Col>
<Col span={4} offset={0} className="left-add">
<Tooltip
title={t.formatMessage({
id: 'device.deviceManage.addAreaBtn',
})}
placement="right"
>
<div
className="device-add-btn"
onClick={() => {
setInitialValuesArea({});
setSubmitAreaForm(0);
setDeviceAddModal(true);
setTreeAddOrEdit('add');
}}
>
<span>
<PlusOutlined />
</span>
</div>
</Tooltip>
</Col>
</Row>
</div>
</div>
<Spin spinning={treeSpinLoading}>
{treeData?.length > 0 ? (
<div className="tree-item">
<DirectoryTree
className="trees"
onSelect={onTreeSelect}
// defaultExpandAll={defaultExpandAll}
blockNode
showIcon={false}
treeData={treeData}
titleRender={renderTitle}
expandedKeys={
onInputChange
? flattenTree.map((item: any) => item.key)
: expandedKeys
}
onExpand={onExpand}
/>
</div>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</Spin>
</div>
</div>
</div>
</>
);
};
export default LeftTree;