Explorar o código

feat(customer): 添加伙伴商管理平台首页数据统计功能

- 新增 PartnerManageIndexDataVo 数据传输对象定义
- 在 IPartnerInfoService 中添加 partnerManageIndexData 方法接口
- 在 PartnerInfoController 中添加 /partnerManageIndexData 接口映射
- 在 PartnerInfoServiceImpl 中实现首页数据统计逻辑
- 实现伙伴商总数、月度新增、季度新增等核心指标统计
- 集成合作项目数、新增趋势、合作型态分布等数据查询
- 实现最新入驻伙伴商列表展示功能
- 集成字典服务获取合作型态名称映射
hurx hai 17 horas
pai
achega
d848e1edca

+ 26 - 17
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/PartnerInfoController.java

@@ -1,28 +1,29 @@
 package org.dromara.customer.controller;
 package org.dromara.customer.controller;
 
 
-import java.util.List;
-
-import lombok.RequiredArgsConstructor;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.customer.domain.vo.CustomerInfoVo;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.validation.annotation.Validated;
-import org.dromara.common.idempotent.annotation.RepeatSubmit;
-import org.dromara.common.log.annotation.Log;
-import org.dromara.common.web.core.BaseController;
-import org.dromara.common.mybatis.core.page.PageQuery;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.EditGroup;
 import org.dromara.common.core.validate.EditGroup;
-import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.excel.utils.ExcelUtil;
-import org.dromara.customer.domain.vo.PartnerInfoVo;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
 import org.dromara.customer.domain.bo.PartnerInfoBo;
 import org.dromara.customer.domain.bo.PartnerInfoBo;
+import org.dromara.customer.domain.vo.PartnerInfoVo;
+import org.dromara.customer.domain.vo.PartnerManageIndexDataVo;
 import org.dromara.customer.service.IPartnerInfoService;
 import org.dromara.customer.service.IPartnerInfoService;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
 
 
 /**
 /**
  * 伙伴商基本信息
  * 伙伴商基本信息
@@ -39,6 +40,14 @@ public class PartnerInfoController extends BaseController {
 
 
     private final IPartnerInfoService partnerInfoService;
     private final IPartnerInfoService partnerInfoService;
 
 
+    /**
+     * 伙伴商管理平台首页数据
+     */
+    @GetMapping("/partnerManageIndexData")
+    public R<PartnerManageIndexDataVo> partnerManageIndexData() {
+        return partnerInfoService.partnerManageIndexData();
+    }
+
     /**
     /**
      * 查询伙伴商基本信息列表
      * 查询伙伴商基本信息列表
      */
      */
@@ -67,7 +76,7 @@ public class PartnerInfoController extends BaseController {
     @SaCheckPermission("customer:partnerInfo:query")
     @SaCheckPermission("customer:partnerInfo:query")
     @GetMapping("/{id}")
     @GetMapping("/{id}")
     public R<PartnerInfoVo> getInfo(@NotNull(message = "主键不能为空")
     public R<PartnerInfoVo> getInfo(@NotNull(message = "主键不能为空")
-                                     @PathVariable("id") Long id) {
+                                    @PathVariable("id") Long id) {
         return R.ok(partnerInfoService.queryById(id));
         return R.ok(partnerInfoService.queryById(id));
     }
     }
 
 

+ 82 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/PartnerManageIndexDataVo.java

@@ -0,0 +1,82 @@
+package org.dromara.customer.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 伙伴商管理平台首页数据
+ *
+ * @author LionLi
+ * @date 2026-06-01
+ */
+@Data
+public class PartnerManageIndexDataVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 伙伴商总数 */
+    private Long partnerTotal;
+
+    /** 本月新增入驻 */
+    private Long monthlyNew;
+
+    /** 季度新增入驻 */
+    private Long quarterlyNew;
+
+    /** 合作中项目数(PartnerContract中cooperationId去重) */
+    private Long cooperationProjectCount;
+
+    /** 近半年新增伙伴商趋势 */
+    private List<MonthTrend> monthlyTrend;
+
+    /** 伙伴合作型态分布 */
+    private List<TypeDistribution> typeDistribution;
+
+    /** 最新入驻伙伴商列表(最新8条) */
+    private List<LatestPartner> latestPartners;
+
+    /**
+     * 月度趋势
+     */
+    @Data
+    public static class MonthTrend implements Serializable {
+        private static final long serialVersionUID = 1L;
+        /** 月份标签,如"5月" */
+        private String month;
+        /** 新增数量 */
+        private Long count;
+    }
+
+    /**
+     * 合作型态分布
+     */
+    @Data
+    public static class TypeDistribution implements Serializable {
+        private static final long serialVersionUID = 1L;
+        /** 类型名称 */
+        private String name;
+        /** 数量 */
+        private Long value;
+    }
+
+    /**
+     * 最新入驻伙伴商
+     */
+    @Data
+    public static class LatestPartner implements Serializable {
+        private static final long serialVersionUID = 1L;
+        /** 伙伴商名称 */
+        private String companyName;
+        /** 联系人 */
+        private String contact;
+        /** 联系电话 */
+        private String phone;
+        /** 合作型态 */
+        private Long partnerCooperateType;
+        /** 申请时间 */
+        private String joinTime;
+    }
+}

