需要展示一个组织结构图,默认展开三层,可以下载文件,具体需求如下图:
经过选择,最终决定使用 Echarts Tree 来实现需求。实现效果如下:
下面来说下具体实现方式:
按照 Echarts 官方文档引入组件:
import * as echarts from 'echarts/core';
import { TooltipComponent } from 'echarts/components';
import { TreeChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
require('echarts/lib/component/toolbox');
echarts.use([TooltipComponent, TreeChart, CanvasRenderer]);
下一步初始化组件:
<div ref="showEChart" :class="className" :style="{height:height,width:width}" :key="treeKey"/>
let showEChart = this.$refs.showEChart;
this.chart = echarts.init(showEChart, 'macarons');
初始化组件数据以及样式,这一步,可以将各节点的公用样式定义在 label 中:
setOptions (data) {
let option = {
// 提供数据视图、还原、下载的工具
toolbox: {
show: true
// feature: {
// mark: { show: true },
// dataView: { show: true, readOnly: false },
// restore: { show: true },
// saveAsImage: { show: true }
// }
},
series: [
{
name: '组织结构图',
type: 'tree',
orient: 'TB', // 竖向或水平 TB代表竖向 LR代表水平
layout: 'orthogonal',
edgeShape: 'polyline',
edgeForkPosition: '63%',
top: '50px',
bottom: '50px',
zlevel: 2,
initialTreeDepth: this.initialTreeDepth, // 树图初始展开的层级(深度)
// expandAndCollapse: true, // 点击节点时不收起子节点,default: true
symbolSize: [100, 40], // 图片大小
itemStyle: {
color: 'transparent',
padding: [10, 0, 10, 0],
borderWidth: 0
},
label: {
borderRadius: [5, 5, 5, 5],
borderColor: '#eeeeee',
backgroundColor: '#ffffff',
shadowColor: 'rgb(0 0 0 / 15%)',
shadowBlur: 1,
rich: {
first: {
align: 'center',
height: 30,
width: 130,
lineHeight: 30,
backgroundColor: '#ecf2f6',
borderRadius: [5, 5, 0, 0]
},
second: {
color: '#0e8dcb',
padding: [5, 10, 5, 10],
lineHeight: 26,
align: 'left'
}
}
},
lineStyle: {
color: '#D5D5D5',
width: 1,
curveness: 1
},
data: [data]
}
]
};
let tree = JSON.parse(JSON.stringify(option));
tree.series[0].zlevel = 1;
tree.series[0].expandAndCollapse = false;
this.chart.setOption(option);
},
动态设置生成图片的宽高:
adjustChart (chart, el) {
let allNodes = chart._chartsViews[0]._data._graphicEls;
let nodeNums = allNodes.filter(node => node !== undefined).length;
const height = window.innerHeight * 0.3;
const width = window.innerWidth * 0.5;
const currentHeight = 200 * this.initialTreeDepth;
const currentWidth = 150 * nodeNums;
const newHeight = Math.max(currentHeight, height);
const newWidth = Math.max(currentWidth, width);
this.$nextTick(() => {
this.height = newHeight + 'px';
this.width = newWidth + 'px';
chart.resize({ width: newWidth, height: newHeight });
});
},
注:在设置完宽高之后,必须调用 resize 方法,刷新组件宽高,否则会出现 height已重新赋值,但是图片的高度用的是上一次计算的高度
下载图片:
downLoad (type, fileName) {
let picInfo = this.chart.getDataURL({
type,
pixelRatio: 1.5, // 放大两倍下载,之后压缩到同等大小展示。解决生成图片在移动端模糊问题
backgroundColor: '#fff'
});// 获取到的是一串base64信息
const elink = document.createElement('a');
elink.download = fileName + '.' + type;
elink.style.display = 'none';
elink.href = picInfo;
document.body.appendChild(elink);
elink.click();
document.body.removeChild(elink);
},
切换图片默认展开层级,但此方法可能出现节点隐藏,边未隐藏的情况,尚未找到解决方法:
/**
* 层级
* @param depth
*/
settingOption (depth) {
let info = this.chart.getOption();
info.series[0].data = this.formatOptions(info.series[0].data, depth);
this.chart.setOption(info);
this.adjustChart(this.chart, 'showEChart');
},
formatOptions (info, depth) {
return info.map(el => {
this.$set(el, 'collapsed', el.level > depth);
if (el.level <= depth && el.children && el.children.length > 0) {
this.formatOptions(el.children, depth);
}
return el;
});
}
也可以通过引用组件时,传入的默认展开层级实现
因为图片下载要求下载完整的组织结构图,所以在此又初始化了一个数,其方法大致与以上相似,只是展示完整的组织结构图用于下载。
完整组件代码如下:
<template>
<div>
<div ref="showEChart" :class="className" :style="{height:height,width:width}" :key="treeKey"/>
<div ref="photoTree" :class="className" style="position: fixed;top: -99999px" :style="{height:treeHeight,width:width}"/>
</div>
</template>
<script>
import debounce from 'lodash.debounce';
import * as echarts from 'echarts/core';
import { TooltipComponent } from 'echarts/components';
import { TreeChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
require('echarts/lib/component/toolbox');
echarts.use([TooltipComponent, TreeChart, CanvasRenderer]);
export default {
props: {
// 初始展开层级,默认为10
initialTreeDepth: {
type: Number,
default: 10
},
className: {
type: String,
default: 'chart'
},
// 默认展示宽度
width: {
type: String,
default: '100%'
},
// 默认展示高度
height: {
type: String,
default: '450px'
},
// 数据
chartData: {
type: Object,
required: true
}
},
data () {
return {
chart: null,
treeHeight: '450px',
photoTree: null,
treeKey: 1
};
},
watch: {
chartData: {
deep: true,
handler (val) {
this.setOptions(val);
}
}
},
mounted () {
this.initChart();
// 是否需要自适应-加了防抖函数
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize();
}
if (this.photoTree) {
this.photoTree.resize();
}
}, 100);
window.addEventListener('resize', this.__resizeHandler);
// 监听侧边栏的变化以实现自适应缩放
const sidebarElm = document.getElementsByClassName('sidebar-container')[0];
sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler);
},
beforeDestroy () {
if (!this.chart && !this.photoTree) {
return;
}
window.removeEventListener('resize', this.__resizeHandler);
this.chart.dispose();
this.photoTree.dispose();
this.chart = null;
this.photoTree = null;
const sidebarElm = document.getElementsByClassName('sidebar-container')[0];
sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler);
},
methods: {
initChart () {
let showEChart = this.$refs.showEChart;
let photoTree = this.$refs.photoTree;
this.chart = echarts.init(showEChart, 'macarons');
this.photoTree = echarts.init(photoTree, 'macarons');
this.setOptions(this.chartData);
this.adjustChart(this.chart, 'showEChart');
this.adjustChart(this.photoTree, 'photoTree');
this.chart.on('click', () => {
this.adjustChart(this.chart, 'showEChart');
}); // 节点点击事件
},
/**
* 节点调整
*/
adjustChart (chart, el) {
let allNodes = this.photoTree._chartsViews[0]._data._graphicEls;
let nodeNums = allNodes.filter(node => node !== undefined).length;
const height = window.innerHeight * 0.3;
const width = window.innerWidth * 0.5;
const currentHeight = 200 * this.initialTreeDepth;
const currentWidth = 150 * nodeNums;
const newHeight = Math.max(currentHeight, height);
const newWidth = Math.max(currentWidth, width);
this.$nextTick(() => {
if (el === 'photoTree') {
this.treeHeight = newHeight + 'px'; // 设置高度自适应
} else {
this.height = newHeight + 'px';
}
this.width = newWidth + 'px';
chart.resize({ width: newWidth, height: newHeight });
});
return true;
},
setOptions (data) {
let option = {
// 提供数据视图、还原、下载的工具
toolbox: {
show: true
// feature: {
// mark: { show: true },
// dataView: { show: true, readOnly: false },
// restore: { show: true },
// saveAsImage: { show: true }
// }
},
series: [
{
name: '组织结构图',
type: 'tree',
orient: 'TB', // 竖向或水平 TB代表竖向 LR代表水平
layout: 'orthogonal',
edgeShape: 'polyline',
edgeForkPosition: '63%',
top: '50px',
bottom: '50px',
zlevel: 2,
initialTreeDepth: this.initialTreeDepth, // 树图初始展开的层级(深度)
// expandAndCollapse: true, // 点击节点时不收起子节点,default: true
symbolSize: [100, 40], // 图片大小
itemStyle: {
color: 'transparent',
padding: [10, 0, 10, 0],
borderWidth: 0
},
label: {
borderRadius: [5, 5, 5, 5],
borderColor: '#eeeeee',
backgroundColor: '#ffffff',
shadowColor: 'rgb(0 0 0 / 15%)',
shadowBlur: 1,
rich: {
first: {
align: 'center',
height: 30,
width: 130,
lineHeight: 30,
backgroundColor: '#ecf2f6',
borderRadius: [5, 5, 0, 0]
},
second: {
color: '#0e8dcb',
padding: [5, 10, 5, 10],
lineHeight: 26,
align: 'left'
}
}
},
lineStyle: {
color: '#D5D5D5',
width: 1,
curveness: 1
},
data: [data]
}
]
};
let tree = JSON.parse(JSON.stringify(option));
tree.series[0].zlevel = 1;
tree.series[0].expandAndCollapse = false;
this.photoTree.setOption(tree);
this.chart.setOption(option);
},
sidebarResizeHandler (e) {
if (e.propertyName === 'width') {
this.__resizeHandler();
}
},
/**
* 下载
* @param type
* @param fileName
*/
downLoad (type, fileName) {
let picInfo = this.photoTree.getDataURL({
type,
pixelRatio: 1.5, // 放大两倍下载,之后压缩到同等大小展示。解决生成图片在移动端模糊问题
backgroundColor: '#fff'
});// 获取到的是一串base64信息
const elink = document.createElement('a');
elink.download = fileName + '.' + type;
elink.style.display = 'none';
elink.href = picInfo;
document.body.appendChild(elink);
elink.click();
document.body.removeChild(elink);
},
/**
* 层级
* @param depth
*/
settingOption (depth) {
let info = this.chart.getOption();
info.series[0].data = this.formatOptions(info.series[0].data, depth);
this.chart.setOption(info);
this.adjustChart(this.chart, 'showEChart');
},
formatOptions (info, depth) {
return info.map(el => {
this.$set(el, 'collapsed', el.level > depth);
if (el.level <= depth && el.children && el.children.length > 0) {
this.formatOptions(el.children, depth);
}
return el;
});
}
}
};
</script>
引用组件:
<Tree :chartData="treeData" ref="tree" :initialTreeDepth="initialTreeDepth" :key="initialTreeDepth"></Tree>
import Tree from './chartTree';
export default {
data () {
return {
initialTreeDepth: 3,
treeData: {}
};
},
methods: {
/**
* 下载
*/
downLoad () {
this.$refs.tree.downLoad(this.search.type, '组织结构图');
},
/**
* 展示
*/
settingOptions (info) {
this.initialTreeDepth = info;
}
},
created () {
this.treeData = {
level: 1,
expandAndCollapse: false,
label: {
formatter: [
'{first|全部}'
].join('\n'),
rich: {
first: {
align: 'center',
borderRadius: [5, 5, 5, 5]
}
}
},
children: [{
'level': 2,
'label': { 'formatter': '{first|zz01}\n{second|001\n用户:11}' },
'expandAndCollapse': false,
'children': [{
'level': 3,
'label': { 'formatter': '{first|组织02}\n{second|002\n用户:11}' },
'expandAndCollapse': true,
'children': [{
'level': 4,
'label': { 'formatter': '{first|组织03}\n{second|003\n用户:8}' },
'expandAndCollapse': true
}]
}]
}, {
'level': 2,
'label': { 'formatter': '{first|银河帝国宇宙军}\n{second|0999999999999999\n用户:10}' },
'expandAndCollapse': false,
'children': [{
'level': 3,
'label': { 'formatter': '{first|阿尔法军团}\n{second|0000001\n用户:10}' },
'expandAndCollapse': true,
'children': [{
'level': 4,
'label': { 'formatter': '{first|泰坦奥米伽}\n{second|0000045\n用户:10}' },
'expandAndCollapse': true,
'children': [{
'level': 5,
'label': { 'formatter': '{first|欧西里斯无畏舰}\n{second|121212312\n用户:10}' },
'expandAndCollapse': true,
'children': [{
'level': 6,
'label': { 'formatter': '{first|天穹战列舰}\n{second|0787878\n用户:9}' },
'expandAndCollapse': true,
'children': [{
'level': 7,
'label': { 'formatter': '{first|狂风导弹巡洋舰}\n{second|123123123\n用户:2}' },
'expandAndCollapse': true
}, {
'level': 7,
'label': { 'formatter': '{first|凛冬战略防御部门}\n{second|00099887\n用户:4}' },
'expandAndCollapse': true
}]
}]
}]
}]
}]
}]
};
}
};