SpringBoot 项目实战:ECharts 数据可视化 + POI Excel 报表导出完整版教程

本文基于 SpringBoot 后端项目,完整实现ECharts 图形报表展示 (会员折线图、套餐饼图)、运营数据统计POI 报表导出功能,全程代码可直接复用,适合后端数据可视化场景学习。

一、ECharts 基础入门

1.1 ECharts 简介

ECharts(Enterprise Charts)是百度开源的商业级数据可视化库,基于 JavaScript 实现,兼容 PC / 移动端与主流浏览器,底层依赖 ZRender 矢量图形库,支持折线图、饼图、柱状图、地图等丰富图表类型。

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)

  1. 准备图表容器
  2. 发送 Ajax 请求获取后台数据
  3. 基于 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)

  1. 定义饼图容器
  2. 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)

  1. 定义数据模型绑定页面
  2. created 钩子请求数据
  3. 提供导出按钮

数据模型

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 准备工作

  1. 准备模板文件report_template.xlsx
  2. 放入项目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);
    }
}

六、总结

本文完整实现了:

  1. ECharts 折线图:展示会员月度增长趋势
  2. ECharts 饼图:展示套餐预约占比
  3. 运营数据表格:统计会员、预约、到诊、热门套餐
  4. POI Excel 导出:基于模板快速生成美观报表

整套方案适用于医疗、电商、管理后台等需要数据可视化与报表导出的 SpringBoot 项目,代码结构清晰、可直接移植使用。

相关推荐
程序员老邢2 小时前
【技术底稿 13】内网 Milvus 2.3.0 向量数据库全流程部署(商助慧 AI 底座,Attu 可视化)
java·数据库·人工智能·ai·语言模型·milvus
YXWik62 小时前
Langchain4j(5)RAG之多格式文档加载(PDF / Word / TXT / 批量文件夹)
java
Seven972 小时前
【从0到1构建一个ClaudeAgent】内存管理-上下文压缩
java
迷藏4942 小时前
**基于Python与OpenCV的光场显示图像处理技术实践**在现代显示技术发展中,**光场显示(Light
java·图像处理·python·opencv
呆子也有梦2 小时前
游戏服务端大地图架构通俗指南:从“分区管理”到“动态调度”
服务器·后端·游戏·架构·系统架构
霸道流氓气质2 小时前
SpringBoot中使用OpenAI集成阿里云百炼实现AI快速对话入门示例
人工智能·spring boot·后端
Godson_beginner2 小时前
Aspose.PDF for Java(实现PDF转Word无水印无页数限制)
java·spring·pdf·文档转换
Lsk_Smion2 小时前
Sability安卓(三)_基础开发知识扫盲,开学XML......
android·java·android studio·安卓
indexsunny2 小时前
互联网大厂Java求职面试实战:从Spring Boot到Kafka的技术问答解析
java·spring boot·spring cloud·kafka·flyway·hikaricp·microservices