Ver Fonte

feat(customer): 添加客户首页数据看板功能

- 新增客户首页数据接口customerIndexData
- 集成订单统计、商品总量、本月营收等核心指标
- 实现最近七天客户趋势图表数据展示
- 添加客户类型分布统计功能
- 集成最新订单明细显示
- 实现热销商品排行榜功能
- 添加相关DTO和VO数据传输对象
hurx há 13 horas atrás
pai
commit
1c2fa8b1c6

+ 28 - 0
ruoyi-api/ruoyi-api-order/src/main/java/org/dromara/product/api/RemoteOrderInfoService.java

@@ -1,7 +1,12 @@
 package org.dromara.product.api;
 
+import org.dromara.product.api.domain.dto.HotProductRankingDto;
+import org.dromara.product.api.domain.dto.LatestOrderDto;
+import org.dromara.product.api.domain.dto.OrderAmountTrendDto;
 import org.dromara.product.api.domain.dto.OrderStatusCountDto;
 
+import java.util.List;
+
 /**
  * @author
  * @date 2026/3/17 下午5:25
@@ -12,4 +17,27 @@ public interface RemoteOrderInfoService {
      * @param itemKey 项目key
     * */
     OrderStatusCountDto getOrderStatusCount(String itemKey);
+
+    /**
+     * 获取最新订单列表
+     * @param itemKey 项目key
+     * @param limit 查询条数
+     * @return 最新订单列表
+     */
+    List<LatestOrderDto> getLatestOrders(String itemKey, int limit);
+
+    /**
+     * 获取本月订单金额趋势(按天汇总)
+     * @param itemKey 项目key
+     * @return 每日订单金额列表
+     */
+    List<OrderAmountTrendDto> getOrderAmountTrend(String itemKey);
+
+    /**
+     * 获取热销商品排行榜
+     * @param itemKey 项目key
+     * @param limit 排行数量
+     * @return 热销商品排行列表
+     */
+    List<HotProductRankingDto> getHotProductRankings(String itemKey, int limit);
 }

+ 26 - 0
ruoyi-api/ruoyi-api-order/src/main/java/org/dromara/product/api/domain/dto/HotProductRankingDto.java

@@ -0,0 +1,26 @@
+package org.dromara.product.api.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 热销商品排行DTO
+ *
+ * @date 2026-06-01
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class HotProductRankingDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 商品名称 */
+    private String productName;
+
+    /** 销量 */
+    private Long salesCount;
+}

+ 39 - 0
ruoyi-api/ruoyi-api-order/src/main/java/org/dromara/product/api/domain/dto/LatestOrderDto.java

@@ -0,0 +1,39 @@
+package org.dromara.product.api.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 最新订单DTO
+ *
+ * @date 2026-06-01
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class LatestOrderDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 订单编号 */
+    private String orderNo;
+
+    /** 客户名称 */
+    private String customerName;
+
+    /** 商品信息(第一个商品名称) */
+    private String productName;
+
+    /** 订单金额 */
+    private BigDecimal amount;
+
+    /** 订单状态 */
+    private String orderStatus;
+
+    /** 下单时间 */
+    private String orderTime;
+}

+ 27 - 0
ruoyi-api/ruoyi-api-order/src/main/java/org/dromara/product/api/domain/dto/OrderAmountTrendDto.java

@@ -0,0 +1,27 @@
+package org.dromara.product.api.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 订单金额趋势DTO
+ *
+ * @date 2026-06-01
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderAmountTrendDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 日期 */
+    private String date;
+
+    /** 金额 */
+    private BigDecimal amount;
+}

+ 5 - 0
ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/RemoteProductService.java

@@ -125,5 +125,10 @@ public interface RemoteProductService {
      */
     Long getProductIdProductNo(String productNo);
 
+    /**
+     * 获取商品总数
+     * @return 商品总数
+     */
+    Long getTotalProductCount();
 
 }

+ 4 - 0
ruoyi-modules/ruoyi-customer/pom.xml

@@ -123,6 +123,10 @@
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-api-product</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-order</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 10 - 7
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerInfoController.java

