JasperReports报表模板:Band布局、PDF中文与JSON数据源避坑 简介《Jasper报表制作小技巧》是一份面向IT开发与报表制作人员的docx实操笔记聚焦Jaspersoft/Jasper报表从结构认知到常见问题处理的入门与进阶场景。内容围绕Title、Page Header、Column Header、Detail、Column Footer、Page Footer、Summary等报表区域展开并整理了JSON数据源接入、PDF中文字段设置、手工补录字段、明细分组、序号列变量、自定义纸张大小及交叉报表控件等实用技巧可帮助读者快速理解band布局并减少实际制表时的反复调试。资源包共1个docx文件约599KB篇幅集中、便于按主题查阅。目前已有242人学习或浏览适合需要借助现成笔记查漏补缺、提升Jasper报表搭建效率的开发者和报表维护人员参考。1. Jasper 报表制作里最容易翻车的环节是模板交接Jasper 报表制作里最容易翻车的环节是模板交接。前段时间接手一个仓储系统里的入库明细模板设计器里预览一切正常编译导出 PDF 后中文全变成方框翻遍每个控件的字体设置也找不到原因——问题根本不在设计器而在 PDF 那条独立的渲染链路上。这类工具的门槛从来不在拖控件而在于搞清楚每个 Band 到底在哪一页出现、字段类型跟数据源对不对得上、导出格式走的是哪条渲染路径。这份技巧集覆盖的正是这些Title 到 Summary 的九个分区、JSON 数据源接法、PDF 中文字体、分组与序号列、Crosstab 交叉表、自定义纸张面单以及模板上传后的打印配置与 SQL 条件绑定。适合已经能把报表拖出个样子、但一导出或一上线就出问题的同学。2. Band 与页面布局九个分区的触发条件、快捷键与自定义纸张报表做得好不好八成取决于设计之前有没有把 Band 的边界想清楚。同一个控件放在 Page Header 和 Column Header 里最终打印出来的位置天差地别Summary 放错区域末页会凭空多出一张空白纸。这一章先把九个分区的语义钉死再讲设计器里怎么少用鼠标最后落到面单场景的自定义纸张。2.1 九个 Band 各在哪一页出现高度又如何影响分页先把对照关系摆出来这是后面所有布局操作的地基。很多人把 Title 当成页眉用结果第二页标题消失回头改了半天样式其实是选错了 Band。Band出现位置出现次数常见用途高频误用Title第一页最上方整份报表仅一次报表大标题、Logo、打印时间当成页眉导致第二页无标题Page Header每页上部首页在 Title 之下每页一次页码、创建人、系统名把列名写在这里分组后列名不跟组走Column Header每列的上部每列一次列名称把明细数据放进来Detail正文区每页重复明细行如收发货记录高度设得过大导致每页只排两三行Column Footer每列的下部每列一次列级小计、列说明与 Page Footer 混用小计重复累加Page Footer每页最下端每页一次页级小计、页码 X/Y高度残留导致末页空白Summary最后一页 Detail 之后整份报表一次全表合计、Crosstab 交叉表放在其他区域分页异常Group Header每个分组之前每组一次组说明、组标志字段高度置 0 后又想显示组名Group Footer每个分组之后每组一次组内小计未勾选重复打印跨页后组尾丢失Last Page Footer最后一页最下端仅最后一页签收栏、审核人与 Page Footer 同时启用互相挤压有个反直觉的点Band 高度设为 0 并不等于这块区域被删除它仍然参与布局计算只是不占垂直空间。做快递面单时之所以敢把 Title、Page Header、Column Header 全设成 0只留 Detail靠的就是这个特性——区域还在只是不显示。反过来如果某个 Band 高度是 0 但里面还挂着会撑开的文本框Stretch Type 设为 Stretch 之类它依然会被内容顶开这时候你把属性面板翻烂也找不到原因。Band 的总高度还直接决定分页。页面可用高度 pageHeight - 上边距 - 下边距所有 Band 高度之和超过这个值JasperReports 就会切页于是出现「明明内容只有三行却多出一张空白页」。遇到这种问题不用怀疑玄学按顺序把 Column Footer、Page Footer、Summary 的高度逐个归零试一遍两分钟就能定位。Detail 还有一个 Split Type 属性控制明细行跨页时怎么断Prevent 表示不拆行整行挪到下一页适合有边框的表格Stretch 允许拉伸拆分适合纯文本长内容。面单和票据类模板建议用 Prevent避免一张单据被腰斩。2.2 设计器布局与快捷键把 Outline 和 Properties 用到顺手Jaspersoft Studio 是基于 Eclipse RCP 构建的所以它继承了一整套 Eclipse 的通用键位熟悉 Java IDE 的人几乎零成本迁移。下面这几个是我每天都在用的注意具体绑定以本机 keymap 为准换过键位方案的可能对不上。快捷键作用使用场景Ctrl S保存当前模板改完属性立刻存避免编译时用的是旧文件Ctrl Z / Ctrl Y撤销 / 重做拖错控件位置时救急Ctrl C / V / X复制 / 粘贴 / 剪切批量复制同一格式的 textFieldCtrl Shift F格式化源码手改 jrxml 后对齐缩进便于比对 diffCtrl 鼠标滚轮缩放设计区面单这种小尺寸纸张必用Ctrl M最大化当前视图在 Outline 和设计器之间来回切F5刷新外部改了 JSON 数据文件后重新加载Ctrl F查找替换在 source 页签里批量改字段名比快捷键更重要的是三个面板的用法。左下角的 Outline报表大纲是整份模板的骨架Band 的增删、fields 和 variables 的管理、group 的创建全在这里右键完成比在设计区点来点去稳得多。右下角的 Properties 是属性黑匣子同一个控件它会根据当前选中的是 Band、是 textField 还是整份报表切换出完全不同的属性集找不到某个属性时先确认你选中的对象对不对。再就是底部的 Problems 视图编译报错全靠它很多人预览失败就反复点预览按钮其实错误信息早就写在那里了。预览和编译我一般不走快捷键切到 Preview 页签工具栏上直接点 PDF 或 HTML 图标比记键位快。要注意预览用的是本地渲染跟最终导出 PDF 走的不是同一条链路这也是后面中文字体问题的根源。2.3 自定义纸张与面单布局Units 选 mm非必要 Band 全部归零快递面单、标签、小票这类场景A4 那套页面格式完全用不上必须走自定义纸张。操作路径是新建完报表初始页面后点页面最外框的外侧空白处选中整份 Report在属性面板找到 Edit Page FormatFormat 下拉选 Custom然后在下方 Units 里选 mm再填宽高和页边距。这里有个必须说清楚的换算问题。Units 只是设计器的显示单位jrxml 文件内部的坐标始终按像素存储所以你把单位切到 mm 输入 100×150 之后回到设计区看到的坐标数字会变大那是正常的单位换算不是设置丢了。反过来如果你直接手改 jrxml 里的 pageWidth 和 pageHeight就得自己算像素px mm ÷ 25.4 × 72。常用的几档我整理成表省得每次掏计算器。面单规格mmjrxml pageWidth × pageHeightpx适用场景100 × 150283 × 425标准快递面单100 × 180283 × 510带商品明细的面单76 × 130215 × 368标签机小标签210 × 99595 × 281三等分A4横版单据页面尺寸定完接着做减法。面单只保留 Detail 区域其余 Band 高度全部设为 0这一步在属性面板里逐个改别漏 Page Footer——它是末页空白纸的第一嫌疑人。页边距也建议压到 0 或者 2mm 以内面单本来就小边距吃掉 10mm 就没地方放内容了。!-- 面单模板骨架只留 Detail其余区域高度归零 -- jasperReport namewaybill_demo pageWidth283 pageHeight425 columnWidth283 leftMargin0 rightMargin0 topMargin0 bottomMargin0 whenNoDataTypeAllSectionsNoDetail title height0/ pageHeader height0/ columnHeader height0/ detail band height425 !-- 面单正文条码、收件人、路由信息等控件放在这里 -- /band /detail columnFooter height0/ pageFooter height0/ summary height0/ /jasperReport这段骨架的关键在三个参数pageWidth 和 pageHeight 用像素给值leftMargin 到 bottomMargin 全压 0 保证内容贴边whenNoDataType 设成 AllSectionsNoDetail 是为了数据为空时也能出一张空白面单打印场景经常需要预打空白单。Detail 的 band height 直接给成整页高度是因为面单内容固定一页不需要自动分页。如果面单里有条码控件记得宽度要留够条码在 PDF 里是按比例渲染的容器窄了会被裁掉一半。3. JSON 数据源落地字段定义、中文 PDF 字体与类型对齐Jasper 报表的数据源选择很灵活JDBC、JavaBean、JSON、CSV 都能接。以 JSON 作为常用数据源时标准顺序是添加数据源主要供预览用没有数据源也能生成报表、新建报表页、关联数据源、添加数据字段、添加并修改字段标签、属性设置、分组、预览、编译。这条链路看着长真正卡人的只有两个点字段类型对不上和 PDF 里中文不见了。3.1 从 JSON 文件到 fields一份能直接跑的样例先准备一份结构清晰的 JSON字段名尽量跟数据库查询出来的列名保持一致后面接 SQL 数据源时能省一轮重命名。嵌套层级不要超过两层层级一深fields 里的路径表达式就容易写错。{ rows: [ { code: RK202401001, shipFromName: 某供应商, closedAt: 2024-01-05 10:12:00, itemCode: IT-001, itemName: 六角螺栓, totalQty: 120, openQty: 20, warehouseCode: WH01 }, { code: RK202401002, shipFromName: 某贸易公司, closedAt: 2024-01-05 15:40:00, itemCode: IT-002, itemName: 平垫圈, totalQty: 300, openQty: 0, warehouseCode: WH02 } ] }数据源配置在 Data Adapters 里新建一个 JSON File 类型选中文件编码必须显式写成 UTF-8否则中文会变成乱码。JSON Source 那一栏填数组节点的路径上面这份就填 rows如果整个文件本身就是数组留空或者填一个点号即可。这个路径填错的表现很直接预览时一行数据都不出控制台提示找不到节点比字段类型错误好排查得多。在 Outline 里右键 fields 新建字段把 JSON 里的每个键配一个 fieldName 必须跟键名逐字符一致包括大小写。Class 按值的实际类型选字符串给 java.lang.String整数给 java.lang.Integer小数给 java.math.BigDecimal布尔给 java.lang.Boolean。日期这里要特别注意JSON 里的 2024-01-05 10:12:00 是个字符串如果直接选成 java.sql.Timestamp填充时会抛类型转换异常稳妥的做法是保持 String在 textField 的 pattern 里用yyyy-MM-dd HH:mm做格式化输出。报表里引用字段写成$F{code}、$F{shipFromName}这种形式。如果数据源不是平台托管的而是自己在 Java 侧填充代码大致是这样// 用 JSON 文件作为数据源填充并导出 PDF InputStream jsonStream new FileInputStream(data/receipt.json); // 第二个参数是数组节点路径必须和 Data Adapter 里填的一致 JsonDataSource ds new JsonDataSource(jsonStream, rows); MapString, Object params new HashMap(); params.put(reportTitle, 入库商品明细表); JasperPrint print JasperFillManager.fillReport(compiledReport, params, ds); JasperExportManager.exportReportToPdfFile(print, out/receipt.pdf);这段代码要说明两处JsonDataSource 的第二个构造参数就是前面说的数组路径两处不一致会出现「设计器里能预览、程序里跑出一张空表」的情况fillReport 的第一个参数必须是编译后的 .jasper 文件如果你改完 jrxml 没重新编译就直接跑 Java用的还是旧模板这种坑排查起来非常耗时间我一般会在构建脚本里固定加一步预编译。3.2 PDF 中文丢失的根因与 pdfStyle 全局配置原始资料里有一句描述特别准确不设置的话报表预览里能显示中文但上传到系统里字就没了。这不是玄学是两条渲染链路用了不同的字体来源。设计器预览走的是本地 Java/Swing 渲染用的是操作系统字体导出 PDF 走的是 PDF 生成库只有两种字体能用——PDF 标准 14 字体或者显式嵌入的字体文件。中文不在标准字体里没嵌入就是空白或者方框。最省事的解法是设一个全局默认样式让所有文本框自动带上中文 PDF 字体属性。在 jrxml 的 source 界面里把 style 节点插到 jasperReport 根节点下、所有 band 之前!-- 全局 PDF 样式isDefaulttrue 会作用到所有未单独指定样式的 textElement -- style namepdfStyle isDefaulttrue pdfFontNameSTSong-Light pdfEncodingUniGB-UCS2-H isPdfEmbeddedtrue/三个参数各管一段。pdfFontName 指定中文字体名STSong-Light 是亚洲字体包里的宋体兼容性最好pdfEncoding 用 UniGB-UCS2-H对应简体中文的 Unicode 编码写成 GBK 之类在部分 PDF 阅读器里会缺字isPdfEmbedded 设 true 表示把字体子集嵌进 PDF文件会大一点但换来的是在任何机器上打开都不掉字。等效的另一种做法是在每个中文控件的 Advanced 面板里单独设或者对单个 textElement 写textElement pdfFontNameSTSong-Light pdfEncodingUniGB-UCS2-H/。标签一多就很啰嗦而且新加的控件容易漏所以才更推荐全局 style。这里有个边界必须提醒从 6.x 版本开始官方逐步用字体扩展font extension替代 pdfFontName 这套老属性较新的 Studio 里这几个字段在属性面板中可能已经不可见。如果你的环境属于这种情况走另一条路——在 Studio 的字体偏好里添加 TTF 字体文件比如常见的开源中文字体导出成 jar 放进 classpath 并在 jrxml 里引用字体名。这条路更重但跨平台表现更稳。另外就算设了 pdfStyle 还是空白先检查 classpath 里有没有亚洲字体支持包Maven 依赖 net.sf.jasperreports:jasperreports-fonts缺了它 STSong-Light 根本加载不到样式写得再对也没用。3.3 漏字段、类型错、name 对不上三条硬规则用 JSON 数据源做报表最常见的返工是字段漏了。原始资料给的做法很实在在左下角报表大纲里右键 fields手工新增所需字段同时在属性框里重命名 Name 和描述选择对应字段类型注意 name 必须与数据库查询字段保持一致。第一条硬规则是名称严格一致。Name 不只是给人看的标签它是运行时的取值键区分大小写多一个下划线都取不到值。我习惯直接从 JSON 或 SQL 查询结果里复制字段名绝不手敲。第二条是类型选对。类型选错不会报错只会静默出错这是最恶心的地方。字符串类型的数值字段做 Sum 计算结果是字符串拼接合计栏会显示 120 300 这种鬼东西日期字段类型选错pattern 格式化不生效显示成一串时间戳。JSON 值形态应选 Class选错的典型表现IT-001java.lang.String—120java.lang.IntegerSum 变字符串拼接1.25java.math.BigDecimal金额精度丢失出现 0.300000000000000042024-01-05 10:12:00java.lang.String pattern格式化不生效或填充时抛类型转换异常true / falsejava.lang.Boolean条件打印判断永远为假第三条是类型和控件要匹配。textField 的 Expression Class 要跟 field 的 Class 一致把 String 字段拖进一个 Expression Class 为 Integer 的文本框里编译能过填充时会抛异常。批量检查的办法是切到 source 页签用 CtrlF 搜classjava.lang.逐个比对二十个字段也就两三分钟。4. 分组、序号、交叉表三个高频控件的参数怎么设这三样东西几乎每个真实报表都会用到也几乎每个新手都会在同一个地方卡住分组后列名不跟组走、序号列永远是 1、交叉表拖进去一片空白。原因基本都出在几个隐藏属性上代码本身没错。4.1 Create Group 的两种做法与 band 高度置 0 的真实含义分组的标准做法是在报表大纲里右键报表名选 Create Group在弹窗里选中分组字段。建完之后会多出 Group Header 和 Group Footer 两个区域组内小计放 Footer组说明放 Header。第一套做法是针对「每组内容独立成页、但不需要显示组名」的报表。选中 Group Header 区域在右下角属性面板的 band properties 里把 Height 设为 0同时勾选 Start New Column 和 Start New Page 前面的复选框。效果是每个分组自动另起一页组与组之间干净切分。第二套做法是给「列名要跟着组走」的报表用的。直接把 Column Header 的 Height 设为 0然后把 Column Header 中原来的所有列名控件全部拖到 Group Header 里包括分组字段本身可以放进来后隐藏显示。这样分组后每一组开头都会重新出现一次列名视觉上就像按店铺、按仓库分册打印。!-- 按仓库分组组头显示列名组尾做小计每组另起一页 -- group namebyWarehouse isStartNewPagetrue isReprintHeaderOnEachPagetrue groupExpression![CDATA[$F{warehouseCode}]]/groupExpression groupHeader band height24 textField reportElement x0 y0 width60 height20/ textFieldExpression![CDATA[$F{warehouseCode}]]/textFieldExpression /textField /band /groupHeader groupFooter band height20 textField pattern#,##0 reportElement x180 y0 width80 height18/ textFieldExpression![CDATA[$V{totalQtySum}]]/textFieldExpression /textField /band /groupFooter /group两个属性值得单独说。isStartNewPage 就是向导里那个 Start New Page 复选框直接控制每组另起一页。isReprintHeaderOnEachPage 更容易被忽略当一组明细多到跨页时如果不勾这个第二页就没列名了打印出来一沓纸里有一半不知道哪列是哪列。做管理报表我默认把它打开。还有个细节把 Height 设为 0 只是让区域不占垂直空间里面的控件仍然参与求值。所以我见过有人把列名拖进 Group Header 后忘了改 Height结果每组页首白白空出一大块。改完高度一定切到 Preview 翻两页看看。4.2 Create Variable 做序号列resetType 与 incrementType 怎么选明细行前面加个行号视觉上能显著提升可读性做核对和签字也方便。原始资料提到用报表大纲里的 Fx 功能右键 Create Variable在属性面板里重命名并设置相应参数最后把变量拖到 Detail 区当第一列。具体参数怎么填这里给一份能抄的。右键 Fx → Create Variable属性面板里按下面填属性值说明NamerowNo变量名报表里用$V{rowNo}引用Value Classjava.lang.Integer必须显式指定留空会报错CalculationSum每求值一次就累加配合下面的表达式实现自增Expression1每次求值加 1Initial Value Expression0分组重置时回到 0Reset TypeReport整份报表从 1 数到底按页重置改成 PageIncrement TypeNone不额外触发递增交给 Detail 每行求值关键在 Calculation 选 Sum、Expression 填 1 这个组合——它等价于「每遇到一条明细就加一」。Initial Value Expression 必须填 0 而不是留空留空在部分版本里会得到 null自变量后第一行变成 null 或直接报错。Reset Type 决定了序号在什么范围内连续整份报表连续用 Report每页从 1 开始用 Page每个分组重新数用 Group 并在下面选具体分组名。填完之后直接将变量拖拽到 Detail 区作为第一列。拖动时注意 Report Inspector 里的 Evaluation Time保持 Auto 或者显式设为 Now如果被设成了 Report显示出来的会是总条数而不是行号——这个坑非常隐蔽因为变量定义本身完全正确只是求值时机不对。顺带说两个内置变量。$V{REPORT_COUNT}是整份报表的明细计数直接放到 Detail 里就是行号懒得建变量时可以用它。$V{PAGE_NUMBER}做页码但必须把 Evaluation Time 设成 Page否则显示的永远是总页数要显示「第 X 页 / 共 Y 页」就得放两个 textField一个 Evaluation Time 为 Now 显示当前页一个为 Report 显示总页数。4.3 Crosstab 交叉表统计字段必须是 Integer控件只能放 Summary交叉表用来做二维汇总比如按仓库和商品类别交叉统计收发数量。原始资料强调了两条约束都是踩过坑才写下来的。第一条统计字段的类型。在导入数据源或手工新建 fields 时凡是要参与交叉表统计的字段必须调整为 Integer 类型例如明细里统计收发货绩效时ReceiptCount 或 TotalQty 这两个字段从 JSON 读过来默认是 String必须手动改成 int。不改的后果是交叉表里 Sum 计算变成字符串拼接单元格里堆出一长串数字看着像乱码。第二条Crosstab 只可存放于 Summary 区。把它拖到其他区域某些场景下能渲染但分页和重复渲染很容易出问题。所以做交叉表类的报表设计第一步就是把多余的功能区域去掉根据需要只保留 Title 和 Summary。操作顺序是从控件面板拖 Crosstab 到 Summary 区选择引用已存在的数据源在向导里按右上角的表格阴影提示依次选字段。左侧放行分组字段比如仓库上部放列分组字段比如商品类别然后是明细区添加明细字段时把 Calculation 函数统一改成 Sum。完成后把 Crosstab 覆盖到整个界面双击进入编辑态深蓝色的 Static Text 就是行列标题可以直接改成业务叫法。最后一步九成人会忘选中 Crosstab 里的全部明细字段把字体大小改成 6 号并居中。默认字体对交叉表格子来说太大列一多就挤成一片白看起来像数据没出来其实是文字被裁掉了。5. 避坑与排查五个翻车现场的现象、原因与解法下面这五条都是我在实际模板里撞过的按「现象 → 原因 → 解决」写遇到类似的直接对号入座。第一条预览有中文导出 PDF 全是方框或者干脆空白。现象很固定设计器里一切正常切到 PDF 预览或者上传到系统后打印中文字段整片消失。原因是 PDF 渲染链不吃操作系统字体而模板里没有任何一个中文控件声明过 PDF 字体导出时只能回退到标准字体中文就没了。解决分三层先在 jrxml 里加那条全局 pdfStyle如果没效果检查 classpath 里有没有亚洲字体支持包再不行就说明当前版本走的是字体扩展路线需要把 TTF 字体打包成 jar 引入并在 textElement 里引用字体族名。别在单个控件上反复折腾字体名先确认依赖到位。第二条序号列永远是 1或者每页开头都从 1 重来。现象是拖进去的 rowNo 变量每一行显示的都是同一个值。三种原因对应三种改法Calculation 被设成了 Nothing变量只求值不累加改成 Sum 并把 Expression 填 1Reset Type 被设成了 Page 或 Column导致每页重置要整份连续就改成 ReportEvaluation Time 被设成 Report显示的是最终值而不是当前行值改回 Auto 或 Now。这三处任意一个出错表现都差不多按这个顺序排查最快。第三条报表末尾莫名其妙多出一张空白页。这个坑的血泪经验是问题九成不在 Detail而在你没注意的角落。常见原因有四个——Page Footer 或 Summary 的高度残留了十几像素Detail 的 band height 比实际内容高出很多最后一行排完刚好溢出所有 Band 高度加上下边距超过了 pageHeight以及空数据时默认行为是输出一张空页。解法是从下往上逐个把 Column Footer、Page Footer、Summary 高度归零验证定位到具体哪个区域再调回去面单类模板直接把非必要区域全设 0空数据场景把 whenNoDataType 设为 AllSectionsNoDetail 或者 NoPages。第四条分组后每组另起一页但列名不见了或者组名被吃掉了。现象有两种原因恰好相反。列名不见是因为把 Column Header 高度置 0 之后忘了把列名控件搬到 Group Header列名跟着原区域一起消失了。组名被吃掉是因为既要显示组名又把 Group Header 高度设成了 0控件还在但没空间渲染。这两种需求本身互斥动手之前先想清楚要「每组带列名」还是「只要分页不要组头」二选一别指望一个配置同时满足。第五条SQL 条件写完不填参数时查出一堆不相干的数据。原始写法是AND (rh.code :code OR rh.code IS NULL)本意是「参数为空时不过滤」但实际语义变成了「单据号等于参数值或者单据号本身是空的行」结果是只要表里有 code 为 NULL 的记录不管你填不填参数都会把它们捞出来。正确写法是把 NULL 判断加在参数上AND (:code IS NULL OR rh.code :code)。同一个模板里还有个方言问题T-SQL 环境里写LIMIT 1会直接语法报错要改成TOP 1或者用 OUTER APPLY。日期条件也别用 :end因为 :end 传进来的通常是当天 00:00:00会丢掉当天的数据用 DATEADD(DAY, 1, :end)才严谨。6. 从模板到上线打印配置 JSON、SQL 条件绑定与交付前自检模板编译完只是半成品在系统的打印模块里还得再配一层。以仓储系统为例路径是「配置 → 打印配置 → 报表」新增一条报表记录填代码标识建议和模板文件名保持一致后面排查能少绕一圈、描述、报表类型然后写条件配置和 SQL 模板。条件配置是个 JSON 数组每个元素描述一个查询字段决策字段四个field、id、name、type。field 和 id 尽量保持一致type 决定下一步 SQL 里怎么绑。对于作业报表除 name 外基本不用改管理报表要加起止日期、商品编码、供应商名这类自定义条件时就得按类型补全。[ {field: , id: h1_basic, name: 基本信息, type: h1}, {field: beg, id: beg, name: 起始日期, type: date}, {field: end, id: end, name: 结束日期, type: date}, {field: itemCode, id: itemCode, name: 商品编码, type: string}, {field: code, id: code, name: 入库单号, type: string}, {field: shipFromName, id: shipFromName, name: 供应商名, type: string} ]type 为 h1 的那条是分组标题没有实际查询意义保留即可。string 对应文本输入框date 和 datetime 会出日期选择器integer 出数字输入。字段名和 SQL 里的命名参数必须一一对应差一个字母查询就恒为空而且不报错只是查不出数据。SQL 模板按前面提的原则写可选条件一律把 NULL 判断放在参数侧SELECT rh.closedAt, rd.receiptCode, rh.shipFromName, it.itemCategory1, it.code, it.name, rd.totalQty, rd.openQty, (rd.totalQty - rd.openQty) AS redQty, rd.warehouseCode, (SELECT TOP 1 ib.barcode FROM item_barcode ib WHERE ib.itemCode rd.itemCode) AS BARCODE FROM RECEIPT_HEADER rh INNER JOIN RECEIPT_DETAIL rd ON rh.id rd.receiptId INNER JOIN ITEM it ON rd.itemCode it.code WHERE rh.leadingSts 900 AND rh.trailingSts 900 AND (:code IS NULL OR rh.code :code) AND (:itemCode IS NULL OR rd.itemCode :itemCode) AND (:shipFromName IS NULL OR rh.shipFromName :shipFromName) AND (:beg IS NULL OR rh.closedAt :beg) AND (:end IS NULL OR rh.closedAt DATEADD(DAY, 1, :end)) ORDER BY rh.closedAt DESC保存后回到列表上传模板。作业报表还要走一步「开发 → 菜单配置」找到目标界面在 View JSON 里打开对应加载项在打印区域加上这条报表文档名称填模板名、id 填打印配置里的代码名点更新生效。管理报表不用挂菜单直接在「管理 → 报表查询」里双击预览选好期间和其他条件点 PDF 下载就能出结果。交付前我固定过一遍清单模板名与代码标识一致、条件字段与 SQL 参数一一对应、PDF 中文已验证、末页无空白、日期边界用开区间。这套流程是从一次上线后才发现合计列错行开始的从那以后每次上传前都强制走一遍再没在这上面返工过。希望帮到你。本文还有配套的精品资源点击获取