针对POI中的后台遍历,存值取值,逐行上色,合并列,在这一次的项目经历中有所学习。
需求:针对给出的大致模板,后台生成excel文件,对某些列进行合并,对某几种条件下的数据进行颜色标注
解决思路:1.根据导出模板,查询出需要导出的数据sql。
2.将表头给定义出来,由于是大致模板,初始化表头有利于后期改动
3.遍历数据,针对需要合并的单元格编写方法
4.筛选条件,对符合条件的数据进行颜色标注。
大致效果,如下:(由于存在敏感数据,只能如此)
1.导出sql语句--根据模板要求写出sql语句即可,如果涉及到合并不好做的话,可以根据情况将sql分开。
2.定义表头,将表头定义到一个config中,这样后期针对顺序的变化以及其他参数的定义都有很大的好处,
public static List<String[]> getNewExcelHeaders(){
//设置表头
List<String[]> headorder = new ArrayList<String[]>();
String[] h1 = new String[5];
h1[0] = "orgName";
h1[1] = "部门";
h1[2] = "5000";
h1[3] ="3"; //3垂直剧中
h1[4] ="0"; //是否合并
headorder.add(h1);
String[] h2 = new String[5];
h2[0] = "empName";
h2[1] = "业务人员";
h2[2] = "5000";
h2[3] = "3";
h2[4] ="0"; //是否合并
headorder.add(h2);
String[] h3 = new String[5];
h3[0] = "firstOrderDate";
h3[1] = "首单日期";
h3[2] = "7000";
h3[3] = "-1";
h3[4] ="1"; //是否合并
headorder.add(h3);
String[] h4 = new String[5];
h4[0] = "customType";
h4[1] = "";
h4[2] = "5000";
h4[3] = "3";
h4[4] ="0"; //是否合并
headorder.add(h4);
String[] h5 = new String[5];
h5[0] = "customName";
h5[1] = "客户名称";
h5[2] = "8000";
h5[3] = "3";
h5[4] ="1"; //是否合并
headorder.add(h5);
return headorder;
}
这里说一下为何这样定义:
定义h[0]和h[1],是为了显示和获取值可以一目了然,便于后期位置的变化,同时可以直接用属性名去遍历
定义h[2]是为了控制宽度,很多时候由于字段名太长,默认宽度显示不了
定义h[3]和h[4]是为了垂直居中(后面知晓有HSSFCellStyle.VERTICAL_CENTER后弃用了)和是否合并
下一步就是将标题和表头给显示出来
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//1.取表头
List<String[]> heads = PaConfig.getNewExcelHeaders();
int line = 1, maxColumn = 0;
maxColumn = heads.size();
HSSFWorkbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet();
Row row = null;
Cell cell = null;
Font font = workbook.createFont();
CellStyle style = workbook.createCellStyle();
style.setBorderBottom(CellStyle.BORDER_THIN);
style.setBorderLeft(CellStyle.BORDER_THIN);
style.setBorderRight(CellStyle.BORDER_THIN);
style.setBorderBottom(CellStyle.BORDER_THIN);
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
//写标题
String title = month+PaConfig.excelTitle;
font.setFontHeightInPoints((short) 28);
font.setBoldweight((short)10);
style.setFont(font);
row = sheet.createRow(0);
row.setHeight((short) (25 * 20*2));
cell = row.createCell(0);
cell.setCellValue(title);
cell.setCellStyle(style);
for(int i=1; i<heads.size(); i++){
String[] h = heads.get(i);
sheet.setColumnWidth((short) i, Short.parseShort(h[2]));
cell = row.createCell(i);
cell.setCellValue("");
cell.setCellStyle(style);
}
//合并单元格
CellRangeAddress cra1title =new CellRangeAddress(0, 0, 0, maxColumn-1); // 起始行, 终止行, 起始列, 终止列
sheet.addMergedRegion(cra1title);
//写表头
row = sheet.createRow(line++);
row.setHeightInPoints(23);
font.setFontHeightInPoints((short) 13);
style.setFont(font);
for(int i=0; i<heads.size(); i++){
String[] h = heads.get(i);
sheet.setColumnWidth((short) i, Short.parseShort(h[2]));
cell = row.createCell(i);
cell.setCellValue(h[1]);
cell.setCellStyle(style);
}
这一步就是将刚才在config中定义的表头数据给显示到上面,主要代码就是
//1.取表头
List<String[]> heads = PaConfig.getNewExcelHeaders();
int line = 1, maxColumn = 0;
第一个是取刚才定义的表头,第二行是定义行数,用于后面的合并数据。
下一步就是读取sql文件显示数据了,如果只是为了单纯的显示数据,那就如下即可:
//写入数据
for(int j=0;j<objs.length;j++){
//保留表头开始另起一行
row = sheet.createRow(line++);
DataObject obj = null;
if(j!=objs.length)
obj = objs[j];
//写入数据--k
for(int k=0; k<heads.size()&&objs!=null; k++){
String[] h = heads.get(k);
cell = row.createCell(k);
cell.setCellStyle(style);
//此处则是取定义表头中的属性值,代码自动遍历读取塞值即可
cell.setCellValue(obj.getString[0]);
很显然,需求不仅仅于此,接下来我们需要合并单元格
由于需求的特殊性,需要根据业务员来进行区分和合并
1.定义dto,存储需要合并的起始列和结束列
package com.mj.flx.pa.excelDto;
public class downloadDtoOrg {
private int startRowIndex;
private int endRowIndex;
private int columnIndex;
public int getStartRowIndex() {
return startRowIndex;
}
public void setStartRowIndex(int startRowIndex) {
this.startRowIndex = startRowIndex;
}
public int getEndRowIndex() {
return endRowIndex;
}
public void setEndRowIndex(int endRowIndex) {
this.endRowIndex = endRowIndex;
}
public int getColumnIndex() {
return columnIndex;
}
public void setColumnIndex(int columnIndex) {
this.columnIndex = columnIndex;
}
}
2.传入遍历的数据和起始行数(最开始的第二行代码),进行遍历读取
public List<downloadDtoOrg> getNewExcelIndexOrg(DataObject[] objs, int line2){
if(objs==null||objs.length==0){
return null;
}
List<downloadDtoOrg> edds = new ArrayList<downloadDtoOrg>(); //建立所有部门dto集合
int line = line2;
//写内容
String tempdetp = null; //区分当前
//处理部门合并对象
downloadDtoOrg edd = null;
for(int i=0; i<=objs.length; i++){
line ++;
DataObject obj = null;
if(i!=objs.length){
obj = objs[i];
}
if(tempdetp==null){
edd = new downloadDtoOrg();
edd.setStartRowIndex(line-1);
}else if(obj==null){
edd.setEndRowIndex(line-2);
edds.add(edd);
}else if(tempdetp.equals(obj.getString("orgName"))){ //如果此行的部门与上一行的部门一样
//取上一行的edd对象
//edd = edd;
}else if(!tempdetp.equals(obj.getString("orgName"))){ //如果此行的部门与上一行的部门不一行
edd.setEndRowIndex(line-2);
edds.add(edd);
//重新创建新对象
edd = new downloadDtoOrg();
edd.setStartRowIndex(line-1);
}else {
throw new RuntimeException("未考虑到问题异常");
}
tempdetp = obj==null?null:obj.getString("orgName");
}
return edds;
}
上面代码的意思大致如下:通过对比数据中业务员的姓名进行分组,碰到不一致的则重新开始下一列,所以我们在sql中就要分好组,以免出现数据不一致的情况。在这里我们记录了合并的起始列和结束列,但是调用poi的方法还需要起始行和结束行,所以。
//合并部门
for(int i=0;i<orgs.size(); i++){
downloadDtoOrg org = orgs.get(i);
int columnIndex = PaConfig.getNewColumnIndex("orgName");
CellRangeAddress cra =new CellRangeAddress(org.getStartRowIndex(), org.getEndRowIndex(), columnIndex, columnIndex); // 起始行, 终止行, 起始列, 终止列
sheet.addMergedRegion(cra);
}
通过最初始定义的表头,获取起始列和结束列。这样就可以进行合并列的操作了,其他以此类推。
思考:如果需要合并的列很多,又不想在一条sql中处理的话,可以分多条sql关注合并列,可以大大减低难度。
public List<downloadDtoOrg> getNewExcelIndexCustomType(DataObject[] objs, int line2){
if(objs==null||objs.length==0){
return null;
}
List<downloadDtoOrg> edds = new ArrayList<downloadDtoOrg>(); //建立所有部门dto集合
int line = line2;
//写内容
String tempdetp = null; //区分当前
String empNames =null;
//处理客户类型合并对象
downloadDtoOrg edd = null;
for(int i=0; i<=objs.length; i++){
line ++;
DataObject obj = null;
if(i!=objs.length){
obj = objs[i];
}
if(tempdetp==null){
edd = new downloadDtoOrg();
edd.setStartRowIndex(line-1);
}else if(obj==null){
edd.setEndRowIndex(line-2);
edds.add(edd);
}else {
//如果销售员和客户类型匹配一致
if(tempdetp.equals(obj.getString("customType"))&&empNames.equals(obj.getString("empName"))){
}else{
edd.setEndRowIndex(line-2);
edds.add(edd);
//重新创建新对象
edd = new downloadDtoOrg();
edd.setStartRowIndex(line-1);
}
}
tempdetp = obj==null?null:obj.getString("customType");
empNames = obj==null?null:obj.getString("empName");
}
return edds;
}
如果后面有需要合并的,又是依赖前面业务员的条件,可以在当中进行多重判断,进而取到相应的起始列,行数据。
最后就是一个标色的操作了,因为有些合并列是不需要标色的,所以在初始化的时候,定义的h[4]的值就开始起作用了。通过判断定义的值就可以进行是否标色操作了。
//标注颜色
if(obj.getString("agendOrder")!=null){
if(heads.get(k)[4].equals("1")){
//cell = row.createCell(k);
style2.setFillForegroundColor(HSSFColor.PALE_BLUE.index);
style2.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
}
cell.setCellStyle(style2);
}
style2第一行是定义颜色,第二行是定义填充样式,最好是我这样,全填充,如果忘记了编写这一行,你会发现,没有任何效果。(POI的一个硬伤就是不能整行标色,只能自己遍历)
思考:我这里因为标了三种不同颜色,通过了三个style进行解决,然后会出现合并列也会被标色的现象,所以没有办法,又定义了一个style将合并列的背景色定义为白色,感觉很low,但是没找到办法,希望有大佬看到后能解惑一下。
总结:
起始就是考验你对POI的相关属性是否熟悉,以及对java的基础知识能否灵活运用,其中POI的几个常用属性如下:
//定义工作簿
HSSFWorkbook workbook = new HSSFWorkbook();
//表格
Sheet sheet = workbook.createSheet();
//行
Row row = null;
//单元格
Cell cell = null;
//字体
Font font = workbook.createFont();
//属性
CellStyle style = workbook.createCellStyle();
各种小属性
style.setBorderBottom(CellStyle.BORDER_THIN);
style.setBorderLeft(CellStyle.BORDER_THIN);
style.setBorderRight(CellStyle.BORDER_THIN);
style.setBorderBottom(CellStyle.BORDER_THIN);
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
style2.setFillForegroundColor(HSSFColor.PALE_BLUE.index);
style2.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
还得继续努力学习哦!