ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/DashboardStatisticsController.java
New file @@ -0,0 +1,36 @@ package com.ruoyi.web.controller.api; import com.ruoyi.common.core.domain.R; import com.ruoyi.system.query.DashboardStatisticsQuery; import com.ruoyi.system.service.DashboardStatisticsService; import com.ruoyi.system.vo.DashboardStatisticsVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 数据统计面板 前端控制器 * * @author xiaochen * @since 2025-12-23 */ @RestController @RequestMapping("/dashboard-statistics") @Api(tags = "(嘉视达)数据统计面板") @RequiredArgsConstructor(onConstructor_ = @Lazy) public class DashboardStatisticsController { private final DashboardStatisticsService dashboardStatisticsService; @ApiOperation(value = "获取数据统计面板数据") @PostMapping("/overview") public R<DashboardStatisticsVO> getOverview(@RequestBody DashboardStatisticsQuery query) { return R.ok(dashboardStatisticsService.getStatistics(query)); } } ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TIntegralSettlementController.java
@@ -61,8 +61,8 @@ response.setHeader("content-Type", "application/vnd.ms-excel"); response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); workbook.write(outputStream); outputStream = response.getOutputStream(); workbook.write(outputStream); } catch (IOException e) { log.error("积分结算导入模板下载失败", e); } finally { ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/agent/AgentCompanyController.java
New file @@ -0,0 +1,56 @@ package com.ruoyi.web.controller.api.agent; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.system.model.TCompany; import com.ruoyi.system.service.TCompanyService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 经销商管理后台 - 公司信息 前端控制器 * * @author xiaochen * @since 2025-12-23 */ @RestController @RequestMapping("/agent/company") @Api(tags = "(经销商后台)公司信息") @RequiredArgsConstructor(onConstructor_ = @Lazy) public class AgentCompanyController extends BaseController { private final TCompanyService companyService; @ApiOperation(value = "获取当前用户所属公司详情") @GetMapping("/detail") public R<TCompany> getDetail() { // 获取当前登录用户 LoginUser loginUser = SecurityUtils.getLoginUser(); SysUser user = loginUser.getUser(); if (!user.getRoleType().equals(2)) { return R.fail("请使用经销商账号访问"); } // 获取用户所属公司ID Integer companyId = user.getCompanyId(); if (companyId == null) { return R.fail("当前用户未关联公司"); } // 查询公司详情 TCompany company = companyService.getById(companyId); if (company == null) { return R.fail("公司信息不存在"); } return R.ok(company); } } ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/AppUserVisitController.java
New file @@ -0,0 +1,35 @@ package com.ruoyi.web.controller.api; import com.ruoyi.common.core.domain.R; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.service.TAppUserVisitService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 用户访问记录 * * @author xiaochen */ @RestController @RequestMapping("/appUserVisit") @Api(tags = "用户访问记录") @RequiredArgsConstructor(onConstructor_ = {@Lazy}) public class AppUserVisitController { private final TokenService tokenService; private final TAppUserVisitService appUserVisitService; @ApiOperation(value = "记录用户访问") @PostMapping("/record") public R<Void> recordVisit() { Integer userId = tokenService.getLoginUserApplet().getUserId(); appUserVisitService.recordVisit(userId); return R.ok(); } } ruoyi-applet/src/main/resources/application-test.yml
@@ -240,7 +240,7 @@ order-refund-url: "https://gw.open.icbc.com.cn/api/cardbusiness/aggregatepay/b2c/online/refundqry/V1" # 完整的回调通知地址,支付完成后通知商户的URL notify-url: "http://jiashida.free.idcfengye.com/icbc-notify/" notify-url: "http://221.182.45.100:7890/icbc-notify/" # 回调方法的路径 notify-url-method: "/icbc-notify/" ruoyi-system/src/main/java/com/ruoyi/system/export/IntegralSettlementImport.java
@@ -14,7 +14,7 @@ private String code; @Excel(name = "结算积分", width = 20) private BigDecimal totalSettlementIntegral; private Integer totalSettlementIntegral; @Excel(name = "备注", width = 20) private String remark; ruoyi-system/src/main/java/com/ruoyi/system/mapper/TIntegralSettlementMapper.java
@@ -25,6 +25,7 @@ * 分页查询积分结算列表(按结算周期分组汇总) */ List<TIntegralSettlementVO> selectSettlementPageList(Page<TIntegralSettlementVO> page, @Param("query") TIntegralSettlementQuery query); /** * 分页查询积分结算详情列表 */ ruoyi-system/src/main/java/com/ruoyi/system/model/TAppUserVisit.java
@@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableField; import java.io.Serializable; import java.time.LocalDateTime; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -39,5 +41,9 @@ @TableField("visit_date") private LocalDate visitDate; @ApiModelProperty(value = "创建时间") @TableField("create_time") private LocalDateTime createTime; } ruoyi-system/src/main/java/com/ruoyi/system/model/TBigGiftPackageOrder.java
@@ -79,7 +79,7 @@ @TableField("payment_price") private BigDecimal paymentPrice; @ApiModelProperty(value = "订单状态(1=待付款,2=已付订金,3=完成支付,4=确认收货,5=已退款)") @ApiModelProperty(value = "订单状态(1=待付款,2=已付订金,3=完成支付,4=确认收货,5=已退款,6=已取消)") @TableField("status") private Integer status; ruoyi-system/src/main/java/com/ruoyi/system/query/DashboardStatisticsQuery.java
New file @@ -0,0 +1,31 @@ package com.ruoyi.system.query; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import java.io.Serializable; import java.time.LocalDate; /** * 数据统计面板查询条件 * * @author xiaochen * @since 2025-12-23 */ @Data @ApiModel(value = "数据统计面板查询条件") public class DashboardStatisticsQuery implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "开始日期") @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate startDate; @ApiModelProperty(value = "结束日期") @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate endDate; } ruoyi-system/src/main/java/com/ruoyi/system/query/TIntegralSettlementDetailQuery.java
@@ -9,8 +9,11 @@ * 积分结算列表查询条件 */ @Data @ApiModel(value = "积分结算列表查询条件") @ApiModel(value = "积分结算详情列表查询条件") public class TIntegralSettlementDetailQuery extends BasePage { @ApiModelProperty("主键ID") private Integer id; @ApiModelProperty(value = "用户名") private String name; @@ -20,4 +23,7 @@ @ApiModelProperty(value = "推广号") private String code; @ApiModelProperty(value = "结算周期(前端不传)") private String settlementInterval; } ruoyi-system/src/main/java/com/ruoyi/system/service/DashboardStatisticsService.java
New file @@ -0,0 +1,22 @@ package com.ruoyi.system.service; import com.ruoyi.system.query.DashboardStatisticsQuery; import com.ruoyi.system.vo.DashboardStatisticsVO; /** * 数据统计面板 服务类 * * @author xiaochen * @since 2025-12-23 */ public interface DashboardStatisticsService { /** * 获取数据统计面板数据 * * @param query 查询条件 * @return 统计数据 */ DashboardStatisticsVO getStatistics(DashboardStatisticsQuery query); } ruoyi-system/src/main/java/com/ruoyi/system/service/TAppUserVisitService.java
@@ -13,4 +13,8 @@ */ public interface TAppUserVisitService extends IService<TAppUserVisit> { /** * 记录用户访问(每日去重) */ void recordVisit(Integer userId); } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DashboardStatisticsServiceImpl.java
New file @@ -0,0 +1,374 @@ package com.ruoyi.system.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.system.model.TAppUserVisit; import com.ruoyi.system.model.TBigGiftPackageOrder; import com.ruoyi.system.model.TPhysicalGoodsOrder; import com.ruoyi.system.query.DashboardStatisticsQuery; import com.ruoyi.system.service.DashboardStatisticsService; import com.ruoyi.system.service.TAppUserVisitService; import com.ruoyi.system.service.TBigGiftPackageOrderService; import com.ruoyi.system.service.TPhysicalGoodsOrderService; import com.ruoyi.system.vo.*; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.Collectors; /** * 数据统计面板 服务实现类 * * @author xiaochen * @since 2025-12-23 */ @Service @RequiredArgsConstructor(onConstructor_ = @Lazy) public class DashboardStatisticsServiceImpl implements DashboardStatisticsService { private final TAppUserVisitService appUserVisitService; private final TPhysicalGoodsOrderService physicalGoodsOrderService; private final TBigGiftPackageOrderService bigGiftPackageOrderService; /** * 实物商品订单有效状态:完成支付(2)、确认收货(3) */ private static final List<Integer> PHYSICAL_ORDER_VALID_STATUS = Arrays.asList(2, 3); /** * 大礼包订单有效状态:已付订金(2)、完成支付(3)、确认收货(4) */ private static final List<Integer> BIG_GIFT_ORDER_VALID_STATUS = Arrays.asList(2, 3, 4); @Override public DashboardStatisticsVO getStatistics(DashboardStatisticsQuery query) { // 处理日期范围,默认为当月 LocalDate endDate = query.getEndDate() != null ? query.getEndDate() : LocalDate.now(); LocalDate startDate = query.getStartDate() != null ? query.getStartDate() : endDate.withDayOfMonth(1); // 计算上一周期的日期范围(用于环比计算) long daysBetween = ChronoUnit.DAYS.between(startDate, endDate) + 1; LocalDate lastPeriodEnd = startDate.minusDays(1); LocalDate lastPeriodStart = lastPeriodEnd.minusDays(daysBetween - 1); return DashboardStatisticsVO.builder() .visitStats(buildVisitStats(startDate, endDate, lastPeriodStart, lastPeriodEnd)) .orderCountStats(buildOrderCountStats(startDate, endDate, lastPeriodStart, lastPeriodEnd)) .orderAmountStats(buildOrderAmountStats(startDate, endDate, lastPeriodStart, lastPeriodEnd)) .monthlyStats(buildMonthlyStats(startDate, endDate)) .build(); } /** * 构建客户访问量统计 */ private StatisticsItemVO buildVisitStats(LocalDate startDate, LocalDate endDate, LocalDate lastPeriodStart, LocalDate lastPeriodEnd) { // 当前周期访问量 long currentTotal = appUserVisitService.count(new LambdaQueryWrapper<TAppUserVisit>() .between(TAppUserVisit::getVisitDate, startDate, endDate)); // 上一周期访问量 long lastTotal = appUserVisitService.count(new LambdaQueryWrapper<TAppUserVisit>() .between(TAppUserVisit::getVisitDate, lastPeriodStart, lastPeriodEnd)); // 趋势数据(按日统计) List<TAppUserVisit> visitList = appUserVisitService.list(new LambdaQueryWrapper<TAppUserVisit>() .between(TAppUserVisit::getVisitDate, startDate, endDate)); Map<LocalDate, Long> visitByDate = visitList.stream() .collect(Collectors.groupingBy(TAppUserVisit::getVisitDate, Collectors.counting())); List<TrendDataVO> trend = buildDailyTrend(startDate, endDate, visitByDate); return StatisticsItemVO.builder() .total(BigDecimal.valueOf(currentTotal)) .rate(calculateRate(currentTotal, lastTotal)) .trend(trend) .build(); } /** * 构建订单数统计 */ private StatisticsItemVO buildOrderCountStats(LocalDate startDate, LocalDate endDate, LocalDate lastPeriodStart, LocalDate lastPeriodEnd) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); LocalDateTime lastStartDateTime = lastPeriodStart.atStartOfDay(); LocalDateTime lastEndDateTime = lastPeriodEnd.atTime(LocalTime.MAX); // 当前周期订单数 long physicalCount = physicalGoodsOrderService.count(new LambdaQueryWrapper<TPhysicalGoodsOrder>() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime)); long bigGiftCount = bigGiftPackageOrderService.count(new LambdaQueryWrapper<TBigGiftPackageOrder>() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime)); long currentTotal = physicalCount + bigGiftCount; // 上一周期订单数 long lastPhysicalCount = physicalGoodsOrderService.count(new LambdaQueryWrapper<TPhysicalGoodsOrder>() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, lastStartDateTime, lastEndDateTime)); long lastBigGiftCount = bigGiftPackageOrderService.count(new LambdaQueryWrapper<TBigGiftPackageOrder>() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, lastStartDateTime, lastEndDateTime)); long lastTotal = lastPhysicalCount + lastBigGiftCount; // 趋势数据 List<TrendDataVO> trend = buildOrderCountTrend(startDate, endDate); return StatisticsItemVO.builder() .total(BigDecimal.valueOf(currentTotal)) .rate(calculateRate(currentTotal, lastTotal)) .trend(trend) .build(); } /** * 构建订单金额统计 */ private StatisticsItemVO buildOrderAmountStats(LocalDate startDate, LocalDate endDate, LocalDate lastPeriodStart, LocalDate lastPeriodEnd) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); LocalDateTime lastStartDateTime = lastPeriodStart.atStartOfDay(); LocalDateTime lastEndDateTime = lastPeriodEnd.atTime(LocalTime.MAX); // 当前周期订单金额 BigDecimal currentTotal = calculateTotalOrderAmount(startDateTime, endDateTime); // 上一周期订单金额 BigDecimal lastTotal = calculateTotalOrderAmount(lastStartDateTime, lastEndDateTime); // 趋势数据 List<TrendDataVO> trend = buildOrderAmountTrend(startDate, endDate); return StatisticsItemVO.builder() .total(currentTotal) .rate(calculateRate(currentTotal, lastTotal)) .trend(trend) .build(); } /** * 计算指定时间范围内的订单总金额 */ private BigDecimal calculateTotalOrderAmount(LocalDateTime startDateTime, LocalDateTime endDateTime) { // 实物商品订单金额 List<TPhysicalGoodsOrder> physicalOrders = physicalGoodsOrderService.list( new LambdaQueryWrapper<TPhysicalGoodsOrder>() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime) .select(TPhysicalGoodsOrder::getPaymentPrice)); BigDecimal physicalAmount = physicalOrders.stream() .map(o -> o.getPaymentPrice() != null ? o.getPaymentPrice() : BigDecimal.ZERO) .reduce(BigDecimal.ZERO, BigDecimal::add); // 大礼包订单金额 List<TBigGiftPackageOrder> bigGiftOrders = bigGiftPackageOrderService.list( new LambdaQueryWrapper<TBigGiftPackageOrder>() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime) .select(TBigGiftPackageOrder::getPaymentPrice)); BigDecimal bigGiftAmount = bigGiftOrders.stream() .map(o -> o.getPaymentPrice() != null ? o.getPaymentPrice() : BigDecimal.ZERO) .reduce(BigDecimal.ZERO, BigDecimal::add); return physicalAmount.add(bigGiftAmount); } /** * 构建月度订单统计 */ private List<MonthlyOrderStatisticsVO> buildMonthlyStats(LocalDate startDate, LocalDate endDate) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); // 获取实物商品订单 List<TPhysicalGoodsOrder> physicalOrders = physicalGoodsOrderService.list( new LambdaQueryWrapper<TPhysicalGoodsOrder>() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime)); // 获取大礼包订单 List<TBigGiftPackageOrder> bigGiftOrders = bigGiftPackageOrderService.list( new LambdaQueryWrapper<TBigGiftPackageOrder>() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime)); // 按月份分组统计 Map<String, Long> countByMonth = new TreeMap<>(); Map<String, BigDecimal> amountByMonth = new TreeMap<>(); // 统计实物商品订单 for (TPhysicalGoodsOrder order : physicalOrders) { String month = order.getCreateTime().getMonthValue() + "月"; countByMonth.merge(month, 1L, Long::sum); BigDecimal amount = order.getPaymentPrice() != null ? order.getPaymentPrice() : BigDecimal.ZERO; amountByMonth.merge(month, amount, BigDecimal::add); } // 统计大礼包订单 for (TBigGiftPackageOrder order : bigGiftOrders) { String month = order.getCreateTime().getMonthValue() + "月"; countByMonth.merge(month, 1L, Long::sum); BigDecimal amount = order.getPaymentPrice() != null ? order.getPaymentPrice() : BigDecimal.ZERO; amountByMonth.merge(month, amount, BigDecimal::add); } // 构建返回结果 List<MonthlyOrderStatisticsVO> result = new ArrayList<>(); for (String month : countByMonth.keySet()) { result.add(new MonthlyOrderStatisticsVO( month, countByMonth.getOrDefault(month, 0L), amountByMonth.getOrDefault(month, BigDecimal.ZERO) )); } // 按月份数字排序 result.sort(Comparator.comparingInt(vo -> Integer.parseInt(vo.getMonth().replace("月", "")))); return result; } /** * 构建每日趋势数据 */ private List<TrendDataVO> buildDailyTrend(LocalDate startDate, LocalDate endDate, Map<LocalDate, Long> dataByDate) { List<TrendDataVO> trend = new ArrayList<>(); LocalDate current = startDate; while (!current.isAfter(endDate)) { Long value = dataByDate.getOrDefault(current, 0L); trend.add(new TrendDataVO(current.toString(), value)); current = current.plusDays(1); } return trend; } /** * 构建订单数趋势数据(优化:一次查询,内存分组) */ private List<TrendDataVO> buildOrderCountTrend(LocalDate startDate, LocalDate endDate) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); // 一次性查询所有实物商品订单 List<TPhysicalGoodsOrder> physicalOrders = physicalGoodsOrderService.list( new LambdaQueryWrapper<TPhysicalGoodsOrder>() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime) .select(TPhysicalGoodsOrder::getCreateTime)); // 一次性查询所有大礼包订单 List<TBigGiftPackageOrder> bigGiftOrders = bigGiftPackageOrderService.list( new LambdaQueryWrapper<TBigGiftPackageOrder>() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime) .select(TBigGiftPackageOrder::getCreateTime)); // 按日期分组统计 Map<LocalDate, Long> countByDate = new HashMap<>(); physicalOrders.forEach(o -> { LocalDate date = o.getCreateTime().toLocalDate(); countByDate.merge(date, 1L, Long::sum); }); bigGiftOrders.forEach(o -> { LocalDate date = o.getCreateTime().toLocalDate(); countByDate.merge(date, 1L, Long::sum); }); // 构建趋势数据 return buildDailyTrend(startDate, endDate, countByDate); } /** * 构建订单金额趋势数据(优化:一次查询,内存分组) */ private List<TrendDataVO> buildOrderAmountTrend(LocalDate startDate, LocalDate endDate) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); // 一次性查询所有实物商品订单 List<TPhysicalGoodsOrder> physicalOrders = physicalGoodsOrderService.list( new LambdaQueryWrapper<TPhysicalGoodsOrder>() .in(TPhysicalGoodsOrder::getStatus, PHYSICAL_ORDER_VALID_STATUS) .between(TPhysicalGoodsOrder::getCreateTime, startDateTime, endDateTime) .select(TPhysicalGoodsOrder::getCreateTime, TPhysicalGoodsOrder::getPaymentPrice)); // 一次性查询所有大礼包订单 List<TBigGiftPackageOrder> bigGiftOrders = bigGiftPackageOrderService.list( new LambdaQueryWrapper<TBigGiftPackageOrder>() .in(TBigGiftPackageOrder::getStatus, BIG_GIFT_ORDER_VALID_STATUS) .between(TBigGiftPackageOrder::getCreateTime, startDateTime, endDateTime) .select(TBigGiftPackageOrder::getCreateTime, TBigGiftPackageOrder::getPaymentPrice)); // 按日期分组统计金额 Map<LocalDate, BigDecimal> amountByDate = new HashMap<>(); physicalOrders.forEach(o -> { LocalDate date = o.getCreateTime().toLocalDate(); BigDecimal amount = o.getPaymentPrice() != null ? o.getPaymentPrice() : BigDecimal.ZERO; amountByDate.merge(date, amount, BigDecimal::add); }); bigGiftOrders.forEach(o -> { LocalDate date = o.getCreateTime().toLocalDate(); BigDecimal amount = o.getPaymentPrice() != null ? o.getPaymentPrice() : BigDecimal.ZERO; amountByDate.merge(date, amount, BigDecimal::add); }); // 构建趋势数据 List<TrendDataVO> trend = new ArrayList<>(); LocalDate current = startDate; while (!current.isAfter(endDate)) { BigDecimal value = amountByDate.getOrDefault(current, BigDecimal.ZERO); trend.add(new TrendDataVO(current.toString(), value)); current = current.plusDays(1); } return trend; } /** * 计算环比增长率 * * @param current 当前值 * @param last 上期值 * @return 环比增长率(百分比) */ private BigDecimal calculateRate(long current, long last) { if (last == 0) { return current > 0 ? BigDecimal.valueOf(100) : BigDecimal.ZERO; } return BigDecimal.valueOf(current - last) .multiply(BigDecimal.valueOf(100)) .divide(BigDecimal.valueOf(last), 2, RoundingMode.HALF_UP); } /** * 计算环比增长率(BigDecimal版本) */ private BigDecimal calculateRate(BigDecimal current, BigDecimal last) { if (last.compareTo(BigDecimal.ZERO) == 0) { return current.compareTo(BigDecimal.ZERO) > 0 ? BigDecimal.valueOf(100) : BigDecimal.ZERO; } return current.subtract(last) .multiply(BigDecimal.valueOf(100)) .divide(last, 2, RoundingMode.HALF_UP); } } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ICBCNotifyServiceImpl.java
@@ -119,7 +119,7 @@ String finalPayOrderCode = bizContent.getString("out_trade_no"); String returnCode = bizContent.getString("return_code"); msgId = bizContent.getString("msg_id"); String orderId = bizContent.getString("order_id"); // 验签 if (validateSignature(request, icbcConfig.getNotifyUrlMethod() + "/big-gift/final-pay-notify")) { signResult = true; ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TAppUserVisitServiceImpl.java
@@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import java.time.LocalDate; /** * <p> * 用户访问记录 服务实现类 @@ -17,4 +19,18 @@ @Service public class TAppUserVisitServiceImpl extends ServiceImpl<TAppUserVisitMapper, TAppUserVisit> implements TAppUserVisitService { @Override public void recordVisit(Integer userId) { LocalDate today = LocalDate.now(); Long count = this.lambdaQuery() .eq(TAppUserVisit::getAppUserId, userId) .eq(TAppUserVisit::getVisitDate, today) .count(); if (count == 0) { TAppUserVisit visit = new TAppUserVisit(); visit.setAppUserId(userId); visit.setVisitDate(today); this.save(visit); } } } ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TIntegralSettlementServiceImpl.java
@@ -8,6 +8,7 @@ import com.google.common.collect.Sets; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.system.dto.TIntegralSettlementDTO; import com.ruoyi.system.export.IntegralSettlementImport; import com.ruoyi.system.model.TAppUser; @@ -68,8 +69,12 @@ Map<String, Integer> allAppUserIdMap = getAllAppUserIdMap(list); List<TIntegralSettlement> saveList = list.stream().map(item -> { TIntegralSettlement model = new TIntegralSettlement(); model.setSettlementInterval(dto.getSettlementInterval()); model.setSettlementStartTime(dto.getSettlementStartTime()); model.setSettlementEndTime(dto.getSettlementEndTime()); model.setAppUserId(allAppUserIdMap.get(item.getCode())); model.setTotalSettlementIntegral(item.getTotalSettlementIntegral()); model.setCreateUserId(SecurityUtils.getUserId().intValue()); model.setRemark(item.getRemark()); return model; }).collect(Collectors.toList()); @@ -102,7 +107,7 @@ TIntegralSettlementVO tIntegralSettlementVO = BeanUtil.copyProperties(integralSettlement, TIntegralSettlementVO.class); if (Objects.nonNull(integralSettlement)) { List<TIntegralSettlement> list = this.lambdaQuery().eq(TIntegralSettlement::getSettlementInterval, integralSettlement.getSettlementInterval()).list(); BigDecimal total = list.stream().map(TIntegralSettlement::getTotalSettlementIntegral).reduce(BigDecimal.ZERO, BigDecimal::add); Integer total = list.stream().mapToInt(TIntegralSettlement::getTotalSettlementIntegral).sum(); tIntegralSettlementVO.setTotalSettlementIntegral(total); SysUser sysUser = sysUserService.selectUserById(Long.valueOf(integralSettlement.getCreateUserId())); tIntegralSettlementVO.setCreateUserName(Objects.nonNull(sysUser) ? sysUser.getNickName() : ""); @@ -112,6 +117,11 @@ @Override public Page<TIntegralSettlementDetailVO> getSettlementDetailPageList(TIntegralSettlementDetailQuery query) { TIntegralSettlement integralSettlement = getById(query.getId()); if (Objects.isNull(integralSettlement)) { throw new ServiceException("积分结算记录不存在"); } query.setSettlementInterval(integralSettlement.getSettlementInterval()); Page<TIntegralSettlementDetailVO> page = new Page<>(query.getPageNum(), query.getPageSize()); page.setRecords(baseMapper.getSettlementDetailPageList(page, query)); return page; ruoyi-system/src/main/java/com/ruoyi/system/vo/DashboardStatisticsVO.java
New file @@ -0,0 +1,40 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.List; /** * 数据统计面板返回对象 * * @author xiaochen * @since 2025-12-23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "数据统计面板返回对象") public class DashboardStatisticsVO implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "客户访问量统计") private StatisticsItemVO visitStats; @ApiModelProperty(value = "订单数统计") private StatisticsItemVO orderCountStats; @ApiModelProperty(value = "订单金额统计") private StatisticsItemVO orderAmountStats; @ApiModelProperty(value = "月度订单统计") private List<MonthlyOrderStatisticsVO> monthlyStats; } ruoyi-system/src/main/java/com/ruoyi/system/vo/MonthlyOrderStatisticsVO.java
New file @@ -0,0 +1,35 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; /** * 月度订单统计 * * @author xiaochen * @since 2025-12-23 */ @Data @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "月度订单统计") public class MonthlyOrderStatisticsVO implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "月份") private String month; @ApiModelProperty(value = "订单数") private Long orderCount; @ApiModelProperty(value = "订单金额") private BigDecimal orderAmount; } ruoyi-system/src/main/java/com/ruoyi/system/vo/StatisticsItemVO.java
New file @@ -0,0 +1,38 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; import java.util.List; /** * 统计项(总量+环比+趋势) * * @author xiaochen * @since 2025-12-23 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "统计项") public class StatisticsItemVO implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "总量") private BigDecimal total; @ApiModelProperty(value = "环比增长率(百分比)") private BigDecimal rate; @ApiModelProperty(value = "趋势数据") private List<TrendDataVO> trend; } ruoyi-system/src/main/java/com/ruoyi/system/vo/TIntegralSettlementDetailVO.java
@@ -30,7 +30,7 @@ private String code; @ApiModelProperty(value = "结算积分") private BigDecimal totalSettlementIntegral; private Integer totalSettlementIntegral; @ApiModelProperty("备注") private String remark; ruoyi-system/src/main/java/com/ruoyi/system/vo/TIntegralSettlementVO.java
@@ -38,6 +38,6 @@ private LocalDateTime createTime; @ApiModelProperty(value = "合计结算积分") private BigDecimal totalSettlementIntegral; private Integer totalSettlementIntegral; } ruoyi-system/src/main/java/com/ruoyi/system/vo/TrendDataVO.java
New file @@ -0,0 +1,37 @@ package com.ruoyi.system.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; /** * 趋势数据点 * * @author xiaochen * @since 2025-12-23 */ @Data @NoArgsConstructor @AllArgsConstructor @ApiModel(value = "趋势数据点") public class TrendDataVO implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "日期") private String date; @ApiModelProperty(value = "数值") private BigDecimal value; public TrendDataVO(String date, Long value) { this.date = date; this.value = value != null ? BigDecimal.valueOf(value) : BigDecimal.ZERO; } } ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -23,6 +23,8 @@ <result property="updateBy" column="update_by" /> <result property="updateTime" column="update_time" /> <result property="remark" column="remark" /> <result property="companyId" column="company_id" /> <result property="roleType" column="role_type" /> <association property="dept" javaType="SysDept" resultMap="deptResult" /> <collection property="roles" javaType="java.util.List" resultMap="RoleResult" /> </resultMap> @@ -50,7 +52,7 @@ select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,u.company_id,role_type from sys_user u left join sys_dept d on u.dept_id = d.dept_id left join sys_user_role ur on u.user_id = ur.user_id ruoyi-system/src/main/resources/mapper/system/TIntegralSettlementMapper.xml
@@ -35,11 +35,11 @@ LEFT JOIN sys_user su ON t.create_user_id = su.user_id <where> <if test="query.settlementInterval != null"> AND t.settlement_interval = #{query.settlementInterval} AND t.settlement_interval LIKE CONCAT('%', #{query.settlementInterval},'%') </if> </where> GROUP BY t.settlement_interval, t.create_user_id, su.nick_name ORDER BY t.settlement_interval DESC, MAX(t.create_time) DESC ORDER BY MAX(t.create_time) DESC </select> <select id="getSettlementDetailPageList" resultType="com.ruoyi.system.vo.TIntegralSettlementDetailVO"> @@ -53,6 +53,9 @@ FROM t_integral_settlement t LEFT JOIN t_app_user tau ON tau.id = t.app_user_id <where> <if test="query.settlementInterval != null and query.settlementInterval != ''"> AND t.settlement_interval = #{query.settlementInterval} </if> <if test="query.name != null and query.name != ''"> AND tau.name LIKE CONCAT('%', #{query.name}, '%') </if>