
下面给你一套完整可落地的"动态透视报表方案(查询 + 导出)",专门针对:
👉 SpringBoot + MyBatis + GaussDB + EasyPoi
👉 动态行分组(产品/型号/项目)+ 动态表头 + 动态汇总 + 页面接口 + 导出接口
重点我会强化:"动态表头 + 查询接口返回结构",让你前端也能直接渲染。
一、最终效果(你要实现的)
| 产品 | 型号 | 项目 | 6月 | 7月 | 8月 | 合计 |
|---|---|---|---|---|---|---|
| AA | AA | AA | 20 | 40 | 60 | 120 |
| BB | BB | BB | 40 | 60 | 80 | 180 |
| 合计 | 60 | 100 | 140 | 300 |
二、接口设计(核心)
✅ 查询接口(页面用)
http
POST /pivot/query
返回:
json
{
"headers": [...动态表头...],
"data": [...数据...]
}
✅ 导出接口(Excel)
http
POST /pivot/export
三、Mapper 层(动态 SQL)
1️⃣ 接口
java
public interface PivotMapper {
List<String> queryMonths();
List<Map<String, Object>> pivotData(
@Param("months") List<String> months,
@Param("groups") List<String> groups
);
}
2️⃣ 查询动态列
xml
<select id="queryMonths" resultType="string">
SELECT DISTINCT month
FROM sales_data
ORDER BY month
</select>
⭐ 3️⃣ 核心透视 SQL
xml
<select id="pivotData" resultType="map">
SELECT
<foreach collection="groups" item="g" separator=",">
${g}
</foreach>
<foreach collection="months" item="m">
, SUM(amount) FILTER (WHERE month = #{m}) AS "${m}"
</foreach>
, SUM(amount) AS "合计"
FROM sales_data
GROUP BY
<foreach collection="groups" item="g" separator=",">
${g}
</foreach>
UNION ALL
SELECT
<foreach collection="groups" item="g" separator=",">
<choose>
<when test="g == 'item'"> '合计' </when>
<otherwise> '' </otherwise>
</choose>
</foreach>
<foreach collection="months" item="m">
, SUM(amount) FILTER (WHERE month = #{m})
</foreach>
, SUM(amount)
FROM sales_data
</select>
四、Service(重点:动态表头构造)
java
@Service
public class PivotService {
@Autowired
private PivotMapper pivotMapper;
public Map<String, Object> query(List<String> groups) {
checkGroups(groups);
// 1. 动态列
List<String> months = pivotMapper.queryMonths();
// 2. 查询数据
List<Map<String, Object>> data =
pivotMapper.pivotData(months, groups);
// 3. 构造动态表头(关键)
List<Map<String, String>> headers =
buildHeaders(groups, months);
Map<String, Object> result = new HashMap<>();
result.put("headers", headers);
result.put("data", data);
return result;
}
// ⭐ 动态表头(前端用)
private List<Map<String, String>> buildHeaders(
List<String> groups, List<String> months) {
List<Map<String, String>> list = new ArrayList<>();
// 行维度
for (String g : groups) {
list.add(header(getName(g), g));
}
// 动态列
for (String m : months) {
list.add(header(m, m));
}
// 合计
list.add(header("合计", "合计"));
return list;
}
private Map<String, String> header(String title, String field) {
Map<String, String> map = new HashMap<>();
map.put("title", title);
map.put("field", field);
return map;
}
private String getName(String field) {
switch (field) {
case "product": return "产品";
case "model": return "型号";
case "item": return "项目";
default: return field;
}
}
// 导出
public Workbook export(List<String> groups) {
Map<String, Object> result = query(groups);
List<Map<String, Object>> data =
(List<Map<String, Object>>) result.get("data");
List<Map<String, String>> headers =
(List<Map<String, String>>) result.get("headers");
List<ExcelExportEntity> excelHeaders =
buildExcelHeader(headers);
ExportParams params =
new ExportParams("动态报表", "sheet1");
return ExcelExportUtil.exportExcel(params, excelHeaders, data);
}
// EasyPoi 表头
private List<ExcelExportEntity> buildExcelHeader(
List<Map<String, String>> headers) {
List<ExcelExportEntity> list = new ArrayList<>();
for (Map<String, String> h : headers) {
list.add(new ExcelExportEntity(
h.get("title"), h.get("field")));
}
return list;
}
// 安全校验
private void checkGroups(List<String> groups) {
List<String> allow =
Arrays.asList("product","model","item");
for (String g : groups) {
if (!allow.contains(g)) {
throw new RuntimeException("非法字段:" + g);
}
}
}
}
五、Controller(查询 + 导出)
java
@RestController
@RequestMapping("/pivot")
public class PivotController {
@Autowired
private PivotService pivotService;
// 查询接口(前端表格)
@PostMapping("/query")
public Map<String, Object> query(
@RequestBody Map<String, Object> param) {
List<String> groups =
(List<String>) param.get("groups");
return pivotService.query(groups);
}
// 导出接口
@PostMapping("/export")
public void export(
@RequestBody Map<String, Object> param,
HttpServletResponse response) throws Exception {
List<String> groups =
(List<String>) param.get("groups");
Workbook wb = pivotService.export(groups);
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition",
"attachment;filename=pivot.xls");
wb.write(response.getOutputStream());
}
}
六、前端渲染(重点)
后端返回:
json
{
"headers": [
{"title":"产品","field":"product"},
{"title":"型号","field":"model"},
{"title":"项目","field":"item"},
{"title":"6月","field":"6月"},
{"title":"7月","field":"7月"},
{"title":"8月","field":"8月"},
{"title":"合计","field":"合计"}
],
"data":[...]
}
👉 前端可以直接:
javascript
columns = headers.map(h => ({
title: h.title,
dataIndex: h.field
}))
七、关键点总结(精华)
✅ 动态三核心
| 类型 | 实现 |
|---|---|
| 行分组 | ${g} |
| 列 | FILTER |
| 表头 | headers + ExcelExportEntity |
✅ 查询接口 = 导出接口基础
text
query() → export()
⚠️ 安全(必须)
java
白名单 groups
八、可升级(强烈建议)
🚀 1. 多级表头(年 → 月)
🚀 2. 多指标(数量 + 金额)
🚀 3. 前端拖拽维度(真正数据透视)
🚀 4. 大数据导出(分批写 Excel)
九、一句话总结
👉 动态 SQL(行+列) + 动态表头(前端+EasyPoi) = 完整透视报表系统
如果你下一步想做👇我可以帮你直接升级:
👉 多级表头(合并单元格)
👉 类 Excel 数据透视(拖拽维度)
👉 千万级数据不卡导出方案
直接说你的目标 👍