@@ -17,17 +17,12 @@ 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.common.web.core.BaseController;
-import org.dromara.customer.domain.bo.CustomerClaimBo;
-import org.dromara.customer.domain.bo.CustomerInfoBo;
-import org.dromara.customer.domain.bo.CustomerListBo;
-import org.dromara.customer.domain.bo.CustomerSalesInfoBo;
-import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
-import org.dromara.customer.domain.dto.SetCustomerInfoTagDto;
+import org.dromara.customer.domain.bo.*;
 import org.dromara.customer.domain.dto.ReleaseToPoolDto;
+import org.dromara.customer.domain.dto.SetCustomerInfoTagDto;
 import org.dromara.customer.domain.vo.*;
 import org.dromara.customer.listener.CustomerImportListener;
 import org.dromara.customer.service.ICustomerInfoService;
-import org.dromara.system.api.RemoteProductTaxrateService;
 import org.dromara.system.api.*;
 import org.dromara.system.api.domain.vo.RemoteComCompanyVo;
 import org.dromara.system.api.domain.vo.RemoteComCustomerLevelVo;
@@ -74,6 +69,14 @@ public class CustomerInfoController extends BaseController {
 
     private final ICustomerInfoService customerInfoService;
 
+    /**
+     * 客户首页数据
+     */
+    @GetMapping("/customerIndexData")
+    public R<CustomerIndexDataVo> customerIndexData() {
+        return customerInfoService.customerIndexData();
+    }
+
     /**
      * 查询客户信息列表
      */

+ 14 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/IndexDataController.java

@@ -0,0 +1,14 @@
+package org.dromara.customer.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/indexData")
+public class IndexDataController {
+
+}

+ 103 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerIndexDataVo.java

@@ -0,0 +1,103 @@
+package org.dromara.customer.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 客户首页数据看板VO
+ *
+ * @date 2026-06-01
+ */
+@Data
+public class CustomerIndexDataVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 总客户数 */
+    private Long totalCustomers;
+
+    /** 总订单数 */
+    private Long totalOrders;
+
+    /** 商品总量 */
+    private Long totalProducts;
+
+    /** 本月营收 */
+    private BigDecimal monthlyRevenue;
+
+    /** 最近七天客户趋势 */
+    private List<TrendItem> customerTrend;
+
+    /** 本月订单金额趋势 */
+    private List<TrendItem> orderAmountTrend;
+
+    /** 客户类型分布 */
+    private List<TypeDistribution> customerTypeDistribution;
+
+    /** 最新订单明细 */
+    private List<LatestOrder> latestOrders;
+
+    /** 热销商品排行榜 */
+    private List<HotProduct> hotProductRankings;
+
+    /**
+     * 趋势数据项
+     */
+    @Data
+    public static class TrendItem implements Serializable {
+        private static final long serialVersionUID = 1L;
+        /** 日期 */
+        private String date;
+        /** 数量/金额 */
+        private BigDecimal value;
+    }
+
+    /**
+     * 类型分布
+     */
+    @Data
+    public static class TypeDistribution implements Serializable {
+        private static final long serialVersionUID = 1L;
+        /** 类型名称 */
+        private String name;
+        /** 数量 */
+        private Long value;
+    }
+
+    /**
+     * 最新订单
+     */
+    @Data
+    public static class LatestOrder implements Serializable {
+        private static final long serialVersionUID = 1L;
+        /** 订单编号 */
+        private String orderNo;
+        /** 客户名称 */
+        private String customer;
+        /** 商品信息 */
+        private String product;
+        /** 金额 */
+        private BigDecimal amount;
+        /** 状态 */
+        private String status;
+        /** 下单时间 */
+        private String date;
+    }
+
+    /**
+     * 热销商品
+     */
+    @Data
+    public static class HotProduct implements Serializable {
+        private static final long serialVersionUID = 1L;
+        /** 商品名称 */
+        private String name;
+        /** 销量 */
+        private Long sales;
+        /** 百分比 */
+        private Integer percentage;
+    }
+}

+ 4 - 6
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerInfoService.java

@@ -1,14 +1,12 @@
 package org.dromara.customer.service;
 
 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.CustomerInfo;
 import org.dromara.customer.domain.bo.*;
