Vue org tree organization chart component application and source code analysis

Douqu programming 2022-06-23 15:59:31 阅读数:210


demand :

Recently, the business needs to do the organizational structure tree function similar to mind mapping , Need to be able to dynamically add, delete and modify nodes , And the node styles of each layer are different

Available plug-ins :

The organization chart plug-ins you can find online are :

1.orgchart.js Address :

This indentation is very unusual , I don't like ;

2.vue-orgchart Address :

This is for the first one vue Modified version , The same indentation is different and the style is not beautiful

3.vue-org-tree Address :

This is a pure vue edition , I like the way it's indented and styled , And it's also github The one with the most stars .

After comparison , I chose vue-org-tree, But it is only equivalent to providing a template , To realize its own functions, you need to modify the plug-in .


vue-org-tree Installation and use :


# use npm
npm i vue2-org-tree --save
# use yarn
yarn add vue2-org-tree


import Vue from 'vue'
import Vue2OrgTree from 'vue2-org-tree'

See this for the component application case :

You can directly copy and run .api Also look directly github Official website

It can be seen that the official function of this component is only to display data , The function of adding, deleting and modifying is not specified , This requires us to modify the components ourselves .

the reason being that vue, Therefore, the addition, deletion and modification of nodes are inseparable from the addition, deletion and modification of data , The emphasis is on data processing .


vue-org-tree Source code analysis

Let me briefly analyze the source code and transformation

Install well vue-org-tree after , find node_modules->vue2-org-tree Folder

among org-tree.vue For component entry ,node.js Use Rendering function Dynamic render node components

About rendering functions , Look at this :


<div class="org-tree-container">
<div class="org-tree" :class="{horizontal, collapsable}">
@on-expand="(e, data) => {$emit('on-expand', e, data)}"
@on-node-click="(e, data) => {$emit('on-node-click', e, data)}"
import render from './node'
export default {
name: 'Vue2OrgTree',
components: {
OrgTreeNode: {
functional: true
props: {
data: {
type: Object,
required: true
props: {
type: Object,
default: () => ({
label: 'nodeName',
expand: 'expand',
children: 'children',
horizontal: Boolean,
selectedKey: [String,Number],
collapsable: Boolean,
renderContent: Function,
labelWidth: [String, Number],
labelClassName: [Function, String],
selectedClassName: [Function, String]
<style lang="less">
@import '../../styles/org-tree';

this vue The main function of is to receive data and parameters ,org-tree-node It is used to generate structure diagram nodes . The point is here :

Generally speaking, components will introduce a vue

Here is a direct introduction render, It looks like a functional rendering , The general rendering function is :

render: function (createElement) {
return createElement('h1', " This is the generated content ")

createElement by vue Generation in dom Methods , stay vue General use ”h” Instead of .

For example, sometimes initialization vue That's what I wrote :

new Vue({
render: h => h(App),

amount to :

return createElment(App)

Directly generate component elements , The setting is omitted components

Be similar to js The original createElement Method , However, the parameter of the native intermediate transmission is the node name , Such as :

var btn=document.createElement("BUTTON");

and vue Medium createElement Parameters in Look at the tutorial first :

The first parameter is generally html Tag name or component ( As in the above example, a component can be rendered directly ), The second is the configuration element attribute , The third one is the configuration sub element ( Can pass string or vnode Array )

vnode It is through createElement The virtual node created .

( It's too much , Be sure to look at the documents and master the basic knowledge )

But this rendering function has no state management and event listening , therefore vue You can also do it in render Add a functional:true, Turn it into “ Functional component ”

course :

render: function (createElement, context) {
// ...

So back to org-tree.vue in ,node.js Must be something like function(h,context){} Function of .


// Determine whether the leaf node
const isLeaf = (data, prop) => {
return !(Array.isArray(data[prop]) && data[prop].length > 0)
// establish node node
export const renderNode = (h, data, context) => {
const { props } = context
const cls = ['org-tree-node']
const childNodes = []
const children = data[props.props.children]
if (isLeaf(data, props.props.children)) {
} else if (props.collapsable && !data[props.props.expand]) {
childNodes.push(renderLabel(h, data, context))
if (!props.collapsable || data[props.props.expand]) {
childNodes.push(renderChildren(h, children, context))
return h('div', {
domProps: {
className: cls.join(' ')
}, childNodes)
// Create an expand collapse button
export const renderBtn = (h, data, { props, listeners }) => {
const expandHandler = listeners['on-expand']
let cls = ['org-tree-node-btn']
if (data[props.props.expand]) {
return h('span', {
domProps: {
className: cls.join(' ')
on: {
click: e => expandHandler && expandHandler(e,data)
// establish label node
export const renderLabel = (h, data, context) => {
const { props, listeners } = context
const label = data[props.props.label]
const labelType=data[props.props.labelType]
const dataCnt=data[props.props.dataCnt]
const renderContent = props.renderContent
const clickHandler = listeners['on-node-click']
const childNodes = []
if(labelType=="tag" || labelType=="root"){
if (typeof renderContent === 'function') {
let vnode = renderContent(h, data)
vnode && childNodes.push(vnode)
} else {
}else if(labelType=="domain"){
childNodes.push(h('br', {
if (props.collapsable && !isLeaf(data, props.props.children)) {
childNodes.push(renderBtn(h, data, context))
const cls = ['org-tree-node-label-inner']
let { labelWidth, labelClassName, selectedClassName, selectedKey } = props
if(labelType == "root"){
}else if(labelType == "tag"){
}else if(labelType == "domain"){
if (typeof labelWidth === 'number') {
labelWidth += 'px'
if (typeof labelClassName === 'function') {
labelClassName = labelClassName(data)
labelClassName && cls.push(labelClassName)
// add selected class and key from props
if (typeof selectedClassName === 'function') {
selectedClassName = selectedClassName(data)
// Add... To the selected node class
if(selectedKey == data[props.props.selectedKey]){
/* console.log(selectedKey)
selectedClassName && selectedKey && data[selectedKey] && cls.push(selectedClassName) */
return h('div', {
domProps: {
className: 'org-tree-node-label'
}, [h('div', {
domProps: {
className: cls.join(' ')
style: { width: labelWidth },
on: {
click: e => clickHandler && clickHandler(e, data)
}, childNodes)])
// establish node Child node
export const renderChildren = (h, list, context) => {
if (Array.isArray(list) && list.length) {
const children = => {
return renderNode(h, item, context)
return h('div', {
domProps: {
className: 'org-tree-node-children'
}, children)
return ''
export const render = (h, context) => {
const {props} = context
return renderNode(h,, context)
export default render

so , It does return render function .

The next step is renderNode-》renderLabel-》renderChildren-》renderBtn

I understand createElement Method , These steps are very good to understand . Just change your business requirements inside .

It is important to note that in renderLabel The middle node and the expand button are the same label, When you click a node, you don't want the button to trigger , Then deal with it like this :

onNodeClick: function(e, data) {
// See whether it is a node or a button
var classList=[];
var label=classList.find(item=> item=="org-tree-node-label-inner")











版权声明:本文为[Douqu programming]所创,转载请带上原文链接,感谢。