Преглед на файлове

初步完成流程图控件

luocaiyang преди 3 години
родител
ревизия
7cfb8b30d9

+ 90 - 0
wispro.sp.web/Components/FlowChart.razor

@@ -0,0 +1,90 @@
+
+    @if (shapeTrees != null)
+    {
+    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="@ChartWidth" height="@ChartHeight">
+
+
+        <text x="@(ChartWidth/2)" y="@(TitleHeight/2)" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="30" font-weight="800">@workflow.Name</text>
+
+        <line x1="@startNode.x" y1="@(startNode.y + startNode.width / 2) " x2="@(InitShape.x)" y2="@(InitShape.y - InitShape.height / 2)" stroke="black" stroke-width="2" />
+
+        @for (int iLevel = 0; iLevel < LevelNodes.Count; iLevel++)
+        {
+            @foreach (var node in LevelNodes[iLevel])
+            {
+                if (node.Type == 0)
+                {
+                    <circle cx="@(node.x)" cy="@node.y" r="@(node.width / 2) " stroke="black" stroke-width="2" fill="white" />
+                    <a @onclick="() => ClickNode(node)">
+                        <text x="@(node.x)" y="@node.y" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="16">
+                            @if (node == startNode)
+                            {
+                                @("开始")
+                            }
+                            else
+                            {
+                                if (node == endNode)
+                                {
+                                    @("结束")
+                                }
+                                else
+                                {
+                                    if (node.NodeObject is entity.workflowDefine.Step)
+                                    {
+                                        @(((entity.workflowDefine.Step)node.NodeObject).Name)
+                                    }
+                                }
+                            }
+                        </text>
+                    </a>
+                }
+
+                if (node.Type == 1)
+                {
+                    <rect x="@(node.x - node.width / 2)" y="@(node.y - node.height / 2)" rx="10" ry="10" width="@node.width" height="@node.height" style="fill:green;stroke:black;stroke-width:3;opacity:0.5" />
+                    <a @onclick="() => ClickNode(node)">
+                        <text x="@node.x" y="@node.y" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="@rectFontSize" font-weight="700">
+                            @if (node == InitShape)
+                            {
+                                @((workflow.InitAction == null || string.IsNullOrEmpty(workflow.InitAction.Name)) ? $"启动{workflow.Name}" : workflow.InitAction.Name)
+                            }
+                            else
+                            {
+                                @(((entity.workflowDefine.Step)node.NodeObject).Name)
+                            }
+                        </text>
+                    </a>
+                }
+            }
+        }
+
+        @foreach (var t in Transfers)
+        {
+            dynamic ret = GetLineParater(t);
+            <line x1="@ret.x1" y1="@ret.y1" x2="@ret.x2" y2="@ret.y2" stroke="black" stroke-width="2" />
+
+            <circle cx="@((ret.x1 + ret.x2) / 2)" cy="@((ret.y1 + ret.y2) / 2)" r="5" stroke="black" stroke-width="1" fill="white" />
+            <a @onclick="() => ClickTrasfer(t)">
+                <text x="@((ret.x1 + ret.x2) / 2)" y="@((ret.y1 + ret.y2) / 2)" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="5">c</text>
+            </a>
+
+        }
+
+        
+        @{ 
+            dynamic endLine = GetEndStepLine();
+            <line x1="@endLine.x1" y1="@endLine.y1" x2="@endLine.x2" y2="@endLine.y2" stroke="black" stroke-width="2" />
+        }
+
+        @*<circle cx="@(endNode.x)" cy="@endNode.y" r="@(endNode.width/2) " stroke="black" stroke-width="2" fill="gray" />
+        <a>
+            <text x="@(endNode.x)" y="@endNode.y" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="5">结束</text>
+        </a>*@
+    </svg>
+    }
+    else
+    {
+        <Spin />
+    }
+
+    

+ 526 - 0
wispro.sp.web/Components/FlowChart.razor.cs

@@ -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)
+        {
+
+        }
+    }
+
+}

+ 1 - 4
wispro.sp.web/Layouts/LoginLayout.razor.cs

