JXLS模板导出 (https://www.cnblogs.com/foxlee1024/p/7620054.html)
上一篇文章我们介绍了JXLS和模板导出最简单的应用,现在我们要更进一步,介绍在模板中循环导出链表中的数据,在日常开发中,循环导出应该才是最常用的功能吧!
首先,我们要建立一个类模拟javabean对象。我们定义一个person类,里面有id,name和age。
public class Person {
String id;
String name;
Integer age;
public Person(String id, String name, Integer age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public Person() {
}
/** 以下省略了对应的get/set方法,请自行补齐 */
}
然后我们修改main 方法,大体上和上一篇文章是一致的,只是模拟了从数据库取出javabean的过程,将取出的javabean对象放入model中,然后传给JxlsUtils。
public class TestMain {
public static void main(String[] args) throws Exception {
// 模板位置,输出流
String templatePath = "E:/template2.xls";
OutputStream os = new FileOutputStream("E:/out.xls");
// 一个装有对象数据的链表
List<Person> persons = new ArrayList<Person>();
Person p1 = new Person("001", "张三", 18);
Person p2 = new Person("002", "李四", 19);
Person p3 = new Person("003", "王五", 20);
persons.add(p1);
persons.add(p2);
persons.add(p3);
Map<String, Object> model = new HashMap<String, Object>();
model.put("person", persons); // 把链表放进model中
JxlsUtils.exportExcel(templatePath, os, model);
os.close();
System.out.println("完成");
}
}
接下来我们设计模板:

A1单元格(A1到C1合并单元格,名字还是A1)中的注释:jx:area(lastCell="D4"),不用多讲划定模板的区域范围。依照上文讲的避免bug的原因,我们依然将区域范围设置成比模板内容至少大一圈。需要说明的是注释必须在左上角的A1中,但是可以在和A1合并后的单元格中。
A3单元格的注释:jx:each(items="person" var="p" lastCell="C3"):
jx:each() 这是一个遍历注释,默认向下增加一行。官网写可以横向遍历(增加direction 参数),我没有试过。
Items= 从java代码中传入的model的键值对中取出键名为“person”的对象,这个对象可以是集合,一般是链表。
Var= 每一条记录的变量名,命名为p。
lastCell= 遍历数据在模板中最后一个单元格的位置。
然后在遍历的区域内写上${ } 表达式,以var中定义的变量名 . 传进来的对象属性为构成。
然后保存模板,执行java代码,就能看到下图的结果了。

我们再来测试下复杂一点的模板呢?

遍历的模板有两行,而且开头一列为合并的单元格,遍历模板第二行为有底纹的单元格,看看整个表格生成后会不会崩掉?我试过几个模板导出插件,很多都是单行遍历模板没问题,一旦出现多行数据和带有合并的单元格整个模板就会崩掉。

没毛病,完美。
最后说一点:如果lastCell的参数的值要设定为一个合并后的单元格,你不知道这个合并后的单元格名字是什么,那就用鼠标点一下该单元格,excel左上角会显示这个合并单元格的名称的,就写这个名字就行。
例如上图001所在的合并单元格,用数据点击后excel提示的单元格名称为A3,其实写A3,A4都行。
2018-08-07 新增:
还有一种用法,就是横向遍历显示list的数据。其实很简单,jx:each的注释里只要写direction="RIGHT"就行了。
jx:each(items="data" var="dat" lastCell="A3" direction="RIGHT")
具体参照下图,我就不写代码了,都一样,只是模板的不同而已。

JXLS 2.4.0系列教程(三)——嵌套循环是怎么做到的
注:本文代码在第一篇文章基础上修改而成,请务必先阅读第一篇文章。
http://www.cnblogs.com/foxlee1024/p/7616987.html
本文也不会过多的讲解模板中遍历表达式的写法和说明,请先阅读第二篇文章。
http://www.cnblogs.com/foxlee1024/p/7617120.html
原本第三篇文章我打算写sheet分页的实现的,后来发现难度比第四篇循环嵌套复杂一点点,我们本着循序渐进的原则,所以先讲讲怎么实现循环嵌套。
说明是嵌套循环?说白了就是大循环套小循环,请看下图:

我们设想一下,有一条哆啦A梦的流水生产线,生产线上在生成这哆啦A梦。我们知道,哆啦A梦的口袋中有很多不同的道具,那么我们在生成的时候就把这些道具预先放进哆啦A梦的口袋吧。
每一个产品的哆啦A梦拥有的道具都是不一样的,这样我们在表中就需要到了两个循环,第一层是哆啦A梦名称和拥有的道具,第二层循环是拥有的道具名称和道具功能。
Main方法中导出的代码和原来没什么不同,所以我们先看看哆啦A梦的javabean是怎么设计的。
public class Doraemon {
private String name; // 哆啦A梦的名字
private List<Tool> tools; // 拥有的道具,这是一个链表,存放的是Tool类
public Doraemon(String name, List<Tool> tools) {
super();
this.name = name;
this.tools = tools;
}
public Doraemon() {
}
/** 以下省略所有get/set方法,请自行添加 */
}
接下来我们看看Tool类:
public class Tool {
private String toolName; // 道具名
private String toolFunction; // 道具功能
public Tool(String toolName, String toolFunction) {
super();
this.toolName = toolName;
this.toolFunction = toolFunction;
}
public Tool() {
}
/** 以下省略所有get/set方法,请自行添加 */
}
现在大家看明白了吗?其实就是在Doraemon 类中加入了一个List链表,泛型为Tool。可以预想的是,只要一层层创建好哆啦A梦这个对象(包括他的道具)后,再把多个多啦A梦放进一个链表中,然后传给Jxls工具就可以生成excel报表了。
现在我们看看Main方法是怎么写的,除了生成哆啦A梦对象的代码外,其他完全没有改动。
public class TestMain2 {
public static void main(String[] args) throws Exception {
String templatePath = "E:/template6.xls";
OutputStream os = new FileOutputStream("E:/out6.xls");
Tool tool1 = new Tool("任意门","想去哪就去哪");
Tool tool2 = new Tool("竹蜻蜓","想飞哪就飞哪");
Tool tool3 = new Tool("空气炮","空气炮");
Tool tool4 = new Tool("翻译饼干","翻译饼干");
List<Doraemon> list = new ArrayList<Doraemon>();
//制作一个哆啦A梦
Doraemon doraemon1 = new Doraemon();
//制作一号哆啦A梦的道具
List<Tool> toolList1 = new ArrayList<Tool>();
toolList1.add(tool1);
toolList1.add(tool2);
//设定一号哆啦A梦信息
doraemon1.setName("哆啦A梦一号");
doraemon1.setTools(toolList1);
//制作一个哆啦A梦
Doraemon doraemon2 = new Doraemon();
//制作二号哆啦A梦的道具
List<Tool> toolList2 = new ArrayList<Tool>();
toolList2.add(tool3);
toolList2.add(tool4);
toolList2.add(tool1);
//设定二号哆啦A梦信息
doraemon2.setName("哆啦A梦二号");
doraemon2.setTools(toolList2);
list.add(doraemon1);
list.add(doraemon2);
Map<String, Object> model = new HashMap<String, Object>();
model.put("data", list);
JxlsUtils.exportExcel(templatePath, os, model);
os.close();
System.out.println("完成");
}
}
Main方法不多解释,就是new出一个新的哆啦A梦后给他set入对应的数据(包括名字和拥有的道具),然后把一众哆啦A梦放进一个链表中,再传进model中。
重点是我们看看模板应该怎么写。

第一句不用多说,设定模板区域(我随便写的,可以写大一点):
jx:area(lastCell="I7")
第二句是第一层循环,items是Main方法中model放入的键名,里面存放有装着生产线所有哆啦A梦对象的一个链表。他的var值是dora,在获取产品名称时就要写成${dora.name}。lastCell的值写C4,D4都行。
jx:each(items="data" var="dora" lastCell="D4")
第三句是第二层循环,是哆啦A梦对象(Doraemon)里的tools属性,是个链表。由于上一层循环中Doraemon对象已经在var值中被命名成dora,所以第二层循环的items写成dora.tools。并且var值为tool。这就意味着Doraemon对象tools属性中的单条遍历记录被命名成tool。所以就可以在表达式中用${tool.toolName}方法获取道具的名字(toolName)了。
jx:each(items="dora.tools" var="tool" lastCell="D4")
不明白的同学请仔细品味一下,items相当于一个链表List,而var相当于链表中存放的单个对象。要使用这些对象就要给他们在var中命名。
写好这三句注解后,就可以回到main方法中执行代码了。

JXLS 2.4.0系列教程(四)——多sheet是怎么做到的
注:本文代码在第一篇文章基础上修改而成,请务必先阅读第一篇文章。
http://www.cnblogs.com/foxlee1024/p/7616987.html
本文也不会过多的讲解模板中遍历表达式的写法和说明,请先阅读第二篇文章。
http://www.cnblogs.com/foxlee1024/p/7617120.html
好吧,今天是国庆第二天,大清早起来先把文章给写了吧!
这篇内容主要讲解一些如何导出多sheet的报表,我将用一个学生成绩表作为讲解案例。多sheet的导出不单单是简单分sheet而已,还加入了分页的功能。效果先看下图:
Sheet1

Sheet2

大家可以看到,两个不同sheet中,表结构是一样,但是里面的学生数据有了个分页的效果。这是怎么做到的呢?
这里,我们得从模板制作讲起,知道了多sheet模板的原理后,写代码就轻松多了。

大家看红框里的注释:
jx:area(lastCell="I8")
jx:each(items="pages", var="page", lastCell="I8" multisheet="sheetNames")
第一行不用说,是划定模板区域。第二行看起来是一个很正常的遍历注释,但是里面多了一个multisheet="sheetNames"参数,这个参数就是分sheet的参数。我们连起来看就是,以整个Excel报表为一个遍历输出的对象,每一个sheet就是一条输出的记录。简单的说,就是遍历一个集合(List)items,将集合内每一个对象输出到每一个独立的sheet中。
只要写上“multisheet”属性JXLS就会自动的分sheet了。那么还有一个问题,就是multisheet属性的值sheetNames是什么意思?这个值是从model中传来的一个集合(一般用List),里面存放着每一页sheet应该起的名字,JXLS在读取pages进行分sheet遍历的时候,也会读取sheetNames进行遍历给每一个sheet改名。
接下来我们看看A4单元格的注释,大家看过第三篇文章应该知道怎么嵌套循环了,这里本质上也是一个嵌套,所以A4里的遍历标签的items写的值就是A1(红框)遍历标签里的page,然后点上对应的属性。
标签讲完了,具体怎么做呢?我们打算用一个叫做Page的类作为分页的javaBean对象,这个对象里存放着这一页应该显示的学生记录(List<Student>)和每一页的页名(sheet名)。然后将这个page对象放进一个链表中,传给model。

上图就是文章开头两张图片中学生成绩分页的链表示意图。
接下来我们开始写代码,假设数据库查询出来的结果就是一个装有学生对象的一长串链表。那么我们从最基本的学生类(student)开始写起,
public class Student {
String id;
String name;
Integer chinese;
Integer math;
Integer english;
Integer politics;
Integer history;
Integer geography;
public Student(String id, String name, Integer chinese, Integer math, Integer english, Integer politics, Integer history, Integer geography) {
super();
this.id = id;
this.name = name;
this.chinese = chinese;
this.math = math;
this.english = english;
this.politics = politics;
this.history = history;
this.geography = geography;
}
public Student() {
}
/** 以下省略了get/set方法,请自行补全 */
}
学生类没什么好说的,学生的基本信息。接下来我们写页面类(Page),也就是每一个sheet应该包含什么信息。
/**
* 该类用来封装每一页的数据
*/
public class Page {
/**
* 页面信息
*/
private String sheetName; // 每个sheet名字
private String currentPage; // 当前页
private String tolalPage; // 总页
/**
* 页面遍历的数据 List 的泛型自行设置,如果所有数据都来着同一个类就写那个类,
* 不是同一个类有继承就写继承类的泛型,没有就写问号。
*/
private List<?> data;
public Page(String sheetName, String currentPage, String tolalPage, List<?> data) {
super();
this.sheetName = sheetName;
this.currentPage = currentPage;
this.tolalPage = tolalPage;
this.data = data;
}
public Page() {
}
/** 以下省略了get/set方法,请自行补全 */
}
这个类说两句,属性data的类型的List,泛型如果你能确定传进来的对象就写上该对象,或者泛型继承,不能就写上问号。还有两个属性:currentPage和tolalPage这算是保留属性,本篇教程中没有用到,但是我还是写上了,建议同学们也可以写上,因为当前页或总页码可以往后使用工具标签时候可以判断是否最后一页。
接下来是重点了,我们已经有了从数据库中查询的一长串装有学生对象的链表,有了每一页应该装什么数据的页面对象,接下来我们要做的就是分页了。
把一长串装有学生对象的链表截成一段段的数据,然后装进page对象中,然后再把一节一节装有数据的page对象装进一个新链表中。返回给JXSL的model。
我们看下分页代码:
/**
* 此类用于分页,就是把从数据库查询出来的一个完整的List链表变成一截一截是数据。
* @author foxlee1024
*/
public class DataByPage {
static int pagesize = 3; // 每页记录数
/**
* 根据每页显示多少条数据计算总页数
* @param dataList 数据库查询的数据
*/
public static int countPages(List<?> dataList) {
int recordcount = dataList.size(); // 总记录数
return (recordcount + pagesize - 1) / pagesize;
}
public static List<Page> byPage(List<?> dataList) {
int pagecount; // 总页数
int nowDataListPoint = 0; // 读取到接收的哪一条数据
pagecount = countPages(dataList); // 计算页码
List<Page> pageList = new ArrayList<Page>(); // 页面分页
for (int i = 0; i < pagecount; i++) {
List<Object> pagedata = new ArrayList<Object>();
// 把传来的数据取出
while (nowDataListPoint < dataList.size()) {
pagedata.add(dataList.get(nowDataListPoint));
nowDataListPoint += 1;
if (nowDataListPoint != 0 && nowDataListPoint % pagesize == 0) {
break;
}
}
Page page = new Page("page_" + (i + 1), (i + 1) + "", pagecount + "", pagedata);
pageList.add(page);
}
return pageList;
}
}
原理就是遍历传进来的一长串链表,然后根据判断将他截成一段后装进链表中,然后把链表封装进page类的data属性里,接着再把page类装进链表中,然后返回装有page对象的链表。
好了,全都齐全了,我们可以开始写main方法了。
public class TestMain {
public static void main(String[] args) throws Exception {
// 模板位置,输出流
String templatePath = "E:/template5.xls";
OutputStream os = new FileOutputStream("E:/out5.xls");
List<Student> list = generateData(); // 模拟数据库获取数据
List<Page> page = DataByPage.byPage(list); // 把获取的数据进行分页转换
Map<String, Object> model = new HashMap<String, Object>();
model.put("pages", page);
model.put("sheetNames", getSheetName(page));
model.put("className", "六年三班");
model.put("teacherComment", "已核实");
model.put("directorComment", "已核实");
JxlsUtils.exportExcel(templatePath, os, model);
os.close();
System.out.println("完成");
}
/**
* Excel 的分页名(页码)的封装
* 此方法用来获取分好页的页名信息,将信息放入一个链表中返回
*/
public static ArrayList<String> getSheetName(List<Page> page) {
ArrayList<String> al = new ArrayList<String>();
for (int i = 0; i < page.size(); i++) {
al.add(page.get(i).getSheetName());
}
return al;
}
/**
* 模拟生成数据
*/
public static List<Student> generateData(){
List<Student> list = new ArrayList<Student>();
Student stu1 = new Student("001", "AAA", 10, 20, 30, 40, 50, 60);
Student stu2 = new Student("002", "BBB", 20, 30, 40, 50, 60, 70);
Student stu3 = new Student("003", "CCC", 30, 40, 50, 60, 70, 80);
Student stu4 = new Student("004", "DDD", 40, 50, 60, 70, 80, 90);
Student stu5 = new Student("005", "EEE", 50, 60, 70, 80, 90, 100);
list.add(stu1);
list.add(stu2);
list.add(stu3);
list.add(stu4);
list.add(stu5);
return list;
}
}
其他没要讲的,唯一有一个就是需要一个getSheetName() 方法,遍历获取每一个page的sheeName,然后装进链表中。然后put入model的sheetNames键里。
模板就按照前边开头我们讲解的那个模板写,接下来我们运行一下代码。
当你看到控制台打出“完成”,欣喜的打开excel文件时候,你会发现第一页sheet是空白的......从第二页开始才是真正的内容。然后你看到第一页的sheet名是你模板的sheet名,你就知道肯定是JXLS在复制模板时候没有删除模板页面造成的。

这个问题我没办法解决,我尝试过在JxlsUtils中设置JxlsHelper的属性:jxlsHelper.setDeleteTemplateSheet(true); 然而并没什么卵用,不知道是我设置的地方不对,还是别的原因。请知道解决方案的同学务必留言告知一下,万分感谢!
虽然没办法从根本上解决,但是可以找到凑活解决的办法,就是利用POI把多余的sheet给删掉,写一个工具类,代码如下:
public class DelSheet {
/**
* 删除指定的Sheet
* @param targetFile 目标文件
* @param sheetName Sheet名称
*/
public static void deleteSheet(String targetFile,String sheetName) {
try {
FileInputStream fis = new FileInputStream(targetFile);
HSSFWorkbook wb = new HSSFWorkbook(fis);
fileWrite(targetFile, wb);
//删除Sheet
wb.removeSheetAt(wb.getSheetIndex(sheetName));
fileWrite(targetFile, wb);
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 写隐藏/删除后的Excel文件
* @param targetFile 目标文件
* @param wb Excel对象
* @throws Exception
*/
public static void fileWrite(String targetFile,HSSFWorkbook wb) throws Exception{
FileOutputStream fileOut = new FileOutputStream(targetFile);
wb.write(fileOut);
fileOut.flush();
fileOut.close();
}
}
接下来我们就在main方法中,执行完excel导出的代码后调用下删除sheet的语句:
JxlsUtils.exportExcel(templatePath, os, model);
os.close();
// 删除多出来的sheet
DelSheet.deleteSheet("E:/out5.xls", "template");
System.out.println("完成");
传入的是excel导出的路径和要删除的sheet名字,其实可以传入sheet的编号的,但是我觉得传入名字可以防止误删除。要传入编号的同学请自行修改deleteSheet方法,wb.getSheetIndex(sheetName)既可以接收String也可以接收Integer,如果我没有记错的话。
我们再运行一遍代码看看,我们就可以看到开头的那样的效果了!

一般来说,这篇文章到这里应该就结束了。但是还有一个问题,就是如果我想做每一个学生的成绩分页呢?怎么做?就是一个学生独占一个sheet。
我不卖关子了,我们记得,每一个sheet的信息其实是对应一个page对象的,这个对象里有一个List<Student> data,所以在一个sheet里才能够将这个data取出,交给模板进行遍历。如果我们要一个学生独占一个sheet,我们只需要在Page类中加入一个对象类型的属性就可以了,在模板中直接取这个对象具体的属性。这也是我为什么要用page对象的原因,扩展性高。
public class Page {
/**
* 页面信息
*/
private String sheetName; // 每个sheet名字
private String currentPage; // 当前页
private String tolalPage; // 总页
/**
* 页面遍历的数据 List 的泛型自行设置,如果所有数据都来着同一个类就写那个类,
* 不是同一个类有继承就写继承类的泛型,没有就写问号。
*/
private List<?> data;
/**
* 一页只保存一个人的信息
*/
private Object onlyOne;
/** 省略构造器和其他get/set方法 */
public Object getOnlyOne() {
return onlyOne;
}
public void setOnlyOne(Object onlyOne) {
this.onlyOne = onlyOne;
}
接下来我们修改一下main方法,把原本用来分页的List<Page> page = DataByPage.byPage(list)注释掉。然后新加一句List<Page> page = individual(list) :
public static void main(String[] args) throws Exception {
// 模板位置,输出流
String templatePath = "E:/template5.xls";
OutputStream os = new FileOutputStream("E:/out5.xls");
List<Student> list = generateData(); // 模拟数据库获取数据
//List<Page> page = DataByPage.byPage(list); // 把获取的数据进行分页转换
List<Page> page = individual(list); // 一页一个人
Map<String, Object> model = new HashMap<String, Object>();
model.put("pages", page);
model.put("sheetNames", getSheetName(page));
model.put("className", "六年三班");
model.put("teacherComment", "已核实");
model.put("directorComment", "已核实");
JxlsUtils.exportExcel(templatePath, os, model);
os.close();
// 删除多出来的sheet
DelSheet.deleteSheet("E:/out5.xls", "template");
System.out.println("完成");
}
Individual() 方法的代码如下:
/**
* 将数据获取的数据封装成一页一个人的List
*/
public static List<Page> individual(List<Student> list){
List<Page> pages = new ArrayList<Page>();
for(int i = 0; i < list.size(); i++){
Page p = new Page();
p.setOnlyOne(list.get(i));
p.setSheetName(list.get(i).getName());
pages.add(p);
}
return pages;
}
接收传进来的List<Student> list链表数据,然后遍历该链表,将其封装Page对象中,并且别忘了设置sheetName。
模板是这样的:

模板中不需要两层循环了,直接在页面中取page的onlyOne属性(装的是Student对象)的值就好了。例如:${page.onlyOne.id}、${page.onlyOne.chinese}。
行了,执行代码看一下效果:

到这里就真的是结束了,这篇文章内容有点多,主要讲了分sheet导出的方法,本质上只是一种的,我先讲了比较复杂的,带有分页效果的分sheet。然后再讲了单纯的分sheet。介于代码比较多,我就把源码发上了让大家一起研究。源码里的jar包我给删掉了,要使用就在第一篇文章先下载jar包吧!
顺便说一句,前几篇教程的源码是不存在的,因为我每一篇都是在原来代码基础上改的,其实所有代码我都发上来了。这篇我是专门开新工程弄的例子。哦,还有一句,家里eclipse太久不用了,编码居然是GBK而不是通用的UTF-8(写完了才发现),大家看着改吧。
jar包下载地址(内有官方2.4.0版本,2.4依赖的jar包,klguang 的demo):这里下载
本文源码下载(内有模板,无jar包,请配合上边jar包使用): 源码下载
JXLS 2.4.0系列教程(四)——拾遗 如何做页面小计
注:阅读本文前,请先阅读第四篇文章。
http://www.cnblogs.com/foxlee1024/p/7619845.html
前面写了第四篇教程,发现有些东西忘了讲了,这里补回来。
忘了讲两个点:
1.本页小计怎么做。
2.在多sheet时候本页小计会出现错乱,怎么解决。

本页小计就是这个东西,在导出报表时候会自动统计累加遍历出来的数据。
实现起来很简单,看下图:

想统计语文成绩的遍历数据${stu.chinese},就只要在将要统计的单元格里写上“=SUM(C4)”就行了。C4就是语文成绩的单元格。Jxls会自动帮你把统计范围扩大的。
数学成绩就写=SUM(D4),英语成绩就写=SUM(E4),以此类推。
还有一个问题,就是在多sheet中进行这样的统计的话,默认情况下会出现问题,就是sheet1统计的范围是sheet1的,sheet2统计的范围还是sheet1的,sheet3统计的范围依然是sheet1的。
这个问题很好解决,官方也提供了解决方案:
就是在JxlsUtils中修改JxlsHelper的属性:
jxlsHelper.setUseFastFormulaProcessor(false).processTemplate(context, transformer);
我之前代码里的JxlsUtils中已经修改了。这里只是告诉各位写这个是意义是什么。
JXLS 2.4.0系列教程(五)——更进一步的应用和页面边距bug修复
注:本文代码建立于前面写的代码。不过不看也不要紧。
前面的文章把JXLS 2.4.0 的基本使用写了一遍,现在讲讲一些更进一步的使用方法。我只写一些我用到过的方法,更多的高级使用方法请参考官网。
一、Map的应用
从一开始,官方dome使用的model中的引用对象一直都是一个javaBean对象。后来我发现其实可以直接往model中放一个map,然后也是直接用“点”的方式在excel中取出数据。
下面用第四篇文章——多sheet的源码做演示,我们先修改main方法,加入一个HashMapMap:
public static void main(String[] args) throws Exception {
// 模板位置,输出流
String templatePath = "E:/template5_2.xls";
OutputStream os = new FileOutputStream("E:/out5_2.xls");
List<Student> list = generateData(); // 模拟数据库获取数据
//List<Page> page = DataByPage.byPage(list); // 把获取的数据进行分页转换
List<Page> page = individual(list); // 一页一个人
// 定义一个Map
Map<String, String> tableInfo = new HashMap<String, String>();
tableInfo.put("className", "六年三班2");
tableInfo.put("teacherComment", "已核实2");
tableInfo.put("directorComment", "已核实2");
Map<String, Object> model = new HashMap<String, Object>();
model.put("pages", page);
model.put("sheetNames", getSheetName(page));
model.put("tableInfo", tableInfo);
//model.put("className", "六年三班");
//model.put("teacherComment", "已核实");
//model.put("directorComment", "已核实");
JxlsUtils.exportExcel(templatePath, os, model);
os.close();
// 删除多出来的sheet
DelSheet.deleteSheet("E:/out5_2.xls", "template");
System.out.println("完成");
}
把原来的model中put的多注释掉,然后把定义的Map tableInfo 放入model中。
我们看看模板中怎么取:

直接取新加入的map的键名,再“点上”tableInfo的键名。

就这样轻松的取出来了。Map也是可以直接放进model中的,有时候Map用起来更加灵活,这就要自行选择了。
二、使用工具
工具的使用先看JxlsUtils类。
public class JxlsUtils{
public static void exportExcel(InputStream is, OutputStream os, Map<String, Object> model) throws IOException{
Context context = PoiTransformer.createInitialContext();
if (model != null) {
for (String key : model.keySet()) {
context.putVar(key, model.get(key));
}
}
JxlsHelper jxlsHelper = JxlsHelper.getInstance();
Transformer transformer = jxlsHelper.createTransformer(is, os);
//获得配置
JexlExpressionEvaluator evaluator = (JexlExpressionEvaluator)transformer.getTransformationConfig().getExpressionEvaluator();
//设置静默模式,不报警告
evaluator.getJexlEngine().setSilent(true);
//函数强制,自定义功能
Map<String, Object> funcs = new HashMap<String, Object>();
funcs.put("utils", new JxlsUtils()); //添加自定义功能
evaluator.getJexlEngine().setFunctions(funcs);
//必须要这个,否者表格函数统计会错乱
jxlsHelper.setUseFastFormulaProcessor(false).processTemplate(context, transformer);
}
public static void exportExcel(File xls, File out, Map<String, Object> model) throws FileNotFoundException, IOException {
exportExcel(new FileInputStream(xls), new FileOutputStream(out), model);
}
public static void exportExcel(String templatePath, OutputStream os, Map<String, Object> model) throws Exception {
File template = getTemplate(templatePath);
if(template != null){
exportExcel(new FileInputStream(template), os, model);
} else {
throw new Exception("Excel 模板未找到。");
}
}
//获取jxls模版文件
public static File getTemplate(String path){
File template = new File(path);
if(template.exists()){
return template;
}
return null;
}
// 日期格式化
public String dateFmt(Date date, String fmt) {
if (date == null) {
return "";
}
try {
SimpleDateFormat dateFmt = new SimpleDateFormat(fmt);
return dateFmt.format(date);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
// if判断
public Object ifelse(boolean b, Object o1, Object o2) {
return b ? o1 : o2;
}
}
我们看到有一行:funcs.put("utils", new JxlsUtils()); //添加自定义功能
//函数强制,自定义功能
Map<String, Object> funcs = new HashMap<String, Object>();
funcs.put("utils", new JxlsUtils()); //添加自定义功能
evaluator.getJexlEngine().setFunctions(funcs);
这行代码的意思就是设置一个工具类JxlsUtils(),在excel中的引用为utils 。当你在excel中使用 utils 时就会调用JxlsUtils() 类(可以是其他的类)的对应方法。
我们来看下模板怎么写:

在模板中,你可以这样调用工具类:
${utils:ifelse(page.currentPage == page.tolalPage,"最后一页","")}
JXLS会自动找到utils这个键对应的值 new JxlsUtils() ,然后在JxlsUtils类中找对应的ifelse方法,然后根据方法接收的参数自动把你在excel写的参数传进去,再把方法的返回值显示在excel中。
还记得我前面保存进Page中的当前页码和总页码这两个属性吗?现在就是使用使用他们了。用他们可以判断现在的sheet是不是最后一页,最后一页可以显示一些别的数据。一般说ifelse这个已经够用了,但是如果你有更加复杂的操作,你也可以自行定制自己需要的工具类。
三、JXLS 是支持模板多个sheet输出数据的
这句话很奇怪,但是我一下子没想出别的表达。这个多sheet不是前面我们讲的分sheet,是说你模板有本身就已经有很多个sheet了,每个sheet显示的表都不一样,不用担心,你存放进model中的数据是可以跨sheet的。只要你想取出就能取出,这个我就不演示了。
四、页面边距bug
这个bug一般人是真没有发现的,但是对我来说就是差点要了老命....我们公司项目导出的excel报表比较大,一张A3纸都只是勉勉强强够打印。所以平常边距要设定得很小,这个边距的bug就是你在模板中设定的页面边距并不会拷贝进你导出的文件的。
你可能试验了一下说,没有啊,我的没问题啊。我说的不会拷贝仅是在分页分sheet导出时候出现的问题。我举个例子,你在模板中设定了页面边距,然后导出,JXLS会先把你的模板复制进导出的文件中,然后建立一个新的sheet,然后在根据模板的设定把内容拷贝进新sheet中,然后再建立新sheet不断循环。
问题就出现在把模板拷进新sheet的过程中,如果你改变了模板的页面边距,再进行一个分sheet导出,不要用POi删除导出文件第一页空的模板sheet,你查看第一页的页边距你发现是已经修改过的页边距,而你查看后面其他sheet的页边距就会是excel默认的页边距了。
不明白不要紧,你只要知道我已经解决了就好(新版本2.4.2我不知道作者有没有修复)。

问题就出现在这个PoiUtil的文件上,我们打开看看,看到copySheetProperties(Sheet src, Sheet dest)这个方法,这个方法就是拷贝sheet属性的。
public static void copySheetProperties(Sheet src, Sheet dest){
dest.setAutobreaks(src.getAutobreaks());
dest.setDisplayGridlines(src.isDisplayGridlines());
dest.setVerticallyCenter(src.getVerticallyCenter());
dest.setFitToPage(src.getFitToPage());
dest.setForceFormulaRecalculation(src.getForceFormulaRecalculation());
dest.setRowSumsRight(src.getRowSumsRight());
dest.setRowSumsBelow( src.getRowSumsBelow() );
//增加页边距保存
dest.setMargin(Sheet.TopMargin, src.getMargin(Sheet.TopMargin));
dest.setMargin(Sheet.LeftMargin, src.getMargin(Sheet.LeftMargin));
dest.setMargin(Sheet.RightMargin, src.getMargin(Sheet.RightMargin));
dest.setMargin(Sheet.BottomMargin, src.getMargin(Sheet.BottomMargin));
copyPrintSetup(src, dest);
}
这个方法内少写了对sheet页边距的拷贝,加上就行。加上后怎么放回包里?你可以自行打包....你也可以在你的项目目录上建立一个与PoiUtil类一模一样的路径,然后在里面放上修改后的类。Java在找包时候会优先找你写的路径,这样就覆盖掉了包路径了。

在src的根目录上,和com同级。
好了,这一篇文章就写到这里,写这篇文章时候我是懒的,我懒得再去做案例验证了,特别是最后的那个页边距的bug,我在当初我在网络上根本找不到和我一样的问题。我也是花了些功夫才找到这个原因的。本来我应该做下案例把这个bug讲清楚些,但是我写到这里我已经有些懒了.....说白了这个bug就是只有在分sheet导出时候才会出现的问题,能自己复现的就自己研究下,不能的,知道怎么解决了就行了。
下载PoiUtil类: 点这里下载
JXLS 2.4.0系列教程(六)番外篇——导出图片(完结)
突然想起来有同学说过能不能导出图片,本来我是想说不懂的,后来我上官网查了查,还挺容易。我就简短的写一写怎么导出图片。
官方提供了导出图片标签:
jx:image(lastCell="D10" src="image" imageType="PNG")
这是在java中的代码调用:
InputStream imageInputStream = ImageDemo.class.getResourceAsStream("business.png");
byte[] imageBytes = Util.toByteArray(imageInputStream);
context.putVar("image", imageBytes);
我把他写进以前的源码中,给同学们看看怎么导出图片。
还记得以前写过一个person类吗?
public class Person {
String id;
String name;
Integer age;
byte[] img;
/** 省略构造器和get/set方法 */
}
我们加入一个新的属性byte[] ,byte数组img。
然后我们看看main方法怎么写。
public static void main(String[] args) throws Exception {
// 模板位置,输出流
String templatePath = "E:/template3.xls";
OutputStream os = new FileOutputStream("E:/out4.xls");
// 文件流,输入一张叫fly的png图片
InputStream imageInputStream = new FileInputStream("E:/fly.png");
// 使用工具方法把流转成byte数组
byte[] imageBytes = Util.toByteArray(imageInputStream);
// 一个装有对象数据的链表
List<Person> persons = new ArrayList<Person>();
Person p1 = new Person("001", "张三", 18);
Person p2 = new Person("002", "李四", 19);
Person p3 = new Person("003", "王五", 20);
// 把图片转换的字节数组存进person对象中
p1.setImg(imageBytes);
p2.setImg(imageBytes);
p3.setImg(imageBytes);
persons.add(p1);
persons.add(p2);
persons.add(p3);
Map<String, Object> model = new HashMap<String, Object>();
model.put("person", persons); // 把链表放进model中
JxlsUtils.exportExcel(templatePath, os, model);
os.close();
System.out.println("完成");
}
代码中有个流转字节数组的工具类Util.toByteArray,这个工具类文章后我会提供下载链接。
下面看下导出模板:

老生常谈的就不说了,说下画红框框的:
jx:image(lastCell="D4" src="p.img" imageType="PNG")
这是一个用来导出图片的标签,依照官方说明imageType图片类型默认是PNG,支持:PNG, JPEG, EMF, WMF, PICT, DIB。
src是model中传入的字节数组byte[]。
lastCell是图片结束位置,会自动拉伸填充完你定义的格子。比如你在A1中加入了这个注解,lastCell写C6,就会把A1到C6都拉伸填充完。
行了,执行代码导出吧。

这里是把字节数组放在遍历对象里的遍历出来的,你要是不想遍历,而是只想显示一张图片,你看了这么多篇我写的文章,应该不用我说该怎么做吧?就是直接把字节数组放进model中,在excel中取出就行了。
官方图片导出简介:
http://jxls.sourceforge.net/reference/image_command.html#
下载工具类依赖的jar包:commons-codec-1.10.rar
我原本的计划是写上五篇JXLS2.4的基础教程的,现在倒是写了六篇,花了两天的时间。目前看来算是写完了。未来会不会有更新机会要看我能把这个工具用到什么程度了。这个工具还有很多的功能,我估计都不大可能用得上了!哈哈哈。先这样吧,写了两天,剩下的国庆时间让我好好的过个国庆!
有什么意见请在文章后留言,如果你要问我某某某怎么实现,你就先翻翻我有没有写过,如果我没有写过,估计我也不懂。
我也不知道有没有代码复制下来执行不了的。这么几行代码都有报错,随缘吧!


浙公网安备 33010602011771号