+ 7 - 4
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IPartnerInfoService.java

@@ -1,12 +1,13 @@
 package org.dromara.customer.service;
 package org.dromara.customer.service;
 
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.customer.domain.PartnerInfo;
 import org.dromara.customer.domain.PartnerInfo;
-import org.dromara.customer.domain.vo.CustomerInfoVo;
-import org.dromara.customer.domain.vo.PartnerInfoVo;
 import org.dromara.customer.domain.bo.PartnerInfoBo;
 import org.dromara.customer.domain.bo.PartnerInfoBo;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.customer.domain.vo.PartnerInfoVo;
+import org.dromara.customer.domain.vo.PartnerManageIndexDataVo;
 
 
 import java.util.Collection;
 import java.util.Collection;
 import java.util.List;
 import java.util.List;
@@ -88,4 +89,6 @@ public interface IPartnerInfoService extends IService<PartnerInfo> {
      * @return 伙伴商信息
      * @return 伙伴商信息
      */
      */
     List<PartnerInfoVo> selectByPartnerName(String partnerName);
     List<PartnerInfoVo> selectByPartnerName(String partnerName);
+
+    R<PartnerManageIndexDataVo> partnerManageIndexData();
 }
 }

+ 173 - 15
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/PartnerInfoServiceImpl.java

@@ -1,29 +1,40 @@
 package org.dromara.customer.service.impl;
 package org.dromara.customer.service.impl;
 
 
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.dromara.common.core.utils.MapstructUtils;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.common.mybatis.core.page.PageQuery;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
-import org.dromara.customer.domain.CustomerInfo;
-import org.dromara.customer.domain.SupplierInfo;
-import org.dromara.customer.domain.vo.CustomerInfoVo;
-import org.springframework.stereotype.Service;
-import org.dromara.customer.domain.bo.PartnerInfoBo;
-import org.dromara.customer.domain.vo.PartnerInfoVo;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.customer.domain.PartnerContacts;
+import org.dromara.customer.domain.PartnerContract;
 import org.dromara.customer.domain.PartnerInfo;
 import org.dromara.customer.domain.PartnerInfo;
 import org.dromara.customer.domain.PartnerUser;
 import org.dromara.customer.domain.PartnerUser;
+import org.dromara.customer.domain.bo.PartnerInfoBo;
+import org.dromara.customer.domain.vo.PartnerInfoVo;
+import org.dromara.customer.domain.vo.PartnerManageIndexDataVo;
+import org.dromara.customer.mapper.PartnerContactsMapper;
+import org.dromara.customer.mapper.PartnerContractMapper;
 import org.dromara.customer.mapper.PartnerInfoMapper;
 import org.dromara.customer.mapper.PartnerInfoMapper;
 import org.dromara.customer.mapper.PartnerUserMapper;
 import org.dromara.customer.mapper.PartnerUserMapper;
 import org.dromara.customer.service.IPartnerInfoService;
 import org.dromara.customer.service.IPartnerInfoService;
