今日首先介绍前端技术Apache ECharts,说明后端需要准备的数据,然后讲解具体统计功能的实现,包括营业额统计、用户统计、订单统计、销量排名。
ECharts是一款基于 Javascript 的数据可视化图表库。我们用它来展示图表数据。
1). 引入echarts.js 文件
2). 为 ECharts 准备一个设置宽高的 DOM
3). 初始化echarts实例
4). 指定图表的配置项和数据
5). 使用指定的配置项和数据显示图表
js文件在黑马对应项目自取。
测试用的html代码:
ECharts
结果页面如下:
然后我们打开ECharts官网Apache ECharts 选择一个图案来试着改一下。
首先进入官网,点击所有示例。
然后点击一个自己喜欢的样式:
复制左边的代码到原代码的option位置:
复制后代码如下:
ECharts
页面展示结果如下:
传输的两列数据,分别是下标和数据。在如下位置:
控制层只要接收数据传给业务层,返回VO(已经有设计好的TurnoverReportVO了)就可以。重点在业务层和持久层。
具体要处理得到两类,或者说两列数据,包括:
所以步骤便是:
那么持久层需要的操作就在第2步,即:
之后几个案例都是大差不差的层次结构,除了数据的种类要求不同。
@RestController
@Slf4j
@Api(tags = "统计相关")
@RequestMapping("/admin/report")
public class ReportController {
@Autowired
private ReportService reportService;
@GetMapping("/turnoverStatistics")
public Result turnoverStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
TurnoverReportVO turnoverReportVO = reportService.turnoverStatistics(begin, end);
return Result.success(turnoverReportVO);
}
}
@Service
public class ReportServiceImpl implements ReportService {
@Autowired
private OrdersMapper ordersMapper;
@Override
public TurnoverReportVO turnoverStatistics(LocalDate begin, LocalDate end) {
// 1. 获取日期列表
List list = getDateList(begin, end);
// 2. 查询每日营业额
List result = new ArrayList<>();
Double turnover;
LocalDateTime dayBegin;
LocalDateTime dayEnd;
if (list != null && list.size() > 0) {
dayBegin = LocalDateTime.of(list.get(0), LocalTime.MIN); // 知识点2和3
dayEnd = LocalDateTime.of(list.get(0), LocalTime.MAX); // 知识点2和3
} else {
return new TurnoverReportVO();
}
for (LocalDate localDate : list) {
Map map = new HashMap<>();
map.put("status", Orders.COMPLETED);
map.put("begin", dayBegin);
map.put("end", dayEnd);
turnover = ordersMapper.sumByMap(map); // 知识点4
result.add(turnover == null ? 0 : turnover);
dayBegin = dayBegin.plusDays(1);
dayEnd = dayEnd.plusDays(1);
}
// 3. 返回
TurnoverReportVO turnoverReportVO = new TurnoverReportVO();
turnoverReportVO.setDateList(StringUtils.join(list, ","));
turnoverReportVO.setTurnoverList(StringUtils.join(result, ","));
return turnoverReportVO;
}
private List getDateList(LocalDate begin, LocalDate end) {
List list = new ArrayList<>();
while (begin.compareTo(end) <= 0) { // 知识点1
list.add(begin);
begin = begin.plusDays(1);
}
return list;
}
}
这里体现了4个知识点:
直接上xml文件了:
elect id="sumByMap" resultType="java.lang.Double">
select sum(amount)
from orders
status = #{status}
and order_time between #{begin} and #{end}
由于三层的架构都大差不差,所以直接介绍所需数据的不同。
@GetMapping("/userStatistics")
public Result userStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
UserReportVO userReportVO = reportService.userStatistics(begin, end);
return Result.success(userReportVO);
}
@Override
public UserReportVO userStatistics(LocalDate begin, LocalDate end) {
// 1. 获取日期列表
List dateList = getDateList(begin, end);
// 2. 获取用户数量列表
List newUserList = new ArrayList<>();
List totalUserList = new ArrayList<>();
LocalDateTime dayBegin;
LocalDateTime dayEnd;
if (dateList != null && dateList.size() > 0) {
dayBegin = LocalDateTime.of(dateList.get(0), LocalTime.MIN);
dayEnd = LocalDateTime.of(dateList.get(0), LocalTime.MAX);
} else {
return new UserReportVO();
}
Integer totalUser;
Integer newUser;
for (LocalDate localDate : dateList) {
Map map = new HashMap<>();
map.put("end", dayEnd);
totalUser = userMapper.countByMap(map);
totalUserList.add(totalUser == null ? 0 : totalUser);
map.put("begin", dayBegin);
newUser = userMapper.countByMap(map);
newUserList.add(newUser == null ? 0 : newUser);
dayBegin = dayBegin.plusDays(1);
dayEnd = dayEnd.plusDays(1);
}
// 3. 返回
UserReportVO userReportVO = new UserReportVO();
userReportVO.setDateList(StringUtils.join(dateList, ","));
userReportVO.setNewUserList(StringUtils.join(newUserList, ","));
userReportVO.setTotalUserList(StringUtils.join(totalUserList, ","));
return userReportVO;
}
第一点,获取日期列表可以抽取出来,供营业额统计、用户统计共同调用。
小tips,抽取函数的快捷键是 ctrl + alt + m 哦。
第二点,持久层的两次查找,可以巧妙的用一个函数来完成的。用动态sql的if判断,分为有begin时间的判断和没有begin时间的判断进行处理。具体看下面持久层代码。
第三点,两个统计都要判断持久层查到的结果是不是null,是的话要归为0哦。
@GetMapping("/ordersStatistics")
public Result ordersStatistics(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
OrderReportVO orderReportVO = reportService.ordersStatistics(begin, end);
return Result.success(orderReportVO);
}
@Override
public OrderReportVO ordersStatistics(LocalDate begin, LocalDate end) {
OrderReportVO orderReportVO = new OrderReportVO();
// 1. 日期列表
List dateList = getDateList(begin, end);
if (dateList == null) {
return orderReportVO;
}
// 2. 订单数列表
List totalOrderList = new ArrayList<>();
// 3. 有效订单数列表
List validOrderList = new ArrayList<>();
// 4. 订单总数
Integer totalOrderCount = 0;
// 5. 有效订单总数
Integer validOrderCount = 0;
for (LocalDate localDate : dateList) {
Map map = new HashMap();
map.put("begin", LocalDateTime.of(localDate, LocalTime.MIN));
map.put("end", LocalDateTime.of(localDate, LocalTime.MAX));
Integer total = ordersMapper.countByMap(map);
total = total == null ? 0 : total;
map.put("status", Orders.COMPLETED);
Integer valid = ordersMapper.countByMap(map);
valid = valid == null ? 0 : valid;
totalOrderList.add(total);
validOrderList.add(valid);
totalOrderCount += total;
validOrderCount += valid;
}
// 6. 订单完成率
Double completionR = 0.0;
if (totalOrderCount != null) {
completionR = validOrderCount * 1.0 / totalOrderCount;
}
orderReportVO.setDateList(StringUtils.join(dateList, ","));
orderReportVO.setOrderCountList(StringUtils.join(totalOrderList, ","));
orderReportVO.setValidOrderCountList(StringUtils.join(validOrderList, ","));
orderReportVO.setTotalOrderCount(totalOrderCount);
orderReportVO.setValidOrderCount(validOrderCount);
orderReportVO.setOrderCompletionRate(completionR);
return orderReportVO;
}
还是用动态sql来巧妙的满足一个函数查询两种不同的数据,即status的if判断是否查询。
没啥好说的,算一个巩固练习。
@GetMapping("/top10")
public Result top10(@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
SalesTop10ReportVO salesTop10ReportVO = reportService.top10(begin, end);
return Result.success(salesTop10ReportVO);
}
@Override
public SalesTop10ReportVO top10(LocalDate begin, LocalDate end) {
List goodsSalesDTOS = ordersMapper.countSaleTop10(LocalDateTime.of(begin, LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));
if (goodsSalesDTOS == null) {
return new SalesTop10ReportVO();
}
List nameList = new ArrayList<>();
List numberList = new ArrayList<>();
for (GoodsSalesDTO goodsSalesDTO : goodsSalesDTOS) {
nameList.add(goodsSalesDTO.getName());
numberList.add(goodsSalesDTO.getNumber());
} // 思考:这里可不可以简写?
SalesTop10ReportVO salesTop10ReportVO = new SalesTop10ReportVO();
salesTop10ReportVO.setNameList(StringUtils.join(nameList, ","));
salesTop10ReportVO.setNumberList(StringUtils.join(numberList, ","));
return salesTop10ReportVO;
}
第一个,下面持久层的多表查询。
第二个,查询到DTO后,对象数据到两列数据的转换。
尝试用流的写法完成。
答案如下:
@Override
public SalesTop10ReportVO top10(LocalDate begin, LocalDate end) {
List goodsSalesDTOS = ordersMapper.countSaleTop10(LocalDateTime.of(begin, LocalTime.MIN), LocalDateTime.of(end, LocalTime.MAX));
if (goodsSalesDTOS == null) {
return new SalesTop10ReportVO();
}
// List nameList = new ArrayList<>();
// List numberList = new ArrayList<>();
// for (GoodsSalesDTO goodsSalesDTO : goodsSalesDTOS) {
// nameList.add(goodsSalesDTO.getName());
// numberList.add(goodsSalesDTO.getNumber());
// }
// ==========注意这里的写法==========
List nameList = goodsSalesDTOS.stream().map(GoodsSalesDTO::getName).collect(Collectors.toList());
List numberList = goodsSalesDTOS.stream().map(GoodsSalesDTO::getNumber).collect(Collectors.toList());
// ==========注意上面的写法==========
SalesTop10ReportVO salesTop10ReportVO = new SalesTop10ReportVO();
salesTop10ReportVO.setNameList(StringUtils.join(nameList, ","));
salesTop10ReportVO.setNumberList(StringUtils.join(numberList, ","));
return salesTop10ReportVO;
}
1.ECharts最少需要准备几列数据?
2.LocalDateTime的比较,以及比较接口讲解的复习
3.日期时间的拼接、时间在一天的最大、最小值
4.Map封装数据进行查找的代码手法
5.统计中,持久层查询为null的归0化处理;
6.查找增量与总量时的简写mapper查询。