jiaxing.zhou

Merge branch 'dev'

# Conflicts:
#	frontend/components.d.ts
package com.apple.erp.controller;
import com.apple.erp.dto.request.RebateAddReq;
import com.apple.erp.dto.request.RebateQueryReq;
import com.apple.erp.dto.request.RebateUpdateReq;
import com.apple.erp.dto.response.ApiRes;
import com.apple.erp.dto.response.RebateRes;
import com.apple.erp.entity.Rebate;
import com.apple.erp.service.RebateService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* 返利台账明细管理控制器
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Slf4j
@Tag(name = "返利台账明细管理")
@RestController
@RequestMapping("/api/rebate")
@Validated
public class RebateController {
@Autowired
private RebateService rebateService;
@Operation(summary = "分页查询返利明细列表")
@PostMapping("/page")
public ApiRes<IPage<RebateRes>> getRebatePage(@Valid @RequestBody RebateQueryReq queryReq) {
log.info("分页查询返利明细列表,参数:{}", queryReq);
try {
IPage<RebateRes> result = rebateService.getRebatePage(queryReq);
return ApiRes.success(result);
} catch (Exception e) {
log.error("分页查询返利明细列表失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "根据ID查询返利详情")
@GetMapping("/{rebateId}")
public ApiRes<RebateRes> getRebateById(@PathVariable Long rebateId) {
log.info("查询返利详情,ID:{}", rebateId);
try {
RebateRes result = rebateService.getRebateById(rebateId);
if (result == null) {
return ApiRes.error("返利记录不存在");
}
return ApiRes.success(result);
} catch (Exception e) {
log.error("查询返利详情失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "新增返利")
@PostMapping("/add")
public ApiRes<Void> addRebate(@Valid @RequestBody RebateAddReq addReq) {
log.info("新增返利,参数:{}", addReq);
try {
boolean result = rebateService.addRebate(addReq);
return result ? ApiRes.success("操作成功", null) : ApiRes.error("新增返利失败");
} catch (Exception e) {
log.error("新增返利失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "更新返利")
@PostMapping("/update")
public ApiRes<Void> updateRebate(@Valid @RequestBody RebateUpdateReq updateReq) {
log.info("更新返利,参数:{}", updateReq);
try {
boolean result = rebateService.updateRebate(updateReq);
return result ? ApiRes.success("操作成功", null) : ApiRes.error("更新返利失败");
} catch (Exception e) {
log.error("更新返利失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "删除返利")
@DeleteMapping("/{rebateId}")
public ApiRes<Void> deleteRebate(@PathVariable Long rebateId) {
log.info("删除返利,ID:{}", rebateId);
try {
boolean result = rebateService.deleteRebate(rebateId);
return result ? ApiRes.success("操作成功", null) : ApiRes.error("删除返利失败");
} catch (Exception e) {
log.error("删除返利失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "批量删除返利")
@PostMapping("/batchDelete")
public ApiRes<Void> batchDeleteRebate(@RequestBody List<Long> rebateIds) {
log.info("批量删除返利,IDs:{}", rebateIds);
try {
boolean result = rebateService.batchDeleteRebate(rebateIds);
return result ? ApiRes.success("操作成功", null) : ApiRes.error("批量删除返利失败");
} catch (Exception e) {
log.error("批量删除返利失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "标记返利为已计算")
@PostMapping("/markCalculated/{rebateId}")
public ApiRes<Void> markRebateCalculated(@PathVariable Long rebateId) {
log.info("标记返利为已计算,ID:{}", rebateId);
try {
Rebate rebate = rebateService.getById(rebateId);
if (rebate == null) {
return ApiRes.error("返利记录不存在");
}
rebate.setCalcFlag(1);
boolean result = rebateService.updateById(rebate);
return result ? ApiRes.success("操作成功", null) : ApiRes.error("标记失败");
} catch (Exception e) {
log.error("标记返利计算状态失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "批量标记返利为已计算")
@PostMapping("/batchMarkCalculated")
public ApiRes<Void> batchMarkRebateCalculated(@RequestBody List<Long> rebateIds) {
log.info("批量标记返利为已计算,IDs:{}", rebateIds);
try {
List<Rebate> rebates = rebateService.listByIds(rebateIds);
rebates.forEach(rebate -> rebate.setCalcFlag(1));
boolean result = rebateService.updateBatchById(rebates);
return result ? ApiRes.success("操作成功", null) : ApiRes.error("批量标记失败");
} catch (Exception e) {
log.error("批量标记返利计算状态失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "根据经销商编码统计返利总金额")
@GetMapping("/sumByDealer/{dealerCode}")
public ApiRes<BigDecimal> sumRebateAmountByDealer(@PathVariable String dealerCode,
@RequestParam(required = false) Integer operateType) {
log.info("统计经销商返利总金额,经销商编码:{},操作类型:{}", dealerCode, operateType);
try {
BigDecimal result = rebateService.sumRebateAmountByDealer(dealerCode, operateType);
return ApiRes.success(result);
} catch (Exception e) {
log.error("统计经销商返利总金额失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "根据产品编码统计返利总金额")
@GetMapping("/sumByProduct/{productCode}")
public ApiRes<BigDecimal> sumRebateAmountByProduct(@PathVariable String productCode,
@RequestParam(required = false) Integer operateType) {
log.info("统计产品返利总金额,产品编码:{},操作类型:{}", productCode, operateType);
try {
BigDecimal result = rebateService.sumRebateAmountByProduct(productCode, operateType);
return ApiRes.success(result);
} catch (Exception e) {
log.error("统计产品返利总金额失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "统计各操作类型的返利数量")
@GetMapping("/countByOperateType")
public ApiRes<List<Map<String, Object>>> countRebateByOperateType() {
log.info("统计各操作类型的返利数量");
try {
List<Map<String, Object>> result = rebateService.countRebateByOperateType();
return ApiRes.success(result);
} catch (Exception e) {
log.error("统计各操作类型的返利数量失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "查询未计算的返利明细列表")
@GetMapping("/unCalc")
public ApiRes<List<RebateRes>> getUnCalcRebates() {
log.info("查询未计算的返利明细列表");
try {
List<RebateRes> result = rebateService.getUnCalcRebates();
return ApiRes.success(result);
} catch (Exception e) {
log.error("查询未计算的返利明细列表失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "根据返利编号查询返利")
@GetMapping("/byNo/{rebateNo}")
public ApiRes<RebateRes> getRebateByNo(@PathVariable String rebateNo) {
log.info("根据返利编号查询返利,编号:{}", rebateNo);
try {
RebateRes result = rebateService.getRebateByNo(rebateNo);
if (result == null) {
return ApiRes.error("返利记录不存在");
}
return ApiRes.success(result);
} catch (Exception e) {
log.error("根据返利编号查询返利失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "生成返利编号")
@GetMapping("/generateNo")
public ApiRes<String> generateRebateNo() {
log.info("生成返利编号");
try {
String result = rebateService.generateRebateNo();
return ApiRes.success(result);
} catch (Exception e) {
log.error("生成返利编号失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "获取返利月度统计数据")
@GetMapping("/monthlyStats")
public ApiRes<List<Map<String, Object>>> getRebateMonthlyStats(@RequestParam(required = false) Integer year) {
log.info("获取返利月度统计数据,年份:{}", year);
try {
if (year == null) {
year = java.time.LocalDate.now().getYear();
}
List<Map<String, Object>> result = rebateService.getRebateMonthlyStats(year);
return ApiRes.success(result);
} catch (Exception e) {
log.error("获取返利月度统计数据失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "获取返利状态统计数据")
@GetMapping("/statusStats")
public ApiRes<Map<String, Object>> getRebateStatusStats() {
log.info("获取返利状态统计数据");
try {
Map<String, Object> result = rebateService.getRebateStatusStats();
return ApiRes.success(result);
} catch (Exception e) {
log.error("获取返利状态统计数据失败", e);
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "获取返利趋势统计数据")
@GetMapping("/trendStats")
public ApiRes<List<Map<String, Object>>> getRebateTrendStats(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
log.info("获取返利趋势统计数据,开始日期:{},结束日期:{}", startDate, endDate);
try {
List<Map<String, Object>> result = rebateService.getRebateTrendStats(startDate, endDate);
return ApiRes.success(result);
} catch (Exception e) {
log.error("获取返利趋势统计数据失败", e);
return ApiRes.error(e.getMessage());
}
}
}
package com.apple.erp.dto.request;
import lombok.Data;
import javax.validation.constraints.*;
import java.math.BigDecimal;
/**
* 返利新增请求DTO
*
* @author Apple ERP
*/
@Data
public class RebateAddReq {
/**
* 返利明细编号
*/
@Size(max = 30, message = "返利明细编号不能超过30个字符")
private String rebateNo;
/**
* 关联订单编号
*/
@NotBlank(message = "关联订单编号不能为空")
@Size(max = 30, message = "订单编号不能超过30个字符")
private String orderNo;
/**
* 经销商编码
*/
@NotBlank(message = "经销商编码不能为空")
@Size(max = 20, message = "经销商编码不能超过20个字符")
private String dealerCode;
/**
* 经销商名称
*/
@NotBlank(message = "经销商名称不能为空")
@Size(max = 100, message = "经销商名称不能超过100个字符")
private String dealerName;
/**
* 产品编码
*/
@NotBlank(message = "产品编码不能为空")
@Size(max = 30, message = "产品编码不能超过30个字符")
private String productCode;
/**
* 返利金额
*/
@NotNull(message = "返利金额不能为空")
@DecimalMin(value = "0", message = "返利金额不能小于0")
private BigDecimal rebateAmount;
/**
* 返利记录日期
*/
@NotBlank(message = "返利记录日期不能为空")
private String rebateDate;
/**
* 操作类型:1-新增,2-扣减
*/
@NotNull(message = "操作类型不能为空")
@Min(value = 1, message = "操作类型值无效")
@Max(value = 2, message = "操作类型值无效")
private Integer operateType;
/**
* 返利计算状态:0-未计算,1-已计算
*/
private Integer calcFlag;
}
package com.apple.erp.dto.request;
import lombok.Data;
/**
* 返利台账明细查询请求DTO
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Data
public class RebateQueryReq {
/**
* 返利明细编号
*/
private String rebateNo;
/**
* 关联订单编号
*/
private String orderNo;
/**
* 经销商编码
*/
private String dealerCode;
/**
* 经销商名称
*/
private String dealerName;
/**
* 产品编码
*/
private String productCode;
/**
* 操作类型:1-新增,2-扣减
*/
private Integer operateType;
/**
* 返利计算状态:0-未计算,1-已计算
*/
private Integer calcFlag;
/**
* 返利记录开始日期
*/
private String rebateStartDate;
/**
* 返利记录结束日期
*/
private String rebateEndDate;
/**
* 页码
*/
private Integer pageNum = 1;
/**
* 页面大小
*/
private Integer pageSize = 10;
}
package com.apple.erp.dto.request;
import lombok.Data;
import javax.validation.constraints.*;
import java.math.BigDecimal;
/**
* 返利更新请求DTO
*
* @author Apple ERP
*/
@Data
public class RebateUpdateReq {
/**
* 返利明细ID
*/
@NotNull(message = "返利明细ID不能为空")
private Long rebateId;
/**
* 返利明细编号
*/
@Size(max = 30, message = "返利明细编号不能超过30个字符")
private String rebateNo;
/**
* 关联订单编号
*/
@NotBlank(message = "关联订单编号不能为空")
@Size(max = 30, message = "订单编号不能超过30个字符")
private String orderNo;
/**
* 经销商编码
*/
@NotBlank(message = "经销商编码不能为空")
@Size(max = 20, message = "经销商编码不能超过20个字符")
private String dealerCode;
/**
* 经销商名称
*/
@NotBlank(message = "经销商名称不能为空")
@Size(max = 100, message = "经销商名称不能超过100个字符")
private String dealerName;
/**
* 产品编码
*/
@NotBlank(message = "产品编码不能为空")
@Size(max = 30, message = "产品编码不能超过30个字符")
private String productCode;
/**
* 返利金额
*/
@NotNull(message = "返利金额不能为空")
@DecimalMin(value = "0", message = "返利金额不能小于0")
private BigDecimal rebateAmount;
/**
* 返利记录日期
*/
@NotBlank(message = "返利记录日期不能为空")
private String rebateDate;
/**
* 操作类型:1-新增,2-扣减
*/
@NotNull(message = "操作类型不能为空")
@Min(value = 1, message = "操作类型值无效")
@Max(value = 2, message = "操作类型值无效")
private Integer operateType;
/**
* 返利计算状态:0-未计算,1-已计算
*/
private Integer calcFlag;
}
package com.apple.erp.dto.response;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 返利响应DTO
*
* @author Apple ERP
*/
@Data
public class RebateRes {
/**
* 返利明细ID
*/
private Long rebateId;
/**
* 返利明细编号
*/
private String rebateNo;
/**
* 关联订单编号
*/
private String orderNo;
/**
* 经销商编码
*/
private String dealerCode;
/**
* 经销商名称
*/
private String dealerName;
/**
* 产品编码
*/
private String productCode;
/**
* 返利金额
*/
private BigDecimal rebateAmount;
/**
* 返利记录日期
*/
private LocalDate rebateDate;
/**
* 操作类型:1-新增,2-扣减
*/
private Integer operateType;
/**
* 操作类型文本
*/
private String operateTypeText;
/**
* 返利计算状态:0-未计算,1-已计算
*/
private Integer calcFlag;
/**
* 计算状态文本
*/
private String calcFlagText;
/**
* 数据上传时间
*/
private LocalDateTime uploadTime;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 创建者
*/
private String createBy;
/**
* 更新者
*/
private String updateBy;
}
package com.apple.erp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 返利实体类
*
* @author Apple ERP
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_rebate_detail")
public class Rebate {
/**
* 返利明细ID
*/
@TableId(value = "rebate_id", type = IdType.AUTO)
private Long rebateId;
/**
* 返利明细编号
*/
@TableField("rebate_no")
private String rebateNo;
/**
* 关联订单编号
*/
@TableField("order_no")
private String orderNo;
/**
* 经销商编码
*/
@TableField("dealer_code")
private String dealerCode;
/**
* 经销商名称
*/
@TableField("dealer_name")
private String dealerName;
/**
* 产品编码
*/
@TableField("product_code")
private String productCode;
/**
* 返利金额
*/
@TableField("rebate_amount")
private BigDecimal rebateAmount;
/**
* 返利记录日期
*/
@TableField("rebate_date")
private LocalDate rebateDate;
/**
* 操作类型:1-新增,2-扣减
*/
@TableField("operate_type")
private Integer operateType;
/**
* 返利计算状态:0-未计算,1-已计算
*/
@TableField("calc_flag")
private Integer calcFlag;
/**
* 数据上传时间
*/
@TableField("upload_time")
private LocalDateTime uploadTime;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 创建者
*/
@TableField(value = "create_by", fill = FieldFill.INSERT)
private String createBy;
/**
* 更新者
*/
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 删除标志:0-存在,2-删除
*/
@TableField("del_flag")
@TableLogic(value = "0", delval = "2")
private String delFlag;
}
package com.apple.erp.mapper;
import com.apple.erp.entity.Rebate;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* 返利台账明细Mapper接口
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Mapper
public interface RebateMapper extends BaseMapper<Rebate> {
/**
* 分页查询返利明细列表
*
* @param page 分页参数
* @param params 查询参数
* @return 返利明细分页列表
*/
IPage<Rebate> selectRebatePage(Page<Rebate> page, @Param("params") Map<String, Object> params);
/**
* 根据经销商编码统计返利总金额
*
* @param dealerCode 经销商编码
* @param operateType 操作类型
* @return 返利总金额
*/
BigDecimal sumRebateAmountByDealer(@Param("dealerCode") String dealerCode, @Param("operateType") Integer operateType);
/**
* 根据产品编码统计返利总金额
*
* @param productCode 产品编码
* @param operateType 操作类型
* @return 返利总金额
*/
BigDecimal sumRebateAmountByProduct(@Param("productCode") String productCode, @Param("operateType") Integer operateType);
/**
* 统计各操作类型的返利数量
*
* @return 操作类型统计
*/
List<Map<String, Object>> countRebateByOperateType();
/**
* 查询未计算的返利明细列表
*
* @return 未计算返利明细列表
*/
List<Rebate> selectUnCalcRebates();
/**
* 根据返利编号查询返利明细
*
* @param rebateNo 返利编号
* @return 返利明细信息
*/
Rebate selectByRebateNo(@Param("rebateNo") String rebateNo);
/**
* 获取返利月度统计数据
*
* @param year 年份
* @return 月度统计数据
*/
List<Map<String, Object>> getRebateMonthlyStats(@Param("year") Integer year);
/**
* 获取返利状态统计数据
*
* @return 状态统计数据
*/
Map<String, Object> getRebateStatusStats();
/**
* 获取返利趋势统计数据
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 趋势统计数据
*/
List<Map<String, Object>> getRebateTrendStats(@Param("startDate") String startDate, @Param("endDate") String endDate);
}
package com.apple.erp.service;
import com.apple.erp.dto.request.RebateAddReq;
import com.apple.erp.dto.request.RebateQueryReq;
import com.apple.erp.dto.request.RebateUpdateReq;
import com.apple.erp.dto.response.RebateRes;
import com.apple.erp.entity.Rebate;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* 返利台账明细服务接口
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
public interface RebateService extends IService<Rebate> {
/**
* 分页查询返利明细列表
*
* @param queryReq 查询请求
* @return 返利明细分页列表
*/
IPage<RebateRes> getRebatePage(RebateQueryReq queryReq);
/**
* 根据ID查询返利明细详情
*
* @param rebateId 返利明细ID
* @return 返利明细详情
*/
RebateRes getRebateById(Long rebateId);
/**
* 新增返利明细
*
* @param addReq 新增请求
* @return 是否成功
*/
boolean addRebate(RebateAddReq addReq);
/**
* 更新返利明细
*
* @param updateReq 更新请求
* @return 是否成功
*/
boolean updateRebate(RebateUpdateReq updateReq);
/**
* 删除返利明细
*
* @param rebateId 返利明细ID
* @return 是否成功
*/
boolean deleteRebate(Long rebateId);
/**
* 批量删除返利明细
*
* @param rebateIds 返利明细ID列表
* @return 是否成功
*/
boolean batchDeleteRebate(List<Long> rebateIds);
/**
* 根据经销商编码统计返利总金额
*
* @param dealerCode 经销商编码
* @param operateType 操作类型(可选)
* @return 返利总金额
*/
BigDecimal sumRebateAmountByDealer(String dealerCode, Integer operateType);
/**
* 根据产品编码统计返利总金额
*
* @param productCode 产品编码
* @param operateType 操作类型(可选)
* @return 返利总金额
*/
BigDecimal sumRebateAmountByProduct(String productCode, Integer operateType);
/**
* 统计各操作类型的返利数量
*
* @return 操作类型统计
*/
List<Map<String, Object>> countRebateByOperateType();
/**
* 查询未计算的返利明细列表
*
* @return 未计算返利明细列表
*/
List<RebateRes> getUnCalcRebates();
/**
* 根据返利编号查询返利明细
*
* @param rebateNo 返利编号
* @return 返利明细信息
*/
RebateRes getRebateByNo(String rebateNo);
/**
* 生成返利编号
*
* @return 返利编号
*/
String generateRebateNo();
/**
* 获取返利月度统计数据
*
* @param year 年份
* @return 月度统计数据
*/
List<Map<String, Object>> getRebateMonthlyStats(Integer year);
/**
* 获取返利状态统计数据
*
* @return 状态统计数据
*/
Map<String, Object> getRebateStatusStats();
/**
* 获取返利趋势统计数据
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 趋势统计数据
*/
List<Map<String, Object>> getRebateTrendStats(String startDate, String endDate);
}
package com.apple.erp.service.impl;
import com.apple.erp.dto.request.RebateAddReq;
import com.apple.erp.dto.request.RebateQueryReq;
import com.apple.erp.dto.request.RebateUpdateReq;
import com.apple.erp.dto.response.RebateRes;
import com.apple.erp.entity.Rebate;
import com.apple.erp.mapper.RebateMapper;
import com.apple.erp.service.RebateService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* 返利台账明细服务实现类
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Slf4j
@Service
public class RebateServiceImpl extends ServiceImpl<RebateMapper, Rebate> implements RebateService {
@Autowired
private RebateMapper rebateMapper;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Override
public IPage<RebateRes> getRebatePage(RebateQueryReq queryReq) {
// 构建查询参数
Map<String, Object> params = buildQueryParams(queryReq);
// 创建分页对象
Page<Rebate> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize());
// 分页查询
IPage<Rebate> rebatePage = rebateMapper.selectRebatePage(page, params);
// 转换为响应DTO
Page<RebateRes> resultPage = new Page<>(rebatePage.getCurrent(), rebatePage.getSize(), rebatePage.getTotal());
List<RebateRes> rebateResList = rebatePage.getRecords().stream()
.map(this::convertToRebateRes)
.collect(Collectors.toList());
resultPage.setRecords(rebateResList);
return resultPage;
}
@Override
public RebateRes getRebateById(Long rebateId) {
Rebate rebate = getById(rebateId);
return rebate != null ? convertToRebateRes(rebate) : null;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addRebate(RebateAddReq addReq) {
Rebate rebate = new Rebate();
BeanUtils.copyProperties(addReq, rebate);
// 设置返利编号(如果未提供)
if (addReq.getRebateNo() == null || addReq.getRebateNo().isEmpty()) {
rebate.setRebateNo(generateRebateNo());
}
// 转换日期字符串
if (addReq.getRebateDate() != null && !addReq.getRebateDate().isEmpty()) {
rebate.setRebateDate(LocalDate.parse(addReq.getRebateDate(), DATE_FORMATTER));
}
// 设置上传时间
rebate.setUploadTime(LocalDateTime.now());
// 默认计算状态为未计算
if (rebate.getCalcFlag() == null) {
rebate.setCalcFlag(0);
}
return save(rebate);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateRebate(RebateUpdateReq updateReq) {
Rebate rebate = getById(updateReq.getRebateId());
if (rebate == null) {
throw new RuntimeException("返利记录不存在");
}
BeanUtils.copyProperties(updateReq, rebate);
// 转换日期字符串
if (updateReq.getRebateDate() != null && !updateReq.getRebateDate().isEmpty()) {
rebate.setRebateDate(LocalDate.parse(updateReq.getRebateDate(), DATE_FORMATTER));
}
return updateById(rebate);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteRebate(Long rebateId) {
return removeById(rebateId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean batchDeleteRebate(List<Long> rebateIds) {
return removeByIds(rebateIds);
}
@Override
public BigDecimal sumRebateAmountByDealer(String dealerCode, Integer operateType) {
BigDecimal amount = rebateMapper.sumRebateAmountByDealer(dealerCode, operateType);
return amount != null ? amount : BigDecimal.ZERO;
}
@Override
public BigDecimal sumRebateAmountByProduct(String productCode, Integer operateType) {
BigDecimal amount = rebateMapper.sumRebateAmountByProduct(productCode, operateType);
return amount != null ? amount : BigDecimal.ZERO;
}
@Override
public List<Map<String, Object>> countRebateByOperateType() {
return rebateMapper.countRebateByOperateType();
}
@Override
public List<RebateRes> getUnCalcRebates() {
List<Rebate> rebates = rebateMapper.selectUnCalcRebates();
return rebates.stream()
.map(this::convertToRebateRes)
.collect(Collectors.toList());
}
@Override
public RebateRes getRebateByNo(String rebateNo) {
Rebate rebate = rebateMapper.selectByRebateNo(rebateNo);
return rebate != null ? convertToRebateRes(rebate) : null;
}
@Override
public String generateRebateNo() {
String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String prefix = "RB" + dateStr;
// 查询当天已有的返利编号数量
LambdaQueryWrapper<Rebate> wrapper = new LambdaQueryWrapper<>();
wrapper.likeRight(Rebate::getRebateNo, prefix);
long count = count(wrapper);
// 生成4位序号
String sequence = String.format("%04d", count + 1);
return prefix + sequence;
}
/**
* 构建查询参数
*/
private Map<String, Object> buildQueryParams(RebateQueryReq queryReq) {
Map<String, Object> params = new HashMap<>();
if (queryReq.getRebateNo() != null && !queryReq.getRebateNo().isEmpty()) {
params.put("rebateNo", queryReq.getRebateNo());
}
if (queryReq.getOrderNo() != null && !queryReq.getOrderNo().isEmpty()) {
params.put("orderNo", queryReq.getOrderNo());
}
if (queryReq.getDealerCode() != null && !queryReq.getDealerCode().isEmpty()) {
params.put("dealerCode", queryReq.getDealerCode());
}
if (queryReq.getDealerName() != null && !queryReq.getDealerName().isEmpty()) {
params.put("dealerName", queryReq.getDealerName());
}
if (queryReq.getProductCode() != null && !queryReq.getProductCode().isEmpty()) {
params.put("productCode", queryReq.getProductCode());
}
if (queryReq.getOperateType() != null) {
params.put("operateType", queryReq.getOperateType());
}
if (queryReq.getCalcFlag() != null) {
params.put("calcFlag", queryReq.getCalcFlag());
}
if (queryReq.getRebateStartDate() != null && !queryReq.getRebateStartDate().isEmpty()) {
params.put("rebateStartDate", queryReq.getRebateStartDate());
}
if (queryReq.getRebateEndDate() != null && !queryReq.getRebateEndDate().isEmpty()) {
params.put("rebateEndDate", queryReq.getRebateEndDate());
}
return params;
}
/**
* 转换为响应DTO
*/
private RebateRes convertToRebateRes(Rebate rebate) {
RebateRes rebateRes = new RebateRes();
BeanUtils.copyProperties(rebate, rebateRes);
// 设置操作类型文本
rebateRes.setOperateTypeText(getOperateTypeText(rebate.getOperateType()));
// 设置计算状态文本
rebateRes.setCalcFlagText(getCalcFlagText(rebate.getCalcFlag()));
return rebateRes;
}
/**
* 获取操作类型文本
*/
private String getOperateTypeText(Integer operateType) {
if (operateType == null) {
return "";
}
switch (operateType) {
case 1:
return "新增";
case 2:
return "扣减";
default:
return "未知";
}
}
/**
* 获取计算状态文本
*/
private String getCalcFlagText(Integer calcFlag) {
if (calcFlag == null) {
return "";
}
switch (calcFlag) {
case 0:
return "未计算";
case 1:
return "已计算";
default:
return "未知";
}
}
@Override
public List<Map<String, Object>> getRebateMonthlyStats(Integer year) {
log.info("获取返利月度统计数据,年份:{}", year);
return rebateMapper.getRebateMonthlyStats(year);
}
@Override
public Map<String, Object> getRebateStatusStats() {
log.info("获取返利状态统计数据");
return rebateMapper.getRebateStatusStats();
}
@Override
public List<Map<String, Object>> getRebateTrendStats(String startDate, String endDate) {
log.info("获取返利趋势统计数据,开始日期:{},结束日期:{}", startDate, endDate);
return rebateMapper.getRebateTrendStats(startDate, endDate);
}
}
......@@ -17,8 +17,8 @@ spring:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: MySQL@123
username: apple_erp_app
password: StrongPassw0rd!
druid:
# 初始连接数
initial-size: 5
......@@ -70,7 +70,7 @@ spring:
redis:
host: localhost
port: 6379
password:
password: 123456
database: 0
timeout: 10000ms
lettuce:
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.apple.erp.mapper.RebateMapper">
<!-- 返利明细基础字段 -->
<sql id="baseColumns">
rebate_id, rebate_no, order_no, dealer_code, dealer_name, product_code,
rebate_amount, rebate_date, operate_type, calc_flag, upload_time,
create_by, create_time, update_by, update_time
</sql>
<!-- 分页查询返利明细列表 -->
<select id="selectRebatePage" resultType="com.apple.erp.entity.Rebate">
SELECT
<include refid="baseColumns"/>
FROM t_rebate_detail
WHERE del_flag = '0'
<if test="params.rebateNo != null and params.rebateNo != ''">
AND rebate_no LIKE CONCAT('%', #{params.rebateNo}, '%')
</if>
<if test="params.orderNo != null and params.orderNo != ''">
AND order_no LIKE CONCAT('%', #{params.orderNo}, '%')
</if>
<if test="params.dealerCode != null and params.dealerCode != ''">
AND dealer_code = #{params.dealerCode}
</if>
<if test="params.dealerName != null and params.dealerName != ''">
AND dealer_name LIKE CONCAT('%', #{params.dealerName}, '%')
</if>
<if test="params.productCode != null and params.productCode != ''">
AND product_code = #{params.productCode}
</if>
<if test="params.operateType != null">
AND operate_type = #{params.operateType}
</if>
<if test="params.calcFlag != null">
AND calc_flag = #{params.calcFlag}
</if>
<if test="params.rebateStartDate != null and params.rebateStartDate != ''">
AND rebate_date &gt;= #{params.rebateStartDate}
</if>
<if test="params.rebateEndDate != null and params.rebateEndDate != ''">
AND rebate_date &lt;= #{params.rebateEndDate}
</if>
ORDER BY create_time DESC
</select>
<!-- 根据经销商编码统计返利总金额 -->
<select id="sumRebateAmountByDealer" resultType="java.math.BigDecimal">
SELECT COALESCE(SUM(rebate_amount), 0)
FROM t_rebate_detail
WHERE del_flag = '0'
AND dealer_code = #{dealerCode}
<if test="operateType != null">
AND operate_type = #{operateType}
</if>
</select>
<!-- 根据产品编码统计返利总金额 -->
<select id="sumRebateAmountByProduct" resultType="java.math.BigDecimal">
SELECT COALESCE(SUM(rebate_amount), 0)
FROM t_rebate_detail
WHERE del_flag = '0'
AND product_code = #{productCode}
<if test="operateType != null">
AND operate_type = #{operateType}
</if>
</select>
<!-- 统计各操作类型的返利数量 -->
<select id="countRebateByOperateType" resultType="java.util.Map">
SELECT
operate_type,
COUNT(*) as count,
COALESCE(SUM(rebate_amount), 0) as totalAmount
FROM t_rebate_detail
WHERE del_flag = '0'
GROUP BY operate_type
</select>
<!-- 查询未计算的返利明细列表 -->
<select id="selectUnCalcRebates" resultType="com.apple.erp.entity.Rebate">
SELECT
<include refid="baseColumns"/>
FROM t_rebate_detail
WHERE del_flag = '0'
AND calc_flag = 0
ORDER BY create_time ASC
</select>
<!-- 根据返利编号查询返利明细 -->
<select id="selectByRebateNo" resultType="com.apple.erp.entity.Rebate">
SELECT
<include refid="baseColumns"/>
FROM t_rebate_detail
WHERE del_flag = '0'
AND rebate_no = #{rebateNo}
</select>
<!-- 获取返利月度统计数据 -->
<select id="getRebateMonthlyStats" resultType="java.util.Map">
SELECT
MONTH(rebate_date) as month,
COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount,
COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
ELSE 0 END), 0) as calculatedAmount,
COUNT(*) as totalCount,
COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount
FROM t_rebate_detail
WHERE del_flag = '0'
AND YEAR(rebate_date) = #{year}
GROUP BY MONTH(rebate_date)
ORDER BY month
</select>
<!-- 获取返利状态统计数据 -->
<select id="getRebateStatusStats" resultType="java.util.Map">
SELECT
COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount,
COUNT(CASE WHEN calc_flag = 0 THEN 1 END) as unCalculatedCount,
COUNT(*) as totalCount,
COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
ELSE 0 END), 0) as calculatedAmount,
COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount
FROM t_rebate_detail
WHERE del_flag = '0'
</select>
<!-- 获取返利趋势统计数据 -->
<select id="getRebateTrendStats" resultType="java.util.Map">
SELECT
DATE(rebate_date) as date,
COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount,
COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
ELSE 0 END), 0) as calculatedAmount,
COUNT(*) as totalCount,
COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount
FROM t_rebate_detail
WHERE del_flag = '0'
<if test="startDate != null and startDate != ''">
AND rebate_date >= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND rebate_date &lt;= #{endDate}
</if>
GROUP BY DATE(rebate_date)
ORDER BY date
</select>
</mapper>
......@@ -51,7 +51,8 @@
</select>
<select id="selectMenuTreeByRoleId" parameterType="Long" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.menu_type, m.status, m.perms, m.icon, m.create_by, m.create_time, m.update_by, m.update_time, m.del_flag
select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.menu_type, m.status, m.perms, m.icon, m.create_by, m.create_time, m.update_by, m.update_time, m.del_flag,
m.sort
from t_sys_menu m
left join t_sys_role_menu rm on m.menu_id = rm.menu_id
where rm.role_id = #{roleId} and m.status = 1 and m.del_flag = '0'
......
import request from '@/utils/request'
// 返利台账明细类型定义
export interface Rebate {
rebateId?: number
rebateNo?: string
orderNo: string
dealerCode: string
dealerName: string
productCode: string
rebateAmount: number
rebateDate: string
operateType: number
operateTypeText?: string
calcFlag?: number
calcFlagText?: string
uploadTime?: string
createTime?: string
updateTime?: string
createBy?: string
updateBy?: string
}
// 返利查询参数
export interface RebateSearchParams {
pageNum: number
pageSize: number
rebateNo?: string
orderNo?: string
dealerCode?: string
dealerName?: string
productCode?: string
operateType?: number
calcFlag?: number
rebateStartDate?: string
rebateEndDate?: string
}
// 返利新增参数
export interface RebateAddParams {
rebateNo?: string
orderNo: string
dealerCode: string
dealerName: string
productCode: string
rebateAmount: number
rebateDate: string
operateType: number
calcFlag?: number
}
// 返利更新参数
export interface RebateUpdateParams extends RebateAddParams {
rebateId: number
}
// 分页查询返利列表
export const getRebatePage = (params: RebateSearchParams) => {
return request({
url: '/api/rebate/page',
method: 'post',
data: params
})
}
// 根据ID查询返利详情
export const getRebateById = (rebateId: number) => {
return request({
url: `/api/rebate/${rebateId}`,
method: 'get'
})
}
// 新增返利
export const addRebate = (data: RebateAddParams) => {
return request({
url: '/api/rebate/add',
method: 'post',
data
})
}
// 更新返利
export const updateRebate = (data: RebateUpdateParams) => {
return request({
url: '/api/rebate/update',
method: 'post',
data
})
}
// 删除返利
export const deleteRebate = (rebateId: number) => {
return request({
url: `/api/rebate/${rebateId}`,
method: 'delete'
})
}
// 批量删除返利
export const batchDeleteRebate = (rebateIds: number[]) => {
return request({
url: '/api/rebate/batchDelete',
method: 'post',
data: rebateIds
})
}
// 标记返利为已计算
export const markRebateCalculated = (rebateId: number) => {
return request({
url: `/api/rebate/markCalculated/${rebateId}`,
method: 'post'
})
}
// 批量标记返利为已计算
export const batchMarkRebateCalculated = (rebateIds: number[]) => {
return request({
url: '/api/rebate/batchMarkCalculated',
method: 'post',
data: rebateIds
})
}
// 根据经销商编码统计返利总金额
export const sumRebateAmountByDealer = (dealerCode: string, operateType?: number) => {
return request({
url: `/api/rebate/sumByDealer/${dealerCode}`,
method: 'get',
params: { operateType }
})
}
// 根据产品编码统计返利总金额
export const sumRebateAmountByProduct = (productCode: string, operateType?: number) => {
return request({
url: `/api/rebate/sumByProduct/${productCode}`,
method: 'get',
params: { operateType }
})
}
// 统计各操作类型的返利数量
export const countRebateByOperateType = () => {
return request({
url: '/api/rebate/countByOperateType',
method: 'get'
})
}
// 查询未计算的返利明细列表
export const getUnCalcRebates = () => {
return request({
url: '/api/rebate/unCalc',
method: 'get'
})
}
// 根据返利编号查询返利
export const getRebateByNo = (rebateNo: string) => {
return request({
url: `/api/rebate/byNo/${rebateNo}`,
method: 'get'
})
}
// 生成返利编号
export const generateRebateNo = () => {
return request({
url: '/api/rebate/generateNo',
method: 'get'
})
}
// 导出返利数据
export const exportRebate = (params: RebateSearchParams) => {
return request({
url: '/api/rebate/export',
method: 'post',
data: params,
responseType: 'blob'
})
}
// 获取返利月度统计数据
export const getRebateMonthlyStats = (year?: number) => {
return request({
url: '/api/rebate/monthlyStats',
method: 'get',
params: { year: year || new Date().getFullYear() }
})
}
// 获取返利状态统计数据
export const getRebateStatusStats = () => {
return request({
url: '/api/rebate/statusStats',
method: 'get'
})
}
// 获取返利趋势统计数据
export const getRebateTrendStats = (startDate?: string, endDate?: string) => {
return request({
url: '/api/rebate/trendStats',
method: 'get',
params: { startDate, endDate }
})
}
export default {
getRebatePage,
getRebateById,
addRebate,
updateRebate,
deleteRebate,
batchDeleteRebate,
markRebateCalculated,
batchMarkRebateCalculated,
sumRebateAmountByDealer,
sumRebateAmountByProduct,
countRebateByOperateType,
getUnCalcRebates,
getRebateByNo,
generateRebateNo,
exportRebate,
getRebateMonthlyStats,
getRebateStatusStats,
getRebateTrendStats
}
......@@ -146,6 +146,7 @@ const menuItems = ref([
{ name: '发票管理', path: '/main/invoice', icon: '🧾' },
{ name: '产品管理', path: '/main/product', icon: '📦' },
{ name: '经销商管理', path: '/main/dealer', icon: '🏢' },
{ name: '返利管理', path: '/main/rebate', icon: '💰' },
{
name: '系统设置',
icon: '⚙️',
......
......@@ -99,24 +99,6 @@ const staticRoutes: RouteRecordRaw[] = [
}
},
{
path: 'dicts',
name: 'Dicts',
component: () => import('@/views/dicts/index.vue'),
meta: {
title: '字典管理',
requiresAuth: true
}
},
{
path: 'logs',
name: 'Logs',
component: () => import('@/views/logs/index.vue'),
meta: {
title: '日志管理',
requiresAuth: true
}
},
{
path: 'product',
name: 'Product',
component: () => import('@/views/product/index.vue'),
......@@ -162,6 +144,15 @@ const staticRoutes: RouteRecordRaw[] = [
}
},
{
path: 'rebate',
name: 'Rebate',
component: () => import('@/views/rebate/index.vue'),
meta: {
title: '返利管理',
requiresAuth: true
}
},
{
path: 'settings',
name: 'Settings',
component: () => import('@/views/settings/index.vue'),
......
......@@ -93,27 +93,27 @@
<th>出库日期</th>
<th>关联订单编号</th>
<th>出库状态</th>
<th>出库时间</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="order in orderList" :key="order.deliveryId">
<td>{{ order.deliveryId }}</td>
<td>{{ order.deliveryNo }}</td>
<td>{{ order.dealerCode }}</td>
<td>{{ order.dealerName }}</td>
<td>{{ formatDate(order.deliveryDate) }}</td>
<td>{{ order.orderNo }}</td>
<tr v-for="delivery in deliveryList" :key="delivery.deliveryId">
<td>{{ delivery.deliveryId }}</td>
<td>{{ delivery.deliveryNo }}</td>
<td>{{ delivery.dealerCode }}</td>
<td>{{ delivery.dealerName }}</td>
<td>{{ formatDate(delivery.deliveryDate) }}</td>
<td>{{ delivery.orderNo }}</td>
<td>
<span :class="getDeliveryStatusClass(order.deliveryStatus)">
{{ getDeliveryStatusText(order.deliveryStatus) }}
<span :class="getDeliveryStatusClass(delivery.deliveryStatus)">
{{ getDeliveryStatusText(delivery.deliveryStatus) }}
</span>
</td>
<td>{{ formatTime(order.uploadTime || '') }}</td>
<td>{{ formatTime(delivery.createTime || '') }}</td>
<td class="action-col">
<button @click="handleView(order)" class="view-btn">👁️ 查看</button>
<button @click="handleDelete(order)" class="delete-btn">🗑️ 删除</button>
<button @click="handleView(delivery)" class="view-btn">👁️ 查看</button>
<button @click="handleDelete(delivery)" class="delete-btn">🗑️ 删除</button>
</td>
</tr>
</tbody>
......@@ -173,44 +173,48 @@
<button @click="closeDetailDialog" class="close-btn">×</button>
</div>
<div class="dialog-body">
<div v-if="orderDetail" class="detail-content">
<div v-if="deliveryDetail" class="detail-content">
<div class="detail-section">
<h4>订单基本信息</h4>
<h4>出库基本信息</h4>
<div class="detail-grid">
<div class="detail-item">
<label>订单编号:</label>
<span>{{ orderDetail.orderNo }}</span>
<label>出库单号:</label>
<span>{{ deliveryDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
<span>{{ deliveryDetail.orderNo }}</span>
</div>
<div class="detail-item">
<label>经销商编码:</label>
<span>{{ orderDetail.dealerCode }}</span>
<span>{{ deliveryDetail.dealerCode }}</span>
</div>
<div class="detail-item">
<label>经销商名称:</label>
<span>{{ orderDetail.dealerName }}</span>
<span>{{ deliveryDetail.dealerName }}</span>
</div>
<div class="detail-item">
<label>订单日期:</label>
<span>{{ formatDate(orderDetail.orderDate) }}</span>
<label>出库日期:</label>
<span>{{ formatDate(deliveryDetail.deliveryDate) }}</span>
</div>
<div class="detail-item">
<label>订单总金额:</label>
<span>{{ formatCurrency(orderDetail.totalAmount) }}</span>
<label>出库总金额:</label>
<span>{{ formatCurrency(deliveryDetail.totalAmount) }}</span>
</div>
<div class="detail-item">
<label>出库状态:</label>
<span :class="getDeliveryStatusClass(orderDetail.deliveryStatus)">
{{ getDeliveryStatusText(orderDetail.deliveryStatus) }}
<span :class="getDeliveryStatusClass(deliveryDetail.deliveryStatus)">
{{ getDeliveryStatusText(deliveryDetail.deliveryStatus) }}
</span>
</div>
<div class="detail-item">
<label>出库时间:</label>
<span>{{ formatTime(orderDetail.uploadTime || '') }}</span>
<label>创建时间:</label>
<span>{{ formatTime(deliveryDetail.createTime || '') }}</span>
</div>
</div>
</div>
<div class="detail-section" v-if="orderDetail.orderItems && orderDetail.orderItems.length > 0">
<div class="detail-section" v-if="deliveryDetail.deliveryItems && deliveryDetail.deliveryItems.length > 0">
<h4>出库明细</h4>
<table class="detail-table">
<thead>
......@@ -218,17 +222,17 @@
<th>产品编码</th>
<th>产品名称</th>
<th>产品型号</th>
<th>数量</th>
<th>单价</th>
<th>总价</th>
<th>出库数量</th>
<th>出库单价</th>
<th>出库总价</th>
</tr>
</thead>
<tbody>
<tr v-for="item in orderDetail.orderItems" :key="item.itemId">
<tr v-for="item in deliveryDetail.deliveryItems" :key="item.itemId">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.productModel }}</td>
<td>{{ item.quantity }}</td>
<td>{{ item.productQty }}</td>
<td>{{ formatCurrency(item.unitPrice) }}</td>
<td>{{ formatCurrency(item.totalPrice) }}</td>
</tr>
......@@ -415,9 +419,9 @@ import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQuery
// 响应式数据
const loading = ref(false)
const orderList = ref<DeliveryInfo[]>([])
const deliveryList = ref<DeliveryInfo[]>([])
const showDetailDialog = ref(false)
const orderDetail = ref<DeliveryInfo | null>(null)
const deliveryDetail = ref<DeliveryInfo | null>(null)
// 新增出库相关数据
const showAddDialog = ref(false)
......@@ -435,6 +439,7 @@ const addFormData = reactive({
// 搜索参数
const searchParams = reactive({
orderNo: '',
deliveryNo: '',
dealerCode: '',
dealerName: '',
......@@ -443,6 +448,8 @@ const searchParams = reactive({
dataSource: '',
deliveryStartDate: '',
deliveryEndDate: '',
startDate: '',
endDate: '',
pageNum: 1,
pageSize: 10
})
......@@ -511,7 +518,7 @@ const getPageNumbers = () => {
}
// 方法
const fetchOrders = async () => {
const fetchDeliveries = async () => {
try {
loading.value = true
const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
......@@ -520,12 +527,12 @@ const fetchOrders = async () => {
// 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data
if (response && response.records) {
orderList.value = response.records || []
deliveryList.value = response.records || []
pagination.total = response.total || 0
pagination.pageNum = response.current || 1
pagination.pageSize = response.size || 10
} else {
orderList.value = []
deliveryList.value = []
pagination.total = 0
showMessage('获取出库列表失败', 'error')
}
......@@ -539,7 +546,7 @@ const fetchOrders = async () => {
const handleSearch = () => {
pagination.pageNum = 1
fetchOrders()
fetchDeliveries()
}
const handleReset = () => {
......@@ -552,33 +559,33 @@ const handleReset = () => {
endDate: ''
})
pagination.pageNum = 1
fetchOrders()
fetchDeliveries()
}
const handleView = async (order: DeliveryInfo) => {
const handleView = async (delivery: DeliveryInfo) => {
try {
const response = await deliveryApi.getDeliveryDetail(order.deliveryId) as any
const response = await deliveryApi.getDeliveryDetail(delivery.deliveryId) as any
console.log('出库查询详情API响应:', response)
// 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data
if (response && response.deliveryId) {
orderDetail.value = response
deliveryDetail.value = response
showDetailDialog.value = true
} else {
showMessage('获取订单详情失败', 'error')
showMessage('获取出库详情失败', 'error')
}
} catch (error) {
console.error('获取订单详情失败:', error)
showMessage('获取订单详情失败, 请重试', 'error')
console.error('获取出库详情失败:', error)
showMessage('获取出库详情失败, 请重试', 'error')
}
}
const handleDelete = async (order: DeliveryInfo) => {
if (confirm(`确定要删除出库单"${order.deliveryNo}"吗?`)) {
const handleDelete = async (delivery: DeliveryInfo) => {
if (confirm(`确定要删除出库单"${delivery.deliveryNo}"吗?`)) {
try {
await deliveryApi.deleteDelivery(order.deliveryId)
await deliveryApi.deleteDelivery(delivery.deliveryId)
showMessage('删除出库单成功', 'success')
fetchOrders() // 刷新列表
fetchDeliveries() // 刷新列表
} catch (error) {
console.error('删除出库单失败:', error)
showMessage('删除出库单失败, 请重试', 'error')
......@@ -641,7 +648,7 @@ const handleSubmitAdd = async () => {
// 将 YYYY-MM-DDTHH:MM:SS 格式转换为 YYYY-MM-DD HH:MM:SS 格式
formattedDeliveryDate = formattedDeliveryDate.replace('T', ' ')
}
// 构造新增出库的请求数据
const deliveryData: DeliveryAddReq = {
deliveryNo: addFormData.deliveryNo,
......@@ -652,19 +659,19 @@ const handleSubmitAdd = async () => {
deliveryStatus: 1, // 默认已出库
warehouseCode: addFormData.warehouseCode,
dataSource: addFormData.dataSource || '系统录入',
deliveryItems: addFormData.deliveryItems.map(item => ({
productCode: item.productCode,
productName: item.productName,
deliveryQty: item.deliveryQty,
deliveryPrice: item.deliveryPrice,
deliveryAmount: item.deliveryAmount
}))
deliveryItems: addFormData.deliveryItems.map(item => ({
productCode: item.productCode,
productName: item.productName,
productQty: item.deliveryQty,
unitPrice: item.deliveryPrice,
totalPrice: item.deliveryAmount
}))
}
await deliveryApi.addDelivery(deliveryData)
showMessage('新增出库成功', 'success')
closeAddDialog()
fetchOrders() // 刷新列表
fetchDeliveries() // 刷新列表
} catch (error) {
console.error('新增出库失败:', error)
showMessage('新增出库失败, 请重试', 'error')
......@@ -676,13 +683,13 @@ const handleSubmitAdd = async () => {
const handlePageChange = (page: number) => {
if (page >= 1 && page <= totalPages.value) {
pagination.pageNum = page
fetchOrders()
fetchDeliveries()
}
}
const handlePageSizeChange = () => {
pagination.pageNum = 1
fetchOrders()
fetchDeliveries()
}
const handleTableSearch = () => {
......@@ -690,7 +697,7 @@ const handleTableSearch = () => {
}
const handleTableRefresh = () => {
fetchOrders()
fetchDeliveries()
}
const handleTableExport = () => {
......@@ -703,12 +710,12 @@ const handleTableViewToggle = () => {
const closeDetailDialog = () => {
showDetailDialog.value = false
orderDetail.value = null
deliveryDetail.value = null
}
// 生命周期
onMounted(() => {
fetchOrders()
fetchDeliveries()
})
</script>
......
......@@ -5,9 +5,9 @@
<div class="search-row">
<div class="search-item">
<label>发票编号:</label>
<input
v-model="searchParams.invoiceNo"
class="search-input"
<input
v-model="searchParams.invoiceNo"
class="search-input"
placeholder="请输入发票编号"
/>
</div>
......@@ -48,7 +48,7 @@
<div class="search-item">
<label>开票开始日期:</label>
<input
v-model="searchParams.invoiceStartDate"
v-model="searchParams.invoiceStartDate"
class="search-input"
type="date"
/>
......@@ -56,7 +56,7 @@
<div class="search-item">
<label>开票结束日期:</label>
<input
v-model="searchParams.invoiceEndDate"
v-model="searchParams.invoiceEndDate"
class="search-input"
type="date"
/>
......
<template>
<div class="rebate-page">
<!-- 搜索区域 -->
<div class="search-section">
<div class="search-form">
<div class="search-row">
<div class="search-item">
<span class="search-label">经销商名称</span>
<el-input
v-model="searchForm.dealerName"
placeholder="请输入经销商名称"
clearable
size="small"
style="width: 160px"
/>
</div>
<div class="search-item">
<span class="search-label">返利编号</span>
<el-input
v-model="searchForm.rebateNo"
placeholder="请输入返利编号"
clearable
size="small"
style="width: 160px"
/>
</div>
<div class="search-item">
<span class="search-label">日期</span>
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
size="small"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
@change="handleDateRangeChange"
style="width: 240px"
/>
</div>
<div class="search-actions">
<el-button type="primary" size="small" @click="handleSearch" :loading="loading">
查询
</el-button>
<el-button size="small" @click="handleReset">
重置
</el-button>
</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="charts-section">
<div class="charts-container">
<!-- 左侧趋势图 -->
<div class="chart-card trend-chart">
<div class="chart-header">
<h3 class="chart-title">返利统计</h3>
<div class="chart-legend">
<div class="legend-item">
<span class="legend-dot" style="background-color: #4CAF50;"></span>
<span>返利金额</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background-color: #2196F3;"></span>
<span>已审核返利</span>
</div>
</div>
</div>
<div class="chart-content">
<div ref="trendChart" class="chart"></div>
</div>
</div>
<!-- 右侧饼图 -->
<div class="chart-card pie-chart">
<div class="chart-header">
<h3 class="chart-title">计算统计</h3>
<div class="chart-legend">
<div class="legend-item">
<span class="legend-dot" style="background-color: #2196F3;"></span>
<span>已计算</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background-color: #4CAF50;"></span>
<span>未计算</span>
</div>
</div>
</div>
<div class="chart-content">
<div ref="pieChart" class="chart"></div>
</div>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="table-title">
返利记录
</div>
<div class="table-info">
数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }}
</div>
</div>
<el-table
v-loading="loading"
:data="rebateList"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 'normal' }"
:empty-text="rebateList.length === 0 ? '暂无数据' : ''"
size="small"
>
<el-table-column prop="rebateNo" label="返利编号" width="140" align="center" />
<el-table-column prop="orderNo" label="订单编号" width="140" align="center" />
<el-table-column prop="dealerCode" label="经销商代码" width="120" align="center" />
<el-table-column prop="dealerName" label="经销商名称" min-width="150" />
<el-table-column prop="productCode" label="产品编码" width="120" align="center" />
<el-table-column prop="rebateAmount" label="返利金额" width="120" align="right">
<template #default="{ row }">
<span class="amount-text">{{ formatAmount(row.rebateAmount) }}</span>
</template>
</el-table-column>
<el-table-column prop="rebateDate" label="返利日期" width="120" align="center" />
<el-table-column prop="operateTypeText" label="操作类型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getOperateTypeTagType(row.operateType)" size="small">
{{ row.operateTypeText || getOperateTypeText(row.operateType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="calcFlagText" label="计算状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getCalcFlagTagType(row.calcFlag)" size="small">
{{ row.calcFlagText || getCalcFlagText(row.calcFlag) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="150" align="center">
<template #default="{ row }">
{{ formatDate(row.updateTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="{ row }">
<el-button type="primary" size="small" link @click="handleViewDetail(row)">
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<div class="pagination-info">
共 {{ pagination.total }} 条,{{ pagination.pageSize }}/页
</div>
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="sizes, prev, pager, next"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
small
/>
<div class="pagination-jump">
前往
<el-input
v-model="jumpPage"
size="small"
style="width: 50px; margin: 0 8px;"
@keyup.enter="handleJumpPage"
/>
</div>
</div>
</div>
<!-- 返利详情对话框 -->
<el-dialog
v-model="detailDialogVisible"
title="返利详情"
width="800px"
:close-on-click-modal="false"
>
<div v-if="rebateDetail" class="rebate-detail">
<el-descriptions :column="2" border>
<el-descriptions-item label="返利编号">
{{ rebateDetail.rebateNo }}
</el-descriptions-item>
<el-descriptions-item label="订单编号">
{{ rebateDetail.orderNo }}
</el-descriptions-item>
<el-descriptions-item label="经销商代码">
{{ rebateDetail.dealerCode }}
</el-descriptions-item>
<el-descriptions-item label="经销商名称">
{{ rebateDetail.dealerName }}
</el-descriptions-item>
<el-descriptions-item label="产品编码">
{{ rebateDetail.productCode }}
</el-descriptions-item>
<el-descriptions-item label="返利金额">
<span class="amount-text">¥{{ formatAmount(rebateDetail.rebateAmount) }}</span>
</el-descriptions-item>
<el-descriptions-item label="返利日期">
{{ rebateDetail.rebateDate }}
</el-descriptions-item>
<el-descriptions-item label="操作类型">
<el-tag :type="getOperateTypeTagType(rebateDetail.operateType)">
{{ rebateDetail.operateTypeText || getOperateTypeText(rebateDetail.operateType) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="计算状态">
<el-tag :type="getCalcFlagTagType(rebateDetail.calcFlag)">
{{ rebateDetail.calcFlagText || getCalcFlagText(rebateDetail.calcFlag) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ rebateDetail.createTime ? formatDate(rebateDetail.createTime) : '-' }}
</el-descriptions-item>
<el-descriptions-item label="更新时间">
{{ rebateDetail.updateTime ? formatDate(rebateDetail.updateTime) : '-' }}
</el-descriptions-item>
<el-descriptions-item label="上传时间">
{{ rebateDetail.uploadTime ? formatDate(rebateDetail.uploadTime) : '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
<template #footer>
<el-button @click="detailDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
<!-- 新增/编辑返利对话框 -->
<el-dialog
v-model="formDialogVisible"
:title="isEdit ? '编辑返利' : '新增返利'"
width="600px"
:close-on-click-modal="false"
>
<el-form
ref="rebateFormRef"
:model="rebateForm"
:rules="rebateFormRules"
label-width="100px"
>
<el-form-item label="经销商" prop="dealerName">
<el-input v-model="rebateForm.dealerName" placeholder="请输入经销商名称" />
</el-form-item>
<el-form-item label="产品名称" prop="productName">
<el-input v-model="rebateForm.productName" placeholder="请输入产品名称" />
</el-form-item>
<el-form-item label="产品型号">
<el-input v-model="rebateForm.productModel" placeholder="请输入产品型号" />
</el-form-item>
<el-form-item label="返利类型" prop="rebateType">
<el-select v-model="rebateForm.rebateType" placeholder="请选择返利类型" style="width: 100%">
<el-option label="销量返利" :value="1" />
<el-option label="业绩返利" :value="2" />
<el-option label="年度返利" :value="3" />
<el-option label="特殊返利" :value="4" />
</el-select>
</el-form-item>
<el-form-item label="返利政策" prop="policyName">
<el-input v-model="rebateForm.policyName" placeholder="请输入返利政策名称" />
</el-form-item>
<el-form-item label="计算基数" prop="calculationBase">
<el-input-number
v-model="rebateForm.calculationBase"
:precision="2"
:min="0"
style="width: 100%"
placeholder="请输入计算基数"
/>
</el-form-item>
<el-form-item label="返利比例(%)" prop="rebateRate">
<el-input-number
v-model="rebateForm.rebateRate"
:precision="2"
:min="0"
:max="100"
style="width: 100%"
placeholder="请输入返利比例"
/>
</el-form-item>
<el-form-item label="返利金额" prop="rebateAmount">
<el-input-number
v-model="rebateForm.rebateAmount"
:precision="2"
:min="0"
style="width: 100%"
placeholder="请输入返利金额"
/>
</el-form-item>
<el-form-item label="返利周期" prop="periodRange">
<el-date-picker
v-model="periodRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="申请备注">
<el-input
v-model="rebateForm.applyRemark"
type="textarea"
:rows="3"
placeholder="请输入申请备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="formDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">
确定
</el-button>
</template>
</el-dialog>
<!-- 审核对话框 -->
<el-dialog
v-model="auditDialogVisible"
title="审核返利"
width="500px"
:close-on-click-modal="false"
>
<el-form
ref="auditFormRef"
:model="auditForm"
:rules="auditFormRules"
label-width="100px"
>
<el-form-item label="审核结果" prop="status">
<el-radio-group v-model="auditForm.status">
<el-radio :label="1">审核通过</el-radio>
<el-radio :label="3">审核拒绝</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审核备注">
<el-input
v-model="auditForm.auditRemark"
type="textarea"
:rows="4"
placeholder="请输入审核备注"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="auditDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmitAudit" :loading="auditLoading">
确定
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import rebateApi, { type Rebate, type RebateSearchParams } from '@/api/rebate'
import { formatDate } from '@/utils/index'
// 响应式数据
const loading = ref(false)
const submitLoading = ref(false)
const auditLoading = ref(false)
const rebateList = ref<Rebate[]>([])
const selectedRebates = ref<Rebate[]>([])
const rebateDetail = ref<Rebate | null>(null)
// 图表引用
const trendChart = ref<HTMLElement>()
const pieChart = ref<HTMLElement>()
// 日期范围
const dateRange = ref<[string, string] | null>(null)
// 对话框显示状态
const detailDialogVisible = ref(false)
const formDialogVisible = ref(false)
const auditDialogVisible = ref(false)
const isEdit = ref(false)
// 分页数据
const pagination = reactive({
pageNum: 1,
pageSize: 10,
total: 0
})
// 页面跳转
const jumpPage = ref<number | string>('')
// 搜索表单
const searchForm = reactive<RebateSearchParams>({
pageNum: 1,
pageSize: 10,
rebateNo: '',
dealerCode: '',
dealerName: '',
productCode: '',
operateType: undefined,
calcFlag: undefined,
rebateStartDate: '',
rebateEndDate: ''
})
// 日期范围
const applyDateRange = ref<[string, string] | null>(null)
const periodRange = ref<[string, string] | null>(null)
// 返利表单
const rebateFormRef = ref()
const rebateForm = reactive({
rebateId: undefined,
dealerId: 1, // 这里应该从经销商选择器获取
dealerName: '',
productId: 1, // 这里应该从产品选择器获取
productName: '',
productModel: '',
rebateType: undefined,
policyName: '',
calculationBase: undefined,
rebateRate: undefined,
rebateAmount: undefined,
periodStart: '',
periodEnd: '',
applyRemark: ''
})
// 审核表单
const auditFormRef = ref()
const auditForm = reactive({
rebateId: undefined as number | undefined,
status: undefined as number | undefined,
auditRemark: ''
})
// 表单验证规则
const rebateFormRules = {
dealerName: [
{ required: true, message: '请输入经销商名称', trigger: 'blur' }
],
productName: [
{ required: true, message: '请输入产品名称', trigger: 'blur' }
],
rebateType: [
{ required: true, message: '请选择返利类型', trigger: 'change' }
],
policyName: [
{ required: true, message: '请输入返利政策名称', trigger: 'blur' }
],
calculationBase: [
{ required: true, message: '请输入计算基数', trigger: 'blur' }
],
rebateRate: [
{ required: true, message: '请输入返利比例', trigger: 'blur' }
],
rebateAmount: [
{ required: true, message: '请输入返利金额', trigger: 'blur' }
]
}
const auditFormRules = {
status: [
{ required: true, message: '请选择审核结果', trigger: 'change' }
]
}
// 获取返利类型标签类型
const getRebateTypeTagType = (rebateType: number) => {
switch (rebateType) {
case 1:
return 'primary'
case 2:
return 'success'
case 3:
return 'warning'
case 4:
return 'danger'
default:
return 'info'
}
}
// 获取状态标签类型
const getStatusTagType = (status: number) => {
switch (status) {
case 0:
return 'warning'
case 1:
return 'success'
case 2:
return 'primary'
case 3:
return 'danger'
default:
return 'info'
}
}
// 格式化金额
const formatAmount = (amount: number) => {
return amount ? amount.toLocaleString('zh-CN', { minimumFractionDigits: 2 }) : '0.00'
}
// 获取操作类型文本
const getOperateTypeText = (operateType: number) => {
switch (operateType) {
case 1: return '新增'
case 2: return '修改'
case 3: return '删除'
default: return '未知'
}
}
// 获取操作类型标签类型
const getOperateTypeTagType = (operateType: number) => {
switch (operateType) {
case 1: return 'success'
case 2: return 'warning'
case 3: return 'danger'
default: return 'info'
}
}
// 获取计算状态文本
const getCalcFlagText = (calcFlag: number | undefined) => {
switch (calcFlag) {
case 0: return '未计算'
case 1: return '已计算'
default: return '未知'
}
}
// 获取计算状态标签类型
const getCalcFlagTagType = (calcFlag: number | undefined) => {
switch (calcFlag) {
case 0: return 'warning'
case 1: return 'success'
default: return 'info'
}
}
// 获取返利列表
const getRebateList = async () => {
try {
loading.value = true
const params = {
...searchForm,
pageNum: pagination.pageNum,
pageSize: pagination.pageSize
}
console.log('请求参数:', params)
const response = await rebateApi.getRebatePage(params)
console.log('完整API响应:', response)
console.log('响应数据结构:', response.data)
console.log('响应对象类型:', typeof response)
console.log('响应对象键:', Object.keys(response || {}))
// 检查不同层级的数据
if (response) {
console.log('response存在')
if (response.data !== undefined) {
console.log('response.data存在:', response.data)
} else {
console.log('response.data不存在,检查其他字段')
console.log('response直接内容:', response)
}
}
// 根据后端实际返回的数据结构进行解析
let dataList = []
let total = 0
if (response) {
let responseData = null
// 首先尝试获取数据,response.data可能不存在
if (response.data !== undefined) {
responseData = response.data
console.log('使用response.data:', responseData)
} else {
// 如果response.data不存在,直接使用response
responseData = response
console.log('直接使用response:', responseData)
}
if (responseData) {
// 情况1: 直接返回数组
if (Array.isArray(responseData)) {
dataList = responseData
total = responseData.length
console.log('情况1: 直接数组,长度:', dataList.length)
}
// 情况2: 包含records字段(MyBatis Plus分页格式)
else if (responseData.records && Array.isArray(responseData.records)) {
dataList = responseData.records
total = responseData.total || responseData.records.length
console.log('情况2: records格式,长度:', dataList.length, '总数:', total)
}
// 情况3: 包含data字段的嵌套结构
else if (responseData.data) {
if (Array.isArray(responseData.data)) {
dataList = responseData.data
total = responseData.total || responseData.data.length
console.log('情况3a: data数组格式,长度:', dataList.length)
} else if (responseData.data.records) {
dataList = responseData.data.records
total = responseData.data.total || 0
console.log('情况3b: data.records格式,长度:', dataList.length)
}
}
// 情况4: 其他对象格式,寻找数组字段
else if (typeof responseData === 'object' && responseData !== null) {
const keys = Object.keys(responseData)
console.log('对象的所有键:', keys)
// 寻找可能的数组字段
const arrayFields = keys.filter(key => Array.isArray(responseData[key]))
console.log('数组字段:', arrayFields)
if (arrayFields.length > 0) {
const arrayField = arrayFields[0]
dataList = responseData[arrayField]
total = responseData.total || responseData.count || dataList.length
console.log('情况4: 找到数组字段', arrayField, '长度:', dataList.length)
}
}
}
}
rebateList.value = dataList
pagination.total = total
console.log('最终解析的列表数据:', rebateList.value)
console.log('数据条数:', rebateList.value.length)
console.log('分页总数:', pagination.total)
if (rebateList.value.length === 0) {
console.warn('解析后的数据为空,请检查数据结构')
console.warn('如果需要测试,可以取消注释下面的模拟数据')
// 临时模拟数据用于测试(可以取消注释来测试表格显示)
/*
rebateList.value = [
{
rebateId: 1,
rebateNo: 'RB2025-001',
orderNo: 'ORD-2025-001',
dealerCode: 'DEALER001',
dealerName: '测试经销商1',
productCode: 'PROD001',
rebateAmount: 1000.00,
rebateDate: '2025-01-15',
operateType: 1,
calcFlag: 0,
createTime: '2025-01-15 10:00:00',
updateTime: '2025-01-15 10:00:00'
}
]
pagination.total = 1
*/
}
} catch (error) {
console.error('获取返利列表失败:', error)
ElMessage.error('获取返利列表失败')
rebateList.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
// 处理日期范围变化
const handleDateRangeChange = (dates: [string, string] | null) => {
if (dates) {
searchForm.rebateStartDate = dates[0]
searchForm.rebateEndDate = dates[1]
} else {
searchForm.rebateStartDate = ''
searchForm.rebateEndDate = ''
}
}
// 搜索
const handleSearch = () => {
pagination.pageNum = 1
getRebateList()
}
// 重置
const handleReset = () => {
Object.assign(searchForm, {
pageNum: 1,
pageSize: 10,
rebateNo: undefined,
dealerName: undefined,
productName: undefined,
rebateType: undefined,
status: undefined,
applyStartTime: undefined,
applyEndTime: undefined
})
applyDateRange.value = null
pagination.pageNum = 1
getRebateList()
}
// 刷新
const handleRefresh = () => {
getRebateList()
}
// 分页变化
const handleSizeChange = (size: number) => {
pagination.pageSize = size
pagination.pageNum = 1
getRebateList()
}
const handleCurrentChange = (page: number) => {
pagination.pageNum = page
getRebateList()
}
// 页面跳转
const handleJumpPage = () => {
const page = Number(jumpPage.value)
if (page && page > 0 && page <= Math.ceil(pagination.total / pagination.pageSize)) {
pagination.pageNum = page
getRebateList()
jumpPage.value = ''
}
}
// 选择变化
const handleSelectionChange = (selection: Rebate[]) => {
selectedRebates.value = selection
}
// 查看详情
const handleViewDetail = async (row: Rebate) => {
try {
if (!row.rebateId) return
console.log('查看详情,返利ID:', row.rebateId)
const response = await rebateApi.getRebateById(row.rebateId)
console.log('详情接口响应:', response)
// 根据request.ts拦截器的处理,response.data已经是实际数据
if (response && response.data) {
rebateDetail.value = response.data
detailDialogVisible.value = true
console.log('详情数据:', response.data)
} else {
ElMessage.error('获取返利详情失败')
}
} catch (error) {
console.error('获取返利详情失败:', error)
ElMessage.error('获取返利详情失败')
}
}
// 新增
const handleAdd = () => {
isEdit.value = false
resetRebateForm()
formDialogVisible.value = true
}
// 编辑
const handleEdit = (row: Rebate) => {
ElMessage.info(`编辑返利记录:${row.rebateNo}`)
// 这里可以打开编辑对话框或跳转到编辑页面
}
// 标记已计算
const handleMarkCalculated = async (row: Rebate) => {
try {
await ElMessageBox.confirm('确认标记该返利为已计算吗?', '确认操作', {
type: 'warning'
})
await rebateApi.markRebateCalculated(row.rebateId!)
ElMessage.success('标记成功')
getRebateList()
} catch (error) {
if (error !== 'cancel') {
console.error('标记失败:', error)
ElMessage.error('标记失败')
}
}
}
// 重置表单
const resetRebateForm = () => {
Object.assign(rebateForm, {
rebateId: undefined,
dealerId: 1,
dealerName: '',
productId: 1,
productName: '',
productModel: '',
rebateType: undefined,
policyName: '',
calculationBase: undefined,
rebateRate: undefined,
rebateAmount: undefined,
periodStart: '',
periodEnd: '',
applyRemark: ''
})
periodRange.value = null
rebateFormRef.value?.clearValidate()
}
// 提交表单
const handleSubmit = async () => {
try {
await rebateFormRef.value?.validate()
if (periodRange.value) {
rebateForm.periodStart = periodRange.value[0]
rebateForm.periodEnd = periodRange.value[1]
}
submitLoading.value = true
if (isEdit.value) {
await rebateApi.updateRebate(rebateForm as any)
ElMessage.success('更新返利成功')
} else {
await rebateApi.addRebate(rebateForm as any)
ElMessage.success('新增返利成功')
}
formDialogVisible.value = false
getRebateList()
} catch (error) {
console.error('提交表单失败:', error)
ElMessage.error('操作失败')
} finally {
submitLoading.value = false
}
}
// 删除
const handleDelete = async (row: Rebate) => {
try {
await ElMessageBox.confirm('确认删除该返利记录吗?', '确认删除', {
type: 'warning'
})
const { data } = await rebateApi.deleteRebate(row.rebateId!)
if (data.code === 200) {
ElMessage.success('删除成功')
getRebateList()
} else {
ElMessage.error(data.message || '删除失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除返利失败:', error)
ElMessage.error('删除返利失败')
}
}
}
// 批量标记已计算
const handleBatchMarkCalculated = async () => {
try {
if (selectedRebates.value.length === 0) {
ElMessage.warning('请选择要标记的记录')
return
}
await ElMessageBox.confirm(`确认标记选中的 ${selectedRebates.value.length} 条返利记录为已计算吗?`, '确认操作', {
type: 'warning'
})
const rebateIds = selectedRebates.value.map(item => item.rebateId!)
const { data } = await rebateApi.batchMarkRebateCalculated(rebateIds)
if (data.code === 200) {
ElMessage.success('批量标记成功')
getRebateList()
} else {
ElMessage.error(data.message || '批量标记失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('批量标记失败:', error)
ElMessage.error('批量标记失败')
}
}
}
// 批量删除
const handleBatchDelete = async () => {
try {
if (selectedRebates.value.length === 0) {
ElMessage.warning('请选择要删除的记录')
return
}
await ElMessageBox.confirm(`确认删除选中的 ${selectedRebates.value.length} 条返利记录吗?`, '确认删除', {
type: 'warning'
})
const rebateIds = selectedRebates.value.map(item => item.rebateId!)
const { data } = await rebateApi.batchDeleteRebate(rebateIds)
if (data.code === 200) {
ElMessage.success('批量删除成功')
getRebateList()
} else {
ElMessage.error(data.message || '批量删除失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('批量删除返利失败:', error)
ElMessage.error('批量删除返利失败')
}
}
}
// 审核
const handleAudit = (row: Rebate) => {
auditForm.rebateId = row.rebateId
auditForm.status = undefined
auditForm.auditRemark = ''
auditDialogVisible.value = true
}
// 提交审核
const handleSubmitAudit = async () => {
try {
await auditFormRef.value?.validate()
auditLoading.value = true
// 模拟审核操作
ElMessage.success('审核返利成功')
auditDialogVisible.value = false
getRebateList()
} catch (error) {
console.error('审核返利失败:', error)
ElMessage.error('审核返利失败')
} finally {
auditLoading.value = false
}
}
// 批量审核
const handleBatchAudit = async (status: number) => {
try {
const statusText = status === 1 ? '通过' : '拒绝'
await ElMessageBox.confirm(`确认批量审核${statusText}选中的 ${selectedRebates.value.length} 条返利记录吗?`, '确认审核', {
type: 'warning'
})
// 模拟批量审核操作
ElMessage.success(`批量审核${statusText}成功`)
getRebateList()
} catch (error) {
if (error !== 'cancel') {
console.error('批量审核失败:', error)
ElMessage.error('批量审核失败')
}
}
}
// 发放
const handleRelease = async (row: any) => {
try {
await ElMessageBox.confirm('确认发放该返利吗?', '确认发放', {
type: 'warning'
})
// 模拟发放操作
ElMessage.success('发放返利成功')
getRebateList()
} catch (error) {
if (error !== 'cancel') {
console.error('发放返利失败:', error)
ElMessage.error('发放返利失败')
}
}
}
// 批量发放
const handleBatchRelease = async () => {
try {
await ElMessageBox.confirm(`确认批量发放选中的 ${selectedRebates.value.length} 条返利记录吗?`, '确认发放', {
type: 'warning'
})
// 模拟批量发放操作
ElMessage.success('批量发放返利成功')
getRebateList()
} catch (error) {
if (error !== 'cancel') {
console.error('批量发放返利失败:', error)
ElMessage.error('批量发放返利失败')
}
}
}
// 导出
const handleExport = async () => {
try {
loading.value = true
const params = { ...searchForm }
const response = await rebateApi.exportRebate(params)
// 创建下载链接
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `返利数据_${new Date().toISOString().split('T')[0]}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
} catch (error) {
console.error('导出失败:', error)
ElMessage.error('导出失败')
} finally {
loading.value = false
}
}
// 初始化趋势图
const initTrendChart = async () => {
if (!trendChart.value) return
const chart = echarts.init(trendChart.value)
try {
// 调用后端API获取月度统计数据
const response = await rebateApi.getRebateMonthlyStats()
let monthlyData = []
if (response && response.data) {
monthlyData = response.data
}
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
// 处理后端返回的月度数据
const totalTrendData = months.map((month, index) => {
const monthIndex = index + 1
const monthData = monthlyData.find((item: any) => item.month === monthIndex)
return monthData ? monthData.totalAmount : 0
})
const calculatedTrendData = months.map((month, index) => {
const monthIndex = index + 1
const monthData = monthlyData.find((item: any) => item.month === monthIndex)
return monthData ? monthData.calculatedAmount : 0
})
const option = {
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
let result = `${params[0].axisValue}<br/>`
params.forEach((param: any) => {
result += `${param.seriesName}: ¥${param.value.toLocaleString()}<br/>`
})
return result
}
},
grid: {
left: '8%',
right: '8%',
top: '15%',
bottom: '15%'
},
xAxis: {
type: 'category',
boundaryGap: false,
data: months,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
color: '#999',
fontSize: 12
}
},
yAxis: {
type: 'value',
axisLine: { show: false },
axisTick: { show: false },
splitLine: {
show: true,
lineStyle: {
color: '#f0f0f0',
type: 'dashed'
}
},
axisLabel: {
color: '#999',
fontSize: 12,
formatter: (value: number) => `${(value / 1000).toFixed(0)}k`
}
},
series: [
{
name: '返利金额',
type: 'line',
smooth: true,
data: totalTrendData,
itemStyle: { color: '#4CAF50' },
lineStyle: {
color: '#4CAF50',
width: 3
},
symbol: 'circle',
symbolSize: 6,
showSymbol: true
},
{
name: '已审核返利',
type: 'line',
smooth: true,
data: calculatedTrendData,
itemStyle: { color: '#2196F3' },
lineStyle: {
color: '#2196F3',
width: 3
},
symbol: 'circle',
symbolSize: 6,
showSymbol: true
}
]
}
chart.setOption(option)
} catch (error) {
console.error('获取趋势图数据失败:', error)
// 如果API调用失败,使用当前列表数据作为备用方案
const totalAmount = rebateList.value.reduce((sum, item) => sum + (item.rebateAmount || 0), 0)
const calculatedAmount = rebateList.value
.filter(item => item.calcFlag === 1)
.reduce((sum, item) => sum + (item.rebateAmount || 0), 0)
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
const totalTrendData = months.map(() => Math.floor(totalAmount / 12))
const calculatedTrendData = months.map(() => Math.floor(calculatedAmount / 12))
const fallbackOption = {
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
let result = `${params[0].axisValue}<br/>`
params.forEach((param: any) => {
result += `${param.seriesName}: ¥${param.value.toLocaleString()}<br/>`
})
return result
}
},
grid: {
left: '8%',
right: '8%',
top: '15%',
bottom: '15%'
},
xAxis: {
type: 'category',
boundaryGap: false,
data: months,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
color: '#999',
fontSize: 12
}
},
yAxis: {
type: 'value',
axisLine: { show: false },
axisTick: { show: false },
splitLine: {
show: true,
lineStyle: {
color: '#f0f0f0',
type: 'dashed'
}
},
axisLabel: {
color: '#999',
fontSize: 12,
formatter: (value: number) => `${(value / 1000).toFixed(0)}k`
}
},
series: [
{
name: '返利金额',
type: 'line',
smooth: true,
data: totalTrendData,
itemStyle: { color: '#4CAF50' },
lineStyle: {
color: '#4CAF50',
width: 3
},
symbol: 'circle',
symbolSize: 6,
showSymbol: true
},
{
name: '已审核返利',
type: 'line',
smooth: true,
data: calculatedTrendData,
itemStyle: { color: '#2196F3' },
lineStyle: {
color: '#2196F3',
width: 3
},
symbol: 'circle',
symbolSize: 6,
showSymbol: true
}
]
}
chart.setOption(fallbackOption)
}
// 响应式调整
window.addEventListener('resize', () => chart.resize())
}
// 初始化饼图
const initPieChart = async () => {
if (!pieChart.value) return
const chart = echarts.init(pieChart.value)
try {
// 调用后端API获取状态统计数据
const response = await rebateApi.getRebateStatusStats()
let statusData = { calculatedCount: 0, unCalculatedCount: 0 }
if (response && response.data) {
statusData = response.data
}
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
series: [
{
name: '审核状态',
type: 'pie',
radius: ['35%', '65%'],
center: ['50%', '50%'],
data: [
{
value: statusData.calculatedCount || 0,
name: '已计算',
itemStyle: { color: '#2196F3' }
},
{
value: statusData.unCalculatedCount || 0,
name: '未计算',
itemStyle: { color: '#4CAF50' }
}
],
label: {
show: true,
position: 'inside',
formatter: '{b}\n{d}%',
fontSize: 12,
color: '#fff',
fontWeight: 'bold'
},
labelLine: {
show: false
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
},
label: {
fontSize: 14
}
}
}
]
}
chart.setOption(option)
} catch (error) {
console.error('获取饼图数据失败:', error)
// 如果API调用失败,使用当前列表数据作为备用方案
const calculatedCount = rebateList.value.filter(item => item.calcFlag === 1).length
const unCalculatedCount = rebateList.value.filter(item => item.calcFlag === 0).length
const fallbackOption = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
series: [
{
name: '审核状态',
type: 'pie',
radius: ['35%', '65%'],
center: ['50%', '50%'],
data: [
{
value: calculatedCount,
name: '已审核',
itemStyle: { color: '#2196F3' }
},
{
value: unCalculatedCount,
name: '未审核',
itemStyle: { color: '#4CAF50' }
}
],
label: {
show: true,
position: 'inside',
formatter: '{b}\n{d}%',
fontSize: 12,
color: '#fff',
fontWeight: 'bold'
},
labelLine: {
show: false
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
},
label: {
fontSize: 14
}
}
}
]
}
chart.setOption(fallbackOption)
}
// 响应式调整
window.addEventListener('resize', () => chart.resize())
}
// 页面加载时获取数据
onMounted(async () => {
await getRebateList()
setTimeout(async () => {
await initTrendChart()
await initPieChart()
}, 100)
})
</script>
<style scoped lang="scss">
.rebate-page {
padding: 20px;
background: #f5f7fa;
min-height: 100vh;
}
.search-section {
background: white;
padding: 16px 20px;
margin-bottom: 16px;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
.search-form {
.search-row {
display: flex;
align-items: center;
gap: 24px;
flex-wrap: wrap;
.search-item {
display: flex;
align-items: center;
gap: 8px;
.search-label {
font-size: 14px;
color: #333;
white-space: nowrap;
min-width: 70px;
}
}
.search-actions {
margin-left: auto;
display: flex;
gap: 8px;
}
}
}
}
.charts-section {
margin-bottom: 16px;
.charts-container {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 16px;
.chart-card {
background: white;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
.chart-header {
padding: 16px 20px 8px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
.chart-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin: 0;
}
.chart-legend {
display: flex;
gap: 16px;
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #666;
.legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
}
}
}
&.trend-chart {
.chart {
height: 260px;
}
}
&.pie-chart {
.chart {
height: 240px;
}
}
.chart-content {
padding: 12px 20px 20px 20px;
}
}
}
}
.chart {
width: 100%;
}
.table-section {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 0 20px;
margin-bottom: 16px;
.table-title {
font-size: 16px;
font-weight: 500;
color: #333;
}
.table-info {
font-size: 12px;
color: #999;
}
}
.el-table {
border: none;
:deep(.el-table__header) {
th {
background-color: #fafafa;
border: none;
font-weight: 500;
color: #333;
}
}
:deep(.el-table__body) {
tr:hover > td {
background-color: #f5f7fa;
}
td {
border: none;
border-bottom: 1px solid #f0f0f0;
}
}
}
.pagination-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
border-top: 1px solid #f0f0f0;
background: #fafafa;
.pagination-info {
font-size: 14px;
color: #666;
}
.pagination-jump {
display: flex;
align-items: center;
font-size: 14px;
color: #666;
}
}
}
.amount-text {
color: #f56c6c;
font-weight: 500;
}
.rebate-detail {
max-height: 600px;
overflow-y: auto;
}
// 响应式设计
@media (max-width: 1200px) {
.charts-container {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.rebate-page {
padding: 16px;
}
.search-form .form-row {
flex-direction: column;
align-items: flex-start;
gap: 12px;
.form-actions {
margin-left: 0;
margin-top: 12px;
}
}
}
</style>