-import org.dromara.customer.domain.vo.ContractVo;
-import org.dromara.customer.domain.vo.CustomerInfoVo;
-import org.dromara.customer.domain.vo.CustomerListVo;
-import org.dromara.customer.domain.vo.MessagePublishCustomerVo;
+import org.dromara.customer.domain.vo.*;
 
 import java.math.BigDecimal;
 import java.util.Collection;
@@ -130,7 +128,7 @@ public interface ICustomerInfoService extends IService<CustomerInfo> {
      * 退回客户到公海
      *
      * @param customerIds 客户ID列表
-     * @param reason 退回原因
+     * @param reason      退回原因
      * @return 更新数量
      */
     int releaseToPool(List<Long> customerIds, String reason);
@@ -195,6 +193,6 @@ public interface ICustomerInfoService extends IService<CustomerInfo> {
      */
     CustomerInfoVo selectCustomerByName(String customerName);
 
-
+    R<CustomerIndexDataVo> customerIndexData();
 
 }

+ 141 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java

@@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.dromara.common.core.constant.GlobalConstants;
 import org.dromara.common.core.context.PlatformContext;
+import org.dromara.common.core.domain.R;
 import org.dromara.common.core.enums.IsDefault;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.DateUtils;
@@ -31,6 +32,12 @@ import org.dromara.customer.mapper.*;
 import org.dromara.customer.service.ICustomerInfoService;
 import org.dromara.customer.utils.qcc.QccUtils;
 import org.dromara.customer.utils.qcc.domain.CompanyInfoResponse;
+import org.dromara.product.api.RemoteOrderInfoService;
+import org.dromara.product.api.RemoteProductService;
+import org.dromara.product.api.domain.dto.HotProductRankingDto;
+import org.dromara.product.api.domain.dto.LatestOrderDto;
+import org.dromara.product.api.domain.dto.OrderAmountTrendDto;
+import org.dromara.product.api.domain.dto.OrderStatusCountDto;
 import org.dromara.system.api.*;
 import org.dromara.system.api.domain.bo.RemoteUserBo;
 import org.dromara.system.api.domain.dto.AddressAreaDTO;
@@ -42,6 +49,8 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.time.Duration;
+import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -89,6 +98,12 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
     @DubboReference
     private RemoteAddressAreaService remoteAddressAreaService;
 
+    @DubboReference
+    private RemoteOrderInfoService remoteOrderInfoService;
+
+    @DubboReference
+    private RemoteProductService remoteProductService;
+
     private static final String CUSTOMER_NO_KEY = "customer_info:customer_no";
     private static final String CONTACT_NO_KEY = "customer_contact:contact_no";
 
@@ -1872,4 +1887,130 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
     public CustomerInfoVo selectCustomerByName(String customerName) {
         return baseMapper.selectVoOne(new LambdaQueryWrapper<CustomerInfo>().eq(CustomerInfo::getCustomerName, customerName));
     }
+
+    @Override
+    public R<CustomerIndexDataVo> customerIndexData() {
+        CustomerIndexDataVo vo = new CustomerIndexDataVo();
+
+        // 1. 总客户数
+        vo.setTotalCustomers(baseMapper.selectCount(null));
+
+        // 2. 订单统计
+        OrderStatusCountDto orderStatusCount = remoteOrderInfoService.getOrderStatusCount(null);
+        vo.setTotalOrders(orderStatusCount.getOrderCount());
+
+        // 3. 商品总量
+        vo.setTotalProducts(remoteProductService.getTotalProductCount());
+
+        // 4. 本月营收(从订单金额趋势汇总)
+        List<OrderAmountTrendDto> orderAmountTrend = remoteOrderInfoService.getOrderAmountTrend(null);
+        BigDecimal monthlyRevenue = orderAmountTrend.stream()
+            .map(OrderAmountTrendDto::getAmount)
+            .filter(Objects::nonNull)
+            .reduce(BigDecimal.ZERO, BigDecimal::add);
+        vo.setMonthlyRevenue(monthlyRevenue);
+
+        // 5. 最近七天客户趋势
+        LocalDate today = LocalDate.now();
+        LocalDate sevenDaysAgo = today.minusDays(6);
+        List<CustomerInfo> recentCustomers = baseMapper.selectList(
+            new LambdaQueryWrapper<CustomerInfo>()
+                .ge(CustomerInfo::getCreateTime, java.sql.Date.valueOf(sevenDaysAgo))
+                .le(CustomerInfo::getCreateTime, java.sql.Date.valueOf(today))
+        );
+        Map<LocalDate, Long> dailyCustomerMap = new LinkedHashMap<>();
+        LocalDate cursor = sevenDaysAgo;
+        while (!cursor.isAfter(today)) {
+            dailyCustomerMap.put(cursor, 0L);
+            cursor = cursor.plusDays(1);
+        }
+        for (CustomerInfo c : recentCustomers) {
+            if (c.getCreateTime() != null) {
+                LocalDate createDate = c.getCreateTime().toInstant()
+                    .atZone(ZoneId.systemDefault()).toLocalDate();
+                dailyCustomerMap.merge(createDate, 1L, Long::sum);
+            }
+        }
+        List<String> weekDayNames = Arrays.asList("周一", "周二", "周三", "周四", "周五", "周六", "周日");
+        List<CustomerIndexDataVo.TrendItem> customerTrend = new ArrayList<>();
+        int idx = 0;
+        for (Map.Entry<LocalDate, Long> entry : dailyCustomerMap.entrySet()) {
+            CustomerIndexDataVo.TrendItem item = new CustomerIndexDataVo.TrendItem();
+            // 使用星期几作为标签,与前端保持一致
+            item.setDate(weekDayNames.get(idx % 7));
+            item.setValue(BigDecimal.valueOf(entry.getValue()));
+            customerTrend.add(item);
+            idx++;
+        }
+        vo.setCustomerTrend(customerTrend);
+
+        // 6. 本月订单金额趋势(转换DTO->VO)
+        List<CustomerIndexDataVo.TrendItem> orderTrendItems = orderAmountTrend.stream().map(dto -> {
+            CustomerIndexDataVo.TrendItem item = new CustomerIndexDataVo.TrendItem();
+            item.setDate(dto.getDate());
+            item.setValue(dto.getAmount());
+            return item;
+        }).collect(Collectors.toList());
+        vo.setOrderAmountTrend(orderTrendItems);
+
+        // 7. 客户类型分布
+        List<CustomerInfo> allCustomers = baseMapper.selectList(
+            new LambdaQueryWrapper<CustomerInfo>()
+                .isNotNull(CustomerInfo::getCustomerTypeId)
+                .select(CustomerInfo::getCustomerTypeId)
+        );
+        Map<Long, Long> typeCountMap = allCustomers.stream()
+            .filter(c -> c.getCustomerTypeId() != null)
+            .collect(Collectors.groupingBy(CustomerInfo::getCustomerTypeId, Collectors.counting()));
+
+        // 查询字典获取类型名称
+        List<RemoteDictDataVo> enterpriseTypeDicts = remoteDictService.selectDictDataByType("Q0001");
+        Map<String, String> dictMap = CollUtil.emptyIfNull(enterpriseTypeDicts).stream()
+            .collect(Collectors.toMap(RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel, (k1, k2) -> k1));
+
+        List<CustomerIndexDataVo.TypeDistribution> typeDistributions = typeCountMap.entrySet().stream()
+            .map(entry -> {
+                CustomerIndexDataVo.TypeDistribution td = new CustomerIndexDataVo.TypeDistribution();
+                String typeName = dictMap.get(String.valueOf(entry.getKey()));
+                td.setName(typeName != null ? typeName : "其他");
+                td.setValue(entry.getValue());
+                return td;
+            }).collect(Collectors.toList());
+        vo.setCustomerTypeDistribution(typeDistributions);
+
+        // 8. 最新订单明细
+        List<LatestOrderDto> latestOrders = remoteOrderInfoService.getLatestOrders(null, 10);
+        List<CustomerIndexDataVo.LatestOrder> orderList = latestOrders.stream().map(dto -> {
+            CustomerIndexDataVo.LatestOrder order = new CustomerIndexDataVo.LatestOrder();
+            order.setOrderNo(dto.getOrderNo());
+            order.setCustomer(dto.getCustomerName());
+            order.setProduct(dto.getProductName());
+            order.setAmount(dto.getAmount());
+            order.setStatus(dto.getOrderStatus());
+            order.setDate(dto.getOrderTime());
+            return order;
+        }).collect(Collectors.toList());
+        vo.setLatestOrders(orderList);
+
+        // 9. 热销商品排行榜
+        List<HotProductRankingDto> hotProductRankings = remoteOrderInfoService.getHotProductRankings(null, 6);
+        // 计算最大销量用于百分比
+        long maxSales = hotProductRankings.stream()
+            .mapToLong(HotProductRankingDto::getSalesCount)
+            .max().orElse(1L);
+        List<CustomerIndexDataVo.HotProduct> hotProducts = hotProductRankings.stream().map(dto -> {
+            CustomerIndexDataVo.HotProduct hp = new CustomerIndexDataVo.HotProduct();
+            hp.setName(dto.getProductName());
+            hp.setSales(dto.getSalesCount());
+            // 百分比 = 当前销量 / 最大销量 * 100
+            int percentage = maxSales > 0
+                ? (int) Math.round(dto.getSalesCount() * 100.0 / maxSales)
+                : 0;
+            hp.setPercentage(percentage);
+            return hp;
+        }).collect(Collectors.toList());
+        vo.setHotProductRankings(hotProducts);
+
+        return R.ok(vo);
+    }
 }

