衆所周知,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> </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模板頁面,圖片生成則和圖片模板完全解耦,維護方便有木有!
此記,以查!