最近在react项目中需要一个树状组件,但是又不想因为这个去引入一套UI组件,故自己封装了一个基于react的树状组件,

个人认为比较难得部分在于数据的处理,话不多说直接上代码:

下面是tree.js

import React, {Component} from 'react';
import './tree.css';
import Stack from '../utils/util'; class Tree extends Component {
constructor(props) {
super(props)
this.state = {
treeData: {},
treeArray: [],
treeObj: {},
type: 'tree',
parentId: 'pid',
id: 'id',
value: 'value',
label: 'label',
children: 'children',
checkBox: false
}
this.checkMap = {
2: 'checked',
1: 'partChecked',
0: ''
}
} componentWillMount() {
if (this.props.config.type.toLowerCase() === 'tree') {
this.setState({
treeData: this.props.treeData,
...this.props.config
})
} else {
this.setState({
treeArray: this.props.treeData,
...this.props.config
})
}
} componentDidMount() {
if (this.state.type.toLowerCase() !== 'tree') {
this.factoryArrayData()
} else {
this.factoryTreeData()
}
} componentDidUpdate() { } componentWillUnmount() { } factoryArrayData() {
let data = this.state.treeArray, obj = {}, rootId = null;
data.map((v, i) => {
if (v[this.state.parentId] || v[this.state.parentId] === 0) {
if (obj[v[this.state.parentId]]) {
if (obj[v[this.state.parentId]].children) {
obj[v[this.state.parentId]].children.push(v)
} else {
obj[v[this.state.parentId]].children = [v]
}
} else {
obj[v[this.state.parentId]] = {
children: [v]
}
}
} else {
rootId = v[this.state.id]
}
if (obj[v[this.state.id]]) {
v.children = obj[v[this.state.id]].children
}
obj[v[this.state.id]] = v
})
this.setState({
treeData: obj[rootId],
treeObj: obj
})
} factoryTreeData() {
let data = this.state.treeData
let stack = new Stack();
let obj = {};
stack.push(data);
while (stack.top) {
let node = stack.pop();
for (let i in node.children) {
stack.push(node.children[i])
}
obj[node[this.state.id]] = node
}
this.setState({
treeObj: obj
})
} openNode (e, data) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
data.open = !data.open
this.forceUpdate()
} selectNode (e, data) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
this.setState({
selectVal: data[this.state.value]
}, () => {
if (this.props.nodeClick) {
this.props.nodeClick(data[this.state.value])
}
})
} selectCheckBox (e, data) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
window.event.cancelBubble = true;
}
let check = data.checked
if (data.children && data.children.length) {
let stack = new Stack();
stack.push(data);
while(stack.top) {
let node = stack.pop()
for (let i in node.children) {
stack.push(node.children[i])
}
if (check === 2) {
node.checked = 0;
} else {
node.checked = 2
}
}
} else {
if (check === 2) {
data.checked = 0;
} else {
data.checked = 2
}
}
if (data[this.state.parentId] || data[this.state.parentId] === 0) {
this.updateParentNode(data)
} else {
this.forceUpdate()
if (this.props.selectChange) {
this.getCheckedItems()
}
}
} updateParentNode (data) {
let par = this.state.treeObj[data[this.state.parentId]], checkLen = 0, partChecked = false;
for (let i in par.children) {
if (par.children[i].checked === 2) {
checkLen++;
} else if (par.children[i].checked === 1) {
partChecked = true;
break;
}
}
if (checkLen === par.children.length) {
par.checked = 2
} else if (partChecked || (checkLen < par.children.length && checkLen > 0)) {
par.checked = 1;
} else {
par.checked = 0;
}
if (this.state.treeObj[par[this.state.parentId]] || this.state.treeObj[par[this.state.parentId]] == 0) {
this.updateParentNode(par)
} else {
this.forceUpdate()
if (this.props.selectChange) {
this.getCheckedItems()
}
}
} getCheckedItems() {
let stack = new Stack ();
let checkedArr = [];
stack.push(this.state.treeData);
while (stack.top) {
let node = stack.pop();
for (let i in node.children) {
stack.push(node.children[i])
}
if (node.checked === 2) {
checkedArr.push(node[this.state.value])
}
}
this.props.selectChange(checkedArr)
} renderTreeParent() {
let data = this.state.treeData
return (
<div className={`parentNode childNode ${data.open?'open':'close'} ${data.children && data.children.length?'':'noChildren'}`}>
<span onClick={(e) => this.openNode(e, data)} className="openNode"></span>
{
this.state.checkBox?
<div className={`checkBox ${this.checkMap[data.checked]}`} onClick={(e) => this.selectCheckBox(e, data)}></div>:
<div className="fileBox">
<img src="./images/file-icon.png" alt=""/>
</div>
}
<div className={`nodeName ${this.state.selectVal === data[this.state.value]?'active':''}`} onClick={(e) => this.selectNode(e, data)}>
{data[this.state.label]}
</div>
{
this.state.treeData.children ?
<div className="childList">
{this.renderTreeNode(data)}
</div> : null
}
</div>
)
} renderTreeNode(data) {
return data.children.map((val, ind) => {
return (
<div key={ind} className={`childNode ${val.open?'open':'close'} ${val.children && val.children.length?'':'noChildren'}`}>
<span onClick={(e) => this.openNode(e, val)} className="openNode"></span>
{
this.state.checkBox?
<div className={`checkBox ${this.checkMap[val.checked]}`} onClick={(e) => this.selectCheckBox(e, val)}></div>:
<div className="fileBox">
<img src="./images/file-icon.png" alt=""/>
</div>
}
{ind === data.children.length - 1?
<span className="lastNode"></span>:null
}
<div className={`nodeName ${this.state.selectVal === val[this.state.value]?'active':''}`} onClick={(e) => this.selectNode(e, val)}>
{val[this.state.label]}
</div>
{
val.children ?
<div className="childList">
{this.renderTreeNode(val)}
</div> : null
}
</div>
)
})
} render() {
return (
<div className="tree">
{this.renderTreeParent()}
</div>
)
}
} export default Tree

