SpringBoot 项目实战:ECharts 数据可视化 + POI Excel 报表导出完整版教程
本文基于 SpringBoot 后端项目,完整实现ECharts 图形报表展示(会员折线图、套餐饼图)、运营数据统计、POI 报表导出功能,全程代码可直接复用,适合后端数据可视化场景学习。
一、ECharts 基础入门
1.1 ECharts 简介
ECharts(Enterprise Charts)是百度开源的商业级数据可视化库,基于 JavaScript 实现,兼容 PC / 移动端与主流浏览器,底层依赖 ZRender 矢量图形库,支持折线图、饼图、柱状图、地图等丰富图表类型。
- 官网:https://echarts.apache.org/
- 下载:npm install echarts 或下载 dist 包引入 echarts.js
1.2 5 分钟快速上手 ECharts
- 引入 js 文件
html
<script src="echarts.js"></script>- 准备 DOM 容器(必须设置宽高)
<div id="main" style="width: 600px;height:400px;"></div>- 初始化并渲染图表
javascript
var myChart = echarts.init(document.getElementById('main')); var option = { title: { text: 'ECharts 入门示例' }, tooltip: {}, legend: { data:['销量'] }, xAxis: { data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"] }, yAxis: {}, series: [{ name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }] }; myChart.setOption(option);二、会员数量折线图实现
2.1 需求说明
展示过去 12 个月每月会员总量,直观反映会员增长趋势。
2.2 前端页面实现(ReportMember.vue)
- 准备图表容器
- 发送 Ajax 请求获取后台数据
- 基于 ECharts 渲染折线图
数据格式要求
json
{ "data":{ "months":["2019.01","2019.02","2019.03","2019.04",...], "memberCount":[3,4,8,10,...] }, "flag":true, "message":"获取会员统计数据成功" }2.3 后台代码实现
2.3.1 Controller(ReportController)
java
/** * 统计报表 Controller * 用于提供各类统计报表数据,包括会员统计、套餐预约统计等 * @author hg */ @RestController @RequestMapping("/report") public class ReportController { @Autowired private ReportService reportService; /** * 查询近 12 个月的会员数量统计报表 * 统计过去 12 个月每个月的会员注册数量,用于折线图展示 * * @return Result 包含统计数据的统一响应对象 * 成功时返回: * - memberCount: List<Integer> 12 个月的会员数量列表 * - monthList: List<String> 12 个月的月份列表(格式:yyyy-MM) */ @RequestMapping("/getMemberReport") public Result getMemberReport(){ try { // 调用 Service 层获取会员统计数据 Map<String,Object> map = reportService.getMemberReport(); return new Result(true, MessageConstant.GET_MEMBER_NUMBER_REPORT_SUCCESS, map); }catch (Exception e){ e.printStackTrace(); return new Result(false, MessageConstant.GET_MEMBER_NUMBER_REPORT_FAIL); } } }2.3.2 Service 接口
java
public interface ReportService { Map<String, Object> getMemberReport(); }2.3.3 Service 实现类
java
/** * 统计报表 Service 实现类 * 提供会员统计、套餐预约统计等业务逻辑实现 * @author hg */ @Service public class ReportServiceImpl implements ReportService { @Autowired private ReportMapper reportMapper; /** * 查询近 12 个月的会员数量统计 * 从当前月份往前推算 12 个月,统计每个月的会员注册数量 * * @return Map<String, Object> 包含会员统计数据 * - memberCount: List<Integer> 12 个月的会员数量列表 * - monthList: List<String> 12 个月的月份列表(格式:yyyy-MM) */ @Override public Map<String, Object> getMemberReport() { // 日期格式化器,格式为 yyyy-MM(如:2026-04) SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM"); // 获取当前日期的日历实例 Calendar calendar = Calendar.getInstance(); // 往前推 12 个月,作为统计的起始月份 calendar.add(Calendar.MONTH, -12); // 存储 12 个月的月份列表 List<String> list = new ArrayList<>(); // 存储 12 个月的会员数量列表 List<Integer> memberCountList = new ArrayList<>(); // 循环 12 次,统计最近 12 个月的数据 for (int i = 0; i < 12; i++) { // 月份加 1,向后推算一个月 calendar.add(Calendar.MONTH, 1); // 格式化为 yyyy-MM 字符串(如:2026-04) String formatDate = dateFormat.format(calendar.getTime()); // 根据月份查询该月的会员注册数量 Integer memberCount = reportMapper.findMemberCountByMonth(formatDate); // 将月份添加到月份列表 list.add(formatDate); // 将会员数量添加到数量列表 memberCountList.add(memberCount); } // 封装返回结果 Map<String, Object> map = new HashMap<>(); map.put("memberCount", memberCountList); // 会员数量列表 map.put("monthList", list); // 月份列表 return map; } }2.3.4 Dao 接口
java
public interface ReportMapper { Integer findMemberCountByMonth(String formatDate); }2.3.5 Mapper 映射文件
xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.hg.mapper.ReportMapper"> <select id="findMemberCountByMonth" parameterType="string" resultType="int"> select count(id) from t_member where regTime LIKE concat(#{regTime}, '%') </select> </mapper>三、套餐预约占比饼形图实现
3.1 需求说明
以饼图展示各体检套餐的预约占比,便于分析热门套餐。
3.2 前端页面实现(ReportSetmeal.vue)
- 定义饼图容器
- Ajax 请求数据并 setOption 渲染
数据格式要求
json
{ "data":{ "setmealNames":["套餐1","套餐2","套餐3"], "setmealCount":[{"name":"套餐1","value":10},{"name":"套餐2","value":30}] }, "flag":true, "message":"获取套餐统计数据成功" }前端核心代码
javascript
axios.get("/report/getSetmealReport.do").then((res)=>{ myChart1.setOption({ title : { text: '套餐预约占比', x:'center' }, tooltip : { trigger: 'item', formatter: "{a} <br>{b} : {c} ({d}%)" }, legend: { orient: 'vertical', left: 'left', data: res.data.data.setmealNames }, series : [{ name: '套餐预约占比', type: 'pie', radius : '55%', center: ['50%', '60%'], data:res.data.data.setmealCount }] }); });3.3 后台代码实现
3.3.1 Controller
java
@RequestMapping("/getSetMealReport") public Result getSetMealReport(){ try { // 调用 Service 层获取套餐预约统计数据 List<Map<String,Object>> map = reportService.getSetMealReport(); return new Result(true, MessageConstant.GET_SETMEAL_COUNT_REPORT_SUCCESS, map); }catch (Exception e){ e.printStackTrace(); return new Result(false, MessageConstant.GET_SETMEAL_COUNT_REPORT_FAIL); } }3.3.2 Service 接口
java
List<Map<String, Object>> getSetMealReport();3.3.3 Service 实现类
java
/** * 查询套餐预约人数占比统计 * 统计各个体检套餐的预约人数,用于饼图展示 * * @return List<Map<String, Object>> 套餐预约统计数据列表 * 每个 Map 包含: * - name: String 套餐名称 * - value: Integer 该套餐的预约数量 */ @Override public List<Map<String, Object>> getSetMealReport() { // 直接调用 Mapper 层查询套餐预约统计数据 // SQL 语句会关联 t_setmeal 和 t_order 表,按套餐名称分组统计预约数量 return reportMapper.getSetMealReport(); }3.3.4 Dao 接口
java
List<Map<String, Object>> getSetMealReport();3.3.5 Mapper 映射文件
xml
<select id="getSetMealReport" resultType="map"> SELECT t1.name, COUNT(t2.id) value FROM t_setmeal t1,t_order t2 WHERE t1.id = t2.setmeal_id GROUP BY t1.name </select>四、运营数据统计页面实现
4.1 需求说明
以表格形式展示:会员数据、预约到诊数据、热门套餐,支持导出 Excel。
4.2 前端页面(ReportBusiness.vue)
- 定义数据模型绑定页面
- created 钩子请求数据
- 提供导出按钮
数据模型
javascript
data:{ reportData:{ reportDate:null, todayNewMember :0, totalMember :0, thisWeekNewMember :0, thisMonthNewMember :0, todayOrderNumber :0, todayVisitsNumber :0, thisWeekOrderNumber :0, thisWeekVisitsNumber :0, thisMonthOrderNumber :0, thisMonthVisitsNumber :0, hotSetmeal :[] } }请求数据
javascript
created(){ axios.get("/report/getBusinessReportData.do").then((res)=>{ if(res.data.flag){ this.reportData = res.data.data; }else{ this.$message.error(res.data.message); } }) }导出按钮
html
<el-button @click="exportExcel">导出Excel</el-button>javascript
exportExcel(){ window.location.href ='/report/exportBusinessReport'; }4.3 后台代码实现
4.3.1 Controller
java
@RequestMapping("/getBusinessReportData") public Result getBusinessReportData(){ try { Map<String,Object> map = reportService.getBusinessReportData(); return new Result(true,MessageConstant.GET_BUSINESS_REPORT_SUCCESS,map); } catch (Exception e) { e.printStackTrace(); return new Result(false,MessageConstant.GET_BUSINESS_REPORT_FAIL); } }4.3.2 Service 接口
java
public interface ReportService { Map<String, Object> getBusinessReportData() throws Exception; }4.3.3 Service 实现类
java
@Service(interfaceClass = ReportService.class) @Transactional public class ReportServiceImpl implements ReportService { @Autowired MemberDao memberDao; @Autowired OrderDao orderDao; @Override public Map<String, Object> getBusinessReportData() throws Exception{ String today = DateUtils.parseDate2String(DateUtils.getToday()); String thisWeekMonday = DateUtils.parseDate2String(DateUtils.getThisWeekMonday()); String firstDay4ThisMonth = DateUtils.parseDate2String(DateUtils.getFirstDay4ThisMonth()); Integer todayNewMember = memberDao.findMemberCountByDate(today); Integer totalMember = memberDao.findMemberTotalCount(); Integer thisWeekNewMember = memberDao.findMemberCountAfterDate(thisWeekMonday); Integer thisMonthNewMember = memberDao.findMemberCountAfterDate(firstDay4ThisMonth); Integer todayOrderNumber = orderDao.findOrderCountByDate(today); Integer thisWeekOrderNumber = orderDao.findOrderCountAfterDate(thisWeekMonday); Integer thisMonthOrderNumber = orderDao.findOrderCountAfterDate(firstDay4ThisMonth); Integer todayVisitsNumber = orderDao.findVisitsCountByDate(today); Integer thisWeekVisitsNumber = orderDao.findVisitsCountAfterDate(thisWeekMonday); Integer thisMonthVisitsNumber = orderDao.findVisitsCountAfterDate(firstDay4ThisMonth); List<Map> hotSetmeal = orderDao.findHotSetmeal(); Map<String,Object> map = new HashMap<>(); map.put("reportDate",today); map.put("todayNewMember",todayNewMember); map.put("totalMember",totalMember); map.put("thisWeekNewMember",thisWeekNewMember); map.put("thisMonthNewMember",thisMonthNewMember); map.put("todayOrderNumber",todayOrderNumber); map.put("thisWeekOrderNumber",thisWeekOrderNumber); map.put("thisMonthOrderNumber",thisMonthOrderNumber); map.put("todayVisitsNumber",todayVisitsNumber); map.put("thisWeekVisitsNumber",thisWeekVisitsNumber); map.put("thisMonthVisitsNumber",thisMonthVisitsNumber); map.put("hotSetmeal",hotSetmeal); return map; } }4.3.4 Dao 与 Mapper
OrderDao
java
Integer findOrderCountByDate(String today); Integer findOrderCountAfterDate(String thisWeekMonday); Integer findVisitsCountByDate(String today); Integer findVisitsCountAfterDate(String thisWeekMonday); List<Map> findHotSetmeal();MemberDao
java
Integer findMemberCountByDate(String today); Integer findMemberTotalCount(); Integer findMemberCountAfterDate(String thisWeekMonday);SQL 语句(关键)
xml
<!-- 热门套餐,取前4 --> <select id="findHotSetmeal" resultType="map"> SELECT s.name, COUNT(o.id) setmeal_count, COUNT(o.id)/(SELECT COUNT(id) FROM t_order) proportion FROM t_order o,t_setmeal s WHERE s.id=o.setmeal_id GROUP BY o.setmeal_id ORDER BY setmeal_count DESC LIMIT 0,4; </select>五、POI 实现 Excel 报表导出
5.1 实现思路
使用 Excel 模板文件(提前设置样式、合并单元格),后台读取模板并填充数据,通过输出流实现浏览器下载。
5.2 准备工作
- 准备模板文件
report_template.xlsx - 放入项目
template目录
5.3 后台导出接口
java
@RequestMapping("/exportBusinessReport") public Result exportBusinessReport(HttpServletRequest request, HttpServletResponse response){ try{ Map<String,Object> result = reportService.getBusinessReportData(); String reportDate = (String) result.get("reportDate"); Integer todayNewMember = (Integer) result.get("todayNewMember"); Integer totalMember = (Integer) result.get("totalMember"); Integer thisWeekNewMember = (Integer) result.get("thisWeekNewMember"); Integer thisMonthNewMember = (Integer) result.get("thisMonthNewMember"); Integer todayOrderNumber = (Integer) result.get("todayOrderNumber"); Integer thisWeekOrderNumber = (Integer) result.get("thisWeekOrderNumber"); Integer thisMonthOrderNumber = (Integer) result.get("thisMonthOrderNumber"); Integer todayVisitsNumber = (Integer) result.get("todayVisitsNumber"); Integer thisWeekVisitsNumber = (Integer) result.get("thisWeekVisitsNumber"); Integer thisMonthVisitsNumber = (Integer) result.get("thisMonthVisitsNumber"); List<Map> hotSetmeal = (List<Map>) result.get("hotSetmeal"); String filePath = request.getSession().getServletContext().getRealPath("template") + File.separator+"report_template.xlsx"; XSSFWorkbook excel = new XSSFWorkbook(new FileInputStream(new File(filePath))); XSSFSheet sheet = excel.getSheetAt(0); XSSFRow row = sheet.getRow(2); row.getCell(5).setCellValue(reportDate); row = sheet.getRow(4); row.getCell(5).setCellValue(todayNewMember); row.getCell(7).setCellValue(totalMember); row = sheet.getRow(5); row.getCell(5).setCellValue(thisWeekNewMember); row.getCell(7).setCellValue(thisMonthNewMember); row = sheet.getRow(7); row.getCell(5).setCellValue(todayOrderNumber); row.getCell(7).setCellValue(todayVisitsNumber); row = sheet.getRow(8); row.getCell(5).setCellValue(thisWeekOrderNumber); row.getCell(7).setCellValue(thisWeekVisitsNumber); row = sheet.getRow(9); row.getCell(5).setCellValue(thisMonthOrderNumber); row.getCell(7).setCellValue(thisMonthVisitsNumber); int rowNum = 12; for(Map map : hotSetmeal){ String name = (String) map.get("name"); Long setmeal_count = (Long) map.get("setmeal_count"); BigDecimal proportion = (BigDecimal) map.get("proportion"); row = sheet.getRow(rowNum ++); row.getCell(4).setCellValue(name); row.getCell(5).setCellValue(setmeal_count); row.getCell(6).setCellValue(proportion.doubleValue()); } OutputStream out = response.getOutputStream(); response.setContentType("application/vnd.ms-excel"); response.setHeader("content-Disposition", "attachment;filename=report.xlsx"); excel.write(out); out.flush(); out.close(); excel.close(); return null; }catch (Exception e){ return new Result(false,MessageConstant.GET_BUSINESS_REPORT_FAIL); } }六、总结
本文完整实现了:
- ECharts 折线图:展示会员月度增长趋势
- ECharts 饼图:展示套餐预约占比
- 运营数据表格:统计会员、预约、到诊、热门套餐
- POI Excel 导出:基于模板快速生成美观报表
整套方案适用于医疗、电商、管理后台等需要数据可视化与报表导出的 SpringBoot 项目,代码结构清晰、可直接移植使用。
