java生成報表(excel表格)的另類方法

衆所周知,java生成報表的工具jfree,能生成各種圖表圖形,可是今天說的是要生成一個表格形式的報表。
其實這也簡單,表格首推excel文檔,只要模板確定,數據填充不是問題,excel操作不是問題,要咋樣就來咋樣啊!
但是產品說了,要能直觀查看,能直接轉發的表格報表,報表生成完之後你要微信模板消息通知我,你特麼讓我在微信裏還要下載個excel文檔再打開再轉發嗎?用戶體驗有木有!反人類操作有木有!
。。。。。。
那我把報表做成一張圖片,把這一批次的圖片生成到一個html頁面上,您模板消息點開就是這個網頁,報表一字排開,雙擊讀圖瀏覽,長按轉發朋友朋友圈,怎麼樣啊?
做出來我看看先,報表大概長這麼個樣子:
在這裏插入圖片描述特別說明:下級1、下級2處並不止有2個下級,有的項目有5個甚至更多個下級,有的項目1個下級都沒有!合計下面還有一行小字,對報表中的各項表頭做基本的解釋,報表表頭隔幾個月可能要增減,大體模板基本固定爲這個樣子。。。
這個需求吧,我就開始琢磨:以前有同事做過這樣子類似報表的,只是做完這個就離職了,相互之間還不太熟悉呢,翻出原來的代碼來看,哇塞,1200多行代碼,其中生成報表圖片的方法就有600多行,看了代碼,直呼瞭然瞭然,怪不得離職!
原來的做法,是逐項逐項的計算距離,定好文字的xy座標,用Graphics2D進行依次繪製,一個位置與一個位置之間的座標點只有邏輯關聯,比如現在要在表頭6之前加入一個表頭,讓原來只有11列的表格變成12列,那原來的代碼從表頭6開始之後的所有文字、邊框的座標點全部都要拿計算器重新計算一遍,這顯然是很恐怖的!
根據這個思路,,我開始用面向對象的方法來考慮,一個報表由若干個表格組成,一個表格有上下左右四條邊,有背景顏色、中間的內容有字體顏色、有字體大小、字體位置座標,於是有如下實體屬性(不要糾結訪問修飾符):

    public String text="",text1=text,text2="";//表格內容較寬時要換成兩行顯示
    public Font font = new Font("宋體", Font.BOLD, 58);
    public Color fontColor=Color.BLACK;
    public Color backgroundColor=Color.WHITE;
    public Color borderColor=Color.black;
    public int fontX,fontY,fontX2,fontY2;//表格內容換行時兩行文字的各自XY軸座標
    public int borderX=0,borderY=0,borderWidth,borderHeight;
    public int gridX,gridY,gridWidth,gridHeight;
    public int orginWidth,orginHeight;//當前表格原點距離座標原點的寬度和高度

看起來棒棒噠,於是開始做表格標題:

/**
     * 得到表格標題grid對象
     * @param tableName
     * @param gridWith
     * @param gridHeight
     * @return
     */
    public static XXXXTableGridBean getInstanceForTableName(String tableName,int gridWith,int gridHeight){
        XXXXTableGridBean bean = new XXXXTableGridBean();
        bean.text = tableName;
        bean.font = new Font("宋體", Font.BOLD, 70);
        bean.fontColor = Color.WHITE;
        FontMetrics fm = new JLabel().getFontMetrics(bean.font);
        bean.fontX = (gridWith - fm.stringWidth(tableName))/2;//文字居中
        bean.fontY = 70;
        bean.backgroundColor = new Color(238, 154, 0);
        bean.gridX=bean.gridY=0;
        bean.gridWidth=gridWith;
        bean.gridHeight=gridHeight;
        bean.borderWidth=bean.gridWidth;
        bean.borderHeight=bean.gridHeight;
        bean.orginWidth = bean.gridWidth;
        bean.orginHeight = bean.gridHeight;
        return bean;
    }

做完標題又做表頭:

/**
     * 得到表頭grid對象
     * @param title
     * @param tableNameGridBean
     * @param preTitleGridBean
     * @param gridX
     * @param gridY
     * @param gridWidth
     * @param gridHeight
     * @return
     */
    public static XXXXTableGridBean getInstanceForTableTitle(String title,
            XXXXTableGridBean tableNameGridBean,XXXXTableGridBean preTitleGridBean,
            int gridX, int gridY, int gridWidth, int gridHeight) {
        XXXXTableGridBean bean = new XXXXTableGridBean();
        bean.text = title;
        bean.backgroundColor = new Color(159, 121, 238);
        bean.gridX=gridX;
        bean.gridY=gridY;
        bean.gridWidth=gridWidth;
        bean.gridHeight=gridHeight;
        bean.borderX=gridX;
        bean.borderY=gridY;
        bean.borderWidth=bean.gridWidth;
        bean.borderHeight=bean.gridHeight;
        bean.orginWidth = preTitleGridBean.orginWidth+preTitleGridBean.gridWidth;
        bean.orginHeight = tableNameGridBean.gridHeight;
        bean.fontColor = Color.WHITE;
        FontMetrics fm = new JLabel().getFontMetrics(bean.font);
        int fontWidth = fm.stringWidth(title);
        if(fontWidth>gridWidth){//文字寬度超過表格寬度,則換行文字
            bean.text1 = bean.text.substring(0,4);
            bean.text2 = bean.text.replace(bean.text1,"").trim();
            fontWidth = fm.stringWidth(bean.text1);
            bean.fontX = bean.orginWidth+((bean.gridWidth - fontWidth)/2);//文字居中
            bean.fontY = tableNameGridBean.gridHeight+80;
            bean.fontX2 = bean.fontX;
            bean.fontY2 =  bean.fontY + 60;
        }else{
            bean.fontX = bean.orginWidth+((bean.gridWidth - fontWidth)/2);//文字居中
            bean.fontY = tableNameGridBean.gridHeight+80;
        }
        return bean;
    }

