ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C# WinForm自研工作流表单设计器核心实现与踩坑实践

C# WinForm自研工作流表单设计器核心实现与踩坑实践 先交代一下背景年初接了一个OA审批类的项目其中一块核心需求是让业务人员自己配置审批流程和表单而不是每次改动都找开发改代码。调研了一圈市面上的工作流组件要么贵得离谱要么跟项目技术栈绑定太死要么就是重得杀鸡用牛刀。最后决定基于 C# WinForm 自己写一个轻量级的工作流表单设计器支持流程节点拖拽绘制、表单字段可视化配置、流程与表单联动绑定。这篇文章就把这个项目的功能设计、核心实现思路和踩掉的坑完整梳理一遍给正在做类似需求的同行一个参考。这套设计器最终实现的功能大概是这样左侧是流程节点工具箱和表单控件面板中间是流程图绘制画布支持开始、审批、条件、抄送、结束等节点的拖拽放置节点之间可以连线绘制流转路径右侧是选中节点的属性面板可以配置审批人角色、条件分支规则、绑定的表单字段和字段权限。表单设计器部分支持文本框、下拉框、日期、附件、数字等常见控件拖拽布局每个控件可以绑定到流程节点实现不同节点看到不同字段、不同编辑权限的效果。整体项目用 VS2015 环境开发框架基于 .NET Framework 4.5纯 GDI 自行绘制流程图和表单布局没有依赖任何第三方图形控件。1. 为什么放着现成的第三方组件不用偏要自己写设计器1.1 业务需求的真实起点项目最初提的需求其实很简单一张请假单从提交人发起经过部门经理审批、人事复核最后归档结束。但随着业务部门把报销、用章、采购申请全部往这个系统里搬需求迅速膨胀成了任意流程任意表单的通用平台。这时候再靠硬编码页面已经撑不住了必须有一个能让实施人员可视化配置流程和表单的工具。当时面临三条路一是采购成熟商业工作流产品功能全但价格高、二次开发受限、部署方式不灵活二是用开源工作流引擎比如 ccflow、RoadFlow 这类引擎本身可用但要嵌入现有业务系统还得做大量适配而且它们的表单设计器大多基于 Web跟我们 WinForm 的桌面端架构格格不入第三条路就是自研一个轻量级的可视化管理工具只解决流程画得出来、表单配得出来、跑得起来这三个核心问题。我选了第三条路。原因很实际项目本身对流程引擎的要求并不高不需要复杂的会签、或签、子流程嵌套主要是线性审批和简单条件分支自研设计器加一个轻量级推进引擎完全够用而且整个设计器工具以后可以直接打包给实施人员使用不占用系统运行时的资源。真实项目里80% 的流程需求都是这种串行审批加少量分支的模式过度设计才是最大的风险。1.2 自研方案与第三方组件的取舍对比对比维度商业表单工作流组件开源Web工作流引擎自研WinForm设计器采购/使用成本高按人头或按部署收费免费但后续维护靠自己一次性开发成本复用率高架构匹配度需要适配桥接多为Web架构与桌面端不搭原生WinForm完美融合二次开发灵活度受厂商接口限制较高但源码复杂完全可控想改就改图表形式要求多为Web流程图多在网页中展示桌面端流程图打印导出方便实施门槛需要专人培训需要熟悉其配置方式内部工具按自己习惯设计这个对比表不是写代码时才想的是在立项阶段就给客户汇报过的。我的想法是工作流表单设计器本质上是给实施人员和管理员用的生产力工具它的核心价值不是把流程引擎做得多么复杂而是把常规的审批流程配置时间从按天计算压缩到按小时计算。自研设计器哪怕前期投入一周时间只要后面能节省每个项目的实施成本这笔账就是划算的。2. 设计器的整体架构与核心数据结构设计2.1 三个核心模块的职责划分整个设计器按功能拆成了三个相对独立的模块流程图绘制模块、表单设计模块、属性配置与序列化模块。这三个模块虽然是三个不同的界面区域但必须共享同一套数据模型否则后面做节点与表单绑定的时候会很难受。流程图绘制模块负责在画布上渲染节点矩形或圆角矩形框和有向连线处理拖拽放置、连线交互、节点选中、框选移动等操作。表单设计模块维护一个画布表单区业务人员从控件面板拖文本框、日期选择器等到表单画布上自由摆放表单画布就是一个可视化的表单布局工具。属性配置与序列化模块统一管理所有对象的属性节点属性、连线属性、控件属性等并把整个设计成果序列化为可落盘的文件数据运行时的引擎从这个文件里读取流程和表单定义。这三个模块之间通过一个DesignerDocument对象串联。这个对象持有流程节点列表、连线列表、表单控件列表以及节点与控件的绑定关系。所有界面操作最终都是对DesignerDocument的增删改查画布重绘时也只是根据内存中的数据重新GDI绘制。2.2 流程节点与表单字段的数据结构这是我的核心设计部分。先给出一段能直接参考的节点定义// 节点类型 public enum NodeType { StartNode, // 开始节点 ApproveNode, // 审批节点 ConditionNode, // 条件分支节点 CopyNode, // 抄送节点 EndNode // 结束节点 } // 流程节点实体 public class FlowNode { public string NodeId { get; set; } // 全局唯一ID public string Name { get; set; } // 节点显示名称 public NodeType NodeType { get; set; } // 节点类型 public RectangleF Bounds { get; set; } // 节点在画布上的位置和尺寸 public Liststring ApproverRoles { get; set; } // 审批角色集合审批节点用到 public string ConditionExpression { get; set; } // 条件表达式条件节点用到 public ListFieldBinding FieldBindings { get; set; } // 绑定的表单字段权限 }表单控件这边的结构是这样的// 表单控件实体 public class FormControl { public string ControlId { get; set; } // 全局唯一ID public string Label { get; set; } // 字段标题 public string FieldName { get; set; } // 数据字段名 public ControlType CtrlType { get; set; }// 控件类型 public RectangleF Bounds { get; set; } // 控件在表单画布上的位置和大小 public bool IsRequired { get; set; } // 是否必填 } // 节点与表单字段的绑定关系 public class FieldBinding { public string ControlId { get; set; } // 表单控件ID public bool IsEditable { get; set; } // 当前节点下该字段是否可编辑 public bool IsVisible { get; set; } // 当前节点下该字段是否可见 }为什么把节点和表单字段的绑定拆成一个单独的FieldBinding列表挂到流程节点上这是从实际业务里逼出来的设计。报销单在发起节点需要填写金额和事由到了部门经理节点金额和事由变成只读同时新增一个审批意见字段到了财务节点金额重新变成可编辑还要追加付款方式字段。每个节点对同一批表单字段的可见性和可编辑性是不同的。如果表单控件上设计一套全局的权限规则根本表达不了这种多节点差异化需求。把绑定关系挂在流程节点上最直接运行时引擎只要拿到当前节点查一下它的FieldBindings就知道要渲染哪些字段、字段是否可编辑。顺带一个经验节点的连线不能只存从哪到哪还要在连线上挂一个ConditionExpression属性。条件分支的两个出口连线各自挂不同的条件表达式运行时引擎判断走向时遍历当前节点的出口连线逐条判断条件真假走第一条为真的连线。这样条件分支的实现成本极低画出来也直观。3. 流程图拖拽绘制的核心实现过程3.1 节点绘制与命中测试的实现细节画布上的节点绘制我用的是 GDI 的GraphicsPath加线性渐变填充节点外框圆角矩形标题栏颜色按节点类型区分开始节点绿色、审批节点蓝色、条件节点橙色、抄送节点灰色、结束节点红色。这样业务人员一眼就能从颜色上分辨节点类型视觉友好度很高。绘制代码核心片段如下using System.Drawing.Drawing2D; private void DrawFlowNode(Graphics g, FlowNode node) { GraphicsPath path new GraphicsPath(); int radius 6; RectangleF rect node.Bounds; // 构造圆角矩形路径 path.AddArc(rect.X, rect.Y, radius * 2, radius * 2, 180, 90); path.AddArc(rect.Right - radius * 2, rect.Y, radius * 2, radius * 2, 270, 90); path.AddArc(rect.Right - radius * 2, rect.Bottom - radius * 2, radius * 2, radius * 2, 0, 90); path.AddArc(rect.X, rect.Bottom - radius * 2, radius * 2, radius * 2, 90, 90); path.CloseFigure(); // 按节点类型填充不同颜色 Color startColor Color.Transparent; Color endColor Color.Transparent; switch (node.NodeType) { case NodeType.StartNode: startColor Color.FromArgb(168, 220, 168); endColor Color.FromArgb(76, 175, 80); break; case NodeType.ApproveNode: startColor Color.FromArgb(187, 222, 251); endColor Color.FromArgb(33, 150, 243); break; case NodeType.ConditionNode: startColor Color.FromArgb(255, 224, 178); endColor Color.FromArgb(255, 152, 0); break; } using (LinearGradientBrush brush new LinearGradientBrush( rect, startColor, endColor, LinearGradientMode.Vertical)) { g.FillPath(brush, path); } using (Pen pen new Pen(Color.FromArgb(80, 80, 80), 1.2f)) { g.DrawPath(pen, path); } // 绘制节点名称 using (SolidBrush textBrush new SolidBrush(Color.Black)) { StringFormat sf new StringFormat { Alignment StringAlignment.Center, LineAlignment StringAlignment.Center }; g.DrawString(node.Name, this.Font, textBrush, rect, sf); } }命中测试是指鼠标点击画布时判断点在了哪个节点或连线上。节点命中直接用RectangleF.Contains(point)就能判断非常简单。但连线命中就有讲究了流程图画完之后连线密密麻麻鼠标要能精确点中一条线其实很不容易。我的实现是把每条连线当成一条虚拟的粗线段用点到线段的距离判断是否命中。具体做法是给每条线段构造一个隐形的矩形区域矩形宽度固定为 12 像素只要鼠标点落在矩形内就算命中该连线。这个做法比纯粹求点到线段距离要简单而且实测后手感不错。3.2 连线拖拽交互的完整交互设计连线的交互是流程图设计器里最容易让用户觉得不跟手的部分。我最终采用的是节点边缘锚点拖动预览线的方案每个节点四周有 4 个锚点上、下、左、右鼠标按下节点锚点时开始拖动拖拽过程中画一条从锚点指向鼠标位置的虚线预览线松开鼠标时判断是否落在目标节点的锚点范围如果命中就在这两个锚点之间生成一条贝塞尔曲线连线。这里有个很关键的设计决策松开鼠标判定目标节点时不能只判断鼠标位置是否在目标节点的矩形内部还要判断落点是否在目标节点有效锚点附近。否则用户从左侧锚点拖到右侧节点的中间位置生成的连线会非常丑各种穿模。实际代码里我做了就近锚点吸附处理鼠标落到目标节点矩形内任意位置自动吸附到离鼠标最近的那个锚点。private void Canvas_MouseMove(object sender, MouseEventArgs e) { if (_draggingLine ! null) { // 更新预览线终点 _draggingLine.TargetPoint e.Location; // 判断是否悬停在可接入的节点上 FlowNode hoverNode HitTestNode(e.Location); if (hoverNode ! null hoverNode.NodeId ! _draggingLine.SourceNodeId) { _draggingLine.TargetNodeId hoverNode.NodeId; _draggingLine.TargetAnchor GetNearestAnchor(hoverNode, e.Location); } else { _draggingLine.TargetNodeId null; } canvas.Invalidate(); } }连线绘制我用的是三次贝塞尔曲线起点和终点方向根据锚点位置决定。从下锚点连出的线初始方向向下连入上锚点的线收尾方向向上。这样画出来的连线比直线美观很多交叉线重叠时也能靠曲率拉开层次。另一个细节新拖拽出的连线默认名字是流转用户选中连线后可以在右侧属性面板里改名或配置条件表达式这个属性和节点属性共用同一个属性网格交互上不用额外维护一套非常省事。3.3 画布缩放与滚动视口的实现设计器最后还必须支持大流程的缩放和滚动。流程图一旦超过四五十个节点固定大小的画布根本无法操作。我的方案是在面板上放一个自定义DesignerCanvas控件开启AutoScroll通过一个ScaleFactor变量控制所有绘制坐标的缩放比例。实现时所有坐标存储都保留逻辑坐标仅在Paint事件中通过Transform矩阵整体缩放private void DesignerCanvas_Paint(object sender, PaintEventArgs e) { e.Graphics.SmoothingMode SmoothingMode.AntiAlias; e.Graphics.ResetTransform(); e.Graphics.ScaleTransform(ScaleFactor, ScaleFactor); // 设置滚动偏移 e.Graphics.TranslateTransform(this.AutoScrollPosition.X / ScaleFactor, this.AutoScrollPosition.Y / ScaleFactor); // 绘制网格 DrawBackgroundGrid(e.Graphics); // 绘制连线 foreach (var line in _document.Lines) DrawFlowLine(e.Graphics, line); // 绘制节点 foreach (var node in _document.Nodes) DrawFlowNode(e.Graphics, node); }有个必须注意的坑由于逻辑坐标被整体缩放鼠标命中测试时的坐标也要先做逆变换。我就是忘了在MouseDown里除一下ScaleFactor一开始缩放 120% 时节点总是点不中排查了半天才发现是坐标变换没对应上。正确做法是封一个PointF LogicFromScreen(Point p)方法统一处理滚动偏移和缩放所有鼠标事件里的坐标都先过一遍这个方法再参与命中测试。4. 表单设计器与流程节点的绑定逻辑4.1 表单控件面板的设计思路流程节点画好之后画布下方可以切换到表单设计视图。这个视图类似一个简单的画布型表单设计器左侧是可拖拽控件面板包含单行文本框、多行文本、数字输入、日期选择、下拉框、复选框、附件上传等常用控件拖到中间表单画布后生成对应的控件实例控件之间可以自由摆放位置、调整大小、对齐分布。表单画布的控件渲染是 WinForm 原生控件叠加还是纯 GDI 绘制这个问题我纠结了很久最后选了纯 GDI 绘制因为原生控件在拖拽、对齐、吸附时的行为不一致而且保存和重载时需要同步控件状态非常繁琐。纯绘制方案的数据就是FormControl对象列表每个对象存坐标、尺寸、标题、字段名等元数据运行时引擎再根据这些元数据动态生成真实的输入控件。设计器里只负责布局和配置不负责真实数据交互这个思路让设计器模块和运行时模块彻底解耦。表格控件在表单设计器里是最难处理的一块。为了不过度设计第一版我采用了明细子表的简化方案一个明细子表控件在表单画布上就是一个带添加明细行功能的区域运行时渲染为一个DataGridView列结构在属性面板中配好。这个方案极大降低了实现复杂度业务上应对报销单多行明细、请假单多段行程足够用了。4.2 节点到表单字段的绑定关系映射表单设计器和流程设计器之间如何联动是整个项目里最容易被低估的技术点。我的做法是选中流程节点时右侧属性面板除了显示基础属性还显示一个表单字段权限分组框列出当前表单全部控件每个控件旁边有可见性和可编辑性两个复选框。这个操作背后做的其实是维护FlowNode.FieldBindings列表节点选中时加载绑定列表任何勾选变化后立即写入内存点击保存按钮时把所有节点的绑定关系一起序列化。这里我设计了一个默认规则新节点创建时如果表单控件已经存在默认全部字段可见、全部可编辑用户按实际业务去调整。新添加的表单控件在已存在的流程节点上默认是不可见不可编辑需要用户逐个节点去配置。这个默认规则在实际配置流程时最省事因为多数场景下新增字段默认只能发起人填写。4.3 审批角色与字段权限的协同配置表单字段的可见性和可编辑性之外还有一个隐藏需求审批环节经常要限制审批人只能看到与自己相关的字段或者某个节点允许指定角色才能编辑某些敏感字段。这部分的实现我归到了协同配置里流程节点属性面板中有审批人角色配置可以多选角色字段权限列表里每个字段右侧除了可见、可编辑复选框外还有一个仅限角色下拉框可以选择某个角色。运行时引擎实际执行时先判断当前节点绑定的审批人角色是否包含当前登录人的角色如果不包含该节点直接跳过如果包含再进入字段权限列表按当前用户角色过滤字段的可见性和可编辑性。这个双重校验的设计保证了即使未来有人绕过界面直接构造请求敏感字段也不会泄露给无权限角色。5. 流程定义的序列化与运行时推进引擎5.1 流程定义文件的序列化方案设计器的最终产物是一份流程定义文件包含流程图节点、连线、条件分支表达式以及表单布局和字段权限绑定关系。序列化格式我用的是 XML虽然 JSON 更轻便但 XML 在定义多级嵌套结构时自然可读而且 .NET 自带的XmlSerializer可以直接控制序列化过程出问题方便排查。文件后缀定义成.flow本质就是一个XML文件。序列化的核心类是DesignerDocument整个文件结构大概是这样的[XmlRoot(FlowDefinition)] public class DesignerDocument { [XmlElement(FlowName)] public string FlowName { get; set; } [XmlArray(Nodes)] [XmlArrayItem(Node)] public ListFlowNode Nodes { get; set; } [XmlArray(Lines)] [XmlArrayItem(Line)] public ListFlowLine Lines { get; set; } [XmlArray(FormControls)] [XmlArrayItem(Control)] public ListFormControl FormControls { get; set; } }这里提醒一句FlowLine里不仅要有SourceNodeId和TargetNodeId我额外存了SourceAnchor和TargetAnchor锚点方向否则重新加载文件时贝塞尔曲线起止方向就丢了连线会全部变成奇怪的形状。这个细节在第一次存盘加载测试时就暴露了属于典型的不存界面状态导致的数据不完整问题一定要在设计阶段就规避。5.2 轻量级运行时引擎的推进逻辑设计器做出来的文件要能被运行时引擎读取执行。我没有引入重型的工作流引擎而是自己写了一个只有几百行的FlowRuntime类核心功能就三个定位当前节点、推进到下一步、判断条件分支走向。public class FlowRuntime { public FlowNode CurrentNode { get; private set; } public void Start(DesignerDocument doc) { CurrentNode doc.Nodes.First(n n.NodeType NodeType.StartNode); } public FlowNode GetNextNode() { // 找出当前节点的所有出口连线 var outLines _document.Lines.Where(l l.SourceNodeId CurrentNode.NodeId); foreach (var line in outLines) { // 无条件限制的连线直接走 if (string.IsNullOrWhiteSpace(line.ConditionExpression)) { return _document.Nodes.First(n n.NodeId line.TargetNodeId); } // 有条件限制的连线要判断表达式结果 bool match EvaluateCondition(line.ConditionExpression); if (match) return _document.Nodes.First(n n.NodeId line.TargetNodeId); } return null; } }EvaluateCondition方法本质是一个简易表达式求值器支持{{amount}} 500、{{dept}} 财务部这类简单表达式。我在运行时引擎中内置了一个字典key 是表单字段名value 是当前表单提交的值先做字符串替换把{{字段名}}替换成实际值再交给DataTable.Compute去求值这是 .NET 自带的功能表达式求值够用且省事。别小看这个简易方案实际请假单、报销单这些业务里条件分支绝大多数就是金额比较、部门判断、天数判断这几类完全不需要上重量级规则引擎。这个轻量级引擎还包含一个当前节点事件机制进入节点时触发NodeEntered事件运行时 UI 根据CurrentNode.FieldBindings动态渲染当前表单字段重新实例化输入控件赋值初始数据调整只读状态。离开节点时触发NodeLeaving事件校验必填字段、保存表单数据快照然后推进到下一个节点。整个过程跟设计器完全是两套代码唯一沟通途径就是序列化出的流程定义文件。6. 实际开发过程中踩过的坑与优化经验6.1 画布刷新性能的优化第一次绘制完流程图后我直接在MouseMove事件里调用了画布的Invalidate()结果节点一多鼠标移动就明显卡顿尤其流程到四五十个节点后掉帧严重。排查发现原因很蠢Invalidate()触发Paint后全画布所有节点和连线全部重绘MouseMove高频触发导致每秒钟几十次全量重画。优化方案是我自己写的脏矩形刷新机制简单有效只把被鼠标拖动影响的节点或连线所在的区域标记为无效区域然后调用Invalidate(region)。GDI 的Invalidate(Rectangle)只重绘指定区域效果立竿见影。对鼠标拖拽预览线这种高频操作我还加了一个双缓冲画布技巧把静态节点和连线先绘制到一个预渲染的Bitmap上MouseMove 时只在这个位图上再叠加动态预览线避免了逐帧重算所有节点坐标。6.2 连线点击数不中问题与吸附策略连线命中难是一个非常影响体验的细节。最初连线的可见宽度是 2 像素点中它的概率低得让人抓狂。我把命中矩形的宽度放宽到了 14 像素之后好了很多但用户点击时仍然经常误命中旁边的连线。后来我加了一套优先级策略同一个鼠标位置命中多条连线时取离鼠标最近的那一条。判断规则简单粗暴计算鼠标位置到各条连线的折线距离距离最小的优先选中。贝塞尔曲线求最近点比较麻烦我用了折线近似法把一条贝塞尔曲线均匀采样 10 个点连成折线再算鼠标到折线段的最小距离精度足够用而且速度很快。6.3 表单字段与节点绑定关系错乱问题开发过程中有一段时间表单字段和节点绑定关系经常错乱典型症状是给节点 A 配置了字段可见性保存重新加载后节点 B 上的绑定数据出现在了节点 A 上。查了很久后来发现是序列化时忘记给FieldBinding里的ControlId和节点NodeId做唯一性约束表单控件重新加载后 ID 变了但FieldBinding里还在引用旧的控件 ID运行时匹配不上就出现了数据串位。修复方式是我在保存时增加了一个 ID 校验步骤遍历所有节点的FieldBindings凡是引用的控件 ID 不存在于当前FormControls列表中的一律清理并重新绑定到默认状态。另外ControlId的生成规则也改成了全局递增且永不重复不像以前用 GUID 导致每次加载都变。这类问题在自研工具里很容易出现因为没有现成框架帮忙保障数据的完整性必须在序列化边界做防御性校验。6.4 设计器随业务演进的功能裁剪自研设计器最大的风险不是做不出来而是越做越复杂。我最初的计划里有很多高级功能支持子流程嵌套、支持会签或签、支持超时自动提醒、表单字段级条件表达式联动……但真正跟业务方聊下来这些功能 90% 根本用不到。最后我坚定地砍掉了子流程嵌套和复杂会签只保留了串行审批、单条件分支、节点级字段权限、抄送节点这四个核心能力。实际交付后业务部门最常用的配置操作是新增审批节点、设置审批人角色、调整字段权限、设置条件分支金额阈值。四个能力覆盖了全部实际流程的 95% 以上。剩下那 5% 的复杂需求比如跨部门联合审批通过配置两个连续审批节点也能模拟出来。自研工具最忌讳的就是面子工程做一堆花哨功能却没人用反而是每个功能都高频使用这个工具才真正有价值。7. 界面美化与打包交付的实践补充这节单独拿出来说是因为很多自研工具做完功能就交付了界面丑得没法看实施人员用起来心里抵触。WinForm 界面美化我用了两个低成本方案一是给主窗体和各面板设置统一的主题色和字体规范整个设计器的工具栏、按钮、面板配色统一走一套 Color 常量表不用到处硬编码颜色值二是用TableLayoutPanel和SplitContainer合理布局避免控件一多界面就乱掉。我没有引入任何第三方皮肤组件纯靠布局细节和配色就达到了内部工具可接受的视觉水平。打包部署方面环境是 VS2015项目目标框架 .NET Framework 4.5。给实施人员交付时我用的是 InstallShield Limited EditionVS2015 自带的打包工具做安装程序。这里有个值得记的坑开发时本机装了 .NET Framework 4.8但目标客户机器只有 4.5结果有些运行时报错在生产环境才暴露。教训是打包前用TargetFramework严格约束目标环境并写一个部署前检查脚本检测客户机器 .NET 版本版本不达标时直接弹窗提示而不是等程序跑起来才报错。另外因为设计器会生成.flow文件我特意做了一层轻量的配置信息加密把 XML 做了 DES 加密保存和加载时自动解密。这不是高安全性措施主要是防止实施人员不小心用文本编辑器打开改动后把流程定义改坏了加密加校验和能拦住大部分手误操作。运行时引擎的加载程序也要做配套解密流程保持和设计器的加解密逻辑完全一致。8. 实测效果与后续扩展方向目前这套设计器已经跑在项目现场实际配置一个包含 8 个节点、15 个表单字段的报销审批流程大约耗时 50 分钟。对比之前的硬编码方式出需求、排开发、测试、发布一个流程至少两天。这个效率提升对实施团队来说是质变。当然自研工具也暴露了一些在当前架构下解决得不算完美的问题比如复杂条件分支的可视化配置还是不够直观节点字段权限列表在字段特别多时滚动浏览效率低这些我都计划在下一版本中优化。如果后续要把这套工具延伸优先级我会这样排第一是增加流程版本的灰度发布支持让流程修改不影响正在运行的实例第二是增加流程耗时统计和效能看板给管理层提供审批效率数据第三是考虑把表单设计器抽离成独立的控件库以后任何 WinForm 项目集成表单设计能力都能直接复用。自研工作流表单设计器这事做到够用、好用、能交付就已经赢了贪多求全反而会拖垮整个项目。我个人在这些天的开发里体会最深的一点是自研这种内部工具一定要先想清楚给谁用、解决什么问题、哪个功能最重要然后其他所有花里胡哨的想法都往后排。做流程设计器不是炫技不是展示你能画多好看的箭头而是让业务配置人员用最小的学习成本完成流程编排。把这一点想明白了设计方案就顺了代码实现也只是时间问题。
返回列表