@@ -11,10 +11,7 @@ namespace wispro.sp.web.Layouts
         [Parameter]
         public string Style { get; set; } = "min-height:100vh;";
 
-        protected override void OnInitialized()
-        {
-            base.OnInitialized();
-        }
+        
         EventCallback _layoutStyleCallBack = EventCallback.Empty;
         EventCallback LayoutStyleCallBack
         {

+ 4 - 39
wispro.sp.web/Pages/Workflow/WorkflowDefine.razor

@@ -14,7 +14,6 @@
     </Breadcrumb>
     <Content>
         <Button Type="primary" Icon="plus" OnClick="AddNew" Style="float:right">添加新流程</Button>
-        <Button Type="primary" Icon="plus" OnClick="ChangeRectSize" Style="float:right">改变流程框大小</Button>
     </Content>
     <ChildContent>
         @if (workflows == null)
@@ -79,45 +78,11 @@
         }
         
         <Divider />
-        
         <div style="height:400px;width:100%;overflow:auto;background:#FFFFFF;">
-            
-            <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="@ChartWidth" height="800">
-                <text x="@(ChartWidth/2)" y="50" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="30" font-weight="800">案件系数申核流程</text>
-
-                <circle cx="@(ChartWidth/5)" cy="200" r="25" stroke="black" stroke-width="2" fill="white" />
-                <a @onclick="() => ClickStep(false)">
-                    <text x="@(ChartWidth/5)" y="200" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="16">开始</text>
-                </a>
-
-                <rect x="@(2*ChartWidth/5-rectWidth/2)" y="@(200-rectHeight/2)" rx="10" ry="10" width="@rectWidth" height="@rectHeight" style="fill:green;stroke:black;stroke-width:3;opacity:0.5" />
-                <a @onclick="() => ClickStep(false)">
-                    <text x="@(2*ChartWidth/5)" y="200" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="@rectFontSize" font-weight="700">提出申诉</text>
-                </a>
-
-                <rect x="@(3*ChartWidth/5-rectWidth/2)" y="@(200-rectHeight/2)" rx="10" ry="10" width="@rectWidth" height="@rectHeight" style="fill:green;stroke:black;stroke-width:3;opacity:0.5" />
-                <a @onclick="() => ClickStep(false)">
-                    <text x="@(3*ChartWidth/5)" y="200" fill="black" alignment-baseline="middle" text-anchor="middle" font-size="@rectFontSize" font-weight="700">主管申核</text>
-                </a>
-
-                <circle cx="@(4*ChartWidth/5)" cy="200" r="25" stroke="black" stroke-width="2" fill="gray" />
-                <a @onclick="() => ClickStep(true)">
-                    <text x="@(4*ChartWidth/5)" y="200" fill="black" alignment-baseline="middle" text-anchor="middle">结束</text>
-                </a>
-
-                <path d="M@(ChartWidth/5+25) 200 H@(2*ChartWidth/5-rectWidth/2)" stroke="black" />
-                <path d="M@(2*ChartWidth/5-rectWidth/2) 200 L@(2*ChartWidth/5-rectWidth/2-8) 205 L@(2*ChartWidth/5-rectWidth/2-8) 195 Z" stroke="black" />
-
-                <path d="M@(2*ChartWidth/5+rectWidth/2) 200 H@(3*ChartWidth/5-rectWidth/2)" stroke="black" />
-                <path d="M@(3*ChartWidth/5-rectWidth/2) 200 L@(3*ChartWidth/5-rectWidth/2-8) 205 L@(3*ChartWidth/5-rectWidth/2-8) 195 Z" stroke="black" />
-
-                <path d="M@(3*ChartWidth/5+rectWidth/2) 200 H@(4*ChartWidth/5-25)" stroke="black" />
-                <path d="M@(4*ChartWidth/5-25) 200 L@(4*ChartWidth/5-25-8) 205 L@(4*ChartWidth/5-25-8) 195 Z" stroke="black" />
-
-            </svg>
-            
+            <wispro.sp.web.Components.FlowChart workflow="@EditingObj" />
         </div>