也還行,表頭也順利生成,於是做表格內容,注意,這裏開始有第一個跨行的表格出現,而具體跨多少行,是由參數裏面的數據條數動態決定的:

public static XXXXTableGridBean getInstanceForTableContentGrid_FirstRowSpan(String val,List<XXXXDTO> allCityInfos,XXXXDTO nowCityInfo){
        XXXXTableGridBean bean = new XXXXTableGridBean();
        bean.text = val;
        int index = allCityInfos.indexOf(nowCityInfo);
        int projectSize=0;
        for(int i=0;i<index;i++){
            projectSize += allCityInfos.get(i).getProjectInfoDTOList().size();
        }
        int topHeight = nameGridHeight+titleGridHeight+projectSize*contentGridHeight;
        bean.borderX=0;
        bean.borderY=topHeight;
        bean.borderWidth=GridBeanConfigs[0].getWidth();
        bean.borderHeight=nowCityInfo.getProjectInfoDTOList().size()*contentGridHeight;
        bean.gridX=bean.borderX;
        bean.gridY=bean.borderY;
        bean.gridWidth=bean.borderWidth;
        bean.gridHeight=bean.borderHeight;
        bean.orginWidth=bean.gridWidth;
        bean.orginHeight=topHeight;
        FontMetrics fm = new JLabel().getFontMetrics(bean.font);
        int fontWidth = fm.stringWidth(bean.text);
        bean.fontX = (bean.orginWidth-fontWidth)/2;
        bean.fontY = bean.orginHeight+(bean.borderHeight-bean.font.getSize())/2+bean.font.getSize();
        return bean;
    }

然後第二個跨行格出現,這裏的跨行數也是由參數中的數據條數動態決定的:

public static XXXXTableGridBean getInstanceForTableContentGrid_GridContent(String val,XXXXTableGridBean preGridBean,
            int gridWidth,int gridHeight,int currentLineNumber,int allSubLineNumber,int nowSubLineNumber){
        XXXXTableGridBean bean = new XXXXTableGridBean();
        bean.text = val;
        bean.font =  new Font("宋體", Font.BOLD, 58/allSubLineNumber*nowSubLineNumber);
        bean.gridWidth=gridWidth;
        bean.gridHeight=gridHeight/allSubLineNumber*nowSubLineNumber;
        bean.borderWidth=bean.gridWidth;
        bean.borderHeight=bean.gridHeight/allSubLineNumber*nowSubLineNumber;
        if(preGridBean==null){
            bean.orginWidth=GridBeanConfigs[0].getWidth();
            bean.orginHeight=nameGridHeight+titleGridHeight+((currentLineNumber-1)*contentGridHeight)/allSubLineNumber*nowSubLineNumber;
        }else{
            bean.orginWidth = preGridBean.orginWidth+preGridBean.gridWidth;
            bean.orginHeight = preGridBean.orginHeight;
        }
        bean.gridX=bean.orginWidth;
        bean.gridY=bean.orginHeight;
        bean.borderX=bean.gridX;
        bean.borderY=bean.gridY;
        FontMetrics fm = new JLabel().getFontMetrics(bean.font);
        bean.fontX = bean.orginWidth+((bean.gridWidth - fm.stringWidth(val))/2);//文字居中
        bean.fontY = bean.orginHeight+bean.font.getSize()+30/allSubLineNumber*nowSubLineNumber;
        return bean;
    }

