通用 Excel 导出功能设计与实现:动态列选择与灵活配置

发布于:2025-06-26 ⋅ 阅读:(22) ⋅ 点赞:(0)

在企业级应用开发中,数据导出是高频需求。本文介绍一种支持动态列选择、灵活配置的通用 Excel 导出方案,通过前后端协同设计,实现导出字段、列顺序、数据格式的自定义,满足多样化业务场景。

一、功能架构设计

核心特性

  1. 动态字段选择:支持通过前端勾选动态指定导出字段,包含字段名(逻辑标识)与显示名(业务含义)的映射
  1. 行数据过滤:支持按用户 ID 筛选导出特定行数据
  1. 多 Sheet 支持:可扩展支持单个 Excel 文件包含多个 Sheet 页
  1. 格式自适应:自动处理日期、数字等数据类型的格式化显示

技术栈

  • 前端:Thymeleaf 模板引擎 + XMLHttpRequest 文件下载
  • 后端:Spring Boot + EasyExcel + Hutool 工具集
  • 核心组件
  • 请求参数:ExcelExportRequest(包含基础配置与字段列表)
  • 响应结构:ExcelExportResponse(封装文件元信息与 Sheet 数据)

二、核心实现细节

1. 前后端数据协议设计

入参结构(ExcelExportRequest)
@Data

public class UserExportRequest extends ExcelExportRequest {

private List<Integer> userIdList; // 待导出的用户ID列表(可选)

}

@Data

public class ExcelExportRequest {

private String excelName; // Excel文件名

private String sheetName; // Sheet页名称

private List<ExcelExportField> fieldList; // 导出字段列表(有序)

}

@Data

public class ExcelExportField {

private String fieldName; // 实体类字段名(如"userId")

private String fieldDesc; // 表格显示名称(如"用户ID")

}
出参结构(ExcelExportResponse)
@Data

public class ExcelExportResponse {

private String excelName; // 导出文件名

private List<ExcelSheet> sheetList; // Sheet数据集合

@Data

public static class ExcelSheet {

private String sheetName; // Sheet名称

private List<ExcelHead> headList; // 表头信息

private List<Map<String, String>> dataList; // 行数据(键值对形式)

@Data

public static class ExcelHead {

private String fieldName; // 字段名

private String fieldDesc; // 显示名

}

}

}

2. 前端交互实现

动态列选择组件

<!-- 案例1:仅列选择 -->

<table border="1">

<caption>

<span class="title">案例1:勾选需要导出的列</span>

<button onclick="exportExcel1(event)">导出</button>

</caption>

<tr>

<th><label><input type="checkbox" class="exportCol" data-field-name="userId" data-field-desc="用户id"> 用户id</label></th>

<th><label><input type="checkbox" class="exportCol" data-field-name="userName" data-field-desc="用户名">用户名</label></th>

<!-- 更多字段... -->

</tr>

</table>

<!-- 案例2:列选择+行筛选 -->

<table border="1">

<caption>

<span class="title">案例2:勾选需要导出的列 & 行</span>

<button onclick="exportExcel2(event)">导出</button>

</caption>

<tr>

<th>选择记录</th>

<th><label><input type="checkbox" class="exportCol" data-field-name="userId" data-field-desc="用户id"> 用户id</label></th>

<!-- 更多字段... -->

</tr>

<tr th:each="user:${userList}">

<td><input type="checkbox" class="userId" th:data-user-id="${user.userId}"></td>

<td th:text="${user.userId}"></td>

<!-- 数据行展示... -->

</tr>

</table>
文件下载逻辑
function download(data, url) {

const xhr = new XMLHttpRequest();

xhr.open("POST", url);

xhr.responseType = 'blob';

xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8');

xhr.onload = function() {

if (this.status === 200) {

const blob = this.response;

if (blob.size > 0) {

// 从响应头解析文件名

const fileName = getFileNameFromResponse(this.getResponseHeader("content-disposition"));

// 创建临时链接下载

const a = document.createElement('a');

a.href = URL.createObjectURL(blob);

a.download = fileName;

a.click();

}

}

};

xhr.send(JSON.stringify(data));

}

// 文件名解析工具

