react树状组件
最近在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: 1; } .tree .childNode .checkBox { position: absolute; width: 16px; left: 20px; top: 0; z-index: 2; margin: 7px 10px 0; height: 16px; box-sizing: border-box; border: 1px solid #d2d2d2; vertical-align: text-bottom; font-size: 0; border-radius: 2px; cursor: pointer; } .tree .childNode .checkBox:hover { cursor: pointer; border-color: #5bb976; } .tree .childNode .checkBox.checked { border: 0; 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: 0; 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: 1; 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: 0; margin: 5px 10px 0; z-index: 2; } .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: 1; } .tree .childNode:after { position: absolute; bottom: -12px; left: 7px; width: 1px; height: 30px; z-index: 3; background-color: #ffffff; content: ''; } .tree .childNode.parentNode:before { border-top: none; } .tree .childNode .openNode { position: absolute; z-index: 5; left: 0; 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: 4; background-color: #ffffff; }
utils里面是封装了一个stack栈,关于js栈的使用请移步js遍历树状数据文章。

浙公网安备 33010602011771号