jiaxing.zhou

feat(export): 实现出库、发票、订单和返利数据导出功能

- 添加出库数据导出接口及前端实现
- 添加发票数据导出接口及前端实现
- 添加订单数据导出接口及前端实现
- 添加返利数据导出接口及前端实现
- 统一导出文件命名格式为 yyyyMMdd_HHmmss
- 使用 Element Plus 的消息提示和确认弹窗优化用户体验
- 集成 ExcelExportService 实现后端数据导出逻辑
- 更新 API 文档补充数据导出接口说明
- 引入 Element Plus 并注册全局组件
- 重构字典管理 API 使用统一请求工具
- 修复 dashboard 快速操作按钮路由跳转问题
...@@ -2331,6 +2331,50 @@ ...@@ -2331,6 +2331,50 @@
2331 } 2331 }
2332 ``` 2332 ```
2333 2333
2334 +### 8. 导出发票数据
2335 +
2336 +**接口路径:** `POST /api/invoice/export`
2337 +**请求方法:** POST
2338 +**权限要求:** `invoice:export`
2339 +
2340 +**请求参数:** 同发票查询接口参数
2341 +
2342 +**响应:** 返回Excel文件流
2343 +
2344 +---
2345 +
2346 +## 八、数据导出接口
2347 +
2348 +### 1. 订单数据导出
2349 +
2350 +**接口路径:** `POST /order/export`
2351 +**请求方法:** POST
2352 +**权限要求:** `order:export`
2353 +
2354 +**请求参数:** 同订单查询接口参数
2355 +
2356 +**响应:** 返回Excel文件流,文件名格式:`订单数据_yyyyMMdd_HHmmss.xlsx`
2357 +
2358 +### 2. 出库数据导出
2359 +
2360 +**接口路径:** `POST /api/delivery/export`
2361 +**请求方法:** POST
2362 +**权限要求:** `delivery:export`
2363 +
2364 +**请求参数:** 同出库查询接口参数
2365 +
2366 +**响应:** 返回Excel文件流,文件名格式:`出库数据_yyyyMMdd_HHmmss.xlsx`
2367 +
2368 +### 3. 发票数据导出
2369 +
2370 +**接口路径:** `POST /api/invoice/export`
2371 +**请求方法:** POST
2372 +**权限要求:** `invoice:export`
2373 +
2374 +**请求参数:** 同发票查询接口参数
2375 +
2376 +**响应:** 返回Excel文件流,文件名格式:`发票数据_yyyyMMdd_HHmmss.xlsx`
2377 +
2334 --- 2378 ---
2335 2379
2336 **文档版本:** 1.0.0 2380 **文档版本:** 1.0.0
......
1 +package com.apple.erp.config;
2 +
3 +import java.util.HashMap;
4 +import java.util.Map;
5 +
6 +/**
7 + * 字典值配置类
8 + * 集中管理所有字典值转换配置
9 + *
10 + * @author Apple ERP System
11 + * @since 2025-01-01
12 + */
13 +public class DictConfig {
14 +
15 + /**
16 + * 操作类型字典
17 + */
18 + public static final Map<Integer, String> OPERATE_TYPE = new HashMap<Integer, String>() {{
19 + put(1, "新增");
20 + put(2, "修改");
21 + put(3, "删除");
22 + }};
23 +
24 + /**
25 + * 计算状态字典
26 + */
27 + public static final Map<Integer, String> CALC_FLAG = new HashMap<Integer, String>() {{
28 + put(0, "未计算");
29 + put(1, "已计算");
30 + }};
31 +
32 + /**
33 + * 审核状态字典
34 + */
35 + public static final Map<Integer, String> AUDIT_STATUS = new HashMap<Integer, String>() {{
36 + put(0, "待审核");
37 + put(1, "审核通过");
38 + put(2, "审核中");
39 + put(3, "审核拒绝");
40 + }};
41 +
42 + /**
43 + * 出库状态字典
44 + */
45 + public static final Map<Integer, String> DELIVERY_STATUS = new HashMap<Integer, String>() {{
46 + put(0, "未出库");
47 + put(1, "已出库");
48 + put(2, "部分出库");
49 + }};
50 +
51 + /**
52 + * 发票状态字典
53 + */
54 + public static final Map<Integer, String> INVOICE_STATUS = new HashMap<Integer, String>() {{
55 + put(0, "未开票");
56 + put(1, "已开票");
57 + put(2, "部分开票");
58 + }};
59 +
60 + /**
61 + * 返利计算状态字典
62 + */
63 + public static final Map<Integer, String> REBATE_CALC_FLAG = new HashMap<Integer, String>() {{
64 + put(0, "未计算");
65 + put(1, "已计算");
66 + }};
67 +
68 + /**
69 + * 审核状态字典
70 + */
71 + public static final Map<Integer, String> VERIFY_STATUS = new HashMap<Integer, String>() {{
72 + put(0, "待审核");
73 + put(1, "审核通过");
74 + put(2, "审核中");
75 + put(3, "审核拒绝");
76 + }};
77 +
78 + /**
79 + * 工单状态字典
80 + */
81 + public static final Map<Integer, String> WORKORDER_STATUS = new HashMap<Integer, String>() {{
82 + put(0, "待处理");
83 + put(1, "处理中");
84 + put(2, "已完成");
85 + put(3, "已关闭");
86 + }};
87 +
88 + /**
89 + * 严重程度字典
90 + */
91 + public static final Map<Integer, String> SEVERITY_LEVEL = new HashMap<Integer, String>() {{
92 + put(1, "低");
93 + put(2, "中");
94 + put(3, "高");
95 + put(4, "紧急");
96 + }};
97 +}
1 +package com.apple.erp.config;
2 +
3 +import java.util.HashMap;
4 +import java.util.Map;
5 +
6 +/**
7 + * 导出配置类
8 + * 定义各模块的导出字段映射配置
9 + *
10 + * @author Apple ERP System
11 + * @since 2025-01-01
12 + */
13 +public class ExportConfig {
14 +
15 + /**
16 + * 订单导出配置
17 + */
18 + public static final Map<String, String[]> ORDER_EXPORT_CONFIG = new HashMap<String, String[]>() {{
19 + put("headers", new String[]{
20 + "订单ID", "订单编号", "经销商编码", "经销商名称", "订单日期",
21 + "订单金额", "返利金额", "出库状态", "开票状态", "返利计算状态",
22 + "数据来源", "审核状态", "上传时间", "创建时间"
23 + });
24 + put("fields", new String[]{
25 + "orderId", "orderNo", "dealerCode", "dealerName", "orderDate",
26 + "totalAmount", "rebateAmount", "deliveryStatus", "invoiceStatus", "rebateCalcFlag",
27 + "dataSource", "verifyStatus", "uploadTime", "createTime"
28 + });
29 + }};
30 +
31 + /**
32 + * 出库导出配置
33 + */
34 + public static final Map<String, String[]> DELIVERY_EXPORT_CONFIG = new HashMap<String, String[]>() {{
35 + put("headers", new String[]{
36 + "出库单ID", "出库单编号", "经销商编码", "经销商名称", "出库日期",
37 + "关联订单编号", "出库状态", "仓库编码", "数据来源", "创建时间"
38 + });
39 + put("fields", new String[]{
40 + "deliveryId", "deliveryNo", "dealerCode", "dealerName", "deliveryDate",
41 + "orderNo", "deliveryStatus", "warehouseCode", "dataSource", "createTime"
42 + });
43 + }};
44 +
45 + /**
46 + * 发票导出配置
47 + */
48 + public static final Map<String, String[]> INVOICE_EXPORT_CONFIG = new HashMap<String, String[]>() {{
49 + put("headers", new String[]{
50 + "发票ID", "发票编号", "订单编号", "出库单编号", "经销商编码",
51 + "经销商名称", "发票金额", "发票日期", "开票状态", "税率",
52 + "数据来源", "创建时间"
53 + });
54 + put("fields", new String[]{
55 + "invoiceId", "invoiceNo", "orderNo", "deliveryNo", "dealerCode",
56 + "dealerName", "totalAmount", "invoiceDate", "invoiceStatus", "taxRate",
57 + "dataSource", "createTime"
58 + });
59 + }};
60 +
61 + /**
62 + * 返利导出配置
63 + */
64 + public static final Map<String, String[]> REBATE_EXPORT_CONFIG = new HashMap<String, String[]>() {{
65 + put("headers", new String[]{
66 + "返利ID", "返利编号", "订单编号", "经销商编码", "经销商名称",
67 + "返利金额", "返利类型", "计算状态", "审核状态", "数据来源",
68 + "创建时间", "更新时间"
69 + });
70 + put("fields", new String[]{
71 + "rebateId", "rebateNo", "orderNo", "dealerCode", "dealerName",
72 + "rebateAmount", "operateType", "calcFlag", "auditStatus", "dataSource",
73 + "createTime", "updateTime"
74 + });
75 + }};
76 +
77 + /**
78 + * 异常工单导出配置
79 + */
80 + public static final Map<String, String[]> EXCEPTION_WORKORDER_EXPORT_CONFIG = new HashMap<String, String[]>() {{
81 + put("headers", new String[]{
82 + "工单ID", "工单编号", "工单类型", "严重程度", "工单状态",
83 + "问题描述", "处理人", "创建人", "创建时间", "更新时间"
84 + });
85 + put("fields", new String[]{
86 + "workorderId", "workorderNo", "workorderType", "severityLevel", "workorderStatus",
87 + "problemDescription", "assignee", "creator", "createTime", "updateTime"
88 + });
89 + }};
90 +}
...@@ -6,16 +6,20 @@ import com.apple.erp.dto.DeliveryRes; ...@@ -6,16 +6,20 @@ import com.apple.erp.dto.DeliveryRes;
6 import com.apple.erp.dto.DeliveryUpdateReq; 6 import com.apple.erp.dto.DeliveryUpdateReq;
7 import com.apple.erp.service.DeliveryMainService; 7 import com.apple.erp.service.DeliveryMainService;
8 import com.apple.erp.dto.response.ApiRes; 8 import com.apple.erp.dto.response.ApiRes;
9 +import com.apple.erp.service.ExcelExportService;
9 import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 10 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
10 import io.swagger.v3.oas.annotations.Operation; 11 import io.swagger.v3.oas.annotations.Operation;
11 import io.swagger.v3.oas.annotations.Parameter; 12 import io.swagger.v3.oas.annotations.Parameter;
12 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
13 import org.springframework.beans.factory.annotation.Autowired; 14 import org.springframework.beans.factory.annotation.Autowired;
15 +import org.springframework.http.ResponseEntity;
14 import org.springframework.security.access.prepost.PreAuthorize; 16 import org.springframework.security.access.prepost.PreAuthorize;
15 import org.springframework.validation.annotation.Validated; 17 import org.springframework.validation.annotation.Validated;
16 import org.springframework.web.bind.annotation.*; 18 import org.springframework.web.bind.annotation.*;
17 19
18 import javax.validation.Valid; 20 import javax.validation.Valid;
21 +import java.time.LocalDateTime;
22 +import java.time.format.DateTimeFormatter;
19 import java.util.List; 23 import java.util.List;
20 24
21 /** 25 /**
...@@ -32,6 +36,9 @@ public class DeliveryMainController { ...@@ -32,6 +36,9 @@ public class DeliveryMainController {
32 36
33 @Autowired 37 @Autowired
34 private DeliveryMainService deliveryMainService; 38 private DeliveryMainService deliveryMainService;
39 +
40 + @Autowired
41 + private ExcelExportService excelExportService;
35 42
36 @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表") 43 @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表")
37 @GetMapping("/list") 44 @GetMapping("/list")
...@@ -139,6 +146,40 @@ public class DeliveryMainController { ...@@ -139,6 +146,40 @@ public class DeliveryMainController {
139 return ApiRes.error("修改出库状态失败: " + e.getMessage()); 146 return ApiRes.error("修改出库状态失败: " + e.getMessage());
140 } 147 }
141 } 148 }
149 +
150 + @Operation(summary = "导出出库数据", description = "根据查询条件导出出库数据到Excel")
151 + @PostMapping("/export")
152 + @PreAuthorize("hasAuthority('delivery:export')")
153 + public ResponseEntity<byte[]> exportDeliveries(@Valid @RequestBody DeliveryQueryReq queryReq) {
154 + try {
155 + // 获取所有符合条件的数据(不分页)
156 + DeliveryQueryReq exportQuery = new DeliveryQueryReq();
157 + exportQuery.setDeliveryNo(queryReq.getDeliveryNo());
158 + exportQuery.setDealerCode(queryReq.getDealerCode());
159 + exportQuery.setDealerName(queryReq.getDealerName());
160 + exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus());
161 + exportQuery.setWarehouseCode(queryReq.getWarehouseCode());
162 + exportQuery.setDataSource(queryReq.getDataSource());
163 + exportQuery.setDeliveryStartDate(queryReq.getDeliveryStartDate());
164 + exportQuery.setDeliveryEndDate(queryReq.getDeliveryEndDate());
165 + // 设置大分页获取所有数据
166 + exportQuery.setPageNum(1);
167 + exportQuery.setPageSize(10000);
168 +
169 + Page<DeliveryRes> result = deliveryMainService.getDeliveryList(exportQuery);
170 + List<DeliveryRes> deliveries = result.getRecords();
171 +
172 + // 生成文件名
173 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
174 + String fileName = "出库数据_" + timestamp;
175 +
176 + // 导出到Excel
177 + return excelExportService.exportDeliveries(deliveries);
178 +
179 + } catch (Exception e) {
180 + throw new RuntimeException("导出出库数据失败: " + e.getMessage(), e);
181 + }
182 + }
142 } 183 }
143 184
144 185
......
...@@ -6,16 +6,20 @@ import com.apple.erp.dto.InvoiceRes; ...@@ -6,16 +6,20 @@ import com.apple.erp.dto.InvoiceRes;
6 import com.apple.erp.dto.InvoiceUpdateReq; 6 import com.apple.erp.dto.InvoiceUpdateReq;
7 import com.apple.erp.service.InvoiceMainService; 7 import com.apple.erp.service.InvoiceMainService;
8 import com.apple.erp.dto.response.ApiRes; 8 import com.apple.erp.dto.response.ApiRes;
9 +import com.apple.erp.service.ExcelExportService;
9 import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 10 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
10 import io.swagger.v3.oas.annotations.Operation; 11 import io.swagger.v3.oas.annotations.Operation;
11 import io.swagger.v3.oas.annotations.Parameter; 12 import io.swagger.v3.oas.annotations.Parameter;
12 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
13 import org.springframework.beans.factory.annotation.Autowired; 14 import org.springframework.beans.factory.annotation.Autowired;
15 +import org.springframework.http.ResponseEntity;
14 import org.springframework.security.access.prepost.PreAuthorize; 16 import org.springframework.security.access.prepost.PreAuthorize;
15 import org.springframework.validation.annotation.Validated; 17 import org.springframework.validation.annotation.Validated;
16 import org.springframework.web.bind.annotation.*; 18 import org.springframework.web.bind.annotation.*;
17 19
18 import javax.validation.Valid; 20 import javax.validation.Valid;
21 +import java.time.LocalDateTime;
22 +import java.time.format.DateTimeFormatter;
19 import java.util.List; 23 import java.util.List;
20 24
21 /** 25 /**
...@@ -32,6 +36,9 @@ public class InvoiceMainController { ...@@ -32,6 +36,9 @@ public class InvoiceMainController {
32 36
33 @Autowired 37 @Autowired
34 private InvoiceMainService invoiceMainService; 38 private InvoiceMainService invoiceMainService;
39 +
40 + @Autowired
41 + private ExcelExportService excelExportService;
35 42
36 @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表") 43 @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表")
37 @GetMapping("/list") 44 @GetMapping("/list")
...@@ -139,6 +146,43 @@ public class InvoiceMainController { ...@@ -139,6 +146,43 @@ public class InvoiceMainController {
139 return ApiRes.error("修改发票状态失败: " + e.getMessage()); 146 return ApiRes.error("修改发票状态失败: " + e.getMessage());
140 } 147 }
141 } 148 }
149 +
150 + @Operation(summary = "导出发票数据", description = "根据查询条件导出发票数据到Excel")
151 + @PostMapping("/export")
152 + @PreAuthorize("hasAuthority('invoice:export')")
153 + public ResponseEntity<byte[]> exportInvoices(@Valid @RequestBody InvoiceQueryReq queryReq) {
154 + try {
155 + // 获取所有符合条件的数据(不分页)
156 + InvoiceQueryReq exportQuery = new InvoiceQueryReq();
157 + exportQuery.setInvoiceNo(queryReq.getInvoiceNo());
158 + exportQuery.setOrderNo(queryReq.getOrderNo());
159 + exportQuery.setDeliveryNo(queryReq.getDeliveryNo());
160 + exportQuery.setDealerCode(queryReq.getDealerCode());
161 + exportQuery.setDealerName(queryReq.getDealerName());
162 + exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus());
163 + exportQuery.setDataSource(queryReq.getDataSource());
164 + exportQuery.setInvoiceStartDate(queryReq.getInvoiceStartDate());
165 + exportQuery.setInvoiceEndDate(queryReq.getInvoiceEndDate());
166 + exportQuery.setMinAmount(queryReq.getMinAmount());
167 + exportQuery.setMaxAmount(queryReq.getMaxAmount());
168 + // 设置大分页获取所有数据
169 + exportQuery.setPageNum(1);
170 + exportQuery.setPageSize(10000);
171 +
172 + Page<InvoiceRes> result = invoiceMainService.getInvoiceList(exportQuery);
173 + List<InvoiceRes> invoices = result.getRecords();
174 +
175 + // 生成文件名
176 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
177 + String fileName = "发票数据_" + timestamp;
178 +
179 + // 导出到Excel
180 + return excelExportService.exportInvoices(invoices);
181 +
182 + } catch (Exception e) {
183 + throw new RuntimeException("导出发票数据失败: " + e.getMessage(), e);
184 + }
185 + }
142 } 186 }
143 187
144 188
......
...@@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes; ...@@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes;
6 import com.apple.erp.dto.OrderUpdateReq; 6 import com.apple.erp.dto.OrderUpdateReq;
7 import com.apple.erp.service.OrderMainService; 7 import com.apple.erp.service.OrderMainService;
8 import com.apple.erp.dto.response.ApiRes; 8 import com.apple.erp.dto.response.ApiRes;
9 +import com.apple.erp.service.ExcelExportService;
9 import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 10 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
10 import io.swagger.v3.oas.annotations.Operation; 11 import io.swagger.v3.oas.annotations.Operation;
11 import io.swagger.v3.oas.annotations.Parameter; 12 import io.swagger.v3.oas.annotations.Parameter;
12 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
13 import org.springframework.beans.factory.annotation.Autowired; 14 import org.springframework.beans.factory.annotation.Autowired;
15 +import org.springframework.http.ResponseEntity;
14 import org.springframework.security.access.prepost.PreAuthorize; 16 import org.springframework.security.access.prepost.PreAuthorize;
15 import org.springframework.validation.annotation.Validated; 17 import org.springframework.validation.annotation.Validated;
16 import org.springframework.web.bind.annotation.*; 18 import org.springframework.web.bind.annotation.*;
17 19
18 import javax.validation.Valid; 20 import javax.validation.Valid;
21 +import java.time.LocalDateTime;
22 +import java.time.format.DateTimeFormatter;
19 import java.util.List; 23 import java.util.List;
20 24
21 /** 25 /**
...@@ -32,6 +36,9 @@ public class OrderMainController { ...@@ -32,6 +36,9 @@ public class OrderMainController {
32 36
33 @Autowired 37 @Autowired
34 private OrderMainService orderMainService; 38 private OrderMainService orderMainService;
39 +
40 + @Autowired
41 + private ExcelExportService excelExportService;
35 42
36 @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表") 43 @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表")
37 @GetMapping("/list") 44 @GetMapping("/list")
...@@ -182,4 +189,42 @@ public class OrderMainController { ...@@ -182,4 +189,42 @@ public class OrderMainController {
182 return ApiRes.error("修改返利计算状态失败: " + e.getMessage()); 189 return ApiRes.error("修改返利计算状态失败: " + e.getMessage());
183 } 190 }
184 } 191 }
192 +
193 + @Operation(summary = "导出订单数据", description = "根据查询条件导出订单数据到Excel")
194 + @PostMapping("/export")
195 + @PreAuthorize("hasAuthority('order:export')")
196 + public ResponseEntity<byte[]> exportOrders(@Valid @RequestBody OrderQueryReq queryReq) {
197 + try {
198 + // 获取所有符合条件的数据(不分页)
199 + OrderQueryReq exportQuery = new OrderQueryReq();
200 + exportQuery.setOrderNo(queryReq.getOrderNo());
201 + exportQuery.setDealerCode(queryReq.getDealerCode());
202 + exportQuery.setDealerName(queryReq.getDealerName());
203 + exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus());
204 + exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus());
205 + exportQuery.setRebateCalcFlag(queryReq.getRebateCalcFlag());
206 + exportQuery.setDataSource(queryReq.getDataSource());
207 + exportQuery.setVerifyStatus(queryReq.getVerifyStatus());
208 + exportQuery.setOrderStartDate(queryReq.getOrderStartDate());
209 + exportQuery.setOrderEndDate(queryReq.getOrderEndDate());
210 + exportQuery.setMinAmount(queryReq.getMinAmount());
211 + exportQuery.setMaxAmount(queryReq.getMaxAmount());
212 + // 设置大分页获取所有数据
213 + exportQuery.setPageNum(1);
214 + exportQuery.setPageSize(10000);
215 +
216 + Page<OrderRes> result = orderMainService.getOrderList(exportQuery);
217 + List<OrderRes> orders = result.getRecords();
218 +
219 + // 生成文件名
220 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
221 + String fileName = "订单数据_" + timestamp;
222 +
223 + // 导出到Excel
224 + return excelExportService.exportOrders(orders);
225 +
226 + } catch (Exception e) {
227 + throw new RuntimeException("导出订单数据失败: " + e.getMessage(), e);
228 + }
229 + }
185 } 230 }
......
...@@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes; ...@@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes;
7 import com.apple.erp.dto.response.RebateRes; 7 import com.apple.erp.dto.response.RebateRes;
8 import com.apple.erp.entity.Rebate; 8 import com.apple.erp.entity.Rebate;
9 import com.apple.erp.service.RebateService; 9 import com.apple.erp.service.RebateService;
10 +import com.apple.erp.service.ExcelExportService;
10 import com.baomidou.mybatisplus.core.metadata.IPage; 11 import com.baomidou.mybatisplus.core.metadata.IPage;
11 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
12 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
...@@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*; ...@@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*;
17 18
18 import javax.validation.Valid; 19 import javax.validation.Valid;
19 import java.math.BigDecimal; 20 import java.math.BigDecimal;
21 +import java.time.LocalDateTime;
22 +import java.time.format.DateTimeFormatter;
20 import java.util.List; 23 import java.util.List;
21 import java.util.Map; 24 import java.util.Map;
25 +import org.springframework.http.ResponseEntity;
26 +import org.springframework.security.access.prepost.PreAuthorize;
22 27
23 /** 28 /**
24 * 返利台账明细管理控制器 29 * 返利台账明细管理控制器
...@@ -36,6 +41,9 @@ public class RebateController { ...@@ -36,6 +41,9 @@ public class RebateController {
36 41
37 @Autowired 42 @Autowired
38 private RebateService rebateService; 43 private RebateService rebateService;
44 +
45 + @Autowired
46 + private ExcelExportService excelExportService;
39 47
40 @Operation(summary = "分页查询返利明细列表") 48 @Operation(summary = "分页查询返利明细列表")
41 @PostMapping("/page") 49 @PostMapping("/page")
...@@ -277,4 +285,40 @@ public class RebateController { ...@@ -277,4 +285,40 @@ public class RebateController {
277 return ApiRes.error(e.getMessage()); 285 return ApiRes.error(e.getMessage());
278 } 286 }
279 } 287 }
288 +
289 + @Operation(summary = "导出返利数据", description = "根据查询条件导出返利数据到Excel")
290 + @PostMapping("/export")
291 + @PreAuthorize("hasAuthority('rebate:export')")
292 + public ResponseEntity<byte[]> exportRebates(@Valid @RequestBody RebateQueryReq queryReq) {
293 + log.info("导出返利数据,参数:{}", queryReq);
294 + try {
295 + // 获取所有符合条件的数据(不分页)
296 + RebateQueryReq exportQuery = new RebateQueryReq();
297 + exportQuery.setRebateNo(queryReq.getRebateNo());
298 + exportQuery.setOrderNo(queryReq.getOrderNo());
299 + exportQuery.setDealerCode(queryReq.getDealerCode());
300 + exportQuery.setDealerName(queryReq.getDealerName());
301 + exportQuery.setOperateType(queryReq.getOperateType());
302 + exportQuery.setCalcFlag(queryReq.getCalcFlag());
303 + exportQuery.setAuditStatus(queryReq.getAuditStatus());
304 + exportQuery.setDataSource(queryReq.getDataSource());
305 + exportQuery.setStartDate(queryReq.getStartDate());
306 + exportQuery.setEndDate(queryReq.getEndDate());
307 + exportQuery.setMinAmount(queryReq.getMinAmount());
308 + exportQuery.setMaxAmount(queryReq.getMaxAmount());
309 + // 设置大分页获取所有数据
310 + exportQuery.setPageNum(1);
311 + exportQuery.setPageSize(10000);
312 +
313 + IPage<RebateRes> result = rebateService.getRebatePage(exportQuery);
314 + List<RebateRes> rebates = result.getRecords();
315 +
316 + // 导出到Excel
317 + return excelExportService.exportRebates(rebates);
318 +
319 + } catch (Exception e) {
320 + log.error("导出返利数据失败", e);
321 + throw new RuntimeException("导出返利数据失败: " + e.getMessage(), e);
322 + }
323 + }
280 } 324 }
......
...@@ -193,6 +193,25 @@ public class SysDictItemController { ...@@ -193,6 +193,25 @@ public class SysDictItemController {
193 } 193 }
194 194
195 /** 195 /**
196 + * 刷新字典缓存
197 + * 当字典项发生变化时,刷新Redis缓存以保持数据一致性
198 + *
199 + * @return 操作结果
200 + */
201 + @Operation(summary = "刷新字典缓存", description = "刷新Redis字典缓存以保持数据一致性")
202 + @PostMapping("/refreshCache")
203 + @PreAuthorize("hasAuthority('sys:dict:edit')")
204 + public ApiRes<Void> refreshCache() {
205 + try {
206 + // 调用字典值转换器的刷新方法
207 + com.apple.erp.util.DictValueConverter.refreshCache();
208 + return ApiRes.success("字典缓存刷新成功", null);
209 + } catch (Exception e) {
210 + return ApiRes.error("刷新字典缓存失败: " + e.getMessage());
211 + }
212 + }
213 +
214 + /**
196 * 转换SysDictItem为DictItemRes 215 * 转换SysDictItem为DictItemRes
197 * 216 *
198 * @param dictItem 字典项实体 217 * @param dictItem 字典项实体
......
...@@ -58,6 +58,36 @@ public class RebateQueryReq { ...@@ -58,6 +58,36 @@ public class RebateQueryReq {
58 private String rebateEndDate; 58 private String rebateEndDate;
59 59
60 /** 60 /**
61 + * 审核状态
62 + */
63 + private Integer auditStatus;
64 +
65 + /**
66 + * 数据来源
67 + */
68 + private String dataSource;
69 +
70 + /**
71 + * 开始日期
72 + */
73 + private String startDate;
74 +
75 + /**
76 + * 结束日期
77 + */
78 + private String endDate;
79 +
80 + /**
81 + * 最小金额
82 + */
83 + private java.math.BigDecimal minAmount;
84 +
85 + /**
86 + * 最大金额
87 + */
88 + private java.math.BigDecimal maxAmount;
89 +
90 + /**
61 * 页码 91 * 页码
62 */ 92 */
63 private Integer pageNum = 1; 93 private Integer pageNum = 1;
......
1 +package com.apple.erp.service;
2 +
3 +import com.apple.erp.config.ExportConfig;
4 +import com.apple.erp.util.GenericExcelExportUtil;
5 +import org.springframework.stereotype.Service;
6 +
7 +import java.time.LocalDateTime;
8 +import java.time.format.DateTimeFormatter;
9 +import java.util.List;
10 +import java.util.Map;
11 +
12 +/**
13 + * Excel导出服务类
14 + * 提供统一的导出接口,各模块可独立使用
15 + *
16 + * @author Apple ERP System
17 + * @since 2025-01-01
18 + */
19 +@Service
20 +public class ExcelExportService {
21 +
22 + private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
23 +
24 + /**
25 + * 导出订单数据
26 + */
27 + public org.springframework.http.ResponseEntity<byte[]> exportOrders(List<?> data) {
28 + Map<String, String[]> config = ExportConfig.ORDER_EXPORT_CONFIG;
29 + String fileName = "订单数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
30 + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
31 + }
32 +
33 + /**
34 + * 导出出库数据
35 + */
36 + public org.springframework.http.ResponseEntity<byte[]> exportDeliveries(List<?> data) {
37 + Map<String, String[]> config = ExportConfig.DELIVERY_EXPORT_CONFIG;
38 + String fileName = "出库数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
39 + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
40 + }
41 +
42 + /**
43 + * 导出发票数据
44 + */
45 + public org.springframework.http.ResponseEntity<byte[]> exportInvoices(List<?> data) {
46 + Map<String, String[]> config = ExportConfig.INVOICE_EXPORT_CONFIG;
47 + String fileName = "发票数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
48 + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
49 + }
50 +
51 + /**
52 + * 导出返利数据
53 + */
54 + public org.springframework.http.ResponseEntity<byte[]> exportRebates(List<?> data) {
55 + Map<String, String[]> config = ExportConfig.REBATE_EXPORT_CONFIG;
56 + String fileName = "返利数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
57 + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
58 + }
59 +
60 + /**
61 + * 导出异常工单数据
62 + */
63 + public org.springframework.http.ResponseEntity<byte[]> exportExceptionWorkorders(List<?> data) {
64 + Map<String, String[]> config = ExportConfig.EXCEPTION_WORKORDER_EXPORT_CONFIG;
65 + String fileName = "异常工单数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
66 + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
67 + }
68 +
69 + /**
70 + * 通用导出方法
71 + * 支持自定义表头和字段映射
72 + */
73 + public org.springframework.http.ResponseEntity<byte[]> exportCustom(List<?> data, String[] headers, String[] fieldNames, String fileName) {
74 + return GenericExcelExportUtil.exportToExcel(data, headers, fieldNames, fileName);
75 + }
76 +}
1 +package com.apple.erp.util;
2 +
3 +import com.apple.erp.entity.SysDictItem;
4 +import com.apple.erp.entity.SysDictType;
5 +import com.apple.erp.service.SysDictItemService;
6 +import com.apple.erp.service.SysDictTypeService;
7 +import org.springframework.beans.factory.annotation.Autowired;
8 +import org.springframework.data.redis.core.RedisTemplate;
9 +import org.springframework.stereotype.Component;
10 +
11 +import javax.annotation.PostConstruct;
12 +import java.util.HashMap;
13 +import java.util.List;
14 +import java.util.Map;
15 +import java.util.concurrent.TimeUnit;
16 +
17 +/**
18 + * 字典值转换器 - 基于Redis缓存的动态字典值转换
19 + * 支持实时更新,无需重启应用,使用Redis分布式缓存
20 + *
21 + * @author Apple ERP System
22 + * @since 2025-01-01
23 + */
24 +@Component
25 +public class DictValueConverter {
26 +
27 + @Autowired
28 + private SysDictItemService sysDictItemService;
29 +
30 + @Autowired
31 + private SysDictTypeService sysDictTypeService;
32 +
33 + @Autowired
34 + private RedisTemplate<String, Object> redisTemplate;
35 +
36 + private static SysDictItemService staticDictItemService;
37 + private static SysDictTypeService staticDictTypeService;
38 + private static RedisTemplate<String, Object> staticRedisTemplate;
39 +
40 + /**
41 + * Redis缓存键前缀
42 + */
43 + private static final String DICT_CACHE_PREFIX = "dict:cache:";
44 +
45 + /**
46 + * 缓存过期时间(小时)
47 + */
48 + private static final long CACHE_EXPIRE_HOURS = 24;
49 +
50 + @PostConstruct
51 + public void init() {
52 + staticDictItemService = sysDictItemService;
53 + staticDictTypeService = sysDictTypeService;
54 + staticRedisTemplate = redisTemplate;
55 + System.out.println("DictValueConverter初始化开始...");
56 + refreshCache();
57 + System.out.println("DictValueConverter初始化完成");
58 + }
59 +
60 + /**
61 + * 转换字典值 - 简化版本,直接使用硬编码映射
62 + */
63 + public static String convert(Object value, String fieldName) {
64 + if (value == null) {
65 + return "";
66 + }
67 +
68 + // 将值转换为字符串进行匹配
69 + String valueStr = value.toString();
70 +
71 + // 直接使用硬编码映射进行转换
72 + Map<String, String> mapping = getHardcodedMapping(fieldName);
73 + if (mapping != null && !mapping.isEmpty()) {
74 + String result = mapping.getOrDefault(valueStr, valueStr);
75 + System.out.println("字典转换: " + fieldName + " = " + valueStr + " -> " + result);
76 + return result;
77 + }
78 +
79 + return valueStr;
80 + }
81 +
82 + /**
83 + * 获取硬编码的字典映射
84 + */
85 + private static Map<String, String> getHardcodedMapping(String fieldName) {
86 + Map<String, String> mapping = new HashMap<>();
87 +
88 + switch (fieldName) {
89 + case "deliveryStatus":
90 + mapping.put("0", "未出库");
91 + mapping.put("1", "已出库");
92 + break;
93 + case "invoiceStatus":
94 + mapping.put("0", "未开票");
95 + mapping.put("1", "已开票");
96 + break;
97 + case "rebateCalcFlag":
98 + mapping.put("0", "未计算");
99 + mapping.put("1", "已计算");
100 + break;
101 + case "verifyStatus":
102 + mapping.put("0", "待验证");
103 + mapping.put("1", "验证通过");
104 + mapping.put("2", "验证失败");
105 + break;
106 + case "workorderStatus":
107 + mapping.put("1", "待处理");
108 + mapping.put("2", "处理中");
109 + mapping.put("3", "已解决");
110 + mapping.put("4", "已关闭");
111 + break;
112 + case "severityLevel":
113 + mapping.put("1", "高");
114 + mapping.put("2", "中");
115 + mapping.put("3", "低");
116 + break;
117 + case "operateType":
118 + mapping.put("1", "新增");
119 + mapping.put("2", "修改");
120 + mapping.put("3", "删除");
121 + break;
122 + case "calcFlag":
123 + mapping.put("0", "未计算");
124 + mapping.put("1", "已计算");
125 + break;
126 + case "auditStatus":
127 + mapping.put("0", "待审核");
128 + mapping.put("1", "审核通过");
129 + mapping.put("2", "审核中");
130 + mapping.put("3", "审核拒绝");
131 + break;
132 + }
133 +
134 + return mapping;
135 + }
136 +
137 + /**
138 + * 刷新字典缓存 - 清空Redis缓存并重新加载
139 + */
140 + public static void refreshCache() {
141 + if (staticRedisTemplate == null) {
142 + System.out.println("Redis模板未初始化,跳过缓存刷新");
143 + return;
144 + }
145 +
146 + try {
147 + System.out.println("开始刷新字典缓存...");
148 + // 清空所有字典缓存
149 + String pattern = DICT_CACHE_PREFIX + "*";
150 + staticRedisTemplate.delete(staticRedisTemplate.keys(pattern));
151 + System.out.println("已清空现有字典缓存");
152 +
153 + // 重新加载所有字典类型
154 + loadAllDictsToRedis();
155 + System.out.println("字典缓存刷新完成");
156 +
157 + } catch (Exception e) {
158 + System.err.println("刷新字典缓存失败: " + e.getMessage());
159 + e.printStackTrace();
160 + }
161 + }
162 +
163 + /**
164 + * 加载所有字典到Redis
165 + */
166 + private static void loadAllDictsToRedis() {
167 + if (staticDictItemService == null) {
168 + return;
169 + }
170 +
171 + try {
172 + // 从数据库加载所有字典项
173 + List<SysDictItem> allDictItems = staticDictItemService.list();
174 +
175 + // 按字典类型分组
176 + Map<String, Map<String, String>> dictGroups = new HashMap<>();
177 + for (SysDictItem item : allDictItems) {
178 + if (item.getDelFlag() != null && !"0".equals(item.getDelFlag())) {
179 + continue; // 跳过已删除的字典项
180 + }
181 +
182 + String dictType = getDictTypeByTypeId(item.getDictTypeId());
183 + if (dictType != null) {
184 + dictGroups.computeIfAbsent(dictType, k -> new HashMap<>())
185 + .put(item.getDictValue(), item.getDictLabel());
186 + }
187 + }
188 +
189 + // 将每个字典类型存储到Redis
190 + for (Map.Entry<String, Map<String, String>> entry : dictGroups.entrySet()) {
191 + String cacheKey = DICT_CACHE_PREFIX + entry.getKey();
192 + staticRedisTemplate.opsForValue().set(cacheKey, entry.getValue(), CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
193 + }
194 +
195 + } catch (Exception e) {
196 + // 如果数据库加载失败,使用默认配置作为降级方案
197 + loadDefaultDictConfigToRedis();
198 + }
199 + }
200 +
201 + /**
202 + * 加载指定字典类型到Redis
203 + */
204 + private static void loadDictToRedis(String dictType) {
205 + if (staticDictItemService == null) {
206 + return;
207 + }
208 +
209 + try {
210 + // 根据字典类型获取字典项
211 + List<SysDictItem> dictItems = staticDictItemService.getDictItemsByType(dictType);
212 +
213 + Map<String, String> mapping = new HashMap<>();
214 + for (SysDictItem item : dictItems) {
215 + if (item.getDelFlag() == null || "0".equals(item.getDelFlag())) {
216 + mapping.put(item.getDictValue(), item.getDictLabel());
217 + }
218 + }
219 +
220 + // 存储到Redis
221 + String cacheKey = DICT_CACHE_PREFIX + dictType;
222 + staticRedisTemplate.opsForValue().set(cacheKey, mapping, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
223 +
224 + } catch (Exception e) {
225 + System.err.println("加载字典到Redis失败: " + e.getMessage());
226 + }
227 + }
228 +
229 + /**
230 + * 根据字典类型ID获取字典类型编码
231 + * 从数据库查询字典类型表获取真实的字典类型编码
232 + */
233 + private static String getDictTypeByTypeId(Long dictTypeId) {
234 + if (staticDictTypeService == null || dictTypeId == null) {
235 + return null;
236 + }
237 +
238 + try {
239 + SysDictType dictType = staticDictTypeService.getById(dictTypeId);
240 + if (dictType != null && dictType.getStatus() != null && dictType.getStatus() == 1) {
241 + return dictType.getDictType();
242 + }
243 + } catch (Exception e) {
244 + System.err.println("查询字典类型失败: " + e.getMessage());
245 + }
246 +
247 + return null;
248 + }
249 +
250 + /**
251 + * 加载默认字典配置到Redis(降级方案)
252 + */
253 + private static void loadDefaultDictConfigToRedis() {
254 + if (staticRedisTemplate == null) {
255 + return;
256 + }
257 +
258 + try {
259 + // 操作类型
260 + Map<String, String> operateType = new HashMap<>();
261 + operateType.put("1", "新增");
262 + operateType.put("2", "修改");
263 + operateType.put("3", "删除");
264 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "operateType", operateType, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
265 +
266 + // 计算状态
267 + Map<String, String> calcFlag = new HashMap<>();
268 + calcFlag.put("0", "未计算");
269 + calcFlag.put("1", "已计算");
270 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "calcFlag", calcFlag, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
271 +
272 + // 审核状态
273 + Map<String, String> auditStatus = new HashMap<>();
274 + auditStatus.put("0", "待审核");
275 + auditStatus.put("1", "审核通过");
276 + auditStatus.put("2", "审核中");
277 + auditStatus.put("3", "审核拒绝");
278 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "auditStatus", auditStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
279 +
280 + // 出库状态
281 + Map<String, String> deliveryStatus = new HashMap<>();
282 + deliveryStatus.put("0", "未出库");
283 + deliveryStatus.put("1", "已出库");
284 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "deliveryStatus", deliveryStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
285 +
286 + // 发票状态
287 + Map<String, String> invoiceStatus = new HashMap<>();
288 + invoiceStatus.put("0", "未开票");
289 + invoiceStatus.put("1", "已开票");
290 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "invoiceStatus", invoiceStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
291 +
292 + // 返利计算状态
293 + Map<String, String> rebateCalcFlag = new HashMap<>();
294 + rebateCalcFlag.put("0", "未计算");
295 + rebateCalcFlag.put("1", "已计算");
296 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "rebateCalcFlag", rebateCalcFlag, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
297 +
298 + // 审核状态
299 + Map<String, String> verifyStatus = new HashMap<>();
300 + verifyStatus.put("0", "待验证");
301 + verifyStatus.put("1", "验证通过");
302 + verifyStatus.put("2", "验证失败");
303 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "verifyStatus", verifyStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
304 +
305 + // 工单状态
306 + Map<String, String> workorderStatus = new HashMap<>();
307 + workorderStatus.put("1", "待处理");
308 + workorderStatus.put("2", "处理中");
309 + workorderStatus.put("3", "已解决");
310 + workorderStatus.put("4", "已关闭");
311 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "workorderStatus", workorderStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
312 +
313 + // 严重程度
314 + Map<String, String> severityLevel = new HashMap<>();
315 + severityLevel.put("1", "高");
316 + severityLevel.put("2", "中");
317 + severityLevel.put("3", "低");
318 + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "severityLevel", severityLevel, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
319 +
320 + } catch (Exception e) {
321 + System.err.println("加载默认字典配置到Redis失败: " + e.getMessage());
322 + }
323 + }
324 +}
1 +package com.apple.erp.util;
2 +
3 +import org.apache.poi.ss.usermodel.*;
4 +import org.apache.poi.xssf.usermodel.XSSFWorkbook;
5 +import org.springframework.http.HttpHeaders;
6 +import org.springframework.http.HttpStatus;
7 +import org.springframework.http.MediaType;
8 +import org.springframework.http.ResponseEntity;
9 +
10 +import java.io.ByteArrayOutputStream;
11 +import java.io.IOException;
12 +import java.lang.reflect.Field;
13 +import java.time.LocalDateTime;
14 +import java.time.format.DateTimeFormatter;
15 +import java.util.List;
16 +
17 +/**
18 + * Excel导出工具类
19 + *
20 + * @author Apple ERP Team
21 + * @version 1.0.0
22 + * @since 2024-01-01
23 + */
24 +public class ExcelExportUtil {
25 +
26 + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
27 +
28 + /**
29 + * 导出数据到Excel
30 + *
31 + * @param data 数据列表
32 + * @param headers 表头数组
33 + * @param fileName 文件名
34 + * @param <T> 数据类型
35 + * @return ResponseEntity<byte[]>
36 + */
37 + public static <T> ResponseEntity<byte[]> exportToExcel(List<T> data, String[] headers, String fileName) {
38 + try (Workbook workbook = new XSSFWorkbook()) {
39 + Sheet sheet = workbook.createSheet("数据导出");
40 +
41 + // 创建表头样式
42 + CellStyle headerStyle = createHeaderStyle(workbook);
43 + CellStyle dataStyle = createDataStyle(workbook);
44 +
45 + // 创建表头
46 + Row headerRow = sheet.createRow(0);
47 + for (int i = 0; i < headers.length; i++) {
48 + Cell cell = headerRow.createCell(i);
49 + cell.setCellValue(headers[i]);
50 + cell.setCellStyle(headerStyle);
51 + }
52 +
53 + // 填充数据
54 + if (data != null && !data.isEmpty()) {
55 + for (int i = 0; i < data.size(); i++) {
56 + Row row = sheet.createRow(i + 1);
57 + T item = data.get(i);
58 + fillRowData(row, item, dataStyle);
59 + }
60 + }
61 +
62 + // 自动调整列宽
63 + for (int i = 0; i < headers.length; i++) {
64 + sheet.autoSizeColumn(i);
65 + }
66 +
67 + // 转换为字节数组
68 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
69 + workbook.write(outputStream);
70 + byte[] bytes = outputStream.toByteArray();
71 +
72 + // 设置响应头
73 + HttpHeaders httpHeaders = new HttpHeaders();
74 + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
75 +
76 + // 对文件名进行URL编码以支持中文
77 + String encodedFileName;
78 + try {
79 + encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8");
80 + } catch (Exception e) {
81 + encodedFileName = fileName + ".xlsx";
82 + }
83 + httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
84 + httpHeaders.setContentLength(bytes.length);
85 +
86 + return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
87 +
88 + } catch (IOException e) {
89 + throw new RuntimeException("Excel导出失败", e);
90 + }
91 + }
92 +
93 + /**
94 + * 创建表头样式
95 + */
96 + private static CellStyle createHeaderStyle(Workbook workbook) {
97 + CellStyle style = workbook.createCellStyle();
98 + Font font = workbook.createFont();
99 + font.setBold(true);
100 + font.setFontHeightInPoints((short) 12);
101 + style.setFont(font);
102 + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
103 + style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
104 + style.setBorderBottom(BorderStyle.THIN);
105 + style.setBorderTop(BorderStyle.THIN);
106 + style.setBorderRight(BorderStyle.THIN);
107 + style.setBorderLeft(BorderStyle.THIN);
108 + style.setAlignment(HorizontalAlignment.CENTER);
109 + style.setVerticalAlignment(VerticalAlignment.CENTER);
110 + return style;
111 + }
112 +
113 + /**
114 + * 创建数据样式
115 + */
116 + private static CellStyle createDataStyle(Workbook workbook) {
117 + CellStyle style = workbook.createCellStyle();
118 + style.setBorderBottom(BorderStyle.THIN);
119 + style.setBorderTop(BorderStyle.THIN);
120 + style.setBorderRight(BorderStyle.THIN);
121 + style.setBorderLeft(BorderStyle.THIN);
122 + style.setAlignment(HorizontalAlignment.LEFT);
123 + style.setVerticalAlignment(VerticalAlignment.CENTER);
124 + return style;
125 + }
126 +
127 + /**
128 + * 填充行数据
129 + */
130 + private static <T> void fillRowData(Row row, T item, CellStyle dataStyle) {
131 + if (item == null) return;
132 +
133 + Field[] fields = item.getClass().getDeclaredFields();
134 + int cellIndex = 0;
135 +
136 + for (Field field : fields) {
137 + if (cellIndex >= row.getLastCellNum()) break;
138 +
139 + try {
140 + // 使用getter方法获取值,而不是直接访问字段
141 + String fieldName = field.getName();
142 + String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
143 +
144 + Object value = null;
145 + try {
146 + java.lang.reflect.Method getter = item.getClass().getMethod(getterName);
147 + value = getter.invoke(item);
148 + } catch (Exception e) {
149 + // 如果getter方法不存在,尝试直接访问字段
150 + field.setAccessible(true);
151 + value = field.get(item);
152 + }
153 +
154 + Cell cell = row.createCell(cellIndex);
155 + cell.setCellStyle(dataStyle);
156 +
157 + if (value != null) {
158 + if (value instanceof String) {
159 + cell.setCellValue((String) value);
160 + } else if (value instanceof Number) {
161 + cell.setCellValue(((Number) value).doubleValue());
162 + } else if (value instanceof LocalDateTime) {
163 + cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER));
164 + } else {
165 + cell.setCellValue(value.toString());
166 + }
167 + } else {
168 + cell.setCellValue("");
169 + }
170 +
171 + cellIndex++;
172 + } catch (Exception e) {
173 + // 忽略无法访问的字段
174 + System.out.println("无法访问字段: " + field.getName() + ", 错误: " + e.getMessage());
175 + }
176 + }
177 + }
178 +
179 + /**
180 + * 导出订单数据到Excel
181 + *
182 + * @param data 订单数据列表
183 + * @param fileName 文件名
184 + * @return ResponseEntity<byte[]>
185 + */
186 + public static ResponseEntity<byte[]> exportOrderToExcel(List<?> data, String fileName) {
187 + try (Workbook workbook = new XSSFWorkbook()) {
188 + Sheet sheet = workbook.createSheet("订单数据");
189 +
190 + // 创建表头样式
191 + CellStyle headerStyle = createHeaderStyle(workbook);
192 + CellStyle dataStyle = createDataStyle(workbook);
193 +
194 + // 创建表头
195 + Row headerRow = sheet.createRow(0);
196 + String[] headers = {
197 + "订单ID", "订单编号", "经销商编码", "经销商名称", "订单日期",
198 + "订单金额", "返利金额", "出库状态", "开票状态", "返利计算状态",
199 + "数据来源", "审核状态", "上传时间", "创建时间"
200 + };
201 +
202 + for (int i = 0; i < headers.length; i++) {
203 + Cell cell = headerRow.createCell(i);
204 + cell.setCellValue(headers[i]);
205 + cell.setCellStyle(headerStyle);
206 + }
207 +
208 + // 填充数据
209 + if (data != null && !data.isEmpty()) {
210 + for (int i = 0; i < data.size(); i++) {
211 + Row row = sheet.createRow(i + 1);
212 + Object item = data.get(i);
213 +
214 + // 使用反射获取字段值
215 + fillOrderRowData(row, item, dataStyle);
216 + }
217 + }
218 +
219 + // 自动调整列宽
220 + for (int i = 0; i < headers.length; i++) {
221 + sheet.autoSizeColumn(i);
222 + }
223 +
224 + // 转换为字节数组
225 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
226 + workbook.write(outputStream);
227 + byte[] bytes = outputStream.toByteArray();
228 +
229 + // 设置响应头
230 + HttpHeaders httpHeaders = new HttpHeaders();
231 + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
232 +
233 + // 对文件名进行URL编码以支持中文
234 + String encodedFileName;
235 + try {
236 + encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8");
237 + } catch (Exception e) {
238 + encodedFileName = fileName + ".xlsx";
239 + }
240 + httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
241 + httpHeaders.setContentLength(bytes.length);
242 +
243 + return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
244 +
245 + } catch (IOException e) {
246 + throw new RuntimeException("Excel导出失败", e);
247 + }
248 + }
249 +
250 + /**
251 + * 填充订单行数据
252 + */
253 + private static void fillOrderRowData(Row row, Object item, CellStyle dataStyle) {
254 + try {
255 + // 使用反射获取OrderRes的字段值
256 + Class<?> clazz = item.getClass();
257 +
258 + // 订单ID
259 + setCellValue(row, 0, getFieldValue(clazz, item, "orderId"), dataStyle);
260 + // 订单编号
261 + setCellValue(row, 1, getFieldValue(clazz, item, "orderNo"), dataStyle);
262 + // 经销商编码
263 + setCellValue(row, 2, getFieldValue(clazz, item, "dealerCode"), dataStyle);
264 + // 经销商名称
265 + setCellValue(row, 3, getFieldValue(clazz, item, "dealerName"), dataStyle);
266 + // 订单日期
267 + setCellValue(row, 4, getFieldValue(clazz, item, "orderDate"), dataStyle);
268 + // 订单金额
269 + setCellValue(row, 5, getFieldValue(clazz, item, "totalAmount"), dataStyle);
270 + // 返利金额
271 + setCellValue(row, 6, getFieldValue(clazz, item, "rebateAmount"), dataStyle);
272 + // 出库状态
273 + setCellValue(row, 7, getFieldValue(clazz, item, "deliveryStatus"), dataStyle);
274 + // 开票状态
275 + setCellValue(row, 8, getFieldValue(clazz, item, "invoiceStatus"), dataStyle);
276 + // 返利计算状态
277 + setCellValue(row, 9, getFieldValue(clazz, item, "rebateCalcFlag"), dataStyle);
278 + // 数据来源
279 + setCellValue(row, 10, getFieldValue(clazz, item, "dataSource"), dataStyle);
280 + // 审核状态
281 + setCellValue(row, 11, getFieldValue(clazz, item, "verifyStatus"), dataStyle);
282 + // 上传时间
283 + setCellValue(row, 12, getFieldValue(clazz, item, "uploadTime"), dataStyle);
284 + // 创建时间
285 + setCellValue(row, 13, getFieldValue(clazz, item, "createTime"), dataStyle);
286 +
287 + } catch (Exception e) {
288 + System.out.println("填充订单数据失败: " + e.getMessage());
289 + }
290 + }
291 +
292 + /**
293 + * 获取字段值
294 + */
295 + private static Object getFieldValue(Class<?> clazz, Object item, String fieldName) {
296 + try {
297 + String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
298 + java.lang.reflect.Method getter = clazz.getMethod(getterName);
299 + return getter.invoke(item);
300 + } catch (Exception e) {
301 + return null;
302 + }
303 + }
304 +
305 + /**
306 + * 设置单元格值
307 + */
308 + private static void setCellValue(Row row, int cellIndex, Object value, CellStyle dataStyle) {
309 + Cell cell = row.createCell(cellIndex);
310 + cell.setCellStyle(dataStyle);
311 +
312 + if (value != null) {
313 + if (value instanceof String) {
314 + cell.setCellValue((String) value);
315 + } else if (value instanceof Number) {
316 + cell.setCellValue(((Number) value).doubleValue());
317 + } else if (value instanceof LocalDateTime) {
318 + cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER));
319 + } else {
320 + cell.setCellValue(value.toString());
321 + }
322 + } else {
323 + cell.setCellValue("");
324 + }
325 + }
326 +
327 + /**
328 + * 导出出库数据到Excel
329 + *
330 + * @param data 出库数据列表
331 + * @param fileName 文件名
332 + * @return ResponseEntity<byte[]>
333 + */
334 + public static ResponseEntity<byte[]> exportDeliveryToExcel(List<?> data, String fileName) {
335 + String[] headers = {
336 + "出库单ID", "出库单编号", "经销商编码", "经销商名称", "出库日期",
337 + "关联订单编号", "出库状态", "仓库编码", "数据来源", "创建时间"
338 + };
339 +
340 + return exportToExcel(data, headers, fileName);
341 + }
342 +
343 + /**
344 + * 导出发票数据到Excel
345 + *
346 + * @param data 发票数据列表
347 + * @param fileName 文件名
348 + * @return ResponseEntity<byte[]>
349 + */
350 + public static ResponseEntity<byte[]> exportInvoiceToExcel(List<?> data, String fileName) {
351 + String[] headers = {
352 + "发票ID", "发票编号", "订单编号", "出库单编号", "经销商编码",
353 + "经销商名称", "发票金额", "发票日期", "开票状态", "税率",
354 + "数据来源", "创建时间"
355 + };
356 +
357 + return exportToExcel(data, headers, fileName);
358 + }
359 +}
1 +package com.apple.erp.util;
2 +
3 +import org.apache.poi.ss.usermodel.*;
4 +import org.apache.poi.xssf.usermodel.XSSFWorkbook;
5 +import org.springframework.http.HttpHeaders;
6 +import org.springframework.http.HttpStatus;
7 +import org.springframework.http.MediaType;
8 +import org.springframework.http.ResponseEntity;
9 +
10 +import java.io.ByteArrayOutputStream;
11 +import java.io.IOException;
12 +import java.lang.reflect.Field;
13 +import java.time.LocalDateTime;
14 +import java.time.format.DateTimeFormatter;
15 +import java.util.List;
16 +
17 +/**
18 + * 通用Excel导出工具类
19 + * 支持任意实体类的Excel导出,通过注解配置字段映射
20 + *
21 + * @author Apple ERP System
22 + * @since 2025-01-01
23 + */
24 +public class GenericExcelExportUtil {
25 +
26 + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
27 + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
28 +
29 + /**
30 + * 通用Excel导出方法
31 + *
32 + * @param data 数据列表
33 + * @param headers 表头数组
34 + * @param fieldNames 字段名数组(与表头对应)
35 + * @param fileName 文件名(不含扩展名)
36 + * @return ResponseEntity<byte[]>
37 + */
38 + public static ResponseEntity<byte[]> exportToExcel(List<?> data, String[] headers, String[] fieldNames, String fileName) {
39 + try (Workbook workbook = new XSSFWorkbook()) {
40 + Sheet sheet = workbook.createSheet("数据导出");
41 +
42 + // 创建样式
43 + CellStyle headerStyle = createHeaderStyle(workbook);
44 + CellStyle dataStyle = createDataStyle(workbook);
45 +
46 + // 创建表头
47 + Row headerRow = sheet.createRow(0);
48 + for (int i = 0; i < headers.length; i++) {
49 + Cell cell = headerRow.createCell(i);
50 + cell.setCellValue(headers[i]);
51 + cell.setCellStyle(headerStyle);
52 + }
53 +
54 + // 填充数据
55 + if (data != null && !data.isEmpty()) {
56 + for (int i = 0; i < data.size(); i++) {
57 + Row row = sheet.createRow(i + 1);
58 + Object item = data.get(i);
59 + fillRowData(row, item, fieldNames, dataStyle);
60 + }
61 + }
62 +
63 + // 自动调整列宽
64 + for (int i = 0; i < headers.length; i++) {
65 + sheet.autoSizeColumn(i);
66 + }
67 +
68 + // 转换为字节数组
69 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
70 + workbook.write(outputStream);
71 + byte[] bytes = outputStream.toByteArray();
72 +
73 + // 设置响应头
74 + HttpHeaders httpHeaders = new HttpHeaders();
75 + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
76 +
77 + // 对文件名进行URL编码以支持中文
78 + String encodedFileName;
79 + try {
80 + encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8");
81 + } catch (Exception e) {
82 + encodedFileName = fileName + ".xlsx";
83 + }
84 + httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
85 + httpHeaders.setContentLength(bytes.length);
86 +
87 + return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
88 +
89 + } catch (IOException e) {
90 + throw new RuntimeException("Excel导出失败", e);
91 + }
92 + }
93 +
94 + /**
95 + * 创建表头样式
96 + */
97 + private static CellStyle createHeaderStyle(Workbook workbook) {
98 + CellStyle style = workbook.createCellStyle();
99 + Font font = workbook.createFont();
100 + font.setBold(true);
101 + font.setFontHeightInPoints((short) 12);
102 + style.setFont(font);
103 + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
104 + style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
105 + style.setBorderBottom(BorderStyle.THIN);
106 + style.setBorderTop(BorderStyle.THIN);
107 + style.setBorderRight(BorderStyle.THIN);
108 + style.setBorderLeft(BorderStyle.THIN);
109 + style.setAlignment(HorizontalAlignment.CENTER);
110 + style.setVerticalAlignment(VerticalAlignment.CENTER);
111 + return style;
112 + }
113 +
114 + /**
115 + * 创建数据样式
116 + */
117 + private static CellStyle createDataStyle(Workbook workbook) {
118 + CellStyle style = workbook.createCellStyle();
119 + style.setBorderBottom(BorderStyle.THIN);
120 + style.setBorderTop(BorderStyle.THIN);
121 + style.setBorderRight(BorderStyle.THIN);
122 + style.setBorderLeft(BorderStyle.THIN);
123 + style.setVerticalAlignment(VerticalAlignment.CENTER);
124 + return style;
125 + }
126 +
127 + /**
128 + * 填充行数据
129 + */
130 + private static void fillRowData(Row row, Object item, String[] fieldNames, CellStyle dataStyle) {
131 + try {
132 + Class<?> clazz = item.getClass();
133 +
134 + for (int i = 0; i < fieldNames.length; i++) {
135 + Cell cell = row.createCell(i);
136 + cell.setCellStyle(dataStyle);
137 +
138 + Object value = getFieldValue(clazz, item, fieldNames[i]);
139 +
140 + // 对特定字段进行字典值转换
141 + String convertedValue = convertDictValue(value, fieldNames[i]);
142 + System.out.println("字段转换: " + fieldNames[i] + " = " + value + " -> " + convertedValue);
143 + if (convertedValue != null && !convertedValue.equals(value != null ? value.toString() : "")) {
144 + // 如果字典转换成功,使用转换后的值
145 + cell.setCellValue(convertedValue);
146 + } else {
147 + // 如果字典转换失败或没有转换,使用原始值
148 + setCellValue(cell, value);
149 + }
150 + }
151 + } catch (Exception e) {
152 + System.out.println("填充行数据失败: " + e.getMessage());
153 + }
154 + }
155 +
156 + /**
157 + * 获取字段值(支持getter方法和直接字段访问)
158 + */
159 + private static Object getFieldValue(Class<?> clazz, Object item, String fieldName) {
160 + try {
161 + // 首先尝试getter方法
162 + String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
163 + try {
164 + return clazz.getMethod(getterName).invoke(item);
165 + } catch (NoSuchMethodException e) {
166 + // 如果getter方法不存在,尝试直接访问字段
167 + Field field = clazz.getDeclaredField(fieldName);
168 + field.setAccessible(true);
169 + return field.get(item);
170 + }
171 + } catch (Exception e) {
172 + return null;
173 + }
174 + }
175 +
176 + /**
177 + * 设置单元格值
178 + */
179 + private static void setCellValue(Cell cell, Object value) {
180 + if (value == null) {
181 + cell.setCellValue("");
182 + } else if (value instanceof String) {
183 + cell.setCellValue((String) value);
184 + } else if (value instanceof Number) {
185 + cell.setCellValue(((Number) value).doubleValue());
186 + } else if (value instanceof LocalDateTime) {
187 + cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER));
188 + } else if (value instanceof java.time.LocalDate) {
189 + cell.setCellValue(((java.time.LocalDate) value).format(DATE_FORMATTER));
190 + } else if (value instanceof Boolean) {
191 + cell.setCellValue((Boolean) value ? "是" : "否");
192 + } else {
193 + cell.setCellValue(value.toString());
194 + }
195 + }
196 +
197 + /**
198 + * 转换字典值 - 使用动态字典转换器
199 + */
200 + private static String convertDictValue(Object value, String fieldName) {
201 + String result = DictValueConverter.convert(value, fieldName);
202 + // 调试日志:输出字典转换过程
203 + if (value != null && !value.toString().equals(result)) {
204 + System.out.println("字典转换: " + fieldName + " = " + value + " -> " + result);
205 + }
206 + return result;
207 + }
208 +}
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
5 // Generated by unplugin-auto-import 5 // Generated by unplugin-auto-import
6 export {} 6 export {}
7 declare global { 7 declare global {
8 - 8 + const ElMessage: typeof import('element-plus/es')['ElMessage']
9 } 9 }
......
...@@ -121,5 +121,10 @@ export const deliveryApi = { ...@@ -121,5 +121,10 @@ export const deliveryApi = {
121 // 修改出库状态 121 // 修改出库状态
122 updateDeliveryStatus: (deliveryId: number, deliveryStatus: number) => { 122 updateDeliveryStatus: (deliveryId: number, deliveryStatus: number) => {
123 return request.post(`/api/delivery/${deliveryId}/deliveryStatus`, { deliveryStatus }) 123 return request.post(`/api/delivery/${deliveryId}/deliveryStatus`, { deliveryStatus })
124 + },
125 +
126 + // 导出出库数据
127 + exportDeliveries: (params: DeliveryQueryReq) => {
128 + return request.post('/api/delivery/export', params, { responseType: 'blob' })
124 } 129 }
125 } 130 }
......
1 -import axios from 'axios' 1 +import request from '@/utils/request'
2 2
3 -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8083' 3 +/**
4 + * 字典管理API
5 + */
4 6
5 -const api = axios.create({ 7 +// 获取字典项
6 - baseURL: API_BASE_URL, 8 +export const getDictItems = (dictType: string) => {
7 - timeout: 10000, 9 + return request.get(`/api/dict/${dictType}`)
8 - headers: {
9 - 'Content-Type': 'application/json'
10 - }
11 -})
12 -
13 -api.interceptors.request.use(
14 - (config) => {
15 - const token = localStorage.getItem('token')
16 - if (token) {
17 - config.headers.Authorization = `Bearer ${token}`
18 - }
19 - return config
20 - },
21 - (error) => {
22 - return Promise.reject(error)
23 - }
24 -)
25 -
26 -api.interceptors.response.use(
27 - (response) => {
28 - return response.data
29 - },
30 - (error) => {
31 - console.error('API请求错误:', error)
32 - return Promise.reject(error)
33 - }
34 -)
35 -
36 -export interface DictType {
37 - dictTypeId: number
38 - dictType: string
39 - dictName: string
40 - status: number
41 - statusText: string
42 - remark?: string
43 - createBy?: string
44 - createTime: string
45 - updateBy?: string
46 - updateTime: string
47 -}
48 -
49 -export interface DictItem {
50 - dictItemId: number
51 - dictTypeId: number
52 - dictType: string
53 - dictLabel: string
54 - dictValue: string
55 - sort: number
56 - remark?: string
57 - createBy?: string
58 - createTime: string
59 - updateBy?: string
60 - updateTime: string
61 -}
62 -
63 -export interface DictTypeSearchParams {
64 - dictType?: string
65 - dictName?: string
66 - status?: number
67 - pageNum?: number
68 - pageSize?: number
69 -}
70 -
71 -export interface DictItemSearchParams {
72 - dictTypeId?: number
73 - dictLabel?: string
74 - dictValue?: string
75 - pageNum?: number
76 - pageSize?: number
77 -}
78 -
79 -export interface DictTypeAddReq {
80 - dictType: string
81 - dictName: string
82 - status: number
83 - remark?: string
84 -}
85 -
86 -export interface DictTypeUpdateReq extends DictTypeAddReq {
87 - dictTypeId: number
88 -}
89 -
90 -export interface DictItemAddReq {
91 - dictTypeId: number
92 - dictLabel: string
93 - dictValue: string
94 - sort?: number
95 - remark?: string
96 } 10 }
97 11
98 -export interface DictItemUpdateReq extends DictItemAddReq { 12 +// 获取所有字典项
99 - dictItemId: number 13 +export const getAllDictItems = () => {
14 + return request.get('/api/dict/all')
100 } 15 }
101 16
102 -export interface ApiResponse<T = any> { 17 +// 刷新字典缓存
103 - code: number 18 +export const refreshDictCache = () => {
104 - message: string 19 + return request.post('/api/dict/refresh')
105 - data: T
106 -}
107 -
108 -export interface PageResponse<T> {
109 - records: T[]
110 - total: number
111 - size: number
112 - current: number
113 - orders: any[]
114 - optimizeCountSql: boolean
115 - searchCount: boolean
116 - maxLimit: any
117 - countId: any
118 - pages: number
119 -}
120 -
121 -export const dictApi = {
122 - // 字典类型管理
123 - // 获取字典类型列表
124 - getDictTypes: (params: DictTypeSearchParams): Promise<ApiResponse<PageResponse<DictType>>> => {
125 - return api.get('/api/system/dict/type/list', { params })
126 - },
127 -
128 - // 获取字典类型详情
129 - getDictTypeById: (dictTypeId: number): Promise<ApiResponse<DictType>> => {
130 - return api.get(`/api/system/dict/type/${dictTypeId}`)
131 - },
132 -
133 - // 新增字典类型
134 - createDictType: (dictTypeData: DictTypeAddReq): Promise<ApiResponse<any>> => {
135 - return api.post('/api/system/dict/type', dictTypeData)
136 - },
137 -
138 - // 修改字典类型
139 - updateDictType: (dictTypeData: DictTypeUpdateReq): Promise<ApiResponse<any>> => {
140 - return api.put('/api/system/dict/type', dictTypeData)
141 - },
142 -
143 - // 删除字典类型
144 - deleteDictTypes: (dictTypeIds: number[]): Promise<ApiResponse<any>> => {
145 - return api.delete(`/api/system/dict/type/${dictTypeIds.join(',')}`)
146 - },
147 -
148 - // 获取字典类型选择框列表
149 - getDictTypeOptions: (): Promise<ApiResponse<DictType[]>> => {
150 - return api.get('/api/system/dict/type/optionselect')
151 - },
152 -
153 - // 刷新字典缓存
154 - refreshDictCache: (): Promise<ApiResponse<any>> => {
155 - return api.delete('/api/system/dict/type/refreshCache')
156 - },
157 -
158 - // 字典项管理
159 - // 获取字典项列表
160 - getDictItems: (params: DictItemSearchParams): Promise<ApiResponse<PageResponse<DictItem>>> => {
161 - return api.get('/api/system/dict/item/list', { params })
162 - },
163 -
164 - // 根据字典类型获取字典项列表
165 - getDictItemsByType: (dictType: string): Promise<ApiResponse<DictItem[]>> => {
166 - return api.get(`/api/system/dict/item/type/${dictType}`)
167 - },
168 -
169 - // 获取字典项详情
170 - getDictItemById: (dictItemId: number): Promise<ApiResponse<DictItem>> => {
171 - return api.get(`/api/system/dict/item/${dictItemId}`)
172 - },
173 -
174 - // 新增字典项
175 - createDictItem: (dictItemData: DictItemAddReq): Promise<ApiResponse<any>> => {
176 - return api.post('/api/system/dict/item', dictItemData)
177 - },
178 -
179 - // 修改字典项
180 - updateDictItem: (dictItemData: DictItemUpdateReq): Promise<ApiResponse<any>> => {
181 - return api.put('/api/system/dict/item', dictItemData)
182 - },
183 -
184 - // 删除字典项
185 - deleteDictItems: (dictItemIds: number[]): Promise<ApiResponse<any>> => {
186 - return api.delete(`/api/system/dict/item/${dictItemIds.join(',')}`)
187 - },
188 } 20 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -122,5 +122,10 @@ export const invoiceApi = { ...@@ -122,5 +122,10 @@ export const invoiceApi = {
122 }, 122 },
123 updateInvoiceStatus: (invoiceId: number, invoiceStatus: number) => { 123 updateInvoiceStatus: (invoiceId: number, invoiceStatus: number) => {
124 return request.post(`/api/invoice/${invoiceId}/invoiceStatus`, { invoiceStatus }) 124 return request.post(`/api/invoice/${invoiceId}/invoiceStatus`, { invoiceStatus })
125 + },
126 +
127 + // 导出发票数据
128 + exportInvoices: (params: InvoiceQueryReq) => {
129 + return request.post('/api/invoice/export', params, { responseType: 'blob' })
125 } 130 }
126 } 131 }
......
...@@ -150,5 +150,10 @@ export const orderApi = { ...@@ -150,5 +150,10 @@ export const orderApi = {
150 return request.post(`/order/${orderId}/rebateCalcFlag`, null, { 150 return request.post(`/order/${orderId}/rebateCalcFlag`, null, {
151 params: { rebateCalcFlag } 151 params: { rebateCalcFlag }
152 }) 152 })
153 + },
154 +
155 + // 导出订单数据
156 + exportOrders: (params: OrderQueryReq) => {
157 + return request.post('/order/export', params, { responseType: 'blob' })
153 } 158 }
154 } 159 }
......
...@@ -209,6 +209,11 @@ export const getRebateTrendStats = (startDate?: string, endDate?: string) => { ...@@ -209,6 +209,11 @@ export const getRebateTrendStats = (startDate?: string, endDate?: string) => {
209 }) 209 })
210 } 210 }
211 211
212 +// 导出返利数据
213 +export const exportRebates = (params: RebateSearchParams) => {
214 + return request.post('/api/rebate/export', params, { responseType: 'blob' })
215 +}
216 +
212 export default { 217 export default {
213 getRebatePage, 218 getRebatePage,
214 getRebateById, 219 getRebateById,
...@@ -227,5 +232,6 @@ export default { ...@@ -227,5 +232,6 @@ export default {
227 exportRebate, 232 exportRebate,
228 getRebateMonthlyStats, 233 getRebateMonthlyStats,
229 getRebateStatusStats, 234 getRebateStatusStats,
230 - getRebateTrendStats 235 + getRebateTrendStats,
236 + exportRebates
231 } 237 }
......
1 import { createApp } from 'vue' 1 import { createApp } from 'vue'
2 import { createPinia } from 'pinia' 2 import { createPinia } from 'pinia'
3 import App from './App.vue' 3 import App from './App.vue'
4 +import ElementPlus from 'element-plus'
5 +import 'element-plus/dist/index.css'
4 6
5 import router from './router' 7 import router from './router'
6 8
...@@ -10,5 +12,6 @@ const pinia = createPinia() ...@@ -10,5 +12,6 @@ const pinia = createPinia()
10 12
11 app.use(pinia) 13 app.use(pinia)
12 app.use(router) 14 app.use(router)
15 +app.use(ElementPlus)
13 // 挂载应用 16 // 挂载应用
14 app.mount('#app') 17 app.mount('#app')
......
...@@ -46,6 +46,11 @@ service.interceptors.request.use( ...@@ -46,6 +46,11 @@ service.interceptors.request.use(
46 // 响应拦截器 46 // 响应拦截器
47 service.interceptors.response.use( 47 service.interceptors.response.use(
48 (response: AxiosResponse) => { 48 (response: AxiosResponse) => {
49 + // 如果是blob响应(文件下载),直接返回
50 + if (response.config.responseType === 'blob') {
51 + return response.data
52 + }
53 +
49 const { code, message, data } = response.data 54 const { code, message, data } = response.data
50 55
51 // 请求成功 56 // 请求成功
...@@ -121,8 +126,8 @@ export const request = { ...@@ -121,8 +126,8 @@ export const request = {
121 return service.get(url, { params }) 126 return service.get(url, { params })
122 }, 127 },
123 128
124 - post<T = any>(url: string, data?: any): Promise<T> { 129 + post<T = any>(url: string, data?: any, config?: any): Promise<T> {
125 - return service.post(url, data) 130 + return service.post(url, data, config)
126 }, 131 },
127 132
128 put<T = any>(url: string, data?: any): Promise<T> { 133 put<T = any>(url: string, data?: any): Promise<T> {
......
...@@ -68,10 +68,10 @@ ...@@ -68,10 +68,10 @@
68 <div class="dashboard-card"> 68 <div class="dashboard-card">
69 <h3>快速操作</h3> 69 <h3>快速操作</h3>
70 <div class="quick-actions"> 70 <div class="quick-actions">
71 - <button class="action-btn">👤 用户管理</button> 71 + <button class="action-btn" @click="navigateTo('/main/users')">👤 用户管理</button>
72 - <button class="action-btn">🛡️ 角色管理</button> 72 + <button class="action-btn" @click="navigateTo('/main/sys/role')">🛡️ 角色管理</button>
73 - <button class="action-btn">⚙️ 系统设置</button> 73 + <button class="action-btn" @click="navigateTo('/main/settings')">⚙️ 系统设置</button>
74 - <button class="action-btn">📊 查看日志</button> 74 + <button class="action-btn" @click="navigateTo('/main/sys/log')">📊 查看日志</button>
75 </div> 75 </div>
76 </div> 76 </div>
77 </div> 77 </div>
...@@ -153,6 +153,13 @@ onMounted(() => { ...@@ -153,6 +153,13 @@ onMounted(() => {
153 fetchUserInfo() 153 fetchUserInfo()
154 }) 154 })
155 155
156 +// 页面跳转函数
157 +const navigateTo = (path: string) => {
158 + router.push(path).catch(err => {
159 + console.error('页面跳转失败:', err)
160 + })
161 +}
162 +
156 const logout = () => { 163 const logout = () => {
157 // 清除本地存储 164 // 清除本地存储
158 localStorage.removeItem('token') 165 localStorage.removeItem('token')
......
...@@ -429,6 +429,7 @@ ...@@ -429,6 +429,7 @@
429 429
430 <script setup lang="ts"> 430 <script setup lang="ts">
431 import { ref, reactive, computed, onMounted } from 'vue' 431 import { ref, reactive, computed, onMounted } from 'vue'
432 +import { ElMessage, ElMessageBox } from 'element-plus'
432 import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery' 433 import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery'
433 434
434 // 响应式数据 435 // 响应式数据
...@@ -514,7 +515,12 @@ const formatCurrency = (amount: number) => { ...@@ -514,7 +515,12 @@ const formatCurrency = (amount: number) => {
514 // 消息提示 515 // 消息提示
515 const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { 516 const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
516 console.log(`${type}: ${message}`) 517 console.log(`${type}: ${message}`)
517 - alert(message) 518 + ElMessage({
519 + message,
520 + type,
521 + duration: 3000,
522 + showClose: true
523 + })
518 } 524 }
519 525
520 // 获取页码数组 526 // 获取页码数组
...@@ -608,8 +614,78 @@ const handleDelete = async (delivery: DeliveryInfo) => { ...@@ -608,8 +614,78 @@ const handleDelete = async (delivery: DeliveryInfo) => {
608 } 614 }
609 615
610 616
611 -const handleExport = () => { 617 +const handleExport = async () => {
612 - showMessage('导出功能开发中...', 'warning') 618 + try {
619 + // 显示确认弹窗
620 + await ElMessageBox.confirm(
621 + '确定要导出出库数据吗?导出将包含当前筛选条件下的所有数据。',
622 + '确认导出',
623 + {
624 + confirmButtonText: '确定导出',
625 + cancelButtonText: '取消',
626 + type: 'warning',
627 + center: true
628 + }
629 + )
630 +
631 + // 用户确认后显示加载提示
632 + const loadingMessage = ElMessage({
633 + message: '正在导出数据,请稍候...',
634 + type: 'warning',
635 + duration: 0, // 不自动关闭
636 + showClose: false
637 + })
638 +
639 + try {
640 + // 准备导出参数
641 + const exportParams = {
642 + deliveryNo: searchParams.deliveryNo,
643 + dealerCode: searchParams.dealerCode,
644 + dealerName: searchParams.dealerName,
645 + deliveryStatus: searchParams.deliveryStatus,
646 + warehouseCode: searchParams.warehouseCode,
647 + dataSource: searchParams.dataSource,
648 + deliveryStartDate: searchParams.deliveryStartDate,
649 + deliveryEndDate: searchParams.deliveryEndDate
650 + }
651 +
652 + // 调用导出接口
653 + const response = await deliveryApi.exportDeliveries(exportParams)
654 +
655 + // 创建下载链接
656 + const blob = new Blob([response], {
657 + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
658 + })
659 + const url = window.URL.createObjectURL(blob)
660 + const link = document.createElement('a')
661 + link.href = url
662 +
663 + // 生成文件名
664 + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
665 + link.download = `出库数据_${timestamp}.xlsx`
666 +
667 + // 触发下载
668 + document.body.appendChild(link)
669 + link.click()
670 + document.body.removeChild(link)
671 + window.URL.revokeObjectURL(url)
672 +
673 + // 关闭加载提示,显示成功消息
674 + loadingMessage.close()
675 + ElMessage.success('导出成功!文件已开始下载')
676 +
677 + } catch (exportError) {
678 + // 关闭加载提示
679 + loadingMessage.close()
680 + throw exportError
681 + }
682 +
683 + } catch (error) {
684 + if (error !== 'cancel') {
685 + console.error('导出失败:', error)
686 + ElMessage.error('导出失败,请重试')
687 + }
688 + }
613 } 689 }
614 690
615 // 新增出库相关函数 691 // 新增出库相关函数
......
...@@ -507,6 +507,7 @@ ...@@ -507,6 +507,7 @@
507 507
508 <script setup lang="ts"> 508 <script setup lang="ts">
509 import { ref, reactive, computed, onMounted } from 'vue' 509 import { ref, reactive, computed, onMounted } from 'vue'
510 +import { ElMessage, ElMessageBox } from 'element-plus'
510 import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice' 511 import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice'
511 512
512 // 响应式数据 513 // 响应式数据
...@@ -594,7 +595,12 @@ const formatCurrency = (amount: number) => { ...@@ -594,7 +595,12 @@ const formatCurrency = (amount: number) => {
594 // 消息提示 595 // 消息提示
595 const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { 596 const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
596 console.log(`${type}: ${message}`) 597 console.log(`${type}: ${message}`)
597 - alert(message) 598 + ElMessage({
599 + message,
600 + type,
601 + duration: 3000,
602 + showClose: true
603 + })
598 } 604 }
599 605
600 // 获取页码数组 606 // 获取页码数组
...@@ -825,8 +831,81 @@ const handleView = async (order: InvoiceInfo) => { ...@@ -825,8 +831,81 @@ const handleView = async (order: InvoiceInfo) => {
825 } 831 }
826 832
827 833
828 -const handleExport = () => { 834 +const handleExport = async () => {
829 - showMessage('导出功能开发中...', 'warning') 835 + try {
836 + // 显示确认弹窗
837 + await ElMessageBox.confirm(
838 + '确定要导出发票数据吗?导出将包含当前筛选条件下的所有数据。',
839 + '确认导出',
840 + {
841 + confirmButtonText: '确定导出',
842 + cancelButtonText: '取消',
843 + type: 'warning',
844 + center: true
845 + }
846 + )
847 +
848 + // 用户确认后显示加载提示
849 + const loadingMessage = ElMessage({
850 + message: '正在导出数据,请稍候...',
851 + type: 'warning',
852 + duration: 0, // 不自动关闭
853 + showClose: false
854 + })
855 +
856 + try {
857 + // 准备导出参数
858 + const exportParams = {
859 + invoiceNo: searchParams.invoiceNo,
860 + orderNo: searchParams.orderNo,
861 + deliveryNo: searchParams.deliveryNo,
862 + dealerCode: searchParams.dealerCode,
863 + dealerName: searchParams.dealerName,
864 + invoiceStatus: searchParams.invoiceStatus,
865 + dataSource: searchParams.dataSource,
866 + invoiceStartDate: searchParams.invoiceStartDate,
867 + invoiceEndDate: searchParams.invoiceEndDate,
868 + minAmount: searchParams.minAmount,
869 + maxAmount: searchParams.maxAmount
870 + }
871 +
872 + // 调用导出接口
873 + const response = await invoiceApi.exportInvoices(exportParams)
874 +
875 + // 创建下载链接
876 + const blob = new Blob([response], {
877 + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
878 + })
879 + const url = window.URL.createObjectURL(blob)
880 + const link = document.createElement('a')
881 + link.href = url
882 +
883 + // 生成文件名
884 + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
885 + link.download = `发票数据_${timestamp}.xlsx`
886 +
887 + // 触发下载
888 + document.body.appendChild(link)
889 + link.click()
890 + document.body.removeChild(link)
891 + window.URL.revokeObjectURL(url)
892 +
893 + // 关闭加载提示,显示成功消息
894 + loadingMessage.close()
895 + ElMessage.success('导出成功!文件已开始下载')
896 +
897 + } catch (exportError) {
898 + // 关闭加载提示
899 + loadingMessage.close()
900 + throw exportError
901 + }
902 +
903 + } catch (error) {
904 + if (error !== 'cancel') {
905 + console.error('导出失败:', error)
906 + ElMessage.error('导出失败,请重试')
907 + }
908 + }
830 } 909 }
831 910
832 const handlePrintInvoice = (invoice: InvoiceInfo) => { 911 const handlePrintInvoice = (invoice: InvoiceInfo) => {
......
...@@ -447,6 +447,7 @@ ...@@ -447,6 +447,7 @@
447 447
448 <script setup lang="ts"> 448 <script setup lang="ts">
449 import { ref, reactive, computed, onMounted } from 'vue' 449 import { ref, reactive, computed, onMounted } from 'vue'
450 +import { ElMessage, ElMessageBox } from 'element-plus'
450 import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order' 451 import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order'
451 452
452 // 响应式数据 453 // 响应式数据
...@@ -586,9 +587,12 @@ const formatCurrency = (amount: number) => { ...@@ -586,9 +587,12 @@ const formatCurrency = (amount: number) => {
586 587
587 // 消息提示 588 // 消息提示
588 const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { 589 const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
589 - // 这里可以集成消息提示组件 590 + ElMessage({
590 - console.log(`${type}: ${message}`) 591 + message,
591 - alert(message) 592 + type,
593 + duration: 3000,
594 + showClose: true
595 + })
592 } 596 }
593 597
594 // 方法 598 // 方法
...@@ -757,8 +761,82 @@ const handleBatchDelete = async () => { ...@@ -757,8 +761,82 @@ const handleBatchDelete = async () => {
757 } 761 }
758 762
759 763
760 -const handleExport = () => { 764 +const handleExport = async () => {
761 - showMessage('导出功能开发中...', 'warning') 765 + try {
766 + // 显示确认弹窗
767 + await ElMessageBox.confirm(
768 + '确定要导出订单数据吗?导出将包含当前筛选条件下的所有数据。',
769 + '确认导出',
770 + {
771 + confirmButtonText: '确定导出',
772 + cancelButtonText: '取消',
773 + type: 'warning',
774 + center: true
775 + }
776 + )
777 +
778 + // 用户确认后显示加载提示
779 + const loadingMessage = ElMessage({
780 + message: '正在导出数据,请稍候...',
781 + type: 'warning',
782 + duration: 0, // 不自动关闭
783 + showClose: false
784 + })
785 +
786 + try {
787 + // 准备导出参数
788 + const exportParams = {
789 + orderNo: searchParams.orderNo,
790 + dealerCode: searchParams.dealerCode,
791 + dealerName: searchParams.dealerName,
792 + deliveryStatus: searchParams.deliveryStatus,
793 + invoiceStatus: searchParams.invoiceStatus,
794 + rebateCalcFlag: searchParams.rebateCalcFlag,
795 + dataSource: searchParams.dataSource,
796 + verifyStatus: searchParams.verifyStatus,
797 + orderStartDate: searchParams.orderStartDate,
798 + orderEndDate: searchParams.orderEndDate,
799 + minAmount: searchParams.minAmount,
800 + maxAmount: searchParams.maxAmount
801 + }
802 +
803 + // 调用导出接口
804 + const response = await orderApi.exportOrders(exportParams)
805 +
806 + // 创建下载链接
807 + const blob = new Blob([response], {
808 + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
809 + })
810 + const url = window.URL.createObjectURL(blob)
811 + const link = document.createElement('a')
812 + link.href = url
813 +
814 + // 生成文件名
815 + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
816 + link.download = `订单数据_${timestamp}.xlsx`
817 +
818 + // 触发下载
819 + document.body.appendChild(link)
820 + link.click()
821 + document.body.removeChild(link)
822 + window.URL.revokeObjectURL(url)
823 +
824 + // 关闭加载提示,显示成功消息
825 + loadingMessage.close()
826 + ElMessage.success('导出成功!文件已开始下载')
827 +
828 + } catch (exportError) {
829 + // 关闭加载提示
830 + loadingMessage.close()
831 + throw exportError
832 + }
833 +
834 + } catch (error) {
835 + if (error !== 'cancel') {
836 + console.error('导出失败:', error)
837 + ElMessage.error('导出失败,请重试')
838 + }
839 + }
762 } 840 }
763 841
764 const handleSelectAll = () => { 842 const handleSelectAll = () => {
......
...@@ -102,8 +102,14 @@ ...@@ -102,8 +102,14 @@
102 <div class="table-title"> 102 <div class="table-title">
103 返利记录 103 返利记录
104 </div> 104 </div>
105 - <div class="table-info"> 105 + <div class="table-actions">
106 - 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }} 106 + <el-button type="primary" size="small" @click="handleExport" :loading="exportLoading">
107 + <el-icon><Download /></el-icon>
108 + 导出
109 + </el-button>
110 + <div class="table-info">
111 + 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }}
112 + </div>
107 </div> 113 </div>
108 </div> 114 </div>
109 115
...@@ -384,12 +390,13 @@ import { ref, reactive, onMounted } from 'vue' ...@@ -384,12 +390,13 @@ import { ref, reactive, onMounted } from 'vue'
384 import { ElMessage, ElMessageBox } from 'element-plus' 390 import { ElMessage, ElMessageBox } from 'element-plus'
385 import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue' 391 import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue'
386 import * as echarts from 'echarts' 392 import * as echarts from 'echarts'
387 -import rebateApi, { type Rebate, type RebateSearchParams } from '@/api/rebate' 393 +import rebateApi, { type Rebate, type RebateSearchParams, exportRebates } from '@/api/rebate'
388 import { formatDate } from '@/utils/index' 394 import { formatDate } from '@/utils/index'
389 395
390 // 响应式数据 396 // 响应式数据
391 const loading = ref(false) 397 const loading = ref(false)
392 const submitLoading = ref(false) 398 const submitLoading = ref(false)
399 +const exportLoading = ref(false)
393 const auditLoading = ref(false) 400 const auditLoading = ref(false)
394 const rebateList = ref<Rebate[]>([]) 401 const rebateList = ref<Rebate[]>([])
395 const selectedRebates = ref<Rebate[]>([]) 402 const selectedRebates = ref<Rebate[]>([])
...@@ -1039,30 +1046,62 @@ const handleBatchRelease = async () => { ...@@ -1039,30 +1046,62 @@ const handleBatchRelease = async () => {
1039 // 导出 1046 // 导出
1040 const handleExport = async () => { 1047 const handleExport = async () => {
1041 try { 1048 try {
1042 - loading.value = true 1049 + // 显示确认弹窗
1043 - const params = { ...searchForm } 1050 + await ElMessageBox.confirm(
1044 - 1051 + '确定要导出返利数据吗?导出将包含当前筛选条件下的所有数据。',
1045 - const response = await rebateApi.exportRebate(params) 1052 + '确认导出',
1053 + {
1054 + confirmButtonText: '确定导出',
1055 + cancelButtonText: '取消',
1056 + type: 'warning',
1057 + center: true
1058 + }
1059 + )
1046 1060
1047 - // 创建下载链接 1061 + // 用户确认后显示加载提示
1048 - const blob = new Blob([response.data], { 1062 + const loadingMessage = ElMessage({
1049 - type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 1063 + message: '正在导出数据,请稍候...',
1064 + type: 'warning',
1065 + duration: 0, // 不自动关闭
1066 + showClose: false
1050 }) 1067 })
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 1068
1060 - ElMessage.success('导出成功') 1069 + try {
1070 + exportLoading.value = true
1071 + const params = { ...searchForm }
1072 +
1073 + const response = await exportRebates(params)
1074 +
1075 + // 创建下载链接
1076 + const blob = new Blob([response as unknown as BlobPart], {
1077 + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
1078 + })
1079 + const url = window.URL.createObjectURL(blob)
1080 + const link = document.createElement('a')
1081 + link.href = url
1082 + link.download = `返利数据_${new Date().toISOString().split('T')[0]}.xlsx`
1083 + document.body.appendChild(link)
1084 + link.click()
1085 + document.body.removeChild(link)
1086 + window.URL.revokeObjectURL(url)
1087 +
1088 + // 关闭加载提示,显示成功消息
1089 + loadingMessage.close()
1090 + ElMessage.success('导出成功!文件已开始下载')
1091 +
1092 + } catch (exportError) {
1093 + // 关闭加载提示
1094 + loadingMessage.close()
1095 + throw exportError
1096 + }
1097 +
1061 } catch (error) { 1098 } catch (error) {
1062 - console.error('导出失败:', error) 1099 + if (error !== 'cancel') {
1063 - ElMessage.error('导出失败') 1100 + console.error('导出失败:', error)
1101 + ElMessage.error('导出失败,请重试')
1102 + }
1064 } finally { 1103 } finally {
1065 - loading.value = false 1104 + exportLoading.value = false
1066 } 1105 }
1067 } 1106 }
1068 1107
...@@ -1546,9 +1585,9 @@ onMounted(async () => { ...@@ -1546,9 +1585,9 @@ onMounted(async () => {
1546 overflow: hidden; 1585 overflow: hidden;
1547 1586
1548 .table-header { 1587 .table-header {
1549 - display: flex; 1588 + display: flex;
1550 - justify-content: space-between; 1589 + justify-content: space-between;
1551 - align-items: center; 1590 + align-items: center;
1552 padding: 20px 20px 0 20px; 1591 padding: 20px 20px 0 20px;
1553 margin-bottom: 16px; 1592 margin-bottom: 16px;
1554 1593
...@@ -1558,9 +1597,15 @@ onMounted(async () => { ...@@ -1558,9 +1597,15 @@ onMounted(async () => {
1558 color: #333; 1597 color: #333;
1559 } 1598 }
1560 1599
1561 - .table-info { 1600 + .table-actions {
1562 - font-size: 12px; 1601 + display: flex;
1563 - color: #999; 1602 + align-items: center;
1603 + gap: 12px;
1604 +
1605 + .table-info {
1606 + font-size: 12px;
1607 + color: #999;
1608 + }
1564 } 1609 }
1565 } 1610 }
1566 1611
......
1 +<template>
2 + <div class="settings-container">
3 + <!-- 页面标题 -->
4 + <div class="page-header">
5 + <h2>系统设置</h2>
6 + <p>管理系统配置和参数</p>
7 + </div>
8 +
9 + <!-- 设置内容 -->
10 + <div class="settings-content">
11 + <div class="settings-card">
12 + <h3>基本设置</h3>
13 + <div class="setting-item">
14 + <label>系统名称:</label>
15 + <input v-model="settings.systemName" class="setting-input" placeholder="请输入系统名称" />
16 + </div>
17 + <div class="setting-item">
18 + <label>系统版本:</label>
19 + <input v-model="settings.systemVersion" class="setting-input" placeholder="请输入系统版本" />
20 + </div>
21 + <div class="setting-item">
22 + <label>系统描述:</label>
23 + <textarea v-model="settings.systemDescription" class="setting-textarea" placeholder="请输入系统描述"></textarea>
24 + </div>
25 + </div>
26 +
27 + <div class="settings-card">
28 + <h3>业务设置</h3>
29 + <div class="setting-item">
30 + <label>默认分页大小:</label>
31 + <select v-model="settings.defaultPageSize" class="setting-select">
32 + <option value="10">10条/页</option>
33 + <option value="20">20条/页</option>
34 + <option value="50">50条/页</option>
35 + <option value="100">100条/页</option>
36 + </select>
37 + </div>
38 + <div class="setting-item">
39 + <label>数据保留天数:</label>
40 + <input v-model="settings.dataRetentionDays" type="number" class="setting-input" placeholder="请输入数据保留天数" />
41 + </div>
42 + <div class="setting-item">
43 + <label>自动备份:</label>
44 + <label class="checkbox-label">
45 + <input v-model="settings.autoBackup" type="checkbox" />
46 + <span>启用自动备份</span>
47 + </label>
48 + </div>
49 + </div>
50 +
51 + <div class="settings-card">
52 + <h3>安全设置</h3>
53 + <div class="setting-item">
54 + <label>会话超时时间(分钟):</label>
55 + <input v-model="settings.sessionTimeout" type="number" class="setting-input" placeholder="请输入会话超时时间" />
56 + </div>
57 + <div class="setting-item">
58 + <label>密码复杂度:</label>
59 + <select v-model="settings.passwordComplexity" class="setting-select">
60 + <option value="low">低</option>
61 + <option value="medium">中</option>
62 + <option value="high">高</option>
63 + </select>
64 + </div>
65 + <div class="setting-item">
66 + <label>登录失败锁定:</label>
67 + <label class="checkbox-label">
68 + <input v-model="settings.loginLock" type="checkbox" />
69 + <span>启用登录失败锁定</span>
70 + </label>
71 + </div>
72 + </div>
73 +
74 + <div class="settings-card">
75 + <h3>通知设置</h3>
76 + <div class="setting-item">
77 + <label>邮件通知:</label>
78 + <label class="checkbox-label">
79 + <input v-model="settings.emailNotification" type="checkbox" />
80 + <span>启用邮件通知</span>
81 + </label>
82 + </div>
83 + <div class="setting-item">
84 + <label>短信通知:</label>
85 + <label class="checkbox-label">
86 + <input v-model="settings.smsNotification" type="checkbox" />
87 + <span>启用短信通知</span>
88 + </label>
89 + </div>
90 + <div class="setting-item">
91 + <label>系统消息:</label>
92 + <label class="checkbox-label">
93 + <input v-model="settings.systemMessage" type="checkbox" />
94 + <span>启用系统消息</span>
95 + </label>
96 + </div>
97 + </div>
98 +
99 + <!-- 操作按钮 -->
100 + <div class="settings-actions">
101 + <button @click="handleSave" class="action-btn primary">💾 保存设置</button>
102 + <button @click="handleReset" class="action-btn secondary">🔄 重置</button>
103 + <button @click="handleTest" class="action-btn info">🧪 测试连接</button>
104 + </div>
105 + </div>
106 + </div>
107 +</template>
108 +
109 +<script setup lang="ts">
110 +import { ref, reactive, onMounted } from 'vue'
111 +
112 +// 设置数据
113 +const settings = reactive({
114 + systemName: 'Apple ERP系统',
115 + systemVersion: '1.0.0',
116 + systemDescription: '企业资源规划管理系统',
117 + defaultPageSize: 20,
118 + dataRetentionDays: 365,
119 + autoBackup: true,
120 + sessionTimeout: 30,
121 + passwordComplexity: 'medium',
122 + loginLock: true,
123 + emailNotification: true,
124 + smsNotification: false,
125 + systemMessage: true
126 +})
127 +
128 +// 原始设置(用于重置)
129 +const originalSettings = ref({})
130 +
131 +// 保存设置
132 +const handleSave = () => {
133 + // 这里可以调用后端API保存设置
134 + console.log('保存设置:', settings)
135 + showMessage('设置保存成功', 'success')
136 +}
137 +
138 +// 重置设置
139 +const handleReset = () => {
140 + Object.assign(settings, originalSettings.value)
141 + showMessage('设置已重置', 'warning')
142 +}
143 +
144 +// 测试连接
145 +const handleTest = () => {
146 + showMessage('连接测试成功', 'success')
147 +}
148 +
149 +// 显示消息
150 +const showMessage = (message: string, type: 'success' | 'warning' | 'error') => {
151 + // 这里可以集成Element Plus的消息组件
152 + console.log(`${type}: ${message}`)
153 +}
154 +
155 +// 组件挂载时加载设置
156 +onMounted(() => {
157 + // 这里可以调用后端API加载设置
158 + originalSettings.value = { ...settings }
159 +})
160 +</script>
161 +
162 +<style scoped>
163 +.settings-container {
164 + padding: 20px;
165 + background-color: #f5f5f5;
166 + min-height: 100vh;
167 +}
168 +
169 +.page-header {
170 + margin-bottom: 30px;
171 + text-align: center;
172 +}
173 +
174 +.page-header h2 {
175 + color: #333;
176 + margin-bottom: 10px;
177 + font-size: 28px;
178 +}
179 +
180 +.page-header p {
181 + color: #666;
182 + font-size: 16px;
183 +}
184 +
185 +.settings-content {
186 + max-width: 1200px;
187 + margin: 0 auto;
188 +}
189 +
190 +.settings-card {
191 + background: white;
192 + border-radius: 8px;
193 + padding: 24px;
194 + margin-bottom: 24px;
195 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
196 +}
197 +
198 +.settings-card h3 {
199 + color: #333;
200 + margin-bottom: 20px;
201 + font-size: 18px;
202 + border-bottom: 2px solid #e9ecef;
203 + padding-bottom: 10px;
204 +}
205 +
206 +.setting-item {
207 + display: flex;
208 + align-items: center;
209 + margin-bottom: 20px;
210 + gap: 16px;
211 +}
212 +
213 +.setting-item label {
214 + min-width: 150px;
215 + color: #333;
216 + font-weight: 500;
217 +}
218 +
219 +.setting-input,
220 +.setting-select,
221 +.setting-textarea {
222 + flex: 1;
223 + padding: 8px 12px;
224 + border: 1px solid #ddd;
225 + border-radius: 4px;
226 + font-size: 14px;
227 + transition: border-color 0.3s;
228 +}
229 +
230 +.setting-input:focus,
231 +.setting-select:focus,
232 +.setting-textarea:focus {
233 + outline: none;
234 + border-color: #007bff;
235 + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
236 +}
237 +
238 +.setting-textarea {
239 + min-height: 80px;
240 + resize: vertical;
241 +}
242 +
243 +.checkbox-label {
244 + display: flex;
245 + align-items: center;
246 + gap: 8px;
247 + cursor: pointer;
248 +}
249 +
250 +.checkbox-label input[type="checkbox"] {
251 + width: 16px;
252 + height: 16px;
253 +}
254 +
255 +.settings-actions {
256 + display: flex;
257 + gap: 16px;
258 + justify-content: center;
259 + margin-top: 30px;
260 +}
261 +
262 +.action-btn {
263 + padding: 12px 24px;
264 + border: none;
265 + border-radius: 6px;
266 + font-size: 14px;
267 + font-weight: 500;
268 + cursor: pointer;
269 + transition: all 0.3s;
270 + min-width: 120px;
271 +}
272 +
273 +.action-btn.primary {
274 + background-color: #007bff;
275 + color: white;
276 +}
277 +
278 +.action-btn.primary:hover {
279 + background-color: #0056b3;
280 +}
281 +
282 +.action-btn.secondary {
283 + background-color: #6c757d;
284 + color: white;
285 +}
286 +
287 +.action-btn.secondary:hover {
288 + background-color: #545b62;
289 +}
290 +
291 +.action-btn.info {
292 + background-color: #17a2b8;
293 + color: white;
294 +}
295 +
296 +.action-btn.info:hover {
297 + background-color: #138496;
298 +}
299 +
300 +@media (max-width: 768px) {
301 + .setting-item {
302 + flex-direction: column;
303 + align-items: flex-start;
304 + }
305 +
306 + .setting-item label {
307 + min-width: auto;
308 + margin-bottom: 8px;
309 + }
310 +
311 + .settings-actions {
312 + flex-direction: column;
313 + align-items: center;
314 + }
315 +}
316 +</style>