FastReport批量打印实战:从循环直打到队列调度的稳定方案
1. 批量打印这件事为什么值得单独拎出来说做过企业信息化的人都有一个共识报表模块往往是项目里最不起眼、但上线后挨骂最多的部分。单张打印谁都会做拖一个控件、绑一个数据源、点一下预览完事。可一旦业务方甩过来一句“这批发货单一共三百多张今天下班前全部打出来每张还要对应各自的明细”很多人的第一反应就是——写个循环慢慢打呗。结果呢打印机吭哧吭哧吐纸程序界面卡死用户以为死机了又点了一次于是同一批单据被重复打印两遍。更麻烦的是中间某一张卡纸了你根本不知道打到第几张只能从头再来。这就是FastReport批量打印这个主题真正要解决的问题不是“能不能打”而是“怎么稳定、可控、可追溯地打一大批”。FastReport 是一套在 .NET 生态里被广泛使用的报表工具它的核心价值在于把“数据”和“呈现”解耦——你用设计器画好模板运行时把数据塞进去它负责渲染和输出。批量打印则是把这个能力放大到“一次处理成百上千份文档”的场景。适合读这篇内容的人大致有三类一是正在做 ERP、WMS、MES 这类系统、被批量单据折磨的后端或全栈开发二是需要给客户交付“一键打印”功能的实施人员三是想搞清楚打印任务到底该怎么调度、怎么避免踩坑的技术负责人。我前后在几个不同类型的项目里都做过批量打印从最早的“循环调 Print()”到后来自己封装打印队列中间踩的坑足够写满一页纸。下面就把这套东西从头到尾拆开讲包括方案怎么选、参数怎么算、代码怎么写、出问题怎么查。2. 批量打印的整体设计与方案选型2.1 先搞清楚“批量”到底难在哪很多人低估了批量打印的复杂度觉得无非是把单张打印放进 for 循环。但只要数量上到几十上百下面这几个问题就会同时冒出来。第一是资源占用。每份报表在渲染时都会创建页面对象、加载字体、解析表达式如果一次性把几百份全部 Prepare 到内存里内存曲线会直接飙上去配置差一点的机器可能直接 OutOfMemory。第二是打印机的物理限制。打印机有缓冲区你连续往 Spooler打印后台处理服务里塞几百个任务它处理不过来就会排队队列一长任何一张出错都会拖累后面全部。第三是用户的可控性。用户需要知道“打到哪了”“能不能暂停”“打错了能不能只重打某几张”这些都不是一个裸循环能给的。所以批量打印的本质是一个带状态管理的任务调度问题而不是一个单纯的 API 调用问题。想清楚这一点后面的方案选型才有依据。2.2 三种主流实现路径的取舍在实际项目里批量打印大致有三条路可走我按适用场景从轻到重排一下。方案核心做法优点缺点适用场景循环直打for 循环里逐份 Prepare Print实现最快代码量最小无进度、无中断、易卡界面数量小于 20 的临时需求合并成单文档把多份报表合并成一个多页文档再打只提交一个打印任务队列干净无法按份控制出错难定位内容同构、无需分份的场景打印队列调度自建队列逐份渲染、逐份提交、带进度回调可控、可中断、可重打实现复杂需要处理线程生产级批量打印我个人的建议很明确只要数量可能超过 20或者用户会盯着进度看就直接上队列调度方案。前两种方案看着省事但后期加需求时改起来比重写还痛苦。下面重点讲队列调度这条路。2.3 为什么选择“逐份渲染 逐份提交”队列调度里有个关键决策是先把所有报表渲染好再统一提交还是渲染一份提交一份我选后者理由有三个。一是内存友好。渲染一份、提交一份、释放一份内存占用基本是恒定的不会随数量增长。二是进度可感知。每提交一份就能回调一次进度用户界面上的进度条是真实的不是假的动画。三是错误可隔离。某一份数据有问题导致渲染失败只影响这一份不会让整批任务崩掉我们可以把它标记为失败并继续后面的。代价是提交次数多Spooler 队列会比较长。但这个问题可以通过控制提交节奏来缓解——比如每提交若干份就短暂让出一下给 Spooler 喘息的机会。这个细节后面会讲。3. 核心细节解析与实操要点3.1 报表对象的生命周期管理FastReport 里最容易被误用的就是 Report 对象的复用。很多人图省事全局只 new 一个 Report每次打印就换一下数据源再 Print。这在单张场景下没问题但在批量场景下会出大问题。原因是 Report 对象在 Prepare 之后会持有大量渲染状态如果你不 Reset上一份的页眉页脚、变量值、甚至某些缓存都可能残留到下一份。我实测过复用对象时偶尔会出现“第二份报表带着第一份的页码”这种诡异现象。正确的做法是每份报表独立创建、独立释放。伪代码大概是这样foreach (var item in batchItems) { using (var report new Report()) { report.Load(templatePath); report.RegisterData(item.DataSource, Data); report.Prepare(); report.PrintPrepared(); } }using保证每份用完就释放虽然创建对象有开销但相比内存泄漏和状态污染这点开销完全值得。如果模板加载本身很慢可以把模板文件缓存成字节数组每次从字节流 Load省掉磁盘 IO。3.2 数据源的绑定方式选择批量打印的数据通常有两种组织方式一种是每份报表对应一个独立的数据集另一种是所有数据在一个大表里、靠分组拆成多份。这两种在 FastReport 里的处理方式完全不同。如果是独立数据集直接给每份报表 RegisterData 就行简单直接。如果是大表分组就要用 FastReport 的分组Group功能在模板里按某个字段分组然后设置“每组新页”。这种方式的好处是只 Prepare 一次就能得到多页文档但缺点是所有数据都在内存里数据量大时压力大。我的经验是数据量小于 500 行、且每份结构完全一致时用分组否则用独立数据集逐份处理。分组方式还有个隐藏坑——如果某个分组字段的值有重复或为空分组结果可能不符合预期一定要在数据源头做去重和校验。3.3 打印参数的设置要点批量打印时打印机的选择、纸张、份数这些参数如果处理不好会出现“打到默认打印机去了”“纸张不对”这类问题。FastReport 的 PrintPrepared 支持传入 PrintSettings我一般会显式指定打印机名称而不是依赖系统默认。var settings new PrintSettings { PrinterName 指定打印机名称, Copies 1, ShowDialog false }; report.PrintPrepared(settings);这里有个关键注意点ShowDialog false必须设置否则每份报表都会弹一次打印对话框批量场景下就是灾难。另外如果模板里设置了纸张大小而打印机实际纸盒里装的是另一种纸打印任务会卡在队列里等待人工干预所以模板纸张和物理纸张必须一致这个要在部署时和现场确认清楚。提示打印机名称建议做成配置项不要硬编码在代码里。不同现场打印机命名不一样硬编码会导致换台机器就跑不起来。3.4 进度回调与取消机制的设计用户最关心的两个问题“打到第几张了”和“我不想打了”。这两个需求必须在设计阶段就考虑进去不能等用户提了再加。进度回调用事件或委托实现即可每提交一份就触发一次把当前序号和总数传出去。取消机制则用一个CancellationToken或者一个简单的布尔标志位在循环里每轮检查一次。注意取消的粒度——是“立即停止”还是“打完当前这份再停”我建议后者因为强行中断正在提交的打印任务可能导致 Spooler 状态异常。for (int i 0; i items.Count; i) { if (token.IsCancellationRequested) break; // 渲染并提交第 i 份 OnProgressChanged(i 1, items.Count); }这个循环一定要跑在后台线程里不能占用 UI 线程否则界面照样卡死。用Task.Run包一层是最简单的做法。4. 实操过程与核心环节实现4.1 环境准备与依赖确认动手之前先把环境理清楚。FastReport 在 .NET 里有不同的发行包桌面端和 Web 端用的库不一样先确认你的项目类型。桌面端WinForms/WPF直接用 FastReport.NETWeb 端要考虑服务端渲染后输出 PDF 或直接调打印服务。依赖方面除了 FastReport 本体通常还需要确认目标机器上装了对应的打印机驱动并且打印后台处理服务正常运行。我遇到过现场服务被禁用导致打印任务提交后毫无反应的情况排查了半天才发现是系统服务的问题。4.2 模板设计阶段的三个约定模板是批量打印的地基设计阶段就要和业务方约定好三件事。第一模板里不要写死份数。份数由代码控制模板只管单份的呈现。第二数据字段命名要统一。批量场景下数据来源可能不止一个字段名统一能省掉大量映射代码。第三留出足够的边距。批量打印经常遇到打印机走纸偏移边距留窄了内容会被裁掉我一般建议上下左右各留 5mm 以上。模板设计完先用单份数据跑通预览确认渲染没问题再进入批量环节。这个顺序不能反否则批量出问题时你分不清是模板问题还是调度问题。4.3 打印队列的完整实现把前面的点串起来一个可用的批量打印队列大概包含这几个部分任务列表、后台执行线程、进度上报、取消支持、错误收集。public class BatchPrintJob { public ListPrintItem Items { get; set; } public string TemplatePath { get; set; } public string PrinterName { get; set; } public Actionint, int OnProgress { get; set; } public ListPrintError Errors { get; set; } new ListPrintError(); public void Execute(CancellationToken token) { for (int i 0; i Items.Count; i) { if (token.IsCancellationRequested) break; try { using (var report new Report()) { report.Load(TemplatePath); report.RegisterData(Items[i].DataSource, Data); report.Prepare(); report.PrintPrepared(new PrintSettings { PrinterName PrinterName, ShowDialog false }); } } catch (Exception ex) { Errors.Add(new PrintError { Index i, Message ex.Message }); } OnProgress?.Invoke(i 1, Items.Count); } } }这段代码的核心思想是单份失败不影响整批。catch 住异常、记录到 Errors 列表、继续下一份最后把失败清单反馈给用户让用户决定是否重打这几份。这比“一出错就整批中断”友好太多。4.4 提交节奏的控制前面提到 Spooler 队列会变长解决办法是在循环里加一个节流。具体做法是每提交 N 份就Thread.Sleep一小段时间给 Spooler 处理的时间。N 和睡眠时长没有标准答案要根据打印机速度调。我的经验值是每 10 份 sleep 200ms对大多数激光打印机够用。如果是针式打印机这种慢速设备可能要每 5 份 sleep 500ms。这个参数最好做成可配置现场调试时方便调整。注意节流不是越多越好。sleep 太多会让整批任务耗时明显变长用户会以为程序卡住了。找到“不堆积”和“不拖沓”之间的平衡点是批量打印调优的关键。4.5 打印结果的核对批量打印最怕的是“打完了但不知道对不对”。所以任务结束后一定要给用户一个明确的核对界面成功多少份、失败多少份、失败的是哪几份、失败原因是什么。如果业务允许还可以在每份报表上打一个流水号或二维码方便事后核对。这个流水号建议由代码生成并写入数据源而不是靠打印机自动编号因为打印机编号在卡纸重打时会乱。5. 常见问题与排查技巧实录5.1 打印任务堆积在队列里不动这是最常见的问题表现是提交了几十份任务但打印机只打了几张就停了。排查顺序是这样的先看打印后台处理服务是否正常再看打印机是否处于脱机或暂停状态最后看是不是某一份任务卡住了导致后面全部排队。如果是某一份卡住通常是这份的数据有问题比如图片路径失效、字体缺失。解决办法是在渲染阶段就做校验把有问题的份提前挑出来不要让它进入打印队列。5.2 内存持续增长直到崩溃如果发现批量打印过程中内存只涨不降八成是 Report 对象没释放或者数据源没解绑。检查两点一是 Report 是否用了 using 或显式 Dispose二是 RegisterData 注册的数据源是否在释放前解绑。FastReport 的数据源如果持有大对象不解绑会一直占着内存。5.3 打印内容错位或串页串页的典型原因是 Report 对象复用导致状态残留前面讲过每份独立创建就能解决。错位则多半是模板边距和打印机走纸精度的问题可以通过调整模板边距、或者在打印机驱动里校准走纸来改善。5.4 常见问题速查表现象可能原因排查方向解决手段队列堆积不动服务异常/打印机脱机/单份卡住查服务、查打印机状态、定位卡住份修复服务、恢复打印机、剔除问题份内存持续增长对象未释放/数据源未解绑检查 Dispose 和解绑逻辑每份独立创建并释放内容串页Report 对象复用检查是否全局单例改为每份新建打印错位边距不足/走纸偏差检查模板边距加大边距、校准打印机重复打印用户重复点击/无防重检查按钮是否禁用加防重锁、任务去重5.5 几个只有踩过才知道的坑第一个坑预览和打印的渲染结果可能不一致。有些打印机驱动对某些字体的处理方式和预览不同导致打印出来字体变了、换行了。解决办法是尽量用打印机自带的标准字体少用特殊字体。第二个坑网络打印机比本地打印机更容易出问题。网络抖动会导致任务提交失败所以网络打印机的场景下提交失败要能自动重试重试次数建议 2 到 3 次。第三个坑大批量任务不要一次性全塞进队列。我试过一次性提交 500 份结果 Spooler 直接卡死只能重启服务。后来改成节流提交就再没出过这个问题。第四个坑取消操作要能真正停下来。如果取消标志只在循环开头检查而单份渲染很慢用户点了取消还要等很久。更好的做法是在渲染的关键节点也检查取消标志让响应更及时。6. 性能调优与规模化建议6.1 渲染性能的优化方向批量打印的耗时大头在渲染不在打印本身。优化渲染有几个方向一是精简模板去掉不必要的图片和复杂表达式二是预编译表达式FastReport 支持把表达式编译成委托能明显提速三是复用数据连接如果数据来自数据库批量场景下应该一次性查出来而不是每份查一次。我做过对比同样的 200 份报表优化前渲染耗时约 90 秒精简模板加预编译后降到 40 秒左右效果还是很明显的。6.2 大规模场景的架构调整如果批量规模上到几千份单机队列调度就不够用了。这时候要考虑把打印任务拆分到多台机器或者引入服务端统一渲染、客户端只负责输出的架构。服务端渲染的好处是性能可控、便于监控客户端只做轻量的打印提交。这种架构下任务的分发、状态同步、失败重试都需要额外设计复杂度会上升一个量级。所以我的建议是能用单机队列解决的就别上分布式除非规模确实到了单机扛不住的程度。6.3 日志与监控的落地批量打印一定要有日志记录每份任务的开始时间、结束时间、结果状态。日志不用很复杂写到一个文本文件或数据库表里就行。有了日志出问题时才能快速定位是第几份、什么时间、什么原因失败的。监控方面可以统计每批任务的平均耗时、失败率这些指标能帮你提前发现打印机老化、数据质量下降等问题。7. 我在实际项目里的一些体会批量打印这个功能技术难度其实不算高但它特别考验开发者对边界情况的考虑。单张打印跑通只完成了 30%剩下的 70% 全在批量场景下的异常处理、进度反馈、资源管理上。我最大的体会是永远不要假设打印一定成功。打印机可能没纸、可能卡纸、可能脱机、可能驱动异常每一种情况都要有对应的处理。把失败当成常态来设计代码的健壮性会完全不一样。另外一个小技巧如果业务方对打印顺序有要求一定要在数据源头就排好序不要指望打印队列按提交顺序输出。因为 Spooler 在某些情况下会调整任务顺序尤其是多打印机环境下。排序这件事越早做越省心。最后再提一句批量打印的测试一定要用真实打印机跑模拟器和虚拟打印机测不出走纸、卡纸、队列堆积这些问题。上线前拿真实设备跑一遍全流程能省掉上线后大量的救火时间。

相关新闻

C++实战:从CSV解析到疫情数据统计管线

C++实战:从CSV解析到疫情数据统计管线

1. 从一份CSV到一套分析管线:这个项目的真实定位很多人看到“用C分析疫情数据”这个标题,第一反应是:为什么不用Python?pandas几行代码就能搞定的事,何必用C折腾?这个问题我当初也问过自己。后来真正动手做…

2026/10/13 17:21:20 阅读更多 →
TDSC 2025区块链论文速读指南:从三看三问到笔记沉淀

TDSC 2025区块链论文速读指南:从三看三问到笔记沉淀

干过区块链方向研究的人应该都有这种体验:周末翻三篇论文,合上PDF之后能复述出主线的没几篇,更别提把方法复现出来了。尤其是TDSC这种CCF A类老牌期刊,随便一篇就是三十页起步,形式化模型、安全证明、系统实验一个不落…

2026/10/13 15:46:49 阅读更多 →
【Spring AI Alibaba】百炼工作流 + Multi-Agent 搭建电商智能导购:把 Base URL 改到 TaoToken 的完整配置

【Spring AI Alibaba】百炼工作流 + Multi-Agent 搭建电商智能导购:把 Base URL 改到 TaoToken 的完整配置

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

2026/10/12 15:04:45 阅读更多 →

最新新闻

mmaction2 Charades-STA 数据集准备指南:从标注下载到 C3D 特征组织,再到 DRN 视频定位训练

mmaction2 Charades-STA 数据集准备指南:从标注下载到 C3D 特征组织,再到 DRN 视频定位训练

人工智能计算机视觉深度学习 【免费下载链接】mmaction2 OpenMMLabs Next Generation Video Understanding Toolbox and Benchmark 项目地址: https://gitcode.com/gh_mirrors/mm/mmaction2 点击查看 免费下载 本文围绕 mmaction2 仓库中 Charades-STA 数据准备文档…

2026/10/13 17:59:40 阅读更多 →
QT框架下旅行模拟查询系统的开发与优化实践

QT框架下旅行模拟查询系统的开发与优化实践

1. 项目概述与核心价值这个旅行模拟查询系统本质上是一个集成了地图可视化、路径规划和信息查询的桌面端应用。不同于简单的网页版工具,基于QT框架的C实现让它具备了本地化运行的高效性,特别适合需要处理大量地理数据或复杂计算场景的旅行规划需求。我去…

2026/10/13 17:59:40 阅读更多 →
向量库索引膨胀致C盘0字节?从原理到重建的完整排查指南

向量库索引膨胀致C盘0字节?从原理到重建的完整排查指南

C盘红了、0字节,数据库直接罢工,这段经历让我每次想起来都后背发凉。我跑的这个微客AI 项目其实不大,原始数据只有 75M,本来以为撑死占个几百兆磁盘空间,结果向量库硬生生把索引撑到了 48.6G,直接把 C 盘干…

2026/10/13 17:59:40 阅读更多 →
SSM毕设实战:农村宅基地管理系统设计与运行全解析

SSM毕设实战:农村宅基地管理系统设计与运行全解析

简介:这是一套基于SSM框架开发的农村宅基地管理系统,属于完整的JavaWeb工程项目,适合计算机相关专业学生用于毕业设计或课程设计实训。系统围绕宅基地审批业务,设计了村民、村委、镇府、县委、管理员五个角色端,覆盖村…

2026/10/13 17:59:40 阅读更多 →
C语言学习心得:从基础语法到项目实战的完整成长路线

C语言学习心得:从基础语法到项目实战的完整成长路线

1. 初识C语言:为什么值得下一个"笨功夫"1.1 一个现实的选择:C语言到底能做什么说起我和C语言的结缘,很多人可能不信——一开始我根本不是奔着"成为程序员"去的。那时候我只是想弄明白,电脑上那些程序到底是怎么跑起来的。同学给我推…

2026/10/13 17:59:40 阅读更多 →
WSL 下 CPU 版 Caffe 与 Faster R-CNN 环境搭建实战

WSL 下 CPU 版 Caffe 与 Faster R-CNN 环境搭建实战

1. 为什么要在 WSL 里折腾 CPU 版 Caffe 和 Faster R-CNN先把场景说清楚。你手里大概率是一台 Windows 笔记本或者台式机,显卡要么是核显,要么是 N 卡但驱动环境不想动,总之短期内不打算碰 CUDA。可你又想跑通一个经典的目标检测流程&#xf…

2026/10/13 17:58:35 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/12 18:11:17 阅读更多 →