该流程图默认有且只有三段,分别是:发起申请-》审核-》结果
若存在流程且每段必定是有数据的,由于是用原生js绘制的流程图,在引入项目中之后可以尝试根据数据的多少动态调节canvas画布的大小。
一:简单的设置一下css样式
<style type="text/css">
html body{
height:100%;
width:100%;
}
.approvalProcess{
margin:0 auto;
width:1000px;
height:500px;
border:1px solid black;
}
.top{
width:100%;
height:40px;
display: flex;
}
.top >div{
width:100px;
height:100%;
}
.state1 >span{
display: inline-block;
width:20px;
height:8px;
background:rgba(37, 137, 255, 1);
}
.state2 >span{
display: inline-block;
width:20px;
height:8px;
background:rgba(53, 195, 185, 1);
}
.state3 >span{
display: inline-block;
width:20px;
height:8px;
background:rgba(245, 172, 86, 1);
}
.state4 >span{
display: inline-block;
width:20px;
height:8px;
background:rgba(212, 48, 48, 1);
}
.bottom{
width:100%;
height:calc( 100% - 40px);
}
canvas{
border:1px solid black;
}
</style>
二:简单的添加一下dom元素
<div class="approvalProcess">
<div class="top">
<div class="state1">
<span></span>
发起人
</div>
<div class="state2">
<span></span>
通过
</div>
<div class="state3">
<span></span>
待审批
</div>
<div class="state4">
<span></span>
驳回
</div>
</div>
<div class="bottom">
<canvas id="processImage" width="1000" height="460"></canvas>
</div>
</div>
三:简单的数据结构及自行封装的功能函数
1,声明的变量
let canvas = document.getElementById('processImage')
let ctx = canvas.getContext('2d');
let flowData = [
[{
userName:'刘备',
state:"发起申请",
color:"rgba(37, 137, 255, 1)",
}],
[
{
userName:'张飞',
state:"同意",
color:'rgba(53, 195, 185, 1)'
},
{
userName:'赵云',
state:'待审批',
color:"rgba(245, 172, 86, 1)"
},
{
userName:'曹操',
state:'驳回',
color:"rgba(212, 48, 48, 1)"
},
],
[
{
state:"通过",
color:'rgba(53, 195, 185, .5)'
}
]
]
let drawStartX = 50; //第一层元素起始中心X坐标
let drawStartY = 200 //第一层元素起始中心Y坐标
let arrowWidth = 5; //绘制箭头的宽度
let horizontalLineSpacing = 50 //分组横线间距
let verticalLineSpacing = 110 //分组纵线间距
let borderWidth = 120 //元素边框宽度
let borderHeight = 90 //元素边框高度
let lineColor = "rgba(37, 137, 255, 1)";
2,封装的功能方法
1,绘制流程节点
function drawProcessNode(startX,startY,stateColor,userName,state){
//绘制圆角边框
ctx.strokeStyle = 'rgba(222, 225, 231, 1)';
ctx.beginPath();
ctx.arc(startX+10,startY-35,10,-Math.PI/2,Math.PI,true);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(startX+10,startY-45);
ctx.lineTo(startX+110,startY-45);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(startX+110,startY-35,10,-Math.PI/2,0,false);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(startX+borderWidth,startY-35)
ctx.lineTo(startX+borderWidth,startY+35)
ctx.stroke()
ctx.closePath()
ctx.beginPath()
ctx.arc(startX+110,startY+35,10,Math.PI/2,0,true);
ctx.stroke()
ctx.closePath()
ctx.beginPath();
ctx.moveTo(startX+110,startY+45);
ctx.lineTo(startX+10,startY+45);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(startX+10,startY+35,10,Math.PI,Math.PI/2,true);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(startX,startY+35);
ctx.lineTo(startX,startY-35);
ctx.stroke();
ctx.closePath();
//绘制填充圆形 头像背景
ctx.beginPath();
ctx.arc(startX+20,startY-20,12,0,Math.PI*2,true)
ctx.fillStyle = stateColor;
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(startX+20,startY-24,4,0,Math.PI*2,true)
ctx.fillStyle = 'white';
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(startX+20,startY-8,10,0,Math.PI/2,true)
ctx.fillStyle = 'white';
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 3;
ctx.arc(startX+20,startY-20,12,0,Math.PI*2,true)
ctx.strokeStyle = stateColor;
ctx.stroke();
ctx.closePath();
//填充文字
ctx.fillStyle="rgba(66, 76, 87, 1)";
ctx.font="14px serif";
ctx.fillText(userName,startX+40,startY-18);
ctx.fillStyle="rgba(66, 76, 87, 1)";
ctx.font="14px serif";
ctx.fillText(state,startX+40,startY+20);
}
2,绘制纵线
//绘制纵线
function drawVerticalLine(startX,startY,endX,endY,color){
ctx.beginPath();
ctx.strokeStyle=color
ctx.moveTo(startX,startY);
ctx.lineTo(endX,endY);
ctx.stroke();
}
3,绘制横线
//绘制横线
function drawHorizontalLine(startX,startY,endX,endY,color){
ctx.strokeStyle=color;
ctx.beginPath();
ctx.moveTo(startX,startY);
ctx.lineTo(endX,endY);
ctx.stroke();
}
4,绘制右箭头
//绘制右箭头
function drawRightArrow(startX,startY,color){
ctx.beginPath();
ctx.moveTo(startX+5,startY);
ctx.lineTo(startX,startY+5);
ctx.lineTo(startX,startY-5);
ctx.fillStyle = color;
ctx.fill();
}
5,绘制圆点
//绘制圆点
function drawDot(centerX,centerY,rediuce,color){
ctx.beginPath();
ctx.lineWidth = 1;
ctx.fillStyle = color;
ctx.arc(centerX,centerY,rediuce,0,Math.PI*2,true)
ctx.fill()
}
6,绘制流程结束节点
//绘制流程结束节点
function drawNullNode(startX,startY,state,color){
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(startX+10,startY-15,10,-Math.PI/2,Math.PI,true);
ctx.lineTo(startX+10,startY-25);
ctx.lineTo(startX+40,startY-25);
ctx.arc(startX+40,startY-15,10,-Math.PI/2,0,false);
ctx.lineTo(startX+50,startY-15)
ctx.lineTo(startX+50,startY+15)
ctx.arc(startX+40,startY+15,10,Math.PI/2,0,true);
ctx.lineTo(startX+40,startY+25);
ctx.lineTo(startX+10,startY+25);
ctx.arc(startX+10,startY+15,10,Math.PI,Math.PI/2,true);
ctx.lineTo(startX,startY+15);
ctx.lineTo(startX,startY-15);
ctx.fill();
ctx.closePath();
ctx.fillStyle = "rgba(66, 76, 87, 1)"
ctx.font="12px"
ctx.fillText(state,startX+10,startY+5)
}
三:主体代码逻辑
//绘制主体逻辑
//流程节点默认只有三段
function draw(){
//绘制发起节点
drawProcessNode(drawStartX,drawStartY,flowData[0][0].color,flowData[0][0].userName,flowData[0][0].state)
//绘制圆点
drawDot(drawStartX+borderWidth,drawStartY,4,lineColor)
//绘制连接发起节点的横线
let appliStartX = drawStartX+borderWidth;
let appliEndX = appliStartX+horizontalLineSpacing;
drawHorizontalLine(appliStartX,drawStartY,appliEndX,drawStartY,lineColor)
let endLineStartX = appliEndX+arrowWidth+horizontalLineSpacing*2+borderWidth;
//绘制第二流程的节点
if(flowData[1].length===1){
//绘制前半线
let centerStartX = appliEndX+horizontalLineSpacing;
drawHorizontalLine(appliEndX,drawStartY,centerStartX,drawStartY,lineColor)
//绘制右箭头
drawRightArrow(centerStartX,drawStartY,lineColor)
//绘制元素
drawProcessNode(centerStartX+arrowWidth,drawStartY,flowData[1][0].color,flowData[1][0].userName,flowData[1][0].state)
//绘制圆点
let centerNodeStartX = centerStartX+arrowWidth+borderWidth;
drawDot(centerNodeStartX,drawStartY,4,lineColor)
//绘制后半线
drawHorizontalLine(centerNodeStartX,drawStartY,centerNodeStartX+horizontalLineSpacing,drawStartY,lineColor)
}else{
drawDot(appliEndX,drawStartY,4,lineColor)
//绘制左侧纵线
//获取左侧纵线起始终止XY坐标
let leftStartY = drawStartY - verticalLineSpacing*(flowData[1].length-1)/2;
let leftEndY = drawStartY + verticalLineSpacing*(flowData[1].length-1)/2;
drawVerticalLine(appliEndX,leftStartY,appliEndX,leftEndY,lineColor);
//绘制右侧纵线
//获取右侧纵线起始终止XY坐标
let rightStartX = appliEndX+arrowWidth+horizontalLineSpacing*2+borderWidth;//120是节点元素边框的宽度
drawVerticalLine(rightStartX,leftStartY,rightStartX,leftEndY,lineColor);
for(let i = 0; i<flowData[1].length;i++){
//绘制前半线
//获取每个审批节点前半线的起始Y坐标
let centerFromtStartY = leftStartY+i*verticalLineSpacing;
//获取每个审批节点前半线的终止X坐标
let centerFrontEndX = appliEndX+horizontalLineSpacing;
drawHorizontalLine(appliEndX,centerFromtStartY,centerFrontEndX,centerFromtStartY,lineColor)
//绘制右箭头
drawRightArrow(centerFrontEndX,centerFromtStartY,lineColor);
//绘制节点元素
drawProcessNode(centerFrontEndX+arrowWidth,centerFromtStartY, flowData[1][i].color,flowData[1][i].userName,flowData[1][i].state)
//绘制圆点
let centerDotX = centerFrontEndX+arrowWidth+borderWidth;
drawDot(centerDotX,centerFromtStartY,4,lineColor)
//绘制右半线
//获取右半线终点X坐标
let centerBehindEndX = centerDotX+horizontalLineSpacing;
drawHorizontalLine(centerDotX,centerFromtStartY,centerBehindEndX,centerFromtStartY,lineColor)
}
drawDot(endLineStartX,drawStartY,4,lineColor)
}
//绘制结束节点,以及链接结束节点的半线箭头
let endLineEndX = endLineStartX+horizontalLineSpacing;
drawHorizontalLine(endLineStartX,drawStartY,endLineEndX,drawStartY,lineColor)
drawRightArrow(endLineEndX,drawStartY,lineColor);
drawNullNode(endLineEndX+arrowWidth,drawStartY,flowData[2][0].state,flowData[2][0].color)
}
draw();
四:效果图