|
|
@@ -670,4 +670,165 @@ public class VoyageStockBoardServiceImpl implements VoyageStockBoardService {
|
|
|
|
|
|
return list;
|
|
|
}
|
|
|
+
|
|
|
+ // ==================== 游客年龄段分析看板 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public VisitorAgeDashboardRespVO getVisitorAgeDashboard(VisitorSourceDashboardReqVO reqVO) {
|
|
|
+ // 1. 查询游客年龄列表
|
|
|
+ List<Integer> ageList = voyageStockBoardMapper.selectVisitorAgeListForAgeDashboard(
|
|
|
+ reqVO.getShipId(), reqVO.getVoyageIds());
|
|
|
+ if (CollUtil.isEmpty(ageList)) {
|
|
|
+ return buildEmptyAgeResp();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 年龄段分组统计
|
|
|
+ return analyzeVisitorAge(ageList);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void exportVisitorAgeDashboardExcel(VisitorSourceDashboardReqVO reqVO, HttpServletResponse response) throws IOException {
|
|
|
+ VisitorAgeDashboardRespVO data = getVisitorAgeDashboard(reqVO);
|
|
|
+
|
|
|
+ List<VisitorAgeExportVO> exportList = buildAgeExportData(data);
|
|
|
+
|
|
|
+ if (CollUtil.isEmpty(exportList)) {
|
|
|
+ ExcelUtils.exportEmpty(response, "游客年龄段分析看板");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ response.addHeader("Content-Disposition",
|
|
|
+ "attachment;filename=" + URLEncoder.encode("游客年龄段分析看板.xlsx", StandardCharsets.UTF_8.name()));
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
|
|
|
+
|
|
|
+ EasyExcel.write(response.getOutputStream(), VisitorAgeExportVO.class)
|
|
|
+ .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
|
|
+ .sheet("游客年龄段数据")
|
|
|
+ .doWrite(exportList);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分析年龄段数据
|
|
|
+ */
|
|
|
+ private VisitorAgeDashboardRespVO analyzeVisitorAge(List<Integer> ageList) {
|
|
|
+ VisitorAgeDashboardRespVO respVO = new VisitorAgeDashboardRespVO();
|
|
|
+
|
|
|
+ int totalCount = ageList.size();
|
|
|
+ int unknownAgeCount = 0;
|
|
|
+ int knownAgeCount = 0;
|
|
|
+
|
|
|
+ // 年龄段统计
|
|
|
+ Map<String, Integer> ageGroupCountMap = new LinkedHashMap<>();
|
|
|
+ // 预定义年龄段顺序
|
|
|
+ String[] ageGroups = {"17岁以下", "18-25岁", "26-35岁", "36-45岁", "46-55岁", "56-65岁", "65岁以上"};
|
|
|
+ for (String group : ageGroups) {
|
|
|
+ ageGroupCountMap.put(group, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (Integer age : ageList) {
|
|
|
+ if (age == null || age < 0) {
|
|
|
+ unknownAgeCount++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ knownAgeCount++;
|
|
|
+
|
|
|
+ String group = getAgeGroup(age);
|
|
|
+ ageGroupCountMap.merge(group, 1, Integer::sum);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建年龄段列表(不含未知)
|
|
|
+ int finalKnownAgeCount = knownAgeCount;
|
|
|
+ List<VisitorAgeDashboardRespVO.VisitorAgeItemVO> ageGroupList = new ArrayList<>();
|
|
|
+ for (String group : ageGroups) {
|
|
|
+ Integer count = ageGroupCountMap.getOrDefault(group, 0);
|
|
|
+ if (count > 0 || finalKnownAgeCount > 0) {
|
|
|
+ VisitorAgeDashboardRespVO.VisitorAgeItemVO item = new VisitorAgeDashboardRespVO.VisitorAgeItemVO();
|
|
|
+ item.setAgeGroup(group);
|
|
|
+ item.setCount(count);
|
|
|
+ item.setRatio(calcRatio(count, finalKnownAgeCount));
|
|
|
+ ageGroupList.add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建年龄段列表(含未知)
|
|
|
+ List<VisitorAgeDashboardRespVO.VisitorAgeItemVO> ageGroupListWithUnknown = new ArrayList<>();
|
|
|
+ for (VisitorAgeDashboardRespVO.VisitorAgeItemVO item : ageGroupList) {
|
|
|
+ VisitorAgeDashboardRespVO.VisitorAgeItemVO newItem = new VisitorAgeDashboardRespVO.VisitorAgeItemVO();
|
|
|
+ newItem.setAgeGroup(item.getAgeGroup());
|
|
|
+ newItem.setCount(item.getCount());
|
|
|
+ newItem.setRatio(calcRatio(item.getCount(), totalCount));
|
|
|
+ ageGroupListWithUnknown.add(newItem);
|
|
|
+ }
|
|
|
+ if (unknownAgeCount > 0) {
|
|
|
+ VisitorAgeDashboardRespVO.VisitorAgeItemVO unknownItem = new VisitorAgeDashboardRespVO.VisitorAgeItemVO();
|
|
|
+ unknownItem.setAgeGroup("未知");
|
|
|
+ unknownItem.setCount(unknownAgeCount);
|
|
|
+ unknownItem.setRatio(calcRatio(unknownAgeCount, totalCount));
|
|
|
+ ageGroupListWithUnknown.add(unknownItem);
|
|
|
+ }
|
|
|
+
|
|
|
+ respVO.setTotalCount(totalCount);
|
|
|
+ respVO.setKnownAgeCount(knownAgeCount);
|
|
|
+ respVO.setUnknownAgeCount(unknownAgeCount);
|
|
|
+ respVO.setAgeGroupList(ageGroupList);
|
|
|
+ respVO.setAgeGroupListWithUnknown(ageGroupListWithUnknown);
|
|
|
+
|
|
|
+ return respVO;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取年龄段分组
|
|
|
+ */
|
|
|
+ private String getAgeGroup(int age) {
|
|
|
+ if (age <= 17) {
|
|
|
+ return "17岁以下";
|
|
|
+ } else if (age <= 25) {
|
|
|
+ return "18-25岁";
|
|
|
+ } else if (age <= 35) {
|
|
|
+ return "26-35岁";
|
|
|
+ } else if (age <= 45) {
|
|
|
+ return "36-45岁";
|
|
|
+ } else if (age <= 55) {
|
|
|
+ return "46-55岁";
|
|
|
+ } else if (age <= 65) {
|
|
|
+ return "56-65岁";
|
|
|
+ } else {
|
|
|
+ return "65岁以上";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建空响应
|
|
|
+ */
|
|
|
+ private VisitorAgeDashboardRespVO buildEmptyAgeResp() {
|
|
|
+ VisitorAgeDashboardRespVO respVO = new VisitorAgeDashboardRespVO();
|
|
|
+ respVO.setTotalCount(0);
|
|
|
+ respVO.setKnownAgeCount(0);
|
|
|
+ respVO.setUnknownAgeCount(0);
|
|
|
+ respVO.setAgeGroupList(Collections.emptyList());
|
|
|
+ respVO.setAgeGroupListWithUnknown(Collections.emptyList());
|
|
|
+ return respVO;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建年龄段导出数据
|
|
|
+ */
|
|
|
+ private List<VisitorAgeExportVO> buildAgeExportData(VisitorAgeDashboardRespVO data) {
|
|
|
+ List<VisitorAgeExportVO> list = new ArrayList<>();
|
|
|
+ List<VisitorAgeDashboardRespVO.VisitorAgeItemVO> ageGroupList = data.getAgeGroupListWithUnknown();
|
|
|
+
|
|
|
+ if (CollUtil.isEmpty(ageGroupList)) {
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (VisitorAgeDashboardRespVO.VisitorAgeItemVO item : ageGroupList) {
|
|
|
+ VisitorAgeExportVO vo = new VisitorAgeExportVO();
|
|
|
+ vo.setAgeGroup(item.getAgeGroup());
|
|
|
+ vo.setCount(item.getCount());
|
|
|
+ vo.setRatio(item.getRatio());
|
|
|
+ list.add(vo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return list;
|
|
|
+ }
|
|
|
}
|