jiaxing.zhou

Merge branch 'dev'

# Conflicts:
#	frontend/components.d.ts
1 +package com.apple.erp.controller;
2 +
3 +import com.apple.erp.dto.request.RebateAddReq;
4 +import com.apple.erp.dto.request.RebateQueryReq;
5 +import com.apple.erp.dto.request.RebateUpdateReq;
6 +import com.apple.erp.dto.response.ApiRes;
7 +import com.apple.erp.dto.response.RebateRes;
8 +import com.apple.erp.entity.Rebate;
9 +import com.apple.erp.service.RebateService;
10 +import com.baomidou.mybatisplus.core.metadata.IPage;
11 +import io.swagger.v3.oas.annotations.Operation;
12 +import io.swagger.v3.oas.annotations.tags.Tag;
13 +import lombok.extern.slf4j.Slf4j;
14 +import org.springframework.beans.factory.annotation.Autowired;
15 +import org.springframework.validation.annotation.Validated;
16 +import org.springframework.web.bind.annotation.*;
17 +
18 +import javax.validation.Valid;
19 +import java.math.BigDecimal;
20 +import java.util.List;
21 +import java.util.Map;
22 +
23 +/**
24 + * 返利台账明细管理控制器
25 + *
26 + * @author Apple ERP Team
27 + * @version 1.0.0
28 + * @since 2024-01-01
29 + */
30 +@Slf4j
31 +@Tag(name = "返利台账明细管理")
32 +@RestController
33 +@RequestMapping("/api/rebate")
34 +@Validated
35 +public class RebateController {
36 +
37 + @Autowired
38 + private RebateService rebateService;
39 +
40 + @Operation(summary = "分页查询返利明细列表")
41 + @PostMapping("/page")
42 + public ApiRes<IPage<RebateRes>> getRebatePage(@Valid @RequestBody RebateQueryReq queryReq) {
43 + log.info("分页查询返利明细列表,参数:{}", queryReq);
44 + try {
45 + IPage<RebateRes> result = rebateService.getRebatePage(queryReq);
46 + return ApiRes.success(result);
47 + } catch (Exception e) {
48 + log.error("分页查询返利明细列表失败", e);
49 + return ApiRes.error(e.getMessage());
50 + }
51 + }
52 +
53 + @Operation(summary = "根据ID查询返利详情")
54 + @GetMapping("/{rebateId}")
55 + public ApiRes<RebateRes> getRebateById(@PathVariable Long rebateId) {
56 + log.info("查询返利详情,ID:{}", rebateId);
57 + try {
58 + RebateRes result = rebateService.getRebateById(rebateId);
59 + if (result == null) {
60 + return ApiRes.error("返利记录不存在");
61 + }
62 + return ApiRes.success(result);
63 + } catch (Exception e) {
64 + log.error("查询返利详情失败", e);
65 + return ApiRes.error(e.getMessage());
66 + }
67 + }
68 +
69 + @Operation(summary = "新增返利")
70 + @PostMapping("/add")
71 + public ApiRes<Void> addRebate(@Valid @RequestBody RebateAddReq addReq) {
72 + log.info("新增返利,参数:{}", addReq);
73 + try {
74 + boolean result = rebateService.addRebate(addReq);
75 + return result ? ApiRes.success("操作成功", null) : ApiRes.error("新增返利失败");
76 + } catch (Exception e) {
77 + log.error("新增返利失败", e);
78 + return ApiRes.error(e.getMessage());
79 + }
80 + }
81 +
82 + @Operation(summary = "更新返利")
83 + @PostMapping("/update")
84 + public ApiRes<Void> updateRebate(@Valid @RequestBody RebateUpdateReq updateReq) {
85 + log.info("更新返利,参数:{}", updateReq);
86 + try {
87 + boolean result = rebateService.updateRebate(updateReq);
88 + return result ? ApiRes.success("操作成功", null) : ApiRes.error("更新返利失败");
89 + } catch (Exception e) {
90 + log.error("更新返利失败", e);
91 + return ApiRes.error(e.getMessage());
92 + }
93 + }
94 +
95 + @Operation(summary = "删除返利")
96 + @DeleteMapping("/{rebateId}")
97 + public ApiRes<Void> deleteRebate(@PathVariable Long rebateId) {
98 + log.info("删除返利,ID:{}", rebateId);
99 + try {
100 + boolean result = rebateService.deleteRebate(rebateId);
101 + return result ? ApiRes.success("操作成功", null) : ApiRes.error("删除返利失败");
102 + } catch (Exception e) {
103 + log.error("删除返利失败", e);
104 + return ApiRes.error(e.getMessage());
105 + }
106 + }
107 +
108 + @Operation(summary = "批量删除返利")
109 + @PostMapping("/batchDelete")
110 + public ApiRes<Void> batchDeleteRebate(@RequestBody List<Long> rebateIds) {
111 + log.info("批量删除返利,IDs:{}", rebateIds);
112 + try {
113 + boolean result = rebateService.batchDeleteRebate(rebateIds);
114 + return result ? ApiRes.success("操作成功", null) : ApiRes.error("批量删除返利失败");
115 + } catch (Exception e) {
116 + log.error("批量删除返利失败", e);
117 + return ApiRes.error(e.getMessage());
118 + }
119 + }
120 +
121 + @Operation(summary = "标记返利为已计算")
122 + @PostMapping("/markCalculated/{rebateId}")
123 + public ApiRes<Void> markRebateCalculated(@PathVariable Long rebateId) {
124 + log.info("标记返利为已计算,ID:{}", rebateId);
125 + try {
126 + Rebate rebate = rebateService.getById(rebateId);
127 + if (rebate == null) {
128 + return ApiRes.error("返利记录不存在");
129 + }
130 + rebate.setCalcFlag(1);
131 + boolean result = rebateService.updateById(rebate);
132 + return result ? ApiRes.success("操作成功", null) : ApiRes.error("标记失败");
133 + } catch (Exception e) {
134 + log.error("标记返利计算状态失败", e);
135 + return ApiRes.error(e.getMessage());
136 + }
137 + }
138 +
139 + @Operation(summary = "批量标记返利为已计算")
140 + @PostMapping("/batchMarkCalculated")
141 + public ApiRes<Void> batchMarkRebateCalculated(@RequestBody List<Long> rebateIds) {
142 + log.info("批量标记返利为已计算,IDs:{}", rebateIds);
143 + try {
144 + List<Rebate> rebates = rebateService.listByIds(rebateIds);
145 + rebates.forEach(rebate -> rebate.setCalcFlag(1));
146 + boolean result = rebateService.updateBatchById(rebates);
147 + return result ? ApiRes.success("操作成功", null) : ApiRes.error("批量标记失败");
148 + } catch (Exception e) {
149 + log.error("批量标记返利计算状态失败", e);
150 + return ApiRes.error(e.getMessage());
151 + }
152 + }
153 +
154 + @Operation(summary = "根据经销商编码统计返利总金额")
155 + @GetMapping("/sumByDealer/{dealerCode}")
156 + public ApiRes<BigDecimal> sumRebateAmountByDealer(@PathVariable String dealerCode,
157 + @RequestParam(required = false) Integer operateType) {
158 + log.info("统计经销商返利总金额,经销商编码:{},操作类型:{}", dealerCode, operateType);
159 + try {
160 + BigDecimal result = rebateService.sumRebateAmountByDealer(dealerCode, operateType);
161 + return ApiRes.success(result);
162 + } catch (Exception e) {
163 + log.error("统计经销商返利总金额失败", e);
164 + return ApiRes.error(e.getMessage());
165 + }
166 + }
167 +
168 + @Operation(summary = "根据产品编码统计返利总金额")
169 + @GetMapping("/sumByProduct/{productCode}")
170 + public ApiRes<BigDecimal> sumRebateAmountByProduct(@PathVariable String productCode,
171 + @RequestParam(required = false) Integer operateType) {
172 + log.info("统计产品返利总金额,产品编码:{},操作类型:{}", productCode, operateType);
173 + try {
174 + BigDecimal result = rebateService.sumRebateAmountByProduct(productCode, operateType);
175 + return ApiRes.success(result);
176 + } catch (Exception e) {
177 + log.error("统计产品返利总金额失败", e);
178 + return ApiRes.error(e.getMessage());
179 + }
180 + }
181 +
182 + @Operation(summary = "统计各操作类型的返利数量")
183 + @GetMapping("/countByOperateType")
184 + public ApiRes<List<Map<String, Object>>> countRebateByOperateType() {
185 + log.info("统计各操作类型的返利数量");
186 + try {
187 + List<Map<String, Object>> result = rebateService.countRebateByOperateType();
188 + return ApiRes.success(result);
189 + } catch (Exception e) {
190 + log.error("统计各操作类型的返利数量失败", e);
191 + return ApiRes.error(e.getMessage());
192 + }
193 + }
194 +
195 + @Operation(summary = "查询未计算的返利明细列表")
196 + @GetMapping("/unCalc")
197 + public ApiRes<List<RebateRes>> getUnCalcRebates() {
198 + log.info("查询未计算的返利明细列表");
199 + try {
200 + List<RebateRes> result = rebateService.getUnCalcRebates();
201 + return ApiRes.success(result);
202 + } catch (Exception e) {
203 + log.error("查询未计算的返利明细列表失败", e);
204 + return ApiRes.error(e.getMessage());
205 + }
206 + }
207 +
208 + @Operation(summary = "根据返利编号查询返利")
209 + @GetMapping("/byNo/{rebateNo}")
210 + public ApiRes<RebateRes> getRebateByNo(@PathVariable String rebateNo) {
211 + log.info("根据返利编号查询返利,编号:{}", rebateNo);
212 + try {
213 + RebateRes result = rebateService.getRebateByNo(rebateNo);
214 + if (result == null) {
215 + return ApiRes.error("返利记录不存在");
216 + }
217 + return ApiRes.success(result);
218 + } catch (Exception e) {
219 + log.error("根据返利编号查询返利失败", e);
220 + return ApiRes.error(e.getMessage());
221 + }
222 + }
223 +
224 + @Operation(summary = "生成返利编号")
225 + @GetMapping("/generateNo")
226 + public ApiRes<String> generateRebateNo() {
227 + log.info("生成返利编号");
228 + try {
229 + String result = rebateService.generateRebateNo();
230 + return ApiRes.success(result);
231 + } catch (Exception e) {
232 + log.error("生成返利编号失败", e);
233 + return ApiRes.error(e.getMessage());
234 + }
235 + }
236 +
237 + @Operation(summary = "获取返利月度统计数据")
238 + @GetMapping("/monthlyStats")
239 + public ApiRes<List<Map<String, Object>>> getRebateMonthlyStats(@RequestParam(required = false) Integer year) {
240 + log.info("获取返利月度统计数据,年份:{}", year);
241 + try {
242 + if (year == null) {
243 + year = java.time.LocalDate.now().getYear();
244 + }
245 + List<Map<String, Object>> result = rebateService.getRebateMonthlyStats(year);
246 + return ApiRes.success(result);
247 + } catch (Exception e) {
248 + log.error("获取返利月度统计数据失败", e);
249 + return ApiRes.error(e.getMessage());
250 + }
251 + }
252 +
253 + @Operation(summary = "获取返利状态统计数据")
254 + @GetMapping("/statusStats")
255 + public ApiRes<Map<String, Object>> getRebateStatusStats() {
256 + log.info("获取返利状态统计数据");
257 + try {
258 + Map<String, Object> result = rebateService.getRebateStatusStats();
259 + return ApiRes.success(result);
260 + } catch (Exception e) {
261 + log.error("获取返利状态统计数据失败", e);
262 + return ApiRes.error(e.getMessage());
263 + }
264 + }
265 +
266 + @Operation(summary = "获取返利趋势统计数据")
267 + @GetMapping("/trendStats")
268 + public ApiRes<List<Map<String, Object>>> getRebateTrendStats(
269 + @RequestParam(required = false) String startDate,
270 + @RequestParam(required = false) String endDate) {
271 + log.info("获取返利趋势统计数据,开始日期:{},结束日期:{}", startDate, endDate);
272 + try {
273 + List<Map<String, Object>> result = rebateService.getRebateTrendStats(startDate, endDate);
274 + return ApiRes.success(result);
275 + } catch (Exception e) {
276 + log.error("获取返利趋势统计数据失败", e);
277 + return ApiRes.error(e.getMessage());
278 + }
279 + }
280 +}
1 +package com.apple.erp.dto.request;
2 +
3 +import lombok.Data;
4 +
5 +import javax.validation.constraints.*;
6 +import java.math.BigDecimal;
7 +
8 +/**
9 + * 返利新增请求DTO
10 + *
11 + * @author Apple ERP
12 + */
13 +@Data
14 +public class RebateAddReq {
15 +
16 + /**
17 + * 返利明细编号
18 + */
19 + @Size(max = 30, message = "返利明细编号不能超过30个字符")
20 + private String rebateNo;
21 +
22 + /**
23 + * 关联订单编号
24 + */
25 + @NotBlank(message = "关联订单编号不能为空")
26 + @Size(max = 30, message = "订单编号不能超过30个字符")
27 + private String orderNo;
28 +
29 + /**
30 + * 经销商编码
31 + */
32 + @NotBlank(message = "经销商编码不能为空")
33 + @Size(max = 20, message = "经销商编码不能超过20个字符")
34 + private String dealerCode;
35 +
36 + /**
37 + * 经销商名称
38 + */
39 + @NotBlank(message = "经销商名称不能为空")
40 + @Size(max = 100, message = "经销商名称不能超过100个字符")
41 + private String dealerName;
42 +
43 + /**
44 + * 产品编码
45 + */
46 + @NotBlank(message = "产品编码不能为空")
47 + @Size(max = 30, message = "产品编码不能超过30个字符")
48 + private String productCode;
49 +
50 + /**
51 + * 返利金额
52 + */
53 + @NotNull(message = "返利金额不能为空")
54 + @DecimalMin(value = "0", message = "返利金额不能小于0")
55 + private BigDecimal rebateAmount;
56 +
57 + /**
58 + * 返利记录日期
59 + */
60 + @NotBlank(message = "返利记录日期不能为空")
61 + private String rebateDate;
62 +
63 + /**
64 + * 操作类型:1-新增,2-扣减
65 + */
66 + @NotNull(message = "操作类型不能为空")
67 + @Min(value = 1, message = "操作类型值无效")
68 + @Max(value = 2, message = "操作类型值无效")
69 + private Integer operateType;
70 +
71 + /**
72 + * 返利计算状态:0-未计算,1-已计算
73 + */
74 + private Integer calcFlag;
75 +}
1 +package com.apple.erp.dto.request;
2 +
3 +import lombok.Data;
4 +
5 +/**
6 + * 返利台账明细查询请求DTO
7 + *
8 + * @author Apple ERP Team
9 + * @version 1.0.0
10 + * @since 2024-01-01
11 + */
12 +@Data
13 +public class RebateQueryReq {
14 +
15 + /**
16 + * 返利明细编号
17 + */
18 + private String rebateNo;
19 +
20 + /**
21 + * 关联订单编号
22 + */
23 + private String orderNo;
24 +
25 + /**
26 + * 经销商编码
27 + */
28 + private String dealerCode;
29 +
30 + /**
31 + * 经销商名称
32 + */
33 + private String dealerName;
34 +
35 + /**
36 + * 产品编码
37 + */
38 + private String productCode;
39 +
40 + /**
41 + * 操作类型:1-新增,2-扣减
42 + */
43 + private Integer operateType;
44 +
45 + /**
46 + * 返利计算状态:0-未计算,1-已计算
47 + */
48 + private Integer calcFlag;
49 +
50 + /**
51 + * 返利记录开始日期
52 + */
53 + private String rebateStartDate;
54 +
55 + /**
56 + * 返利记录结束日期
57 + */
58 + private String rebateEndDate;
59 +
60 + /**
61 + * 页码
62 + */
63 + private Integer pageNum = 1;
64 +
65 + /**
66 + * 页面大小
67 + */
68 + private Integer pageSize = 10;
69 +}
1 +package com.apple.erp.dto.request;
2 +
3 +import lombok.Data;
4 +
5 +import javax.validation.constraints.*;
6 +import java.math.BigDecimal;
7 +
8 +/**
9 + * 返利更新请求DTO
10 + *
11 + * @author Apple ERP
12 + */
13 +@Data
14 +public class RebateUpdateReq {
15 +
16 + /**
17 + * 返利明细ID
18 + */
19 + @NotNull(message = "返利明细ID不能为空")
20 + private Long rebateId;
21 +
22 + /**
23 + * 返利明细编号
24 + */
25 + @Size(max = 30, message = "返利明细编号不能超过30个字符")
26 + private String rebateNo;
27 +
28 + /**
29 + * 关联订单编号
30 + */
31 + @NotBlank(message = "关联订单编号不能为空")
32 + @Size(max = 30, message = "订单编号不能超过30个字符")
33 + private String orderNo;
34 +
35 + /**
36 + * 经销商编码
37 + */
38 + @NotBlank(message = "经销商编码不能为空")
39 + @Size(max = 20, message = "经销商编码不能超过20个字符")
40 + private String dealerCode;
41 +
42 + /**
43 + * 经销商名称
44 + */
45 + @NotBlank(message = "经销商名称不能为空")
46 + @Size(max = 100, message = "经销商名称不能超过100个字符")
47 + private String dealerName;
48 +
49 + /**
50 + * 产品编码
51 + */
52 + @NotBlank(message = "产品编码不能为空")
53 + @Size(max = 30, message = "产品编码不能超过30个字符")
54 + private String productCode;
55 +
56 + /**
57 + * 返利金额
58 + */
59 + @NotNull(message = "返利金额不能为空")
60 + @DecimalMin(value = "0", message = "返利金额不能小于0")
61 + private BigDecimal rebateAmount;
62 +
63 + /**
64 + * 返利记录日期
65 + */
66 + @NotBlank(message = "返利记录日期不能为空")
67 + private String rebateDate;
68 +
69 + /**
70 + * 操作类型:1-新增,2-扣减
71 + */
72 + @NotNull(message = "操作类型不能为空")
73 + @Min(value = 1, message = "操作类型值无效")
74 + @Max(value = 2, message = "操作类型值无效")
75 + private Integer operateType;
76 +
77 + /**
78 + * 返利计算状态:0-未计算,1-已计算
79 + */
80 + private Integer calcFlag;
81 +}
1 +package com.apple.erp.dto.response;
2 +
3 +import lombok.Data;
4 +
5 +import java.math.BigDecimal;
6 +import java.time.LocalDate;
7 +import java.time.LocalDateTime;
8 +
9 +/**
10 + * 返利响应DTO
11 + *
12 + * @author Apple ERP
13 + */
14 +@Data
15 +public class RebateRes {
16 +
17 + /**
18 + * 返利明细ID
19 + */
20 + private Long rebateId;
21 +
22 + /**
23 + * 返利明细编号
24 + */
25 + private String rebateNo;
26 +
27 + /**
28 + * 关联订单编号
29 + */
30 + private String orderNo;
31 +
32 + /**
33 + * 经销商编码
34 + */
35 + private String dealerCode;
36 +
37 + /**
38 + * 经销商名称
39 + */
40 + private String dealerName;
41 +
42 + /**
43 + * 产品编码
44 + */
45 + private String productCode;
46 +
47 + /**
48 + * 返利金额
49 + */
50 + private BigDecimal rebateAmount;
51 +
52 + /**
53 + * 返利记录日期
54 + */
55 + private LocalDate rebateDate;
56 +
57 + /**
58 + * 操作类型:1-新增,2-扣减
59 + */
60 + private Integer operateType;
61 +
62 + /**
63 + * 操作类型文本
64 + */
65 + private String operateTypeText;
66 +
67 + /**
68 + * 返利计算状态:0-未计算,1-已计算
69 + */
70 + private Integer calcFlag;
71 +
72 + /**
73 + * 计算状态文本
74 + */
75 + private String calcFlagText;
76 +
77 + /**
78 + * 数据上传时间
79 + */
80 + private LocalDateTime uploadTime;
81 +
82 + /**
83 + * 创建时间
84 + */
85 + private LocalDateTime createTime;
86 +
87 + /**
88 + * 更新时间
89 + */
90 + private LocalDateTime updateTime;
91 +
92 + /**
93 + * 创建者
94 + */
95 + private String createBy;
96 +
97 + /**
98 + * 更新者
99 + */
100 + private String updateBy;
101 +}
1 +package com.apple.erp.entity;
2 +
3 +import com.baomidou.mybatisplus.annotation.*;
4 +import lombok.Data;
5 +import lombok.EqualsAndHashCode;
6 +
7 +import java.math.BigDecimal;
8 +import java.time.LocalDate;
9 +import java.time.LocalDateTime;
10 +
11 +/**
12 + * 返利实体类
13 + *
14 + * @author Apple ERP
15 + */
16 +@Data
17 +@EqualsAndHashCode(callSuper = false)
18 +@TableName("t_rebate_detail")
19 +public class Rebate {
20 +
21 + /**
22 + * 返利明细ID
23 + */
24 + @TableId(value = "rebate_id", type = IdType.AUTO)
25 + private Long rebateId;
26 +
27 + /**
28 + * 返利明细编号
29 + */
30 + @TableField("rebate_no")
31 + private String rebateNo;
32 +
33 + /**
34 + * 关联订单编号
35 + */
36 + @TableField("order_no")
37 + private String orderNo;
38 +
39 + /**
40 + * 经销商编码
41 + */
42 + @TableField("dealer_code")
43 + private String dealerCode;
44 +
45 + /**
46 + * 经销商名称
47 + */
48 + @TableField("dealer_name")
49 + private String dealerName;
50 +
51 + /**
52 + * 产品编码
53 + */
54 + @TableField("product_code")
55 + private String productCode;
56 +
57 + /**
58 + * 返利金额
59 + */
60 + @TableField("rebate_amount")
61 + private BigDecimal rebateAmount;
62 +
63 + /**
64 + * 返利记录日期
65 + */
66 + @TableField("rebate_date")
67 + private LocalDate rebateDate;
68 +
69 + /**
70 + * 操作类型:1-新增,2-扣减
71 + */
72 + @TableField("operate_type")
73 + private Integer operateType;
74 +
75 + /**
76 + * 返利计算状态:0-未计算,1-已计算
77 + */
78 + @TableField("calc_flag")
79 + private Integer calcFlag;
80 +
81 + /**
82 + * 数据上传时间
83 + */
84 + @TableField("upload_time")
85 + private LocalDateTime uploadTime;
86 +
87 + /**
88 + * 创建时间
89 + */
90 + @TableField(value = "create_time", fill = FieldFill.INSERT)
91 + private LocalDateTime createTime;
92 +
93 + /**
94 + * 更新时间
95 + */
96 + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
97 + private LocalDateTime updateTime;
98 +
99 + /**
100 + * 创建者
101 + */
102 + @TableField(value = "create_by", fill = FieldFill.INSERT)
103 + private String createBy;
104 +
105 + /**
106 + * 更新者
107 + */
108 + @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
109 + private String updateBy;
110 +
111 + /**
112 + * 删除标志:0-存在,2-删除
113 + */
114 + @TableField("del_flag")
115 + @TableLogic(value = "0", delval = "2")
116 + private String delFlag;
117 +}
1 +package com.apple.erp.mapper;
2 +
3 +import com.apple.erp.entity.Rebate;
4 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 +import com.baomidou.mybatisplus.core.metadata.IPage;
6 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
7 +import org.apache.ibatis.annotations.Mapper;
8 +import org.apache.ibatis.annotations.Param;
9 +
10 +import java.math.BigDecimal;
11 +import java.util.List;
12 +import java.util.Map;
13 +
14 +/**
15 + * 返利台账明细Mapper接口
16 + *
17 + * @author Apple ERP Team
18 + * @version 1.0.0
19 + * @since 2024-01-01
20 + */
21 +@Mapper
22 +public interface RebateMapper extends BaseMapper<Rebate> {
23 +
24 + /**
25 + * 分页查询返利明细列表
26 + *
27 + * @param page 分页参数
28 + * @param params 查询参数
29 + * @return 返利明细分页列表
30 + */
31 + IPage<Rebate> selectRebatePage(Page<Rebate> page, @Param("params") Map<String, Object> params);
32 +
33 + /**
34 + * 根据经销商编码统计返利总金额
35 + *
36 + * @param dealerCode 经销商编码
37 + * @param operateType 操作类型
38 + * @return 返利总金额
39 + */
40 + BigDecimal sumRebateAmountByDealer(@Param("dealerCode") String dealerCode, @Param("operateType") Integer operateType);
41 +
42 + /**
43 + * 根据产品编码统计返利总金额
44 + *
45 + * @param productCode 产品编码
46 + * @param operateType 操作类型
47 + * @return 返利总金额
48 + */
49 + BigDecimal sumRebateAmountByProduct(@Param("productCode") String productCode, @Param("operateType") Integer operateType);
50 +
51 + /**
52 + * 统计各操作类型的返利数量
53 + *
54 + * @return 操作类型统计
55 + */
56 + List<Map<String, Object>> countRebateByOperateType();
57 +
58 + /**
59 + * 查询未计算的返利明细列表
60 + *
61 + * @return 未计算返利明细列表
62 + */
63 + List<Rebate> selectUnCalcRebates();
64 +
65 + /**
66 + * 根据返利编号查询返利明细
67 + *
68 + * @param rebateNo 返利编号
69 + * @return 返利明细信息
70 + */
71 + Rebate selectByRebateNo(@Param("rebateNo") String rebateNo);
72 +
73 + /**
74 + * 获取返利月度统计数据
75 + *
76 + * @param year 年份
77 + * @return 月度统计数据
78 + */
79 + List<Map<String, Object>> getRebateMonthlyStats(@Param("year") Integer year);
80 +
81 + /**
82 + * 获取返利状态统计数据
83 + *
84 + * @return 状态统计数据
85 + */
86 + Map<String, Object> getRebateStatusStats();
87 +
88 + /**
89 + * 获取返利趋势统计数据
90 + *
91 + * @param startDate 开始日期
92 + * @param endDate 结束日期
93 + * @return 趋势统计数据
94 + */
95 + List<Map<String, Object>> getRebateTrendStats(@Param("startDate") String startDate, @Param("endDate") String endDate);
96 +}
1 +package com.apple.erp.service;
2 +
3 +import com.apple.erp.dto.request.RebateAddReq;
4 +import com.apple.erp.dto.request.RebateQueryReq;
5 +import com.apple.erp.dto.request.RebateUpdateReq;
6 +import com.apple.erp.dto.response.RebateRes;
7 +import com.apple.erp.entity.Rebate;
8 +import com.baomidou.mybatisplus.core.metadata.IPage;
9 +import com.baomidou.mybatisplus.extension.service.IService;
10 +
11 +import java.math.BigDecimal;
12 +import java.util.List;
13 +import java.util.Map;
14 +
15 +/**
16 + * 返利台账明细服务接口
17 + *
18 + * @author Apple ERP Team
19 + * @version 1.0.0
20 + * @since 2024-01-01
21 + */
22 +public interface RebateService extends IService<Rebate> {
23 +
24 + /**
25 + * 分页查询返利明细列表
26 + *
27 + * @param queryReq 查询请求
28 + * @return 返利明细分页列表
29 + */
30 + IPage<RebateRes> getRebatePage(RebateQueryReq queryReq);
31 +
32 + /**
33 + * 根据ID查询返利明细详情
34 + *
35 + * @param rebateId 返利明细ID
36 + * @return 返利明细详情
37 + */
38 + RebateRes getRebateById(Long rebateId);
39 +
40 + /**
41 + * 新增返利明细
42 + *
43 + * @param addReq 新增请求
44 + * @return 是否成功
45 + */
46 + boolean addRebate(RebateAddReq addReq);
47 +
48 + /**
49 + * 更新返利明细
50 + *
51 + * @param updateReq 更新请求
52 + * @return 是否成功
53 + */
54 + boolean updateRebate(RebateUpdateReq updateReq);
55 +
56 + /**
57 + * 删除返利明细
58 + *
59 + * @param rebateId 返利明细ID
60 + * @return 是否成功
61 + */
62 + boolean deleteRebate(Long rebateId);
63 +
64 + /**
65 + * 批量删除返利明细
66 + *
67 + * @param rebateIds 返利明细ID列表
68 + * @return 是否成功
69 + */
70 + boolean batchDeleteRebate(List<Long> rebateIds);
71 +
72 + /**
73 + * 根据经销商编码统计返利总金额
74 + *
75 + * @param dealerCode 经销商编码
76 + * @param operateType 操作类型(可选)
77 + * @return 返利总金额
78 + */
79 + BigDecimal sumRebateAmountByDealer(String dealerCode, Integer operateType);
80 +
81 + /**
82 + * 根据产品编码统计返利总金额
83 + *
84 + * @param productCode 产品编码
85 + * @param operateType 操作类型(可选)
86 + * @return 返利总金额
87 + */
88 + BigDecimal sumRebateAmountByProduct(String productCode, Integer operateType);
89 +
90 + /**
91 + * 统计各操作类型的返利数量
92 + *
93 + * @return 操作类型统计
94 + */
95 + List<Map<String, Object>> countRebateByOperateType();
96 +
97 + /**
98 + * 查询未计算的返利明细列表
99 + *
100 + * @return 未计算返利明细列表
101 + */
102 + List<RebateRes> getUnCalcRebates();
103 +
104 + /**
105 + * 根据返利编号查询返利明细
106 + *
107 + * @param rebateNo 返利编号
108 + * @return 返利明细信息
109 + */
110 + RebateRes getRebateByNo(String rebateNo);
111 +
112 + /**
113 + * 生成返利编号
114 + *
115 + * @return 返利编号
116 + */
117 + String generateRebateNo();
118 +
119 + /**
120 + * 获取返利月度统计数据
121 + *
122 + * @param year 年份
123 + * @return 月度统计数据
124 + */
125 + List<Map<String, Object>> getRebateMonthlyStats(Integer year);
126 +
127 + /**
128 + * 获取返利状态统计数据
129 + *
130 + * @return 状态统计数据
131 + */
132 + Map<String, Object> getRebateStatusStats();
133 +
134 + /**
135 + * 获取返利趋势统计数据
136 + *
137 + * @param startDate 开始日期
138 + * @param endDate 结束日期
139 + * @return 趋势统计数据
140 + */
141 + List<Map<String, Object>> getRebateTrendStats(String startDate, String endDate);
142 +}
1 +package com.apple.erp.service.impl;
2 +
3 +import com.apple.erp.dto.request.RebateAddReq;
4 +import com.apple.erp.dto.request.RebateQueryReq;
5 +import com.apple.erp.dto.request.RebateUpdateReq;
6 +import com.apple.erp.dto.response.RebateRes;
7 +import com.apple.erp.entity.Rebate;
8 +import com.apple.erp.mapper.RebateMapper;
9 +import com.apple.erp.service.RebateService;
10 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
11 +import com.baomidou.mybatisplus.core.metadata.IPage;
12 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
13 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
14 +import lombok.extern.slf4j.Slf4j;
15 +import org.springframework.beans.BeanUtils;
16 +import org.springframework.beans.factory.annotation.Autowired;
17 +import org.springframework.stereotype.Service;
18 +import org.springframework.transaction.annotation.Transactional;
19 +
20 +import java.math.BigDecimal;
21 +import java.time.LocalDate;
22 +import java.time.LocalDateTime;
23 +import java.time.format.DateTimeFormatter;
24 +import java.util.*;
25 +import java.util.stream.Collectors;
26 +
27 +/**
28 + * 返利台账明细服务实现类
29 + *
30 + * @author Apple ERP Team
31 + * @version 1.0.0
32 + * @since 2024-01-01
33 + */
34 +@Slf4j
35 +@Service
36 +public class RebateServiceImpl extends ServiceImpl<RebateMapper, Rebate> implements RebateService {
37 +
38 + @Autowired
39 + private RebateMapper rebateMapper;
40 +
41 + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
42 +
43 + @Override
44 + public IPage<RebateRes> getRebatePage(RebateQueryReq queryReq) {
45 + // 构建查询参数
46 + Map<String, Object> params = buildQueryParams(queryReq);
47 +
48 + // 创建分页对象
49 + Page<Rebate> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize());
50 +
51 + // 分页查询
52 + IPage<Rebate> rebatePage = rebateMapper.selectRebatePage(page, params);
53 +
54 + // 转换为响应DTO
55 + Page<RebateRes> resultPage = new Page<>(rebatePage.getCurrent(), rebatePage.getSize(), rebatePage.getTotal());
56 + List<RebateRes> rebateResList = rebatePage.getRecords().stream()
57 + .map(this::convertToRebateRes)
58 + .collect(Collectors.toList());
59 + resultPage.setRecords(rebateResList);
60 +
61 + return resultPage;
62 + }
63 +
64 + @Override
65 + public RebateRes getRebateById(Long rebateId) {
66 + Rebate rebate = getById(rebateId);
67 + return rebate != null ? convertToRebateRes(rebate) : null;
68 + }
69 +
70 + @Override
71 + @Transactional(rollbackFor = Exception.class)
72 + public boolean addRebate(RebateAddReq addReq) {
73 + Rebate rebate = new Rebate();
74 + BeanUtils.copyProperties(addReq, rebate);
75 +
76 + // 设置返利编号(如果未提供)
77 + if (addReq.getRebateNo() == null || addReq.getRebateNo().isEmpty()) {
78 + rebate.setRebateNo(generateRebateNo());
79 + }
80 +
81 + // 转换日期字符串
82 + if (addReq.getRebateDate() != null && !addReq.getRebateDate().isEmpty()) {
83 + rebate.setRebateDate(LocalDate.parse(addReq.getRebateDate(), DATE_FORMATTER));
84 + }
85 +
86 + // 设置上传时间
87 + rebate.setUploadTime(LocalDateTime.now());
88 +
89 + // 默认计算状态为未计算
90 + if (rebate.getCalcFlag() == null) {
91 + rebate.setCalcFlag(0);
92 + }
93 +
94 + return save(rebate);
95 + }
96 +
97 + @Override
98 + @Transactional(rollbackFor = Exception.class)
99 + public boolean updateRebate(RebateUpdateReq updateReq) {
100 + Rebate rebate = getById(updateReq.getRebateId());
101 + if (rebate == null) {
102 + throw new RuntimeException("返利记录不存在");
103 + }
104 +
105 + BeanUtils.copyProperties(updateReq, rebate);
106 +
107 + // 转换日期字符串
108 + if (updateReq.getRebateDate() != null && !updateReq.getRebateDate().isEmpty()) {
109 + rebate.setRebateDate(LocalDate.parse(updateReq.getRebateDate(), DATE_FORMATTER));
110 + }
111 +
112 + return updateById(rebate);
113 + }
114 +
115 + @Override
116 + @Transactional(rollbackFor = Exception.class)
117 + public boolean deleteRebate(Long rebateId) {
118 + return removeById(rebateId);
119 + }
120 +
121 + @Override
122 + @Transactional(rollbackFor = Exception.class)
123 + public boolean batchDeleteRebate(List<Long> rebateIds) {
124 + return removeByIds(rebateIds);
125 + }
126 +
127 + @Override
128 + public BigDecimal sumRebateAmountByDealer(String dealerCode, Integer operateType) {
129 + BigDecimal amount = rebateMapper.sumRebateAmountByDealer(dealerCode, operateType);
130 + return amount != null ? amount : BigDecimal.ZERO;
131 + }
132 +
133 + @Override
134 + public BigDecimal sumRebateAmountByProduct(String productCode, Integer operateType) {
135 + BigDecimal amount = rebateMapper.sumRebateAmountByProduct(productCode, operateType);
136 + return amount != null ? amount : BigDecimal.ZERO;
137 + }
138 +
139 + @Override
140 + public List<Map<String, Object>> countRebateByOperateType() {
141 + return rebateMapper.countRebateByOperateType();
142 + }
143 +
144 + @Override
145 + public List<RebateRes> getUnCalcRebates() {
146 + List<Rebate> rebates = rebateMapper.selectUnCalcRebates();
147 + return rebates.stream()
148 + .map(this::convertToRebateRes)
149 + .collect(Collectors.toList());
150 + }
151 +
152 + @Override
153 + public RebateRes getRebateByNo(String rebateNo) {
154 + Rebate rebate = rebateMapper.selectByRebateNo(rebateNo);
155 + return rebate != null ? convertToRebateRes(rebate) : null;
156 + }
157 +
158 + @Override
159 + public String generateRebateNo() {
160 + String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
161 + String prefix = "RB" + dateStr;
162 +
163 + // 查询当天已有的返利编号数量
164 + LambdaQueryWrapper<Rebate> wrapper = new LambdaQueryWrapper<>();
165 + wrapper.likeRight(Rebate::getRebateNo, prefix);
166 + long count = count(wrapper);
167 +
168 + // 生成4位序号
169 + String sequence = String.format("%04d", count + 1);
170 +
171 + return prefix + sequence;
172 + }
173 +
174 + /**
175 + * 构建查询参数
176 + */
177 + private Map<String, Object> buildQueryParams(RebateQueryReq queryReq) {
178 + Map<String, Object> params = new HashMap<>();
179 +
180 + if (queryReq.getRebateNo() != null && !queryReq.getRebateNo().isEmpty()) {
181 + params.put("rebateNo", queryReq.getRebateNo());
182 + }
183 + if (queryReq.getOrderNo() != null && !queryReq.getOrderNo().isEmpty()) {
184 + params.put("orderNo", queryReq.getOrderNo());
185 + }
186 + if (queryReq.getDealerCode() != null && !queryReq.getDealerCode().isEmpty()) {
187 + params.put("dealerCode", queryReq.getDealerCode());
188 + }
189 + if (queryReq.getDealerName() != null && !queryReq.getDealerName().isEmpty()) {
190 + params.put("dealerName", queryReq.getDealerName());
191 + }
192 + if (queryReq.getProductCode() != null && !queryReq.getProductCode().isEmpty()) {
193 + params.put("productCode", queryReq.getProductCode());
194 + }
195 + if (queryReq.getOperateType() != null) {
196 + params.put("operateType", queryReq.getOperateType());
197 + }
198 + if (queryReq.getCalcFlag() != null) {
199 + params.put("calcFlag", queryReq.getCalcFlag());
200 + }
201 + if (queryReq.getRebateStartDate() != null && !queryReq.getRebateStartDate().isEmpty()) {
202 + params.put("rebateStartDate", queryReq.getRebateStartDate());
203 + }
204 + if (queryReq.getRebateEndDate() != null && !queryReq.getRebateEndDate().isEmpty()) {
205 + params.put("rebateEndDate", queryReq.getRebateEndDate());
206 + }
207 +
208 + return params;
209 + }
210 +
211 + /**
212 + * 转换为响应DTO
213 + */
214 + private RebateRes convertToRebateRes(Rebate rebate) {
215 + RebateRes rebateRes = new RebateRes();
216 + BeanUtils.copyProperties(rebate, rebateRes);
217 +
218 + // 设置操作类型文本
219 + rebateRes.setOperateTypeText(getOperateTypeText(rebate.getOperateType()));
220 +
221 + // 设置计算状态文本
222 + rebateRes.setCalcFlagText(getCalcFlagText(rebate.getCalcFlag()));
223 +
224 + return rebateRes;
225 + }
226 +
227 + /**
228 + * 获取操作类型文本
229 + */
230 + private String getOperateTypeText(Integer operateType) {
231 + if (operateType == null) {
232 + return "";
233 + }
234 + switch (operateType) {
235 + case 1:
236 + return "新增";
237 + case 2:
238 + return "扣减";
239 + default:
240 + return "未知";
241 + }
242 + }
243 +
244 + /**
245 + * 获取计算状态文本
246 + */
247 + private String getCalcFlagText(Integer calcFlag) {
248 + if (calcFlag == null) {
249 + return "";
250 + }
251 + switch (calcFlag) {
252 + case 0:
253 + return "未计算";
254 + case 1:
255 + return "已计算";
256 + default:
257 + return "未知";
258 + }
259 + }
260 +
261 + @Override
262 + public List<Map<String, Object>> getRebateMonthlyStats(Integer year) {
263 + log.info("获取返利月度统计数据,年份:{}", year);
264 + return rebateMapper.getRebateMonthlyStats(year);
265 + }
266 +
267 + @Override
268 + public Map<String, Object> getRebateStatusStats() {
269 + log.info("获取返利状态统计数据");
270 + return rebateMapper.getRebateStatusStats();
271 + }
272 +
273 + @Override
274 + public List<Map<String, Object>> getRebateTrendStats(String startDate, String endDate) {
275 + log.info("获取返利趋势统计数据,开始日期:{},结束日期:{}", startDate, endDate);
276 + return rebateMapper.getRebateTrendStats(startDate, endDate);
277 + }
278 +}
...@@ -17,8 +17,8 @@ spring: ...@@ -17,8 +17,8 @@ spring:
17 type: com.alibaba.druid.pool.DruidDataSource 17 type: com.alibaba.druid.pool.DruidDataSource
18 driver-class-name: com.mysql.cj.jdbc.Driver 18 driver-class-name: com.mysql.cj.jdbc.Driver
19 url: jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true 19 url: jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
20 - username: root 20 + username: apple_erp_app
21 - password: MySQL@123 21 + password: StrongPassw0rd!
22 druid: 22 druid:
23 # 初始连接数 23 # 初始连接数
24 initial-size: 5 24 initial-size: 5
...@@ -70,7 +70,7 @@ spring: ...@@ -70,7 +70,7 @@ spring:
70 redis: 70 redis:
71 host: localhost 71 host: localhost
72 port: 6379 72 port: 6379
73 - password: 73 + password: 123456
74 database: 0 74 database: 0
75 timeout: 10000ms 75 timeout: 10000ms
76 lettuce: 76 lettuce:
......
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3 +<mapper namespace="com.apple.erp.mapper.RebateMapper">
4 +
5 + <!-- 返利明细基础字段 -->
6 + <sql id="baseColumns">
7 + rebate_id, rebate_no, order_no, dealer_code, dealer_name, product_code,
8 + rebate_amount, rebate_date, operate_type, calc_flag, upload_time,
9 + create_by, create_time, update_by, update_time
10 + </sql>
11 +
12 + <!-- 分页查询返利明细列表 -->
13 + <select id="selectRebatePage" resultType="com.apple.erp.entity.Rebate">
14 + SELECT
15 + <include refid="baseColumns"/>
16 + FROM t_rebate_detail
17 + WHERE del_flag = '0'
18 + <if test="params.rebateNo != null and params.rebateNo != ''">
19 + AND rebate_no LIKE CONCAT('%', #{params.rebateNo}, '%')
20 + </if>
21 + <if test="params.orderNo != null and params.orderNo != ''">
22 + AND order_no LIKE CONCAT('%', #{params.orderNo}, '%')
23 + </if>
24 + <if test="params.dealerCode != null and params.dealerCode != ''">
25 + AND dealer_code = #{params.dealerCode}
26 + </if>
27 + <if test="params.dealerName != null and params.dealerName != ''">
28 + AND dealer_name LIKE CONCAT('%', #{params.dealerName}, '%')
29 + </if>
30 + <if test="params.productCode != null and params.productCode != ''">
31 + AND product_code = #{params.productCode}
32 + </if>
33 + <if test="params.operateType != null">
34 + AND operate_type = #{params.operateType}
35 + </if>
36 + <if test="params.calcFlag != null">
37 + AND calc_flag = #{params.calcFlag}
38 + </if>
39 + <if test="params.rebateStartDate != null and params.rebateStartDate != ''">
40 + AND rebate_date &gt;= #{params.rebateStartDate}
41 + </if>
42 + <if test="params.rebateEndDate != null and params.rebateEndDate != ''">
43 + AND rebate_date &lt;= #{params.rebateEndDate}
44 + </if>
45 + ORDER BY create_time DESC
46 + </select>
47 +
48 + <!-- 根据经销商编码统计返利总金额 -->
49 + <select id="sumRebateAmountByDealer" resultType="java.math.BigDecimal">
50 + SELECT COALESCE(SUM(rebate_amount), 0)
51 + FROM t_rebate_detail
52 + WHERE del_flag = '0'
53 + AND dealer_code = #{dealerCode}
54 + <if test="operateType != null">
55 + AND operate_type = #{operateType}
56 + </if>
57 + </select>
58 +
59 + <!-- 根据产品编码统计返利总金额 -->
60 + <select id="sumRebateAmountByProduct" resultType="java.math.BigDecimal">
61 + SELECT COALESCE(SUM(rebate_amount), 0)
62 + FROM t_rebate_detail
63 + WHERE del_flag = '0'
64 + AND product_code = #{productCode}
65 + <if test="operateType != null">
66 + AND operate_type = #{operateType}
67 + </if>
68 + </select>
69 +
70 + <!-- 统计各操作类型的返利数量 -->
71 + <select id="countRebateByOperateType" resultType="java.util.Map">
72 + SELECT
73 + operate_type,
74 + COUNT(*) as count,
75 + COALESCE(SUM(rebate_amount), 0) as totalAmount
76 + FROM t_rebate_detail
77 + WHERE del_flag = '0'
78 + GROUP BY operate_type
79 + </select>
80 +
81 + <!-- 查询未计算的返利明细列表 -->
82 + <select id="selectUnCalcRebates" resultType="com.apple.erp.entity.Rebate">
83 + SELECT
84 + <include refid="baseColumns"/>
85 + FROM t_rebate_detail
86 + WHERE del_flag = '0'
87 + AND calc_flag = 0
88 + ORDER BY create_time ASC
89 + </select>
90 +
91 + <!-- 根据返利编号查询返利明细 -->
92 + <select id="selectByRebateNo" resultType="com.apple.erp.entity.Rebate">
93 + SELECT
94 + <include refid="baseColumns"/>
95 + FROM t_rebate_detail
96 + WHERE del_flag = '0'
97 + AND rebate_no = #{rebateNo}
98 + </select>
99 +
100 + <!-- 获取返利月度统计数据 -->
101 + <select id="getRebateMonthlyStats" resultType="java.util.Map">
102 + SELECT
103 + MONTH(rebate_date) as month,
104 + COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount,
105 + COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
106 + WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
107 + ELSE 0 END), 0) as calculatedAmount,
108 + COUNT(*) as totalCount,
109 + COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount
110 + FROM t_rebate_detail
111 + WHERE del_flag = '0'
112 + AND YEAR(rebate_date) = #{year}
113 + GROUP BY MONTH(rebate_date)
114 + ORDER BY month
115 + </select>
116 +
117 + <!-- 获取返利状态统计数据 -->
118 + <select id="getRebateStatusStats" resultType="java.util.Map">
119 + SELECT
120 + COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount,
121 + COUNT(CASE WHEN calc_flag = 0 THEN 1 END) as unCalculatedCount,
122 + COUNT(*) as totalCount,
123 + COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
124 + WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
125 + ELSE 0 END), 0) as calculatedAmount,
126 + COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount
127 + FROM t_rebate_detail
128 + WHERE del_flag = '0'
129 + </select>
130 +
131 + <!-- 获取返利趋势统计数据 -->
132 + <select id="getRebateTrendStats" resultType="java.util.Map">
133 + SELECT
134 + DATE(rebate_date) as date,
135 + COALESCE(SUM(CASE WHEN operate_type = 1 THEN rebate_amount ELSE -rebate_amount END), 0) as totalAmount,
136 + COALESCE(SUM(CASE WHEN calc_flag = 1 AND operate_type = 1 THEN rebate_amount
137 + WHEN calc_flag = 1 AND operate_type = 2 THEN -rebate_amount
138 + ELSE 0 END), 0) as calculatedAmount,
139 + COUNT(*) as totalCount,
140 + COUNT(CASE WHEN calc_flag = 1 THEN 1 END) as calculatedCount
141 + FROM t_rebate_detail
142 + WHERE del_flag = '0'
143 + <if test="startDate != null and startDate != ''">
144 + AND rebate_date >= #{startDate}
145 + </if>
146 + <if test="endDate != null and endDate != ''">
147 + AND rebate_date &lt;= #{endDate}
148 + </if>
149 + GROUP BY DATE(rebate_date)
150 + ORDER BY date
151 + </select>
152 +
153 +</mapper>
...@@ -51,7 +51,8 @@ ...@@ -51,7 +51,8 @@
51 </select> 51 </select>
52 52
53 <select id="selectMenuTreeByRoleId" parameterType="Long" resultMap="SysMenuResult"> 53 <select id="selectMenuTreeByRoleId" parameterType="Long" resultMap="SysMenuResult">
54 - 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 54 + 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,
55 + m.sort
55 from t_sys_menu m 56 from t_sys_menu m
56 left join t_sys_role_menu rm on m.menu_id = rm.menu_id 57 left join t_sys_role_menu rm on m.menu_id = rm.menu_id
57 where rm.role_id = #{roleId} and m.status = 1 and m.del_flag = '0' 58 where rm.role_id = #{roleId} and m.status = 1 and m.del_flag = '0'
......
1 +import request from '@/utils/request'
2 +
3 +// 返利台账明细类型定义
4 +export interface Rebate {
5 + rebateId?: number
6 + rebateNo?: string
7 + orderNo: string
8 + dealerCode: string
9 + dealerName: string
10 + productCode: string
11 + rebateAmount: number
12 + rebateDate: string
13 + operateType: number
14 + operateTypeText?: string
15 + calcFlag?: number
16 + calcFlagText?: string
17 + uploadTime?: string
18 + createTime?: string
19 + updateTime?: string
20 + createBy?: string
21 + updateBy?: string
22 +}
23 +
24 +// 返利查询参数
25 +export interface RebateSearchParams {
26 + pageNum: number
27 + pageSize: number
28 + rebateNo?: string
29 + orderNo?: string
30 + dealerCode?: string
31 + dealerName?: string
32 + productCode?: string
33 + operateType?: number
34 + calcFlag?: number
35 + rebateStartDate?: string
36 + rebateEndDate?: string
37 +}
38 +
39 +// 返利新增参数
40 +export interface RebateAddParams {
41 + rebateNo?: string
42 + orderNo: string
43 + dealerCode: string
44 + dealerName: string
45 + productCode: string
46 + rebateAmount: number
47 + rebateDate: string
48 + operateType: number
49 + calcFlag?: number
50 +}
51 +
52 +// 返利更新参数
53 +export interface RebateUpdateParams extends RebateAddParams {
54 + rebateId: number
55 +}
56 +
57 +// 分页查询返利列表
58 +export const getRebatePage = (params: RebateSearchParams) => {
59 + return request({
60 + url: '/api/rebate/page',
61 + method: 'post',
62 + data: params
63 + })
64 +}
65 +
66 +// 根据ID查询返利详情
67 +export const getRebateById = (rebateId: number) => {
68 + return request({
69 + url: `/api/rebate/${rebateId}`,
70 + method: 'get'
71 + })
72 +}
73 +
74 +// 新增返利
75 +export const addRebate = (data: RebateAddParams) => {
76 + return request({
77 + url: '/api/rebate/add',
78 + method: 'post',
79 + data
80 + })
81 +}
82 +
83 +// 更新返利
84 +export const updateRebate = (data: RebateUpdateParams) => {
85 + return request({
86 + url: '/api/rebate/update',
87 + method: 'post',
88 + data
89 + })
90 +}
91 +
92 +// 删除返利
93 +export const deleteRebate = (rebateId: number) => {
94 + return request({
95 + url: `/api/rebate/${rebateId}`,
96 + method: 'delete'
97 + })
98 +}
99 +
100 +// 批量删除返利
101 +export const batchDeleteRebate = (rebateIds: number[]) => {
102 + return request({
103 + url: '/api/rebate/batchDelete',
104 + method: 'post',
105 + data: rebateIds
106 + })
107 +}
108 +
109 +// 标记返利为已计算
110 +export const markRebateCalculated = (rebateId: number) => {
111 + return request({
112 + url: `/api/rebate/markCalculated/${rebateId}`,
113 + method: 'post'
114 + })
115 +}
116 +
117 +// 批量标记返利为已计算
118 +export const batchMarkRebateCalculated = (rebateIds: number[]) => {
119 + return request({
120 + url: '/api/rebate/batchMarkCalculated',
121 + method: 'post',
122 + data: rebateIds
123 + })
124 +}
125 +
126 +// 根据经销商编码统计返利总金额
127 +export const sumRebateAmountByDealer = (dealerCode: string, operateType?: number) => {
128 + return request({
129 + url: `/api/rebate/sumByDealer/${dealerCode}`,
130 + method: 'get',
131 + params: { operateType }
132 + })
133 +}
134 +
135 +// 根据产品编码统计返利总金额
136 +export const sumRebateAmountByProduct = (productCode: string, operateType?: number) => {
137 + return request({
138 + url: `/api/rebate/sumByProduct/${productCode}`,
139 + method: 'get',
140 + params: { operateType }
141 + })
142 +}
143 +
144 +// 统计各操作类型的返利数量
145 +export const countRebateByOperateType = () => {
146 + return request({
147 + url: '/api/rebate/countByOperateType',
148 + method: 'get'
149 + })
150 +}
151 +
152 +// 查询未计算的返利明细列表
153 +export const getUnCalcRebates = () => {
154 + return request({
155 + url: '/api/rebate/unCalc',
156 + method: 'get'
157 + })
158 +}
159 +
160 +// 根据返利编号查询返利
161 +export const getRebateByNo = (rebateNo: string) => {
162 + return request({
163 + url: `/api/rebate/byNo/${rebateNo}`,
164 + method: 'get'
165 + })
166 +}
167 +
168 +// 生成返利编号
169 +export const generateRebateNo = () => {
170 + return request({
171 + url: '/api/rebate/generateNo',
172 + method: 'get'
173 + })
174 +}
175 +
176 +// 导出返利数据
177 +export const exportRebate = (params: RebateSearchParams) => {
178 + return request({
179 + url: '/api/rebate/export',
180 + method: 'post',
181 + data: params,
182 + responseType: 'blob'
183 + })
184 +}
185 +
186 +// 获取返利月度统计数据
187 +export const getRebateMonthlyStats = (year?: number) => {
188 + return request({
189 + url: '/api/rebate/monthlyStats',
190 + method: 'get',
191 + params: { year: year || new Date().getFullYear() }
192 + })
193 +}
194 +
195 +// 获取返利状态统计数据
196 +export const getRebateStatusStats = () => {
197 + return request({
198 + url: '/api/rebate/statusStats',
199 + method: 'get'
200 + })
201 +}
202 +
203 +// 获取返利趋势统计数据
204 +export const getRebateTrendStats = (startDate?: string, endDate?: string) => {
205 + return request({
206 + url: '/api/rebate/trendStats',
207 + method: 'get',
208 + params: { startDate, endDate }
209 + })
210 +}
211 +
212 +export default {
213 + getRebatePage,
214 + getRebateById,
215 + addRebate,
216 + updateRebate,
217 + deleteRebate,
218 + batchDeleteRebate,
219 + markRebateCalculated,
220 + batchMarkRebateCalculated,
221 + sumRebateAmountByDealer,
222 + sumRebateAmountByProduct,
223 + countRebateByOperateType,
224 + getUnCalcRebates,
225 + getRebateByNo,
226 + generateRebateNo,
227 + exportRebate,
228 + getRebateMonthlyStats,
229 + getRebateStatusStats,
230 + getRebateTrendStats
231 +}
...@@ -146,6 +146,7 @@ const menuItems = ref([ ...@@ -146,6 +146,7 @@ const menuItems = ref([
146 { name: '发票管理', path: '/main/invoice', icon: '🧾' }, 146 { name: '发票管理', path: '/main/invoice', icon: '🧾' },
147 { name: '产品管理', path: '/main/product', icon: '📦' }, 147 { name: '产品管理', path: '/main/product', icon: '📦' },
148 { name: '经销商管理', path: '/main/dealer', icon: '🏢' }, 148 { name: '经销商管理', path: '/main/dealer', icon: '🏢' },
149 + { name: '返利管理', path: '/main/rebate', icon: '💰' },
149 { 150 {
150 name: '系统设置', 151 name: '系统设置',
151 icon: '⚙️', 152 icon: '⚙️',
......
...@@ -99,24 +99,6 @@ const staticRoutes: RouteRecordRaw[] = [ ...@@ -99,24 +99,6 @@ const staticRoutes: RouteRecordRaw[] = [
99 } 99 }
100 }, 100 },
101 { 101 {
102 - path: 'dicts',
103 - name: 'Dicts',
104 - component: () => import('@/views/dicts/index.vue'),
105 - meta: {
106 - title: '字典管理',
107 - requiresAuth: true
108 - }
109 - },
110 - {
111 - path: 'logs',
112 - name: 'Logs',
113 - component: () => import('@/views/logs/index.vue'),
114 - meta: {
115 - title: '日志管理',
116 - requiresAuth: true
117 - }
118 - },
119 - {
120 path: 'product', 102 path: 'product',
121 name: 'Product', 103 name: 'Product',
122 component: () => import('@/views/product/index.vue'), 104 component: () => import('@/views/product/index.vue'),
...@@ -162,6 +144,15 @@ const staticRoutes: RouteRecordRaw[] = [ ...@@ -162,6 +144,15 @@ const staticRoutes: RouteRecordRaw[] = [
162 } 144 }
163 }, 145 },
164 { 146 {
147 + path: 'rebate',
148 + name: 'Rebate',
149 + component: () => import('@/views/rebate/index.vue'),
150 + meta: {
151 + title: '返利管理',
152 + requiresAuth: true
153 + }
154 + },
155 + {
165 path: 'settings', 156 path: 'settings',
166 name: 'Settings', 157 name: 'Settings',
167 component: () => import('@/views/settings/index.vue'), 158 component: () => import('@/views/settings/index.vue'),
......
...@@ -93,27 +93,27 @@ ...@@ -93,27 +93,27 @@
93 <th>出库日期</th> 93 <th>出库日期</th>
94 <th>关联订单编号</th> 94 <th>关联订单编号</th>
95 <th>出库状态</th> 95 <th>出库状态</th>
96 - <th>出库时间</th> 96 + <th>创建时间</th>
97 <th>操作</th> 97 <th>操作</th>
98 </tr> 98 </tr>
99 </thead> 99 </thead>
100 <tbody> 100 <tbody>
101 - <tr v-for="order in orderList" :key="order.deliveryId"> 101 + <tr v-for="delivery in deliveryList" :key="delivery.deliveryId">
102 - <td>{{ order.deliveryId }}</td> 102 + <td>{{ delivery.deliveryId }}</td>
103 - <td>{{ order.deliveryNo }}</td> 103 + <td>{{ delivery.deliveryNo }}</td>
104 - <td>{{ order.dealerCode }}</td> 104 + <td>{{ delivery.dealerCode }}</td>
105 - <td>{{ order.dealerName }}</td> 105 + <td>{{ delivery.dealerName }}</td>
106 - <td>{{ formatDate(order.deliveryDate) }}</td> 106 + <td>{{ formatDate(delivery.deliveryDate) }}</td>
107 - <td>{{ order.orderNo }}</td> 107 + <td>{{ delivery.orderNo }}</td>
108 <td> 108 <td>
109 - <span :class="getDeliveryStatusClass(order.deliveryStatus)"> 109 + <span :class="getDeliveryStatusClass(delivery.deliveryStatus)">
110 - {{ getDeliveryStatusText(order.deliveryStatus) }} 110 + {{ getDeliveryStatusText(delivery.deliveryStatus) }}
111 </span> 111 </span>
112 </td> 112 </td>
113 - <td>{{ formatTime(order.uploadTime || '') }}</td> 113 + <td>{{ formatTime(delivery.createTime || '') }}</td>
114 <td class="action-col"> 114 <td class="action-col">
115 - <button @click="handleView(order)" class="view-btn">👁️ 查看</button> 115 + <button @click="handleView(delivery)" class="view-btn">👁️ 查看</button>
116 - <button @click="handleDelete(order)" class="delete-btn">🗑️ 删除</button> 116 + <button @click="handleDelete(delivery)" class="delete-btn">🗑️ 删除</button>
117 </td> 117 </td>
118 </tr> 118 </tr>
119 </tbody> 119 </tbody>
...@@ -173,44 +173,48 @@ ...@@ -173,44 +173,48 @@
173 <button @click="closeDetailDialog" class="close-btn">×</button> 173 <button @click="closeDetailDialog" class="close-btn">×</button>
174 </div> 174 </div>
175 <div class="dialog-body"> 175 <div class="dialog-body">
176 - <div v-if="orderDetail" class="detail-content"> 176 + <div v-if="deliveryDetail" class="detail-content">
177 <div class="detail-section"> 177 <div class="detail-section">
178 - <h4>订单基本信息</h4> 178 + <h4>出库基本信息</h4>
179 <div class="detail-grid"> 179 <div class="detail-grid">
180 <div class="detail-item"> 180 <div class="detail-item">
181 - <label>订单编号:</label> 181 + <label>出库单号:</label>
182 - <span>{{ orderDetail.orderNo }}</span> 182 + <span>{{ deliveryDetail.deliveryNo }}</span>
183 + </div>
184 + <div class="detail-item">
185 + <label>关联订单编号:</label>
186 + <span>{{ deliveryDetail.orderNo }}</span>
183 </div> 187 </div>
184 <div class="detail-item"> 188 <div class="detail-item">
185 <label>经销商编码:</label> 189 <label>经销商编码:</label>
186 - <span>{{ orderDetail.dealerCode }}</span> 190 + <span>{{ deliveryDetail.dealerCode }}</span>
187 </div> 191 </div>
188 <div class="detail-item"> 192 <div class="detail-item">
189 <label>经销商名称:</label> 193 <label>经销商名称:</label>
190 - <span>{{ orderDetail.dealerName }}</span> 194 + <span>{{ deliveryDetail.dealerName }}</span>
191 </div> 195 </div>
192 <div class="detail-item"> 196 <div class="detail-item">
193 - <label>订单日期:</label> 197 + <label>出库日期:</label>
194 - <span>{{ formatDate(orderDetail.orderDate) }}</span> 198 + <span>{{ formatDate(deliveryDetail.deliveryDate) }}</span>
195 </div> 199 </div>
196 <div class="detail-item"> 200 <div class="detail-item">
197 - <label>订单总金额:</label> 201 + <label>出库总金额:</label>
198 - <span>{{ formatCurrency(orderDetail.totalAmount) }}</span> 202 + <span>{{ formatCurrency(deliveryDetail.totalAmount) }}</span>
199 </div> 203 </div>
200 <div class="detail-item"> 204 <div class="detail-item">
201 <label>出库状态:</label> 205 <label>出库状态:</label>
202 - <span :class="getDeliveryStatusClass(orderDetail.deliveryStatus)"> 206 + <span :class="getDeliveryStatusClass(deliveryDetail.deliveryStatus)">
203 - {{ getDeliveryStatusText(orderDetail.deliveryStatus) }} 207 + {{ getDeliveryStatusText(deliveryDetail.deliveryStatus) }}
204 </span> 208 </span>
205 </div> 209 </div>
206 <div class="detail-item"> 210 <div class="detail-item">
207 - <label>出库时间:</label> 211 + <label>创建时间:</label>
208 - <span>{{ formatTime(orderDetail.uploadTime || '') }}</span> 212 + <span>{{ formatTime(deliveryDetail.createTime || '') }}</span>
209 </div> 213 </div>
210 </div> 214 </div>
211 </div> 215 </div>
212 216
213 - <div class="detail-section" v-if="orderDetail.orderItems && orderDetail.orderItems.length > 0"> 217 + <div class="detail-section" v-if="deliveryDetail.deliveryItems && deliveryDetail.deliveryItems.length > 0">
214 <h4>出库明细</h4> 218 <h4>出库明细</h4>
215 <table class="detail-table"> 219 <table class="detail-table">
216 <thead> 220 <thead>
...@@ -218,17 +222,17 @@ ...@@ -218,17 +222,17 @@
218 <th>产品编码</th> 222 <th>产品编码</th>
219 <th>产品名称</th> 223 <th>产品名称</th>
220 <th>产品型号</th> 224 <th>产品型号</th>
221 - <th>数量</th> 225 + <th>出库数量</th>
222 - <th>单价</th> 226 + <th>出库单价</th>
223 - <th>总价</th> 227 + <th>出库总价</th>
224 </tr> 228 </tr>
225 </thead> 229 </thead>
226 <tbody> 230 <tbody>
227 - <tr v-for="item in orderDetail.orderItems" :key="item.itemId"> 231 + <tr v-for="item in deliveryDetail.deliveryItems" :key="item.itemId">
228 <td>{{ item.productCode }}</td> 232 <td>{{ item.productCode }}</td>
229 <td>{{ item.productName }}</td> 233 <td>{{ item.productName }}</td>
230 <td>{{ item.productModel }}</td> 234 <td>{{ item.productModel }}</td>
231 - <td>{{ item.quantity }}</td> 235 + <td>{{ item.productQty }}</td>
232 <td>{{ formatCurrency(item.unitPrice) }}</td> 236 <td>{{ formatCurrency(item.unitPrice) }}</td>
233 <td>{{ formatCurrency(item.totalPrice) }}</td> 237 <td>{{ formatCurrency(item.totalPrice) }}</td>
234 </tr> 238 </tr>
...@@ -415,9 +419,9 @@ import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQuery ...@@ -415,9 +419,9 @@ import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQuery
415 419
416 // 响应式数据 420 // 响应式数据
417 const loading = ref(false) 421 const loading = ref(false)
418 -const orderList = ref<DeliveryInfo[]>([]) 422 +const deliveryList = ref<DeliveryInfo[]>([])
419 const showDetailDialog = ref(false) 423 const showDetailDialog = ref(false)
420 -const orderDetail = ref<DeliveryInfo | null>(null) 424 +const deliveryDetail = ref<DeliveryInfo | null>(null)
421 425
422 // 新增出库相关数据 426 // 新增出库相关数据
423 const showAddDialog = ref(false) 427 const showAddDialog = ref(false)
...@@ -435,6 +439,7 @@ const addFormData = reactive({ ...@@ -435,6 +439,7 @@ const addFormData = reactive({
435 439
436 // 搜索参数 440 // 搜索参数
437 const searchParams = reactive({ 441 const searchParams = reactive({
442 + orderNo: '',
438 deliveryNo: '', 443 deliveryNo: '',
439 dealerCode: '', 444 dealerCode: '',
440 dealerName: '', 445 dealerName: '',
...@@ -443,6 +448,8 @@ const searchParams = reactive({ ...@@ -443,6 +448,8 @@ const searchParams = reactive({
443 dataSource: '', 448 dataSource: '',
444 deliveryStartDate: '', 449 deliveryStartDate: '',
445 deliveryEndDate: '', 450 deliveryEndDate: '',
451 + startDate: '',
452 + endDate: '',
446 pageNum: 1, 453 pageNum: 1,
447 pageSize: 10 454 pageSize: 10
448 }) 455 })
...@@ -511,7 +518,7 @@ const getPageNumbers = () => { ...@@ -511,7 +518,7 @@ const getPageNumbers = () => {
511 } 518 }
512 519
513 // 方法 520 // 方法
514 -const fetchOrders = async () => { 521 +const fetchDeliveries = async () => {
515 try { 522 try {
516 loading.value = true 523 loading.value = true
517 const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize } 524 const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
...@@ -520,12 +527,12 @@ const fetchOrders = async () => { ...@@ -520,12 +527,12 @@ const fetchOrders = async () => {
520 527
521 // 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data 528 // 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data
522 if (response && response.records) { 529 if (response && response.records) {
523 - orderList.value = response.records || [] 530 + deliveryList.value = response.records || []
524 pagination.total = response.total || 0 531 pagination.total = response.total || 0
525 pagination.pageNum = response.current || 1 532 pagination.pageNum = response.current || 1
526 pagination.pageSize = response.size || 10 533 pagination.pageSize = response.size || 10
527 } else { 534 } else {
528 - orderList.value = [] 535 + deliveryList.value = []
529 pagination.total = 0 536 pagination.total = 0
530 showMessage('获取出库列表失败', 'error') 537 showMessage('获取出库列表失败', 'error')
531 } 538 }
...@@ -539,7 +546,7 @@ const fetchOrders = async () => { ...@@ -539,7 +546,7 @@ const fetchOrders = async () => {
539 546
540 const handleSearch = () => { 547 const handleSearch = () => {
541 pagination.pageNum = 1 548 pagination.pageNum = 1
542 - fetchOrders() 549 + fetchDeliveries()
543 } 550 }
544 551
545 const handleReset = () => { 552 const handleReset = () => {
...@@ -552,33 +559,33 @@ const handleReset = () => { ...@@ -552,33 +559,33 @@ const handleReset = () => {
552 endDate: '' 559 endDate: ''
553 }) 560 })
554 pagination.pageNum = 1 561 pagination.pageNum = 1
555 - fetchOrders() 562 + fetchDeliveries()
556 } 563 }
557 564
558 -const handleView = async (order: DeliveryInfo) => { 565 +const handleView = async (delivery: DeliveryInfo) => {
559 try { 566 try {
560 - const response = await deliveryApi.getDeliveryDetail(order.deliveryId) as any 567 + const response = await deliveryApi.getDeliveryDetail(delivery.deliveryId) as any
561 console.log('出库查询详情API响应:', response) 568 console.log('出库查询详情API响应:', response)
562 569
563 // 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data 570 // 由于request.ts的响应拦截器已经处理了code=200的情况,直接返回data
564 if (response && response.deliveryId) { 571 if (response && response.deliveryId) {
565 - orderDetail.value = response 572 + deliveryDetail.value = response
566 showDetailDialog.value = true 573 showDetailDialog.value = true
567 } else { 574 } else {
568 - showMessage('获取订单详情失败', 'error') 575 + showMessage('获取出库详情失败', 'error')
569 } 576 }
570 } catch (error) { 577 } catch (error) {
571 - console.error('获取订单详情失败:', error) 578 + console.error('获取出库详情失败:', error)
572 - showMessage('获取订单详情失败, 请重试', 'error') 579 + showMessage('获取出库详情失败, 请重试', 'error')
573 } 580 }
574 } 581 }
575 582
576 -const handleDelete = async (order: DeliveryInfo) => { 583 +const handleDelete = async (delivery: DeliveryInfo) => {
577 - if (confirm(`确定要删除出库单"${order.deliveryNo}"吗?`)) { 584 + if (confirm(`确定要删除出库单"${delivery.deliveryNo}"吗?`)) {
578 try { 585 try {
579 - await deliveryApi.deleteDelivery(order.deliveryId) 586 + await deliveryApi.deleteDelivery(delivery.deliveryId)
580 showMessage('删除出库单成功', 'success') 587 showMessage('删除出库单成功', 'success')
581 - fetchOrders() // 刷新列表 588 + fetchDeliveries() // 刷新列表
582 } catch (error) { 589 } catch (error) {
583 console.error('删除出库单失败:', error) 590 console.error('删除出库单失败:', error)
584 showMessage('删除出库单失败, 请重试', 'error') 591 showMessage('删除出库单失败, 请重试', 'error')
...@@ -655,16 +662,16 @@ const handleSubmitAdd = async () => { ...@@ -655,16 +662,16 @@ const handleSubmitAdd = async () => {
655 deliveryItems: addFormData.deliveryItems.map(item => ({ 662 deliveryItems: addFormData.deliveryItems.map(item => ({
656 productCode: item.productCode, 663 productCode: item.productCode,
657 productName: item.productName, 664 productName: item.productName,
658 - deliveryQty: item.deliveryQty, 665 + productQty: item.deliveryQty,
659 - deliveryPrice: item.deliveryPrice, 666 + unitPrice: item.deliveryPrice,
660 - deliveryAmount: item.deliveryAmount 667 + totalPrice: item.deliveryAmount
661 })) 668 }))
662 } 669 }
663 670
664 await deliveryApi.addDelivery(deliveryData) 671 await deliveryApi.addDelivery(deliveryData)
665 showMessage('新增出库成功', 'success') 672 showMessage('新增出库成功', 'success')
666 closeAddDialog() 673 closeAddDialog()
667 - fetchOrders() // 刷新列表 674 + fetchDeliveries() // 刷新列表
668 } catch (error) { 675 } catch (error) {
669 console.error('新增出库失败:', error) 676 console.error('新增出库失败:', error)
670 showMessage('新增出库失败, 请重试', 'error') 677 showMessage('新增出库失败, 请重试', 'error')
...@@ -676,13 +683,13 @@ const handleSubmitAdd = async () => { ...@@ -676,13 +683,13 @@ const handleSubmitAdd = async () => {
676 const handlePageChange = (page: number) => { 683 const handlePageChange = (page: number) => {
677 if (page >= 1 && page <= totalPages.value) { 684 if (page >= 1 && page <= totalPages.value) {
678 pagination.pageNum = page 685 pagination.pageNum = page
679 - fetchOrders() 686 + fetchDeliveries()
680 } 687 }
681 } 688 }
682 689
683 const handlePageSizeChange = () => { 690 const handlePageSizeChange = () => {
684 pagination.pageNum = 1 691 pagination.pageNum = 1
685 - fetchOrders() 692 + fetchDeliveries()
686 } 693 }
687 694
688 const handleTableSearch = () => { 695 const handleTableSearch = () => {
...@@ -690,7 +697,7 @@ const handleTableSearch = () => { ...@@ -690,7 +697,7 @@ const handleTableSearch = () => {
690 } 697 }
691 698
692 const handleTableRefresh = () => { 699 const handleTableRefresh = () => {
693 - fetchOrders() 700 + fetchDeliveries()
694 } 701 }
695 702
696 const handleTableExport = () => { 703 const handleTableExport = () => {
...@@ -703,12 +710,12 @@ const handleTableViewToggle = () => { ...@@ -703,12 +710,12 @@ const handleTableViewToggle = () => {
703 710
704 const closeDetailDialog = () => { 711 const closeDetailDialog = () => {
705 showDetailDialog.value = false 712 showDetailDialog.value = false
706 - orderDetail.value = null 713 + deliveryDetail.value = null
707 } 714 }
708 715
709 // 生命周期 716 // 生命周期
710 onMounted(() => { 717 onMounted(() => {
711 - fetchOrders() 718 + fetchDeliveries()
712 }) 719 })
713 </script> 720 </script>
714 721
......
1 +<template>
2 + <div class="rebate-page">
3 + <!-- 搜索区域 -->
4 + <div class="search-section">
5 + <div class="search-form">
6 + <div class="search-row">
7 + <div class="search-item">
8 + <span class="search-label">经销商名称</span>
9 + <el-input
10 + v-model="searchForm.dealerName"
11 + placeholder="请输入经销商名称"
12 + clearable
13 + size="small"
14 + style="width: 160px"
15 + />
16 + </div>
17 + <div class="search-item">
18 + <span class="search-label">返利编号</span>
19 + <el-input
20 + v-model="searchForm.rebateNo"
21 + placeholder="请输入返利编号"
22 + clearable
23 + size="small"
24 + style="width: 160px"
25 + />
26 + </div>
27 + <div class="search-item">
28 + <span class="search-label">日期</span>
29 + <el-date-picker
30 + v-model="dateRange"
31 + type="daterange"
32 + range-separator="至"
33 + start-placeholder="开始日期"
34 + end-placeholder="结束日期"
35 + size="small"
36 + format="YYYY-MM-DD"
37 + value-format="YYYY-MM-DD"
38 + @change="handleDateRangeChange"
39 + style="width: 240px"
40 + />
41 + </div>
42 + <div class="search-actions">
43 + <el-button type="primary" size="small" @click="handleSearch" :loading="loading">
44 + 查询
45 + </el-button>
46 + <el-button size="small" @click="handleReset">
47 + 重置
48 + </el-button>
49 + </div>
50 + </div>
51 + </div>
52 + </div>
53 +
54 + <!-- 图表区域 -->
55 + <div class="charts-section">
56 + <div class="charts-container">
57 + <!-- 左侧趋势图 -->
58 + <div class="chart-card trend-chart">
59 + <div class="chart-header">
60 + <h3 class="chart-title">返利统计</h3>
61 + <div class="chart-legend">
62 + <div class="legend-item">
63 + <span class="legend-dot" style="background-color: #4CAF50;"></span>
64 + <span>返利金额</span>
65 + </div>
66 + <div class="legend-item">
67 + <span class="legend-dot" style="background-color: #2196F3;"></span>
68 + <span>已审核返利</span>
69 + </div>
70 + </div>
71 + </div>
72 + <div class="chart-content">
73 + <div ref="trendChart" class="chart"></div>
74 + </div>
75 + </div>
76 +
77 + <!-- 右侧饼图 -->
78 + <div class="chart-card pie-chart">
79 + <div class="chart-header">
80 + <h3 class="chart-title">计算统计</h3>
81 + <div class="chart-legend">
82 + <div class="legend-item">
83 + <span class="legend-dot" style="background-color: #2196F3;"></span>
84 + <span>已计算</span>
85 + </div>
86 + <div class="legend-item">
87 + <span class="legend-dot" style="background-color: #4CAF50;"></span>
88 + <span>未计算</span>
89 + </div>
90 + </div>
91 + </div>
92 + <div class="chart-content">
93 + <div ref="pieChart" class="chart"></div>
94 + </div>
95 + </div>
96 + </div>
97 + </div>
98 +
99 + <!-- 数据表格 -->
100 + <div class="table-section">
101 + <div class="table-header">
102 + <div class="table-title">
103 + 返利记录
104 + </div>
105 + <div class="table-info">
106 + 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }}
107 + </div>
108 + </div>
109 +
110 + <el-table
111 + v-loading="loading"
112 + :data="rebateList"
113 + style="width: 100%"
114 + :header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 'normal' }"
115 + :empty-text="rebateList.length === 0 ? '暂无数据' : ''"
116 + size="small"
117 + >
118 + <el-table-column prop="rebateNo" label="返利编号" width="140" align="center" />
119 + <el-table-column prop="orderNo" label="订单编号" width="140" align="center" />
120 + <el-table-column prop="dealerCode" label="经销商代码" width="120" align="center" />
121 + <el-table-column prop="dealerName" label="经销商名称" min-width="150" />
122 + <el-table-column prop="productCode" label="产品编码" width="120" align="center" />
123 + <el-table-column prop="rebateAmount" label="返利金额" width="120" align="right">
124 + <template #default="{ row }">
125 + <span class="amount-text">{{ formatAmount(row.rebateAmount) }}</span>
126 + </template>
127 + </el-table-column>
128 + <el-table-column prop="rebateDate" label="返利日期" width="120" align="center" />
129 + <el-table-column prop="operateTypeText" label="操作类型" width="100" align="center">
130 + <template #default="{ row }">
131 + <el-tag :type="getOperateTypeTagType(row.operateType)" size="small">
132 + {{ row.operateTypeText || getOperateTypeText(row.operateType) }}
133 + </el-tag>
134 + </template>
135 + </el-table-column>
136 + <el-table-column prop="calcFlagText" label="计算状态" width="100" align="center">
137 + <template #default="{ row }">
138 + <el-tag :type="getCalcFlagTagType(row.calcFlag)" size="small">
139 + {{ row.calcFlagText || getCalcFlagText(row.calcFlag) }}
140 + </el-tag>
141 + </template>
142 + </el-table-column>
143 + <el-table-column prop="updateTime" label="更新时间" width="150" align="center">
144 + <template #default="{ row }">
145 + {{ formatDate(row.updateTime) }}
146 + </template>
147 + </el-table-column>
148 + <el-table-column label="操作" width="80" align="center">
149 + <template #default="{ row }">
150 + <el-button type="primary" size="small" link @click="handleViewDetail(row)">
151 + 详情
152 + </el-button>
153 + </template>
154 + </el-table-column>
155 + </el-table>
156 +
157 + <!-- 分页 -->
158 + <div class="pagination-wrapper">
159 + <div class="pagination-info">
160 + 共 {{ pagination.total }} 条,{{ pagination.pageSize }}/页
161 + </div>
162 + <el-pagination
163 + v-model:current-page="pagination.pageNum"
164 + v-model:page-size="pagination.pageSize"
165 + :page-sizes="[10, 20, 50, 100]"
166 + :total="pagination.total"
167 + layout="sizes, prev, pager, next"
168 + @size-change="handleSizeChange"
169 + @current-change="handleCurrentChange"
170 + small
171 + />
172 + <div class="pagination-jump">
173 + 前往
174 + <el-input
175 + v-model="jumpPage"
176 + size="small"
177 + style="width: 50px; margin: 0 8px;"
178 + @keyup.enter="handleJumpPage"
179 + />
180 +
181 + </div>
182 + </div>
183 + </div>
184 +
185 + <!-- 返利详情对话框 -->
186 + <el-dialog
187 + v-model="detailDialogVisible"
188 + title="返利详情"
189 + width="800px"
190 + :close-on-click-modal="false"
191 + >
192 + <div v-if="rebateDetail" class="rebate-detail">
193 + <el-descriptions :column="2" border>
194 + <el-descriptions-item label="返利编号">
195 + {{ rebateDetail.rebateNo }}
196 + </el-descriptions-item>
197 + <el-descriptions-item label="订单编号">
198 + {{ rebateDetail.orderNo }}
199 + </el-descriptions-item>
200 + <el-descriptions-item label="经销商代码">
201 + {{ rebateDetail.dealerCode }}
202 + </el-descriptions-item>
203 + <el-descriptions-item label="经销商名称">
204 + {{ rebateDetail.dealerName }}
205 + </el-descriptions-item>
206 + <el-descriptions-item label="产品编码">
207 + {{ rebateDetail.productCode }}
208 + </el-descriptions-item>
209 + <el-descriptions-item label="返利金额">
210 + <span class="amount-text">¥{{ formatAmount(rebateDetail.rebateAmount) }}</span>
211 + </el-descriptions-item>
212 + <el-descriptions-item label="返利日期">
213 + {{ rebateDetail.rebateDate }}
214 + </el-descriptions-item>
215 + <el-descriptions-item label="操作类型">
216 + <el-tag :type="getOperateTypeTagType(rebateDetail.operateType)">
217 + {{ rebateDetail.operateTypeText || getOperateTypeText(rebateDetail.operateType) }}
218 + </el-tag>
219 + </el-descriptions-item>
220 + <el-descriptions-item label="计算状态">
221 + <el-tag :type="getCalcFlagTagType(rebateDetail.calcFlag)">
222 + {{ rebateDetail.calcFlagText || getCalcFlagText(rebateDetail.calcFlag) }}
223 + </el-tag>
224 + </el-descriptions-item>
225 + <el-descriptions-item label="创建时间">
226 + {{ rebateDetail.createTime ? formatDate(rebateDetail.createTime) : '-' }}
227 + </el-descriptions-item>
228 + <el-descriptions-item label="更新时间">
229 + {{ rebateDetail.updateTime ? formatDate(rebateDetail.updateTime) : '-' }}
230 + </el-descriptions-item>
231 + <el-descriptions-item label="上传时间">
232 + {{ rebateDetail.uploadTime ? formatDate(rebateDetail.uploadTime) : '-' }}
233 + </el-descriptions-item>
234 + </el-descriptions>
235 + </div>
236 +
237 + <template #footer>
238 + <el-button @click="detailDialogVisible = false">关闭</el-button>
239 + </template>
240 + </el-dialog>
241 +
242 + <!-- 新增/编辑返利对话框 -->
243 + <el-dialog
244 + v-model="formDialogVisible"
245 + :title="isEdit ? '编辑返利' : '新增返利'"
246 + width="600px"
247 + :close-on-click-modal="false"
248 + >
249 + <el-form
250 + ref="rebateFormRef"
251 + :model="rebateForm"
252 + :rules="rebateFormRules"
253 + label-width="100px"
254 + >
255 + <el-form-item label="经销商" prop="dealerName">
256 + <el-input v-model="rebateForm.dealerName" placeholder="请输入经销商名称" />
257 + </el-form-item>
258 +
259 + <el-form-item label="产品名称" prop="productName">
260 + <el-input v-model="rebateForm.productName" placeholder="请输入产品名称" />
261 + </el-form-item>
262 +
263 + <el-form-item label="产品型号">
264 + <el-input v-model="rebateForm.productModel" placeholder="请输入产品型号" />
265 + </el-form-item>
266 +
267 + <el-form-item label="返利类型" prop="rebateType">
268 + <el-select v-model="rebateForm.rebateType" placeholder="请选择返利类型" style="width: 100%">
269 + <el-option label="销量返利" :value="1" />
270 + <el-option label="业绩返利" :value="2" />
271 + <el-option label="年度返利" :value="3" />
272 + <el-option label="特殊返利" :value="4" />
273 + </el-select>
274 + </el-form-item>
275 +
276 + <el-form-item label="返利政策" prop="policyName">
277 + <el-input v-model="rebateForm.policyName" placeholder="请输入返利政策名称" />
278 + </el-form-item>
279 +
280 + <el-form-item label="计算基数" prop="calculationBase">
281 + <el-input-number
282 + v-model="rebateForm.calculationBase"
283 + :precision="2"
284 + :min="0"
285 + style="width: 100%"
286 + placeholder="请输入计算基数"
287 + />
288 + </el-form-item>
289 +
290 + <el-form-item label="返利比例(%)" prop="rebateRate">
291 + <el-input-number
292 + v-model="rebateForm.rebateRate"
293 + :precision="2"
294 + :min="0"
295 + :max="100"
296 + style="width: 100%"
297 + placeholder="请输入返利比例"
298 + />
299 + </el-form-item>
300 +
301 + <el-form-item label="返利金额" prop="rebateAmount">
302 + <el-input-number
303 + v-model="rebateForm.rebateAmount"
304 + :precision="2"
305 + :min="0"
306 + style="width: 100%"
307 + placeholder="请输入返利金额"
308 + />
309 + </el-form-item>
310 +
311 + <el-form-item label="返利周期" prop="periodRange">
312 + <el-date-picker
313 + v-model="periodRange"
314 + type="datetimerange"
315 + range-separator="至"
316 + start-placeholder="开始时间"
317 + end-placeholder="结束时间"
318 + format="YYYY-MM-DD HH:mm:ss"
319 + value-format="YYYY-MM-DD HH:mm:ss"
320 + style="width: 100%"
321 + />
322 + </el-form-item>
323 +
324 + <el-form-item label="申请备注">
325 + <el-input
326 + v-model="rebateForm.applyRemark"
327 + type="textarea"
328 + :rows="3"
329 + placeholder="请输入申请备注"
330 + />
331 + </el-form-item>
332 + </el-form>
333 +
334 + <template #footer>
335 + <el-button @click="formDialogVisible = false">取消</el-button>
336 + <el-button type="primary" @click="handleSubmit" :loading="submitLoading">
337 + 确定
338 + </el-button>
339 + </template>
340 + </el-dialog>
341 +
342 + <!-- 审核对话框 -->
343 + <el-dialog
344 + v-model="auditDialogVisible"
345 + title="审核返利"
346 + width="500px"
347 + :close-on-click-modal="false"
348 + >
349 + <el-form
350 + ref="auditFormRef"
351 + :model="auditForm"
352 + :rules="auditFormRules"
353 + label-width="100px"
354 + >
355 + <el-form-item label="审核结果" prop="status">
356 + <el-radio-group v-model="auditForm.status">
357 + <el-radio :label="1">审核通过</el-radio>
358 + <el-radio :label="3">审核拒绝</el-radio>
359 + </el-radio-group>
360 + </el-form-item>
361 +
362 + <el-form-item label="审核备注">
363 + <el-input
364 + v-model="auditForm.auditRemark"
365 + type="textarea"
366 + :rows="4"
367 + placeholder="请输入审核备注"
368 + />
369 + </el-form-item>
370 + </el-form>
371 +
372 + <template #footer>
373 + <el-button @click="auditDialogVisible = false">取消</el-button>
374 + <el-button type="primary" @click="handleSubmitAudit" :loading="auditLoading">
375 + 确定
376 + </el-button>
377 + </template>
378 + </el-dialog>
379 + </div>
380 +</template>
381 +
382 +<script setup lang="ts">
383 +import { ref, reactive, onMounted } from 'vue'
384 +import { ElMessage, ElMessageBox } from 'element-plus'
385 +import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue'
386 +import * as echarts from 'echarts'
387 +import rebateApi, { type Rebate, type RebateSearchParams } from '@/api/rebate'
388 +import { formatDate } from '@/utils/index'
389 +
390 +// 响应式数据
391 +const loading = ref(false)
392 +const submitLoading = ref(false)
393 +const auditLoading = ref(false)
394 +const rebateList = ref<Rebate[]>([])
395 +const selectedRebates = ref<Rebate[]>([])
396 +const rebateDetail = ref<Rebate | null>(null)
397 +
398 +// 图表引用
399 +const trendChart = ref<HTMLElement>()
400 +const pieChart = ref<HTMLElement>()
401 +
402 +// 日期范围
403 +const dateRange = ref<[string, string] | null>(null)
404 +
405 +// 对话框显示状态
406 +const detailDialogVisible = ref(false)
407 +const formDialogVisible = ref(false)
408 +const auditDialogVisible = ref(false)
409 +const isEdit = ref(false)
410 +
411 +// 分页数据
412 +const pagination = reactive({
413 + pageNum: 1,
414 + pageSize: 10,
415 + total: 0
416 +})
417 +
418 +// 页面跳转
419 +const jumpPage = ref<number | string>('')
420 +
421 +// 搜索表单
422 +const searchForm = reactive<RebateSearchParams>({
423 + pageNum: 1,
424 + pageSize: 10,
425 + rebateNo: '',
426 + dealerCode: '',
427 + dealerName: '',
428 + productCode: '',
429 + operateType: undefined,
430 + calcFlag: undefined,
431 + rebateStartDate: '',
432 + rebateEndDate: ''
433 +})
434 +
435 +// 日期范围
436 +const applyDateRange = ref<[string, string] | null>(null)
437 +const periodRange = ref<[string, string] | null>(null)
438 +
439 +// 返利表单
440 +const rebateFormRef = ref()
441 +const rebateForm = reactive({
442 + rebateId: undefined,
443 + dealerId: 1, // 这里应该从经销商选择器获取
444 + dealerName: '',
445 + productId: 1, // 这里应该从产品选择器获取
446 + productName: '',
447 + productModel: '',
448 + rebateType: undefined,
449 + policyName: '',
450 + calculationBase: undefined,
451 + rebateRate: undefined,
452 + rebateAmount: undefined,
453 + periodStart: '',
454 + periodEnd: '',
455 + applyRemark: ''
456 +})
457 +
458 +// 审核表单
459 +const auditFormRef = ref()
460 +const auditForm = reactive({
461 + rebateId: undefined as number | undefined,
462 + status: undefined as number | undefined,
463 + auditRemark: ''
464 +})
465 +
466 +// 表单验证规则
467 +const rebateFormRules = {
468 + dealerName: [
469 + { required: true, message: '请输入经销商名称', trigger: 'blur' }
470 + ],
471 + productName: [
472 + { required: true, message: '请输入产品名称', trigger: 'blur' }
473 + ],
474 + rebateType: [
475 + { required: true, message: '请选择返利类型', trigger: 'change' }
476 + ],
477 + policyName: [
478 + { required: true, message: '请输入返利政策名称', trigger: 'blur' }
479 + ],
480 + calculationBase: [
481 + { required: true, message: '请输入计算基数', trigger: 'blur' }
482 + ],
483 + rebateRate: [
484 + { required: true, message: '请输入返利比例', trigger: 'blur' }
485 + ],
486 + rebateAmount: [
487 + { required: true, message: '请输入返利金额', trigger: 'blur' }
488 + ]
489 +}
490 +
491 +const auditFormRules = {
492 + status: [
493 + { required: true, message: '请选择审核结果', trigger: 'change' }
494 + ]
495 +}
496 +
497 +// 获取返利类型标签类型
498 +const getRebateTypeTagType = (rebateType: number) => {
499 + switch (rebateType) {
500 + case 1:
501 + return 'primary'
502 + case 2:
503 + return 'success'
504 + case 3:
505 + return 'warning'
506 + case 4:
507 + return 'danger'
508 + default:
509 + return 'info'
510 + }
511 +}
512 +
513 +// 获取状态标签类型
514 +const getStatusTagType = (status: number) => {
515 + switch (status) {
516 + case 0:
517 + return 'warning'
518 + case 1:
519 + return 'success'
520 + case 2:
521 + return 'primary'
522 + case 3:
523 + return 'danger'
524 + default:
525 + return 'info'
526 + }
527 +}
528 +
529 +// 格式化金额
530 +const formatAmount = (amount: number) => {
531 + return amount ? amount.toLocaleString('zh-CN', { minimumFractionDigits: 2 }) : '0.00'
532 +}
533 +
534 +// 获取操作类型文本
535 +const getOperateTypeText = (operateType: number) => {
536 + switch (operateType) {
537 + case 1: return '新增'
538 + case 2: return '修改'
539 + case 3: return '删除'
540 + default: return '未知'
541 + }
542 +}
543 +
544 +// 获取操作类型标签类型
545 +const getOperateTypeTagType = (operateType: number) => {
546 + switch (operateType) {
547 + case 1: return 'success'
548 + case 2: return 'warning'
549 + case 3: return 'danger'
550 + default: return 'info'
551 + }
552 +}
553 +
554 +// 获取计算状态文本
555 +const getCalcFlagText = (calcFlag: number | undefined) => {
556 + switch (calcFlag) {
557 + case 0: return '未计算'
558 + case 1: return '已计算'
559 + default: return '未知'
560 + }
561 +}
562 +
563 +// 获取计算状态标签类型
564 +const getCalcFlagTagType = (calcFlag: number | undefined) => {
565 + switch (calcFlag) {
566 + case 0: return 'warning'
567 + case 1: return 'success'
568 + default: return 'info'
569 + }
570 +}
571 +
572 +// 获取返利列表
573 +const getRebateList = async () => {
574 + try {
575 + loading.value = true
576 + const params = {
577 + ...searchForm,
578 + pageNum: pagination.pageNum,
579 + pageSize: pagination.pageSize
580 + }
581 +
582 + console.log('请求参数:', params)
583 + const response = await rebateApi.getRebatePage(params)
584 + console.log('完整API响应:', response)
585 + console.log('响应数据结构:', response.data)
586 + console.log('响应对象类型:', typeof response)
587 + console.log('响应对象键:', Object.keys(response || {}))
588 +
589 + // 检查不同层级的数据
590 + if (response) {
591 + console.log('response存在')
592 + if (response.data !== undefined) {
593 + console.log('response.data存在:', response.data)
594 + } else {
595 + console.log('response.data不存在,检查其他字段')
596 + console.log('response直接内容:', response)
597 + }
598 + }
599 +
600 + // 根据后端实际返回的数据结构进行解析
601 + let dataList = []
602 + let total = 0
603 +
604 + if (response) {
605 + let responseData = null
606 +
607 + // 首先尝试获取数据,response.data可能不存在
608 + if (response.data !== undefined) {
609 + responseData = response.data
610 + console.log('使用response.data:', responseData)
611 + } else {
612 + // 如果response.data不存在,直接使用response
613 + responseData = response
614 + console.log('直接使用response:', responseData)
615 + }
616 +
617 + if (responseData) {
618 + // 情况1: 直接返回数组
619 + if (Array.isArray(responseData)) {
620 + dataList = responseData
621 + total = responseData.length
622 + console.log('情况1: 直接数组,长度:', dataList.length)
623 + }
624 + // 情况2: 包含records字段(MyBatis Plus分页格式)
625 + else if (responseData.records && Array.isArray(responseData.records)) {
626 + dataList = responseData.records
627 + total = responseData.total || responseData.records.length
628 + console.log('情况2: records格式,长度:', dataList.length, '总数:', total)
629 + }
630 + // 情况3: 包含data字段的嵌套结构
631 + else if (responseData.data) {
632 + if (Array.isArray(responseData.data)) {
633 + dataList = responseData.data
634 + total = responseData.total || responseData.data.length
635 + console.log('情况3a: data数组格式,长度:', dataList.length)
636 + } else if (responseData.data.records) {
637 + dataList = responseData.data.records
638 + total = responseData.data.total || 0
639 + console.log('情况3b: data.records格式,长度:', dataList.length)
640 + }
641 + }
642 + // 情况4: 其他对象格式,寻找数组字段
643 + else if (typeof responseData === 'object' && responseData !== null) {
644 + const keys = Object.keys(responseData)
645 + console.log('对象的所有键:', keys)
646 +
647 + // 寻找可能的数组字段
648 + const arrayFields = keys.filter(key => Array.isArray(responseData[key]))
649 + console.log('数组字段:', arrayFields)
650 +
651 + if (arrayFields.length > 0) {
652 + const arrayField = arrayFields[0]
653 + dataList = responseData[arrayField]
654 + total = responseData.total || responseData.count || dataList.length
655 + console.log('情况4: 找到数组字段', arrayField, '长度:', dataList.length)
656 + }
657 + }
658 + }
659 + }
660 +
661 + rebateList.value = dataList
662 + pagination.total = total
663 +
664 + console.log('最终解析的列表数据:', rebateList.value)
665 + console.log('数据条数:', rebateList.value.length)
666 + console.log('分页总数:', pagination.total)
667 +
668 + if (rebateList.value.length === 0) {
669 + console.warn('解析后的数据为空,请检查数据结构')
670 + console.warn('如果需要测试,可以取消注释下面的模拟数据')
671 +
672 + // 临时模拟数据用于测试(可以取消注释来测试表格显示)
673 + /*
674 + rebateList.value = [
675 + {
676 + rebateId: 1,
677 + rebateNo: 'RB2025-001',
678 + orderNo: 'ORD-2025-001',
679 + dealerCode: 'DEALER001',
680 + dealerName: '测试经销商1',
681 + productCode: 'PROD001',
682 + rebateAmount: 1000.00,
683 + rebateDate: '2025-01-15',
684 + operateType: 1,
685 + calcFlag: 0,
686 + createTime: '2025-01-15 10:00:00',
687 + updateTime: '2025-01-15 10:00:00'
688 + }
689 + ]
690 + pagination.total = 1
691 + */
692 + }
693 +
694 + } catch (error) {
695 + console.error('获取返利列表失败:', error)
696 + ElMessage.error('获取返利列表失败')
697 + rebateList.value = []
698 + pagination.total = 0
699 + } finally {
700 + loading.value = false
701 + }
702 +}
703 +
704 +// 处理日期范围变化
705 +const handleDateRangeChange = (dates: [string, string] | null) => {
706 + if (dates) {
707 + searchForm.rebateStartDate = dates[0]
708 + searchForm.rebateEndDate = dates[1]
709 + } else {
710 + searchForm.rebateStartDate = ''
711 + searchForm.rebateEndDate = ''
712 + }
713 +}
714 +
715 +// 搜索
716 +const handleSearch = () => {
717 + pagination.pageNum = 1
718 + getRebateList()
719 +}
720 +
721 +// 重置
722 +const handleReset = () => {
723 + Object.assign(searchForm, {
724 + pageNum: 1,
725 + pageSize: 10,
726 + rebateNo: undefined,
727 + dealerName: undefined,
728 + productName: undefined,
729 + rebateType: undefined,
730 + status: undefined,
731 + applyStartTime: undefined,
732 + applyEndTime: undefined
733 + })
734 + applyDateRange.value = null
735 + pagination.pageNum = 1
736 + getRebateList()
737 +}
738 +
739 +// 刷新
740 +const handleRefresh = () => {
741 + getRebateList()
742 +}
743 +
744 +// 分页变化
745 +const handleSizeChange = (size: number) => {
746 + pagination.pageSize = size
747 + pagination.pageNum = 1
748 + getRebateList()
749 +}
750 +
751 +const handleCurrentChange = (page: number) => {
752 + pagination.pageNum = page
753 + getRebateList()
754 +}
755 +
756 +// 页面跳转
757 +const handleJumpPage = () => {
758 + const page = Number(jumpPage.value)
759 + if (page && page > 0 && page <= Math.ceil(pagination.total / pagination.pageSize)) {
760 + pagination.pageNum = page
761 + getRebateList()
762 + jumpPage.value = ''
763 + }
764 +}
765 +
766 +// 选择变化
767 +const handleSelectionChange = (selection: Rebate[]) => {
768 + selectedRebates.value = selection
769 +}
770 +
771 +// 查看详情
772 +const handleViewDetail = async (row: Rebate) => {
773 + try {
774 + if (!row.rebateId) return
775 + console.log('查看详情,返利ID:', row.rebateId)
776 +
777 + const response = await rebateApi.getRebateById(row.rebateId)
778 + console.log('详情接口响应:', response)
779 +
780 + // 根据request.ts拦截器的处理,response.data已经是实际数据
781 + if (response && response.data) {
782 + rebateDetail.value = response.data
783 + detailDialogVisible.value = true
784 + console.log('详情数据:', response.data)
785 + } else {
786 + ElMessage.error('获取返利详情失败')
787 + }
788 + } catch (error) {
789 + console.error('获取返利详情失败:', error)
790 + ElMessage.error('获取返利详情失败')
791 + }
792 +}
793 +
794 +// 新增
795 +const handleAdd = () => {
796 + isEdit.value = false
797 + resetRebateForm()
798 + formDialogVisible.value = true
799 +}
800 +
801 +// 编辑
802 +const handleEdit = (row: Rebate) => {
803 + ElMessage.info(`编辑返利记录:${row.rebateNo}`)
804 + // 这里可以打开编辑对话框或跳转到编辑页面
805 +}
806 +
807 +// 标记已计算
808 +const handleMarkCalculated = async (row: Rebate) => {
809 + try {
810 + await ElMessageBox.confirm('确认标记该返利为已计算吗?', '确认操作', {
811 + type: 'warning'
812 + })
813 +
814 + await rebateApi.markRebateCalculated(row.rebateId!)
815 + ElMessage.success('标记成功')
816 + getRebateList()
817 + } catch (error) {
818 + if (error !== 'cancel') {
819 + console.error('标记失败:', error)
820 + ElMessage.error('标记失败')
821 + }
822 + }
823 +}
824 +
825 +// 重置表单
826 +const resetRebateForm = () => {
827 + Object.assign(rebateForm, {
828 + rebateId: undefined,
829 + dealerId: 1,
830 + dealerName: '',
831 + productId: 1,
832 + productName: '',
833 + productModel: '',
834 + rebateType: undefined,
835 + policyName: '',
836 + calculationBase: undefined,
837 + rebateRate: undefined,
838 + rebateAmount: undefined,
839 + periodStart: '',
840 + periodEnd: '',
841 + applyRemark: ''
842 + })
843 + periodRange.value = null
844 + rebateFormRef.value?.clearValidate()
845 +}
846 +
847 +// 提交表单
848 +const handleSubmit = async () => {
849 + try {
850 + await rebateFormRef.value?.validate()
851 +
852 + if (periodRange.value) {
853 + rebateForm.periodStart = periodRange.value[0]
854 + rebateForm.periodEnd = periodRange.value[1]
855 + }
856 +
857 + submitLoading.value = true
858 +
859 + if (isEdit.value) {
860 + await rebateApi.updateRebate(rebateForm as any)
861 + ElMessage.success('更新返利成功')
862 + } else {
863 + await rebateApi.addRebate(rebateForm as any)
864 + ElMessage.success('新增返利成功')
865 + }
866 +
867 + formDialogVisible.value = false
868 + getRebateList()
869 + } catch (error) {
870 + console.error('提交表单失败:', error)
871 + ElMessage.error('操作失败')
872 + } finally {
873 + submitLoading.value = false
874 + }
875 +}
876 +
877 +// 删除
878 +const handleDelete = async (row: Rebate) => {
879 + try {
880 + await ElMessageBox.confirm('确认删除该返利记录吗?', '确认删除', {
881 + type: 'warning'
882 + })
883 +
884 + const { data } = await rebateApi.deleteRebate(row.rebateId!)
885 + if (data.code === 200) {
886 + ElMessage.success('删除成功')
887 + getRebateList()
888 + } else {
889 + ElMessage.error(data.message || '删除失败')
890 + }
891 + } catch (error) {
892 + if (error !== 'cancel') {
893 + console.error('删除返利失败:', error)
894 + ElMessage.error('删除返利失败')
895 + }
896 + }
897 +}
898 +
899 +// 批量标记已计算
900 +const handleBatchMarkCalculated = async () => {
901 + try {
902 + if (selectedRebates.value.length === 0) {
903 + ElMessage.warning('请选择要标记的记录')
904 + return
905 + }
906 +
907 + await ElMessageBox.confirm(`确认标记选中的 ${selectedRebates.value.length} 条返利记录为已计算吗?`, '确认操作', {
908 + type: 'warning'
909 + })
910 +
911 + const rebateIds = selectedRebates.value.map(item => item.rebateId!)
912 + const { data } = await rebateApi.batchMarkRebateCalculated(rebateIds)
913 +
914 + if (data.code === 200) {
915 + ElMessage.success('批量标记成功')
916 + getRebateList()
917 + } else {
918 + ElMessage.error(data.message || '批量标记失败')
919 + }
920 + } catch (error) {
921 + if (error !== 'cancel') {
922 + console.error('批量标记失败:', error)
923 + ElMessage.error('批量标记失败')
924 + }
925 + }
926 +}
927 +
928 +// 批量删除
929 +const handleBatchDelete = async () => {
930 + try {
931 + if (selectedRebates.value.length === 0) {
932 + ElMessage.warning('请选择要删除的记录')
933 + return
934 + }
935 +
936 + await ElMessageBox.confirm(`确认删除选中的 ${selectedRebates.value.length} 条返利记录吗?`, '确认删除', {
937 + type: 'warning'
938 + })
939 +
940 + const rebateIds = selectedRebates.value.map(item => item.rebateId!)
941 + const { data } = await rebateApi.batchDeleteRebate(rebateIds)
942 +
943 + if (data.code === 200) {
944 + ElMessage.success('批量删除成功')
945 + getRebateList()
946 + } else {
947 + ElMessage.error(data.message || '批量删除失败')
948 + }
949 + } catch (error) {
950 + if (error !== 'cancel') {
951 + console.error('批量删除返利失败:', error)
952 + ElMessage.error('批量删除返利失败')
953 + }
954 + }
955 +}
956 +
957 +// 审核
958 +const handleAudit = (row: Rebate) => {
959 + auditForm.rebateId = row.rebateId
960 + auditForm.status = undefined
961 + auditForm.auditRemark = ''
962 + auditDialogVisible.value = true
963 +}
964 +
965 +// 提交审核
966 +const handleSubmitAudit = async () => {
967 + try {
968 + await auditFormRef.value?.validate()
969 +
970 + auditLoading.value = true
971 + // 模拟审核操作
972 + ElMessage.success('审核返利成功')
973 +
974 + auditDialogVisible.value = false
975 + getRebateList()
976 + } catch (error) {
977 + console.error('审核返利失败:', error)
978 + ElMessage.error('审核返利失败')
979 + } finally {
980 + auditLoading.value = false
981 + }
982 +}
983 +
984 +// 批量审核
985 +const handleBatchAudit = async (status: number) => {
986 + try {
987 + const statusText = status === 1 ? '通过' : '拒绝'
988 + await ElMessageBox.confirm(`确认批量审核${statusText}选中的 ${selectedRebates.value.length} 条返利记录吗?`, '确认审核', {
989 + type: 'warning'
990 + })
991 +
992 + // 模拟批量审核操作
993 + ElMessage.success(`批量审核${statusText}成功`)
994 + getRebateList()
995 + } catch (error) {
996 + if (error !== 'cancel') {
997 + console.error('批量审核失败:', error)
998 + ElMessage.error('批量审核失败')
999 + }
1000 + }
1001 +}
1002 +
1003 +// 发放
1004 +const handleRelease = async (row: any) => {
1005 + try {
1006 + await ElMessageBox.confirm('确认发放该返利吗?', '确认发放', {
1007 + type: 'warning'
1008 + })
1009 +
1010 + // 模拟发放操作
1011 + ElMessage.success('发放返利成功')
1012 + getRebateList()
1013 + } catch (error) {
1014 + if (error !== 'cancel') {
1015 + console.error('发放返利失败:', error)
1016 + ElMessage.error('发放返利失败')
1017 + }
1018 + }
1019 +}
1020 +
1021 +// 批量发放
1022 +const handleBatchRelease = async () => {
1023 + try {
1024 + await ElMessageBox.confirm(`确认批量发放选中的 ${selectedRebates.value.length} 条返利记录吗?`, '确认发放', {
1025 + type: 'warning'
1026 + })
1027 +
1028 + // 模拟批量发放操作
1029 + ElMessage.success('批量发放返利成功')
1030 + getRebateList()
1031 + } catch (error) {
1032 + if (error !== 'cancel') {
1033 + console.error('批量发放返利失败:', error)
1034 + ElMessage.error('批量发放返利失败')
1035 + }
1036 + }
1037 +}
1038 +
1039 +// 导出
1040 +const handleExport = async () => {
1041 + try {
1042 + loading.value = true
1043 + const params = { ...searchForm }
1044 +
1045 + const response = await rebateApi.exportRebate(params)
1046 +
1047 + // 创建下载链接
1048 + const blob = new Blob([response.data], {
1049 + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
1050 + })
1051 + const url = window.URL.createObjectURL(blob)
1052 + const link = document.createElement('a')
1053 + link.href = url
1054 + link.download = `返利数据_${new Date().toISOString().split('T')[0]}.xlsx`
1055 + document.body.appendChild(link)
1056 + link.click()
1057 + document.body.removeChild(link)
1058 + window.URL.revokeObjectURL(url)
1059 +
1060 + ElMessage.success('导出成功')
1061 + } catch (error) {
1062 + console.error('导出失败:', error)
1063 + ElMessage.error('导出失败')
1064 + } finally {
1065 + loading.value = false
1066 + }
1067 +}
1068 +
1069 +// 初始化趋势图
1070 +const initTrendChart = async () => {
1071 + if (!trendChart.value) return
1072 +
1073 + const chart = echarts.init(trendChart.value)
1074 +
1075 + try {
1076 + // 调用后端API获取月度统计数据
1077 + const response = await rebateApi.getRebateMonthlyStats()
1078 + let monthlyData = []
1079 +
1080 + if (response && response.data) {
1081 + monthlyData = response.data
1082 + }
1083 +
1084 + const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
1085 +
1086 + // 处理后端返回的月度数据
1087 + const totalTrendData = months.map((month, index) => {
1088 + const monthIndex = index + 1
1089 + const monthData = monthlyData.find((item: any) => item.month === monthIndex)
1090 + return monthData ? monthData.totalAmount : 0
1091 + })
1092 +
1093 + const calculatedTrendData = months.map((month, index) => {
1094 + const monthIndex = index + 1
1095 + const monthData = monthlyData.find((item: any) => item.month === monthIndex)
1096 + return monthData ? monthData.calculatedAmount : 0
1097 + })
1098 +
1099 + const option = {
1100 + tooltip: {
1101 + trigger: 'axis',
1102 + formatter: (params: any) => {
1103 + let result = `${params[0].axisValue}<br/>`
1104 + params.forEach((param: any) => {
1105 + result += `${param.seriesName}: ¥${param.value.toLocaleString()}<br/>`
1106 + })
1107 + return result
1108 + }
1109 + },
1110 + grid: {
1111 + left: '8%',
1112 + right: '8%',
1113 + top: '15%',
1114 + bottom: '15%'
1115 + },
1116 + xAxis: {
1117 + type: 'category',
1118 + boundaryGap: false,
1119 + data: months,
1120 + axisLine: { show: false },
1121 + axisTick: { show: false },
1122 + axisLabel: {
1123 + color: '#999',
1124 + fontSize: 12
1125 + }
1126 + },
1127 + yAxis: {
1128 + type: 'value',
1129 + axisLine: { show: false },
1130 + axisTick: { show: false },
1131 + splitLine: {
1132 + show: true,
1133 + lineStyle: {
1134 + color: '#f0f0f0',
1135 + type: 'dashed'
1136 + }
1137 + },
1138 + axisLabel: {
1139 + color: '#999',
1140 + fontSize: 12,
1141 + formatter: (value: number) => `${(value / 1000).toFixed(0)}k`
1142 + }
1143 + },
1144 + series: [
1145 + {
1146 + name: '返利金额',
1147 + type: 'line',
1148 + smooth: true,
1149 + data: totalTrendData,
1150 + itemStyle: { color: '#4CAF50' },
1151 + lineStyle: {
1152 + color: '#4CAF50',
1153 + width: 3
1154 + },
1155 + symbol: 'circle',
1156 + symbolSize: 6,
1157 + showSymbol: true
1158 + },
1159 + {
1160 + name: '已审核返利',
1161 + type: 'line',
1162 + smooth: true,
1163 + data: calculatedTrendData,
1164 + itemStyle: { color: '#2196F3' },
1165 + lineStyle: {
1166 + color: '#2196F3',
1167 + width: 3
1168 + },
1169 + symbol: 'circle',
1170 + symbolSize: 6,
1171 + showSymbol: true
1172 + }
1173 + ]
1174 + }
1175 + chart.setOption(option)
1176 +
1177 + } catch (error) {
1178 + console.error('获取趋势图数据失败:', error)
1179 +
1180 + // 如果API调用失败,使用当前列表数据作为备用方案
1181 + const totalAmount = rebateList.value.reduce((sum, item) => sum + (item.rebateAmount || 0), 0)
1182 + const calculatedAmount = rebateList.value
1183 + .filter(item => item.calcFlag === 1)
1184 + .reduce((sum, item) => sum + (item.rebateAmount || 0), 0)
1185 +
1186 + const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
1187 +
1188 + const totalTrendData = months.map(() => Math.floor(totalAmount / 12))
1189 + const calculatedTrendData = months.map(() => Math.floor(calculatedAmount / 12))
1190 +
1191 + const fallbackOption = {
1192 + tooltip: {
1193 + trigger: 'axis',
1194 + formatter: (params: any) => {
1195 + let result = `${params[0].axisValue}<br/>`
1196 + params.forEach((param: any) => {
1197 + result += `${param.seriesName}: ¥${param.value.toLocaleString()}<br/>`
1198 + })
1199 + return result
1200 + }
1201 + },
1202 + grid: {
1203 + left: '8%',
1204 + right: '8%',
1205 + top: '15%',
1206 + bottom: '15%'
1207 + },
1208 + xAxis: {
1209 + type: 'category',
1210 + boundaryGap: false,
1211 + data: months,
1212 + axisLine: { show: false },
1213 + axisTick: { show: false },
1214 + axisLabel: {
1215 + color: '#999',
1216 + fontSize: 12
1217 + }
1218 + },
1219 + yAxis: {
1220 + type: 'value',
1221 + axisLine: { show: false },
1222 + axisTick: { show: false },
1223 + splitLine: {
1224 + show: true,
1225 + lineStyle: {
1226 + color: '#f0f0f0',
1227 + type: 'dashed'
1228 + }
1229 + },
1230 + axisLabel: {
1231 + color: '#999',
1232 + fontSize: 12,
1233 + formatter: (value: number) => `${(value / 1000).toFixed(0)}k`
1234 + }
1235 + },
1236 + series: [
1237 + {
1238 + name: '返利金额',
1239 + type: 'line',
1240 + smooth: true,
1241 + data: totalTrendData,
1242 + itemStyle: { color: '#4CAF50' },
1243 + lineStyle: {
1244 + color: '#4CAF50',
1245 + width: 3
1246 + },
1247 + symbol: 'circle',
1248 + symbolSize: 6,
1249 + showSymbol: true
1250 + },
1251 + {
1252 + name: '已审核返利',
1253 + type: 'line',
1254 + smooth: true,
1255 + data: calculatedTrendData,
1256 + itemStyle: { color: '#2196F3' },
1257 + lineStyle: {
1258 + color: '#2196F3',
1259 + width: 3
1260 + },
1261 + symbol: 'circle',
1262 + symbolSize: 6,
1263 + showSymbol: true
1264 + }
1265 + ]
1266 + }
1267 + chart.setOption(fallbackOption)
1268 + }
1269 +
1270 + // 响应式调整
1271 + window.addEventListener('resize', () => chart.resize())
1272 +}
1273 +
1274 +// 初始化饼图
1275 +const initPieChart = async () => {
1276 + if (!pieChart.value) return
1277 +
1278 + const chart = echarts.init(pieChart.value)
1279 +
1280 + try {
1281 + // 调用后端API获取状态统计数据
1282 + const response = await rebateApi.getRebateStatusStats()
1283 + let statusData = { calculatedCount: 0, unCalculatedCount: 0 }
1284 +
1285 + if (response && response.data) {
1286 + statusData = response.data
1287 + }
1288 +
1289 + const option = {
1290 + tooltip: {
1291 + trigger: 'item',
1292 + formatter: '{b}: {c} ({d}%)'
1293 + },
1294 + series: [
1295 + {
1296 + name: '审核状态',
1297 + type: 'pie',
1298 + radius: ['35%', '65%'],
1299 + center: ['50%', '50%'],
1300 + data: [
1301 + {
1302 + value: statusData.calculatedCount || 0,
1303 + name: '已计算',
1304 + itemStyle: { color: '#2196F3' }
1305 + },
1306 + {
1307 + value: statusData.unCalculatedCount || 0,
1308 + name: '未计算',
1309 + itemStyle: { color: '#4CAF50' }
1310 + }
1311 + ],
1312 + label: {
1313 + show: true,
1314 + position: 'inside',
1315 + formatter: '{b}\n{d}%',
1316 + fontSize: 12,
1317 + color: '#fff',
1318 + fontWeight: 'bold'
1319 + },
1320 + labelLine: {
1321 + show: false
1322 + },
1323 + emphasis: {
1324 + itemStyle: {
1325 + shadowBlur: 10,
1326 + shadowOffsetX: 0,
1327 + shadowColor: 'rgba(0, 0, 0, 0.5)'
1328 + },
1329 + label: {
1330 + fontSize: 14
1331 + }
1332 + }
1333 + }
1334 + ]
1335 + }
1336 + chart.setOption(option)
1337 +
1338 + } catch (error) {
1339 + console.error('获取饼图数据失败:', error)
1340 +
1341 + // 如果API调用失败,使用当前列表数据作为备用方案
1342 + const calculatedCount = rebateList.value.filter(item => item.calcFlag === 1).length
1343 + const unCalculatedCount = rebateList.value.filter(item => item.calcFlag === 0).length
1344 +
1345 + const fallbackOption = {
1346 + tooltip: {
1347 + trigger: 'item',
1348 + formatter: '{b}: {c} ({d}%)'
1349 + },
1350 + series: [
1351 + {
1352 + name: '审核状态',
1353 + type: 'pie',
1354 + radius: ['35%', '65%'],
1355 + center: ['50%', '50%'],
1356 + data: [
1357 + {
1358 + value: calculatedCount,
1359 + name: '已审核',
1360 + itemStyle: { color: '#2196F3' }
1361 + },
1362 + {
1363 + value: unCalculatedCount,
1364 + name: '未审核',
1365 + itemStyle: { color: '#4CAF50' }
1366 + }
1367 + ],
1368 + label: {
1369 + show: true,
1370 + position: 'inside',
1371 + formatter: '{b}\n{d}%',
1372 + fontSize: 12,
1373 + color: '#fff',
1374 + fontWeight: 'bold'
1375 + },
1376 + labelLine: {
1377 + show: false
1378 + },
1379 + emphasis: {
1380 + itemStyle: {
1381 + shadowBlur: 10,
1382 + shadowOffsetX: 0,
1383 + shadowColor: 'rgba(0, 0, 0, 0.5)'
1384 + },
1385 + label: {
1386 + fontSize: 14
1387 + }
1388 + }
1389 + }
1390 + ]
1391 + }
1392 + chart.setOption(fallbackOption)
1393 + }
1394 +
1395 + // 响应式调整
1396 + window.addEventListener('resize', () => chart.resize())
1397 +}
1398 +
1399 +// 页面加载时获取数据
1400 +onMounted(async () => {
1401 + await getRebateList()
1402 + setTimeout(async () => {
1403 + await initTrendChart()
1404 + await initPieChart()
1405 + }, 100)
1406 +})
1407 +</script>
1408 +
1409 +<style scoped lang="scss">
1410 +.rebate-page {
1411 + padding: 20px;
1412 + background: #f5f7fa;
1413 + min-height: 100vh;
1414 +
1415 +}
1416 +
1417 +.search-section {
1418 + background: white;
1419 + padding: 16px 20px;
1420 + margin-bottom: 16px;
1421 + border-radius: 4px;
1422 + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
1423 +
1424 + .search-form {
1425 + .search-row {
1426 + display: flex;
1427 + align-items: center;
1428 + gap: 24px;
1429 + flex-wrap: wrap;
1430 +
1431 + .search-item {
1432 + display: flex;
1433 + align-items: center;
1434 + gap: 8px;
1435 +
1436 + .search-label {
1437 + font-size: 14px;
1438 + color: #333;
1439 + white-space: nowrap;
1440 + min-width: 70px;
1441 + }
1442 + }
1443 +
1444 + .search-actions {
1445 + margin-left: auto;
1446 + display: flex;
1447 + gap: 8px;
1448 + }
1449 + }
1450 + }
1451 +}
1452 +
1453 +.charts-section {
1454 + margin-bottom: 16px;
1455 +
1456 + .charts-container {
1457 + display: grid;
1458 + grid-template-columns: 2fr 1fr;
1459 + gap: 16px;
1460 +
1461 + .chart-card {
1462 + background: white;
1463 + border-radius: 4px;
1464 + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
1465 + overflow: hidden;
1466 +
1467 + .chart-header {
1468 + padding: 16px 20px 8px 20px;
1469 + border-bottom: 1px solid #f0f0f0;
1470 + display: flex;
1471 + justify-content: space-between;
1472 + align-items: center;
1473 +
1474 + .chart-title {
1475 + font-size: 16px;
1476 + font-weight: 500;
1477 + color: #333;
1478 + margin: 0;
1479 + }
1480 +
1481 + .chart-legend {
1482 + display: flex;
1483 + gap: 16px;
1484 +
1485 + .legend-item {
1486 + display: flex;
1487 + align-items: center;
1488 + gap: 6px;
1489 + font-size: 12px;
1490 + color: #666;
1491 +
1492 + .legend-dot {
1493 + width: 8px;
1494 + height: 8px;
1495 + border-radius: 50%;
1496 + }
1497 + }
1498 + }
1499 + }
1500 +
1501 + &.trend-chart {
1502 + .chart {
1503 + height: 260px;
1504 + }
1505 + }
1506 +
1507 + &.pie-chart {
1508 + .chart {
1509 + height: 240px;
1510 + }
1511 + }
1512 +
1513 + .chart-content {
1514 + padding: 12px 20px 20px 20px;
1515 + }
1516 + }
1517 + }
1518 +}
1519 +
1520 +.chart {
1521 + width: 100%;
1522 +}
1523 +
1524 +.table-section {
1525 + background: white;
1526 + border-radius: 8px;
1527 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1528 + overflow: hidden;
1529 +
1530 + .table-header {
1531 + display: flex;
1532 + justify-content: space-between;
1533 + align-items: center;
1534 + padding: 20px 20px 0 20px;
1535 + margin-bottom: 16px;
1536 +
1537 + .table-title {
1538 + font-size: 16px;
1539 + font-weight: 500;
1540 + color: #333;
1541 + }
1542 +
1543 + .table-info {
1544 + font-size: 12px;
1545 + color: #999;
1546 + }
1547 + }
1548 +
1549 + .el-table {
1550 + border: none;
1551 +
1552 + :deep(.el-table__header) {
1553 + th {
1554 + background-color: #fafafa;
1555 + border: none;
1556 + font-weight: 500;
1557 + color: #333;
1558 + }
1559 + }
1560 +
1561 + :deep(.el-table__body) {
1562 + tr:hover > td {
1563 + background-color: #f5f7fa;
1564 + }
1565 +
1566 + td {
1567 + border: none;
1568 + border-bottom: 1px solid #f0f0f0;
1569 + }
1570 + }
1571 + }
1572 +
1573 + .pagination-wrapper {
1574 + display: flex;
1575 + justify-content: space-between;
1576 + align-items: center;
1577 + padding: 12px 20px;
1578 + border-top: 1px solid #f0f0f0;
1579 + background: #fafafa;
1580 +
1581 + .pagination-info {
1582 + font-size: 14px;
1583 + color: #666;
1584 + }
1585 +
1586 + .pagination-jump {
1587 + display: flex;
1588 + align-items: center;
1589 + font-size: 14px;
1590 + color: #666;
1591 + }
1592 + }
1593 +}
1594 +
1595 +.amount-text {
1596 + color: #f56c6c;
1597 + font-weight: 500;
1598 +}
1599 +
1600 +.rebate-detail {
1601 + max-height: 600px;
1602 + overflow-y: auto;
1603 +}
1604 +
1605 +// 响应式设计
1606 +@media (max-width: 1200px) {
1607 + .charts-container {
1608 + grid-template-columns: 1fr;
1609 + }
1610 +}
1611 +
1612 +@media (max-width: 768px) {
1613 + .rebate-page {
1614 + padding: 16px;
1615 + }
1616 +
1617 + .search-form .form-row {
1618 + flex-direction: column;
1619 + align-items: flex-start;
1620 + gap: 12px;
1621 +
1622 + .form-actions {
1623 + margin-left: 0;
1624 + margin-top: 12px;
1625 + }
1626 + }
1627 +
1628 +}
1629 +</style>