本文基于 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 容器(必须设置宽高)
-
初始化并渲染图表
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
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
java
public interface ReportService {
Map<String, Object> getMemberReport();
}
2.3.3 Service 实现类
java
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
java
public interface ReportMapper {
Integer findMemberCountByMonth(String formatDate);
}
2.3.5 Mapper 映射文件
xml
html
<?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
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
java
List<Map<String, Object>> getSetMealReport();
3.3.3 Service 实现类
java
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
java
List<Map<String, Object>> getSetMealReport();
3.3.5 Mapper 映射文件
xml
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 项目,代码结构清晰、可直接移植使用。