【苍穹外卖 day11|统计报表接口与 Apache ECharts 图表展示】

前言

后台运营页面需要把订单、用户和商品数据转换成可以直接阅读的趋势与排行。苍穹外卖的统计模块将日期范围作为统一输入,在服务层按天汇总数据,再把日期序列和数值序列返回给前端;前端拿到这些数据后,可使用 Apache ECharts 绘制折线图、柱状图等图表。

本文只梳理四类统计:营业额、用户、订单和销量 Top10,并介绍 ECharts 的基本使用方式。

一、统计模块的接口入口

统计接口集中在 ReportController,统一的请求路径前缀是 /admin/report。四个接口都接收 begin 与 end 两个日期参数,并通过 @DateTimeFormat(pattern = "yyyy-MM-dd") 转换为 LocalDate。

java 复制代码
@GetMapping("/turnoverStatistics")
public Result<TurnoverReportVO> turnoverStatistics(
        @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
        @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
    return Result.success(reportService.getTurnoverStatistica(begin, end));
}

其余三个接口与它的结构一致:

统计内容 接口 返回对象
营业额 /admin/report/turnoverStatistics TurnoverReportVO
用户 /admin/report/userStatistics UserReportVO
订单 /admin/report/ordersStatistics OrderReportVO
销量 Top10 /admin/report/top10 SalesTop10ReportVO

日期参数在 Controller 层完成转换,业务统计放到 ReportServiceImpl。这样接口只负责接收参数和返回结果,按日循环、查询数据库、封装图表数据等工作都在服务层完成。

二、为什么返回逗号分隔的数据

趋势图需要两组一一对应的数据:横坐标日期和纵坐标数值。项目中的 VO 使用 String 保存逗号分隔的列表,例如营业额统计的返回对象:

java 复制代码
public class TurnoverReportVO implements Serializable {
    private String dateList;
    private String turnoverList;
}

当日期列表为 2026-07-01,2026-07-02,2026-07-03,营业额列表为 406.0,1520.0,75.0 时,前端按逗号拆分后,就能让每个日期与同下标的营业额对应。这也是后续配置 ECharts xAxis 和 series.data 的数据基础。

三、营业额统计:只统计已完成订单

营业额统计先生成 begin 到 end 之间的完整日期列表,然后逐日查询。每一天都会被转换为当天的起止时间:00:00:00 到 23:59:59.999999999。

java 复制代码
for (LocalDate date : dateList) {
    LocalDateTime beginTime = LocalDateTime.of(date, LocalTime.MIN);
    LocalDateTime endTime = LocalDateTime.of(date, LocalTime.MAX);

    Map map = new HashMap<>();
    map.put("begin", beginTime);
    map.put("end", endTime);
    map.put("status", Orders.COMPLETED);
    Double turnover = orderMapper.sumByMap(map);

    if (turnover == null) {
        turnover = 0.0;
    }
    turnoverList.add(turnover);
}

Orders.COMPLETED 在实体中对应状态值 5。统计营业额时带上这个状态条件,避免待支付、已取消等订单金额被计入运营数据。

Mapper 中的聚合查询如下:

xml 复制代码
<select id="sumByMap" resultType="java.lang.Double">
    select sum(amount) from orders
    <where>
        <if test="begin != null">
            and order_time &gt;= #{begin}
        </if>
        <if test="end != null">
            and order_time &lt;= #{end}
        </if>
        <if test="status != null">
            and status = #{status}
        </if>
    </where>
</select>

sum(amount) 在某天没有匹配订单时会得到 null。服务层把它转为 0.0,保证日期列表与营业额列表长度始终一致,图表不会因为中间某天无订单而缺少一个数据点。

四、用户统计:新增用户与累计用户同时计算

用户统计同样按天循环,但一次循环会查询两组数据:

java 复制代码
map.put("endTime", endTime);
Integer totalUser = userMapper.countByMap(map);

map.put("beginTime", beginTime);
Integer newUser = userMapper.countByMap(map);

totalUserList.add(totalUser);
newUserList.add(newUser);

第一次查询只传 endTime,SQL 会统计截至当天结束时的用户总数;第二次再补上 beginTime,SQL 就限定在当天创建时间范围内,得到当日新增用户数。

xml 复制代码
<select id="countByMap" resultType="java.lang.Integer">
    select count(id) from user
    <where>
        <if test="beginTime!=null">
            create_time &gt;= #{beginTime}
        </if>
        <if test="endTime!=null">
            and create_time &lt; #{endTime}
        </if>
    </where>
</select>

UserReportVO 返回 dateList、totalUserList、newUserList 三项数据。页面可以用新增用户绘制每日增长趋势,用用户总量绘制累计增长趋势。

五、订单统计:订单数、有效订单数和完成率

订单统计把每天的订单数和有效订单数分别保存起来:

java 复制代码
Integer orderCount = getOrderCount(beginTime, endTime, null);
Integer validOrderCount = getOrderCount(
        beginTime, endTime, Orders.COMPLETED);

orderCountList.add(orderCount);
validOrderCountList.add(validOrderCount);

传入 null 时,Mapper 不会拼接状态条件,得到当天全部订单数;传入 Orders.COMPLETED 时,得到当天有效订单数。随后将两个列表求和,并计算整个统计区间的订单完成率:

java 复制代码
Double orderCompletionRate = 0.0;
if (totalOrderCount != 0) {
    orderCompletionRate = validOrderCount.doubleValue()
            / totalOrderCount.doubleValue();
}

