罗展丰
Published on 2026-06-17 / 112 Visits
0
0

打印模块对接文档

一、概述

打印模块支持两种模板类型:

类型 文件后缀 报表名枚举 说明
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.code
  • ids 通常为业务表主键 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;
    }
}

六、最终效果

无论单表还是多表模式,系统启动后:

  1. 自动扫描带 @PrintPlan 的 Bean
  2. 递归解析 @PrintScheme / @PrintSchemeColumn 构建元数据
  3. 方案同步到 sys_print_scheme 表
  4. 前端模板管理页面可见该方案,可关联模板并设计/打印

七、完整接入步骤

  1. 确定模式:单表用实体类加注解,多表用继承 AbstractBillPrintable
  2. 加注解:@PrintPlan(必须)+ @PrintScheme(推荐)+ @PrintSchemeColumn(字段级)
  3. 多表模式额外实现:printData() + previewData()
  4. 启动应用,确认日志 找到N个打印方案, 开始解析打印方案 无报错
  5. 前端上传模板文件(.sied 或 .btw),创建模板记录
  6. 设计器编辑模板,选择对应方案的数据源
  7. BarTender 打印或 SIE 报表渲染

Comment