vue和react实现组织架构树


公司项目需求,要在页面中实现一个公司组织架构图,并实现每个节点可以通过点击事件增加子节点,删除子节点。当时首先想到的是用echart的树图实现,但是echart的树图的节点不能自定义样式,而且点击事件也不是那么方便,于是就放弃了。然后在github上找找轮子,发现两个轻量的组织架构树组件,一个是vue版本,另一个是react版本的。在此记录一下使用步骤。

组织树org-tree

vue版本:vue-org-tree

这是一个纯vue版本,我喜欢它的缩进方式和样式,同时它也是github上星星最多的。它只是相当于提供了模板,要实现自己的功能需要对插件进行改造。github项目地址

安装

npm install --save-dev vue2-org-tree

// 只引入vue2-org-tree运行会报错少包
npm install --save-dev less less-loader

注意,demo中有两个bug,节点无法展开问题:

// 底下这个函数一定要写两个参数,如果只写onExpand(data),data.expand结果是undefined
// 这个应该是插件的bug,一般只写一个参数也是可以
onExpand(e,data) {
      if ("expand" in data) {
        data.expand = !data.expand;
        if (!data.expand && data.children) {
          this.collapse(data.children);
        }
      } else {
        this.$set(data, "expand", true);
      }
    }

点击node要跳转链接,点击展开收缩按钮也会跳转问题

  增加判断语句即可,默认展开收缩按钮对象里的url肯定是空
  onNodeClick(e, data) {
       //console.log(data.label);
       if(data.url==null){
        return false
      }else{
        window.open(data.url)
      }
    },

效果图

使用

刚开始把玩一番,颇为得意,很轻松的解决了烦人的需求。真是天道酬勤,哈哈哈哈,报应不爽啊,才懒一会儿,就被泼了凉水,因为我发现一个很让人绝望的问题。虽然这个组件支持你把数据传进来,然后渲染出效果。然后点两下开关,横排竖排显示。但是node节点不支持扩展,也就是说node节点只能显示字符串,不能自己自定义样式,这可不能满足变态的需求啊。可以看到节点方法,这个方法的返回值会渲染的节点中显示

renderContent(h, data) {
      return data.label;
    }

我尝试着在返回值中加入html标签,发现会当做字符串显示,而不会解析成dom。本想着放弃了,于是想着试试返回JSX模板,vue默认是不支持JSX,需要导入插件才能支持,这就是会用到一个 Babel plugin 插件,然后导入以下组件

npm install babel-plugin-syntax-jsx --save-dev
npm install babel-plugin-transform-vue-jsx --save-dev
npm install babel-helper-vue-jsx-merge-props --save-dev
npm install babel-preset-env --save-dev

修改项目根目录下的.babelrc文件,加入以下内容

{
 "presets": ["es2015"],
 "plugins": ["transform-vue-jsx"]
}

然后就可以这样写node的初始化方法了

renderContent: function(h, data) {
                return (
                    <div>
                        <li>{data.label}</li>
                    </div>)
            }

组件完整代码

