|
@@ -0,0 +1,265 @@
|
|
|
+package com.dayou.utils;
|
|
|
+
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import com.dayou.annotation.Excel;
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
+import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.OutputStream;
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+public class ExcelOneToManyExport {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出一对多excel
|
|
|
+ *
|
|
|
+ * @param response response
|
|
|
+ * @param parentList 数据
|
|
|
+ * @param parentClazz 父级类
|
|
|
+ * @param childClazz 子级类
|
|
|
+ * @param title 标题
|
|
|
+ * @param listName 子级的集合字段(属性)名
|
|
|
+ */
|
|
|
+ public static void exportWithParentChild(HttpServletResponse response, List<?> parentList, Class<?> parentClazz, Class<?> childClazz, String title, String listName) throws IOException {
|
|
|
+ // 获取注解信息
|
|
|
+ List<Excel> annoFieldInfo = new ArrayList<>();
|
|
|
+ annoFieldInfo.addAll(getAnnotatedFieldNames(parentClazz));
|
|
|
+ annoFieldInfo.addAll(getAnnotatedFieldNames(childClazz));
|
|
|
+
|
|
|
+ // 父级字段(属性)名
|
|
|
+ List<String> parentFieldNames = getFieldNames(parentClazz);
|
|
|
+
|
|
|
+ // 子级字段(属性)名
|
|
|
+ List<String> childFieldNames = getFieldNames(childClazz);
|
|
|
+
|
|
|
+ XSSFWorkbook workbook = new XSSFWorkbook();
|
|
|
+ // 创建表
|
|
|
+ Sheet sheet = workbook.createSheet(title);
|
|
|
+ // 创建标题行(表头)
|
|
|
+ Row titleRow = sheet.createRow(0);
|
|
|
+ int nameIndex = 0;
|
|
|
+ for (Excel excelInfo : annoFieldInfo) {
|
|
|
+ titleRow.createCell(nameIndex).setCellValue(excelInfo.name());
|
|
|
+ titleRow.getCell(nameIndex).setCellStyle(getCellStyleBorderWithColor(workbook, IndexedColors.GREY_50_PERCENT.getIndex(), IndexedColors.WHITE.getIndex()));
|
|
|
+ sheet.setColumnWidth(nameIndex, (int) ((excelInfo.width() + 0.72) * 256));
|
|
|
+ nameIndex++;
|
|
|
+ }
|
|
|
+ int rowIndex = 1; // 开始数据行的索引
|
|
|
+ for (Object parent : parentList) {
|
|
|
+ // 创建行
|
|
|
+ Row parentRow = sheet.createRow(rowIndex++);
|
|
|
+ // 填充父对象数据
|
|
|
+ int dataIndex = 0;
|
|
|
+ for (String fieldName : parentFieldNames) {
|
|
|
+ // 判断数据是否为空
|
|
|
+ Object parentValue = getFieldValueByName(parent, fieldName);
|
|
|
+ if (ObjectUtil.isNotNull(parentValue)) {
|
|
|
+ parentRow.createCell(dataIndex).setCellValue(parentValue.toString());
|
|
|
+ } else {
|
|
|
+ parentRow.createCell(dataIndex).setCellValue("");
|
|
|
+ }
|
|
|
+ parentRow.getCell(dataIndex).setCellStyle(getNormalCellStyle(workbook));
|
|
|
+
|
|
|
+ // 父级写入完成,获取子级数据
|
|
|
+ if (dataIndex + 1 == parentFieldNames.size()) {
|
|
|
+ // 获取子级集合
|
|
|
+ List<?> children = (List<?>) getFieldValueByName(parent, listName);
|
|
|
+ int childWriteLoop = 0;
|
|
|
+ // 循环子级集合
|
|
|
+ for (Object child : children) {
|
|
|
+ // 判断是否第一次循环子级集合
|
|
|
+ if (childWriteLoop > 0) { // 非第一次循环
|
|
|
+ int childDataIndex = dataIndex + 1;
|
|
|
+ // 新建行
|
|
|
+ Row childRow = sheet.createRow(rowIndex++);
|
|
|
+ // 需要写入空数据,否则设置样式会空指针异常
|
|
|
+ for (int nullIndex = 0; nullIndex <= dataIndex; nullIndex++) {
|
|
|
+ childRow.createCell(nullIndex).setCellValue("");
|
|
|
+ childRow.getCell(nullIndex).setCellStyle(getNormalCellStyle(workbook));
|
|
|
+ }
|
|
|
+ for (String childFieldName : childFieldNames) {
|
|
|
+ // 判断数据是否为空
|
|
|
+ Object childValue = getFieldValueByName(child, childFieldName);
|
|
|
+ if (ObjectUtil.isNotNull(childValue)) {
|
|
|
+ childRow.createCell(childDataIndex).setCellValue(childValue.toString());
|
|
|
+ } else {
|
|
|
+ childRow.createCell(childDataIndex).setCellValue("");
|
|
|
+ }
|
|
|
+ childRow.getCell(childDataIndex).setCellStyle(getNormalCellStyle(workbook));
|
|
|
+ childDataIndex++;
|
|
|
+ }
|
|
|
+ } else { // 第一次循环不新建行,直接跟在父级行后面填充数据
|
|
|
+ int childDataIndex = dataIndex + 1;
|
|
|
+ for (String childFieldName : childFieldNames) {
|
|
|
+ // 判断数据是否为空
|
|
|
+ Object childValue = getFieldValueByName(child, childFieldName);
|
|
|
+ if (ObjectUtil.isNotNull(childValue)) {
|
|
|
+ parentRow.createCell(childDataIndex).setCellValue(childValue.toString());
|
|
|
+ } else {
|
|
|
+ parentRow.createCell(childDataIndex).setCellValue("");
|
|
|
+ }
|
|
|
+ parentRow.getCell(childDataIndex).setCellStyle(getNormalCellStyle(workbook));
|
|
|
+ childDataIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ childWriteLoop++;
|
|
|
+ }
|
|
|
+ // 如果子级数据是空的,也需要填充空数据,否侧设置样式会空指针
|
|
|
+ if (children.isEmpty()) {
|
|
|
+ int childDataIndex = dataIndex + 1;
|
|
|
+ for (String childFieldName : childFieldNames) {
|
|
|
+ parentRow.createCell(childDataIndex).setCellValue("");
|
|
|
+ parentRow.getCell(childDataIndex).setCellStyle(getNormalCellStyle(workbook));
|
|
|
+ childDataIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 子级数据填充完成后,且子数据集合大于一合并父级对象单元格
|
|
|
+ if (!children.isEmpty() && children.size() > 1) {
|
|
|
+ for (int i = 0; i < parentFieldNames.size(); i++) {
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(rowIndex - 2, rowIndex - 1, i, i));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ dataIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置响应头
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
+ response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(title, "UTF-8") + ".xlsx");
|
|
|
+
|
|
|
+ // 写入Excel文件
|
|
|
+ OutputStream outputStream = response.getOutputStream();
|
|
|
+ workbook.write(outputStream);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过字段(属性)名获取指定字段的值
|
|
|
+ *
|
|
|
+ * @param obj 对象
|
|
|
+ * @param fieldName 字段名
|
|
|
+ * @return String
|
|
|
+ */
|
|
|
+ public static Object getFieldValueByName(Object obj, String fieldName) {
|
|
|
+ Object fieldValue = null;
|
|
|
+ try {
|
|
|
+ // 获取对象的Class对象
|
|
|
+ Class<?> clazz = obj.getClass();
|
|
|
+ // 通过反射获取指定名称的字段
|
|
|
+ Field field = clazz.getDeclaredField(fieldName);
|
|
|
+ // 设置字段可访问,以便能够访问私有字段
|
|
|
+ field.setAccessible(true);
|
|
|
+ // 获取字段值
|
|
|
+ fieldValue = field.get(obj);
|
|
|
+ } catch (NoSuchFieldException e) {
|
|
|
+ System.out.println("字段 " + fieldName + " 在类 " + obj.getClass().getName() + " 中不存在.");
|
|
|
+ } catch (IllegalAccessException e) {
|
|
|
+ System.out.println("无法访问字段 " + fieldName + " 的值.");
|
|
|
+ }
|
|
|
+ return fieldValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取类的字段名
|
|
|
+ *
|
|
|
+ * @param clazz class
|
|
|
+ * @return List<String>
|
|
|
+ */
|
|
|
+ public static List<String> getFieldNames(Class<?> clazz) {
|
|
|
+
|
|
|
+ List<String> fieldNames = new ArrayList<>();
|
|
|
+
|
|
|
+ // 获取类的所有声明字段(不包括继承的字段)
|
|
|
+ Field[] declaredFields = clazz.getDeclaredFields();
|
|
|
+ for (Field field : declaredFields) {
|
|
|
+ if (field.isAnnotationPresent(Excel.class)) {
|
|
|
+ fieldNames.add(field.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return fieldNames;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取clazz的注解name属性的值
|
|
|
+ *
|
|
|
+ * @param clazz 类
|
|
|
+ * @return List<String>
|
|
|
+ */
|
|
|
+ public static List<Excel> getAnnotatedFieldNames(Class<?> clazz) {
|
|
|
+ List<Excel> annoFieldInfo = new ArrayList<>();
|
|
|
+ Field[] fields = clazz.getDeclaredFields();
|
|
|
+
|
|
|
+ for (Field field : fields) {
|
|
|
+ field.setAccessible(true); // 确保可以访问私有字段
|
|
|
+ if (field.isAnnotationPresent(Excel.class)) {
|
|
|
+ Excel excelAnnotation = field.getAnnotation(Excel.class);
|
|
|
+ annoFieldInfo.add(excelAnnotation);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return annoFieldInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置单元格边框线与字体、颜色等
|
|
|
+ *
|
|
|
+ * @param workbook 工作簿
|
|
|
+ * @param backColor 背景色
|
|
|
+ * @param textColor 字体颜色
|
|
|
+ * @return CellStyle
|
|
|
+ */
|
|
|
+ public static CellStyle getCellStyleBorderWithColor(XSSFWorkbook workbook, Short backColor, Short textColor) {
|
|
|
+ // 设置边框样式
|
|
|
+ CellStyle style = workbook.createCellStyle();
|
|
|
+ if (ObjectUtil.isNotNull(backColor)) {
|
|
|
+ style.setFillBackgroundColor(backColor);
|
|
|
+ }
|
|
|
+ Font headerFont = workbook.createFont();
|
|
|
+ headerFont.setFontName("Arial");
|
|
|
+ headerFont.setFontHeightInPoints((short) 10);
|
|
|
+ if (ObjectUtil.isNotNull(textColor)) {
|
|
|
+ headerFont.setBold(true);
|
|
|
+ }
|
|
|
+ style.setFont(headerFont);
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置单元格边框线
|
|
|
+ *
|
|
|
+ * @param workbook 工作簿
|
|
|
+ * @return CellStyle
|
|
|
+ */
|
|
|
+ public static CellStyle getNormalCellStyle(XSSFWorkbook workbook) {
|
|
|
+ CellStyle style = workbook.createCellStyle();
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|