Przeglądaj źródła

新增年龄段看板接口

jincheng 2 tygodni temu
rodzic
commit
2a73264c19

+ 28 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/report/KanbanBoardController.java

@@ -119,4 +119,32 @@ public class KanbanBoardController {
         voyageStockBoardService.exportVisitorSourceDashboardExcel(reqVO, response);
     }
 
+    /**
+     * 查询游客年龄段分析看板数据
+     *
+     * @param reqVO 查询条件
+     * @return 游客年龄段分析看板数据
+     */
+    @GetMapping("/visitorAgeDashboard")
+    @Operation(summary = "查询游客年龄段分析看板数据")
+    public CommonResult<VisitorAgeDashboardRespVO> getVisitorAgeDashboard(@Valid VisitorSourceDashboardReqVO reqVO) {
+        VisitorAgeDashboardRespVO respVO = voyageStockBoardService.getVisitorAgeDashboard(reqVO);
+        return success(respVO);
+    }
+
+    /**
+     * 导出游客年龄段分析看板 Excel
+     *
+     * @param reqVO    查询条件
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    @GetMapping("/visitorAgeDashboard/export-excel")
+    @Operation(summary = "导出游客年龄段分析看板 Excel")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportVisitorAgeDashboardExcel(@Valid VisitorSourceDashboardReqVO reqVO,
+                                                HttpServletResponse response) throws IOException {
+        voyageStockBoardService.exportVisitorAgeDashboardExcel(reqVO, response);
+    }
+
 }

+ 46 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/report/vo/VisitorAgeDashboardRespVO.java

@@ -0,0 +1,46 @@
+package com.yc.ship.module.trade.controller.admin.report.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 游客年龄段分析看板响应 VO
+ */
+@Schema(description = "管理后台 - 游客年龄段分析看板响应")
+@Data
+public class VisitorAgeDashboardRespVO {
+
+    @Schema(description = "游客总人数")
+    private Integer totalCount;
+
+    @Schema(description = "已知年龄人数")
+    private Integer knownAgeCount;
+
+    @Schema(description = "未知年龄人数")
+    private Integer unknownAgeCount;
+
+    @Schema(description = "年龄段分布列表(不含未知)")
+    private List<VisitorAgeItemVO> ageGroupList;
+
+    @Schema(description = "年龄段分布列表(含未知)")
+    private List<VisitorAgeItemVO> ageGroupListWithUnknown;
+
+    /**
+     * 年龄段单项 VO
+     */
+    @Schema(description = "年龄段单项")
+    @Data
+    public static class VisitorAgeItemVO {
+
+        @Schema(description = "年龄段名称")
+        private String ageGroup;
+
+        @Schema(description = "人数")
+        private Integer count;
+
+        @Schema(description = "占比,如 25.00%")
+        private String ratio;
+    }
+}

+ 20 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/report/vo/VisitorAgeExportVO.java

@@ -0,0 +1,20 @@
+package com.yc.ship.module.trade.controller.admin.report.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+/**
+ * 游客年龄段导出 VO
+ */
+@Data
+public class VisitorAgeExportVO {
+
+    @ExcelProperty("年龄段")
+    private String ageGroup;
+
+    @ExcelProperty("人数")
+    private Integer count;
+
+    @ExcelProperty("占比")
+    private String ratio;
+}

+ 9 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/dal/mysql/report/VoyageStockBoardMapper.java

@@ -47,4 +47,13 @@ public interface VoyageStockBoardMapper {
      * @return 游客列表(含证件类型、证件号、国籍及国籍名称)
      */
     List<TradeVisitorDO> selectVisitorListForSourceDashboard(@Param("shipId") Long shipId, @Param("voyageIds") List<Long> voyageIds);
+
+    /**
+     * 查询游客年龄段分析看板所需的游客年龄列表
+     *
+     * @param shipId    游轮ID
+     * @param voyageIds 航次ID列表
+     * @return 游客年龄列表
+     */
+    List<Integer> selectVisitorAgeListForAgeDashboard(@Param("shipId") Long shipId, @Param("voyageIds") List<Long> voyageIds);
 }

+ 17 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/report/VoyageStockBoardService.java

@@ -60,4 +60,21 @@ public interface VoyageStockBoardService {
      * @throws IOException IO异常
      */
     void exportVisitorSourceDashboardExcel(@Valid VisitorSourceDashboardReqVO reqVO, HttpServletResponse response) throws IOException;
+
+    /**
+     * 查询游客年龄段分析看板数据
+     *
+     * @param reqVO 查询条件
+     * @return 游客年龄段分析看板数据
+     */
+    VisitorAgeDashboardRespVO getVisitorAgeDashboard(@Valid VisitorSourceDashboardReqVO reqVO);
+
+    /**
+     * 导出游客年龄段分析看板 Excel
+     *
+     * @param reqVO    查询条件
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void exportVisitorAgeDashboardExcel(@Valid VisitorSourceDashboardReqVO reqVO, HttpServletResponse response) throws IOException;
 }

+ 161 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/report/impl/VoyageStockBoardServiceImpl.java

@@ -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;
+    }
 }

+ 18 - 0
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/VoyageStockBoardMapper.xml

@@ -124,4 +124,22 @@
         </if>
     </select>
 
+    <select id="selectVisitorAgeListForAgeDashboard" resultType="java.lang.Integer">
+        SELECT tv.age
+        FROM trade_visitor tv
+        INNER JOIN trade_order tor ON tv.order_id = tor.id AND tor.deleted = 0
+        LEFT JOIN product_voyage pv ON tor.voyage_id = pv.id
+        WHERE tv.deleted = 0
+        AND tor.order_status IN (15, 14, 13, 10, 12, 9, 8, 7, 6, 5, 4, 3, 1, 0)
+        <if test="shipId != null">
+            AND pv.ship_id = #{shipId}
+        </if>
+        <if test="voyageIds != null and voyageIds.size() > 0">
+            AND tor.voyage_id IN
+            <foreach collection="voyageIds" item="item" separator="," open="(" close=")">
+                #{item}
+            </foreach>
+        </if>
+    </select>
+
 </mapper>