需求:
1:动态生成流程图
2:根据待办、已办(或者其他类似需求)区分不同的背景颜色
3:鼠标划过展示相应信息
4:生成的流程图具有层级关系,因为每一步有可能有多个接收方,比如流程的第一步在一个X轴上,第二步在相同的X轴上,以此类推,这样看起来比较有层次感,也不会乱
实现
前提是你会用echarts其他的图比如简单柱状图、饼状图等,知道其中的api,如果是初次接触,移步官方地址https://echarts.apache.org/zh/index.html。
本例使用的是echarts关系图,x、y轴的方式进行布局
1:动态生成流程图
这个好办,其实就是series里的data属性而已,自己看echarts关系图
2:根据待办、已办区分不同的背景颜色
官方文档是默认的全局颜色,也就是series里的symbol属性
在这里,我们可以把它封装data里,去官网做个简单实现,所以在data里可以个性化不同的背景图
3:鼠标划过展示相应信息
简单的文字可以直接用formatter函数用data里的字段进行展示,比如
tooltip: {
formatter: function(x) {
return x.data.toolText;
}
}
但是涉及到多些文字拼接的,就不好用了,从上面可以看出toolText依旧是data里的属性,所以思路就明确了,把多些文字封装到data里,比如toolText,在传到echart里就把字段组装好
4:生成的流程图具有层级关系
后台数据实现,考虑到以上分析,数据库中设置ordernum字段,开始步骤为1,每进行下一步就把ordernum加1,根据ordernum和数据量的大小来动态的判断x、y轴的坐标,其他数据和关系图api类似即可
相应的前端代码:
父页面逻辑
viewProcess为前台点击事件,根据流程的主ID获取完整流程图,横向单次流程目前设置长度为15步,可以自己根据需求来个性化,目前未找到好的办法,只有写死了15个order
<template>
<div>
<!-- 其他隐藏,点击事件根据需求自己添加 -->
<!-- 流程图公共弹窗 -->
<relationBar v-bind="relationBarOptions" @close="clickCloseRelationBar"></relationBar>
</div>
</template>
<script>
import relationBar from "./relationBar.vue";
export default {
name: "xxx",
props: {
title: {
type: String,
default: ""
},
show: {
type: Boolean,
default: false
},
},
data() {
return {
//======流程图属性 ======START
relationBarOptions: {
data: [],
showFlag: false
},
order2: 1,
order3: 1,
order4: 1,
order5: 1,
order6: 1,
order7: 1,
order8: 1,
order9: 1,
order10: 1,
order11: 1,
order12: 1,
order13: 1,
order14: 1,
order15: 1,
dataInit: []
//======流程图属性 ======END
};
},
components: {
relationBar
},
watch: {
},
mounted() {
},
methods: {
//查看流程图
viewProcess() {
this.order1 = 1;
this.order2 = 1;
this.order3 = 1;
this.order4 = 1;
this.order5 = 1;
this.order6 = 1;
this.order7 = 1;
this.order8 = 1;
this.order9 = 1;
this.order10 = 1;
this.order11 = 1;
this.order12 = 1;
this.order13 = 1;
this.order14 = 1;
this.order15 = 1;
//echarts数据
let echartsData = [];
//echarts关系数据
let echartslinks = [];
let dataAfterComBine = [];
//根据insid获取数据
let params = {};
params.insId = this.editForm.id;
params.mark = "2";
this.$api
.getProcessListByInsId({ params })
.then(res => {
if (res.code == 0) {
let dataInitTemp = res.result;
//解决名字重复问题,自己看官网API就知道原因了
dataInitTemp.forEach(data => {
let theSameNUm = 0;
let deptname = data.deptname;
res.result.forEach(data2 => {
if (data2.deptname == deptname) {
theSameNUm++;
}
if (theSameNUm == 2) {
data2.deptname = data2.deptname + " ";
}
if (theSameNUm == 3) {
data2.deptname = data2.deptname + " ";
}
});
});
this.dataInit = res.result;
}
})
.then(res => {
//################################计算位移 START###########################################
//获取最大步长
let maxOrderNum = 0;
this.dataInit.forEach(d => {
if (Number(d.ordernum) > maxOrderNum) {
maxOrderNum = Number(d.ordernum);
}
});
//初始值 x步长:1*200 y:1*100
let addStepX = 1;
let addStepY = 1;
//判断 最大ordernum 根据ordernum和数据量进行X,Y判断,为了方便 使用*3计算
for (let i = 3; i < 10; i++) {
if (maxOrderNum >= i) {
if (this.dataInit.length > i * 3) {
addStepX = maxOrderNum - 1;
addStepY = maxOrderNum - 1;
}
}
}
//添加数据
this.dataInit.forEach(d => {
let ordernumOfx = Number(d.ordernum) * 300 * Number(addStepX);
let ordernumOfy = Number(d.ordernum) * 100 * Number(addStepY);
//公共属性,开始节点特殊化
let commnDataInit = {
toolText:
d.deptname +
"</br>" +
"新增人员: " +
d.reciveuser +
"</br>" +
"新增时间: " +
d.recivedate +
"</br>" +
"办理人员: " +
d.opeuser +
"</br>" +
"办理时间: " +
d.opedate,
user: d.opeuser,
date: d.opedate,
name: d.deptname,
x: ordernumOfx,
text: d.currentcontent,
//背景图可以用网上的,这里是用nginx代理的服务器图片,根据自己需求来
symbol:
"image://http://XXXX.XXXX.XXXX:8096/XXX/processImg/redDone.jpg"
};
//公共属性,流程节点属性
let commnData = {
toolText:
d.deptname +
"</br>" +
"签收人员: " +
d.reciveuser +
"</br>" +
"签收时间: " +
d.recivedate +
"</br>" +
"办理人员: " +
d.opeuser +
"</br>" +
"办理时间: " +
d.opedate +
"</br>" +
"意 见: " +
d.currentcontent +
"</br>",
user: d.opeuser,
date: d.opedate,
name: d.deptname,
x: ordernumOfx,
text: d.currentcontent
};
if (d.ordernum == "1") {
echartsData.push({
...commnDataInit,
y: 0
});
} else {
this.setEchartsData(
echartsData,
d,
ordernumOfx,
ordernumOfy,
commnData
);
}
let databeforeComBine = {
id: d.id,
name: d.deptname,
childrens: []
};
dataAfterComBine.push(databeforeComBine);
this.comBineLinks(d.id, databeforeComBine, this.dataInit);
});
dataAfterComBine.forEach(e => {
e.childrens.forEach(element => {
echartslinks.push({
source: e.name,
target: element
});
});
});
this.relationBarOptions.data = echartsData;
this.relationBarOptions.links = echartslinks;
this.relationBarOptions.showFlag = true;
});
},
setEchartsData(echartsData, d, ordernumOfx, ordernumOfy, commnData) {
if (d.status == "0") {
commnData.symbol =
"image://http://XXXX.XXXX.XXXX:8096/XXX/processImg/processImg/grennTodo.jpg";
} else {
commnData.symbol =
"image://http://XXXX.XXXX.XXXX:8096/XXX/processImg/processImg/redDone.jpg";
}
if (d.ordernum == "2") {
echartsData.push({
...commnData,
y: this.order2 == 1 ? 0 : this.order2 * ordernumOfy
});
this.order2++;
}
if (d.ordernum == "3") {
echartsData.push({
...commnData,
y: this.order3 == 1 ? 0 : this.order3 * ordernumOfy
});
this.order3++;
}
if (d.ordernum == "4") {
echartsData.push({
...commnData,
y: this.order4 == 1 ? 0 : this.order4 * ordernumOfy
});
this.order4++;
}
if (d.ordernum == "5") {
echartsData.push({
...commnData,
y: this.order5 == 1 ? 0 : this.order5 * ordernumOfy
});
this.order5++;
}
if (d.ordernum == "6") {
echartsData.push({
...commnData,
y: this.order6 == 1 ? 0 : this.order6 * ordernumOfy
});
this.order6++;
}
if (d.ordernum == "7") {
echartsData.push({
...commnData,
y: this.order7 == 1 ? 0 : this.order7 * ordernumOfy
});
this.order7++;
}
if (d.ordernum == "8") {
echartsData.push({
...commnData,
y: this.order8 == 1 ? 0 : this.order8 * ordernumOfy
});
this.order8++;
}
if (d.ordernum == "9") {
echartsData.push({
...commnData,
y: this.order9 == 1 ? 0 : this.order9 * ordernumOfy
});
this.order9++;
}
if (d.ordernum == "10") {
echartsData.push({
...commnData,
y: this.order10 == 1 ? 0 : this.order10 * ordernumOfy
});
this.order10++;
}
if (d.ordernum == "11") {
echartsData.push({
...commnData,
y: this.order11 == 1 ? 0 : this.order11 * ordernumOfy
});
this.order11++;
}
if (d.ordernum == "12") {
echartsData.push({
...commnData,
y: this.order12 == 1 ? 0 : this.order12 * ordernumOfy
});
this.order12++;
}
if (d.ordernum == "13") {
echartsData.push({
...commnData,
y: this.order13 == 1 ? 0 : this.order13 * ordernumOfy
});
this.order13++;
}
if (d.ordernum == "14") {
echartsData.push({
...commnData,
y: this.order14 == 1 ? 0 : this.order14 * ordernumOfy
});
this.order14++;
}
if (d.ordernum == "15") {
echartsData.push({
...commnData,
y: this.order15 == 1 ? 0 : this.order15 * ordernumOfy
});
this.order15++;
}
},
comBineLinks(id, databeforeComBine, data) {
data.forEach(d => {
if (d.parentId == id) {
databeforeComBine.childrens.push(d.deptname);
}
});
},
processTypeFlt(row) {
//0下发1请示2反馈3退回4结束
switch (row.processType) {
case "0":
if (row.ordernum == "1") {
return "下发";
} else {
return "流转";
}
break;
case "1":
return "请示";
break;
case "2":
return "回报";
break;
break;
case "4":
return "结束";
break;
default:
if (row.iscopy == "0") {
if (row.ordernum == "1") {
return "新增";
} else {
return "签收";
}
} else {
return "接收";
}
}
}
//################################计算位移 END###########################################
},
filters: {
}
};
</script>
<style lang="scss">
</style>
子页面(echarts组件):
<template>
<el-dialog :visible="show" @close="clickClose()" append-to-body width="60%">
<template slot="title">
<span style="width: calc(100% - 60px);display: inline-block;" class="single-line">
<slot name="title">{{dialogtitle}}</slot>
</span>
</template>
<div class="relationBar">
<div class="relationBarDiv" ref="relationEcharts"></div>
</div>
<span slot="footer" class="dialog-footer">
<el-button>关 闭</el-button>
</span>
</el-dialog>
</template>
<script>
var option = {
title: {
text: "",
x: "center",
textStyle: {
fontWeight: "normal" //标题颜色
}
},
//个性化划过事件
tooltip: {},
series: [
{
// symbol:"image://data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7",
// symbol: "rect", //circle圆形 rect矩形
type: "graph", //关系图
layout: "none", // 图的布局。[ default: 'none' ] 'none' 不采用任何布局,使用节点中提供的 x, y 作为节点的位置。 'circular' 采用环形布局;'force' 采用力引导布局.
symbolSize: 50,
roam: true,
label: {
show: true
},
edgeSymbol: ["circle", "arrow"],
edgeSymbolSize: [4, 10],
edgeLabel: {
fontSize: 20
},
//color:'#A6FFA6',
data: [],
links: [],
lineStyle: {
opacity: 0.9,
width: 2,
curveness: 0,
//color: '#000', // 线的颜色[ default: '#aaa' ]
// width: 1, // 线宽[ default: 1 ]
// type: 'solid', // 线的类型[ default: solid ] 'dashed' 'dotted'
// opacity: 0.5, // 图形透明度。支持从 0 到 1 的数字,为 0 时不绘制该图形。[ default: 0.5 ]
// curveness: 0 // 边的曲度,支持从 0 到 1 的值,值越大曲度越大。[ default: 0 ]
}
}
]
};
export default {
name: "relationBar",
props: {
data: {
type: Array,
default: () => {
[];
}
},
links: {
type: Array,
default: () => {
[];
}
},
title: {
type: String,
default: ""
},
showFlag: {
type: Boolean,
default: false
}
},
data() {
return {
dialogtitle: "流程关系图",
show: false
};
},
created() {},
watch: {
showFlag: {
handler(n, o) {
this.show = n;
if (n) {
this.initEcharts();
}
},
immediate: true
}
},
mounted() {
//this.initEcharts();
},
methods: {
clickClose() {
this.$emit("close");
},
setOption(data) {
let op = JSON.parse(JSON.stringify(option));
op.series[0].data = this.data;
op.series[0].links = this.links;
op.tooltip = {
formatter: function(x) {
return x.data.toolText;
}
};
return op;
},
initEcharts() {
this.$nextTick(() => {
let ctx = this;
let myChart = ctx.$echarts.init(ctx.$refs.relationEcharts);
myChart.setOption(ctx.setOption(ctx.data));
ctx.$refs.relationEcharts.style.width = "100%";
});
}
}
};
</script>
<style lang="scss">
.relationBar {
width: 100%;
height: 700px;
border: #e3e3e3 1px solid;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
overflow: scroll;
.relationBarDiv {
width: 100%;
height: 100%;
min-height: 700px;
min-width: 250px;
}
}
</style>
最终效果,目前是第3步,步数增加,页面会相应变化:红色图片代表已办、绿色图片代表待办,流程弹窗具有办理的意见