-import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.RemoteDictService;
+import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+import org.springframework.stereotype.Service;
 
 
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.*;
+import java.util.stream.Collectors;
 
 
 /**
 /**
  * 伙伴商基本信息Service业务层处理
  * 伙伴商基本信息Service业务层处理
@@ -38,6 +49,11 @@ public class PartnerInfoServiceImpl extends ServiceImpl<PartnerInfoMapper, Partn
 
 
     private final PartnerInfoMapper baseMapper;
     private final PartnerInfoMapper baseMapper;
     private final PartnerUserMapper partnerUserMapper;
     private final PartnerUserMapper partnerUserMapper;
+    private final PartnerContractMapper partnerContractMapper;
+    private final PartnerContactsMapper partnerContactsMapper;
+
+    @DubboReference
+    private RemoteDictService remoteDictService;
 
 
     /**
     /**
      * 查询伙伴商基本信息
      * 查询伙伴商基本信息
@@ -209,7 +225,7 @@ public class PartnerInfoServiceImpl extends ServiceImpl<PartnerInfoMapper, Partn
     private String generatePartnerNo() {
     private String generatePartnerNo() {
         // 获取当前年份
         // 获取当前年份
         String year = String.valueOf(java.time.LocalDate.now().getYear());
         String year = String.valueOf(java.time.LocalDate.now().getYear());
-        
+
         // 查询当年最大的编号
         // 查询当年最大的编号
         LambdaQueryWrapper<PartnerInfo> lqw = Wrappers.lambdaQuery();
         LambdaQueryWrapper<PartnerInfo> lqw = Wrappers.lambdaQuery();
         lqw.select(PartnerInfo::getPartnerNo);
         lqw.select(PartnerInfo::getPartnerNo);
@@ -231,8 +247,150 @@ public class PartnerInfoServiceImpl extends ServiceImpl<PartnerInfoMapper, Partn
                 log.warn("解析伙伴商编号失败: {}", maxRecord.getPartnerNo());
                 log.warn("解析伙伴商编号失败: {}", maxRecord.getPartnerNo());
             }
             }
         }
         }
-        
+
         // 格式化为 P + 年份 + 3位序号,如 P2026001
         // 格式化为 P + 年份 + 3位序号,如 P2026001
         return String.format("P%s%03d", year, nextNum);
         return String.format("P%s%03d", year, nextNum);
     }
     }
+
+    @Override
+    public R<PartnerManageIndexDataVo> partnerManageIndexData() {
+        PartnerManageIndexDataVo vo = new PartnerManageIndexDataVo();
+        LocalDate today = LocalDate.now();
+
+        // 1. 伙伴商总数
+        vo.setPartnerTotal(baseMapper.selectCount(null));
+
+        // 2. 本月新增入驻
+        LocalDate firstDayOfMonth = today.withDayOfMonth(1);
+        vo.setMonthlyNew(baseMapper.selectCount(new LambdaQueryWrapper<PartnerInfo>()
+            .ge(PartnerInfo::getCreateTime, java.sql.Date.valueOf(firstDayOfMonth))
+            .lt(PartnerInfo::getCreateTime, java.sql.Date.valueOf(today.plusDays(1)))
+        ));
+
+        // 3. 季度新增入驻
+        int quarterStartMonth = ((today.getMonthValue() - 1) / 3) * 3 + 1;
+        LocalDate firstDayOfQuarter = LocalDate.of(today.getYear(), quarterStartMonth, 1);
+        vo.setQuarterlyNew(baseMapper.selectCount(new LambdaQueryWrapper<PartnerInfo>()
+            .ge(PartnerInfo::getCreateTime, java.sql.Date.valueOf(firstDayOfQuarter))
+            .lt(PartnerInfo::getCreateTime, java.sql.Date.valueOf(today.plusDays(1)))
+        ));
+
+        // 4. 合作中项目数(PartnerContract中cooperationId去重)
+        List<PartnerContract> contracts = partnerContractMapper.selectList(
+            new LambdaQueryWrapper<PartnerContract>()
+                .isNotNull(PartnerContract::getCooperationId)
+                .select(PartnerContract::getCooperationId)
+        );
+        vo.setCooperationProjectCount(contracts.stream()
+            .map(PartnerContract::getCooperationId)
+            .filter(Objects::nonNull)
+            .distinct()
+            .count());
+
+        // 5. 近半年新增伙伴商趋势
+        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("M月");
+        List<PartnerManageIndexDataVo.MonthTrend> monthlyTrend = new ArrayList<>();
+        for (int i = 5; i >= 0; i--) {
+            LocalDate monthStart = today.minusMonths(i).withDayOfMonth(1);
+            LocalDate monthEnd = monthStart.plusMonths(1);
+
+            PartnerManageIndexDataVo.MonthTrend trend = new PartnerManageIndexDataVo.MonthTrend();
+            trend.setMonth(monthStart.format(monthFormatter));
+            trend.setCount(baseMapper.selectCount(new LambdaQueryWrapper<PartnerInfo>()
+                .ge(PartnerInfo::getCreateTime, java.sql.Date.valueOf(monthStart))
+                .lt(PartnerInfo::getCreateTime, java.sql.Date.valueOf(monthEnd))
+            ));
+            monthlyTrend.add(trend);
+        }
+        vo.setMonthlyTrend(monthlyTrend);
+
+        // 6. 伙伴合作型态分布
+        List<PartnerInfo> allPartners = baseMapper.selectList(
+            new LambdaQueryWrapper<PartnerInfo>()
+                .isNotNull(PartnerInfo::getPartnerCooperateType)
+                .select(PartnerInfo::getPartnerCooperateType)
+        );
+        Map<Long, Long> typeCountMap = allPartners.stream()
+            .filter(p -> p.getPartnerCooperateType() != null)
+            .collect(Collectors.groupingBy(PartnerInfo::getPartnerCooperateType, Collectors.counting()));
+
+        // 查询字典获取类型名称
+        List<RemoteDictDataVo> cooperateTypeDicts = remoteDictService.selectDictDataByType("cooperate_type");
+        Map<String, String> dictMap = CollUtil.emptyIfNull(cooperateTypeDicts).stream()
+            .collect(Collectors.toMap(RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel, (k1, k2) -> k1));
+
+        List<PartnerManageIndexDataVo.TypeDistribution> typeDistributions = typeCountMap.entrySet().stream()
+            .map(entry -> {
+                PartnerManageIndexDataVo.TypeDistribution td = new PartnerManageIndexDataVo.TypeDistribution();
+                String typeName = dictMap.get(String.valueOf(entry.getKey()));
+                td.setName(typeName != null ? typeName : "未知");
+                td.setValue(entry.getValue());
+                return td;
+            }).collect(Collectors.toList());
+        vo.setTypeDistribution(typeDistributions);
+
+        // 7. 最新入驻伙伴商列表(最新8条)
+        List<PartnerInfo> latestPartners = baseMapper.selectList(
+            new LambdaQueryWrapper<PartnerInfo>()
+                .orderByDesc(PartnerInfo::getCreateTime)
+                .last("LIMIT 8")
+        );
+
+        if (CollUtil.isNotEmpty(latestPartners)) {
+            Set<Long> partnerIds = latestPartners.stream()
+                .map(PartnerInfo::getId)
+                .collect(Collectors.toSet());
+
+            // 查询联系人,优先取contactType=1,没有则取contactType=2的任意一个
+            List<PartnerContacts> allContacts = partnerContactsMapper.selectList(
+                new LambdaQueryWrapper<PartnerContacts>()
+                    .in(PartnerContacts::getPartnerId, partnerIds)
+            );
+
+            Map<Long, PartnerContacts> contactMap = allContacts.stream()
+                .collect(Collectors.groupingBy(
+                    PartnerContacts::getPartnerId,
+                    Collectors.collectingAndThen(
+                        Collectors.toList(),
+                        list -> {
+                            // 先找type=1
+                            Optional<PartnerContacts> type1 = list.stream()
+                                .filter(c -> c.getContactType() != null && c.getContactType() == 1)
+                                .findFirst();
+                            if (type1.isPresent()) return type1.get();
+                            // 再找type=2
+                            Optional<PartnerContacts> type2 = list.stream()
+                                .filter(c -> c.getContactType() != null && c.getContactType() == 2)
+                                .findFirst();
+                            return type2.orElse(null);
+                        }
+                    )
+                ));
+
+            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+            List<PartnerManageIndexDataVo.LatestPartner> partnerList = latestPartners.stream().map(p -> {
+                PartnerManageIndexDataVo.LatestPartner lp = new PartnerManageIndexDataVo.LatestPartner();
+                lp.setCompanyName(p.getPartnerName());
+                lp.setPartnerCooperateType(p.getPartnerCooperateType());
+
+                PartnerContacts contact = contactMap.get(p.getId());
+                if (contact != null) {
+                    lp.setContact(contact.getName());
+                    lp.setPhone(contact.getPhone());
+                }
+
+                if (p.getCreateTime() != null) {
+                    lp.setJoinTime(p.getCreateTime().toInstant()
+                        .atZone(ZoneId.systemDefault())
+                        .toLocalDateTime().format(dateTimeFormatter));
+                }
+                return lp;
+            }).collect(Collectors.toList());
+            vo.setLatestPartners(partnerList);
+        } else {
+            vo.setLatestPartners(Collections.emptyList());
+        }
+
+        return R.ok(vo);
+    }
 }
 }