|
@@ -0,0 +1,526 @@
|
|
|
+using Microsoft.AspNetCore.Components;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Dynamic;
|
|
|
+using System.Linq;
|
|
|
+using System.Threading.Tasks;
|
|
|
+
|
|
|
+namespace wispro.sp.web.Components
|
|
|
+{
|
|
|
+ public partial class FlowChart
|
|
|
+ {
|
|
|
+ double TitleHeight = 100;
|
|
|
+ double ChartWidth = 1400;
|
|
|
+ double ChartHeight = 1000;
|
|
|
+ double rectWidth = 150;
|
|
|
+ double rectHeight = 60;
|
|
|
+ double initRadius = 25;
|
|
|
+ double EndRadius = 25;
|
|
|
+ double hSeparation = 40;
|
|
|
+
|
|
|
+ int rectFontSize = 18;
|
|
|
+
|
|
|
+
|
|
|
+ private List<entity.workflowDefine.Step> Steps { get; set; }
|
|
|
+
|
|
|
+ private List<entity.workflowDefine.TrasferCondition> Transfers { get; set; }
|
|
|
+
|
|
|
+ private List<entity.workflowDefine.Action> Actions { get; set; }
|
|
|
+
|
|
|
+ [Parameter]
|
|
|
+ public entity.workflowDefine.Workflow workflow { get; set; }
|
|
|
+
|
|
|
+ internal class shapeNode
|
|
|
+ {
|
|
|
+ public int InCount { get; set; }
|
|
|
+
|
|
|
+ public int OutCount { get; set; }
|
|
|
+ public double x { get; set; }
|
|
|
+
|
|
|
+ public double y { get; set; }
|
|
|
+
|
|
|
+ public double width { get; set; }
|
|
|
+
|
|
|
+ public double height { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 形状类型
|
|
|
+ /// 0:圆
|
|
|
+ /// 1:矩形
|
|
|
+ /// 3: 虚拟
|
|
|
+ /// </summary>
|
|
|
+ public int Type { get; set; }
|
|
|
+
|
|
|
+ public int Level { get; set; } = 1;
|
|
|
+
|
|
|
+ public dynamic NodeObject { get; set; }
|
|
|
+
|
|
|
+ public List<shapeNode> Childrens { get; set; }
|
|
|
+
|
|
|
+ public List<shapeNode> Parents { get; set; }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<shapeNode> shapeTrees = null;
|
|
|
+
|
|
|
+ private shapeNode FindNode(int stepId, out int Level, List<shapeNode> lstNodes)
|
|
|
+ {
|
|
|
+ //Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(lstNodes));
|
|
|
+ Console.WriteLine(stepId.ToString());
|
|
|
+ Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(Steps));
|
|
|
+ Level = 1;
|
|
|
+ foreach (var sNode in lstNodes)
|
|
|
+ {
|
|
|
+ if (sNode.NodeObject is entity.workflowDefine.Step && sNode.NodeObject.Id == stepId)
|
|
|
+ {
|
|
|
+ return sNode;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (sNode.Childrens != null)
|
|
|
+ {
|
|
|
+ Level += 1;
|
|
|
+
|
|
|
+ var retObj = FindNode(stepId, out Level, sNode.Childrens);
|
|
|
+
|
|
|
+ if (retObj != null)
|
|
|
+ {
|
|
|
+ return retObj;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void GetShapeLevelNodes(Dictionary<int, List<shapeNode>> levelNodes, List<shapeNode> TreeNodes)
|
|
|
+ {
|
|
|
+ if (TreeNodes != null)
|
|
|
+ {
|
|
|
+ foreach (var sNode in TreeNodes)
|
|
|
+ {
|
|
|
+ List<shapeNode> nodes = new List<shapeNode>();
|
|
|
+ if (levelNodes.ContainsKey(sNode.Level))
|
|
|
+ {
|
|
|
+ nodes = levelNodes[sNode.Level];
|
|
|
+ nodes.Add(sNode);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ nodes.Add(sNode);
|
|
|
+ levelNodes.Add(sNode.Level, nodes);
|
|
|
+ }
|
|
|
+
|
|
|
+ GetShapeLevelNodes(levelNodes, sNode.Childrens);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ shapeNode startNode;
|
|
|
+ shapeNode InitShape;
|
|
|
+ shapeNode endNode;
|
|
|
+ Dictionary<int, List<shapeNode>> LevelNodes = new Dictionary<int, List<shapeNode>>();
|
|
|
+
|
|
|
+ private void initShapeTree()
|
|
|
+ {
|
|
|
+ #region Demo流程数据
|
|
|
+ workflow = new entity.workflowDefine.Workflow();
|
|
|
+ workflow.Name = "请假处理流程";
|
|
|
+ workflow.Id = 1;
|
|
|
+
|
|
|
+ workflow.InitAction = new entity.workflowDefine.Action();
|
|
|
+ workflow.InitAction.Name = "填写请教条";
|
|
|
+
|
|
|
+ Steps = new List<entity.workflowDefine.Step>();
|
|
|
+ var step1 = new entity.workflowDefine.Step();
|
|
|
+ step1.Name = "部门主管申核";
|
|
|
+ step1.Id = 1;
|
|
|
+ Steps.Add(step1);
|
|
|
+
|
|
|
+ var step2 = new entity.workflowDefine.Step();
|
|
|
+ step2.Name = "老板申核";
|
|
|
+ step2.Id = 2;
|
|
|
+ Steps.Add(step2);
|
|
|
+
|
|
|
+ var step3 = new entity.workflowDefine.Step();
|
|
|
+ step3.Name = "申核结果通知";
|
|
|
+ step3.Id = 3;
|
|
|
+ Steps.Add(step3);
|
|
|
+
|
|
|
+ var step4 = new entity.workflowDefine.Step();
|
|
|
+ step4.Name = "新步骤";
|
|
|
+ step4.Id = 4;
|
|
|
+ Steps.Add(step4);
|
|
|
+
|
|
|
+ workflow.EndStepId = 3;
|
|
|
+
|
|
|
+ Transfers = new List<entity.workflowDefine.TrasferCondition>();
|
|
|
+ var t = new entity.workflowDefine.TrasferCondition();
|
|
|
+ t.StepId = null;
|
|
|
+ t.nextStepId = 1;
|
|
|
+ Transfers.Add(t);
|
|
|
+
|
|
|
+ var t1 = new entity.workflowDefine.TrasferCondition();
|
|
|
+ t1.StepId = 1;
|
|
|
+ t1.nextStepId = 2;
|
|
|
+ Transfers.Add(t1);
|
|
|
+
|
|
|
+ var t2 = new entity.workflowDefine.TrasferCondition();
|
|
|
+ t2.StepId = 1;
|
|
|
+ t2.nextStepId = 3;
|
|
|
+ Transfers.Add(t2);
|
|
|
+
|
|
|
+ var t3 = new entity.workflowDefine.TrasferCondition();
|
|
|
+ t3.StepId = 2;
|
|
|
+ t3.nextStepId = 3;
|
|
|
+ Transfers.Add(t3);
|
|
|
+ #endregion
|
|
|
+
|
|
|
+
|
|
|
+ shapeTrees = new List<shapeNode>();
|
|
|
+
|
|
|
+ startNode = new shapeNode()
|
|
|
+ {
|
|
|
+ //NodeObject = workflow.InitAction,
|
|
|
+ InCount = 0,
|
|
|
+ OutCount = 0,
|
|
|
+ width = 2 * initRadius,
|
|
|
+ height = 2 * initRadius,
|
|
|
+ Level = 0
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ InitShape = new shapeNode()
|
|
|
+ {
|
|
|
+ NodeObject = workflow.InitAction,
|
|
|
+ InCount = 0,
|
|
|
+ OutCount = 0,
|
|
|
+ Type = 1,
|
|
|
+ Level = 1
|
|
|
+ };
|
|
|
+
|
|
|
+ startNode.Childrens = new List<shapeNode>();
|
|
|
+ startNode.Childrens.Add(InitShape);
|
|
|
+
|
|
|
+ InitShape.Parents = new List<shapeNode>();
|
|
|
+ InitShape.Parents.Add(startNode);
|
|
|
+
|
|
|
+ endNode = new shapeNode()
|
|
|
+ {
|
|
|
+ InCount = 0,
|
|
|
+ OutCount = 0,
|
|
|
+ height = 2 * EndRadius,
|
|
|
+ width = 2 * EndRadius
|
|
|
+ };
|
|
|
+
|
|
|
+ shapeTrees.Add(startNode);
|
|
|
+
|
|
|
+ //shapeTrees.Add(endNode);
|
|
|
+
|
|
|
+ #region 将步骤对象生成形状Node并添加到列表中
|
|
|
+ foreach (var step in Steps)
|
|
|
+ {
|
|
|
+ var temNode = new shapeNode() { NodeObject = step, InCount = 0, OutCount = 0, Type = 1 };
|
|
|
+
|
|
|
+ if (workflow.EndStepId == step.Id)
|
|
|
+ {
|
|
|
+ temNode.Childrens = new List<shapeNode>();
|
|
|
+ temNode.Childrens.Add(endNode);
|
|
|
+ endNode.Parents = new List<shapeNode>();
|
|
|
+ endNode.Parents.Add(temNode);
|
|
|
+ }
|
|
|
+
|
|
|
+ shapeTrees.Add(temNode);
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region 遍历转移条件,生成流程树
|
|
|
+ foreach (var transfer in Transfers)
|
|
|
+ {
|
|
|
+ var FromNode = InitShape;
|
|
|
+ int FromLevel = 0;
|
|
|
+
|
|
|
+ if (transfer.StepId != null)
|
|
|
+ {
|
|
|
+ FromNode = FindNode(transfer.StepId.Value, out FromLevel, shapeTrees);
|
|
|
+ }
|
|
|
+
|
|
|
+ int ToLevel = 0;
|
|
|
+ var ToNode = FindNode(transfer.nextStepId, out ToLevel, shapeTrees);
|
|
|
+
|
|
|
+ if (FromNode.Childrens == null)
|
|
|
+ {
|
|
|
+ FromNode.Childrens = new List<shapeNode>();
|
|
|
+ }
|
|
|
+ FromNode.Childrens.Add(ToNode);
|
|
|
+
|
|
|
+ if (ToNode.Parents == null)
|
|
|
+ {
|
|
|
+ ToNode.Parents = new List<shapeNode>();
|
|
|
+ }
|
|
|
+ ToNode.Parents.Add(FromNode);
|
|
|
+
|
|
|
+ FromNode.OutCount += 1;
|
|
|
+ ToNode.InCount += 1;
|
|
|
+
|
|
|
+ if (FromNode.Level >= ToLevel)
|
|
|
+ {
|
|
|
+ ToNode.Level = FromNode.Level + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shapeTrees.Contains(ToNode))
|
|
|
+ {
|
|
|
+
|
|
|
+ shapeTrees.Remove(ToNode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ endNode.Level = endNode.Parents[0].Level + 1;
|
|
|
+ #endregion
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ GetShapeLevelNodes(LevelNodes, shapeTrees);
|
|
|
+
|
|
|
+ #region 添加跨层连接的中间层的虚拟节点
|
|
|
+ //foreach (int level in LevelNodes.Keys)
|
|
|
+ //{
|
|
|
+ // foreach (var temNode in LevelNodes[level])
|
|
|
+ // {
|
|
|
+ // if (temNode.Childrens != null)
|
|
|
+ // {
|
|
|
+ // foreach (var temChildrenNode in temNode.Childrens)
|
|
|
+ // {
|
|
|
+ // if ((temChildrenNode.Level - temNode.Level) > 1)
|
|
|
+ // {
|
|
|
+ // temNode.Childrens.Remove(temChildrenNode);
|
|
|
+ // temChildrenNode.Parents.Remove(temNode);
|
|
|
+
|
|
|
+ // var parentNode = temNode;
|
|
|
+
|
|
|
+ // for (int iLevel = temNode.Level + 1; iLevel < temChildrenNode.Level; iLevel++)
|
|
|
+ // {
|
|
|
+ // var xnNode = new shapeNode() { Type = 3, Parents = new List<shapeNode>(), Childrens = new List<shapeNode>() };
|
|
|
+ // parentNode.Childrens.Add(xnNode);
|
|
|
+ // xnNode.Parents = new List<shapeNode>();
|
|
|
+ // xnNode.Parents.Add(parentNode);
|
|
|
+ // parentNode = xnNode;
|
|
|
+ // }
|
|
|
+
|
|
|
+ // parentNode.Childrens.Add(temChildrenNode);
|
|
|
+ // temChildrenNode.Parents.Add(parentNode);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ //}
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ int MaxNodeLevel = 0;
|
|
|
+ int NodeCount = 0;
|
|
|
+
|
|
|
+
|
|
|
+ foreach (int level in LevelNodes.Keys)
|
|
|
+ {
|
|
|
+ if (level > 1)
|
|
|
+ {
|
|
|
+ int temLevelCount = 0;
|
|
|
+
|
|
|
+ foreach (var temNode in LevelNodes[level])
|
|
|
+ {
|
|
|
+ int graterInOrOut = (temNode.OutCount > temNode.InCount) ? temNode.OutCount : temNode.InCount;
|
|
|
+ if (graterInOrOut <= 1)
|
|
|
+ {
|
|
|
+ temLevelCount += 1;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ temLevelCount += graterInOrOut;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (temLevelCount > MaxNodeLevel)
|
|
|
+ {
|
|
|
+ MaxNodeLevel = level;
|
|
|
+ NodeCount = temLevelCount;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #region 每层节点排序
|
|
|
+ // 2.1. sort out each layer by looking at where it connects from
|
|
|
+ for (var i = 1; i < LevelNodes.Count; ++i)
|
|
|
+ {
|
|
|
+ var top_layer = LevelNodes[i - 1];
|
|
|
+
|
|
|
+ LevelNodes[i] = LevelNodes[i].OrderBy(node =>
|
|
|
+ {
|
|
|
+ // calculate average position based on connected nodes in top layer
|
|
|
+ if(node.Parents == null)
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ var connected_nodes = node.Parents
|
|
|
+ .Where(l => l.Level == (i - 1)).ToList();
|
|
|
+
|
|
|
+ if (!connected_nodes.Any())
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ var average_index = connected_nodes.Select(cn => { var i = top_layer.IndexOf(cn); return i; }).Average();
|
|
|
+ return average_index;
|
|
|
+ }).ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2.2. now that all but the first layer are layed out, let's deal with the first
|
|
|
+ if (LevelNodes.Count > 1)
|
|
|
+ {
|
|
|
+ LevelNodes[0] = LevelNodes[0].OrderBy(node =>
|
|
|
+ {
|
|
|
+ // calculate average position based on connected nodes in top layer
|
|
|
+ var connected_nodes = node.Childrens
|
|
|
+ .Where(l => l.Level == 1)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (!connected_nodes.Any())
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ var average_index = connected_nodes.Select(cn => { var i = LevelNodes[1].IndexOf(cn); return i; }).Average();
|
|
|
+ return average_index;
|
|
|
+ }).ToList();
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ ArrangeNodesInRows(LevelNodes);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ArrangeNodesInRows(Dictionary<int, List<shapeNode>> LevelNodes)
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+ double preBotton = TitleHeight;
|
|
|
+ for (int iLevel = 0; iLevel < LevelNodes.Count; iLevel++)
|
|
|
+ {
|
|
|
+ int iCount = 0;
|
|
|
+ foreach (var node in LevelNodes[iLevel])
|
|
|
+ {
|
|
|
+ int max = (node.InCount > node.OutCount) ? node.InCount : node.OutCount;
|
|
|
+ if (max == 0)
|
|
|
+ {
|
|
|
+ max = 1;
|
|
|
+ }
|
|
|
+ iCount += max;
|
|
|
+ }
|
|
|
+
|
|
|
+ double onNodeWidth = ChartWidth / (iCount + 1);
|
|
|
+
|
|
|
+ iCount = 0;
|
|
|
+ double maxHeight = 0;
|
|
|
+ foreach (var node in LevelNodes[iLevel])
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+ int max = (node.InCount > node.OutCount) ? node.InCount : node.OutCount;
|
|
|
+ if (max == 0)
|
|
|
+ {
|
|
|
+ max = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ node.x = onNodeWidth * (iCount + 1) + (max - 1) * onNodeWidth / 2;
|
|
|
+ if (node.Type == 0)
|
|
|
+ {
|
|
|
+ node.width = 2 * initRadius;
|
|
|
+ node.height = 2 * initRadius;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ node.width = rectWidth;
|
|
|
+ node.height = rectHeight;
|
|
|
+ }
|
|
|
+ iCount += max;
|
|
|
+
|
|
|
+ if (node.height > maxHeight)
|
|
|
+ {
|
|
|
+ maxHeight = node.height;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var node in LevelNodes[iLevel])
|
|
|
+ {
|
|
|
+ node.y = preBotton + hSeparation + maxHeight / 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ preBotton = preBotton + hSeparation + maxHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public dynamic GetLineParater(entity.workflowDefine.TrasferCondition trasferCondition)
|
|
|
+ {
|
|
|
+ int level = 0;
|
|
|
+ var startNode = InitShape;
|
|
|
+ if (trasferCondition.StepId != null)
|
|
|
+ {
|
|
|
+ startNode = FindNode(trasferCondition.StepId.Value, out level, shapeTrees);
|
|
|
+ }
|
|
|
+ var endNode = FindNode(trasferCondition.nextStepId, out level, shapeTrees);
|
|
|
+
|
|
|
+ dynamic ret = new ExpandoObject();
|
|
|
+ ret.x1 = startNode.x;
|
|
|
+ ret.y1 = startNode.y + startNode.height / 2;
|
|
|
+
|
|
|
+ ret.x2 = endNode.x;
|
|
|
+ ret.y2 = endNode.y - endNode.height / 2;
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ public dynamic GetEndStepLine()
|
|
|
+ {
|
|
|
+ var startNode =endNode.Parents[0];
|
|
|
+ dynamic ret = new ExpandoObject();
|
|
|
+ ret.x1 = startNode.x;
|
|
|
+ ret.y1 = startNode.y + startNode.height / 2;
|
|
|
+
|
|
|
+ ret.x2 = endNode.x;
|
|
|
+ ret.y2 = endNode.y - endNode.height / 2;
|
|
|
+
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnInitialized()
|
|
|
+ {
|
|
|
+ initShapeTree();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ void ClickStep(entity.workflowDefine.Step step)
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ void ClickTrasfer(entity.workflowDefine.TrasferCondition trasferCondition)
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ void InitAction()
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ void ClickNode(shapeNode node)
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|