<template>
  <div>
    <div class="container">
      <div class="col-md-10 col-md-offset-1">
        <div class="page-header">
          <h3>基于Vue的组织架构树组件</h3>
        </div>
        <div class="row">
          <div class="col-md-8 col-md-offset-2">
            <form class="form-horizontal row">
              <div class="col-md-4">
                <div class="checkbox">
                  <label>
                    <input
                      type="checkbox"
                      v-model="horizontal"
                    > Horizontal
                  </label>
                </div>
              </div>
              <div class="col-md-4">
                <div class="checkbox">
                  <label>
                    <input
                      type="checkbox"
                      v-model="collapsable"
                    > Collapsable
                  </label>
                </div>
              </div>
              <div class="col-md-4">
                <div class="checkbox">
                  <label>
                    <input
                      type="checkbox"
                      v-model="expandAll"
                      @change="expandChange"
                    > Expand All
                  </label>
                </div>
              </div>
              <br>
              <div class="col-md-6">
                <div class="form-group">
                  <label class="control-label col-md-5">labelClassName:</label>
                  <div class="col-md-7">
                    <select
                      class="form-control"
                      v-model="labelClassName"
                    >
                      <option value="bg-white">bg-white</option>
                      <option value="bg-orange">bg-orange</option>
                      <option value="bg-gold">bg-gold</option>
                      <option value="bg-gray">bg-gray</option>
                      <option value="bg-lightpink">bg-lightpink</option>
                      <option value="bg-chocolate">bg-chocolate</option>
                      <option value="bg-tomato">bg-tomato</option>
                    </select>
                  </div>
                </div>
              </div>
            </form>
          </div>
        </div>
        <br>
        <div style="margin-left: 150px"><el-button type="primary" @click.native="testBtn">主要按钮</el-button></div>
        <div class="text-center" style="text-align: center;">
          <vue2-org-tree name="test"
                         :data="orgTree"
                         :horizontal="horizontal"
                         :collapsable="collapsable"
                         :label-class-name="labelClassName"
                         :render-content="renderContent"
                         @on-expand="onExpand"
                         @on-node-click="onNodeClick"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script type="text/ecmascript-6">
    export default {
        data() {
            return {
                orgTree: {
                    id: 0,
                    label: "XXX科技有限公司",
                    children: [
                        {
                            id: 2,
                            label: "产品研发部",
                            children: [
                                {
                                    id: 5,
                                    label: "研发-前端"
                                },
                                {
                                    id: 6,
                                    label: "研发-后端"
                                },
                                {
                                    id: 9,
                                    label: "UI设计"
                                },
                                {
                                    id: 10,
                                    label: "产品经理"
                                }
                            ]
                        },
                        {
                            id: 3,
                            label: "销售部",
                            children: [
                                {
                                    id: 7,
                                    label: "销售一部"
                                },
                                {
                                    id: 8,
                                    label: "销售二部"
                                }
                            ]
                        },
                        {
                            id: 4,
                            label: "财务部"
                        },
                        {
                            id: 9,
                            label: "HR人事"
                        }
                    ]
                },
                horizontal: false,
                collapsable: true,
                expandAll: true,
                labelClassName: "bg-orange"
            };
        },
        methods: {
            testBtn(){
              this.orgTree={
                  id: 0,
                  label: "XXX科技有限公司",
                  children: [
                      {
                          id: 2,
                          label: "产品研发部",
                          children: [
                              {
                                  id: 5,
                                  label: "研发-前端"
                              },
                              {
                                  id: 6,
                                  label: "研发-后端",
                                  children: [
                                      {
                                          id: 13,
                                          label: "Java研发组"
                                      },
                                      {
                                          id: 14,
                                          label: "C++研发组"
                                      }
                                  ]
                              },
                              {
                                  id: 9,
                                  label: "UI设计"
                              },
                              {
                                  id: 10,
                                  label: "产品经理"
                              }
                          ]
                      },
                      {
                          id: 3,
                          label: "销售部",
                          children: [
                              {
                                  id: 7,
                                  label: "销售一部"
                              },
                              {
                                  id: 8,
                                  label: "销售二部"
                              }
                          ]
                      },
                      {
                          id: 4,
                          label: "财务部",
                          children: [
                              {
                                  id: 10,
                                  label: "财务一部"
                              },
                              {
                                  id: 11,
                                  label: "财务二部"
                              }
                          ]
                      },
                      {
                          id: 9,
                          label: "HR人事"
                      }
                  ]
              }
            },
            renderContent: function(h, data) {
                return (
                    <div>
                        <li>{data.label}</li>
                    </div>)
            },
            onExpand: function(e, data) {
                if ('expand' in data) {
                    data.expand = !data.expand;
                    if (!data.expand && data.children) {
                        this.collapse(data.children)
                    }
                } else {
                    this.$set(data, 'expand', true)
                }
            },
            onNodeClick: function(e, data) {
                //console.log(data.label);
                if(data.url==null){
                    return false
                }else{
                    window.open(data.url)
                }
            },
            collapse: function(list) {
                let _this = this
                list.forEach(function(child) {
                    if (child.expand) {
                        child.expand = false
                    }

                    child.children && _this.collapse(child.children)
                })
            },
            expandChange: function() {
                this.toggleExpand(this.data, this.expandAll)
            },
            toggleExpand: function(data, val) {
                let _this = this;
                if (Array.isArray(data)) {
                    data.forEach(function(item){
                        _this.$set(item, 'expand', val)
                        if (item.children) {
                            _this.toggleExpand(item.children, val)
                        }
                    })
                } else {
                    this.$set(data, 'expand', val)
                    if (data.children) {
                        _this.toggleExpand(data.children, val)
                    }
                }
            }
        }
    };
</script>
<style type="text/css">
  .org-tree-node-label {
    white-space: nowrap;
  }
  .bg-white {
    background-color: white;
  }
  .bg-orange {
    background-color: orange;
  }
  .bg-gold {
    background-color: gold;
  }
  .bg-gray {
    background-color: gray;
  }
  .bg-lightpink {
    background-color: lightpink;
  }
  .bg-chocolate {
    background-color: chocolate;
  }
  .bg-tomato {
    background-color: tomato;
  }
