news 2026/9/25 1:47:00

Delphi 12.3 集成 ReportMachine v3.67 实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Delphi 12.3 集成 ReportMachine v3.67 实战指南

简介:本资源是面向Delphi与BCB(Borland C++ Builder)开发者的高级报表控件ReportMachine v3.67完整源码包,专为适配Delphi 12.3环境优化,适用于需快速构建可定制化报表的桌面应用开发场景,尤其适合中高级RAD开发者进行二次开发与深度集成。压缩包共425个文件,含188个Pascal源码(.pas)、74个窗体描述(.dfm)、54个资源文件(.res)及16个多语言说明文档(.txt),涵盖安装指南、许可协议、FAQ、历史版本记录及压缩算法支持说明;整体体积仅967KB,结构紧凑、模块清晰。已有142人下载学习,开发者可直接编译调试、修改核心逻辑、扩展导出/打印功能,并借助MAKEALL.BAT等批处理脚本一键构建不同IDE版本的组件包,显著提升报表模块开发效率与兼容性适配能力。

1. ReportMachine v3.67 是什么?不是“又一个报表控件”,而是 Delphi/BCB 工程师在 Win32 原生项目里能真正甩开 IDE 报表设计器、手写逻辑、自由嵌入 PDF 导出与数据库绑定的轻量级渲染引擎

ReportMachine v3.67.Full.Source.Delphi.BCB.055684.rar 这个文件名不是随便堆砌的——它直指一个被低估但仍在大量存量工业软件、医疗终端、金融后台中默默服役的报表方案:它不是 VCL 的“皮肤插件”,也不是 FMX 的跨平台幻影,而是专为 Delphi 12(含 12.3)、C++Builder(BCB)原生 Win32 应用深度打磨的源码级报表渲染器。它不依赖第三方运行时(如 .NET 或 Java),不强制绑定特定数据库驱动,更不把用户锁死在拖拽式设计器里。你写TReport实例,调Prepare,再Print或ExportToPDF,全程可控、可调试、可嵌入多线程导出逻辑。很多工程师直到项目上线前一周才发现:IDE 自带的 QuickReport 在处理 5000 行带子带的主从报表时内存暴涨、字体错位;而 ReportMachine 同样数据量下稳定输出 PDF,且.dfm里只存 3 行对象声明,其余全靠代码控制——这才是它在 Delphi 12.3 环境下仍被反复检索、下载、集成的真实原因:它解决的是“报表逻辑必须和业务层耦合”这个老问题,而不是“怎么让报表看起来更炫”。适合正在维护或重构 Win32 桌面客户端、需要导出合规 PDF(如发票、检验单)、又不愿引入新框架(如 REST+Web 报表)的实战派开发者。别被“v3.67”这个版本号骗了——它不是过时产物,而是经过 15 年以上真实产线锤炼的稳定分支,尤其适配 Delphi 12 的 Unicode 字符集与高 DPI 缩放逻辑。

2. 从 RAR 解压到工程可编译:三步完成 ReportMachine v3.67 在 Delphi 12.3 + BCB 中的最小集成

ReportMachine 的源码包结构看似松散,实则暗藏适配逻辑。.rar文件解压后你会看到Source、Demos、Lib三个主目录,但直接往lib路径加搜索路径会翻车——因为 v3.67 对 Delphi 12.3 的 RTL 变更做了针对性修补,这些补丁分散在Source\Delphi\和Source\BCB\下的.pas文件里,必须按顺序加载。下面是你在 Delphi 12.3 IDE 中真正能跑通的三步法,跳过任何一步都可能触发EAccessViolation或Class not registered错误。

2.1 解压与目录重定向:为什么不能直接 Add to Library Path?

ReportMachine v3.67 的源码组织是“按编译器分叉”的:Source\Delphi\下是.pas文件,Source\BCB\下是.cpp和.h,而Lib\目录里存放的是预编译的.dcu(Delphi)和.obj(BCB)——但这些预编译文件仅适用于 Delphi 10.4 或 BCB 10.3,在 Delphi 12.3 上直接引用会导致Unit not found: 'RMClasses'。正确做法是彻底忽略Lib\,只用Source\Delphi\下的 PAS 文件,并手动修正 RTL 兼容性。我一般会新建一个RM_v367_D123文件夹,把Source\Delphi\*.pas全部复制进去,再创建RM_v367_D123\Include存放自定义头文件(后面会用到)。

