一、概述
打印模块支持两种模板类型:
| 类型 | 文件后缀 | 报表名枚举 | 说明 |
|---|---|---|---|
| SIE 报表 | .sied |
PrintReportNameEnum.SIE_REPORT |
Stimulsoft 设计器,JSON 格式 |
| BarTender | .btw |
PrintReportNameEnum.BARTENDER |
BarTender 原生模板 |
业务开发接入打印有两种模式:单表打印(注解驱动,框架自动处理)和多表打印(继承 AbstractBillPrintable,手动实现数据获取)。
二、方案自动注册
应用启动时,框架自动扫描所有带 @PrintPlan 注解的类,解析数据源结构并同步到 sys_print_scheme 表。无需手动创建方案。
三、单表打印(TABLE 模式)
适用于:打印数据直接来自一张数据库表,无需跨表关联或自定义查询。
只需在实体类上添加 @PrintPlan 注解即可,无需写任何 Java 代码。框架自动根据 ID 执行单表查询获取打印数据。
3.1 注解
| 注解 | 位置 | 说明 |
|---|---|---|
@PrintPlan |
类上 | 声明打印方案 |
@PrintScheme |
类上 | 声明数据源schema |
@PrintSchemeColumn |
字段上 | 声明列元数据 |
3.2 @PrintPlan
@PrintPlan(
code = "myCode", // 方案编码,不填默认取类名 SimpleName
name = "我的方案", // 方案名称(必填)
deploy = true // 是否注册部署,默认 true
)
3.3 @PrintSchemeColumn
@PrintSchemeColumn(
code = "field_code", // 列编码,不填默认取字段名
name = "字段显示名", // 列显示名称
type = "System.String", // Stimulsoft 数据类型,不填自动根据 Java 类型映射
ignore = false // 是否忽略该字段,默认 false
)
3.4 示例
@Data
@EqualsAndHashCode(callSuper = true)
@Table(value = "sys_dict")
@PrintPlan(code = "dictPrint", name = "字典打印方案")
@PrintScheme(name = "字典信息")
public class Dict extends BasicEntity<Dict> {
@PrintSchemeColumn(code = "dictCode", name = "字典编码")
private String dictCode;
@PrintSchemeColumn(code = "dictName", name = "字典名称")
private String dictName;
@PrintSchemeColumn(ignore = true) // 不需要打印的字段
private String internalField;
// ... 其他字段
}
实体类必须继承
BaseEntity或BasicEntity,框架据此判断为 TABLE 模式。
四、多表打印(CUSTOM 模式)
适用于:打印数据需要跨表关联查询、自定义业务逻辑或数据转换。
4.1 继承 AbstractBillPrintable
@Data
@Component
@PrintPlan(name = "qms单据打印")
@PrintScheme(name = "qms单据")
public class QmsOrderPrint extends AbstractBillPrintable {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderLineMapper orderLineMapper;
@Autowired
private OrderLineDtlMapper orderLineDtlMapper;
@Autowired
private OrderDefectRecordMapper orderDefectRecordMapper;
@Autowired
private OrderObjectMapper orderObjectMapper;
private Order order;
private OrderLine orderLine;
private OrderLineDtl orderLineDtl;
private DefectRecord defectRecord;
private OrderObject orderObject;
@Override
public Map<String, List<?>> printData(List<String> ids) {
Map<String, List<?>> map = new HashMap<>();
// 获取主单据
List<Order> orderList;
if (CollectionUtils.isEmpty(ids)) {
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.orderBy(Order::getUpdateDate).desc();
Page<Order> paginate = orderMapper.paginate(1, 1, queryWrapper);
if (CollectionUtils.isEmpty(paginate.getRecords())) {
return new HashMap<>();
}
orderList = paginate.getRecords();
} else {
orderList = orderMapper.selectListByIds(ids);
}
map.put(Order.class.getSimpleName(), orderList);
if (CollectionUtils.isEmpty(orderList)) {
return map;
}
// 获取单据明细(OrderLine)
List<Long> orderIdList = orderList.stream().map(Order::getId).toList();
List<String> orderIdStrList = orderIdList.stream().map(String::valueOf).toList();
QueryWrapper orderLineQuery = new QueryWrapper();
orderLineQuery.where(OrderLine::getInspectOrderId).in(orderIdStrList);
List<OrderLine> orderLineList = orderLineMapper.selectListByQuery(orderLineQuery);
map.put(OrderLine.class.getSimpleName(), orderLineList);
// 获取检验录入明细(OrderLineDtl)
if (CollectionUtils.isNotEmpty(orderLineList)) {
List<Long> orderLineIdList = orderLineList.stream().map(OrderLine::getId).toList();
List<String> orderLineIdStrList = orderLineIdList.stream().map(String::valueOf).toList();
QueryWrapper dtlQueryWrapper = new QueryWrapper();
dtlQueryWrapper.where(OrderLineDtl::getInspectOrderLineId).in(orderLineIdStrList);
List<OrderLineDtl> orderLineDtlList = orderLineDtlMapper.selectListByQuery(dtlQueryWrapper);
map.put(OrderLineDtl.class.getSimpleName(), orderLineDtlList);
}
// 获取流转条码(OrderObject)
QueryWrapper objectQueryWrapper = new QueryWrapper();
objectQueryWrapper.where(OrderObject::getInspectOrderId).in(orderIdStrList);
List<OrderObject> orderObjectList = orderObjectMapper.selectListByQuery(objectQueryWrapper);
map.put(OrderObject.class.getSimpleName(), orderObjectList);
// 获取不合格记录(DefectRecord)- 通过检验单ID查询
QueryWrapper defectQueryWrapper = new QueryWrapper();
defectQueryWrapper.where(DefectRecord::getInspectOrderId).in(orderIdStrList);
List<DefectRecord> defectRecordList = orderDefectRecordMapper.selectListByQuery(defectQueryWrapper);
map.put(DefectRecord.class.getSimpleName(), defectRecordList);
//标签
List<OrderPrintDTO> dataList = BeanUtil.copyToList(orderList, OrderPrintDTO.class);
map.put(QmsOrderPrint.class.getSimpleName(), dataList);
return map;
}
@Override
public Map<String, List<?>> previewData() {
List<Order> list = orderMapper.selectListByQuery(
QueryWrapper.create()
.orderBy(Order::getUpdateDate, false)
.limit(5)
);
List<OrderPrintDTO> dataList = BeanUtil.copyToList(list, OrderPrintDTO.class);
Map<String, List<?>> map = new HashMap<>();
map.put(QmsOrderPrint.class.getSimpleName(), dataList);
return map;
}
}
4.2 关键要点
| 项目 | 说明 |
|---|---|
| 父类 | 必须继承 AbstractBillPrintable |
| 注解 | 类上必须加 @PrintPlan + @Component |
printData(ids) |
正式打印时调用,根据业务 ID 列表查询数据 |
previewData() |
设计器预览时调用,返回少量数据供模板设计用 |
| 返回值 | Map<String, List<?>>,key 为 类名SimpleName(表名),value 为 列表数据 |
| DTO | 建议新建 DTO 类承载字段(用于 @PrintSchemeColumn 注解) |
4.3 DTO 字段注解示例
/**
* @author 罗展丰
* @date 2026/05/20
* @description
*/
@Data
public class OrderPrintDTO {
/**
* 检验单号
*/
@PrintSchemeColumn(name="检验单号")
private String inspectOrderNo;
/**
* 检验结果
*/
@PrintSchemeColumn(name="检验结果")
private String inspectOrderResult;
/**
* 检验业务类型
*/
@PrintSchemeColumn(name="检验业务类型")
private String inspectOrderType;
/**
* 检验方案名称
*/
@PrintSchemeColumn(name="检验方案名称")
private String inspectPlanName;
/**
* 物料编码
*/
@PrintSchemeColumn(name="物料编码")
private String partNo;
/**
* 物料版本
*/
@PrintSchemeColumn(name="物料版本")
private String partRevision;
/**
* 检验状态
*/
@PrintSchemeColumn(name="检验状态")
private String status;
/**
* 记录检验人员
*/
@PrintSchemeColumn(name="检验人员")
private String employeeName;
}
DTO 字段上加
@PrintSchemeColumn会被PrintInitialization反射解析,自动注册为方案的数据列。
4.4 参数说明
printData(List<String> ids) — ids 由前端传入,来源是 PrintBartenderProxyDTO.ids:
{
"path": "http://.../oss/file/download?id=123",
"code": "taskPrint",
"ids": ["799665818685509", "799665818685510"],
"printCount": 1
}
code对应@PrintPlan.codeids通常为业务表主键 ID 列表
previewData() — 无参数,返回少量数据(通常查最新 N 条)供模板设计器展示。
五、BarTender 标签打印专用说明
标签打印(templateType = label,reportName = bartender)必须使用 CUSTOM 模式,因为 BarTender 通过 CSV 文件读取数据,需要 previewData() 提供 CSV 数据源。
5.1 数据流向
用户触发打印
└→ PrintBartenderService.printProxy()
└→ printBean.printData(ids) → 业务数据 Map → Python 代理用
└→ printBean.previewData() → CSV 数据源 → 打包为 data.csv
└→ getCsvContent() 转换
├→ 反射 DTO 字段名 → 生成 CSV 表头
├→ 数据行用 | 作为分隔符
└→ 打包进 ZIP(data.csv + xxx.btw)
└→ 下载到本地 → BarTender 打开 .btw 读取 .csv → 打印
5.2 previewData 返回的 DTO 就是 CSV 列
previewData() 返回的 DTO 字段,会直接作为 CSV 的表头列名。BarTender 模板中的字段名需要与 CSV 列名一致。
// DTO 字段 = CSV 列名
@Data
public class LabelPrintDTO {
@PrintSchemeColumn(name = "产品编码")
private String productCode; // CSV列: productCode
@PrintSchemeColumn(name = "产品名称")
private String productName; // CSV列: productName
@PrintSchemeColumn(name = "批次号")
private String batchNo; // CSV列: batchNo
}
生成的 CSV 格式(| 分隔):
productCode|productName|batchNo
PCB-001|主板V2|B20260601
PCB-002|显卡X1|B20260602
注意:如果
previewData()返回的printData为空(list 为空),CSV 将只有空行无表头,上传.btw模板后会无法识别数据源。必须保证previewData()至少返回一条数据。
5.3 标签打印 Printable 示例
@Component
@PrintPlan(code = "pcbLabelPrint", name = "PCB标签打印")
@PrintScheme(name = "PCB标签数据")
public class PcbLabelPrintable extends AbstractBillPrintable {
@Autowired
private PcbService pcbService;
@Override
public Map<String, List<?>> printData(List<String> ids) {
List<Pcb> list = pcbService.listByIds(ids);
return toResult(list);
}
@Override
public Map<String, List<?>> previewData() {
// 必须返回至少一条数据,否则CSV无表头
List<Pcb> list = pcbService.list(
new QueryWrapper<Pcb>().last("LIMIT 1")
);
return toResult(list);
}
private Map<String, List<?>> toResult(List<Pcb> list) {
List<LabelPrintDTO> dtoList = BeanUtil.copyToList(list, LabelPrintDTO.class);
Map<String, List<?>> map = new HashMap<>();
map.put(PcbLabelPrintable.class.getSimpleName(), dtoList);
return map;
}
}
六、最终效果
无论单表还是多表模式,系统启动后:
- 自动扫描带
@PrintPlan的 Bean - 递归解析
@PrintScheme/@PrintSchemeColumn构建元数据 - 方案同步到
sys_print_scheme表 - 前端模板管理页面可见该方案,可关联模板并设计/打印
七、完整接入步骤
- 确定模式:单表用实体类加注解,多表用继承
AbstractBillPrintable - 加注解:
@PrintPlan(必须)+@PrintScheme(推荐)+@PrintSchemeColumn(字段级) - 多表模式额外实现:
printData()+previewData() - 启动应用,确认日志
找到N个打印方案, 开始解析打印方案无报错 - 前端上传模板文件(.sied 或 .btw),创建模板记录
- 设计器编辑模板,选择对应方案的数据源
- BarTender 打印或 SIE 报表渲染