做到這,我已經呼吸急促頭昏眼花了,趕緊用聽診器聽了一下心跳!
在這裏插入圖片描述
我感覺這累死累活的不靠譜,這圖畫到後期,腦汁完全不夠用,以後維護也是一大難題,擴展性或許是比原來高了一點點,但是優勢不明顯,半斤八兩,代碼量一上去就很頭大了,就不能有其他解決方案嗎?
我於是帶着這個問題在睡夢中沉思,主要是想:既然報表要方便擴展易於變化,那最簡單的就是做成excel,excel能不能動態設置跨行呢?可以,excel能不能在服務端打開以後自動截個圖呢?不知道,但是excel倒是可以轉存爲圖片,操作excel需要藉助poi或者jxl,轉存圖片需要藉助aspose-cells包,這篇文章有介紹:https://www.cnblogs.com/abc8023/p/9336513.html
還是覺得麻煩,表格這東西最熟悉的莫過於html的table標籤,有沒有可能把做好的html轉存爲圖片呢?嘿,還真有:
基於Java內置瀏覽器DJNativeSwing的實現(https://blog.csdn.net/ltllml44/article/details/72910295)
基於qt的qtjambi包的shi(https://blog.csdn.net/redlevin/article/details/80145963)
基於Html2Image包的實現(https://www.cnblogs.com/zxf330301/p/5666592.html)
簡直像突然感覺打開了新世界的大門!
綜合對比下來,決定採用Html2Image嘗試一下:
首先構造好數據,根據數據生成好html模板頁面(這裏採用jsp):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <style type="text/css">
     html,body{margin:0px;padding:0px;}
     table{width:3680px;font-size:58px;border-top:1px solid black;border-left:1px solid black;text-align: center;}
     table td, table th{height:120px;border-right:1px solid black; border-bottom:1px solid black;line-height:58px;text-align: center;}
     table td{font-size: 58px;text-align: center;}
    </style>
</head>
<body>
<table cellpadding="0" cellspacing="0">
    <tr>
        <th colspan="14" style="height:100px;line-height: 100px;background-color:#EF9A01;color:white;font-size:70px;">XXX報表(${dto.provinceName})${dto.dayText}</th>
    </tr>
    <tr style="height: 170px;background-color: #9F7AEE;color:white;">
        <th width="220">表頭</th>
        <th width="520">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
        <th width="300">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
        <th width="240">表頭</th>
    </tr>
    <c:forEach items="${dto.dxxxDTOs}" var="d" varStatus="dStatus">
        <c:set var="preCityGridIndex" value="-1" />
        <c:set var="preProjectGridIndex" value="-1" />
        <c:forEach items="${d.projectInfoDTOList}" var="project" varStatus="pStatus">
            <c:forEach items="${project.childXXXDTOS}" var="c">
                <tr>
                    <c:if test="${dStatus.index!=preCityGridIndex}">
                        <td rowspan="${d.cityRowSpanCount}">${d.downtownName}</td>
                    </c:if>
                    <c:if test="${pStatus.index!=preProjectGridIndex}">
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item0}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item1}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item2}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item3}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item4}</td>
                        <td rowspan="${fn:length(project.childXXXDTOS)}">${project.item5}</td>
                    </c:if>
                    <td >${c.childName}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                    <td >${c.childxx}</td>
                </tr>
                <c:set var="preCityGridIndex" value="${dStatus.index}" />
                <c:set var="preProjectGridIndex" value="${pStatus.index}" />
            </c:forEach>
        </c:forEach>
    </c:forEach>
    <tr style="height: 120px;background-color:#7D26CD;color:white;line-height: 58px;">
        <td colspan="2">合計</td>
        <c:forEach items="${dto.totalCountList}" var="n" varStatus="nStatus">
            <c:if test="${nStatus.index == 5}">
                <td>&nbsp;</td>
            </c:if>
            <c:if test="${nStatus.index != 5}">
                <td>${n}</td>
            </c:if>
        </c:forEach>
    </tr>
</table>
</body>
</html>

然後用Html2Image生成圖片:
加入jar包:

<dependency>
	<groupId>com.github.xuwei-k</groupId>
	<artifactId>html2image</artifactId>
	<version>0.1.0</version>
</dependency>

生成圖片:

private void html2Image(String configId,String imageID, String province, String templateWebURL) {
        HtmlImageGenerator generator = new HtmlImageGenerator();
        generator.loadUrl(templateWebURL);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        generator.getBufferedImage();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        String entryKey = chineseCharToPinYin(province);
        if (StringUtil.isEmpty(entryKey)) {
            entryKey = String.valueOf(Math.random());
        }
        entryKey += imageID;
        imageFilePath = Constant.XXXFilePath
                .replace("{day}", DateUtil.formatNoLineDate(new Date()))
                .replace("{daily_configId}", configId)
                + "img/";
        File imgFile = new File(imageFilePath);
        if (!imgFile.exists()) {
            imgFile.mkdirs();
        }
        generator.saveAsImage(imageFilePath + entryKey + ".png"); //經試驗,在win7本地生成jpg圖片整體背景色會發紅,生成png則正常
    }

這裏要注意:在win7本地生成jpg圖片整體背景色會發紅,生成png則正常,具體何故不知。
ok,到這裏圖片文件輕鬆搞定,再加上生成html文件代碼、發送微信模板消息通知代碼,前前後後總共約500行代碼,關鍵是這種方式以後擴展很方便,輕鬆修改html模板頁面,圖片生成則和圖片模板完全解耦,維護方便有木有!

此記,以查!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章