+ 170 - 17
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/dubbo/RemoteOrderInfoServiceImpl.java

@@ -1,22 +1,33 @@
 package org.dromara.order.dubbo;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.dromara.common.core.enums.OrderStatus;
+import org.dromara.customer.api.RemoteCustomerService;
 import org.dromara.order.domain.OrderMain;
+import org.dromara.order.domain.OrderProduct;
 import org.dromara.order.domain.OrderReturn;
 import org.dromara.order.service.IOrderMainService;
+import org.dromara.order.service.IOrderProductService;
 import org.dromara.order.service.IOrderReturnService;
 import org.dromara.product.api.RemoteOrderInfoService;
+import org.dromara.product.api.domain.dto.HotProductRankingDto;
+import org.dromara.product.api.domain.dto.LatestOrderDto;
+import org.dromara.product.api.domain.dto.OrderAmountTrendDto;
 import org.dromara.product.api.domain.dto.OrderStatusCountDto;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
-import java.util.List;
-import java.util.Objects;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @author
@@ -32,6 +43,11 @@ public class RemoteOrderInfoServiceImpl implements RemoteOrderInfoService {
 
     private final IOrderReturnService orderReturnService;
 
+    private final IOrderProductService orderProductService;
+
+    @DubboReference
+    private RemoteCustomerService remoteCustomerService;
+
     /**
      * 获取订单各个状态的数量
      */
@@ -41,50 +57,187 @@ public class RemoteOrderInfoServiceImpl implements RemoteOrderInfoService {
         // 订单数量
         orderStatusCount.setOrderCount(
             orderMainService.count(Wrappers.<OrderMain>lambdaQuery()
-                .eq(OrderMain::getDataSource, itemKey)
-        ));
+                .eq(StrUtil.isNotBlank(itemKey), OrderMain::getDataSource, itemKey)
+            ));
         // 已完成订单数量
         orderStatusCount.setCompletedCount(
             orderMainService.count(Wrappers.<OrderMain>lambdaQuery()
-                .eq(OrderMain::getDataSource, itemKey)
+                .eq(StrUtil.isNotBlank(itemKey), OrderMain::getDataSource, itemKey)
                 .eq(OrderMain::getOrderStatus, OrderStatus.COMPLETED)
-        ));
+            ));
         // 待付款订单数量
         orderStatusCount.setWaitPayCount(
             orderMainService.count(Wrappers.<OrderMain>lambdaQuery()
-                .eq(OrderMain::getDataSource, itemKey)
+                .eq(StrUtil.isNotBlank(itemKey), OrderMain::getDataSource, itemKey)
                 .eq(OrderMain::getOrderStatus, OrderStatus.PENDING_PAYMENT)
-        ));
+            ));
         // 待发货订单数量
         orderStatusCount.setWaitDeliverCount(
             orderMainService.count(Wrappers.<OrderMain>lambdaQuery()
-                .eq(OrderMain::getDataSource, itemKey)
+                .eq(StrUtil.isNotBlank(itemKey), OrderMain::getDataSource, itemKey)
                 .eq(OrderMain::getOrderStatus, OrderStatus.PENDING_SHIPMENT)
-        ));
+            ));
         // 售后订单数量
         orderStatusCount.setWaitDeliverCount(
             orderReturnService.count(Wrappers.<OrderReturn>lambdaQuery()
-                .eq(OrderReturn::getDataSource, itemKey)
-        ));
+                .eq(StrUtil.isNotBlank(itemKey), OrderReturn::getDataSource, itemKey)
+            ));
         // 订单总金额
         List<OrderMain> orderMains = orderMainService.list(Wrappers.<OrderMain>lambdaQuery()
-            .eq(OrderMain::getDataSource, itemKey)
+            .eq(StrUtil.isNotBlank(itemKey), OrderMain::getDataSource, itemKey)
             .select(OrderMain::getTotalAmount)
         );
