如何用二开的方式将报表数据导出到Excel,并调整Excel样式原创
金蝶云社区-吴锐雄
吴锐雄
5人赞赏了该文章 1,881次浏览 未经作者许可,禁止转载编辑于2024年04月10日 16:34:14
summary-icon摘要由AI智能服务提供

本文讨论了现有系统中引出数据样式受限的问题,客户对表格样式有个性化需求。为此,提出了在报表中通过二次开发(二开)的方式导出Excel并自定义样式的解决方案。该方案包括查询准备导出的数据、构造工作簿、生成输出流、上传文件服务器、下载文件或返回下载链接等步骤。具体实现了对水果和化肥单据的数据查询、合并,并通过自定义报表插件和页面插件,修改数据样式(如添加标题行、调整背景色、文字样式等),最后通过文件服务上传并生成下载链接。文中还给出了详细的代码示例,包括查询数据的逻辑、Excel样式的设置、文件上传和弹出下载链接的方法,并提醒了上传时可能遇到的异常问题及排查方向。

背景

苍穹没有提供引出操作插件(有引入操作插件)。

引出时,只支持在”单据列表插件“上干预引出结果,但是在报表动态表单上均无法对引出数据进行干预。

客户常常对引出表格样式有个性化需求,比如添加标题行,修改引出Excel列头的样式,如调整单元格高度,单元格背景色,文字字体/颜色/字号等风格。

需要一个更加自由地,自定义程度更高的二开方式去实现引出数据

接下来,让我们一起探索在报表中,以二开的方式导出Excel并自定义样式。

解决方案

二开实现方案如下:

查询准备导出的数据 → 构造工作簿 → 使用工作簿生成输出流 → 再转成输入流 → 调用文件服务,把输入流上传到文件服务器 → 拼接下载路径 → 下载文件或返回下载链接。

 

准备工作

创建2个单据并添加预置数据,给2个单据添加数据,在接下来的内容里面,将引出这两个单据的数据。

以下是单据详情:

水果单据,其中关键字段有 产品类型(下拉列表),产地(基础资料),水果名称(单据体文本)

 image.png

化肥单据,其中关键字段有 肥料名称(文本),产地(基础资料):

 image.png

添加预置数据如下:

 image.png

 image.png

 

 

报表二开实现引出数据

1.新建报表页面,注册报表取数插件和报表页面插件

 image.png

 image.png

 

2.编写插件代码

查询数据

查询数据的代码,是导出的数据源,同时也是报表的数据源,这一部分的逻辑写在一个工具类中,共用代码。

 

使用QueryServiceHelper查询数据,将2DateSet对象用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)

 

效果图

 image.png

 image.png

image.png

image.png

 

注意事项

1)在上传时,遇到了抛异常的场景,文件服务拒接连接。

可以从以下方向排查问题:

l 文件服务的磁盘容量是否超出上限,尝试删除上传路径的部分文件,看看能否解决问题。

l 文件服务是否在运行,尝试重启文件服务。

服务端和客户端的端口号是否正确,默认是8100

 image.png

 

参考资料

【开发平台】指导手册

学习成长中心

插件开发





赞 5