实现组织架构图(vue-org-tree)
如果向使用原来的依赖可以使用这个人的,因为我也是根据这个博客大佬仿照Vue-org-tree实现的方案
对此有几点不惑,问了大佬,大佬也没有回复我
由于还在启动项目中,还没有完善,目前已经可以支持动态创建节点了
这是通过点击实现抽屉展示
这是通过hover鼠标悬停高亮,由于我并不是一个正儿八经的前端,准确的说我是来搞笑的。所以对于每个节点面板渲染是很简陋的。
# 这里是安装大佬提供的依赖
npm install vue-tree-color
检查less和less-loader,因为该组件使用到了less,可以看看项目工程是否已经安装
npm install --save-dev less less-loader
注意这里可能会在项目启动的时候出现以下问题
报错信息:Syntax Error: TypeError: this.getOptions is not a function
解决方案-需要在安装less和lessloader时,指定版本号,否则就会导致兼容问题。
npm i [email protected] [email protected] -D
找到main.js中追加一下内容
import Vue2OrgTree from 'vue-tree-color'
Vue.use(Vue2OrgTree)
在公共层面@components 目录下,我们需要进行二次封装,为什么要二次封装,上面已经说到原因了,因为定制化内容不同, 肯定会涉及更改组件渲染方式以及样式,因为二次封装是非常有必要的
我就随便给取了一个名字 SimpleTree 目录
组件页面,我没做更改,直接拿来用
<template>
<div class="org-tree-container">
<div class="org-tree" :class="{horizontal, collapsable}">
<org-tree-node
:data="data"
:props="props"
:judge="judge"
:NodeClass="NodeClass"
:horizontal="horizontal"
:label-width="labelWidth"
:collapsable="collapsable"
:render-content="renderContent"
:label-class-name="labelClassName"
@on-expand="(e, data) => $emit('on-expand', e, data)"
@on-node-focus="(e, data) => $emit('on-node-focus', e, data)"
@on-node-click="(e, data) => $emit('on-node-click', e, data)"
@on-node-mouseover="(e, data) => $emit('on-node-mouseover', e, data)"
@on-node-mouseout="(e, data) => $emit('on-node-mouseout', e, data)"
/>
div>
div>
template>
<script>
import render from './node'
export default {
name: "SimpleTree",
components: {
OrgTreeNode: {
render,
functional: true
}
},
props: {
data: {
type: Object,
required: true
},
props: {
type: Object,
default: () => ({
id: 'id',
logo: 'logo',
key: 'key',
parentKey: 'parentKey',
label: 'label',
expand: 'expand',
children: 'children',
type: 'type',
status: 'status',
controller: 'controller',
sys: 'sys',
remark: 'remark',
description: 'description',
registration: 'registration'
})
},
judge: {
type: Object,
required: false
},
NodeClass: {
type: Array,
required: false
},
horizontal: Boolean,
selectedKey: String,
collapsable: Boolean,
renderContent: Function,
labelWidth: [String, Number],
labelClassName: [Function, String],
selectedClassName: [Function, String]
}
}
script>
<style lang="less">
@import './tree';
style>
这个js就是组件渲染的函数,可以稍微研究一下
// 判断是否叶子节点
const isLeaf = (data, prop) => {
return !(Array.isArray(data[prop]) && data[prop].length > 0)
}
// 创建 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)) {
cls.push('is-leaf')
} else if (props.collapsable && !data[props.props.expand]) {
cls.push('collapsed')
}
if (data) {
childNodes.push(renderLabel(h, data, context))
}
if ((!props.collapsable || data[props.props.expand]) && children.length > 0) {
childNodes.push(renderChildren(h, children, context))
}
return h('div', {
domProps: {
className: cls.join(' ')
}
}, childNodes)
}
// 创建展开折叠按钮
export const renderBtn = (h, data, { props, listeners }) => {
const expandHandler = listeners['on-expand']
let cls = ['org-tree-node-btn']
if (data[props.props.expand]) {
cls.push('expanded')
}
return h('span', {
domProps: {
className: cls.join(' ')
},
on: {
click: e => expandHandler && expandHandler(e,data)
}
})
}
// 创建 label 节点
export const renderLabel = (h, data, context) => {
const { props, listeners } = context
const label = data[props.props.label]
const renderContent = props.renderContent
// event handlers
const clickHandler = listeners['on-node-click']
const mouseOverHandler = listeners['on-node-mouseover']
const mouseOutHandler = listeners['on-node-mouseout']
const childNodes = []
if (typeof renderContent === 'function') {
let vnode = renderContent(h, data)
vnode && childNodes.push(vnode)
} else {
childNodes.push(label)
}
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 ,judge,NodeClass} = props
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)
}
selectedClassName && selectedKey && data[selectedKey] && cls.push(selectedClassName)
return h('div', {
domProps: {
className: 'org-tree-node-label'
}
}, [h('div', {
domProps: {
className:ChangeTheColor(data,judge,NodeClass) + " org-tree-node-label-inner"
},
style: { width: labelWidth },
on: {
'click': e => clickHandler && clickHandler(e, data),
'mouseover': e => mouseOverHandler && mouseOverHandler(e, data),
'mouseout': e => mouseOutHandler && mouseOutHandler(e, data)
}
}, childNodes)])
}
function ChangeTheColor(e,judge,NodeClass){
if(judge !== "" && judge !== undefined && judge !== null && judge.swtich !== false){
for(var k in judge) {
var a = (eval("e."+k))
if(NodeClass){
for(let c =0 ;c<NodeClass.length;c++){
if( a === NodeClass[c])
return NodeClass[c]
else if(NodeClass.length-1==c)
return ""
}
}else{
return ""
}
}
}else{
return ""
}
}
// 创建 node 子节点
export const renderChildren = (h, list, context) => {
if (Array.isArray(list) && list.length > 0) {
const children = list.map(item => {
return renderNode(h, item, context)
})
return h('div', {
domProps: {
className: 'org-tree-node-children'
}
}, children)
}
return ''
}
export const render = (h, context) => {
const {props} = context
if (props.data.id) {
return renderNode(h, props.data, context)
} else {
return ''
}
}
export default render
.org-tree-container {
display: inline-block;
padding: 15px;
background-color: #fff;
}
.org-tree {
// display: inline-block;
display: table;
text-align: center;
&:before, &:after {
content: '';
display: table;
}
&:after {
clear: both;
}
}
.org-tree-node,
.org-tree-node-children {
position: relative;
margin: 0;
padding: 0;
list-style-type: none;
&:before, &:after {
transition: all .35s;
}
}
.org-tree-node-label {
position: relative;
display: inline-block;
.org-tree-node-label-inner {
padding: 10px 15px;
text-align: center;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
-webkit-transition-duration: 0.3s;
transition-duration: 0.3s;
cursor: pointer;
}
.org-tree-node-label-inner:hover {
background-color: #c6e2ff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)
}
}
.render-node-panel-header {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
margin: 10px 0;
}
.node-box {
min-width: 200px;
}
.render-node-panel-middle {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
}
.org-tree-node-btn {
position: absolute;
top: 100%;
left: 50%;
width: 20px;
height: 20px;
z-index: 10;
margin-left: -11px;
margin-top: 9px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 50%;
box-shadow: 0 0 2px rgba(0, 0, 0, .15);
cursor: pointer;
transition: all .35s ease;
&:hover {
background-color: #e7e8e9;
transform: scale(1.15);
}
&:before, &:after {
content: '';
position: absolute;
}
&:before {
top: 50%;
left: 4px;
right: 4px;
height: 0;
border-top: 1px solid #ccc;
}
&:after {
top: 4px;
left: 50%;
bottom: 4px;
width: 0;
border-left: 1px solid #ccc;
}
&.expanded:after {
border: none;
}
}
.org-tree-node {
padding-top: 20px;
display: table-cell;
vertical-align: top;
&.is-leaf, &.collapsed {
padding-left: 10px;
padding-right: 10px;
}
&:before, &:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 50%;
height: 19px;
}
&:after {
left: 50%;
border-left: 1px solid #ddd;
}
&:not(:first-child):before,
&:not(:last-child):after {
border-top: 1px solid #ddd;
}
}
.collapsable .org-tree-node.collapsed {
padding-bottom: 30px;
.org-tree-node-label:after {
content: '';
position: absolute;
top: 100%;
left: 0;
width: 50%;
height: 20px;
border-right: 1px solid #ddd;
}
}
.org-tree > .org-tree-node {
padding-top: 0;
&:after {
border-left: 0;
}
}
.org-tree-node-children {
padding-top: 20px;
display: table;
&:before {
content: '';
position: absolute;
top: 0;
left: 50%;
width: 0;
height: 20px;
border-left: 1px solid #ddd;
}
&:after {
content: '';
display: table;
clear: both;
}
}
.horizontal {
.org-tree-node {
// display: flex;
// flex-direction: row;
// justify-content: flex-start;
// align-items: center;
display: table-cell;
float: none;
padding-top: 0;
padding-left: 20px;
&.is-leaf, &.collapsed {
padding-top: 10px;
padding-bottom: 10px;
}
&:before, &:after {
width: 19px;
height: 50%;
}
&:after {
top: 50%;
left: 0;
border-left: 0;
}
&:only-child:before {
top: 1px;
border-bottom: 1px solid #ddd;
}
&:not(:first-child):before,
&:not(:last-child):after {
border-top: 0;
border-left: 1px solid #ddd;
}
&:not(:only-child):after {
border-top: 1px solid #ddd;
}
.org-tree-node-inner {
display: table;
}
}
.org-tree-node-label {
display: table-cell;
vertical-align: middle;
}
&.collapsable .org-tree-node.collapsed {
padding-right: 30px;
.org-tree-node-label:after {
top: 0;
left: 100%;
width: 20px;
height: 50%;
border-right: 0;
border-bottom: 1px solid #ddd;
}
}
.org-tree-node-btn {
top: 50%;
left: 100%;
margin-top: -11px;
margin-left: 9px;
}
& > .org-tree-node:only-child:before {
border-bottom: 0;
}
.org-tree-node-children {
// display: flex;
// flex-direction: column;
// justify-content: center;
// align-items: flex-start;
display: table-cell;
padding-top: 0;
padding-left: 20px;
&:before {
top: 50%;
left: 0;
width: 20px;
height: 0;
border-left: 0;
border-top: 1px solid #ddd;
}
&:after {
display: none;
}
& > .org-tree-node {
display: block;
}
}
}
data: 就是树形结构,下面会有案例
horizontal:排列形式
collapsable: 是否展开
on-expand: 展开、闭合节点
on-node-click: 节点点击事件
on-node-mouseover:鼠标悬停
on-node-mouseout:鼠标悬出
renderContent:渲染函数
data(){
return{
labelClassName: "bg-color-orange", // 看到node.js 中这个实际没用的
basicInfo: { id: null, label: null },
basicSwitch: false,
data: {
id: 0,
label: "XXX科技有限公司",
className: 'nxnnxnxn', // 切记HTML的class 标签属性不能以数字开头
children: [
{
id: 2,
label: "产品研发部",
className: 'nxnnxnxn-1',
children: [
{
id: 5,
label: "研发-前端",
children: [
{
id: 55,
className: 'nxnnxnxn-55',
label: "前端1"
},
{
id: 56,
className: 'nxnnxnxn-56',
label: "前端2"
},
{
id: 57,
className: 'nxnnxnxn-57',
label: "前端3"
},
{
id: 58,
className: 'nxnnxnxn-58',
label: "前端4"
}
]
}
]
}
]
},
}
},
methods:{
//渲染节点
renderContent(h, data) {
// 通过data中的className属性来对div元素进行注入class
// 每个节点渲染必然会走这个函数
//这里对应的不同的className 需要在上面的tree.less中写入样式
return (
<div class = {data.className}>
<div>
<i class="el-icon-user-solid"></i>
<span>{data.label}</span>
<span>男</span>
</div>
<div style="font-size:12px;line-height:20px;">测试人员</div>
</div>
);
},
//鼠标移出
onMouseout(e, data) {
console.log("onMouseout", data)
},
//鼠标移入
onMouseover(e, data) {
console.log("onMouseover", data)
},
//点击节点
NodeClick(e, data) {
console.log(e, data);
},
//默认展开
toggleExpand(data, val) {
if (Array.isArray(data)) {
data.forEach(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);
}
}
},
collapse(list) {
list.forEach(child => {
if (child.expand) {
child.expand = false;
}
child.children && this.collapse(child.children);
});
},
//展开
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);
}
},
}