| | |
| | | } |
| | | |
| | | /** |
| | | * 图表Sheet构建回调:由调用方在给定工作簿上追加一个(或多个)带图表的Sheet |
| | | */ |
| | | public interface ChartSheetBuilder { |
| | | void build(XSSFWorkbook workbook); |
| | | } |
| | | |
| | | /** |
| | | * 取底层 XSSFWorkbook(SXSSF 流式工作簿也支持挂原生图表),不支持时返回 null |
| | | */ |
| | | public XSSFWorkbook getXssfWorkbook() { |
| | | if (wb instanceof SXSSFWorkbook) { |
| | | return ((SXSSFWorkbook) wb).getXSSFWorkbook(); |
| | | } |
| | | if (wb instanceof XSSFWorkbook) { |
| | | return (XSSFWorkbook) wb; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 导出两个数据Sheet + 一个图表Sheet(图表内容由调用方通过 builder 追加) |
| | | * |
| | | * @param response 返回数据 |
| | | * @param list1 第一个Sheet的数据集合 |
| | | * @param sheetName1 第一个Sheet的名称 |
| | | * @param list2 第二个Sheet的数据集合 |
| | | * @param sheetName2 第二个Sheet的名称 |
| | | * @param clazz2 第二个Sheet数据对象的Class |
| | | * @param chartBuilder 图表Sheet构建回调,可为空(为空时与两Sheet导出完全一样) |
| | | */ |
| | | public void exportExcelTwoSheetWithChart(HttpServletResponse response, |
| | | List<T> list1, String sheetName1, |
| | | List<?> list2, String sheetName2, Class<?> clazz2, |
| | | ChartSheetBuilder chartBuilder) { |
| | | response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
| | | response.setCharacterEncoding("utf-8"); |
| | | // 初始化并写入Sheet1 |
| | | this.init(list1, sheetName1, StringUtils.EMPTY, Type.EXPORT); |
| | | writeSheet(); |
| | | // 追加写入Sheet2 |
| | | writeAdditionalSheet(list2, sheetName2, clazz2); |
| | | // 追加图表Sheet |
| | | if (chartBuilder != null) { |
| | | XSSFWorkbook xssfWorkbook = getXssfWorkbook(); |
| | | if (xssfWorkbook != null) { |
| | | chartBuilder.build(xssfWorkbook); |
| | | } else { |
| | | log.warn("当前工作簿不支持原生图表,跳过图表Sheet"); |
| | | } |
| | | } |
| | | // 输出到响应流 |
| | | try { |
| | | wb.write(response.getOutputStream()); |
| | | } catch (Exception e) { |
| | | log.error("导出Excel异常{}", e.getMessage()); |
| | | } finally { |
| | | IOUtils.closeQuietly(wb); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 向当前工作簿追加写入一个Sheet |
| | | * |
| | | * @param dataList 数据集合 |
| | |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | public void writeAdditionalSheet(List<?> dataList, String sheetName, Class<?> dataClazz) { |
| | | if (dataList == null) { |
| | | dataList = new ArrayList<>(); |
| | | // 没有数据或没有行类型时跳过附加 Sheet: |
| | | // SatisfactionExportBundle.empty() 的 rowClass 为 null,若继续执行会把 this.clazz 置空, |
| | | // 随后 getFields() 调用 clazz.getSuperclass() 触发 NullPointerException。 |
| | | if (dataClazz == null || dataList == null || dataList.isEmpty()) { |
| | | return; |
| | | } |
| | | // 保存原始状态 |
| | | Class<T> originClazz = this.clazz; |
| | |
| | | */ |
| | | public List<Object[]> getFields() { |
| | | List<Object[]> fields = new ArrayList<Object[]>(); |
| | | // 防御:clazz 为空时不再调用 getSuperclass(),避免 NullPointerException。 |
| | | if (clazz == null) { |
| | | log.warn("ExcelUtil.clazz 为空,无法解析导出字段,跳过当前 Sheet"); |
| | | return fields; |
| | | } |
| | | List<Field> tempFields = new ArrayList<>(); |
| | | tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); |
| | | tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); |