2.2 修改关键单元以适配 Delphi 12.3 的 Unicode 与高 DPI

Delphi 12.3 默认启用{$IFDEF UNICODE}且TCanvas.Font.PixelsPerInch行为变更,ReportMachine 原始RMClasses.pas中的TRMFont.GetHeight方法会返回错误值,导致所有文本垂直居中失效。必须定位并修改以下两处:

// RMClasses.pas 第 1243 行附近,原代码: function TRMFont.GetHeight: Integer; begin Result := Round(FSize * 1.3); // 玄学系数,旧版有效,D123 下崩 end; // 改为(适配 D123 的 Font.Height 计算逻辑): function TRMFont.GetHeight: Integer; begin if Assigned(FCanvas) and (FCanvas.Handle <> 0) then Result := FCanvas.TextHeight('A') // 直接走 GDI 测量,绕过缩放陷阱 else Result := Round(FSize * 1.2); // 安全兜底 end;

另一处是RMExportPDF.pas中的TRMPDFDocument.AddFont方法,它硬编码调用AddTrueTypeFont,但在 Delphi 12.3 的System.SysUtils中AnsiString已被弃用。需替换为:

// RMExportPDF.pas 第 3892 行,原代码: FontName := AnsiString(FTTFPath); // 改为(显式 UTF8 转换): FontName := UTF8Encode(FTTFPath);

提示:这两处修改是 Delphi 12.3 下 ReportMachine 渲染中文不乱码、PDF 字体不丢失的唯一可靠路径。网上流传的“改 Compiler Version Directive”方案在 D123 中已失效,会引发Invalid typecast。

2.3 创建最小可运行 Demo 工程:验证是否真集成成功

新建一个 VCL Forms Application,删掉默认Form1,新建一个TRMReport实例(非窗体组件,纯代码创建)。这是验证集成是否成功的黄金标准——不依赖任何.dfm设计器资源:

// Unit1.pas uses RMClasses, RMExportPDF, RMRender, SysUtils, Classes; procedure GenerateSimpleReport; var Rpt: TRMReport; Page: TRMPage; Text: TRMText; begin Rpt := TRMReport.Create(nil); try Rpt.Title := 'Test Report'; // 必须显式创建 Page,否则 Prepare 失败 Page := TRMPage.Create(Rpt); Rpt.Pages.Add(Page); // 添加文本控件(注意:坐标单位是 Twip,不是 Pixel) Text := TRMText.Create(Page); Text.Left := 1440; // 1 inch = 1440 twips Text.Top := 1440; Text.Width := 5760; // 4 inches Text.Height := 288; // 0.2 inch Text.Caption := 'Hello from Delphi 12.3 + ReportMachine v3.67'; Page.Objects.Add(Text); // 关键:Prepare 前必须设置 Printer 或 Exporter Rpt.Prepare; // 此刻才真正解析布局 // 导出 PDF 验证(比 Print 更快,无需物理打印机) Rpt.ExportToPDF('test_output.pdf'); ShowMessage('PDF generated: test_output.pdf'); finally Rpt.Free; end; end;

这段代码必须能无报错执行并生成test_output.pdf,且打开后文字清晰、无方块乱码、无偏移——这才是集成成功的铁证。如果卡在Rpt.Prepare抛EInvalidOperation,大概率是RMClasses.pas的GetHeight没改对;如果 PDF 里全是空白,检查RMExportPDF.pas的AddFont是否用了UTF8Encode。

3. 数据库绑定实战:用 TADOQuery + ReportMachine 实现“动态主从报表”,避开 DataSet 事件陷阱

ReportMachine 不自带数据感知控件(如TRMDataSet),但它提供TRMDataBand和TRMDataField两个核心类,让你用纯代码绑定任意TDataSet。很多人栽在“为什么子带数据不刷新”上——根本原因不是 ReportMachine 的 Bug,而是 Delphi 12.3 的TDataSet事件链在多线程或快速切换记录时被截断。下面是一个经产线验证的、支持主从关系(如订单头 + 订单明细)的绑定模式,它绕过了OnCalcFields和AfterScroll的脆弱性。

