使用 Less 构建树结构视图
需求说明
- 构建一颗无限层级的树结构视图
- 节点之间使用连线表示父子关系
实现思路
基于 Html+Css 实现整个树结构视图展示 基于 Flex 布局实现树结构视图的排列(左右排列) 递归渲染树结构视图
- 使用
React
构建树结构视图 - 使用
Less
构建树结构视图样式
实现步骤
- 定义数据结构
const tree = [
{
id: 1,
name: "ROOT",
children: [
{
id: 2,
name: "MIDDLE",
children: [
{
id: 3,
name: "LEAF",
},
{
id: 4,
name: "LEAF",
},
],
},
{
id: 5,
name: "MIDDLE",
children: [
{
id: 6,
name: "LEAF",
},
{
id: 7,
name: "LEAF",
},
],
},
],
},
];
- 创建树结构视图组件
- TagNode.tsx
import { Button } from "antd";
import classNames from "classnames";
import { NODE_TYPE } from "../../enums";
import "./index.less";
interface TagNodeProps {
nodeType: NODE_TYPE;
item: any;
add?: (values: any) => void;
children?: React.ReactNode;
}
/**
* @description TagNode component 标签树的基本节点
* 基本节点包括 根节点 ROOT、叶子节点LEAF、中间节点MIDDLE
* @returns {JSX.Element}
*/
const TagNode: React.FC<TagNodeProps> = (props) => {
const { nodeType, add, item, children } = props;
return (
<div
id={item.id}
className="tagNode"
draggable
onDrag={(e) => {
e.currentTarget.style.border = "dashed";
e.dataTransfer.setData("text", e.target.id);
}}
>
<div className={classNames("selfNode")}>
<Button
onClick={() => {
add?.({
parentId: item.id,
name: nodeType,
});
}}
>
{nodeType}
</Button>
</div>
{children && <div className="childNode">{children}</div>}
</div>
);
};
export default TagNode;
- TagTree.tsx
import { Button } from "antd";
import TagNode from "./components/TagNode/TagNode";
import { NODE_TYPE } from "./enums";
import { useTagList } from "./hooks/useTagList";
import "./index.less";
const TagTree = () => {
const { tagTree, add, update, remove } = useTagList();
const renderTagTree = (values: any) => {
return values.map((tag: any) => {
return (
<TagNode
nodeType={tag.name as NODE_TYPE}
key={tag.id}
item={tag}
add={add}
>
{tag.children && renderTagTree(tag.children)}
</TagNode>
);
});
};
return (
<div className="tag_content">
<Button
onClick={() => {
add({
name: "ROOT",
});
}}
>
添加根节点
</Button>
{renderTagTree(tagTree)}
</div>
);
};
export default TagTree;
- 使用 Less 构建树结构视图样式
@line: {
@color: rgb(102, 0, 255);
@width: 10px;
@margin-left: 30px;
@size: 3px;
@radius: 5px;
};
@selfNode: {
@padding: 10px;
};
@leafNode: {
@padding: 10px 0;
};
.dragging {
background-color: black;
}
.tagNode {
display: flex;
align-items: center;
padding: 0 @selfNode[@padding];
.tagNode {
position: relative;
margin-left: @line[@margin-left];
&::before {
position: absolute;
left: -@line[@width];
top: 50%;
transform: translate(0, -50%);
content: "";
display: block;
width: @line[@width];
height: 100%;
border-left: @line[@size] solid @line[@color];
}
}
.tagNode:first-child {
&::before {
height: calc(50% + @line[@size]);
border-top: @line[@size] solid @line[@color];
border-top-left-radius: @line[@radius];
transform: translate(0, -(@line[@size] / 2));
}
}
.tagNode:last-child {
&::before {
height: calc(50% + @line[@size]);
border-bottom: @line[@size] solid @line[@color];
border-bottom-left-radius: @line[@radius];
transform: translate(0, calc(-100% + (@line[@size] / 2)));
}
}
.selfNode {
position: relative;
&::before {
position: absolute;
left: -@line[@width] - @selfNode[@padding] + @line[@size];
top: 50%;
transform: translate(0, -50%);
content: "";
display: block;
width: @line[@width] + @selfNode[@padding];
height: @line[@size];
background-color: @line[@color];
}
&::after {
position: absolute;
right: 0;
top: 50%;
transform: translate(100%, -50%);
content: "";
display: block;
width: @line[@width] + @selfNode[@padding];
height: @line[@size];
background-color: @line[@color];
}
&:only-child {
padding: @leafNode[@padding];
&::after {
display: none;
}
}
}
.tagNode:only-child {
&::before {
display: none;
}
& > .selfNode::before {
left: -@line[@width] - @selfNode[@padding];
}
}
}
- 效果展示