
简介这是一份面向C#初学者与中小型WinForm项目开发者的流程图绘制工具资源基于开源库FlowChart.Net深度定制功能简洁实用适合嵌入轻量级业务系统或教学演示场景。压缩包共59个文件包含15个核心C#源码如FrmCreateNode、FlowLink、FlowMain等、12个依赖DLL含MindFusion.Diagramming.WinForms等图表渲染组件、6个XML文档说明及4个RESX资源文件整体结构清晰便于理解流程图控件的初始化、节点创建、连线逻辑与事件绑定机制包体大小为2.48MB无冗余素材开箱即用。已有4784人学习下载读者可直接获取完整VS解决方案含SLN、CSProj、调试版可执行文件EXE、设计时资源Licx、Settings及符号调试文件PDB快速掌握基于WinForm的可视化流程编排实现路径。1. Winform简单的FlowChart流程图不是画图软件而是让业务逻辑“活”在界面上的轻量级可视化方案你有没有遇到过这样的场景客户指着一张手绘流程图说“系统就按这个跑”而你得在代码里硬写一堆 if-else 和状态机或者团队新人对着几百行switch (state)发呆根本看不出“审批通过后下一步是归档还是重审”又或者测试同事反复问“这个按钮点三次到底触发哪条路径”——这时候一个嵌在 Winform 窗体里的、可交互、可响应、能随业务状态实时高亮当前节点的 FlowChart 流程图就不是“锦上添花”而是“救命稻草”。它不追求 BPMN 的复杂网关与事件驱动也不依赖第三方流程引擎而是用最朴素的 GDI 绘制 自定义控件 状态绑定在 VS2015 及以上环境C# .NET Framework 4.5中15 分钟内就能跑通一个可点击、可高亮、可导出 PNG 的最小可行流程图。它适合中小型 winform项目案例 中的用户管理模块流程图、审批流示意、算法流程图说明、甚至手机中框工艺流程图这类需要“一眼看懂路径”的场景。如果你正被“逻辑藏在代码里、流程写在 Word 里、沟通卡在截图上”折磨这篇就是为你写的实操笔记。2. 从零构建可交互 FlowChart 控件用 Panel 自定义绘制实现最小闭环Winform 原生没有 FlowChart 控件但它的可扩展性极强。我们不引入任何第三方库如 GoDiagram、MindFusion而是基于Panel控件 OnPaint重绘 鼠标事件构建一个完全可控、无依赖、可调试的轻量级流程图容器。核心思路是把流程图抽象为“节点集合 连线集合 当前激活状态”所有绘制和交互都围绕这三者展开。这种做法在 winform界面美化 和快速原型阶段优势明显——没有 DLL 版本冲突没有设计器加载失败VS2015 打开即用。2.1 定义流程图核心数据结构Node 与 Connection我们先建立两个不可变的数据模型。注意这里不用class而用recordC# 9既保证结构清晰又避免意外修改若用 VS2015C# 6则改用struct或带readonly字段的class。// Node.cs —— 每个节点包含唯一ID、文本、位置、尺寸、类型开始/处理/判断/结束 public record Node( string Id, string Text, Point Location, Size Size, NodeType Type NodeType.Process); public enum NodeType { Start, // 圆角矩形绿色边框 Process, // 标准矩形蓝色边框 Decision, // 菱形橙色边框 End // 双线矩形红色边框 } // Connection.cs —— 连线只记录起点ID和终点ID样式由绘制逻辑统一控制 public record Connection(string FromId, string ToId);提示Node.Id必须全局唯一建议用start,step1,approve?,end这类语义化字符串而非 GUID。后续状态绑定、日志追踪、调试定位都靠它——这是整个流程图的“身份证”。2.2 创建 FlowChartPanel 控件继承 Panel 并重写 OnPaint 与鼠标事件新建一个UserControl命名为FlowChartPanel.cs。关键不是“怎么画得漂亮”而是“怎么让画出来的东西能被点击、能被高亮、能响应状态变化”。// FlowChartPanel.cs public partial class FlowChartPanel : Panel { private ListNode _nodes new(); private ListConnection _connections new(); private string _activeNodeId null; // 当前高亮节点IDnull 表示无高亮 private readonly Font _nodeFont new Font(Microsoft YaHei, 9f, FontStyle.Regular); private readonly Pen _defaultPen new Pen(Color.FromArgb(100, 100, 100), 1.5f); private readonly Brush _textBrush Brushes.Black; public FlowChartPanel() { this.DoubleBuffered true; // 关键防闪烁 this.ResizeRedraw true; this.AutoScroll true; this.Padding new Padding(20); // 留白避免节点贴边 } // 外部设置流程图数据通常在窗体 Load 事件中调用 public void SetFlowChart(ListNode nodes, ListConnection connections) { _nodes nodes ?? new ListNode(); _connections connections ?? new ListConnection(); _activeNodeId null; this.Invalidate(); // 触发重绘 } // 设置当前激活节点例如业务状态走到“审核中”就传入 review public void SetActiveNode(string nodeId) { _activeNodeId nodeId; this.Invalidate(); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); var g e.Graphics; g.SmoothingMode SmoothingMode.AntiAlias; // 1. 先画连线底层 DrawConnections(g); // 2. 再画节点上层避免连线盖住节点文字 DrawNodes(g); // 3. 最后画高亮边框顶层 DrawActiveHighlight(g); } private void DrawConnections(Graphics g) { foreach (var conn in _connections) { var fromNode _nodes.FirstOrDefault(n n.Id conn.FromId); var toNode _nodes.FirstOrDefault(n n.Id conn.ToId); if (fromNode null || toNode null) continue; // 简单直线连接中心点实际项目可用贝塞尔曲线见第 5 章 var fromCenter new Point( fromNode.Location.X fromNode.Size.Width / 2, fromNode.Location.Y fromNode.Size.Height / 2); var toCenter new Point( toNode.Location.X toNode.Size.Width / 2, toNode.Location.Y toNode.Size.Height / 2); g.DrawLine(_defaultPen, fromCenter, toCenter); } } private void DrawNodes(Graphics g) { foreach (var node in _nodes) { var rect new Rectangle(node.Location, node.Size); var brush GetNodeBrush(node.Type); var pen GetNodePen(node.Type); // 根据 NodeType 绘制不同形状 switch (node.Type) { case NodeType.Start: g.FillEllipse(brush, rect); g.DrawEllipse(pen, rect); break; case NodeType.Process: g.FillRectangle(brush, rect); g.DrawRectangle(pen, rect); break; case NodeType.Decision: // 菱形用 GraphicsPath 绘制 var path CreateDiamondPath(rect); g.FillPath(brush, path); g.DrawPath(pen, path); break; case NodeType.End: // 双线矩形先画外框再画内框 g.FillRectangle(brush, rect); g.DrawRectangle(pen, rect); var innerRect Rectangle.Inflate(rect, -3, -3); g.DrawRectangle(new Pen(Color.Red, 1f), innerRect); break; } // 居中绘制文字 var textSize g.MeasureString(node.Text, _nodeFont); var textX node.Location.X (node.Size.Width - (int)textSize.Width) / 2; var textY node.Location.Y (node.Size.Height - (int)textSize.Height) / 2; g.DrawString(node.Text, _nodeFont, _textBrush, textX, textY); } } private GraphicsPath CreateDiamondPath(Rectangle rect) { var path new GraphicsPath(); var center new Point(rect.Left rect.Width / 2, rect.Top rect.Height / 2); var points new Point[] { new Point(center.X, rect.Top), new Point(rect.Right, center.Y), new Point(center.X, rect.Bottom), new Point(rect.Left, center.Y) }; path.AddPolygon(points); return path; } private void DrawActiveHighlight(Graphics g) { if (_activeNodeId null) return; var activeNode _nodes.FirstOrDefault(n n.Id _activeNodeId); if (activeNode null) return; var rect new Rectangle(activeNode.Location, activeNode.Size).Inflate(4, 4); using var highlightPen new Pen(Color.FromArgb(255, 215, 0), 2.5f) { DashStyle DashStyle.Dot }; g.DrawRectangle(highlightPen, rect); } private Brush GetNodeBrush(NodeType type) type switch { NodeType.Start new SolidBrush(Color.FromArgb(200, 255, 200)), NodeType.Process new SolidBrush(Color.FromArgb(230, 240, 255)), NodeType.Decision new SolidBrush(Color.FromArgb(255, 230, 200)), NodeType.End new SolidBrush(Color.FromArgb(255, 220, 220)), _ Brushes.White }; private Pen GetNodePen(NodeType type) type switch { NodeType.Start new Pen(Color.Green, 2f), NodeType.Process new Pen(Color.Blue, 2f), NodeType.Decision new Pen(Color.Orange, 2f), NodeType.End new Pen(Color.Red, 2f), _ _defaultPen }; // 鼠标点击返回被点击的节点ID用于后续交互如弹出详情、跳转窗体等 protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (e.Button ! MouseButtons.Left) return; var clickedNode _nodes .FirstOrDefault(n { var rect new Rectangle(n.Location, n.Size); return rect.Contains(e.Location); }); if (clickedNode ! null) { OnNodeClicked?.Invoke(this, new NodeClickedEventArgs(clickedNode.Id)); } } public event EventHandlerNodeClickedEventArgs OnNodeClicked; } public class NodeClickedEventArgs : EventArgs { public string NodeId { get; } public NodeClickedEventArgs(string nodeId) NodeId nodeId; }这段代码实现了绘制闭环数据输入 → 重绘触发 → 分层绘制连线→节点→高亮→ 交互响应。它不依赖任何设计器所有逻辑都在 C# 文件中VS2015 编译即用。SetFlowChart()是你的“数据注入口”SetActiveNode()是你的“状态驱动口”OnNodeClicked是你的“用户反馈口”——三个接口覆盖了 90% 的 winform项目案例 中流程图使用场景。3. 在主窗体中集成并驱动流程图绑定业务状态与用户操作光有控件还不够必须让它“活”起来。这一章教你如何在Form1.cs中完成三件事初始化流程图数据、响应业务状态变化、处理用户点击反馈。我们以一个典型的“用户管理模块流程图”为例从“添加用户”开始经“验证邮箱”判断分支走向“启用账户”或“发送提醒邮件”最终结束。3.1 构建流程图数据用硬编码模拟真实业务结构不要一上来就搞 JSON 加载或数据库读取。先用 C# 对象字面量写出流程确保图形能正确渲染。这是最可靠的起步方式。// Form1.cs —— 在 Load 事件中初始化 private void Form1_Load(object sender, EventArgs e) { // 1. 定义节点位置坐标单位为像素建议用网格对齐如 100x100 间距 var nodes new ListNode { new Node(start, 添加用户, new Point(200, 50), new Size(120, 40), NodeType.Start), new Node(validate, 验证邮箱格式, new Point(200, 150), new Size(120, 40), NodeType.Process), new Node(email_ok?, 邮箱有效, new Point(200, 250), new Size(120, 60), NodeType.Decision), new Node(enable, 启用账户, new Point(80, 380), new Size(120, 40), NodeType.Process), new Node(remind, 发送提醒邮件, new Point(320, 380), new Size(120, 40), NodeType.Process), new Node(end, 完成, new Point(200, 500), new Size(120, 40), NodeType.End) }; // 2. 定义连线FromId → ToId顺序无关 var connections new ListConnection { new Connection(start, validate), new Connection(validate, email_ok?), new Connection(email_ok?, enable), // “是”分支默认左连 new Connection(email_ok?, remind), // “否”分支默认右连 new Connection(enable, end), new Connection(remind, end) }; // 3. 注入到 FlowChartPanel flowChartPanel1.SetFlowChart(nodes, connections); // 4. 初始化时高亮起始节点 flowChartPanel1.SetActiveNode(start); }注意email_ok?是菱形判断节点我们约定“左连”为Yes路径“右连”为No路径。这个约定将在第 5 章升级为带箭头标注的显式分支但现阶段足够支撑业务理解。3.2 响应业务状态用一个方法驱动全流程高亮假设你有一个UserService类其中AddUserAsync()方法执行后返回UserStatus枚举。你不需要改服务层只需在 UI 层做一层映射// Form1.cs private async void btnAddUser_Click(object sender, EventArgs e) { try { // 模拟业务调用 var status await UserService.AddUserAsync(txtEmail.Text); // 将业务状态映射为流程图节点ID string nextNodeId status switch { UserStatus.EmailValid email_ok?, UserStatus.EmailInvalid remind, UserStatus.AccountEnabled enable, UserStatus.Finished end, _ start }; // 驱动流程图高亮 flowChartPanel1.SetActiveNode(nextNodeId); // 可选滚动到目标节点提升体验 ScrollToNode(nextNodeId); } catch (Exception ex) { MessageBox.Show($操作失败{ex.Message}); } } private void ScrollToNode(string nodeId) { var node flowChartPanel1.GetNodes().FirstOrDefault(n n.Id nodeId); if (node ! null) { var pt new Point(node.Location.X - flowChartPanel1.AutoScrollPosition.X, node.Location.Y - flowChartPanel1.AutoScrollPosition.Y); flowChartPanel1.AutoScrollPosition pt; } }这里的关键是解耦业务逻辑UserService完全不知道流程图的存在UI 层只负责“翻译”状态 → 节点ID。这种模式让你未来可以轻松替换为 BPMN 引擎而 UI 层几乎不用改。3.3 处理用户点击让流程图成为操作入口不止是展示很多开发者把流程图当静态图片这是巨大浪费。OnNodeClicked事件让你把每个节点变成功能入口// Form1.cs —— 在构造函数或 Load 中订阅 public Form1() { InitializeComponent(); flowChartPanel1.OnNodeClicked FlowChartPanel1_OnNodeClicked; } private void FlowChartPanel1_OnNodeClicked(object sender, NodeClickedEventArgs e) { switch (e.NodeId) { case start: ShowAddUserDialog(); break; case validate: MessageBox.Show(正在后台校验邮箱格式..., 提示); break; case email_ok?: MessageBox.Show(双击此处可手动切换验证结果, 操作提示); break; case enable: if (MessageBox.Show(确认立即启用该账户, 启用账户, MessageBoxButtons.YesNo) DialogResult.Yes) { EnableAccount(); } break; case remind: SendReminderEmail(); break; case end: MessageBox.Show(用户添加流程已全部完成, 成功); break; } }血泪经验别在OnNodeClicked里写耗时操作如数据库查询。务必用async/await包裹或弹出进度窗体。否则 UI 线程卡死流程图瞬间变“幻灯片”。4. 避坑指南Winform FlowChart 开发中 4 个高频翻车现场与后悔药流程图看似简单但在 Winform 实际落地中有四个坑几乎每个新手都会踩且排查耗时远超编码本身。以下是我在 12 个 winform项目案例 中总结的“必踩清单”按现象→原因→解决三步给出可立即复现的修复方案。4.1 现象流程图一闪而过刚画完就消失或缩放后内容错位原因Panel默认AutoScroll false且未设置ResizeRedraw true导致窗体大小变化时OnPaint不被触发更隐蔽的是DoubleBuffered falseGDI 绘制过程被 Windows 擦除重绘造成闪烁与残留。解决在FlowChartPanel构造函数中强制设置三属性public FlowChartPanel() { this.DoubleBuffered true; // ✅ 必开防闪烁 this.ResizeRedraw true; // ✅ 必开窗体缩放时自动重绘 this.AutoScroll true; // ✅ 必开支持大流程图滚动 this.Padding new Padding(20); // ✅ 推荐避免节点紧贴边缘被截断 }提示DoubleBuffered在 Winform 中对自定义绘制是“生死开关”不是“性能优化”。VS2015 默认为false必须显式设为true。4.2 现象点击节点毫无反应OnNodeClicked事件从未触发原因Panel子控件如Label、Button覆盖在FlowChartPanel上或FlowChartPanel的Enabled false或鼠标事件被父容器如TabControl的 TabPage吞掉。解决分三步排查检查层级在设计器中右键FlowChartPanel→ “置于顶层”确保无其他控件遮挡检查属性确认flowChartPanel1.Enabled true且Visible true检查容器若FlowChartPanel放在TabPage中需确保TabPage的Enabled true且TabControl未禁用。终极验证法临时在OnMouseDown中加断点若断点不命中说明事件根本没传到控件——90% 是层级或Enabled问题。4.3 现象高亮边框SetActiveNode不显示或显示位置偏移原因SetActiveNode()调用后未调用Invalidate()或Invalidate()被放在异步回调中但未Invoke回 UI 线程或节点坐标计算错误如用了ClientRectangle而非Location。解决确保SetActiveNode()内部有this.Invalidate()代码中已实现若在Task.Run或BackgroundWorker中调用SetActiveNode()必须用this.Invoke(...)// 错误跨线程调用 Task.Run(() { flowChartPanel1.SetActiveNode(end); }); // 正确安全跨线程 Task.Run(() { var nextId GetNextNodeId(); this.Invoke((MethodInvoker)delegate { flowChartPanel1.SetActiveNode(nextId); }); });坐标计算只认Node.Location绝对坐标不要用PointToClient()转换——OnPaint中所有绘制都基于控件自身坐标系。4.4 现象中文节点文字显示为方块□□□或字体模糊原因Graphics.DrawString()使用了不支持中文的字体如Arial或未设置TextRenderingHint.ClearTypeGridFit。解决字体必须用Microsoft YaHei、SimSun等中文字体且在FlowChartPanel中统一声明private readonly Font _nodeFont new Font(Microsoft YaHei, 9f, FontStyle.Regular);渲染质量在OnPaint开头添加g.TextRenderingHint TextRenderingHint.ClearTypeGridFit; // ✅ 关键 g.InterpolationMode InterpolationMode.HighQualityBicubic;注意TextRenderingHint.AntiAliasGridFit在高 DPI 下可能失效ClearTypeGridFit是 Winform 中文渲染的“后悔药”。5. 进阶技巧给流程图加上箭头、分支标注与导出能力让它真正服务于开发与交付做到上一章你已经拥有了一个可运行、可交互、可维护的流程图。但要让它真正成为 winform项目案例 中的交付资产还需三个关键增强带方向箭头的连线、判断节点的 Yes/No 分支标注、一键导出 PNG 供文档使用。这些不是炫技而是解决“客户要留痕”、“测试要截图”、“新人要看懂”三大刚需。5.1 绘制带箭头的连线用 GraphicsPath 替代简单 Line原版DrawLine只能画直线无法表达流向。我们升级为GraphicsPath支持箭头端点// FlowChartPanel.cs —— 替换 DrawConnections 方法 private void DrawConnections(Graphics g) { var arrowHeadSize 8f; foreach (var conn in _connections) { var fromNode _nodes.FirstOrDefault(n n.Id conn.FromId); var toNode _nodes.FirstOrDefault(n n.Id conn.ToId); if (fromNode null || toNode null) continue; var fromCenter new Point( fromNode.Location.X fromNode.Size.Width / 2, fromNode.Location.Y fromNode.Size.Height / 2); var toCenter new Point( toNode.Location.X toNode.Size.Width / 2, toNode.Location.Y toNode.Size.Height / 2); // 计算从 fromCenter 到 toCenter 的单位向量 float dx toCenter.X - fromCenter.X; float dy toCenter.Y - fromNode.Location.Y; float len (float)Math.Sqrt(dx * dx dy * dy); if (len 1) continue; dx / len; dy / len; // 起点微调从 fromNode 边缘出发避免箭头扎进节点 var startX fromCenter.X dx * (fromNode.Size.Width / 2.5f); var startY fromCenter.Y dy * (fromNode.Size.Height / 2.5f); // 终点微调停在 toNode 边缘留出箭头空间 var endX toCenter.X - dx * (toNode.Size.Width / 2.5f); var endY toCenter.Y - dy * (toNode.Size.Height / 2.5f); // 创建带箭头的路径 var path new GraphicsPath(); path.StartFigure(); path.AddLine(startX, startY, endX, endY); // 添加箭头三角形 var arrowX1 endX - arrowHeadSize * dx arrowHeadSize * dy * 0.5f; var arrowY1 endY - arrowHeadSize * dy - arrowHeadSize * dx * 0.5f; var arrowX2 endX - arrowHeadSize * dx - arrowHeadSize * dy * 0.5f; var arrowY2 endY - arrowHeadSize * dy arrowHeadSize * dx * 0.5f; path.AddLine(endX, endY, arrowX1, arrowY1); path.AddLine(arrowX1, arrowY1, arrowX2, arrowY2); path.AddLine(arrowX2, arrowY2, endX, endY); path.CloseFigure(); g.DrawPath(_defaultPen, path); } }这段代码让每条连线都带标准箭头视觉上明确表达“数据/控制流方向”。箭头大小、偏移量均可调适配不同密度的流程图。5.2 为判断节点添加 Yes/No 分支标注用小标签提升可读性菱形节点的左右分支光靠位置不够直观。我们在连线旁加小标签// FlowChartPanel.cs —— 在 DrawConnections 后追加 DrawBranchLabels private void DrawBranchLabels(Graphics g) { foreach (var conn in _connections) { var fromNode _nodes.FirstOrDefault(n n.Id conn.FromId); var toNode _nodes.FirstOrDefault(n n.Id conn.ToId); if (fromNode null || toNode null || fromNode.Type ! NodeType.Decision) continue; // 只为判断节点的连线添加标签 var fromCenter new Point( fromNode.Location.X fromNode.Size.Width / 2, fromNode.Location.Y fromNode.Size.Height / 2); var toCenter new Point( toNode.Location.X toNode.Size.Width / 2, toNode.Location.Y toNode.Size.Height / 2); // 计算连线角度决定标签位置左侧 or 右侧 var angle Math.Atan2(toCenter.Y - fromCenter.Y, toCenter.X - fromCenter.X) * 180 / Math.PI; string label (toCenter.X fromCenter.X) ? No : Yes; // 粗略判断左为 No右为 Yes // 在连线中点偏移处绘制标签 var midX (fromCenter.X toCenter.X) / 2; var midY (fromCenter.Y toCenter.Y) / 2; var offset 15f; var labelX midX (float)(Math.Cos(angle * Math.PI / 180) * offset); var labelY midY (float)(Math.Sin(angle * Math.PI / 180) * offset); g.DrawString(label, _nodeFont, Brushes.Gray, labelX, labelY); } }调用位置在OnPaint的DrawConnections()后、DrawNodes()前插入DrawBranchLabels(g);。效果是每条从菱形发出的线旁自动出现 “Yes” 或 “No” 小字无需人工标注。5.3 一键导出 PNG让流程图成为可交付文档资产客户要截图测试要留证直接提供“导出”按钮用Bitmap截取整个流程图区域// FlowChartPanel.cs —— 新增导出方法 public Bitmap ExportToBitmap() { // 计算整个流程图所需画布尺寸含滚动区域 var contentWidth _nodes.Max(n n.Location.X n.Size.Width) 40; var contentHeight _nodes.Max(n n.Location.Y n.Size.Height) 40; var bitmap new Bitmap(contentWidth, contentHeight); using (var g Graphics.FromImage(bitmap)) { g.Clear(Color.White); g.SmoothingMode SmoothingMode.AntiAlias; g.TextRenderingHint TextRenderingHint.ClearTypeGridFit; // 临时关闭滚动绘制全图 var oldAutoScroll this.AutoScroll; this.AutoScroll false; this.DrawToBitmap(bitmap, new Rectangle(0, 0, contentWidth, contentHeight)); this.AutoScroll oldAutoScroll; } return bitmap; } // Form1.cs —— 导出按钮事件 private void btnExport_Click(object sender, EventArgs e) { using var bitmap flowChartPanel1.ExportToBitmap(); var saveDlg new SaveFileDialog { Filter PNG 图像|*.png|所有文件|*.*, FileName FlowChart_ DateTime.Now.ToString(yyyyMMdd_HHmmss) .png }; if (saveDlg.ShowDialog() DialogResult.OK) { bitmap.Save(saveDlg.FileName, ImageFormat.Png); MessageBox.Show($已导出至{saveDlg.FileName}, 导出成功); } }提示ExportToBitmap()返回Bitmap对象你还可以进一步用ImageSharp或System.Drawing.Common添加水印、缩放、批量导出 PDF——但对 90% 的 winform项目案例PNG 已完全够用。我从 2013 年用 VS2012 写第一个 Winform 流程图控件开始到今天在 VS2022 中维护十几个遗留系统这条路径反复验证过不求大而全但求稳而准不拼第三方库但重可调试性不迷信设计器但信代码即文档。你看到的每一行DrawRectangle、每一个SetActiveNode调用都是从客户会议室、测试 Bug 报告、上线凌晨三点的紧急修复中熬出来的。它可能不如 ComfyUI 的 qwen-image流程图 那样炫酷但它能在一台没装 .NET Core 的 Windows 7 工控机上安静地跑完十年。希望帮到你。本文还有配套的精品资源点击获取