3.1 主从数据准备:用 TADOQuery 构建父子 DataSet,但禁用 AutoOpen

// DataModule.pas uses ADODB, RMClasses, RMData; type TDataModule1 = class(TDataModule) ADOConnection1: TADOConnection; ADOQueryMaster: TADOQuery; // 订单头 ADOQueryDetail: TADOQuery; // 订单明细 private fMasterDS: TDataSet; fDetailDS: TDataSet; public procedure LoadData(const OrderID: string); end; procedure TDataModule1.LoadData(const OrderID: string); begin // 关键:禁用 AutoOpen,手动控制 Open/Close 时机 ADOQueryMaster.Close; ADOQueryMaster.Parameters.ParamByName('OrderID').Value := OrderID; ADOQueryMaster.Open; // 主数据先加载 ADOQueryDetail.Close; ADOQueryDetail.Parameters.ParamByName('OrderID').Value := OrderID; ADOQueryDetail.Open; // 子数据后加载 fMasterDS := ADOQueryMaster; fDetailDS := ADOQueryDetail; end;

注意:TADOQuery的CachedUpdates必须设为False,否则 ReportMachine 在Prepare时读不到实时记录。

3.2 报表代码层绑定:用 TRMDataBand.OnBeforePrint 替代传统 OnCalcFields

ReportMachine 的TRMDataBand不监听TDataSet.AfterScroll,而是靠OnBeforePrint事件在每次打印前拉取当前记录。这是最稳定的方式:

// ReportUnit.pas uses RMClasses, RMData, Data.DB; procedure TReportUnit.BuildReport; var Rpt: TRMReport; MasterBand, DetailBand: TRMDataBand; MasterText, DetailText: TRMText; begin Rpt := TRMReport.Create(nil); try // 主带:绑定订单头 MasterBand := TRMDataBand.Create(Rpt); MasterBand.DataSet := DataModule1.fMasterDS; MasterBand.OnBeforePrint := MasterBandBeforePrint; Rpt.Pages[0].Objects.Add(MasterBand); // 子带:绑定订单明细,注意 ParentBand 设置 DetailBand := TRMDataBand.Create(Rpt); DetailBand.DataSet := DataModule1.fDetailDS; DetailBand.ParentBand := MasterBand; // 关键!建立父子关系 DetailBand.OnBeforePrint := DetailBandBeforePrint; Rpt.Pages[0].Objects.Add(DetailBand); Rpt.Prepare; Rpt.ExportToPDF('order_report.pdf'); finally Rpt.Free; end; end; procedure MasterBandBeforePrint(Sender: TObject; var PrintIt: Boolean); var Band: TRMDataBand; begin Band := Sender as TRMDataBand; // 此刻 Band.DataSet.Position 是有效的 // 直接读字段,不依赖事件链 CaptionText.Caption := Format('订单号:%s,客户:%s', [Band.DataSet.FieldByName('OrderNo').AsString, Band.DataSet.FieldByName('CustomerName').AsString]); end; procedure DetailBandBeforePrint(Sender: TObject; var PrintIt: Boolean); var Band: TRMDataBand; begin Band := Sender as TRMDataBand; // 子带自动跟随主带的 MasterField(需提前设置 Band.MasterField := 'OrderID') // 这里直接读明细字段 DetailText.Caption := Format('%s × %d 件,单价:%s', [Band.DataSet.FieldByName('ProductName').AsString, Band.DataSet.FieldByName('Qty').AsInteger, Band.DataSet.FieldByName('UnitPrice').AsString]); end;

3.3 动态字段宽度适配:解决中文长文本撑破列宽的“血泪经验”

ReportMachine 的TRMText.Width是固定值,但中文字段长度不可控(如客户名称从 5 字到 30 字)。硬编码Width := 5760会导致换行错乱。我的做法是:在OnBeforePrint中动态计算文本像素宽度,再反推 Twip 值:

