如何实现:列表导出时,合并单据头字段
1 业务背景
选择列表导出时,是“所见即所得”的。
同时,列表字段中可以选择“单头字段合并”。此时按列表导出,excel的展示如图所示:
但是,这种展示看起来仍不够好看,有些情况下希望做到单据头字段在excel中合并单元格。
2 解决方案
2.1 方案总述
需要编写列表导出插件,在插件中,获取到需要合并的行和列,然后对excel文件进行修改保存。
2.2 实现步骤
2.2.1 前提条件:此方案适用于选中行导出。整体逻辑需要实现在afterExportFile方法中,且修改excel文件使用的是org.apache.poi.xssf.usermodel.XSSFWorkbook类。
/** 导出文件后事件,可以用来修改导出的文件内容,比如修改excel数据、格式、加密等 */ public void afterExportFile(ExportFileEvent e) {}
2.2.2 操作步骤
整体分为三步,第一步是获取需要合并的列;第二步是获取到需要合并的行;第三步是通过行和列,进行修改excel。
步骤1: 获取需要合并的列。excel中的列就是苍穹列表中的列表字段。需要合并的列自然就是单据头字段。所以,当前需要的就是获取到列表中的字段是否是单据头字段。
billlistap.getListFields();
通过以上代码可以获取到列表中的字段,然后遍历字段,可以再通过获取实体名称判断此名称是单据的实体名称还是单据体的实体名称来判断是否是单据头的字段。因为单据头字段的实体名称都是单据的实体名称。
步骤2: 获取需要合并的行。这个可以通过列表选中行,获取到选中行的主键。如果选中行的主键和下一行是同一个,那么说明是分录数据,则需要合并(如果是没有分录或者只有一条分录,也就不存在“合并”这个场景了)
步骤3:传入以上两个步骤获取到的行和列,调用XSSFWorkbook类的功能“合并单元格”:
//创建合并样式对象,四个参数从前到后分别为:起始行,终止行,起始列,终止列 CellRangeAddress cellAddresses = new CellRangeAddress(entry.getKey() + 1, entry.getValue() + 1, colmun, colmun); //样式加载 sheet.addMergedRegion(cellAddresses);
步骤4:最后写入excel。
2.3 实现效果
实现效果如下:下图为此插件生效后的样式。可以自行与开始的图片样式比较。
2.3 完整代码
package nm21.cosmic.plugin.list; import kd.bos.entity.datamodel.ListField; import kd.bos.entity.datamodel.ListSelectedRowCollection; import kd.bos.form.events.ExportFileEvent; import kd.bos.list.BillList; import kd.bos.list.plugin.AbstractListPlugin; import kd.sdk.plugin.Plugin; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.util.*; /** * 标准单据列表插件 */ public class ExportTestPlugin extends AbstractListPlugin implements Plugin { @Override public void afterExportFile(ExportFileEvent e) { super.afterExportFile(e); //计算需要合并的列 List<Integer> colmuns = calColumns(); //计算需要合并的行 Map<Integer, Integer> rowMap = calRows(); //导出修改的excel modifyExcel(e, colmuns, rowMap); } private void modifyExcel(ExportFileEvent e, List<Integer> colmuns, Map<Integer, Integer> rowMap) { //如果没有需要合并的列或者没有需要合并的行,那说明没有必要合并 if (colmuns.size() <= 0 || rowMap.size() <= 0) return; //修改excel File file = e.getFile(); if (file != null) { try (FileInputStream fis = new FileInputStream(file);) { Workbook wb = new XSSFWorkbook(fis); Sheet sheet = wb.getSheetAt(0); //遍历列 for (Integer colmun : colmuns) { //遍历行 Set<Map.Entry<Integer, Integer>> entries = rowMap.entrySet(); for (Map.Entry<Integer, Integer> entry : entries) { //创建合并样式对象,四个参数从前到后分别为:起始行,终止行,起始列,终止列 CellRangeAddress cellAddresses = new CellRangeAddress(entry.getKey() + 1, entry.getValue() + 1, colmun, colmun); //样式加载 sheet.addMergedRegion(cellAddresses); } } // 保存 FileOutputStream out = new FileOutputStream(file); wb.write(out); wb.close(); out.close(); } catch (Throwable ex) { //错误日志,这里需要自行记录日志 } } } private Map<Integer, Integer> calRows() { /*1 逻辑:从首行开始 * 2 检测到一致就存放入map * 3 检测到不一致就更新起始行 * 4重复2、3步 * */ Map<Integer, Integer> rowMap = new HashMap<>(); //获取需要合并的项(通过判断选中行的id是否一致,来获取需要合并的行数据) ListSelectedRowCollection selectedRows = this.getSelectedRows(); if (selectedRows.size() <= 1) return rowMap; //初始行 int startRow = 0; //获取id,用作判断是否是同一个单据头 long startRowPk = Long.parseLong(selectedRows.get(startRow).getPrimaryKeyValue().toString()); //循环 for (int i = 1; i < selectedRows.size(); i++) { long nowPk = Long.parseLong(selectedRows.get(i).getPrimaryKeyValue().toString()); //判断当前行的pk是否和首行一样 if (startRowPk != nowPk) { //不一致则更新首行 startRow = i; startRowPk = Long.parseLong(selectedRows.get(startRow).getPrimaryKeyValue().toString()); } else { //数据一致,放入map;如果下一行也一样,那就会被更新掉 rowMap.put(startRow, i); } } return rowMap; } private List<Integer> calColumns() { List<Integer> colmuns = new ArrayList<>(); //获取需要合并的列数据------如何判断哪些字段是需要合并的 BillList billlistap = this.getControl("billlistap"); List<ListField> listFields = billlistap.getListFields(); //i从1开始,是因为0列在excel里是序号,在单据实体里是主键,正好都用不到,所以从1开始。后续的顺序都是匹配的 for (int i = 1; i < listFields.size(); i++) { ListField listField = listFields.get(i); //判断是否是单据头字段 if ("nm21_yi".equals(listField.getEntityName())) { //将单据头字段放入集合 colmuns.add(i); } } return colmuns; } }
4 性能分析
该方案不适用于数据量和列表字段的乘积的数量大的列表导出场景。因为该方案会分别遍历选中行以及列表字段,然后再进行单元格合并。假设数据量为m,列表字段数量为n,那么最大的情况就是m+n+n*m/2,有乘积的存在,在一些场景下还是比较耗费性能的。
5 注意事项
1 此方案适用于列表选中行导出。不适用于直接导出,原因:要通过选中行获取需要合并的行
2 此方案是列表级别的,需要列表上注册插件,无法全局使用。
6 相关文档
如何实现:列表导出时,合并单据头字段
本文2024-09-23 00:17:14发表“云苍穹知识”栏目。
本文链接:https://wenku.my7c.com/article/kingdee-cangqiong-138504.html