一 前言
接到一个需求,要求可以自定义导出字段,翻看了大量资料,最终集齐众家所长做了出来,也对excel导出有了不少了解
二 前后端代码
2.1 Vue页面
<el-col :span="1.5">
<el-popover
placement="right"
width="600"
trigger="click">
<el-col v-for="ct in columnTitle" :key="ct.prop" :span="10">
<!-- <span v-if="ct.prop != 'index'">
<el-checkbox class="btn-check" v-model="ct.checked" border>{{ct.label}}</el-checkbox>
</span>-->
<span >
<el-checkbox class="btn-check" v-model="ct.checked" border>{{ct.label}}</el-checkbox>
</span>
</el-col>
<div style="text-align: right; margin: 0">
<el-button type="primary" @click="exportDatChecked">导出已选择的列</el-button>
<el-button class="btn-all" @click="exportDataAll">导出全部列</el-button>
</div>
<el-button slot="reference"
v-has-permi="['smms:research:export']">导出</el-button>
</el-popover>
</el-col>
exportDatChecked(){
let columnTitleNew=[]
this.columnTitle.filter(item =>item.checked).forEach(item => {
columnTitleNew.push(item)
})
console.log(columnTitleNew)
this.queryParams.fieldsName= JSON.stringify(columnTitleNew)
console.log(this.queryParams.fieldsName)
this.download('/smms/server-smms/ResourceReserve/exportResearchTasks', {
...this.queryParams
}, `研培任务详情导出${new Date().getTime()}.xls`).then(res => {
if(res.code == '200'){
this.$message.success('导出成功')
}else {
this.$message.error("导出失败")
}
this.queryParams.fieldsName=null
})
this.queryParams.fieldsName=null
},
exportDataAll(){
let columnTitleNew=[]
this.columnTitle.forEach(item => {
columnTitleNew.push(item)
})
this.queryParams.fieldsName= JSON.stringify(columnTitleNew)
console.log(this.queryParams.fieldsName)
this.download('/smms/server-smms/ResourceReserve/exportResearchTasks', {
...this.queryParams
}, `研培任务详情导出${new Date().getTime()}.xls`).then(res => {
if(res.code == '200'){
this.$message.success('导出成功')
}else {
this.$message.error("导出失败")
}
this.queryParams.fieldsName=null
})
this.queryParams.fieldsName=null
},
columnTitle:[
{'label':'任务名称','checked':false,'prop':'name'},
{'label':'任务内容','checked':false,'prop':'taskContent'},
{'label':'责任部门','checked':false,'prop':'dutyDept'},
{'label':'负责人','checked':false,'prop':'responsiblePerson'},
{'label':'上周工作完成情况','checked':false,'prop':'nextWeek'},
{'label':'本周工作计划','checked':false,'prop':'thisWeek'},
{'label':'目标值','checked':false,'prop':'planSchedule'},
{'label':'时序进度','checked':false,'prop':'timingSchedule'},
{'label':'偏差','checked':false,'prop':'deviation'},
{'label':'偏差说明','checked':false,'prop':'remark'},
{'label':'周','checked':false,'prop':'week'},
{'label':'更新时间','checked':false,'prop':'updateTime'},
],
这里我用了element-UI的弹出层,主要是想尝试一下不同的写法,个人觉得应该还可以用dialog写,然后就是这里标题总数是写死了的,不利于后期维护,有想法的可以通过别的方法得到值,不用将它写死。
2.2 java后端
导出工具类
package com.hckj.utils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
public class ExportExcel {
// 导出表的标题
private String title;
// 导出表的列名
private String[] rowName;
// 导出表的数据
private List<Object[]> dataList = new ArrayList<Object[]>();
// 构造函数,传入要导出的数据
public ExportExcel(String title, String[] rowName, List<Object[]> dataList) {
this.dataList = dataList;
this.rowName = rowName;
this.title = title;
}
// 导出数据
public void export(OutputStream out) throws Exception {
try {
// 創建一個EXCEL並設置名稱
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet(title);
// 產生表格標題行
HSSFRow rowm = sheet.createRow(0);
HSSFCell cellTitle = rowm.createCell(0);
// SHEET樣式定義
HSSFCellStyle columnTopStyle = this.getColumnTopStyle(workbook);
HSSFCellStyle style = this.getStyle(workbook);
sheet.addMergedRegion(new CellRangeAddress(0, 1, 0, (rowName.length - 1)));
cellTitle.setCellStyle(columnTopStyle);
cellTitle.setCellValue(title);
// 定義所需要的列數
int columnNum = rowName.length;
HSSFRow rowRowName = sheet.createRow(2);
// 將列頭設置到SHEET的單元格中
for (int n = 0; n < columnNum; n++) {
HSSFCell cellRowName = rowRowName.createCell(n);
cellRowName.setCellType(CellType.STRING);
HSSFRichTextString text = new HSSFRichTextString(rowName[n]);
cellRowName.setCellValue(text);
cellRowName.setCellStyle(style);
}
// 將數據設置到SHEET的單元格中
for (int i = 0; i < dataList.size(); i++) {
Object[] obj = dataList.get(i);
HSSFRow row = sheet.createRow(i + 3);
for (int j = 0; j < obj.length; j++) {
HSSFCell cell = null;
if (j == 0) {
cell = row.createCell(j, CellType.NUMERIC);
cell.setCellValue(i + 1);
} else {
cell = row.createCell(j, CellType.STRING);
if (!"".equals(obj[j]) && obj[j] != null) {
cell.setCellValue(obj[j].toString());
} else {
cell.setCellValue(" ");
}
}
cell.setCellStyle(style);
}
}
// 讓列寬隨著導出的列長自動適應
for (int colNum = 0; colNum < columnNum; colNum++) {
int columnWidth = sheet.getColumnWidth(colNum) / 256;
/* for (int rowNum = 0; rowNum < sheet.getLastRowNum(); rowNum++) {
HSSFRow currentRow;
if (sheet.getRow(rowNum) == null) {
currentRow = sheet.createRow(rowNum);
} else {
currentRow = sheet.getRow(rowNum);
}
if (currentRow.getCell(colNum) != null) {
HSSFCell currentCell = currentRow.getCell(colNum);
if (currentCell.getCellTypeEnum() == CellType.STRING) {
int length = currentCell.getStringCellValue().getBytes().length;
if (columnWidth < length) {
columnWidth = length;
}
}
}
}*/
if (colNum == 0) {
sheet.setColumnWidth(colNum, (columnWidth - 2) * 128);
} else {
sheet.setColumnWidth(colNum, (columnWidth + 4) * 256);
}
}
if (workbook != null) {
try {
workbook.write(out);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) { }
}
/**
* 表格标题样式
*/
public HSSFCellStyle getColumnTopStyle(HSSFWorkbook workbook) {
// 设置字体
HSSFFont font = workbook.createFont();
// 设置字体大小
font.setFontHeightInPoints((short) 11);
// 设置字体颜色
font.setColor(IndexedColors.BLACK.getIndex());
// 字体加粗
font.setBold(true);
// 设置字体名字
font.setFontName("Courier New");
// 设置样式
HSSFCellStyle style = workbook.createCellStyle();
// 设置标题背景色
style.setFillForegroundColor(IndexedColors.WHITE.getIndex());
// 设置背景颜色填充样式
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置低边框
style.setBorderBottom(BorderStyle.THIN);
// 设置低边框颜色
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
// 设置右边框
style.setBorderRight(BorderStyle.THIN);
// 设置顶边框
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
// 设置顶边框颜色
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
// 在样式中应用设置的字体
style.setFont(font);
// 设置自动换行
style.setWrapText(false);
// 设置水平对齐的样式为居中对齐;
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
/**
* 表格数据样式
*/
public HSSFCellStyle getStyle(HSSFWorkbook workbook) {
// 设置字体
HSSFFont font = workbook.createFont();
// 设置字体大小
font.setFontHeightInPoints((short) 10);
// 设置字体名字
font.setFontName("Courier New");
// 设置样式;
HSSFCellStyle style = workbook.createCellStyle();
// 设置底边框;
style.setBorderBottom(BorderStyle.THIN);
// 设置底边框颜色;
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
// 设置左边框;
style.setBorderLeft(BorderStyle.THIN);
// 设置左边框颜色;
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
// 设置右边框;
style.setBorderRight(BorderStyle.THIN);
// 设置右边框颜色;
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
// 设置顶边框;
style.setBorderTop(BorderStyle.THIN);
// 设置顶边框颜色;
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
// 在样式用应用设置的字体;
style.setFont(font);
// 设置自动换行;
style.setWrapText(true);
// 设置水平对齐的样式为居中对齐;
style.setAlignment(HorizontalAlignment.CENTER);
// 设置垂直对齐的样式为居中对齐;
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
}
转换工具类
package com.hckj.utils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Json转换类
*/
public class JsonUtil {
// 使用jackson进行json转换
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
return OBJECT_MAPPER.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 将对象转换成json字符串。
*/
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
/**
* 将json结果集转化为对象
*
* @param jsonData json数据
* @param beanType 对象中的object类型
*/
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json数据转换成pojo对象list
*/
public static <T> List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String listToJson(List<Object> list) throws JsonParseException, JsonMappingException, IOException{
String comment_json = OBJECT_MAPPER.writeValueAsString(list);
return comment_json;
}
public static String mapToJson(Map<String,Object> map) throws JsonParseException, JsonMappingException, IOException{
String comment_json = OBJECT_MAPPER.writeValueAsString(map);
return comment_json;
}
public static Map<String,Object> jsonStringToMap(String jsonString) throws Exception{
Map<String,Object> map = OBJECT_MAPPER.readValue(jsonString, Map.class);
return map;
}
public static String listToJson1(List<Map<String, Object>> targetList) throws JsonParseException, JsonMappingException, IOException{
String comment_json = OBJECT_MAPPER.writeValueAsString(targetList);
return comment_json;
}
@SuppressWarnings("unchecked")
public static List<Map<String, Object>> jsonStringToList1(String jsonString) throws JsonParseException, JsonMappingException, IOException{
List<Map<String, Object>> commodityOrderInfoList=new ArrayList<Map<String, Object>>();
if(!jsonString.equals("")){
JavaType javaType = getCollectionType(ArrayList.class, Object.class);
commodityOrderInfoList = (List<Map<String, Object>>)OBJECT_MAPPER.readValue(jsonString, javaType);
}
return commodityOrderInfoList;
}
}
Controller层
@ApiOperation("导出研培任务")
@PostMapping("/exportResearchTasks")
public AjaxResult exportResearchTasks(HttpServletResponse response , ResearchTaskDTO researchTasks) throws Exception {
List<ResearchTaskDTO> list = resourceReserveService.queryResearchTasks(researchTasks);
String fileName = DateUtils.getNowDate() +"研培任务详情导出.xls";
String headStr = "attachment; filename=" + fileName;
response.setContentType("APPLICATION/OCTET-STREAM");
response.setHeader("Content-Disposition", headStr);
response.setCharacterEncoding("utf-8");
// 把需要展示的列json数据转为List
List<Map<String, Object>> ts = JsonUtil.jsonStringToList1(researchTasks.getFieldsName());
resourceReserveService.exportExcel(response.getOutputStream(),ts,list);
return AjaxResult.success();
重点来了
@Override
public List<ResearchTaskDTO> queryResearchTasks(ResearchTaskDTO researchTasks) {
/*String userName=SecurityUtils.getCurrentUser().getNickName();
if(!userName.contains("管理员") && !userName.contains("运维") ){
researchTasks.setResponsiblePerson(userName);
}
if(userName.contains("江银")){
researchTasks.setResponsiblePerson(null);
}*/
List<ResearchTaskDTO> tasks= resourceReserveMapper.queryResearchTasks(researchTasks);
ArrayList<ResearchTaskDTO> temporaryList = new ArrayList<>();
//排序,使相同名字的数据在相邻的地方
List<String> collect = tasks.stream().map(ResearchTasks::getName).distinct().collect(Collectors.toList());
for (int i = 0; i < collect.size(); i++) {
for (int j = 0; j <tasks.size() ; j++) {
if (collect.get(i).equals(tasks.get(j).getName())){
temporaryList.add(tasks.get(j));
}
}
}
return temporaryList;
}
@Override
public void exportExcel(OutputStream out, List<Map<String, Object>> cols, List<ResearchTaskDTO> list) throws Exception {
List<Object[]> dataList = new ArrayList<Object[]>();
// 设置Excel列标题
String[] rowsName = new String[cols.size()+1];
rowsName[0] = "序号";
for (int i = 0; i < cols.size(); i++) {
rowsName[i+1] = cols.get(i).get("label").toString();
}
try {
// 设置Excel行数据
for (ResearchTaskDTO p : list) {
Object[] objs = new Object[cols.size()+1];
objs[0] = "";
for (int j = 0; j < cols.size(); j++) {
String prop = cols.get(j).get("prop").toString();
if (prop.equals("name")) {
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name;
}else if (prop.equals("taskContent")){
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name;
}else if (prop.equals("dutyDept")){
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name;
}else if (prop.equals("responsiblePerson")){
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name;
}else if (prop.equals("nextWeek")){
if(p.getNextWeek() != null){
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name;
}else {
objs[j+1] ="/";
}
}else if (prop.equals("thisWeek")){
if(p.getThisWeek() != null){
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name;
}else {
objs[j+1] ="/";
}
}else if (prop.equals("planSchedule")){
p.setPlanSchedule(p.getPlanSchedule()*100);
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name.substring(0, name.lastIndexOf("."))+"%";
}else if (prop.equals("timingSchedule")){
p.setTimingSchedule(p.getTimingSchedule()*100);
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name.substring(0, name.lastIndexOf("."))+"%";
}else if (prop.equals("deviation")){
p.setDeviation(p.getDeviation()*100);
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name.substring(0, name.lastIndexOf("."))+"%";
}else if (prop.equals("remark")){
if(p.getRemark() != null){
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name;
}else {
objs[j+1] ="/";
}
}else if (prop.equals("week")){
if(p.getWeek() != null){
String name = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
objs[j+1] = name;
}else {
objs[j+1] ="/";
}
}else if (prop.equals("updateTime")){
if(p.getUpdateTime() != null){
String dateStr = invokeGetMethod(ResearchTaskDTO.class, prop, p).toString();
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US);
Date date = new Date();
try{
date = sdf.parse(dateStr);
}catch (Exception e){
e.printStackTrace();
}
String formatStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
objs[j+1] = formatStr;
}else {
objs[j+1] ="/";
}
}
}
dataList.add(objs);
}
}catch (Exception e) {
e.printStackTrace();
}
String title ="2022年国网福建党校(管培中心)研究培训工作周进度看板";
if(list.get(0).getWeek() != null){
title+="("+list.get(0).getWeek()+")";
}
System.out.println(title);
ExportExcel ex = new ExportExcel(title, rowsName, dataList);
try {
ex.export(out);
} catch (Exception e) {
e.printStackTrace();
}
out.flush();
out.close();
}
/**
* 拼接属性,调用目标Class的get方法
*
* @param c 属性所属实体类
* @param filedName 需要调用的属性名
* @param obj 实体类实例
* @return 返回get方法结果
*/
public static Object invokeGetMethod(Class<?> c, String filedName, Object obj) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
String methodName = "get" + filedName.substring(0, 1).toUpperCase() + filedName.substring(1);
Class<?>[] nullClasses = null;
Method method = c.getMethod(methodName, nullClasses);
Object[] nullObjects = null;
return method.invoke(obj, nullObjects);
}
然后这个是我根据网上的葫芦画瓢来的
后面根据那个导出工具类自己写了个excel
三 excel实现
这个就是前端的表格,导出表格也要一样
然后我们来分析一下
public void exportExcel(ServletOutputStream outputStream, List<Map<String, String>> tableColumns, List<Map<String, Object>> tableData) {
// 设置Excel列标题
String[] rowsName = new String[tableColumns.size()+3];
String[] rowsName1 = new String[tableColumns.size()+3];
rowsName[0] = "序号";
rowsName[1] = "办学校区";
rowsName[2] = "培训班名称";
rowsName1[0] = "序号";
rowsName1[1] = "办学校区";
rowsName1[2] = "培训班名称";
for (int i = 0; i < tableColumns.size(); i++) {
rowsName[i+3] = tableColumns.get(i).get("label1").toString();
rowsName1[i+3] = tableColumns.get(i).get("label2").toString();
}
try {
//新建工作簿
XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
//新建工作表
XSSFSheet sheet = xssfWorkbook.createSheet("住宿统计表");
//指定合并开始行、合并结束行 合并开始列、合并结束列
CellRangeAddress rangeAddress = new CellRangeAddress(0, 1, 0, 0);
//添加要合并地址到表格
sheet.addMergedRegion(rangeAddress);
sheet.addMergedRegion(new CellRangeAddress(tableData.size()+1, tableData.size()+1, 1, 2));
sheet.addMergedRegion(new CellRangeAddress(0, 1, 1, 1));
sheet.addMergedRegion(new CellRangeAddress(0, 1, 2, 2));
//这个合并地址可以多次添加,这里合并的行列也是之前分析的
//创建行,指定起始行号,从0开始
XSSFRow row = sheet.createRow(0);
//创建单元格,指定起始列号,从0开始
XSSFCell cell = row.createCell(0);
// 定義所需要的列數
int columnNum = rowsName.length;
XSSFRow rowRowName = sheet.createRow(1);
for (int i = 0; i < 2; i++) {
if(i==0){
// 將列頭設置到SHEET的單元格中
for (int n = 0; n < columnNum; n++) {
XSSFCell cellRowName = row.createCell(n);
cellRowName.setCellType(CellType.STRING);
XSSFRichTextString text = new XSSFRichTextString(rowsName[n]);
cellRowName.setCellValue(text);
}
}else {
for (int n = 0; n < columnNum; n++) {
XSSFCell cellRowName = rowRowName.createCell(n);
cellRowName.setCellType(CellType.STRING);
XSSFRichTextString text = new XSSFRichTextString(rowsName1[n]);
cellRowName.setCellValue(text);
}
}
}
//这里就是前面2行标题设置,此时已经将1,2行写到工作簿中去了
// 將數據設置到SHEET的單元格中
for (int i = 0; i < tableData.size(); i++) {
XSSFRow rows= sheet.createRow(i + 2);
List<String> data = (List<String>) tableData.get(i).get("data");
String trainClassName = (String) tableData.get(i).get("trainClassName");
String campusType=null;
String type=null;
if(i != tableData.size()-1){
campusType = (String) tableData.get(i).get("campusType");
type= remoteDictService.selectDictLabel("campus_type", campusType).getMsg();
}
for (int j = 0; j < data.size()+3; j++) {
XSSFCell xssfCell=null;
if(j==0) {
xssfCell = rows.createCell(j, CellType.NUMERIC);
xssfCell.setCellValue(i + 1);
}else if(j==1) {
xssfCell = rows.createCell(j, CellType.STRING);
if (campusType != null) {
xssfCell.setCellValue(type);
}else {
xssfCell.setCellValue(trainClassName);
}
}else if(j==2) {
xssfCell = rows.createCell(j, CellType.STRING);
xssfCell.setCellValue(trainClassName);
}else {
xssfCell = rows.createCell(j, CellType.STRING);
if (!"".equals(data.get(j-3)) && data.get(j-3) != null) {
xssfCell.setCellValue(data.get(j-3).toString());
} else {
xssfCell.setCellValue(" ");
}
}
}
}
//这一层循环是将数据写到工作簿中去,这里这样写的原因是基于数据而改变的
if (xssfWorkbook != null) {
try {
xssfWorkbook.write(outputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
这里就自己实现了,我还没有用到那个样式,有需要的同学也可以用一下导出工具类中的样式渲染
四 总结
参加工作之后,导入导出经常用到,之前一直用的开源软件提供的,也有大神自己根据业务写的导出工具类,这还是第一次自己理解excel怎么写的,意义重大,也将这个分享给大家,虽然这个不能复用,哈哈