function GetTextWidthPx(const S: string; Font: TFont): Integer; var Canvas: TCanvas; begin Canvas := TCanvas.Create; try Canvas.Font := Font; Result := Canvas.TextWidth(S); finally Canvas.Free; end; end; procedure MasterBandBeforePrint(Sender: TObject; var PrintIt: Boolean); var Band: TRMDataBand; TextWidthTwips: Integer; begin Band := Sender as TRMDataBand; // 动态计算客户名称宽度(单位:pixel) TextWidthTwips := GetTextWidthPx( Band.DataSet.FieldByName('CustomerName').AsString, CaptionText.Font ) * 1440 div GetDeviceCaps(GetDC(0), LOGPIXELSX); // pixel → twips // 设置安全上限(避免过宽) CaptionText.Width := Min(TextWidthTwips, 8640); // max 6 inches CaptionText.Caption := Band.DataSet.FieldByName('CustomerName').AsString; end;

这个函数在OnBeforePrint中调用,确保每行都按实际内容重算宽度。虽然有轻微性能开销,但比手动调WordWrap或AutoSize更精准——后者在 ReportMachine 中常因 DPI 缩放失效。

4. 常见问题排查:Delphi 12.3 + ReportMachine v3.67 的 5 个高频翻车点与硬核解法

ReportMachine v3.67 在 Delphi 12.3 下的稳定性远超预期,但新手常因忽略底层机制而反复踩坑。以下是我在 3 个工业项目中记录的真实问题清单,每一条都对应一个具体现象、根本原因和可复制的修复动作。不要跳过这一章——90% 的“ReportMachine 不工作”都能在这里找到答案。

4.1 现象:PDF 导出后中文显示为方块,但预览窗体里正常

原因:ReportMachine v3.67 默认使用RMExportPDF.pas内置的AddTrueTypeFont加载系统字体,但在 Delphi 12.3 的高 DPI 模式下,GetStockObject(DEFAULT_GUI_FONT)返回的字体句柄无法被 PDF 引擎正确序列化。
解决:强制指定中文字体路径,禁用系统字体回退:

// 在 ExportToPDF 前添加 Rpt.PDFExport.Fonts.AddFontFromFile('C:\Windows\Fonts\msyh.ttc', 'Microsoft YaHei'); // 必须用 .ttc 而非 .ttf Rpt.PDFExport.DefaultFontName := 'Microsoft YaHei';

注意:msyh.ttc是微软雅黑的 TrueType Collection,比msyh.ttf更兼容 PDF 嵌入。若部署环境无此字体,需随程序分发.ttc文件并用相对路径加载。

4.2 现象:调用Rpt.Print时弹出“Printer not found”,但系统里明明有默认打印机

原因:ReportMachine 的TRMPrinter类在 Delphi 12.3 中调用GetDefaultPrinterAPI 时未处理PRINTER_ENUM_LOCAL标志变更,导致枚举失败。
解决:绕过 ReportMachine 的打印机选择,直接传入物理打印机名:

Rpt.Printer.PrinterName := 'HP LaserJet MFP M426fdw'; // 硬编码打印机名(从控制面板复制) Rpt.Printer.UseDefaultPrinter := False; Rpt.Print;

血泪经验:不要信Rpt.Printer.PrinterIndex,它在 D123 中返回 -1 是常态。用WinSpool.pas的EnumPrinters手动枚举并缓存列表才是正解。

4.3 现象:TRMDataBand子带数据重复打印 3 遍,且每遍内容相同

原因:TRMDataBand.ParentBand未正确设置,或MasterField字段名拼写错误(如写成'OrderID '多了个空格),导致 ReportMachine 无法建立主从关联,退化为对每个主记录都遍历全部子记录。
解决:在BuildReport中加入断言校验:

Assert(Assigned(DetailBand.ParentBand), 'DetailBand.ParentBand must be assigned!'); Assert(DetailBand.ParentBand = MasterBand, 'ParentBand mismatch!'); Assert(DetailBand.MasterField <> '', 'MasterField cannot be empty!'); Assert(DetailBand.DataSet.FindField(DetailBand.MasterField) <> nil, Format('MasterField "%s" not found in detail dataset', [DetailBand.MasterField]));

