You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.8 KiB

poi-tl 与 ruoyi-common-word 模块说明

1. 模块定位ruoyi-common-word

  • 模块路径hwbm-cloud/ruoyi-common/ruoyi-common-word
  • 主要职责
    • 基于 poi-tl 提供统一的 Word 模板渲染与导出能力
    • 仅负责模板加载、渲染、输出,不侵入具体业务字段和业务规则;
    • 供各业务模块(合同、报价、发货单、项目收货/验收等)统一复用。
  • 核心工具类org.dromara.common.word.util.WordTemplateUtil
    • renderToResponse(...)Controller 直接导出 Word
    • renderToOutputStream(...):向任意 OutputStream 写入 WordOSS、本地文件等
    • renderToBytes(...):返回字节数组,便于后续上传或作为接口返回值。

详细 JavaDoc 和更多示例可参考 WordTemplateUtil 源码本身。


2. poi-tl 简介

poi-tl 是构建在 Apache POI 之上的 Word 模板引擎,通过简单的占位符语法实现:

  • 普通字段占位符{{fieldName}}
  • 列表循环占位符
    • 语法:{{#list}} ... {{/list}}
    • list 对应数据 Map 中的 List<Map<String, Object>>
    • 循环体内部使用 {{列名}} 访问每一行 Map 中的字段。

本项目只做了一层简单封装,不改变 poi-tl 原生语义,方便直接对照官方文档理解。


3. 模板文件存放规范

  • 建议所有业务模块将 Word 模板统一放在各自模块的 resources/templates 目录下,例如:
    • hwbm-cloud/ruoyi-modules/ruoyi-wms/src/main/resources/templates/wms_shipping_bill.docx
    • 其他模块可类比:templates/erp_contract.docxtemplates/crm_quote.docx 等。
  • WordTemplateUtil 接收的是 classpath 相对路径,例如:
WordTemplateUtil.renderToResponse("templates/wms_shipping_bill.docx", "发货单_X123", data, response);

部署后,只要模板文件在 Jar 的 BOOT-INF/classes/templates 下即可被正常加载。


4. 数据 Map 结构约定

WordTemplateUtil 不关心业务字段含义,只约定数据以 Map<String, Object> 形式传入:

  • 普通字段

    Map<String, Object> data = new HashMap<>();
    data.put("customerName", "某某公司");    // {{customerName}}
    data.put("shippingDate", "2025-12-10"); // {{shippingDate}}
    data.put("contractCode", "HT-2025-001"); // {{contractCode}}
    
  • 列表字段(循环表格)

    List<Map<String, Object>> details = new ArrayList<>();
    
    Map<String, Object> row1 = new HashMap<>();
    row1.put("seq", 1);                 // {{seq}}
    row1.put("materialName", "胶辊");   // {{materialName}}
    row1.put("quantity", 10);          // {{quantity}}
    row1.put("unit", "件");           // {{unit}}
    row1.put("remark", "加急发货");    // {{remark}}
    details.add(row1);
    
    data.put("details", details);       // 对应模板 {{#details}}...{{/details}}
    
  • 合计字段(可选)

    data.put("totalQuantity", "100");  // {{totalQuantity}}
    data.put("totalAmount", "12345.67"); // {{totalAmount}}
    

模板与数据字段的对应关系完全由业务模块自己约定,但应在各自模块文档中说明,便于维护。


5. 发货单WmsShippingBill标准示例

发货单模块(ruoyi-wms)是 WordTemplateUtil 的一个完整应用示例,推荐其他单据类模块(合同、报价、项目收货/验收等)参照其设计。

5.1 Controller 入口示例

文件:WmsShippingBillController.exportWord

@GetMapping("/exportWord/{shippingBillId}")
public void exportWord(@PathVariable Long shippingBillId, HttpServletResponse response) {
    // 1由 Service 组装模板数据 Map包含主表字段 + 明细列表)
    Map<String, Object> data = wmsShippingBillService.buildWordExportData(shippingBillId);

    // 2生成下载文件名优先使用发货单号
    WmsShippingBillVo vo = wmsShippingBillService.queryById(shippingBillId);
    String fileName = "发货单_" + (vo != null && vo.getShippingCode() != null
        ? vo.getShippingCode() : shippingBillId);

    // 3调用通用工具类完成渲染与输出
    WordTemplateUtil.renderToResponse("templates/wms_shipping_bill.docx", fileName, data, response);
}

5.2 Service 组装数据示例

文件:WmsShippingBillServiceImpl.buildWordExportData

核心思路:

  1. 查询发货单主表 + 明细列表 VO
  2. 组装主表字段(顶部信息、落款信息);
  3. 组装明细列表 details(循环表格数据源)。

部分代码示意:

public Map<String, Object> buildWordExportData(Long shippingBillId) {
    WmsShippingBillVo vo = queryById(shippingBillId);
    if (vo == null) {
        throw new ServiceException("发货单不存在ID" + shippingBillId);
    }

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Map<String, Object> data = new HashMap<>();

    // === 主表字段 ===
    data.put("customerName", StringUtils.blankToDefault(vo.getCustomerName(), ""));    // {{customerName}}
    data.put("receiverName", StringUtils.blankToDefault(vo.getReceiverName(), ""));    // {{receiverName}}
    data.put("receiverPhone", StringUtils.blankToDefault(vo.getReceiverPhone(), ""));  // {{receiverPhone}}
    data.put("shippingDate", vo.getShippingTime() != null ? sdf.format(vo.getShippingTime()) : ""); // {{shippingDate}}
    data.put("contractCode", StringUtils.blankToDefault(vo.getContractCode(), ""));    // {{contractCode}}
    data.put("receivedDate", ""); // 收货日期留空,由客户签收时手工填写 → {{receivedDate}}

    // 落款信息(供应商 / 发货方)
    data.put("shippingUnit", StringUtils.blankToDefault(vo.getSupplier(), ""));       // {{shippingUnit}}
    data.put("shipper", StringUtils.blankToDefault(vo.getContactUser(), ""));         // {{shipper}}
    data.put("contactNumber", StringUtils.blankToDefault(vo.getContactNumber(), "")); // {{contactNumber}}

    // === 明细列表,对应 {{#details}}...{{/details}} ===
    List<Map<String, Object>> details = new ArrayList<>();
    if (CollUtil.isNotEmpty(vo.getItemsVo())) {
        int seq = 1;
        for (WmsShippingDetailsVo item : vo.getItemsVo()) {
            Map<String, Object> row = new HashMap<>();
            row.put("seq", String.valueOf(seq++));                      // {{seq}}
            row.put("materialName", StringUtils.blankToDefault(item.getMaterialName(), "")); // {{materialName}}
            row.put("quantity", item.getShippingStockAmount() != null
                ? item.getShippingStockAmount().toPlainString() : "");  // {{quantity}}
            row.put("unit", StringUtils.blankToDefault(item.getUnitName(), ""));             // {{unit}}
            row.put("remark", StringUtils.blankToDefault(item.getRemark(), ""));             // {{remark}}
            details.add(row);
        }
    }
    data.put("details", details);

    return data;
}

5.3 发货单模板占位符示意

下面仅用伪代码示意占位符位置,实际模板在 Word 中通过表格 / 段落排版完成。

客户名称:{{customerName}}
联系人:{{receiverName}}    联系电话:{{receiverPhone}}
发货日期:{{shippingDate}}  合同编号:{{contractCode}}

{{#details}}
序号:{{seq}}  物料:{{materialName}}  数量:{{quantity}}  单位:{{unit}}  备注:{{remark}}
{{/details}}

收货日期:{{receivedDate}}

发货单位:{{shippingUnit}}
发货人:{{shipper}}    联系电话:{{contactNumber}}

其它单据(如报价、合同、项目收货/验收)可以直接复用上述模式:

  • 主表字段:顶部信息 + 落款信息;
  • 明细字段:details 循环表格;
  • 可扩展合计字段:totalQuantitytotalAmount 等。

6. 常见使用场景总结

  1. Web 端直接下载 Word

    • Controller 调用 WordTemplateUtil.renderToResponse(...)
    • 浏览器收到 Content-Disposition: attachment 头,触发下载。
  2. 上传到 OSS / MinIO 等对象存储

    • 使用 renderToBytes(...) 生成字节数组;
    • 由 OSS 客户端统一上传并返回访问 URL。
  3. 定时任务或后台服务批量生成 Word

    • 使用 renderToOutputStream(...) 写入本地文件或网络流;
    • 后续可再做压缩、归档、推送等操作。
  4. 集成测试 / 模板验收

    • 使用 renderToBytes(...) 生成文档;
    • 对生成内容进行人工或自动化检查(例如比对关键字段是否渲染正确)。

7. 推荐阅读顺序

  1. 阅读本 word.md 文档,理解整体设计与发货单示例;
  2. 打开 WordTemplateUtil 源码,查看 JavaDoc 中的详细注释和示例;
  3. 在业务模块中设计自己的模板(.docx)和 buildXXXExportData 方法,与本示例保持同样的模式;
  4. 将新模块的模板字段 ↔ 数据字段映射关系同步更新到各自模块的业务文档中(例如 hwbm.md)。