一、Excel基本导出
1、 默认模板目录的配置
可以通过配置文件配置一个模板的目录来获取目录中的模板,当然这不是必须的,只是为了统一代码管理。
1.1 非web工程配置
在classpath:目录下smile.properties文件中配置 export.templateDir属可以是绝对路也可以是相对路径:
例如:
export.templateDir=${user.dir}/src/
例如:
export.templateDir=D:/template
1.2 Web项目配置
web项目的时候除可在smile.properties中配置外,还实现了另一种可以在web.xml文件中的配置,此方法与其它使用listener加载配置的原理一致
例如:
<context-param>
<param-name>xlsTemplateFileDir</param-name>
<param-value>/WEB-INF/xlsTemplate</param-value>
</context-param>
也可以是classpath下
<context-param>
<param-name>xlsTemplateFileDir</param-name>
<param-value>classpath:xlsTemplate</param-value>
</context-param>
再配置一个listener加载配置
<listener>
<listener-class>org.smile.report.excel.XlsExportWebSupportListener</listener-class>
</listener>
1.3 配置信息使用
在配置的templateFileDir 目录下创建模板一个导出模板。
使用方法 XlsExportTemplate.getFilePath("/vmi/deliveryGoods.xls") 来获取模板的路径
代码样例:
XlsExportTemplate temp = new XlsExportTemplate();
try {
temp.loadXlsTemplate(XlsExportTemplate.getFilePath("/vmi/deliveryGoods.xls"));
setReponseXls("VMI送货单_",".xls");
List list=DeliveryQueryUtil.queryPageDeliery(vmiDeliveryService, query);
temp.fillDataSource(list);
BufferedOutputStream os = new BufferedOutputStream(getResponse().getOutputStream());
temp.write(os);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
设置模板样例:
2、Excel模板
通用的模板样式为第一个sheet页中设置导出信息,第一行为导出信息的名称,第二行为导出信息对应导出对象的字段名。
2.1 对应字段名
如要显示名称 在单元格中 输入 name
如果属性是一个象则使用“ . ” 的方式调用,如 : id.name
属性可以支持 List 如 sublist.name 则会循环显示 对象sublist 中的对象的name 属性
2.2 转换函数
如果需要对对象属性进行转换,可以注册函数来处理
实现 org.smile.report.excel.Function 接口
package org.smile.report.excel;
public abstract class Function{
/**
* 转换对象值
* @param oneData 行对象
* @param exp 表达式
* @param expValue 字段值
* @return
*/
public abstract Object convert(Object oneData,String exp,Object expValue);
/**
* 是否需要字段的值
* 当返回false时 convert方法中expValue是不会有值的
* 返回true时convert方法中expValue会根据exp表达式的值从oneData中获取值
* @return
*/
public boolean needFieldValue(){
return true;
}
}
注册后 ,模板中 使用 函数名{字段名} 方式调用。
如:
temp.registerFunction("f", new Function() {
@Override
public Object convert(Object oneData, String exp,
Object expValue) {
ReportQuery reportData=(ReportQuery)oneData;
String status=reportData.getVstatus();
if(StringUtils.isEmpty(status)){
return "未确认";
}else if("5".equals(status)){
return "待采购确认";
}else if("0".equals(status)){
return "已确认";
}
});
在excel单元格中填写 f{status} 就会调用转换函数进行显示转换
2.3显示常量
如需在xls中显示常量 可以通过 /**增加一个参数*/
public void addParam(String name,Object value)
方法添加常量 在模板中 以#name 方式调用
例如:
XlsExportTemplate temp=new XlsExportTemplate();
temp.addParam("china", "中华人民共和国");
在单元格中输入#china 输出的内容为中华人民共和国
2.4 Ognl表达式支持
导出模板中还可以对ongl表达式支持,只需要将ognl.jar加载到classpath中,在单元格中使用${exp} 或 ognl{exp} 方式对ognl表达式支持输出
例${state==1?'成功':'失败'} 对ognl的使用请参考网上的资料
2.5 内部变量
导出模板中还支持对 XlsExportTemplate 的成员变量进行输出显示,使用##fieldName 方式进行输出显示
/** 当前数据填充到的序号 */
protected int dataRowNumber = 1;
##dataRowNumber 可会输出当前填充到的数据行号
/** 数据的长度 */
protected int dataSize;
二、Excel导出高级特性
1、数据填充行
数据填充行是可以设置的,只是在默认情况下是行二行,第一行为描述,当不是第二行的时候需要在代码中设置。
/**
* 设置 数据填充名称行号
* @param dataNameRowIndex
*/
public void setDataNameRowIndex(int dataNameRowIndex) {
this.dataNameRowIndex = dataNameRowIndex;
}
例如:
XlsExportTemplate temp = new XlsExportTemplate();
temp.setDataNameRowIndex(4); temp.loadXlsTemplate(temp.getFilePath("/vmi/deliveryGoodsNo.xls"));
此模板就是以第5行做为填充数据行,所以设置索引的时候是4
2、填充数据行后的内容
有的时候在填充数据行后面的行还需要固定内空的行的时候,现要在代码中设置模板底部的索引和底部行数。
/**
* 设置页脚信息
* @param bottomRowIndex 页脚开始行索引
* @param rowCount 页脚行数
*/
public void setBottomRowIndex(int bottomRowIndex, int rowCount) {
this.bottomRowIndex = bottomRowIndex;
this.bottomRowEnd = bottomRowIndex + rowCount-1;
}
使用代码如下:
XlsExportTemplate temp = new XlsExportTemplate();
setReponseXls("机加工送货单_",".xls");
temp.loadXlsTemplate(temp.getFilePath("/auinf/auinfDeliveryNote.xls"));
temp.setDataNameRowIndex(8);
temp.setBottomRowIndex(28, 7);
模板:
3、存在头信息和尾信息
需要导出的模板存在头信息和尾信息的情况
3.1例子
XlsExportTemplate template=new XlsExportTemplate();
try {
List list=new LinkedList();
for(int i=0;i<2;i++){
for(int j=0;j<2;j++){
for(int k=0;k<2;k++){
Map map=new HashMap();
map.put("a", "A"+i);
map.put("b", "B"+i);
map.put("c", "C"+j);
map.put("d", k);
list.add(map);
}
}
}
template.setMergeConfig(new MergeConfig(new String[]{"a"}, new String[]{"a","${b+1}","b"}));
template.addMergeConfig(new MergeConfig(new String[]{"a","c"}, new String[]{"c"}));
Map context=new HashMap();
context.put("dataSource", list);
template.setDataNameRowIndex(5);
template.setBottomRowIndex(6, 3);
context.put("name", "广东迈瑞");
context.put("date", DateUtils.parseDate("2017-09-08"));
CellImage image= new CellImage(new FileInputStream("D:/temp/test.jpg"),Workbook.PICTURE_TYPE_JPEG);
image.setWidth(3.5F);
image.setHeight(1f);
image.setLeft(10);
image.setTop(20);
context.put("image",image);
template.loadXlsTemplate("d:/temp/test.xls");
template.addParam("ttt", "胡真山");
template.registerFunction("ff", new Function(){
@Override
public Object convert(Object oneData, String exp, Object expValue) {
return "测试";
}
});
template.fillDataSource(context);
3.2模板
3.3输出
4、插入图片
通过代码在要输出的对象中加入图片对象,在模板中跟输出普通字段属性一个设置即可
代码:
CellImage image= new CellImage(new FileInputStream("D:/temp/test.jpg"),Workbook.PICTURE_TYPE_JPEG);
image.setWidth(3.5F);
image.setHeight(1f);
image.setLeft(10);
image.setTop(20);
context.put("image",image);
在模板中只要设置 ${image} 就会输出为图片
5、单元格合并
在template中添加合并配置即会对单元格行进行合并
template.addMergeConfig(new MergeConfig(new String[]{"a","c"}, new String[]{"c"}));
上面的代码实现了:a 和 c 的值相同的行 对 c 列进行合并
5.1 MergeConfig 类的代码
package org.smile.report.excel;
import java.util.HashMap;
import java.util.Map;
import org.smile.report.poi.ObjectMergeSet;
/**
* 设置要合并的列
* @author 胡真山
*/
public class MergeConfig extends ObjectMergeSet{
/***
* 要合并的列名
*/
protected String[] mergeName;
protected Map<String,Integer> nameIndex=new HashMap<String,Integer>();
/**
* @param keyName
* @param mergeColumn 要合并的列的索引
*/
public MergeConfig(String keyPropertyName, Integer[] mergeColumn){
this.propertyName=new String[]{keyPropertyName};
this.mergeColumn=mergeColumn;
}
/**
* 以标题名称来标记合并
* @param keyName
* @param mergeName
*/
public MergeConfig(String keyPropertyName, String[] mergeName){
this.propertyName=new String[]{keyPropertyName};
this.mergeName=mergeName;
}
/**
* @param keyName
* @param mergeColumn 要合并的列的索引
*/
public MergeConfig(String[] keyPropertyName, Integer[] mergeColumn){
this.propertyName=keyPropertyName;
this.mergeColumn=mergeColumn;
}
/**
* 以标题名称来标记合并
* @param keyName
* @param mergeName
*/
public MergeConfig(String[] keyPropertyName, String[] mergeName){
this.propertyName=keyPropertyName;
this.mergeName=mergeName;
}
/**
* 从xls的标题初始化出要合并的列的索引
* @param names
*/
protected void initMerge(String[] names,int firstCellNum){
initNameIndex(names);
if(mergeColumn==null){
initMergeColumn(names,firstCellNum);
}
}
public Integer getColumnIndex(String name){
return nameIndex.get(name);
}
protected void initNameIndex(String[] names){
int i=0;
for(String n:names){
nameIndex.put(n, i++);
}
}
protected void initMergeColumn(String[] names,int firstCellNum){
mergeColumn=new Integer[mergeName.length];
int i=firstCellNum;
for(String n:mergeName){
Integer idx=nameIndex.get(n);
if(idx==null){
throw new NullPointerException("不存在的数据名称列:"+n);
}
mergeColumn[i]=idx;
i++;
}
}
}
6、自定义行的高度
通过方法重写实现高度的自定义。
例子:
/**
* 通过一定策略对行高度进行控制
* @author 胡真山
*
*/
protected class ExportContractTemplete extends XlsExportTemplate{
private float keyRowHeight;
private float rate;
private float height;
@Override
protected Row onCreateDataRow(Sheet sheet, Row keyRow, Object rowData, int rowIndex) {
Row row=super.onCreateDataRow(sheet, keyRow, rowData, rowIndex);
if(rowIndex==dataNameRowIndex){
keyRowHeight=keyRow.getHeightInPoints();
rate=40f/dataSize;
if(rate>2.5){
rate=2.5f;
}else if(rate<0.8){
rate=0.8f;
}
height=keyRowHeight*rate;
}
if(dataSize<20){
TDacContractDetail obj=(TDacContractDetail)rowData;
int length=obj.getMaktx().length();
if(obj.getMaktx().length()>20){
float nrate=(length/10)*0.8f;
if(nrate>5){
nrate=5;
}else if(nrate<2.5){
nrate=2.5f;
}
row.setHeightInPoints(keyRowHeight*nrate);
}else{
row.setHeightInPoints(height);
}
}else{
row.setHeightInPoints(height);
}
return row;
}
}
7、多sheet页填充
org.smile.report.excel .XlsExportTemplateUtils中定义了一个多sheet页填充数据,从第一个sheet页复制模板,进行数据填充。
/**
* 一次填充多页数据
* @param template
* @param contexts
* @param nameHandler
*/
public static void fillDataSource(XlsExportTemplate template,Map<String,Object>[] contexts,SheetNameHandler nameHandler){
int index=0;
int sheetIndex=1;
for(Map<String,Object> context :contexts){
if(index>1){
template.copySheet(0, nameHandler);
template.setSheetIndex(sheetIndex++);
template.fillDataSource(context);
}
index++;
}
//填充第一个sheet
template.setSheetIndex(0);
template.fillDataSource(contexts[0]);
}
8、在模板中配置属性
可以在第一行第一列单元格中配置模板表头和表尾信息,配置了之后在代码中就不需要使用代码对这几个属性进行设置了。
如下图:
9、动态模板
在代码中使用setDynamic 方法设置动态列,在模板中使用 %[name] 的方式设置动态列
代码示例:
XlsExportDynamicTemplate template=new XlsExportDynamicTemplate();
try {
template.loadXlsTemplate("d:/temp/dynamic.xls");
template.setDynamic("names", new String[]{"二月","三月","四月"});
template.setDynamic("ms", new String[]{"m2","m3","m4"});
template.setDynamic("names2", new String[]{"五月","六月","七月"});
template.setDynamic("ms2", new String[]{"m5","m6","m7"});
List list=new LinkedList();
for(int i=0;i<10;i++){
Map map=new HashMap();
map.put("m1", 10);
map.put("m2", 20);
map.put("m3", 30);
map.put("m4", 40);
map.put("m5", 50);
map.put("m6", 60);
map.put("m7", 70);
map.put("total", 8000);
list.add(map);
}
template.fillDataSource(list);
template.write(new FileOutputStream(new File("d:/temp/dynameic_out.xls")));
} catch (IOException e) {
e.printStackTrace();
}
模板样例:
输出结果:
三、Excel导入
1、基本导入
1.1代码样例
final String batchFlag=UUIDGenerator.uuid();
XlsImportTemplete template=new XlsImportTemplete(upload){
@Override
protected Object newTargetInstanse() {
TDacDelayPaymentTemp obj=new TDacDelayPaymentTemp();
obj.setBatchFlag(batchFlag);
return obj;
}
@Override
protected void onAfterReadRow(Object targetObject, Row currentRow) {
TDacDelayPaymentTemp obj=(TDacDelayPaymentTemp)targetObject;
Date date=DateUtils.parseDate(obj.getImportDate());
Date latestFeedbackDate=DateUtils.getBeforeDay(date, -29);
obj.setLatestFeedbackDate(DateUtils.formatOnlyDate(latestFeedbackDate));
obj.setImportDate(DateUtils.formatOnlyDate(date));
}
@Override
protected boolean validateTitles() throws ExcelException{
String[] t=new String[]{"月份","序号","报关单号","报关合同号","币种","进口金额","进口日期"};
if(ArrayUtils.check(getTitles(), new int[]{0,1,2,3,4,5,6}, t)){
return true;
}else{
throw new ExcelException("请选择正确的模板"+(titleRowIndex+1)+"行必须为标题:"+JSONValue.toJSONString(t));
}
}
};
template.initTargetClass(TDacDelayPaymentTemp.class);
List<TDacDelayPaymentTemp> list=template.readDataToList(CollectionUtils.newLinkedHashMap(new Integer[]{0,1,2,3,4,5,6},
new String[]{"month","serialNum","customsNo","contractNo","currency","importMoney","importDate"}));
模板样例
1.2 简单样例
XlsImportTemplete importTemplate=new XlsImportTemplete(upload);
importTemplate.initTargetClass(TDacHkStockDetail.class);
List<TDacHkStockDetail> dataList=importTemplate.readDataToList(CollectionUtils.newLinkedHashMap(
new Integer[]{6,1,2,3,4,5,8,9,10,11,12,13,14,15},
new String[]{"quantity","lifnr","matnr","model","brand","producePlace","grossWeight","netWeight","boxNum","batchNo","checkindate","deliveryNo","unit","remark"}));
1.3 List 属性支持
可以对导入类有list属性支持 ,list属性必须有getter setter 和泛型。xls属性以第一列做为是否是同一个对象的区分 ,就是说第一列同一个对象第二行只能为空,合并后不影响
public List<TestVo> getSubList();
这们就可以在xls中设置 subList.name 这样就会把单元格的值导入到subList 属性中的对象的name 属性中
四、其它Excel操作工具
1、PoiSupport
org.smile.report.poi.PoiSupport 类是一个poi库操作的工具类,提供了一此方便的功能。