提示:Assert在 Release 版本中会被忽略,但开发期开启{$IFDEF DEBUG}可瞬间定位字段名错误。

4.4 现象:报表中图片(TRMImage)显示模糊,放大后锯齿严重

原因:ReportMachine 默认将TPicture转为TBitmap时使用StretchBlt,在 Delphi 12.3 的高 DPI 下未启用SetStretchBltMode的HALFTONE模式。
解决:重载TRMImage.Draw方法,插入高质量缩放:

// 在 TRMImage.Create 后调用 TRMImage(MyImage).Draw := DrawHighQualityImage; procedure DrawHighQualityImage(Sender: TObject; Canvas: TCanvas; Rect: TRect); var BM: TBitmap; begin BM := TBitmap.Create; try BM.Assign(TRMImage(Sender).Picture.Graphic); SetStretchBltMode(Canvas.Handle, HALFTONE); StretchBlt(Canvas.Handle, Rect.Left, Rect.Top, Rect.Right - Rect.Left, Rect.Bottom - Rect.Top, BM.Canvas.Handle, 0, 0, BM.Width, BM.Height, SRCCOPY); finally BM.Free; end; end;

4.5 现象:多线程导出 PDF 时偶发Access violation at address...,堆栈指向RMRender.pas第 2187 行

原因:ReportMachine 的TRMRenderer是非线程安全的,Prepare过程中共享了全局GCache字典。Delphi 12.3 的TThread.Synchronize在高并发下触发竞态。
解决:为每个线程创建独立TRMReport实例,并禁用缓存:

// 线程 Execute 方法中 var Rpt: TRMReport; begin Rpt := TRMReport.Create(nil); try Rpt.CacheEnabled := False; // 关键!关闭全局缓存 Rpt.Prepare; Rpt.ExportToPDF(Format('report_%d.pdf', [ThreadID])); finally Rpt.Free; end; end;

注意:CacheEnabled := False是多线程场景下的强制要求,否则GCache会成为性能瓶颈和崩溃源头。

5. 进阶技巧:用 ReportMachine v3.67 实现“条件样式”与“跨页页眉复用”,告别 Designer 依赖

ReportMachine 的强大不在拖拽界面,而在你能用几行 Pascal 代码实现 Designer 做不到的事。比如:同一份报表模板,在不同客户环境下自动切换配色方案;或当明细数据跨页时,第二页及以后的页眉自动显示“续表”字样。这些功能不需要改源码,只需理解TRMPage.OnBeforePrint和TRMText.OnGetText的执行时序。

5.1 条件样式:根据字段值动态改变字体颜色与背景

ReportMachine 没有Style属性,但TRMText.OnGetText事件在每次渲染前触发,此时你可以修改Sender的Font.Color和Brush.Color:

procedure SetupConditionalStyle; var DetailText: TRMText; begin DetailText := TRMText.Create(DetailBand); DetailText.OnGetText := DetailTextGetText; DetailBand.Objects.Add(DetailText); end; procedure DetailTextGetText(Sender: TObject; var Text: string); var TextObj: TRMText; Qty: Integer; begin TextObj := Sender as TRMText; // 获取当前明细记录的 Qty 字段 Qty := DetailBand.DataSet.FieldByName('Qty').AsInteger; // 动态设色 if Qty > 100 then begin TextObj.Font.Color := clRed; TextObj.Brush.Color := $00F0F0F0; // 浅灰背景 end else if Qty > 50 then begin TextObj.Font.Color := clOrange; TextObj.Brush.Color := clWhite; end else begin TextObj.Font.Color := clBlack; TextObj.Brush.Color := clWhite; end; // 设置显示文本(注意:Text 参数是输出值,不是输入值) Text := DetailBand.DataSet.FieldByName('ProductName').AsString; end;

关键点:OnGetText的Text参数是输出参数,你赋值给它,ReportMachine 就渲染那个字符串;而TextObj.Font.Color等属性是状态参数,影响本次渲染的外观。二者必须在同一事件中设置,否则样式不生效。

5.2 跨页页眉复用:用 TRMPage.OnBeforePrint 实现“续表”智能识别