function getFileNameFromResponse(disposition) {

const match = /filename=(.*)/.exec(disposition);

return decodeURIComponent(match[1].replace(/['"]/g, ''));

}

3. 后端核心处理

控制器设计
@Controller

@CrossOrigin

public class UserController {

@Resource private UserService userService;

// 页面跳转

@GetMapping("/userList")

public String userList(Model model) {

model.addAttribute("userList", userService.getUserList());

return "userList";

}

// 导出接口

@PostMapping("/userExport")

public void userExport(@RequestBody UserExportRequest request) throws IOException {

ExcelExportResponse response = userService.userExport(request);

ExcelExportUtils.writeExcelToResponse(response);

}

}
业务层逻辑
@Service

public class UserServiceImpl implements UserService {

@Override

public ExcelExportResponse userExport(UserExportRequest request) {

List<User> dataList;

// 处理行筛选逻辑

if (CollectionUtil.isEmpty(request.getUserIdList())) {

dataList = getUserList(); // 导出全部数据

} else {

dataList = getUserList(request.getUserIdList()); // 按ID筛选

}

// 构建导出数据

return ExcelExportUtils.build(dataList, request);

}

// 模拟数据获取

private List<User> getUserList() {

List<User> list = new ArrayList<>();

for (int i = 1; i <= 10; i++) {

list.add(new User(i, "用户名-" + i, 20 + i, "地址-" + i));

}

return list;

}

}
导出工具类
public class ExcelExportUtils {

public static ExcelExportResponse build(List<?> dataList, ExcelExportRequest request) {

ExcelExportResponse result = new ExcelExportResponse();

result.setExcelName(request.getExcelName());

List<ExcelSheet> sheetList = new ArrayList<>();

ExcelSheet sheet = new ExcelSheet();

sheet.setSheetName(request.getSheetName());

// 构建表头(保持字段顺序)

sheet.setHeadList(buildSheetHeadList(request.getFieldList()));

// 构建数据行(通过反射获取字段值)

sheet.setDataList(buildSheetDataList(dataList, request.getFieldList()));

sheetList.add(sheet);

result.setSheetList(sheetList);

return result;

}

private static List<ExcelSheet.ExcelHead> buildSheetHeadList(List<ExcelExportField> fields) {

return fields.stream()

.map(field -> new ExcelSheet.ExcelHead(field.getFieldName(), field.getFieldDesc()))

.collect(Collectors.toList());

}

// 反射获取对象字段值

private static List<Map<String, String>> buildSheetDataList(List<?> dataList, List<ExcelExportField> fields) {

return dataList.stream().map(obj -> {

Map<String, String> row = new HashMap<>();

fields.forEach(field -> {

Object value = ReflectUtil.getFieldValue(obj, field.getFieldName());

row.put(field.getFieldName(), Objects.toString(value, ""));

});

return row;

}).collect(Collectors.toList());

}

// 响应输出处理

public static void writeExcelToResponse(ExcelExportResponse result) throws IOException {

HttpServletResponse response = getResponse();

response.setContentType("application/vnd.ms-excel");

response.setHeader("Content-Disposition",

"attachment; filename=" + URLEncodeUtil.encode(result.getExcelName() + ".xlsx"));

try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) {

result.getSheetList().forEach(sheet -> {

WriteSheet writeSheet = EasyExcel.writerSheet(sheet.getSheetName()).build();

// 写入表头与数据

writer.write(buildEasyExcelData(sheet), writeSheet);

});

}

}

}

三、方案优势分析

  1. 灵活性:通过fieldList实现导出字段的动态排序与筛选,适应不同业务视图需求
  1. 扩展性:支持添加多 Sheet、数据格式化(如日期转换)、样式配置等扩展功能
  1. 易用性:前端可视化勾选操作,后端自动处理反射映射,降低使用门槛
  1. 性能优化:通过工具类封装重复逻辑,减少代码冗余,提升开发效率

四、应用场景

  • 数据报表导出:支持不同角色用户自定义报表字段
  • 批量数据下载:结合行筛选功能实现精准数据提取
  • 系统对接:为第三方系统提供标准化 Excel 数据输出接口

通过该方案,开发者可快速实现具备灵活配置能力的 Excel 导出功能,同时保持代码的可维护性与扩展性。实际应用中可根据业务需求,进一步扩展数据格式化、单元格样式、多语言支持等高级功能。


网站公告

今日签到

点亮在社区的每一天
去签到