下面是tree.css

.tree {
text-align: left;
}
.tree .childNode {
padding-left: 20px;
position: relative;
background-color: #ffffff;
z-index:;
}
.tree .childNode .checkBox {
position: absolute;
width: 16px;
left: 20px;
top:;
z-index:;
margin: 7px 10px 0;
height: 16px;
box-sizing: border-box;
border: 1px solid #d2d2d2;
vertical-align: text-bottom;
font-size:;
border-radius: 2px;
cursor: pointer;
}
.tree .childNode .checkBox:hover {
cursor: pointer;
border-color: #5bb976;
}
.tree .childNode .checkBox.checked {
border:;
background: url(../images/icon-check-green.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/icon-check-green.png', sizingMethod='scale') \9;
}
.tree .childNode .checkBox.partChecked {
border:;
background: url(../images/part-checked.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/part-checked.png', sizingMethod='scale') \9;
}
.tree .childNode .nodeName {
padding-left: 36px;
font-size: 14px;
color: #333333;
white-space: nowrap;
overflow: hidden;
line-height: 30px;
height: 30px;
text-overflow: ellipsis;
position: relative;
z-index:;
display: inline-block;
padding-right: 10px;
}
.tree .childNode .nodeName.active {
background-color: #DEF1FF;
}
.tree .childNode .nodeName:hover {
text-decoration: underline;
cursor: pointer;
}
.tree .childNode.open .openNode {
background: url(../images/department-close.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/department-close.png', sizingMethod='scale') \9;
}
.tree .childNode.open .childList {
display: block;
}
.tree .childNode.close .openNode {
background: url(../images/depart-open.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/depart-open.png', sizingMethod='scale') \9;
}
.tree .childNode.close .childList {
display: none;
}
.tree .childNode .fileBox {
position: absolute;
width: 16px;
left: 20px;
top:;
margin: 5px 10px 0;
z-index:;
}
.tree .childNode .fileBox:hover {
cursor: pointer;
}
.tree .childNode .fileBox img {
width: 16px;
}
.tree .childNode:before {
position: absolute;
left: -13px;
top: 15px;
width: 20px;
height: 100%;
border-top: 1px solid #CFCFCF;
border-right: 1px solid #CFCFCF;
content: '';
z-index:;
}
.tree .childNode:after {
position: absolute;
bottom: -12px;
left: 7px;
width: 1px;
height: 30px;
z-index:;
background-color: #ffffff;
content: '';
}
.tree .childNode.parentNode:before {
border-top: none;
}
.tree .childNode .openNode {
position: absolute;
z-index:;
left:;
top: 8px;
width: 14px;
height: 14px;
}
.tree .childNode .openNode:hover {
cursor: pointer;
}
.tree .childNode.noChildren .openNode {
width: 10px;
height: 10px;
top: 10px;
left: 7px;
background: url(../images/no-child.png) no-repeat center center;
background-size: 100% 100%;
background: none\9;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='./images/no-child.png', sizingMethod='scale') \9;
}
.tree .childNode.noChildren .openNode:hover {
cursor: default;
}
.tree .childNode .lastNode {
position: absolute;
bottom: -15px;
left: -13px;
width: 1px;
height: 100%;
z-index:;
background-color: #ffffff;
}

utils里面是封装了一个stack栈,关于js栈的使用请移步js遍历树状数据文章。

github项目地址

react树状组件的更多相关文章

  1. Java Swing 树状组件JTree的使用方法(转)

    树中特定的节点可以由 TreePath(封装节点及其所有祖先的对象)标识,或由其显示行(其中显示区域中的每一行都显示一个节点)标识.展开 节点是一个非叶节点(由返回 false 的 TreeModel ...

  2. react 使用antd的TreeSelect树选择组件实现多个树选择循环

    需求说明,一个帐号角色可以设置管理多个项目的菜单权限 且菜单接口每次只能查询特定项目的菜单数据[无法查全部] 开发思路: 1,获取项目接口数组,得到项目数据 2,循环项目数据,以此为参数递归查询菜单数 ...

  3. 聊聊React高阶组件(Higher-Order Components)

    使用 react已经有不短的时间了,最近看到关于 react高阶组件的一篇文章,看了之后顿时眼前一亮,对于我这种还在新手村晃荡.一切朝着打怪升级看齐的小喽啰来说,像这种难度不是太高同时门槛也不是那么低 ...

  4. EasyUI + ajax + treegrid/datagrid 接收 json 数据,显示树状/网状表结构

    最后一更了,时间间隔有点久了~~ EasyUI作为一个成熟的前端框架,封装了ajax,对于数据的处理配合datagrid组件的使用,使其非常适合后台管理界面的开发(目前来说界面有点过时了). 通过aj ...

  5. vue 树状图数据的循环 递归循环

    在main.js中注册一个子组件 在父组件中引用 树状图的数据格式 绑定一个数据传入子组件,子组件props接收数据 子组件中循环调用组件,就实现了递归循环

  6. Android中的树状(tree)列表

    树状列表前端挺常用的,还有人专门写过Ztree,Android中有的时候也需要使用到树状列表,上篇文章写了一下ExpandableListView,ExpandableListView最多支持两级结构 ...

  7. Tkinter 之TreeView表格与树状标签

    一.TreeView介绍 TreeView组件是一个树状结构和表格的结合体.第一列是树状结构,后几列是列表.每一行表示一个item,树的item可以分级,每个item有子item,名称对应text标签 ...

  8. 手把手教学~基于element封装tree树状下拉框

    在日常项目开发中,树状下拉框的需求还是比较常见的,但是element并没有这种组件以供使用.在这里,小编就基于element如何封装一个树状下拉框做个详细的介绍. 通过这篇文章,你可以了解学习到一个树 ...

  9. jquery-treegrid树状表格的使用(.Net平台)

    上一篇介绍了DataTable,这一篇在DT的基础之上再使用jquery的一款插件:treegrid,官网地址:http://maxazan.github.io/jquery-treegrid/ 一. ...

随机推荐

  1. 老男孩Day1作业(一):编写登录接口

    需求:编写登陆接口1. 用户输入帐号密码进行登陆2. 用户信息保存在文件内3. 用户密码输入错误三次后锁定用户 1)编写思路 编写思路参考下面GitHub链接中的流程图 https://github. ...

  2. Apple开发者账号更改公司名称

    对于大多数的开发者而言,可能很少遇见要修改账户的公司名称的问题,本来公司之前是这个名称,但是后来改名为另外的名称,这样的话在Apple开发者账号上的公司名称再用就不好了,所以就需要到Apple dev ...

  3. day23作业详解

    1.题目 2.题目详解 点击查看详细内容 1. 1-1 封装 把功能封装到类中 class Message(object): def email(self):pass def msg(self):pa ...

  4. 6、kvm克隆虚拟机

    kvm克隆有两种方法可以使用先暂停kvm虚拟机 virsh suspend privi-server 方法一:对虚拟机本身直接clone virt-clone -o privi-server -n p ...

  5. SharePoint2016 母版页引用样式和脚本路径无效

    直接引用16目录(/_layouts/16/)会导致页面找不到文件,必须将16目录改为15目录(/_layouts/15/),估计是内部机制还没有更新,这个坑不知道要多久才会填上=,=

  6. win10系统添加照片查看器(win7版)

    很多人不习惯win10的照片查看器,还是比较怀念win7版本的照片查看器,通过以下两种方法可以在win10图片上点击右键时,"打开方式"中重现"Windows照片查看器& ...

  7. SpringBoot | 第十九章:web应用开发之WebSocket

    前言 web开发也讲解了三章了,这章节开始讲解关于与前端通信相关知识.实现一个在线聊天室类似的功能或者后端推送消息到前端,在没有WebSocket时,读大学那伙还有接触过DWR(Direct Web ...

  8. 解决WinSCP连接虚拟机

    其实虚拟机你也可以将它形象化,认为它就是一台电脑,只是这个电脑在你的内存中,所以,一般电脑所具有的的功能虚拟机一样拥有,它也可以当成一台独立的个体哦. 针对很多使用WinSCP连接不上虚拟机的问题,这 ...

  9. textArea 默认添加的空格

    1.会莫名多出一些空格(标签闭合换行了) <textarea class="borderstyle width97b height75" name="SmsTax. ...

  10. 《Head First 设计模式》之适配器模式与外观模式

    适配器模式(Adapter) 适配器(adapter-pattern):将一个类的接口,转换成客户期望的另一个接口.适配器让原来接口不兼容的类可以合作无间.两种形式: 对象适配器(组合) 类适配器(多 ...