1、技术方案
- freemark生成doc
- word文本中的图片通过调用phantomjs执行echarts,返回图片的base64编码。
2、具体步骤
2.1创建word模板
创建一个word文档,设计好报表内容及格式,使用占位符替换掉内容。
2.2word另存为xml
- 这里选择word2013,没有选择word2003,因为2003图片一致无法渲染成功。
- 使用nodpad++格式化打开xml,手动调整被分割开的xml文档。
- 把xml模板中图片base64编码替换为占位符,其中图片的编号对象在模板的图片的顺序,例如image10.png,代表模板中第10张图片。
- 在表格循环的地方使用<#list datalist as item><#/list>
调整好xml内容后,修改后缀改为.ftl。
2.3phantomjs执行echarts生成图片
phantomjs安装教程网上还多,这里不再说明,注意执行js时不支持es6语法。编写生成图片的echarts.html,返回生成的图片base64字符串。
etharts.html内容为:
<html>
<meta charset="UTF-8">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<head>
<script src="./jquery-3.4.1.min.js"></script>
<script src="./echarts.min.js"></script>
</head>
<body>
<div id="charsDom" style="height:350px;width:700px"></div>
</body>
<script>
var charsDom = null ;
function getPie(param){
var title = param.title , data = param.data;
var option = {
title : {
text: title,
x:'center'
},
animation:false,
tooltip : {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient: 'vertical',
left: 'left',
},
series : [
{
name: title,
type: 'pie',
radius : '55%',
center: ['50%', '60%'],
data:data,
label:{
formatter:"{b}:({d}%)"
},
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
return option;
}
function getBar(param){
var title = param.title , data = param.data;
var option = {
title:{
text:title,
x:'center'
},
animation:false,
color: ['#3398DB'],
tooltip : {
trigger: 'axis',
axisPointer : { // 坐标轴指示器,坐标轴触发有效
type : 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
grid: {
left: '10px',
right: '50px',
bottom: '10px',
top: '10px',
containLabel: true
},
yAxis : [{
type: 'category',
minInterval: 1,
min:0,
data: data.yData,
axisTick:{
interval:0
},
axisLabel: {
interval: 0,
},
max:function (value) {
if (value.max < 10 ){
value.max = 10;
} else {
value.max = value.max;
}
return value.max;
},
scale:true
}],
xAxis : [{
name:"数量",
type: 'value',
boundaryGap: true,
min:0,
minInterval: 1,
axisTick:{
interval:0
},
axisLabel: {
interval: 0,
rotate: 0,
},
}],
series : [
{
name:title,
type:'bar',
barWidth: '60%',
data:data.xData,
label: {
normal: {
position: 'right',
show: true
}
},
}
]
};
return option;
}
function noData(){
var option = {
graphic:[
{
type: 'rect', left: 'center', top: 'center', z: 100,
shape: {
width: 700,
height: 350
},
style: {
fill:'#f5f5f9'
}
},
{
type: 'text', left: 'center', top: 'center', z: 100,
style: {
fill: '#999',
text: '暂 无 数 据 ',
font: '18px Microsoft YaHei'
}
}
],
series:[]
};
var el = document.getElementById('charsDom');
if (charsDom !=null) {
echarts.dispose(document.getElementById('charsDom'));
}
charsDom = echarts.init(document.getElementById('charsDom'));
charsDom.setOption(option);
// 将第一个画布作为基准。
var baseCanvas = $(el).find("canvas").first()[0];
if (!baseCanvas) {
return false;
};
var width = el.width;
var height = el.height;
var ctx = baseCanvas.getContext("2d");
//遍历,将后续的画布添加到在第一个上
$(el).find("canvas").each(function(i, canvasObj) {
if (i > 0) {
var canvasTmp = $(canvasObj)[0];
ctx.drawImage(canvasTmp, 0, 0, width, height);
}
});
console.log(baseCanvas.toDataURL('image/png',0.5).replace("data:image/png;base64,",""));
//获取base64位的url
return baseCanvas.toDataURL('image/png',0.5).replace("data:image/png;base64,","");
}
function returnEchartImg(echartObj){
return charsDom.getDataURL();
}
function getImage(param , fun){
var el = document.getElementById('charsDom');
if (charsDom !=null) {
echarts.dispose(document.getElementById('charsDom'));
}
charsDom = echarts.init(document.getElementById('charsDom'));
charsDom.setOption(fun(param));
// 将第一个画布作为基准。
var baseCanvas = $(el).find("canvas").first()[0];
if (!baseCanvas) {
return false;
};
var width = el.width;
var height = el.height;
var ctx = baseCanvas.getContext("2d");
//遍历,将后续的画布添加到在第一个上
$(el).find("canvas").each(function(i, canvasObj) {
if (i > 0) {
var canvasTmp = $(canvasObj)[0];
ctx.drawImage(canvasTmp, 0, 0, width, height);
}
});
console.log(baseCanvas.toDataURL('image/png',0.5).replace("data:image/png;base64,",""));
//获取base64位的url
return baseCanvas.toDataURL('image/png',0.5).replace("data:image/png;base64,","");
}
</script>
</html>
2.4生成模板
通过java shell调用phantomjs执行etharts.html
//创建无界面的浏览器对象
private static PhantomJSDriver getPhantomJSDriver() {
//设置必要参数
DesiredCapabilities dcaps = new DesiredCapabilities();
dcaps.setCapability("acceptSslCerts", true);
dcaps.setCapability("takesScreenshot", true);
dcaps.setCapability("cssSelectorsEnabled", true);
dcaps.setJavascriptEnabled(true);
dcaps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY, "/usr/bin/phantomjs");
//创建无界面浏览器对象
return new PhantomJSDriver(dcaps);
}
//获取报表图的base64编码
public static String getImage(String data, String function) {
WebDriver driver = null;
try {
driver = getPhantomJSDriver();
driver.get("file:///root/echarts/echarts.html");
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
JavascriptExecutor js = (JavascriptExecutor) driver;
if (data.length() == 0) {
return (String) js.executeScript("return noData()");
} else {
return (String) js.executeScript("return getImage(" + data + "," + function + ")");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (driver != null) {
driver.close();
driver.quit();
}
}
return null;
}
定义模板数据类,并生成模板,数据类的变量名与我们模板中占位变量名相同。
public class ReportModel{
private List<xx> xxxList;
private String date;
}
Configuration configuration = new Configuration(Configuration.VERSION_2_3_21);
configuration.setClassForTemplateLoading(this.getClass(), "/template/");
configuration.setClassicCompatible(true);
configuration.setDefaultEncoding("UTF-8");
Template template = configuration.getTemplate("报表模板.ftl");
String wordName = projectId + "_" + taskId + ".doc";
File outFile = new File(taskFolderPath + "/" + wordName);
Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
template.process(reportModel, writer);
writer.close();
生成word如下:
但是生成的doc文档,底层模式的还是xml,转化为pdf时,结果pdf的内容全部为xml,暂时没有解决这个问题。