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.

219 lines
8.8 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# poi-tl 与 ruoyi-common-word 模块说明
## 1. 模块定位ruoyi-common-word
- **模块路径**`hwbm-cloud/ruoyi-common/ruoyi-common-word`
- **主要职责**
- 基于 [poi-tl](https://github.com/Sayi/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.docx`、`templates/crm_quote.docx` 等。
- `WordTemplateUtil` 接收的是 **classpath 相对路径**,例如:
```java
WordTemplateUtil.renderToResponse("templates/wms_shipping_bill.docx", "发货单_X123", data, response);
```
部署后,只要模板文件在 Jar 的 `BOOT-INF/classes/templates` 下即可被正常加载。
---
## 4. 数据 Map 结构约定
`WordTemplateUtil` 不关心业务字段含义,只约定数据以 `Map<String, Object>` 形式传入:
- **普通字段**
```java
Map<String, Object> data = new HashMap<>();
data.put("customerName", "某某公司"); // {{customerName}}
data.put("shippingDate", "2025-12-10"); // {{shippingDate}}
data.put("contractCode", "HT-2025-001"); // {{contractCode}}
```
- **列表字段(循环表格)**
```java
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}}
```
- **合计字段(可选)**
```java
data.put("totalQuantity", "100"); // {{totalQuantity}}
data.put("totalAmount", "12345.67"); // {{totalAmount}}
```
模板与数据字段的对应关系完全由业务模块自己约定,但**应在各自模块文档中说明**,便于维护。
---
## 5. 发货单WmsShippingBill标准示例
发货单模块(`ruoyi-wms`)是 `WordTemplateUtil` 的一个完整应用示例,推荐其他单据类模块(合同、报价、项目收货/验收等)参照其设计。
### 5.1 Controller 入口示例
文件:`WmsShippingBillController.exportWord`
```java
@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`(循环表格数据源)。
部分代码示意:
```java
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 中通过表格 / 段落排版完成。
```text
客户名称:{{customerName}}
联系人:{{receiverName}} 联系电话:{{receiverPhone}}
发货日期:{{shippingDate}} 合同编号:{{contractCode}}
{{#details}}
序号:{{seq}} 物料:{{materialName}} 数量:{{quantity}} 单位:{{unit}} 备注:{{remark}}
{{/details}}
收货日期:{{receivedDate}}
发货单位:{{shippingUnit}}
发货人:{{shipper}} 联系电话:{{contactNumber}}
```
> 其它单据(如报价、合同、项目收货/验收)可以直接复用上述模式:
> - 主表字段:顶部信息 + 落款信息;
> - 明细字段:`details` 循环表格;
> - 可扩展合计字段:`totalQuantity`、`totalAmount` 等。
---
## 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`)。