</style>

react版本:react-org-tree

这是一个纯react版本,同时也是vue-org-tree的react版本,但似乎与vue-org-tree不是一个作者,vue-org-tree的原作者开始并没有提供react版本。react-org-tree完全还原了vue-org-tree的全部功能和样式,为需要使用react版本的的组织树提供了便利。react-org-tree项目地址,可以关注下

安装

修复了vue版本的少包问题,只要导入react-org-tree即可

npm install react-org-tree

效果图

横向展示图

纵向展示图

组件完整代码

import React, {Component} from "react";
import {PageHeaderWrapper} from "@ant-design/pro-layout";
import { Collapse,Button,Rate  } from 'antd';
import OrgTree from 'react-org-tree';


const { Panel } = Collapse;

const MyNodeComponent = node => (
    <div className="initechNode" key={node.id} onClick={() => nodeClick(node)}>
      <li>{node.label}</li>
      <Rate allowHalf defaultValue={node.rate} />
    </div>
  );

function nodeClick(node) {
  alert(node.label);
}

function callback(key) {
  console.log(key);
}


class MyOrgTree extends Component {
  constructor(props){
    super(props);
    this.state = {
      orgTree: {
        id: 0,
        label: 'XXX股份有限公司',
        rate:5,
        children: [{
          id: 1,
          rate:3,
          label: '技术部',
          children: [{
            id: 4,
            rate:2.5,
            label: '后端工程师'
          }, {
            id: 5,
            rate:2,
            label: '前端工程师'
          }, {
            id: 6,
            rate:2.5,
            label: '运维工程师'
          }]
        }, {
          id: 2,
          rate:3.5,
          label: '人事部'
        }, {
          id: 3,
          rate:4,
          label: '销售部'
        }]
      },
      horizontal : false,
      collapsable : true,
      expandAll : true
    };
  }

  testBtn=()=>{
    const org = {
      id: 0,
      label: 'XXX股份有限公司',
      rate:5,
      children: [{
        id: 1,
        rate:3,
        label: '技术部',
        children: [{
          id: 4,
          rate:2.5,
          label: '后端工程师'
        }, {
          id: 5,
          rate:2,
          label: '前端工程师'
        }, {
          id: 6,
          rate:2.5,
          label: '运维工程师'
        }]
      }, {
        id: 2,
        rate:3.5,
        label: '人事部',
        children:[{
          id: 15,
          rate:2,
          label: '人事一部'
        }, {
          id: 16,
          rate:2.5,
          label: '人事二部'
        }]
      }, {
        id: 3,
        rate:4,
        label: '销售部'
      }]
    };
    this.setState({
      orgTree:org
    });
    this.toggleExpand(org,true);
  };

  testBtn1=()=>{
    this.setState({
      horizontal:false
    });
  };

  testBtn2=()=>{
    this.setState({
      horizontal:true
    });
  };

  toggleExpand=(data, val) => {
    // eslint-disable-next-line no-underscore-dangle
    const _this = this;
    if (Array.isArray(data)) {
      data.forEach(function (item) {
        item.expand = val;
        if (item.children) {
          _this.toggleExpand(item.children, val);
        }
      });
    } else {
      data.expand = val;
      if (data.children) {
        _this.toggleExpand(data.children, val);
      }
    }
    this.forceUpdate();
  };

  render() {
    return (
      <PageHeaderWrapper>
        <div>
          <Collapse defaultActiveKey={['1']} onChange={callback}>
            <Panel header="This is panel header 1" key="1">
              <OrgTree
                data={this.state.orgTree}
                horizontal={this.state.horizontal}
                collapsable={this.state.collapsable}
                expandAll={this.state.expandAll}
                renderContent={MyNodeComponent}
              />
            </Panel>
            <Panel header="This is panel header 1" key="2">
              <Button style={{marginLeft:'20px'}} type="primary" onClick={this.testBtn}>添加节点</Button>
              <Button style={{marginLeft:'20px'}} type="primary" onClick={this.testBtn1}>纵向展示</Button>
              <Button style={{marginLeft:'20px'}} type="primary" onClick={this.testBtn2}>横向展示</Button>
            </Panel>
          </Collapse>

        </div>
      </PageHeaderWrapper>
    );
  }
}
export default MyOrgTree;

orgchart

