最近在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. 利用canvas实现倒计时功能

    wxml代码:<view class=“page-body”><view class=“page-body-wrapper”><canvas canvas-id=“can ...

  2. Javascript专题(三)b.各种轮播和细节分析--上下滚动轮播

    这一次,我们用原生JS实现上下滚动方式的轮播.顺带学习一下用JS来创建HTML元素. 上一次写的轮播是淡入淡出效果的,相对来说其实是比较简单的. github源码: 上下轮播源码-github A. ...

  3. P2746 [USACO5.3]校园网Network of Schools

    传送门 把所有学校的关系构成一个图,显然一个强联通分量的所有学校只要有一个有新软件,其他学校也都会有 考虑缩点,发现入度为 0 的块一定要给,因为没有其他人给它 入度不为 0 的块一定有其他人给,我们 ...

  4. python大战机器学习——模型评估、选择与验证

    1.损失函数和风险函数 (1)损失函数:常见的有 0-1损失函数  绝对损失函数  平方损失函数  对数损失函数 (2)风险函数:损失函数的期望      经验风险:模型在数据集T上的平均损失 根据大 ...

  5. mysql事务锁表

    -- 查看被锁住的SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; -- 等待锁定SELECT * FROM INFORMATION_SCHEMA.INNO ...

  6. 整理一下postgresql的扩展功能postgis和pgrouting的使用

    postgis windows的下的安装使用postgresql的bin目录下的stackbuiler Ubuntu14.04下的安装: apt-get install postgresql-9.3- ...

  7. Fence Repair (二叉树求解)(优先队列,先取出小的)

    题目链接:http://poj.org/problem?id=3253 Fence Repair Time Limit: 2000MS   Memory Limit: 65536K Total Sub ...

  8. HDU 4622 Reincarnation Hash解法详解

    今天想学字符串hash是怎么弄的.就看到了这题模板题 http://acm.hdu.edu.cn/showproblem.php?pid=4622 刚开始当然不懂啦,然后就上网搜解法.很多都是什么后缀 ...

  9. js电话号码校验

    var contact_phone = $("#contact_phone").val();        if(contact_phone && /^1[3|4| ...

  10. SpringBoot | 第十二章:RabbitMQ的集成和使用

    前言 上节讲了缓存数据库redis的使用,在实际工作中,一般上在系统或者应用间通信或者进行异步通知(登录后发送短信或者邮件等)时,都会使用消息队列进行解决此业务场景的解耦问题.这章节讲解下消息队列Ra ...