-    </ChildContent>
+
+</ChildContent>
 </PageContainer>
 
 <style>
@@ -136,7 +101,7 @@
        OnCancel="@InitCancel"
         OkText="@("确认")"
         CancelText="@("取消")"
-       Width="900">
+       Width="900" MaskClosable="false">
         <wispro.sp.web.Components.InputValueSetting DataSource="@InitInputValues"/>
     </Modal>
 

+ 2 - 27
wispro.sp.web/Pages/Workflow/WorkflowDefine.razor.cs

@@ -15,10 +15,7 @@ namespace wispro.sp.web.Pages.Workflow
         private List<wispro.sp.entity.workflowDefine.Workflow> workflows = new List<wispro.sp.entity.workflowDefine.Workflow>();
         ITable table;
 
-        double ChartWidth = 1000;
-        double  rectWidth = 120;
-        double rectHeight = 60;
-        int rectFontSize = 26;
+        
         int _pageIndex = 1;
         int _pageSize = 10;
         int _total = 0;
@@ -149,29 +146,7 @@ namespace wispro.sp.web.Pages.Workflow
             _initModalVisible = false;
         }
 
-        void ClickStep(bool isEnd)
-        {
-            var msg = "您点击了开始步骤!";
-
-            if (isEnd)
-            {
-                msg = "您点击了结束步骤!";
-            }
-
-            _msgService.Info(msg);
-        }
 
-        void ChangeRectSize()
-        {
-            Random rd = new Random();
-
-            var s = 0.5 + rd.NextDouble();
-            rectWidth = s * rectWidth;
-            rectHeight = s * rectHeight;
-            rectFontSize =(int)(s * rectFontSize);
-
-            StateHasChanged();
-
-        }
+       
     }
 }

+ 495 - 0
wispro.sp.winClient/FlowChart.cs

@@ -0,0 +1,495 @@
+using Microsoft.AspNetCore.Components;
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace wispro.sp.winClient
+{
+    public partial class FlowChart
+    {
+        double TitleHeight = 60;
+        double ChartWidth = 1000;
+        double rectWidth = 120;
+        double rectHeight = 60;
+        double initRadius = 25;
+        double EndRadius = 25;
+        double hSeparation = 50;
+
+        int rectFontSize = 24;
+
+
+        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;
+        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);
+
+            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
+
+            Dictionary<int, List<shapeNode>> LevelNodes = new Dictionary<int, List<shapeNode>>();
+
+            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
+                    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 void OnInitialized()
+        {
+            initShapeTree();
+        }
+
+        
+
+        void ClickStep(entity.workflowDefine.Step step)
+        {
+
+        }
+
+        void ClickTrasfer(entity.workflowDefine.TrasferCondition trasferCondition)
+        {
+
+        }
+
+        void InitAction()
+        {
+
+        }
+    }
+
+}

+ 7 - 2
wispro.sp.winClient/Form1.cs

@@ -180,9 +180,14 @@ namespace wispro.sp.winClient
 
         private async void button3_Click(object sender, EventArgs e)
         {
-            var test =wispro.sp.utility.EmunHelper.getEnumDescriptionDic<wispro.sp.entity.workflowDefine.LogicSymbols>();
 
-            Console.WriteLine("");
+            FlowChart flowChart = new FlowChart();
+            flowChart.OnInitialized();
+            flowChart.GetLineParater(new entity.workflowDefine.TrasferCondition() { nextStepId = 1 });
+
+            //var test =wispro.sp.utility.EmunHelper.getEnumDescriptionDic<wispro.sp.entity.workflowDefine.LogicSymbols>();
+
+            //Console.WriteLine("");
             //dynamic dynObj = new ExpandoObject();
             //dynObj.Name = "名称";
             //dynObj.Text = "aaabc";

+ 1 - 1
wospro.sp.entity/workflowDefine/trasferCondition.cs

@@ -21,7 +21,7 @@ namespace wispro.sp.entity.workflowDefine
         /// <summary>
         /// 所属步骤Id
         /// </summary>
-        public int StepId{get;set;}
+        public int? StepId{get;set;}
 
 
         /// <summary>