本文讨论了现有系统中引出数据样式受限的问题,客户对表格样式有个性化需求。为此,提出了在报表中通过二次开发(二开)的方式导出Excel并自定义样式的解决方案。该方案包括查询准备导出的数据、构造工作簿、生成输出流、上传文件服务器、下载文件或返回下载链接等步骤。具体实现了对水果和化肥单据的数据查询、合并,并通过自定义报表插件和页面插件,修改数据样式(如添加标题行、调整背景色、文字样式等),最后通过文件服务上传并生成下载链接。文中还给出了详细的代码示例,包括查询数据的逻辑、Excel样式的设置、文件上传和弹出下载链接的方法,并提醒了上传时可能遇到的异常问题及排查方向。
背景
苍穹没有提供引出操作插件(有引入操作插件)。
引出时,只支持在”单据列表插件“上干预引出结果,但是在报表、动态表单上均无法对引出数据进行干预。
客户常常对引出表格样式有个性化需求,比如添加标题行,修改引出Excel列头的样式,如调整单元格高度,单元格背景色,文字字体/颜色/字号等风格。
需要一个更加自由地,自定义程度更高的二开方式去实现引出数据。
接下来,让我们一起探索在报表中,以二开的方式导出Excel并自定义样式。
解决方案
二开实现方案如下:
查询准备导出的数据 → 构造工作簿 → 使用工作簿生成输出流 → 再转成输入流 → 调用文件服务,把输入流上传到文件服务器 → 拼接下载路径 → 下载文件或返回下载链接。
准备工作
创建2个单据并添加预置数据,给2个单据添加数据,在接下来的内容里面,将引出这两个单据的数据。
以下是单据详情:
水果单据,其中关键字段有 产品类型(下拉列表),产地(基础资料),水果名称(单据体文本)
化肥单据,其中关键字段有 肥料名称(文本),产地(基础资料):
添加预置数据如下:
报表二开实现引出数据
1.新建报表页面,注册报表取数插件和报表页面插件
2.编写插件代码
查询数据
查询数据的代码,是导出的数据源,同时也是报表的数据源,这一部分的逻辑写在一个工具类中,共用代码。
使用QueryServiceHelper查询数据,将2个DateSet对象用join进行合并,代码如下:
public class FPQueryUtil { public static DataSet queryData() { DataSet dataSetFruits = QueryServiceHelper.queryDataSet("FruitPro", "kdec_fruit_bill", "id, billno as kdec_fnumber, kdec_entryentity.kdec_fruits as kdec_fname, kdec_fruit_type, kdec_fruit_pro.id as proid, kdec_fruit_pro.name as kdec_pro", null, ""); DataSet dataSet2 = dataSetFruits.copy(); Iterator<Row> iterator = dataSet2.iterator(); List proList = new ArrayList<>(); while (iterator.hasNext()) { Row row = iterator.next(); proList.add(row.get("proid")); } QFilter regionFilter = new QFilter("kdec_region", QCP.in, proList); QFilter statusFilter = new QFilter("billstatus", QCP.like, "C"); DataSet dataSetFertilizer = QueryServiceHelper.queryDataSet("FruitPro", "kdec_fertilizer_bill", "id, billno as kdec_fernumber, kdec_name as kdec_fername, kdec_region", new QFilter[]{regionFilter,statusFilter}, ""); DataSet result = dataSetFruits.rightJoin(dataSetFertilizer) .select(new String[]{"kdec_fnumber", "kdec_fname", "kdec_pro", "kdec_fruit_type"}, new String[]{"kdec_fernumber", "kdec_fername"}) .on("proid", "kdec_region").finish(); return result; } }
报表取数插件
public class FruitProReportListDataPlugin extends AbstractReportListDataPlugin { @Override public DataSet query(ReportQueryParam reportQueryParam, Object o) throws Throwable { return FPQueryUtil.queryData(); } }
导出时,解析DataSet,修改部分数据
页面插件监听导出按钮,点击时,调用上述代码,加载数据并解析。
将水果的产品类型的字符串添加到水果名称之前,部分代码如下:
// excelList用于提供数据源给 excel工作簿,外层的list是行,里面的List<String>是列,存储value List<List<String>> excelList = new ArrayList<>(); // 添加列头,此处代码省略,开发者自定义列头信息,可以参考附件源码。 excelList.add(titles); while (dataSet.hasNext()) { Row row = dataSet.next(); List<String> strings = new ArrayList<>(); for (AbstractReportColumn column : listColumns) { String field = ((ReportColumn)column).getFieldKey(); String value = row.getString(field); if (field.equals("kdec_fname")) { // 将水果的产品类型的字符串添加到水果名称之前 value = row.get("kdec_fruit_type").toString() + "的" + value; } strings.add(value); } excelList.add(strings); }
解析后,构造XSSFWorkbook
创建第一行的标题,修改背景色、文字颜色、进行单元格合并等等样式,部分代码如下:
// 创建标题 XSSFRow headRow = sheet.createRow(0); XSSFCell headCell = headRow.createCell(0); // 设置第一行的值 headCell.setCellValue(title); // 设置首行标题的一些风格样式 XSSFCellStyle titleStyle = workbook.createCellStyle(); // 设置背景色 //设置填充方案 titleStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 设置自定义填充颜色,天蓝色 titleStyle.setFillForegroundColor(new XSSFColor(new Color(135,206,250))); // 设置水平居中 titleStyle.setAlignment(HorizontalAlignment.CENTER); // 设置垂直居中 titleStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 设置字 XSSFFont font = workbook.createFont(); // 字体颜色橘红色 font.setColor(new XSSFColor(new Color(255 ,69,0))); // 设置字号 font.setFontHeight(20); // 设置字体 font.setFontName("黑体"); titleStyle.setFont(font); headCell.setCellStyle(titleStyle); // 合并第1行的前几列,合并列数 = excel的列数 CellRangeAddress titleCellAddresses = new CellRangeAddress(0, 0, 0, excel.get(0).size()-1); sheet.addMergedRegion(titleCellAddresses);
创建普通单元格样式,填充单元格数据,代码如下:
// 普通单元格的style // 单据列表数据风格样式,设置字体为黑体,字号15 XSSFCellStyle billStyle = workbook.createCellStyle(); // 设置背景色以及填充方案 billStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); billStyle.setFillForegroundColor(new XSSFColor(new Color(220,220,220))); // 设置字体 XSSFFont billFont = workbook.createFont(); billFont.setFontName("宋体"); billFont.setFontHeight(15); billStyle.setFont(billFont); //写入单据列表数据 for (int i = 0; i < excelList.size(); i++) { // i+1是因为前面第1行加了一个标题,单据列表数据是从Excel的第2行开始的,所以要+1 XSSFRow nrow = sheet.createRow(i + 1); for (int u = 0; u < excelList.get(i).size(); u++) { XSSFCell ncell = nrow.createCell(u); ncell.setCellStyle(billStyle); ncell.setCellValue(excelList.get(i).get(u)); } }
上传文件
代码如下: public static String upload (String entityName, XSSFWorkbook workbook) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); String fileName = entityName + sdf.format(new Date()) + ".xlsx"; String pathName = "/offices/" + fileName; try { OutputStream outputStream = new ByteArrayOutputStream(); workbook.write(outputStream); InputStream inputStream = parse(outputStream); FileService fs = FileServiceFactory.getAttachmentFileService(); String path = fs.upload(new FileItem(fileName, pathName, inputStream)); return path; } catch (Exception e) { System.out.println(e.getMessage()); } return ""; }
文件流处理
public static ByteArrayInputStream parse(final OutputStream out) throws Exception { ByteArrayOutputStream baos = (ByteArrayOutputStream) out; final ByteArrayInputStream swapStream = new ByteArrayInputStream(baos.toByteArray()); return swapStream; }
弹出下载链接弹框
// 弹出提示框 getView().showMessage("下载文件链接:" + RequestContext.get().getClientFullContextPath() + "/attachment/download.do?path=" + path);
如果需要直接下载链接,可以用openUrl方法,在浏览器中打开新页签,访问下载链接,
这样浏览器就会直接下载这个excel文件,代码如下:
getView().openUrl(RequestContext.get().getClientFullContextPath() + "/attachment/download.do?path=" + path)
效果图
注意事项
(1)在上传时,遇到了抛异常的场景,文件服务拒接连接。
可以从以下方向排查问题:
l 文件服务的磁盘容量是否超出上限,尝试删除上传路径的部分文件,看看能否解决问题。
l 文件服务是否在运行,尝试重启文件服务。
l 服务端和客户端的端口号是否正确,默认是8100。
参考资料
export_demo.zip(22.24KB)
推荐阅读