-        if(ObjectUtil.isNotEmpty(orderMains)){
+        if (ObjectUtil.isNotEmpty(orderMains)) {
             orderStatusCount.setOrderAmount(orderMains.stream().map(OrderMain::getTotalAmount).filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));
         }
         // 售后订单金额 如果为null 则为0
         List<OrderReturn> orderReturns = orderReturnService.list(Wrappers.<OrderReturn>lambdaQuery()
-            .eq(OrderReturn::getDataSource, itemKey)
+            .eq(StrUtil.isNotBlank(itemKey), OrderReturn::getDataSource, itemKey)
             .select(OrderReturn::getAfterSaleAmount)
         );
-        if(ObjectUtil.isNotEmpty(orderReturns)){
+        if (ObjectUtil.isNotEmpty(orderReturns)) {
             orderStatusCount.setRefundAmount(
-            orderReturns.stream().map(OrderReturn::getAfterSaleAmount).filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO)
+                orderReturns.stream().map(OrderReturn::getAfterSaleAmount).filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO)
             );
         }
 
         return orderStatusCount;
     }
+
+    /**
+     * 获取最新订单列表
+     */
+    @Override
+    public List<LatestOrderDto> getLatestOrders(String itemKey, int limit) {
+        // 查询最新N条一级订单(currentLevel=1),按创建时间倒序
+        List<OrderMain> orderMains = orderMainService.list(Wrappers.<OrderMain>lambdaQuery()
+            .eq(StrUtil.isNotBlank(itemKey), OrderMain::getDataSource, itemKey)
+            .eq(OrderMain::getCurrentLevel, 1)
+            .orderByDesc(OrderMain::getCreateTime)
+            .last("LIMIT " + limit)
+        );
+
+        if (CollUtil.isEmpty(orderMains)) {
+            return Collections.emptyList();
+        }
+
+        // 收集所有订单ID
+        Set<Long> orderIds = orderMains.stream()
+            .map(OrderMain::getId)
+            .collect(Collectors.toSet());
+
+        // 查询订单商品
+        List<OrderProduct> orderProducts = orderProductService.list(Wrappers.<OrderProduct>lambdaQuery()
+            .in(OrderProduct::getOrderId, orderIds)
+        );
+
+        // 按订单ID分组(取每个订单的第一个商品名称)
+        Map<Long, String> orderProductMap = orderProducts.stream()
+            .collect(Collectors.groupingBy(
+                OrderProduct::getOrderId,
+                Collectors.collectingAndThen(
+                    Collectors.toList(),
+                    list -> list.isEmpty() ? "" : list.get(0).getProductName()
+                )
+            ));
+
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+        // 收集所有客户ID,远程批量查询客户名称
+        Set<Long> customerIds = orderMains.stream()
+            .map(OrderMain::getCustomerId)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toSet());
+        Map<Long, String> customerNameMap = CollUtil.isEmpty(customerIds)
+            ? Collections.emptyMap()
+            : remoteCustomerService.selectCustomerNameByIds(customerIds);
+
+        return orderMains.stream().map(order -> {
+            LatestOrderDto dto = new LatestOrderDto();
+            dto.setOrderNo(order.getOrderNo());
+            dto.setCustomerName(customerNameMap.getOrDefault(order.getCustomerId(), ""));
+            dto.setProductName(orderProductMap.getOrDefault(order.getId(), ""));
+            dto.setAmount(order.getTotalAmount());
+            dto.setOrderStatus(order.getOrderStatus());
+            if (order.getOrderTime() != null) {
+                dto.setOrderTime(order.getOrderTime().toInstant()
+                    .atZone(java.time.ZoneId.systemDefault())
+                    .toLocalDateTime().format(formatter));
+            }
+            return dto;
+        }).collect(Collectors.toList());
+    }
+
+    /**
+     * 获取本月订单金额趋势
+     */
+    @Override
+    public List<OrderAmountTrendDto> getOrderAmountTrend(String itemKey) {
+        // 获取本月第一天
+        LocalDate firstDayOfMonth = LocalDate.now().withDayOfMonth(1);
+        LocalDate today = LocalDate.now();
+
+        List<OrderMain> orderMains = orderMainService.list(Wrappers.<OrderMain>lambdaQuery()
+            .eq(StrUtil.isNotBlank(itemKey), OrderMain::getDataSource, itemKey)
+            .ge(OrderMain::getCreateTime, java.sql.Date.valueOf(firstDayOfMonth))
+            .lt(OrderMain::getCreateTime, java.sql.Date.valueOf(today.plusDays(1)))
+            .select(OrderMain::getCreateTime, OrderMain::getTotalAmount)
+        );
+
+        // 按天分组汇总
+        Map<LocalDate, BigDecimal> dailyAmountMap = new LinkedHashMap<>();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d日");
+
+        // 初始化本月所有日期
+        LocalDate current = firstDayOfMonth;
+        while (!current.isAfter(today)) {
+            dailyAmountMap.put(current, BigDecimal.ZERO);
+            current = current.plusDays(1);
+        }
+
+        // 汇总每天金额
+        for (OrderMain order : orderMains) {
+            if (order.getCreateTime() != null && order.getTotalAmount() != null) {
+                LocalDate orderDate = order.getCreateTime().toInstant()
+                    .atZone(java.time.ZoneId.systemDefault())
+                    .toLocalDate();
+                dailyAmountMap.merge(orderDate, order.getTotalAmount(), BigDecimal::add);
+            }
+        }
+
+        return dailyAmountMap.entrySet().stream()
+            .map(entry -> new OrderAmountTrendDto(entry.getKey().format(formatter), entry.getValue()))
+            .collect(Collectors.toList());
+    }
+
+    /**
+     * 获取热销商品排行榜
+     */
+    @Override
+    public List<HotProductRankingDto> getHotProductRankings(String itemKey, int limit) {
+        // 查询所有订单商品,按商品名称分组汇总销量
+        List<OrderProduct> orderProducts = orderProductService.list(Wrappers.<OrderProduct>lambdaQuery()
+            .eq(StrUtil.isNotBlank(itemKey), OrderProduct::getDataSource, itemKey)
+            .select(OrderProduct::getProductName, OrderProduct::getOrderQuantity)
+        );
+
+        if (CollUtil.isEmpty(orderProducts)) {
+            return Collections.emptyList();
+        }
+
+        // 按商品名称分组汇总销量
+        Map<String, Long> productSalesMap = orderProducts.stream()
+            .filter(p -> p.getProductName() != null && p.getOrderQuantity() != null)
+            .collect(Collectors.groupingBy(
+                OrderProduct::getProductName,
+                Collectors.reducing(0L, OrderProduct::getOrderQuantity, Long::sum)
+            ));
+
+        // 按销量降序排列,取前N条
+        return productSalesMap.entrySet().stream()
+            .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
+            .limit(limit)
+            .map(entry -> new HotProductRankingDto(entry.getKey(), entry.getValue()))
+            .collect(Collectors.toList());
+    }
 }

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/dubbo/RemoteProductServiceImpl.java

@@ -701,4 +701,12 @@ public class RemoteProductServiceImpl implements RemoteProductService {
 
         return Objects.isNull(productBase) ? null : productBase.getId();
     }
+
+    /**
+     * 获取商品总数
+     */
+    @Override
+    public Long getTotalProductCount() {
+        return productBaseService.count();
+    }
 }