react-orgchart是一个纯的react的组织树插件,网上有个叫orgchart的组件,它是基于jquery的,使用时必须导入jquery。而这里使用的react-orgchart则不需要依赖其他东西,npm官方地址。react-orgchart基本也能实现react-org-tree的效果,唯一不足的是没有横向展示,只有纵向展示。此外节点不支持收缩展开。不过这一点可以自己实现,大致思路如下:react-orgchart渲染出来是使用table来实现的,每个node实际上在tr中,而子节点所在的tr和父节点的tr是兄弟元素。因此在节点上添加点击事件,获取当前dom所在的tr,然后把这个tr的所有兄弟tr隐藏即可。下面介绍一下简单使用

安装

npm install react-orgchart

效果图

使用

首先引入组件和样式

import OrgChart from 'react-orgchart';
import 'react-orgchart/index.css';

刚开始使用的时候呢,发现它没有vue版本的好看,不过随后我就发现它的每个节点也是可以自定义样式的,每个节点也是一个组件,因此可以轻松的改写样式,节点上写点击事情也非常容易,对react兼容的非常好。不像vue版本那样需要额外引入支持JSX的插件,节点组件代码如下

const MyNodeComponent = ({node}) => {
  return (
    <div className="initechNode" onClick={() => alert("Hi my real name is: " + node.actor)}>{ node.name }</div>
  );
};

组件完整代码

import React, {Component} from "react";
import {PageHeaderWrapper} from "@ant-design/pro-layout";
import { Collapse,Button } from 'antd';
import OrgChart from 'react-orgchart';
import 'react-orgchart/index.css';


const { Panel } = Collapse;

const MyNodeComponent = ({node}) => (
    <div className="initechNode" style={{border:'1px dashed #000',width:'100px',margin:'0 auto'}} onClick={() => alert(node.actor)}>
      { node.name }
    </div>
  );

function callback(key) {
  console.log(key);
}


class MyOrgTree extends Component {
  constructor(props){
    super(props);
    this.state = {
      orgTree:{
        name: "root",
        actor: "root",
        children: [
          {
            name: "Peter Gibbons",
            actor: "Ron Livingston",
            children: [
              {
                name: "And More!!",
                actor: "Gary Cole1"
              },
              {
                name: "And More!!",
                actor: "Gary Cole1"
              }
            ]
          },
          {
            name: "test",
            actor: "test"
          },
          {
            name: "Bob Slydell",
            actor: "John C. McGi..."
          },
        ]
      }
    };
  }

  testBtn=()=>{
    this.setState({
      orgTree:{
        name: "root",
        actor: "root",
        children: [
          {
            name: "Peter Gibbons",
            actor: "Ron Livingston",
            children: [
              {
                name: "And More!!",
                actor: "Gary Cole1"
              },
              {
                name: "And More!!",
                actor: "Gary Cole1"
              }
            ]
          },
          {
            name: "test",
            actor: "Stephen Root",
            children: [
              {
                name: "test1",
                actor: "test1",
                children: [
                  {
                    name: "test3",
                    actor: "test3"
                  },
                  {
                    name: "test4",
                    actor: "test4"
                  }
                ]
              },
              {
                name: "test2",
                actor: "test2"
              }
            ]
          },
          {
            name: "Bob Slydell",
            actor: "John C. McGi..."
          },
        ]
      }
    })
  };

  render() {
    return (
      <PageHeaderWrapper>
        <div>
          <Collapse defaultActiveKey={['1']} onChange={callback}>
            <Panel header="This is panel header 1" key="1">
              <OrgChart tree={this.state.orgTree} NodeComponent={MyNodeComponent} />
            </Panel>
            <Panel header="This is panel header 1" key="2">
              <Button type="primary" onClick={this.testBtn}>Primary</Button>
            </Panel>
          </Collapse>

        </div>
      </PageHeaderWrapper>
    );
  }
}
export default MyOrgTree;

Author: 顺坚
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source 顺坚 !
评论
 Previous
spring-boot注解大全 spring-boot注解大全
spring boot是后端开发最主流的框架,spring boot的核心之一就是注解,它提供了很多注解来帮助我们简化配置,通过各种组合注解,极大地简化了spring项目的搭建和开发。为了方便我们在日常开发注解的使用,本文将开发所需要的注解
2020-04-04
Next 
element-ui实现二级多选框 element-ui实现二级多选框
最近公司做项目碰到个需求,要实现一个二级下拉的多选框,并且实现选中一级条目时,此条目下的二级条目全选和全不选的功能。全选和全不选倒简单通过JS实现一下就行了,最主要的是elementUI没有现成的二级多选下拉框,因此首先需要实现一个多选二级
2020-03-30
  TOC