发布时间:2025-06-24 17:09:31 作者:北方职教升学中心 阅读量:800
/** * 数据处理:tableData与tableHeader对应关系处理 * * @param tableHeader 表格表头 [{"name":"指标项1","field":"index1code","children":[{"name":"指标项1-1","field":"index1code1"},{"name":"指标项1-2","field":"index1code2"]},]... * @param tableData 表格数据 * @return 处理后的数据 [["value11","value12"],["value21","value22"]] */ private static List<List<String>> data(List<Map<String, Object>> tableHeader, List<Map<String, Object>> tableData) { List<List<String>> dataList = new ArrayList<>(); List<String> fieldList = titleFieldList(tableHeader); for (Map<String, Object> row : tableData) { List<String> list = new ArrayList<>(); for (String field : fieldList) { list.add(Optional.ofNullable(row.get(field)).map(Object::toString).orElse("")); } dataList.add(list); } return dataList; } /** * 获取表头field列表 * * @param tableHeader * @return */ private static List<String> titleFieldList(List<Map<String, Object>> tableHeader) { ArrayList<String> list = new ArrayList<>(); for (Map<String, Object> map : tableHeader) { if (map.get("children") instanceof List) { List<Map<String, Object>> children = (List<Map<String, Object>>) map.get("children"); List<String> child = titleFieldList(children); list.addAll(child); } else { list.add(Optional.ofNullable(map.get("field")).map(Object::toString).orElse("")); } } return list; }
3.实战测试
4.使用方法
1.controller层两行代码进行调用即可,动态列导出,不需要再定义相关的VO实体类
2.一般由serviceImpl来做对数据的处理,这里我们用一个map集合就可以。
1.背景
在后端开发中,有时会涉及到excel文件导出,这个一方面最趁手的工具莫过于easyExcel。
public CustomResultBO searchXxxList(xxxQueryBO queryBO){ //表头数据 List<ALlDistOrgEvaConfigPO> headList = RemoteSqlUtil.queryForList(DB_INSTANCE, sqlId + "getMgtIndexOfOrgEvaConfig", queryPO, ALlDistOrgEvaConfigPO.class);//table-data数据List<MgtIndexEvaluationResultPO> dataList = RemoteSqlUtil.queryForList(DB_INSTANCE, sqlId + "getMgtIndicEvaList", queryPO, MgtIndexEvaluationResultPO.class);// 数据处理:1.拼装表头 2.table数据封装List<Map<String, Object>> tableHeader = new ArrayList<>();List<Map<String, Object>> tableData = new ArrayList<>();// 1.拼装表头if (CollectionUtil.isNotEmpty(configList)) { // 管理单位字段 Map<String, Object> mgtOrgHeader = new HashMap<>(); mgtOrgHeader.put("field", "mgtOrgName"); mgtOrgHeader.put("name", "管理单位"); tableHeader.add(mgtOrgHeader); // 总体评分字段 Map<String, Object> totalScore = new HashMap<>(); totalScore.put("field", "totalScore"); totalScore.put("name", "总体评分"); tableHeader.add(totalScore); ...// 根据请求得来的headList数据,对表头进行处理(父子关系等)}// 2.table数据封装if (CollectionUtil.isNotEmpty(dataList)) { // 对请求得到的dataList数据集根据业务需要转为List<Map<String, Object>>的形式 // 每一个list对应页面上的一行记录,map中的key为与headList中的field字段对 //应,value为具体的值}CustomResultBO resultBO = new CustomResultBO();resultBO.setTableHeader(tableHeader);resultBO.setTableData(tableData);return resultBO;}
5.附:ExcelUtils代码
/** * @author : tianlong * @ClassName : ExcelUtils * @description: * @datetime : 2024/ 06/ 10 * @version: : 1.0 */public class ExcelUtils { private static final Logger log = LoggerFactory.getLogger(ExcelUtils.class); /** * 多级excel动态导出 * * @author : tianlong * @param response * @param fileName 文件名 * @param tableHeader 表头 [{"name":"指标项1","field":"index1code","children":[{"name":"指标项1-1","field":"index1code1"},{"name":"指标项1-2","field":"index1code2"]},] * @param tableData 数据集合 [{"index1code":"value1","index2code":"value2"},...] key与tableHeader的field对应, value为单元格数据 * @param mark 水印 */ public static void exportDynamic(HttpServletResponse response, String fileName, List<Map<String, Object>> tableHeader, List<Map<String, Object>> tableData, String mark) { try { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); FileUtils.checkFileName(fileName, "xlsx"); ServletOutputStream out = response.getOutputStream(); if (StringUtil.isBlank(mark)) { mark = "xx1.0"; } String sheetName = FileUtils.getFilePrefix(fileName);; EasyExcel.write(out) .head(head(tableHeader)) .sheet(sheetName) .registerWriteHandler(new WaterMarkStrategy(new WaterMark(mark))) .registerWriteHandler(new CustemHandler()) .doWrite(data(tableHeader, tableData)); } catch (IOException e) { throw new RuntimeException(e); } } /** * 处理表头 * * @param tableHeader 表头 [{"name":"指标项1","field":"index1code","children":[{"name":"指标项1-1","field":"index1code1"},{"name":"指标项1-2","field":"index1code2"]},] * @return 表头名列表 [[指标项1, 指标项1-1],[指标项1, 指标项1-1]] */ public static List<List<String>> head(List<Map<String, Object>> tableHeader) { List<Object> columnsList = new ArrayList<>(); int maxDeep = getDeep(tableHeader); for (Map<String, Object> map : tableHeader) { List<Object> columnItem = getChildren(map, null, maxDeep); columnsList.add(columnItem); } return flatList(columnsList); } /** * 数据处理:tableData与tableHeader对应关系处理 * * @param tableHeader 表格表头 [{"name":"指标项1","field":"index1code","children":[{"name":"指标项1-1","field":"index1code1"},{"name":"指标项1-2","field":"index1code2"]},]... * @param tableData 表格数据 * @return 处理后的数据 [["value11","value12"],["value21","value22"]] */ private static List<List<String>> data(List<Map<String, Object>> tableHeader, List<Map<String, Object>> tableData) { List<List<String>> dataList = new ArrayList<>(); List<String> fieldList = titleFieldList(tableHeader); for (Map<String, Object> row : tableData) { List<String> list = new ArrayList<>(); for (String field : fieldList) { list.add(Optional.ofNullable(row.get(field)).map(Object::toString).orElse("")); } dataList.add(list); } return dataList; } /** * 获取表头field列表 * * @param tableHeader * @return */ private static List<String> titleFieldList(List<Map<String, Object>> tableHeader) { ArrayList<String> list = new ArrayList<>(); for (Map<String, Object> map : tableHeader) { if (map.get("children") instanceof List) { List<Map<String, Object>> children = (List<Map<String, Object>>) map.get("children"); List<String> child = titleFieldList(children); list.addAll(child); } else { list.add(Optional.ofNullable(map.get("field")).map(Object::toString).orElse("")); } } return list; } /** * 递归表头map获取所有列 * * @param columnList [{"name":"指标项1","field":"index1code","children":[{"name":"指标项1-1","field":"index1code1"},{"name":"指标项1-2","field":"index1code2"]}] * @param top 路径列名列表 * @param deep 表头深度最大深度 * @return [[指标项1, 指标项1-1],[指标项1, 指标项1-2]] */ private static List<Object> getChildren(Map<String, Object> columnList, List<String> top, int deep) { if (columnList == null) return null; if (top == null) top = new ArrayList<>(); List<Object> current = new ArrayList<>(); String curNodeName =Optional.ofNullable(columnList.get("name").toString()).map(Object::toString).orElse(""); // 如果有子节点,则递归调用 if (columnList.get("children") instanceof List) { top.add(curNodeName); List<Map<String, Object>> children = (List<Map<String, Object>>) columnList.get("children"); for (Map<String, Object> child : children) { current.add(getChildren(child, new ArrayList<>(top), deep)); } } else { // 叶子节点时填充列 int size = top.size(); String copyName = (size > 0) ? top.get(size - 1) : curNodeName; List<String> tempList = new ArrayList<>(top); while (tempList.size() + 1 < deep) { tempList.add(copyName); } tempList.add(curNodeName); current = new ArrayList<>(tempList); } return current; } /** * 打平嵌套list * * @param nestedList [[指标项1, 指标项1, 指标项1], [[指标项2, 指标项2, 指标项1], [指标项2, 指标项2, 指标项3]]] * @return [[指标项1, 指标项1, 指标项1], [指标项2, 指标项2, 指标项1], [指标项2, 指标项2, 指标项3]] */ private static List<List<String>> flatList(List<?> nestedList) { List<List<String>> rs = new ArrayList<>(); for (Object element : nestedList) { if (element instanceof List) { rs.addAll(flatList((List<?>) element)); } else { // 不是list数据类型,就不需要遍历 rs.add(new ArrayList<>((List<String>) nestedList)); return rs; } } return rs; } /** * 获取表头深度方法 * * @param tableHeader [{"name":"指标项1","field":"index1code"},{"name":"指标项2","field":"index2code"},...] * @return deep深度 */ private static int getDeep(List<Map<String, Object>> tableHeader) { int deep = 0; for (Map<String, Object> map : tableHeader) { if (map.get("children") instanceof List) { List<Map<String, Object>> children = (List<Map<String, Object>>) map.get("children"); if (!children.isEmpty()) { deep = Math.max(deep, getDeep(children)); } } } return deep + 1; }}
2.实现步骤
2.1定义一个工具类
public class ExcelUtils { /** * 多级excel动态导出 * * @author : tianlong * @param response * @param fileName 文件名 * @param tableHeader 表头 [{"name":"指标项1","field":"index1code","children":[{"name":"指标项1-1","field":"index1code1"},{"name":"指标项1-2","field":"index1code2"]},] * @param tableData 数据集合 [{"index1code":"value1","index2code":"value2"},...] key与tableHeader的field对应, value为单元格数据 * @param mark 水印 */ public static void exportDynamic(HttpServletResponse response, String fileName, List<Map<String, Object>> tableHeader, List<Map<String, Object>> tableData, String mark) { try { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); FileUtils.checkFileName(fileName, "xlsx"); ServletOutputStream out = response.getOutputStream(); if (StringUtil.isBlank(mark)) { mark = "xx1.0"; } String sheetName = FileUtils.getFilePrefix(fileName);; EasyExcel.write(out) .head(head(tableHeader)) .sheet(sheetName) .registerWriteHandler(new WaterMarkStrategy(new WaterMark(mark))) //.registerWriteHandler(new CustemHandler()) .doWrite(data(tableHeader, tableData)); } catch (IOException e) { throw new RuntimeException(e); } }}
2.2处理列表头
head()对表头列的处理是这里面比较难办的地方。不管是多少级表头的excel的导出,需要更多些的考虑。
我们在处理多级动态列导出时,无法知道当前表头是多少级的,在知道多少级之后,还需要填充我们得到的配置列数据,以便让表头深度对齐。像下面这样来对查表后得到的数据进行加工至我们预期的那种格式。具体代码实现如下所示:
/** * 处理表头 * * @param tableHeader 表头 [{"name":"指标项1","field":"index1code","children":[{"name":"指标项1-1","field":"index1code1"},{"name":"指标项1-2","field":"index1code2"]},] * @return 表头名列表 [[指标项1, 指标项1-1],[指标项1, 指标项1-1]] */ public static List<List<String>> head(List<Map<String, Object>> tableHeader) { List<Object> columnsList = new ArrayList<>(); int maxDeep = getDeep(tableHeader); for (Map<String, Object> map : tableHeader) { List<Object> columnItem = getChildren(map, null, maxDeep); columnsList.add(columnItem); } return flatList(columnsList); }/** * 递归表头map获取所有列 * * @param columnList [{"name":"指标项1","field":"index1code","children":[{"name":"指标项1-1","field":"index1code1"},{"name":"指标项1-2","field":"index1code2"]}] * @param top 路径列名列表 * @param deep 表头深度最大深度 * @return [[指标项1, 指标项1-1],[指标项1, 指标项1-2]] */ private static List<Object> getChildren(Map<String, Object> columnList, List<String> top, int deep) { if (columnList == null) return null; if (top == null) top = new ArrayList<>(); List<Object> current = new ArrayList<>(); String curNodeName =Optional.ofNullable(columnList.get("name").toString()).map(Object::toString).orElse(""); // 如果有子节点,则递归调用 if (columnList.get("children") instanceof List) { top.add(curNodeName); List<Map<String, Object>> children = (List<Map<String, Object>>) columnList.get("children"); for (Map<String, Object> child : children) { current.add(getChildren(child, new ArrayList<>(top), deep)); } } else { // 叶子节点时填充列 int size = top.size(); String copyName = (size > 0) ? top.get(size - 1) : curNodeName; List<String> tempList = new ArrayList<>(top); while (tempList.size() + 1 < deep) { tempList.add(copyName); } tempList.add(curNodeName); current = new ArrayList<>(tempList); } return current; } /** * 打平嵌套list * * @param nestedList [[指标项1, 指标项1, 指标项1], [[指标项2, 指标项2, 指标项1], [指标项2, 指标项2, 指标项3]]] * @return [[指标项1, 指标项1, 指标项1], [指标项2, 指标项2, 指标项1], [指标项2, 指标项2, 指标项3]] */ private static List<List<String>> flatList(List<?> nestedList) { List<List<String>> rs = new ArrayList<>(); for (Object element : nestedList) { if (element instanceof List) { rs.addAll(flatList((List<?>) element)); } else { // 不是list数据类型,就不需要遍历 rs.add(new ArrayList<>((List<String>) nestedList)); return rs; } } return rs; }
当然,获取表头深度,我们采用递归的做法:
/** * 获取表头深度方法 * * @param tableHeader [{"name":"指标项1","field":"index1code"},{"name":"指标项2","field":"index2code"},...] * @return deep深度 */ private static int getDeep(List<Map<String, Object>> tableHeader) { int deep = 0; for (Map<String, Object> map : tableHeader) { if (map.get("children") instanceof List) { List<Map<String, Object>> children = (List<Map<String, Object>>) map.get("children"); if (!children.isEmpty()) { deep = Math.max(deep, getDeep(children)); } } } return deep + 1; }
2.3处理列表头与数据的对应关系
剩下的部分就比较好做了。因为要想做出一个通用的、
这里需要先提一下easyExcel单元格合并的机制了,单元格(以列为角度)对应的field相同的自动合并在一起,当然,两个相邻的单元格对应的filed相同的,也会合并在一起。首先,确定确定要处理数据的格式:
[ { "name":"指标项1", "field":"index1code", "children":[ { "name": "指标项1-1", "field":"index1code11" }, { "name": "指标项1-2", "field":"index1code12", "children":[ { "name": "指标项1-2-1", "field":"index1code121" }, ] }, { "name": "指标项2", "field":"index1code2" },]
可以看出它是一个多级的样子。同样,easyExcel提供的动态列导出功能,官网例子相对简单,在这里我就easyExcel的动态列导出做进一步的丰富和实现,使其可以做到多级表头的动态导出。