这里先判断总订单数是否为 0。没有订单的时间范围直接返回 0.0,避免除以 0。OrderReportVO 除了两条按日曲线,还会返回 totalOrderCount、validOrderCount 和 orderCompletionRate,适合页面顶部的运营指标卡片。

六、销量 Top10:从订单明细中汇总商品销量

营业额和订单数来自 orders 表,商品销量要从订单明细表中计算。getSalesTop10 查询把 order_detail 与 orders 关联,只保留已完成订单,按商品名称分组后汇总数量:

xml 复制代码
<select id="getSalesTop10" resultType="com.sky.dto.GoodsSalesDTO">
    select od.name, sum(od.number) number
    from order_detail od, orders o
    where od.order_id = o.id and o.status = 5
    <if test="beginTime != null">
        and o.order_time &gt;= #{beginTime}
    </if>
    <if test="endTime != null">
        and o.order_time &lt;= #{endTime}
    </if>
    group by od.name
    order by number desc
    limit 0, 10
</select>

查询结果是 GoodsSalesDTO 列表。服务层分别提取 name 和 number,再通过逗号拼接成 SalesTop10ReportVO:

java 复制代码
List<String> names = salesTop10.stream()
        .map(GoodsSalesDTO::getName)
        .collect(Collectors.toList());

List<Integer> numbers = salesTop10.stream()
        .map(GoodsSalesDTO::getNumber)
        .collect(Collectors.toList());

这组数据适合用横向柱状图展示:商品名称放在类目轴,销量放在数值轴。名称较长时,横向柱状图也比竖向柱状图更容易阅读。

七、Apache ECharts:把统计数据画成图表

Apache ECharts 是基于 JavaScript 的数据可视化图表库,可用于柱状图、折线图、饼图等交互式图表。对于统计页面,后端只负责提供日期和数值;图表标题、坐标轴、提示框、折线或柱体则由前端的 option 配置决定。

一个最小的 ECharts 图表需要四步。

1. 准备带宽高的 DOM 容器

html 复制代码
<div id="turnoverChart" style="width: 600px; height: 400px;"></div>

容器必须有明确宽高,否则图表没有可绘制区域。

2. 初始化图表实例

javascript 复制代码
var chart = echarts.init(document.getElementById('turnoverChart'));

这一步把 DOM 元素交给 ECharts,得到后续配置和更新使用的图表实例。

3. 组装统计接口返回的数据

javascript 复制代码
var dates = result.data.dateList.split(',');
var turnover = result.data.turnoverList.split(',').map(Number);

dateList 拆分后用作横坐标;turnoverList 拆分并转为 Number 后,用作图表数值。两个数组下标必须对应,这也是服务端按天补 0 的原因。

4. 配置 option 并渲染

javascript 复制代码
chart.setOption({
  title: { text: '营业额趋势' },
  tooltip: { trigger: 'axis' },
  xAxis: { type: 'category', data: dates },
  yAxis: { type: 'value' },
  series: [{
    name: '营业额',
    type: 'line',
    data: turnover
  }]
});

xAxis.data 对应日期数组,series.data 对应营业额数组。将 type 改成 bar 就能以柱状图呈现同一份统计数据。ECharts 的 setOption 也可在拿到新的时间范围数据后再次调用,用于刷新图表。

八、接口自测与排查入口

可以先调用营业额接口验证返回结构:

text 复制代码
GET /admin/report/turnoverStatistics?begin=2026-07-01&end=2026-07-07

检查时重点关注三点:

  1. dateList 中的日期数量应与查询区间天数一致。
  2. turnoverList、newUserList、orderCountList 等序列长度应与 dateList 一致。
  3. 销量 Top10 只应包含已完成订单中的商品;如果排行异常,先检查订单状态与订单明细关联条件。

图表没有显示时,先检查 DOM 是否已设置宽高,再检查接口返回的字符串是否成功拆分为数组,最后确认 xAxis.data 与 series.data 长度是否相同。

总结

Day11 的统计代码以日期范围为共同入口:服务层逐日汇总订单和用户数据,Mapper 使用聚合、分组与排序完成数据库统计,VO 再把结果整理为前端图表需要的数据序列。ECharts 负责把这些序列渲染为趋势图和排行图,后台运营数据也就从表记录变成了可直接观察的图表。

相关推荐
yy403319 小时前
【HarmonyOS学习笔记】2026-07-19 | 布局性能实验:百分比vs固定值vs预计算
前端·harmonyos
渣波19 小时前
🚀 全栈AI革命:用 Node.js + LangChain + dotenv 打造你的智能应用基座
前端
程序员Jason19 小时前
Node.js 极简安装指南(Mac / Windows / Linux 通用,含国内镜像)
前端
半个落月19 小时前
Vue 3 如何接住大模型的流式回答:从 ReadableStream 到可靠的 SSE 解析
前端·javascript·人工智能
蓝银草同学19 小时前
Stream 数据统计实战:求和、平均值、分组汇总(AI 辅助学习 Java 8)
java·前端·后端
Dontla19 小时前
Hero Section(首屏大图区 / 英雄区)介绍(Web网页落地页Landing Page最顶部的区域)
前端
问商十三载19 小时前
2026大模型GEO优化体系:3层链路提收录,零成本提34%引用率附工具包
大数据·前端·人工智能
IT_陈寒19 小时前
Redis过期key的坑,差点让线上服务崩了
前端·人工智能·后端