Winform自绘流程图核心实践:GDI+节点连线与坐标变换详解
简介这是一份基于FlowChart.Net改写的Winform流程图示例面向需要在桌面项目中快速引入节点连线功能的开发者无论用于内部工具还是毕业设计都能快速上手。功能设计克制仅保留绘制节点、创建连线等核心交互适合小项目直接二次开发或学习流程图绘制原理。压缩包共59个文件约2.48MB以cs源码、dll依赖库、xml配置/说明、resx资源文件为主其中dll主要为MindFusion图表库等运行时依赖xml多为程序集配置与注释文档另含可直接运行的exe及相关调试文件解压后即可用Visual Studio打开解决方案查看结构。已有4775人学习下载在同类型轻量流程图中关注度较高。资源内包含FlowNode、FlowLink、FlowMain等核心类以及FrmCreateNode、FrmCreateLink两个创建窗体可帮助理解节点编辑、连线交互与界面布局的实现思路FrmCreateNode和FrmCreateLink演示了弹窗交互与数据传递方式FlowHelper则提供辅助计算方法整体结构清晰便于按需修改。基于MindFusion组件库也适合希望了解第三方流程图控件用法的开发者参考。1. 为什么最后选了自绘1.1 现成控件库都有点“笨重”先说结论Winform 下做流程图市面上不是没有现成方案像 NodeRed、YAD、Netron 这些控件库我都试过功能确实全节点拖拽、连线、缩放、序列化全给你包好了。但真正用在项目里你会发现两个问题一是上手成本真的高光是搞懂它的事件模型和渲染管线就得折腾好几天文档还不一定全二是样式改起来太痛苦客户一句“节点能不能换个圆角风格”你可能得翻半天源码才能找到绘制入口。我之前帮朋友做一个排产系统的工序流程图模块需求其实不复杂节点就是工序连线就是前后依赖关系能拖、能选、能删、能保存界面干净就行。但试了一圈现成库之后反而坚定了自绘的决心。一个只有二三十个节点的轻量级场景用 GDI 自己画比引一个几百 KB 的第三方库要可控得多。1.2 自绘的边界与适合场景自绘要画到什么程度得先想清楚边界。我这个方案定位是“简单”二字支持节点拖拽、连线绘制、单选多选、删除、缩放、平移数据序列化自己定不做自动布局、不做缩略图、不做撤销重做。这个边界很重要你一旦自绘所有功能都要自己实现范围失控会变成无底洞。适合自绘的典型场景我总结过节点数量在 200 个以内不需要频繁大批量重绘流程结构相对固定不需要复杂的自动布局算法需要高度定制节点外观和交互逻辑不想引入第三方依赖发布包体积可控。如果你的需求是那种几百上千个节点、需要自动排列、折叠展开、跨平台的大规模流程图那还是别自绘了直接上成熟方案更靠谱。自绘真正舒服的区间就是中小规模的业务流程图、状态机编辑器、节点配置面板这一类。2. 数据层设计先想清楚再动笔2.1 Node、Link 两个类就够了流程图最核心的数据结构就是两个节点和连线。不要一上来就参考 Visio 那套复杂的对象模型对于简单场景各定义一个类就够用。节点类我建议这样设计public class FlowNode { public string Id { get; set; } Guid.NewGuid().ToString(N); public string Text { get; set; } ; public Rectangle Bounds { get; set; } // 逻辑坐标下的节点区域 public Color BackColor { get; set; } Color.FromArgb(64, 158, 255); public bool IsSelected { get; set; } }连线类的核心是记录两个节点的 ID而不是记录坐标。坐标是绘制时实时算的如果你把坐标存下来拖动节点后连线就断开了。public class FlowLink { public string Id { get; set; } Guid.NewGuid().ToString(N); public string SourceNodeId { get; set; } public string TargetNodeId { get; set; } public string Label { get; set; } ; public bool IsSelected { get; set; } }这里有个容易被忽略的点连线的命中测试判断鼠标点没点中这条线最好在 Link 里提供一个方法而不是在控件的事件里写一坨 switch 逻辑。我用的方式是先把连线转成一条 GraphicsPath然后用IsOutlineVisible做命中判断这样以后如果要支持曲线样式改一个方法就行。2.2 坐标系统与缩放换算这是整个自绘流程图里最大的坑没有之一。Winform 的PaintEventArgs里给的是设备坐标也就是屏幕上实际的像素位置。而你的节点数据存的是逻辑坐标。一旦你做了缩放和平移两套坐标之间的换算必须统一处理否则会出现“鼠标点在一个位置节点在另一个位置”的诡异现象。我的做法是设置一个视图变换参数public class ViewTransform { public float Scale 1.0f; public PointF Offset PointF.Empty; public PointF ScreenToLogic(Point screen) { return new PointF( (screen.X - Offset.X) / Scale, (screen.Y - Offset.Y) / Scale); } public Point LogicToScreen(PointF logic) { return new Point( (int)(logic.X * Scale Offset.X), (int)(logic.Y * Scale Offset.Y)); } }绘制的时候把整个画布都包在Graphics.TranslateTransform和Graphics.ScaleTransform里这样 OnPaint 里面画的坐标全是逻辑坐标不需要每个节点自己换算一遍。而鼠标事件里拿到的屏幕坐标要先通过ScreenToLogic转成逻辑坐标再做命中测试。注意缩放和平移的变换只影响绘制逻辑千万别直接改节点的 Bounds否则鼠标拖拽的起点和终点会随着缩放倍率变得混乱。3. 交互层拖、连、选、删3.1 连线绘制与贝塞尔曲线连线是流程图的视觉骨架画得是否顺眼直接影响整体感受。我用的是三次贝塞尔曲线而不是直愣愣的直线。两个节点之间从源节点的右边中点出发到目标节点的左边中点收尾两个控制点分别向水平方向偏移一个固定值private GraphicsPath BuildLinkPath(FlowNode src, FlowNode dst) { Point start new Point(src.Bounds.Right, src.Bounds.Top src.Bounds.Height / 2); Point end new Point(dst.Bounds.Left, dst.Bounds.Top dst.Bounds.Height / 2); int dx Math.Max(30, Math.Abs(end.X - start.X) / 2); Point c1 new Point(start.X dx, start.Y); Point c2 new Point(end.X - dx, end.Y); GraphicsPath path new GraphicsPath(); path.AddBezier(start, c1, c2, end); return path; }控制点偏移量dx用Math.Max(30, 两节点水平距离的一半)这个公式节点靠近时曲线不会太平节点远离时曲线弧度和大多数流程图的观感接近。实测下来这个经验公式在大多数场景下都成立。箭头绘制有两种方案一种是画在终点位置但方向跟随曲线切线另一种是等分路径长度找点。我选用前者private void DrawArrow(Graphics g, GraphicsPath path, Pen pen) { // 取路径末尾的切线方向 PathData pd path.PathData; PointF lastPoint pd.Points[pd.Points.Length - 1]; PointF prevPoint pd.Points[pd.Points.Length - 2]; float angle (float)(Math.Atan2(lastPoint.Y - prevPoint.Y, lastPoint.X - prevPoint.X)); int size 10; PointF p1 new PointF( lastPoint.X - size * Math.Cos(angle - 0.4f), lastPoint.Y - size * Math.Sin(angle - 0.4f)); PointF p2 new PointF( lastPoint.X - size * Math.Cos(angle 0.4f), lastPoint.Y - size * Math.Sin(angle 0.4f)); g.DrawLine(pen, lastPoint, p1); g.DrawLine(pen, lastPoint, p2); }用Math.Atan2计算方向角正负 0.4 弧度是一个比较自然的张开角度箭头的两个侧边线不用再单独填充多边形线条风格和连线保持一致看上去更利落。3.2 常用的三种命中测试节点和连线都画完之后交互的第一步就是命中测试。我维护了三个状态hoverNode、hoverLink、selectedNodes。鼠标移动时不断更新前两个状态用于改变光标形状和绘制高亮点击时更新选中集合。核心命中测试分三类节点命中遍历所有节点判断逻辑坐标点在哪个节点的 Bounds 里。因为节点数量少几十个直接线性遍历就好不需要空间索引连线命中先遍历所有连线把连线转成 GraphicsPath再用IsOutlineVisible判断注意这个方法的第三个参数需要传入画笔宽度否则精确度会很差空白区域以上都没有命中的情况此时点击空白处应清空选中状态。这里有个细节IsOutlineVisible在缩放较大的时候比如放大到 150%鼠标稍微偏出线条就选不中。我实测的解决办法是把命中检测用的画笔宽度固定为 8 像素不跟随缩放变换这样手感会稳很多。鼠标按下和移动的核心逻辑protected override void OnMouseDown(MouseEventArgs e) { PointF logic _transform.ScreenToLogic(e.Location); FlowNode hit HitTestNode(logic); if (hit ! null) { if (!hit.IsSelected) { _selectedNodes.Clear(); hit.IsSelected true; _selectedNodes.Add(hit); } _dragMode DragMode.MoveNode; _dragStart logic; _nodeOriginalBounds hit.Bounds; } else if (HitTestLink(logic, out FlowLink link)) { link.IsSelected true; } else { ClearSelection(); } Invalidate(); }拖动的时候别直接用鼠标坐标减起始坐标的差值去改节点位置应该记下按下时的原始 Bounds再用当前鼠标位置减去按下时的位置得到偏移量把原始 Bounds 平移这个偏移。否则第二次拖动时节点会先跳一下。3.3 删除逻辑与界面体验删除节点时要把与它相关的连线一并删除否则会留下“悬空”的连线视觉上很丑数据上也是脏数据。private void DeleteSelected() { HashSetstring nodeIds new HashSetstring(_selectedNodes.Select(n n.Id)); _links.RemoveAll(l nodeIds.Contains(l.SourceNodeId) || nodeIds.Contains(l.TargetNodeId)); _nodes.RemoveAll(n nodeIds.Contains(n.Id)); _selectedNodes.Clear(); Invalidate(); }我还在节点上做了一个“拖出来一条线就开始连线”的交互鼠标从一个节点上按下后拖到空白区域时进入DragMode.Link状态此时画布上会实时绘制一条从源节点到当前鼠标位置的预览线松手时如果鼠标落在某个节点上就创建一条新连线。这个交互不算复杂但对于用户体验的提升很明显类似的功能在众多专业流程编辑器中都是标配。4. 渲染细节画得好看才是生产力4.1 绘制顺序和双缓冲绘制顺序很关键顺序反了会出现连线盖住节点文字、选中高亮被节点盖住的情况。我按这个顺序来底层网格背景所有连线选中的连线用高亮颜色画一遍所有节点选中的节点外圈加一圈描边节点文字拖动中的预览连线。OnPaint里面要设置好绘制质量protected override void OnPaint(PaintEventArgs e) { Graphics g e.Graphics; g.SmoothingMode System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; g.TranslateTransform(_transform.Offset.X, _transform.Offset.Y); g.ScaleTransform(_transform.Scale, _transform.Scale); // ... 按顺序绘制 }Winform 的控件默认DoubleBuffered是 false直接在上面画会出现明显的闪烁。我是直接在构造函数里强制开启双缓冲这比手动创建 BufferGraphics 要省心很多SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);4.2 节点外观和圆角矩形节点的默认外观是一个圆角矩形GDI 里没有直接画圆角矩形的 API需要自己构建 GraphicsPath。我把这个方法放在一个静态类里因为画节点、画选中框、画拖拽预览都要用public static GraphicsPath CreateRoundedRect(Rectangle rect, int radius) { int r Math.Min(radius, rect.Height / 2); GraphicsPath path new GraphicsPath(); path.AddArc(rect.X, rect.Y, r * 2, r * 2, 180, 90); path.AddArc(rect.Right - r * 2, rect.Y, r * 2, r * 2, 270, 90); path.AddArc(rect.Right - r * 2, rect.Bottom - r * 2, r * 2, r * 2, 0, 90); path.AddArc(rect.X, rect.Bottom - r * 2, r * 2, r * 2, 90, 90); path.CloseFigure(); return path; }绘制时加一个 1px 的边框让节点有立体感。选中状态时边框用橙色或高亮蓝色加粗这样用户一眼就能看出当前的选中对象。5. 常见问题与排查技巧实录5.1 刚踩过的几个坑我把开发过程中碰到的典型问题列成了一张表这些问题在大部分 Winform 自绘场景里都有参考价值问题现象原因解决办法双击面板闪烁拖拽缩放时界面剧烈闪烁没有开启双缓冲SetStyle(OptimizedDoubleBuffer, true)鼠标点击位置偏移放大 150% 后点不中节点命中测试没做坐标换算使用ScreenToLogic统一转换连线箭头方向不对箭头指向了终点旁边的区域直接用最后一个点算角度抖动用倒数第二个点和最后一个点的差值算角度文字模糊节点文字边缘毛刺默认文本渲染模式设置TextRenderingHint ClearTypeGridFit缩放后连线粗细变放大之后连线粗得像根棍子画笔宽度跟随缩放构建笔宽时除以Scale缩放后连线的笔宽问题是我加缩放功能之后最先暴露出来的。画笔宽度乘以倍数之后整个画面会显得头重脚轻。现在的做法是画连线时用_transform.Scale做一次除法让屏幕上看连线的像素宽度基本恒定观感会好很多。5.2 实际排错思路从现象倒推遇到“鼠标选中连线选中不到”这类问题我的排查思路是先确认坐标换算有没有问题再用一个简单的临时办法验证在 OnPaint 里把连线的 GraphicsPath 直接用红色画出来如果红色路径和实际能选中的范围一致那就说明问题出在命中测试的画笔宽度上而不是绘制问题。5.3 性能优化能撑住多大场景测了三种典型场景20 个节点、80 个节点、200 个节点。在普通配置的电脑上拖动、缩放、刷新基本都保持在 60fps 左右200 个节点时用双缓冲依旧稳定但如果节点的文字或连线上面带很多装饰元素帧率会明显下降。我自己优化后的做法是给OnPaint里判断IsVisible可见区域之外的对象直接跳过绘制虽然这个 demo 里作用不明显但代码结构是从大项目里带过来的习惯以后真要扩展也能兜住。5.4 进阶扩展的几个思路这个简单版做完之后扩充的方向其实非常多序列化把FlowNode和FlowLink序列化为 JSON保存流程定义反序列化时重新构建节点集合多选框选按住鼠标在空白区域拖出一个矩形批量选中内部节点自动布局对有层级关系的流程用TopologicalSort做排序再按层计算节点坐标导出图片用Bitmap创建一张大画布然后调用控件的DrawToBitmap撤销重做用命令模式把每次操作记录进栈我这个版本为了控制复杂度就没有加。5.5 最后一点心得在我实际做这个项目之前也想过直接用控件库是不是更省事。但做完之后最大的收益反而不是“省了第三方依赖”而是对整个 GDI 绘制体系有了更深入的理解。现在再去做 Winform 里的各种自定义控件比如仪表盘、甘特图、思维导图都是完全同一条技术路线数据层和绘制层分离、坐标换算清晰、命中测试稳定。以后凡是遇到这种“轻量级可视化编辑器”的需求我基本上不需要查资料就能直接动手。如果硬要挑一个建议的话我建议你把ScreenToLogic和LogicToScreen这套坐标换算单独做在一个类里写单元测试不要在画布代码里顺手写。这个看似简单的函数是整个自绘流程图的命门它一旦出错所有交互都会变得难以排查。我自己的版本里这个换算逻辑跑了三个项目都没怎么改过这就是把它独立出来的价值。本文还有配套的精品资源点击获取

相关新闻

HStudio全球开放:从接入到项目实战的完整指南

HStudio全球开放:从接入到项目实战的完整指南

最近 HStudio 面向全球 172 个国家和地区开放的消息,让不少开发者的关注点从“这个产品是什么”转向了“我能拿它做什么”。尤其是做 AI 应用、自动化脚本和云端交付的同学,更关心的是接入流程、认证方式、项目组织方式,以及上线后的运维细节…

2026/9/2 4:11:46 阅读更多 →
ConfuserEx从解压到实战:.NET代码混淆与seed可复现构建指南

ConfuserEx从解压到实战:.NET代码混淆与seed可复现构建指南

简介:ConfuserEx(.NET混淆工具)1.0版完整资源包,面向.NET开发者和安全测试人员,用于对.NET程序集进行代码混淆、反调试、反静态分析和资源保护,解决程序被轻易反编译与篡改的问题。整个zip共27个文件&#…

2026/9/3 6:44:01 阅读更多 →
VS静态链接OpenSSL:libeay64/ssleay64库编译与集成

VS静态链接OpenSSL:libeay64/ssleay64库编译与集成

简介:OpenSSL 1.0 的 64 位静态库难找?libeay64.lib 与 ssleay64.lib 一包搞定。资源面向在 Windows 下使用 Visual Studio 开发、需要直接链接 OpenSSL 的 C/C 程序员,省去编译源码、排查工具链的繁琐过程。压缩包共 168 个文件,…

2026/9/3 5:46:15 阅读更多 →

最新新闻

基于Q-Learning的路径规划MATLAB仿真:从原理到动态避障实践

基于Q-Learning的路径规划MATLAB仿真:从原理到动态避障实践

简介:这是一套面向机器学习初学者与机器人路径规划研究者的MATLAB实践资源,基于Q-Learning强化学习算法实现动态障碍物环境下的自主路径规划,支持用户交互式设定起点与目标点,兼具算法理解与工程实现价值。压缩包共36个文件&#…

2026/9/3 7:28:24 阅读更多 →
戴森HT01美发造型器技术解析:气流温控与康达效应如何重塑居家造型

戴森HT01美发造型器技术解析:气流温控与康达效应如何重塑居家造型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/3 7:28:24 阅读更多 →
终极骷髅1.4.5蓝超巨星打法:痛苦亡灵骑士泰坦天赋选择与实战流程

终极骷髅1.4.5蓝超巨星打法:痛苦亡灵骑士泰坦天赋选择与实战流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/3 7:28:24 阅读更多 →
AI重塑软件工程:从经济寿命到AI编程工作流的实践指南

AI重塑软件工程:从经济寿命到AI编程工作流的实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/3 7:28:24 阅读更多 →
AI内容人性化优化API:降低朱雀AI检测率的技术实现

AI内容人性化优化API:降低朱雀AI检测率的技术实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/3 7:28:24 阅读更多 →
AD717X系列高精度ADC嵌入式C驱动开发指南

AD717X系列高精度ADC嵌入式C驱动开发指南

简介:本资源是一套面向嵌入式工程师与精密测量系统开发者的AD717X系列多路复用Σ-Δ型模数转换器(ADC)通用驱动C源码,覆盖AD7172-2/4、AD7173-8、AD7175-2/8、AD7176-2及AD7177-2等主流型号,解决高精度传感器信号采集中…

2026/9/3 7:27:24 阅读更多 →

日新闻

AI智能体辅助JS逆向:从V8环境搭建到补环境实战

AI智能体辅助JS逆向:从V8环境搭建到补环境实战

先别急着点开,这不是劝退文,而是想讲清楚一件事:用 AI 做逆向值不值得学?如果要用,怎么搭一套“V8 环境 AI 智能体”来提升效率。最近逆向圈、爬虫圈都在聊 AI Agent、AST 工程逆向、JS 逆向这些词,很多新手…

2026/9/3 0:00:29 阅读更多 →
安卓设备通过修改机型信息解锁游戏高帧率:原理、操作与风险指南

安卓设备通过修改机型信息解锁游戏高帧率:原理、操作与风险指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/3 0:00:29 阅读更多 →
ARM版OpenJDK 11安装部署全攻略:下载、配置与避坑指南

ARM版OpenJDK 11安装部署全攻略:下载、配置与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/3 0:00:29 阅读更多 →

周新闻

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

每年校招季我都会接触不少准备数据库方向笔试的同学,看到最多的状态就是:简历上写着“熟悉 MySQL”“了解索引优化”,一碰到数据库管理工程师的笔试卷,却在索引、事务、锁、备份恢复这些题目上翻车。网易这套 2018 校园招聘数据库…

2026/9/3 4:22:22 阅读更多 →
数字电路时序基石:深入理解建立时间与保持时间

数字电路时序基石:深入理解建立时间与保持时间

1. 这不是“背公式”的事:时间参数到底在约束什么你翻过数字电路教材,一定见过这两个词:建立时间(Setup Time)和保持时间(Hold Time)。它们常被并列写在触发器(Flip-Flop&#xff09…

2026/9/3 4:22:01 阅读更多 →
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

1. 项目缘起:从赛题到超声波测距机的诞生第八届蓝桥杯单片机设计与开发国赛的题目,我至今记忆犹新。它没有直接给出一个花哨的名字,而是用“超声波测距机”这个朴实无华的功能描述,精准地勾勒出了考核的核心。对于当时备赛的我而言…

2026/9/3 4:22:59 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/3 4:17:49 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/3 4:18:56 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/3 4:21:44 阅读更多 →