feat(export): 实现出库、发票、订单和返利数据导出功能
- 添加出库数据导出接口及前端实现 - 添加发票数据导出接口及前端实现 - 添加订单数据导出接口及前端实现 - 添加返利数据导出接口及前端实现 - 统一导出文件命名格式为 yyyyMMdd_HHmmss - 使用 Element Plus 的消息提示和确认弹窗优化用户体验 - 集成 ExcelExportService 实现后端数据导出逻辑 - 更新 API 文档补充数据导出接口说明 - 引入 Element Plus 并注册全局组件 - 重构字典管理 API 使用统一请求工具 - 修复 dashboard 快速操作按钮路由跳转问题
Showing
27 changed files
with
1407 additions
and
207 deletions
| ... | @@ -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 | /** |
| ... | @@ -33,6 +37,9 @@ public class DeliveryMainController { | ... | @@ -33,6 +37,9 @@ public class DeliveryMainController { |
| 33 | @Autowired | 37 | @Autowired |
| 34 | private DeliveryMainService deliveryMainService; | 38 | private DeliveryMainService deliveryMainService; |
| 35 | 39 | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 42 | + | ||
| 36 | @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表") | 43 | @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| 38 | @PreAuthorize("hasAuthority('delivery:list')") | 45 | @PreAuthorize("hasAuthority('delivery: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 | /** |
| ... | @@ -33,6 +37,9 @@ public class InvoiceMainController { | ... | @@ -33,6 +37,9 @@ public class InvoiceMainController { |
| 33 | @Autowired | 37 | @Autowired |
| 34 | private InvoiceMainService invoiceMainService; | 38 | private InvoiceMainService invoiceMainService; |
| 35 | 39 | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 42 | + | ||
| 36 | @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表") | 43 | @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| 38 | @PreAuthorize("hasAuthority('invoice:list')") | 45 | @PreAuthorize("hasAuthority('invoice: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 | /** |
| ... | @@ -33,6 +37,9 @@ public class OrderMainController { | ... | @@ -33,6 +37,9 @@ public class OrderMainController { |
| 33 | @Autowired | 37 | @Autowired |
| 34 | private OrderMainService orderMainService; | 38 | private OrderMainService orderMainService; |
| 35 | 39 | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 42 | + | ||
| 36 | @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表") | 43 | @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| 38 | @PreAuthorize("hasAuthority('order:list')") | 45 | @PreAuthorize("hasAuthority('order: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 | * 返利台账明细管理控制器 |
| ... | @@ -37,6 +42,9 @@ public class RebateController { | ... | @@ -37,6 +42,9 @@ public class RebateController { |
| 37 | @Autowired | 42 | @Autowired |
| 38 | private RebateService rebateService; | 43 | private RebateService rebateService; |
| 39 | 44 | ||
| 45 | + @Autowired | ||
| 46 | + private ExcelExportService excelExportService; | ||
| 47 | + | ||
| 40 | @Operation(summary = "分页查询返利明细列表") | 48 | @Operation(summary = "分页查询返利明细列表") |
| 41 | @PostMapping("/page") | 49 | @PostMapping("/page") |
| 42 | public ApiRes<IPage<RebateRes>> getRebatePage(@Valid @RequestBody RebateQueryReq queryReq) { | 50 | public ApiRes<IPage<RebateRes>> getRebatePage(@Valid @RequestBody RebateQueryReq queryReq) { |
| ... | @@ -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 | +} |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
| 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 | +} |
| ... | @@ -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,10 +102,16 @@ | ... | @@ -102,10 +102,16 @@ |
| 102 | <div class="table-title"> | 102 | <div class="table-title"> |
| 103 | 返利记录 | 103 | 返利记录 |
| 104 | </div> | 104 | </div> |
| 105 | + <div class="table-actions"> | ||
| 106 | + <el-button type="primary" size="small" @click="handleExport" :loading="exportLoading"> | ||
| 107 | + <el-icon><Download /></el-icon> | ||
| 108 | + 导出 | ||
| 109 | + </el-button> | ||
| 105 | <div class="table-info"> | 110 | <div class="table-info"> |
| 106 | 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }} | 111 | 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }} |
| 107 | </div> | 112 | </div> |
| 108 | </div> | 113 | </div> |
| 114 | + </div> | ||
| 109 | 115 | ||
| 110 | <el-table | 116 | <el-table |
| 111 | v-loading="loading" | 117 | v-loading="loading" |
| ... | @@ -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,13 +1046,34 @@ const handleBatchRelease = async () => { | ... | @@ -1039,13 +1046,34 @@ const handleBatchRelease = async () => { |
| 1039 | // 导出 | 1046 | // 导出 |
| 1040 | const handleExport = async () => { | 1047 | const handleExport = async () => { |
| 1041 | try { | 1048 | try { |
| 1042 | - loading.value = true | 1049 | + // 显示确认弹窗 |
| 1050 | + await ElMessageBox.confirm( | ||
| 1051 | + '确定要导出返利数据吗?导出将包含当前筛选条件下的所有数据。', | ||
| 1052 | + '确认导出', | ||
| 1053 | + { | ||
| 1054 | + confirmButtonText: '确定导出', | ||
| 1055 | + cancelButtonText: '取消', | ||
| 1056 | + type: 'warning', | ||
| 1057 | + center: true | ||
| 1058 | + } | ||
| 1059 | + ) | ||
| 1060 | + | ||
| 1061 | + // 用户确认后显示加载提示 | ||
| 1062 | + const loadingMessage = ElMessage({ | ||
| 1063 | + message: '正在导出数据,请稍候...', | ||
| 1064 | + type: 'warning', | ||
| 1065 | + duration: 0, // 不自动关闭 | ||
| 1066 | + showClose: false | ||
| 1067 | + }) | ||
| 1068 | + | ||
| 1069 | + try { | ||
| 1070 | + exportLoading.value = true | ||
| 1043 | const params = { ...searchForm } | 1071 | const params = { ...searchForm } |
| 1044 | 1072 | ||
| 1045 | - const response = await rebateApi.exportRebate(params) | 1073 | + const response = await exportRebates(params) |
| 1046 | 1074 | ||
| 1047 | // 创建下载链接 | 1075 | // 创建下载链接 |
| 1048 | - const blob = new Blob([response.data], { | 1076 | + const blob = new Blob([response as unknown as BlobPart], { |
| 1049 | type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | 1077 | type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' |
| 1050 | }) | 1078 | }) |
| 1051 | const url = window.URL.createObjectURL(blob) | 1079 | const url = window.URL.createObjectURL(blob) |
| ... | @@ -1057,12 +1085,23 @@ const handleExport = async () => { | ... | @@ -1057,12 +1085,23 @@ const handleExport = async () => { |
| 1057 | document.body.removeChild(link) | 1085 | document.body.removeChild(link) |
| 1058 | window.URL.revokeObjectURL(url) | 1086 | window.URL.revokeObjectURL(url) |
| 1059 | 1087 | ||
| 1060 | - ElMessage.success('导出成功') | 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) { |
| 1099 | + if (error !== 'cancel') { | ||
| 1062 | console.error('导出失败:', error) | 1100 | console.error('导出失败:', error) |
| 1063 | - ElMessage.error('导出失败') | 1101 | + ElMessage.error('导出失败,请重试') |
| 1102 | + } | ||
| 1064 | } finally { | 1103 | } finally { |
| 1065 | - loading.value = false | 1104 | + exportLoading.value = false |
| 1066 | } | 1105 | } |
| 1067 | } | 1106 | } |
| 1068 | 1107 | ||
| ... | @@ -1558,11 +1597,17 @@ onMounted(async () => { | ... | @@ -1558,11 +1597,17 @@ onMounted(async () => { |
| 1558 | color: #333; | 1597 | color: #333; |
| 1559 | } | 1598 | } |
| 1560 | 1599 | ||
| 1600 | + .table-actions { | ||
| 1601 | + display: flex; | ||
| 1602 | + align-items: center; | ||
| 1603 | + gap: 12px; | ||
| 1604 | + | ||
| 1561 | .table-info { | 1605 | .table-info { |
| 1562 | font-size: 12px; | 1606 | font-size: 12px; |
| 1563 | color: #999; | 1607 | color: #999; |
| 1564 | } | 1608 | } |
| 1565 | } | 1609 | } |
| 1610 | + } | ||
| 1566 | 1611 | ||
| 1567 | .el-table { | 1612 | .el-table { |
| 1568 | border: none; | 1613 | border: none; | ... | ... |
frontend/src/views/settings/index.vue
0 → 100644
| 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> |
-
Please register or login to post a comment