ReportMachine 的TRMPage没有内置“是否首页”标志,但你可以通过Rpt.CurrentPageNumber和Rpt.PageCount判断:

procedure SetupContinuationHeader(Rpt: TRMReport); var Page: TRMPage; HeaderText: TRMText; begin for Page in Rpt.Pages do begin Page.OnBeforePrint := PageBeforePrint; // 添加页眉文本(位置固定在顶部) HeaderText := TRMText.Create(Page); HeaderText.Left := 1440; HeaderText.Top := 720; // 0.5 inch from top HeaderText.Width := 5760; HeaderText.Height := 288; HeaderText.Alignment := taCenter; HeaderText.Font.Size := 10; Page.Objects.Add(HeaderText); end; end; procedure PageBeforePrint(Sender: TObject; var PrintIt: Boolean); var Page: TRMPage; Rpt: TRMReport; HeaderText: TRMText; begin Page := Sender as TRMPage; Rpt := Page.Owner as TRMReport; // 查找本页的页眉文本(按创建顺序,假设它是第一个 TRMText) if Page.Objects.Count > 0 then begin HeaderText := Page.Objects[0] as TRMText; if Rpt.CurrentPageNumber = 1 then HeaderText.Caption := '订单明细表' else HeaderText.Caption := Format('订单明细表(续表 - 第 %d 页)', [Rpt.CurrentPageNumber]); end; end;

这个技巧的价值在于:它让报表具备“语义感知”能力。当用户导出 PDF 时,第一页是“订单明细表”,第二页自动变成“订单明细表(续表 - 第 2 页)”,无需人工干预,也无需 Designer 中设置多个页眉。

5.3 PDF 元数据注入:让生成的 PDF 具备可检索的文档属性

ReportMachine v3.67 的TRMPDFDocument允许写入标准 PDF 元数据,这对归档系统至关重要:

procedure InjectPDFMetadata(Rpt: TRMReport; const DocTitle, Author, Keywords: string); var PDFDoc: TRMPDFDocument; begin PDFDoc := Rpt.PDFExport.PDFDocument; PDFDoc.Title := DocTitle; PDFDoc.Author := Author; PDFDoc.Subject := 'Business Report'; PDFDoc.Keywords := Keywords; PDFDoc.Creator := 'Delphi 12.3 + ReportMachine v3.67'; PDFDoc.Producer := 'ReportMachine PDF Exporter'; end; // 调用示例 InjectPDFMetadata(Rpt, Format('订单报表 - %s', [DateToStr(Now)]), 'Finance Department', 'order,invoice,export');

验证方法:用 Adobe Acrobat 打开 PDF → 文件 → 属性 → 描述,确认元数据已写入。这能让企业文档管理系统(如 SharePoint 或 NAS 搜索)准确索引报表。

我坚持在每个项目里用OnBeforePrint替代 Designer 绑定,用OnGetText实现条件样式,用PDFDocument注入元数据——不是为了炫技,而是因为这些代码能放进 Git,能 Code Review,能写单元测试,而 Designer 里的连线和属性设置,永远只是黑匣子。ReportMachine v3.67 的价值,从来不在它多像 Crystal Reports,而在于它让你用 Pascal 写报表逻辑时,依然保有对每一行代码的完全掌控。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 1:46:13

小米R3G刷Breed不死Bootloader完整教程:从开发版SSH到救砖避坑

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

作者头像 李华
网站建设 2026/9/25 1:46:05

ISO 11898与SAE J1939协议分层解析:CAN总线通信核心区别

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

作者头像 李华
网站建设 2026/9/25 1:45:55

STM32开发环境搭建全攻略:CubeMX与Keil5安装配置避坑指南

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

作者头像 李华
网站建设 2026/9/25 1:45:42

眼动模块进阶开发:如何扩展新Blockly积木块与自定义图片ID

眼动模块进阶开发&#xff1a;如何扩展新Blockly积木块与自定义图片ID 【免费下载链接】eye-tracking-module 源师兄扩展项目: 眼动模块 | 由源师兄组织创建 项目地址: https://gitcode.com/yuanshixiong/eye-tracking-module 源师兄眼动模块是一个通过串口控制圆形屏幕…

作者头像 李华