|
|
@@ -0,0 +1,249 @@
|
|
|
+package com.jeeplus.common.utils.excel;
|
|
|
+
|
|
|
+import com.jeeplus.modules.sys.utils.DictUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
+import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.OutputStream;
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+import com.jeeplus.common.utils.excel.annotation.ExcelField;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 知识库多页签Excel导出工具类
|
|
|
+ * 基于ExportMultipleTabsExcel,针对知识库导出场景做了以下定制:
|
|
|
+ * 1. 表头格式为 "中文名|mapKey",导出时表头行只显示中文名部分
|
|
|
+ * 2. 序号列(第一列)使用较窄的列宽
|
|
|
+ */
|
|
|
+public class ExportKnowledgeBaseExcel {
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(ExportKnowledgeBaseExcel.class);
|
|
|
+ private Workbook workbook;
|
|
|
+ private List<MapSheetData> mapSheetDataList = new ArrayList<>();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 内部类:存储单个Sheet的数据(Map模式)
|
|
|
+ */
|
|
|
+ private static class MapSheetData {
|
|
|
+ String sheetName;
|
|
|
+ String title;
|
|
|
+ List<String> headers;
|
|
|
+ List<Map<String, Object>> dataList;
|
|
|
+
|
|
|
+ public MapSheetData(String sheetName, String title, List<String> headers, List<Map<String, Object>> dataList) {
|
|
|
+ this.sheetName = sheetName;
|
|
|
+ this.title = title;
|
|
|
+ this.headers = headers;
|
|
|
+ this.dataList = dataList;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造函数:初始化工作簿
|
|
|
+ */
|
|
|
+ public ExportKnowledgeBaseExcel() {
|
|
|
+ this.workbook = new SXSSFWorkbook(1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加一个Sheet页(Map模式,使用自定义表头和Map数据)
|
|
|
+ * 表头格式为 "中文名|mapKey",导出时表头只显示中文名,数据按mapKey取值
|
|
|
+ */
|
|
|
+ public void addSheet(String sheetName, String title, List<String> headers, List<Map<String, Object>> dataList) {
|
|
|
+ mapSheetDataList.add(new MapSheetData(sheetName, title, headers, dataList));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建单元格样式
|
|
|
+ */
|
|
|
+ private Map<String, CellStyle> createStyles(Workbook wb) {
|
|
|
+ Map<String, CellStyle> styles = new HashMap<>();
|
|
|
+
|
|
|
+ // 标题样式
|
|
|
+ CellStyle style = wb.createCellStyle();
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ Font titleFont = wb.createFont();
|
|
|
+ titleFont.setFontName("宋体");
|
|
|
+ titleFont.setFontHeightInPoints((short) 16);
|
|
|
+ titleFont.setBold(true);
|
|
|
+ style.setFont(titleFont);
|
|
|
+ styles.put("title", style);
|
|
|
+
|
|
|
+ // 表头样式(无边框 + 更深底色 + 白色字体)
|
|
|
+ style = wb.createCellStyle();
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+ style.setBorderBottom(BorderStyle.NONE);
|
|
|
+ style.setBorderLeft(BorderStyle.NONE);
|
|
|
+ style.setBorderRight(BorderStyle.NONE);
|
|
|
+ style.setBorderTop(BorderStyle.NONE);
|
|
|
+ style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
+ Font headerFont = wb.createFont();
|
|
|
+ headerFont.setFontName("宋体");
|
|
|
+ headerFont.setFontHeightInPoints((short) 11);
|
|
|
+ headerFont.setBold(true);
|
|
|
+ headerFont.setColor(IndexedColors.WHITE.getIndex());
|
|
|
+ style.setFont(headerFont);
|
|
|
+ styles.put("header", style);
|
|
|
+
|
|
|
+ // 字符串居中样式
|
|
|
+ CellStyle stringStyle = wb.createCellStyle();
|
|
|
+ stringStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ stringStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+ stringStyle.setBorderBottom(BorderStyle.THIN);
|
|
|
+ stringStyle.setBorderLeft(BorderStyle.THIN);
|
|
|
+ stringStyle.setBorderRight(BorderStyle.THIN);
|
|
|
+ stringStyle.setBorderTop(BorderStyle.THIN);
|
|
|
+ Font stringFont = wb.createFont();
|
|
|
+ stringFont.setFontName("宋体");
|
|
|
+ stringFont.setFontHeightInPoints((short) 11);
|
|
|
+ stringStyle.setFont(stringFont);
|
|
|
+ styles.put("data_string", stringStyle);
|
|
|
+
|
|
|
+ // 数字靠右样式
|
|
|
+ CellStyle numberStyle = wb.createCellStyle();
|
|
|
+ numberStyle.setAlignment(HorizontalAlignment.RIGHT);
|
|
|
+ numberStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+ numberStyle.setBorderBottom(BorderStyle.THIN);
|
|
|
+ numberStyle.setBorderLeft(BorderStyle.THIN);
|
|
|
+ numberStyle.setBorderRight(BorderStyle.THIN);
|
|
|
+ numberStyle.setBorderTop(BorderStyle.THIN);
|
|
|
+ Font numberFont = wb.createFont();
|
|
|
+ numberFont.setFontName("宋体");
|
|
|
+ numberFont.setFontHeightInPoints((short) 11);
|
|
|
+ numberStyle.setFont(numberFont);
|
|
|
+ styles.put("data_number", numberStyle);
|
|
|
+
|
|
|
+ return styles;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成Excel文件
|
|
|
+ */
|
|
|
+ public void write(OutputStream os) throws IOException {
|
|
|
+ for (MapSheetData sheetData : mapSheetDataList) {
|
|
|
+ createMapSheetContent(sheetData);
|
|
|
+ }
|
|
|
+ workbook.write(os);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建Map模式Sheet的内容
|
|
|
+ * 表头格式为 "中文名|mapKey":表头行只显示中文名,数据按mapKey从Map中取值
|
|
|
+ */
|
|
|
+ private void createMapSheetContent(MapSheetData sheetData) {
|
|
|
+ Sheet sheet = workbook.createSheet(sheetData.sheetName);
|
|
|
+ Map<String, CellStyle> styles = createStyles(workbook);
|
|
|
+ List<String> headers = sheetData.headers;
|
|
|
+ int rownum = 0;
|
|
|
+
|
|
|
+ // 创建标题行
|
|
|
+ if (StringUtils.isNotBlank(sheetData.title)) {
|
|
|
+ Row titleRow = sheet.createRow(rownum++);
|
|
|
+ titleRow.setHeightInPoints(30);
|
|
|
+ Cell titleCell = titleRow.createCell(0);
|
|
|
+ titleCell.setCellStyle(styles.get("title"));
|
|
|
+ titleCell.setCellValue(sheetData.title);
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建表头行
|
|
|
+ Row headerRow = sheet.createRow(rownum++);
|
|
|
+ headerRow.setHeightInPoints(18);
|
|
|
+ for (int i = 0; i < headers.size(); i++) {
|
|
|
+ String header = headers.get(i);
|
|
|
+ // 表头只显示"|"前的中文名称
|
|
|
+ String displayHeader = header.contains("|") ? header.split("\\|")[0] : header;
|
|
|
+ int width = 0;
|
|
|
+ for (char c : displayHeader.toCharArray()) {
|
|
|
+ width += (c > 127) ? 2 : 1;
|
|
|
+ }
|
|
|
+ width = Math.max(width, 10);
|
|
|
+ width = Math.min(width, 50);
|
|
|
+ // 序号列限制较窄宽度(一般三位数即可)
|
|
|
+ if (i == 0) {
|
|
|
+ width = Math.min(width, 5);
|
|
|
+ }
|
|
|
+ sheet.setColumnWidth(i, width * 256 * 2);
|
|
|
+
|
|
|
+ Cell headerCell = headerRow.createCell(i);
|
|
|
+ headerCell.setCellStyle(styles.get("header"));
|
|
|
+ headerCell.setCellValue(displayHeader);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充数据行
|
|
|
+ if (sheetData.dataList != null && !sheetData.dataList.isEmpty()) {
|
|
|
+ for (Map<String, Object> dataMap : sheetData.dataList) {
|
|
|
+ Row dataRow = sheet.createRow(rownum++);
|
|
|
+ dataRow.setHeightInPoints(16);
|
|
|
+ for (int i = 0; i < headers.size(); i++) {
|
|
|
+ String header = headers.get(i);
|
|
|
+ // 从header提取map key(格式:header|key 或纯header)
|
|
|
+ String mapKey = header.contains("|") ? header.split("\\|")[1] : header;
|
|
|
+ Object value = dataMap.get(mapKey);
|
|
|
+
|
|
|
+ Cell cell = dataRow.createCell(i);
|
|
|
+ if (value instanceof Number) {
|
|
|
+ cell.setCellStyle(styles.get("data_number"));
|
|
|
+ } else {
|
|
|
+ cell.setCellStyle(styles.get("data_string"));
|
|
|
+ }
|
|
|
+ setMapCellValue(cell, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置Map模式单元格值
|
|
|
+ */
|
|
|
+ private void setMapCellValue(Cell cell, Object value) {
|
|
|
+ if (value == null) {
|
|
|
+ cell.setCellValue("");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (value instanceof Date) {
|
|
|
+ cell.setCellValue(new SimpleDateFormat("yyyy-MM-dd").format((Date) value));
|
|
|
+ } else if (value instanceof Number) {
|
|
|
+ cell.setCellValue(((Number) value).doubleValue());
|
|
|
+ } else if (value instanceof Boolean) {
|
|
|
+ cell.setCellValue((Boolean) value);
|
|
|
+ } else {
|
|
|
+ cell.setCellValue(value.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 输出到客户端下载
|
|
|
+ */
|
|
|
+ public void write(HttpServletResponse response, String fileName) throws IOException {
|
|
|
+ response.reset();
|
|
|
+ response.setContentType("application/octet-stream; charset=utf-8");
|
|
|
+ response.setHeader("Content-Disposition", "attachment; filename="
|
|
|
+ + new String(fileName.getBytes("utf-8"), "ISO-8859-1"));
|
|
|
+ write(response.getOutputStream());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清理临时文件(避免内存泄漏)
|
|
|
+ */
|
|
|
+ public void dispose() {
|
|
|
+ if (workbook instanceof SXSSFWorkbook) {
|
|
|
+ ((SXSSFWorkbook) workbook).dispose();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|