jiaxing.zhou

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

- 添加出库数据导出接口及前端实现
- 添加发票数据导出接口及前端实现
- 添加订单数据导出接口及前端实现
- 添加返利数据导出接口及前端实现
- 统一导出文件命名格式为 yyyyMMdd_HHmmss
- 使用 Element Plus 的消息提示和确认弹窗优化用户体验
- 集成 ExcelExportService 实现后端数据导出逻辑
- 更新 API 文档补充数据导出接口说明
- 引入 Element Plus 并注册全局组件
- 重构字典管理 API 使用统一请求工具
- 修复 dashboard 快速操作按钮路由跳转问题
......@@ -2331,6 +2331,50 @@
}
```
### 8. 导出发票数据
**接口路径:** `POST /api/invoice/export`
**请求方法:** POST
**权限要求:** `invoice:export`
**请求参数:** 同发票查询接口参数
**响应:** 返回Excel文件流
---
## 八、数据导出接口
### 1. 订单数据导出
**接口路径:** `POST /order/export`
**请求方法:** POST
**权限要求:** `order:export`
**请求参数:** 同订单查询接口参数
**响应:** 返回Excel文件流,文件名格式:`订单数据_yyyyMMdd_HHmmss.xlsx`
### 2. 出库数据导出
**接口路径:** `POST /api/delivery/export`
**请求方法:** POST
**权限要求:** `delivery:export`
**请求参数:** 同出库查询接口参数
**响应:** 返回Excel文件流,文件名格式:`出库数据_yyyyMMdd_HHmmss.xlsx`
### 3. 发票数据导出
**接口路径:** `POST /api/invoice/export`
**请求方法:** POST
**权限要求:** `invoice:export`
**请求参数:** 同发票查询接口参数
**响应:** 返回Excel文件流,文件名格式:`发票数据_yyyyMMdd_HHmmss.xlsx`
---
**文档版本:** 1.0.0
......
package com.apple.erp.config;
import java.util.HashMap;
import java.util.Map;
/**
* 字典值配置类
* 集中管理所有字典值转换配置
*
* @author Apple ERP System
* @since 2025-01-01
*/
public class DictConfig {
/**
* 操作类型字典
*/
public static final Map<Integer, String> OPERATE_TYPE = new HashMap<Integer, String>() {{
put(1, "新增");
put(2, "修改");
put(3, "删除");
}};
/**
* 计算状态字典
*/
public static final Map<Integer, String> CALC_FLAG = new HashMap<Integer, String>() {{
put(0, "未计算");
put(1, "已计算");
}};
/**
* 审核状态字典
*/
public static final Map<Integer, String> AUDIT_STATUS = new HashMap<Integer, String>() {{
put(0, "待审核");
put(1, "审核通过");
put(2, "审核中");
put(3, "审核拒绝");
}};
/**
* 出库状态字典
*/
public static final Map<Integer, String> DELIVERY_STATUS = new HashMap<Integer, String>() {{
put(0, "未出库");
put(1, "已出库");
put(2, "部分出库");
}};
/**
* 发票状态字典
*/
public static final Map<Integer, String> INVOICE_STATUS = new HashMap<Integer, String>() {{
put(0, "未开票");
put(1, "已开票");
put(2, "部分开票");
}};
/**
* 返利计算状态字典
*/
public static final Map<Integer, String> REBATE_CALC_FLAG = new HashMap<Integer, String>() {{
put(0, "未计算");
put(1, "已计算");
}};
/**
* 审核状态字典
*/
public static final Map<Integer, String> VERIFY_STATUS = new HashMap<Integer, String>() {{
put(0, "待审核");
put(1, "审核通过");
put(2, "审核中");
put(3, "审核拒绝");
}};
/**
* 工单状态字典
*/
public static final Map<Integer, String> WORKORDER_STATUS = new HashMap<Integer, String>() {{
put(0, "待处理");
put(1, "处理中");
put(2, "已完成");
put(3, "已关闭");
}};
/**
* 严重程度字典
*/
public static final Map<Integer, String> SEVERITY_LEVEL = new HashMap<Integer, String>() {{
put(1, "低");
put(2, "中");
put(3, "高");
put(4, "紧急");
}};
}
package com.apple.erp.config;
import java.util.HashMap;
import java.util.Map;
/**
* 导出配置类
* 定义各模块的导出字段映射配置
*
* @author Apple ERP System
* @since 2025-01-01
*/
public class ExportConfig {
/**
* 订单导出配置
*/
public static final Map<String, String[]> ORDER_EXPORT_CONFIG = new HashMap<String, String[]>() {{
put("headers", new String[]{
"订单ID", "订单编号", "经销商编码", "经销商名称", "订单日期",
"订单金额", "返利金额", "出库状态", "开票状态", "返利计算状态",
"数据来源", "审核状态", "上传时间", "创建时间"
});
put("fields", new String[]{
"orderId", "orderNo", "dealerCode", "dealerName", "orderDate",
"totalAmount", "rebateAmount", "deliveryStatus", "invoiceStatus", "rebateCalcFlag",
"dataSource", "verifyStatus", "uploadTime", "createTime"
});
}};
/**
* 出库导出配置
*/
public static final Map<String, String[]> DELIVERY_EXPORT_CONFIG = new HashMap<String, String[]>() {{
put("headers", new String[]{
"出库单ID", "出库单编号", "经销商编码", "经销商名称", "出库日期",
"关联订单编号", "出库状态", "仓库编码", "数据来源", "创建时间"
});
put("fields", new String[]{
"deliveryId", "deliveryNo", "dealerCode", "dealerName", "deliveryDate",
"orderNo", "deliveryStatus", "warehouseCode", "dataSource", "createTime"
});
}};
/**
* 发票导出配置
*/
public static final Map<String, String[]> INVOICE_EXPORT_CONFIG = new HashMap<String, String[]>() {{
put("headers", new String[]{
"发票ID", "发票编号", "订单编号", "出库单编号", "经销商编码",
"经销商名称", "发票金额", "发票日期", "开票状态", "税率",
"数据来源", "创建时间"
});
put("fields", new String[]{
"invoiceId", "invoiceNo", "orderNo", "deliveryNo", "dealerCode",
"dealerName", "totalAmount", "invoiceDate", "invoiceStatus", "taxRate",
"dataSource", "createTime"
});
}};
/**
* 返利导出配置
*/
public static final Map<String, String[]> REBATE_EXPORT_CONFIG = new HashMap<String, String[]>() {{
put("headers", new String[]{
"返利ID", "返利编号", "订单编号", "经销商编码", "经销商名称",
"返利金额", "返利类型", "计算状态", "审核状态", "数据来源",
"创建时间", "更新时间"
});
put("fields", new String[]{
"rebateId", "rebateNo", "orderNo", "dealerCode", "dealerName",
"rebateAmount", "operateType", "calcFlag", "auditStatus", "dataSource",
"createTime", "updateTime"
});
}};
/**
* 异常工单导出配置
*/
public static final Map<String, String[]> EXCEPTION_WORKORDER_EXPORT_CONFIG = new HashMap<String, String[]>() {{
put("headers", new String[]{
"工单ID", "工单编号", "工单类型", "严重程度", "工单状态",
"问题描述", "处理人", "创建人", "创建时间", "更新时间"
});
put("fields", new String[]{
"workorderId", "workorderNo", "workorderType", "severityLevel", "workorderStatus",
"problemDescription", "assignee", "creator", "createTime", "updateTime"
});
}};
}
......@@ -6,16 +6,20 @@ import com.apple.erp.dto.DeliveryRes;
import com.apple.erp.dto.DeliveryUpdateReq;
import com.apple.erp.service.DeliveryMainService;
import com.apple.erp.dto.response.ApiRes;
import com.apple.erp.service.ExcelExportService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
......@@ -32,6 +36,9 @@ public class DeliveryMainController {
@Autowired
private DeliveryMainService deliveryMainService;
@Autowired
private ExcelExportService excelExportService;
@Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表")
@GetMapping("/list")
......@@ -139,6 +146,40 @@ public class DeliveryMainController {
return ApiRes.error("修改出库状态失败: " + e.getMessage());
}
}
@Operation(summary = "导出出库数据", description = "根据查询条件导出出库数据到Excel")
@PostMapping("/export")
@PreAuthorize("hasAuthority('delivery:export')")
public ResponseEntity<byte[]> exportDeliveries(@Valid @RequestBody DeliveryQueryReq queryReq) {
try {
// 获取所有符合条件的数据(不分页)
DeliveryQueryReq exportQuery = new DeliveryQueryReq();
exportQuery.setDeliveryNo(queryReq.getDeliveryNo());
exportQuery.setDealerCode(queryReq.getDealerCode());
exportQuery.setDealerName(queryReq.getDealerName());
exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus());
exportQuery.setWarehouseCode(queryReq.getWarehouseCode());
exportQuery.setDataSource(queryReq.getDataSource());
exportQuery.setDeliveryStartDate(queryReq.getDeliveryStartDate());
exportQuery.setDeliveryEndDate(queryReq.getDeliveryEndDate());
// 设置大分页获取所有数据
exportQuery.setPageNum(1);
exportQuery.setPageSize(10000);
Page<DeliveryRes> result = deliveryMainService.getDeliveryList(exportQuery);
List<DeliveryRes> deliveries = result.getRecords();
// 生成文件名
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String fileName = "出库数据_" + timestamp;
// 导出到Excel
return excelExportService.exportDeliveries(deliveries);
} catch (Exception e) {
throw new RuntimeException("导出出库数据失败: " + e.getMessage(), e);
}
}
}
......
......@@ -6,16 +6,20 @@ import com.apple.erp.dto.InvoiceRes;
import com.apple.erp.dto.InvoiceUpdateReq;
import com.apple.erp.service.InvoiceMainService;
import com.apple.erp.dto.response.ApiRes;
import com.apple.erp.service.ExcelExportService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
......@@ -32,6 +36,9 @@ public class InvoiceMainController {
@Autowired
private InvoiceMainService invoiceMainService;
@Autowired
private ExcelExportService excelExportService;
@Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表")
@GetMapping("/list")
......@@ -139,6 +146,43 @@ public class InvoiceMainController {
return ApiRes.error("修改发票状态失败: " + e.getMessage());
}
}
@Operation(summary = "导出发票数据", description = "根据查询条件导出发票数据到Excel")
@PostMapping("/export")
@PreAuthorize("hasAuthority('invoice:export')")
public ResponseEntity<byte[]> exportInvoices(@Valid @RequestBody InvoiceQueryReq queryReq) {
try {
// 获取所有符合条件的数据(不分页)
InvoiceQueryReq exportQuery = new InvoiceQueryReq();
exportQuery.setInvoiceNo(queryReq.getInvoiceNo());
exportQuery.setOrderNo(queryReq.getOrderNo());
exportQuery.setDeliveryNo(queryReq.getDeliveryNo());
exportQuery.setDealerCode(queryReq.getDealerCode());
exportQuery.setDealerName(queryReq.getDealerName());
exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus());
exportQuery.setDataSource(queryReq.getDataSource());
exportQuery.setInvoiceStartDate(queryReq.getInvoiceStartDate());
exportQuery.setInvoiceEndDate(queryReq.getInvoiceEndDate());
exportQuery.setMinAmount(queryReq.getMinAmount());
exportQuery.setMaxAmount(queryReq.getMaxAmount());
// 设置大分页获取所有数据
exportQuery.setPageNum(1);
exportQuery.setPageSize(10000);
Page<InvoiceRes> result = invoiceMainService.getInvoiceList(exportQuery);
List<InvoiceRes> invoices = result.getRecords();
// 生成文件名
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String fileName = "发票数据_" + timestamp;
// 导出到Excel
return excelExportService.exportInvoices(invoices);
} catch (Exception e) {
throw new RuntimeException("导出发票数据失败: " + e.getMessage(), e);
}
}
}
......
......@@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes;
import com.apple.erp.dto.OrderUpdateReq;
import com.apple.erp.service.OrderMainService;
import com.apple.erp.dto.response.ApiRes;
import com.apple.erp.service.ExcelExportService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
......@@ -32,6 +36,9 @@ public class OrderMainController {
@Autowired
private OrderMainService orderMainService;
@Autowired
private ExcelExportService excelExportService;
@Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表")
@GetMapping("/list")
......@@ -182,4 +189,42 @@ public class OrderMainController {
return ApiRes.error("修改返利计算状态失败: " + e.getMessage());
}
}
@Operation(summary = "导出订单数据", description = "根据查询条件导出订单数据到Excel")
@PostMapping("/export")
@PreAuthorize("hasAuthority('order:export')")
public ResponseEntity<byte[]> exportOrders(@Valid @RequestBody OrderQueryReq queryReq) {
try {
// 获取所有符合条件的数据(不分页)
OrderQueryReq exportQuery = new OrderQueryReq();
exportQuery.setOrderNo(queryReq.getOrderNo());
exportQuery.setDealerCode(queryReq.getDealerCode());
exportQuery.setDealerName(queryReq.getDealerName());
exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus());
exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus());
exportQuery.setRebateCalcFlag(queryReq.getRebateCalcFlag());
exportQuery.setDataSource(queryReq.getDataSource());
exportQuery.setVerifyStatus(queryReq.getVerifyStatus());
exportQuery.setOrderStartDate(queryReq.getOrderStartDate());
exportQuery.setOrderEndDate(queryReq.getOrderEndDate());
exportQuery.setMinAmount(queryReq.getMinAmount());
exportQuery.setMaxAmount(queryReq.getMaxAmount());
// 设置大分页获取所有数据
exportQuery.setPageNum(1);
exportQuery.setPageSize(10000);
Page<OrderRes> result = orderMainService.getOrderList(exportQuery);
List<OrderRes> orders = result.getRecords();
// 生成文件名
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String fileName = "订单数据_" + timestamp;
// 导出到Excel
return excelExportService.exportOrders(orders);
} catch (Exception e) {
throw new RuntimeException("导出订单数据失败: " + e.getMessage(), e);
}
}
}
......
......@@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes;
import com.apple.erp.dto.response.RebateRes;
import com.apple.erp.entity.Rebate;
import com.apple.erp.service.RebateService;
import com.apple.erp.service.ExcelExportService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
......@@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
/**
* 返利台账明细管理控制器
......@@ -36,6 +41,9 @@ public class RebateController {
@Autowired
private RebateService rebateService;
@Autowired
private ExcelExportService excelExportService;
@Operation(summary = "分页查询返利明细列表")
@PostMapping("/page")
......@@ -277,4 +285,40 @@ public class RebateController {
return ApiRes.error(e.getMessage());
}
}
@Operation(summary = "导出返利数据", description = "根据查询条件导出返利数据到Excel")
@PostMapping("/export")
@PreAuthorize("hasAuthority('rebate:export')")
public ResponseEntity<byte[]> exportRebates(@Valid @RequestBody RebateQueryReq queryReq) {
log.info("导出返利数据,参数:{}", queryReq);
try {
// 获取所有符合条件的数据(不分页)
RebateQueryReq exportQuery = new RebateQueryReq();
exportQuery.setRebateNo(queryReq.getRebateNo());
exportQuery.setOrderNo(queryReq.getOrderNo());
exportQuery.setDealerCode(queryReq.getDealerCode());
exportQuery.setDealerName(queryReq.getDealerName());
exportQuery.setOperateType(queryReq.getOperateType());
exportQuery.setCalcFlag(queryReq.getCalcFlag());
exportQuery.setAuditStatus(queryReq.getAuditStatus());
exportQuery.setDataSource(queryReq.getDataSource());
exportQuery.setStartDate(queryReq.getStartDate());
exportQuery.setEndDate(queryReq.getEndDate());
exportQuery.setMinAmount(queryReq.getMinAmount());
exportQuery.setMaxAmount(queryReq.getMaxAmount());
// 设置大分页获取所有数据
exportQuery.setPageNum(1);
exportQuery.setPageSize(10000);
IPage<RebateRes> result = rebateService.getRebatePage(exportQuery);
List<RebateRes> rebates = result.getRecords();
// 导出到Excel
return excelExportService.exportRebates(rebates);
} catch (Exception e) {
log.error("导出返利数据失败", e);
throw new RuntimeException("导出返利数据失败: " + e.getMessage(), e);
}
}
}
......
......@@ -193,6 +193,25 @@ public class SysDictItemController {
}
/**
* 刷新字典缓存
* 当字典项发生变化时,刷新Redis缓存以保持数据一致性
*
* @return 操作结果
*/
@Operation(summary = "刷新字典缓存", description = "刷新Redis字典缓存以保持数据一致性")
@PostMapping("/refreshCache")
@PreAuthorize("hasAuthority('sys:dict:edit')")
public ApiRes<Void> refreshCache() {
try {
// 调用字典值转换器的刷新方法
com.apple.erp.util.DictValueConverter.refreshCache();
return ApiRes.success("字典缓存刷新成功", null);
} catch (Exception e) {
return ApiRes.error("刷新字典缓存失败: " + e.getMessage());
}
}
/**
* 转换SysDictItem为DictItemRes
*
* @param dictItem 字典项实体
......
......@@ -58,6 +58,36 @@ public class RebateQueryReq {
private String rebateEndDate;
/**
* 审核状态
*/
private Integer auditStatus;
/**
* 数据来源
*/
private String dataSource;
/**
* 开始日期
*/
private String startDate;
/**
* 结束日期
*/
private String endDate;
/**
* 最小金额
*/
private java.math.BigDecimal minAmount;
/**
* 最大金额
*/
private java.math.BigDecimal maxAmount;
/**
* 页码
*/
private Integer pageNum = 1;
......
package com.apple.erp.service;
import com.apple.erp.config.ExportConfig;
import com.apple.erp.util.GenericExcelExportUtil;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
/**
* Excel导出服务类
* 提供统一的导出接口,各模块可独立使用
*
* @author Apple ERP System
* @since 2025-01-01
*/
@Service
public class ExcelExportService {
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
/**
* 导出订单数据
*/
public org.springframework.http.ResponseEntity<byte[]> exportOrders(List<?> data) {
Map<String, String[]> config = ExportConfig.ORDER_EXPORT_CONFIG;
String fileName = "订单数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
}
/**
* 导出出库数据
*/
public org.springframework.http.ResponseEntity<byte[]> exportDeliveries(List<?> data) {
Map<String, String[]> config = ExportConfig.DELIVERY_EXPORT_CONFIG;
String fileName = "出库数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
}
/**
* 导出发票数据
*/
public org.springframework.http.ResponseEntity<byte[]> exportInvoices(List<?> data) {
Map<String, String[]> config = ExportConfig.INVOICE_EXPORT_CONFIG;
String fileName = "发票数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
}
/**
* 导出返利数据
*/
public org.springframework.http.ResponseEntity<byte[]> exportRebates(List<?> data) {
Map<String, String[]> config = ExportConfig.REBATE_EXPORT_CONFIG;
String fileName = "返利数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
}
/**
* 导出异常工单数据
*/
public org.springframework.http.ResponseEntity<byte[]> exportExceptionWorkorders(List<?> data) {
Map<String, String[]> config = ExportConfig.EXCEPTION_WORKORDER_EXPORT_CONFIG;
String fileName = "异常工单数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER);
return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName);
}
/**
* 通用导出方法
* 支持自定义表头和字段映射
*/
public org.springframework.http.ResponseEntity<byte[]> exportCustom(List<?> data, String[] headers, String[] fieldNames, String fileName) {
return GenericExcelExportUtil.exportToExcel(data, headers, fieldNames, fileName);
}
}
package com.apple.erp.util;
import com.apple.erp.entity.SysDictItem;
import com.apple.erp.entity.SysDictType;
import com.apple.erp.service.SysDictItemService;
import com.apple.erp.service.SysDictTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 字典值转换器 - 基于Redis缓存的动态字典值转换
* 支持实时更新,无需重启应用,使用Redis分布式缓存
*
* @author Apple ERP System
* @since 2025-01-01
*/
@Component
public class DictValueConverter {
@Autowired
private SysDictItemService sysDictItemService;
@Autowired
private SysDictTypeService sysDictTypeService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static SysDictItemService staticDictItemService;
private static SysDictTypeService staticDictTypeService;
private static RedisTemplate<String, Object> staticRedisTemplate;
/**
* Redis缓存键前缀
*/
private static final String DICT_CACHE_PREFIX = "dict:cache:";
/**
* 缓存过期时间(小时)
*/
private static final long CACHE_EXPIRE_HOURS = 24;
@PostConstruct
public void init() {
staticDictItemService = sysDictItemService;
staticDictTypeService = sysDictTypeService;
staticRedisTemplate = redisTemplate;
System.out.println("DictValueConverter初始化开始...");
refreshCache();
System.out.println("DictValueConverter初始化完成");
}
/**
* 转换字典值 - 简化版本,直接使用硬编码映射
*/
public static String convert(Object value, String fieldName) {
if (value == null) {
return "";
}
// 将值转换为字符串进行匹配
String valueStr = value.toString();
// 直接使用硬编码映射进行转换
Map<String, String> mapping = getHardcodedMapping(fieldName);
if (mapping != null && !mapping.isEmpty()) {
String result = mapping.getOrDefault(valueStr, valueStr);
System.out.println("字典转换: " + fieldName + " = " + valueStr + " -> " + result);
return result;
}
return valueStr;
}
/**
* 获取硬编码的字典映射
*/
private static Map<String, String> getHardcodedMapping(String fieldName) {
Map<String, String> mapping = new HashMap<>();
switch (fieldName) {
case "deliveryStatus":
mapping.put("0", "未出库");
mapping.put("1", "已出库");
break;
case "invoiceStatus":
mapping.put("0", "未开票");
mapping.put("1", "已开票");
break;
case "rebateCalcFlag":
mapping.put("0", "未计算");
mapping.put("1", "已计算");
break;
case "verifyStatus":
mapping.put("0", "待验证");
mapping.put("1", "验证通过");
mapping.put("2", "验证失败");
break;
case "workorderStatus":
mapping.put("1", "待处理");
mapping.put("2", "处理中");
mapping.put("3", "已解决");
mapping.put("4", "已关闭");
break;
case "severityLevel":
mapping.put("1", "高");
mapping.put("2", "中");
mapping.put("3", "低");
break;
case "operateType":
mapping.put("1", "新增");
mapping.put("2", "修改");
mapping.put("3", "删除");
break;
case "calcFlag":
mapping.put("0", "未计算");
mapping.put("1", "已计算");
break;
case "auditStatus":
mapping.put("0", "待审核");
mapping.put("1", "审核通过");
mapping.put("2", "审核中");
mapping.put("3", "审核拒绝");
break;
}
return mapping;
}
/**
* 刷新字典缓存 - 清空Redis缓存并重新加载
*/
public static void refreshCache() {
if (staticRedisTemplate == null) {
System.out.println("Redis模板未初始化,跳过缓存刷新");
return;
}
try {
System.out.println("开始刷新字典缓存...");
// 清空所有字典缓存
String pattern = DICT_CACHE_PREFIX + "*";
staticRedisTemplate.delete(staticRedisTemplate.keys(pattern));
System.out.println("已清空现有字典缓存");
// 重新加载所有字典类型
loadAllDictsToRedis();
System.out.println("字典缓存刷新完成");
} catch (Exception e) {
System.err.println("刷新字典缓存失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 加载所有字典到Redis
*/
private static void loadAllDictsToRedis() {
if (staticDictItemService == null) {
return;
}
try {
// 从数据库加载所有字典项
List<SysDictItem> allDictItems = staticDictItemService.list();
// 按字典类型分组
Map<String, Map<String, String>> dictGroups = new HashMap<>();
for (SysDictItem item : allDictItems) {
if (item.getDelFlag() != null && !"0".equals(item.getDelFlag())) {
continue; // 跳过已删除的字典项
}
String dictType = getDictTypeByTypeId(item.getDictTypeId());
if (dictType != null) {
dictGroups.computeIfAbsent(dictType, k -> new HashMap<>())
.put(item.getDictValue(), item.getDictLabel());
}
}
// 将每个字典类型存储到Redis
for (Map.Entry<String, Map<String, String>> entry : dictGroups.entrySet()) {
String cacheKey = DICT_CACHE_PREFIX + entry.getKey();
staticRedisTemplate.opsForValue().set(cacheKey, entry.getValue(), CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
}
} catch (Exception e) {
// 如果数据库加载失败,使用默认配置作为降级方案
loadDefaultDictConfigToRedis();
}
}
/**
* 加载指定字典类型到Redis
*/
private static void loadDictToRedis(String dictType) {
if (staticDictItemService == null) {
return;
}
try {
// 根据字典类型获取字典项
List<SysDictItem> dictItems = staticDictItemService.getDictItemsByType(dictType);
Map<String, String> mapping = new HashMap<>();
for (SysDictItem item : dictItems) {
if (item.getDelFlag() == null || "0".equals(item.getDelFlag())) {
mapping.put(item.getDictValue(), item.getDictLabel());
}
}
// 存储到Redis
String cacheKey = DICT_CACHE_PREFIX + dictType;
staticRedisTemplate.opsForValue().set(cacheKey, mapping, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
} catch (Exception e) {
System.err.println("加载字典到Redis失败: " + e.getMessage());
}
}
/**
* 根据字典类型ID获取字典类型编码
* 从数据库查询字典类型表获取真实的字典类型编码
*/
private static String getDictTypeByTypeId(Long dictTypeId) {
if (staticDictTypeService == null || dictTypeId == null) {
return null;
}
try {
SysDictType dictType = staticDictTypeService.getById(dictTypeId);
if (dictType != null && dictType.getStatus() != null && dictType.getStatus() == 1) {
return dictType.getDictType();
}
} catch (Exception e) {
System.err.println("查询字典类型失败: " + e.getMessage());
}
return null;
}
/**
* 加载默认字典配置到Redis(降级方案)
*/
private static void loadDefaultDictConfigToRedis() {
if (staticRedisTemplate == null) {
return;
}
try {
// 操作类型
Map<String, String> operateType = new HashMap<>();
operateType.put("1", "新增");
operateType.put("2", "修改");
operateType.put("3", "删除");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "operateType", operateType, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 计算状态
Map<String, String> calcFlag = new HashMap<>();
calcFlag.put("0", "未计算");
calcFlag.put("1", "已计算");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "calcFlag", calcFlag, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 审核状态
Map<String, String> auditStatus = new HashMap<>();
auditStatus.put("0", "待审核");
auditStatus.put("1", "审核通过");
auditStatus.put("2", "审核中");
auditStatus.put("3", "审核拒绝");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "auditStatus", auditStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 出库状态
Map<String, String> deliveryStatus = new HashMap<>();
deliveryStatus.put("0", "未出库");
deliveryStatus.put("1", "已出库");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "deliveryStatus", deliveryStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 发票状态
Map<String, String> invoiceStatus = new HashMap<>();
invoiceStatus.put("0", "未开票");
invoiceStatus.put("1", "已开票");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "invoiceStatus", invoiceStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 返利计算状态
Map<String, String> rebateCalcFlag = new HashMap<>();
rebateCalcFlag.put("0", "未计算");
rebateCalcFlag.put("1", "已计算");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "rebateCalcFlag", rebateCalcFlag, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 审核状态
Map<String, String> verifyStatus = new HashMap<>();
verifyStatus.put("0", "待验证");
verifyStatus.put("1", "验证通过");
verifyStatus.put("2", "验证失败");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "verifyStatus", verifyStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 工单状态
Map<String, String> workorderStatus = new HashMap<>();
workorderStatus.put("1", "待处理");
workorderStatus.put("2", "处理中");
workorderStatus.put("3", "已解决");
workorderStatus.put("4", "已关闭");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "workorderStatus", workorderStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
// 严重程度
Map<String, String> severityLevel = new HashMap<>();
severityLevel.put("1", "高");
severityLevel.put("2", "中");
severityLevel.put("3", "低");
staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "severityLevel", severityLevel, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
} catch (Exception e) {
System.err.println("加载默认字典配置到Redis失败: " + e.getMessage());
}
}
}
package com.apple.erp.util;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* Excel导出工具类
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
public class ExcelExportUtil {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 导出数据到Excel
*
* @param data 数据列表
* @param headers 表头数组
* @param fileName 文件名
* @param <T> 数据类型
* @return ResponseEntity<byte[]>
*/
public static <T> ResponseEntity<byte[]> exportToExcel(List<T> data, String[] headers, String fileName) {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("数据导出");
// 创建表头样式
CellStyle headerStyle = createHeaderStyle(workbook);
CellStyle dataStyle = createDataStyle(workbook);
// 创建表头
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 填充数据
if (data != null && !data.isEmpty()) {
for (int i = 0; i < data.size(); i++) {
Row row = sheet.createRow(i + 1);
T item = data.get(i);
fillRowData(row, item, dataStyle);
}
}
// 自动调整列宽
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 转换为字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
byte[] bytes = outputStream.toByteArray();
// 设置响应头
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 对文件名进行URL编码以支持中文
String encodedFileName;
try {
encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8");
} catch (Exception e) {
encodedFileName = fileName + ".xlsx";
}
httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
httpHeaders.setContentLength(bytes.length);
return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
} catch (IOException e) {
throw new RuntimeException("Excel导出失败", e);
}
}
/**
* 创建表头样式
*/
private static CellStyle createHeaderStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 12);
style.setFont(font);
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
/**
* 创建数据样式
*/
private static CellStyle createDataStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setBorderBottom(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setAlignment(HorizontalAlignment.LEFT);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
/**
* 填充行数据
*/
private static <T> void fillRowData(Row row, T item, CellStyle dataStyle) {
if (item == null) return;
Field[] fields = item.getClass().getDeclaredFields();
int cellIndex = 0;
for (Field field : fields) {
if (cellIndex >= row.getLastCellNum()) break;
try {
// 使用getter方法获取值,而不是直接访问字段
String fieldName = field.getName();
String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Object value = null;
try {
java.lang.reflect.Method getter = item.getClass().getMethod(getterName);
value = getter.invoke(item);
} catch (Exception e) {
// 如果getter方法不存在,尝试直接访问字段
field.setAccessible(true);
value = field.get(item);
}
Cell cell = row.createCell(cellIndex);
cell.setCellStyle(dataStyle);
if (value != null) {
if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
} else if (value instanceof LocalDateTime) {
cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER));
} else {
cell.setCellValue(value.toString());
}
} else {
cell.setCellValue("");
}
cellIndex++;
} catch (Exception e) {
// 忽略无法访问的字段
System.out.println("无法访问字段: " + field.getName() + ", 错误: " + e.getMessage());
}
}
}
/**
* 导出订单数据到Excel
*
* @param data 订单数据列表
* @param fileName 文件名
* @return ResponseEntity<byte[]>
*/
public static ResponseEntity<byte[]> exportOrderToExcel(List<?> data, String fileName) {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("订单数据");
// 创建表头样式
CellStyle headerStyle = createHeaderStyle(workbook);
CellStyle dataStyle = createDataStyle(workbook);
// 创建表头
Row headerRow = sheet.createRow(0);
String[] headers = {
"订单ID", "订单编号", "经销商编码", "经销商名称", "订单日期",
"订单金额", "返利金额", "出库状态", "开票状态", "返利计算状态",
"数据来源", "审核状态", "上传时间", "创建时间"
};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 填充数据
if (data != null && !data.isEmpty()) {
for (int i = 0; i < data.size(); i++) {
Row row = sheet.createRow(i + 1);
Object item = data.get(i);
// 使用反射获取字段值
fillOrderRowData(row, item, dataStyle);
}
}
// 自动调整列宽
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 转换为字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
byte[] bytes = outputStream.toByteArray();
// 设置响应头
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 对文件名进行URL编码以支持中文
String encodedFileName;
try {
encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8");
} catch (Exception e) {
encodedFileName = fileName + ".xlsx";
}
httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
httpHeaders.setContentLength(bytes.length);
return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
} catch (IOException e) {
throw new RuntimeException("Excel导出失败", e);
}
}
/**
* 填充订单行数据
*/
private static void fillOrderRowData(Row row, Object item, CellStyle dataStyle) {
try {
// 使用反射获取OrderRes的字段值
Class<?> clazz = item.getClass();
// 订单ID
setCellValue(row, 0, getFieldValue(clazz, item, "orderId"), dataStyle);
// 订单编号
setCellValue(row, 1, getFieldValue(clazz, item, "orderNo"), dataStyle);
// 经销商编码
setCellValue(row, 2, getFieldValue(clazz, item, "dealerCode"), dataStyle);
// 经销商名称
setCellValue(row, 3, getFieldValue(clazz, item, "dealerName"), dataStyle);
// 订单日期
setCellValue(row, 4, getFieldValue(clazz, item, "orderDate"), dataStyle);
// 订单金额
setCellValue(row, 5, getFieldValue(clazz, item, "totalAmount"), dataStyle);
// 返利金额
setCellValue(row, 6, getFieldValue(clazz, item, "rebateAmount"), dataStyle);
// 出库状态
setCellValue(row, 7, getFieldValue(clazz, item, "deliveryStatus"), dataStyle);
// 开票状态
setCellValue(row, 8, getFieldValue(clazz, item, "invoiceStatus"), dataStyle);
// 返利计算状态
setCellValue(row, 9, getFieldValue(clazz, item, "rebateCalcFlag"), dataStyle);
// 数据来源
setCellValue(row, 10, getFieldValue(clazz, item, "dataSource"), dataStyle);
// 审核状态
setCellValue(row, 11, getFieldValue(clazz, item, "verifyStatus"), dataStyle);
// 上传时间
setCellValue(row, 12, getFieldValue(clazz, item, "uploadTime"), dataStyle);
// 创建时间
setCellValue(row, 13, getFieldValue(clazz, item, "createTime"), dataStyle);
} catch (Exception e) {
System.out.println("填充订单数据失败: " + e.getMessage());
}
}
/**
* 获取字段值
*/
private static Object getFieldValue(Class<?> clazz, Object item, String fieldName) {
try {
String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
java.lang.reflect.Method getter = clazz.getMethod(getterName);
return getter.invoke(item);
} catch (Exception e) {
return null;
}
}
/**
* 设置单元格值
*/
private static void setCellValue(Row row, int cellIndex, Object value, CellStyle dataStyle) {
Cell cell = row.createCell(cellIndex);
cell.setCellStyle(dataStyle);
if (value != null) {
if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
} else if (value instanceof LocalDateTime) {
cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER));
} else {
cell.setCellValue(value.toString());
}
} else {
cell.setCellValue("");
}
}
/**
* 导出出库数据到Excel
*
* @param data 出库数据列表
* @param fileName 文件名
* @return ResponseEntity<byte[]>
*/
public static ResponseEntity<byte[]> exportDeliveryToExcel(List<?> data, String fileName) {
String[] headers = {
"出库单ID", "出库单编号", "经销商编码", "经销商名称", "出库日期",
"关联订单编号", "出库状态", "仓库编码", "数据来源", "创建时间"
};
return exportToExcel(data, headers, fileName);
}
/**
* 导出发票数据到Excel
*
* @param data 发票数据列表
* @param fileName 文件名
* @return ResponseEntity<byte[]>
*/
public static ResponseEntity<byte[]> exportInvoiceToExcel(List<?> data, String fileName) {
String[] headers = {
"发票ID", "发票编号", "订单编号", "出库单编号", "经销商编码",
"经销商名称", "发票金额", "发票日期", "开票状态", "税率",
"数据来源", "创建时间"
};
return exportToExcel(data, headers, fileName);
}
}
package com.apple.erp.util;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 通用Excel导出工具类
* 支持任意实体类的Excel导出,通过注解配置字段映射
*
* @author Apple ERP System
* @since 2025-01-01
*/
public class GenericExcelExportUtil {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* 通用Excel导出方法
*
* @param data 数据列表
* @param headers 表头数组
* @param fieldNames 字段名数组(与表头对应)
* @param fileName 文件名(不含扩展名)
* @return ResponseEntity<byte[]>
*/
public static ResponseEntity<byte[]> exportToExcel(List<?> data, String[] headers, String[] fieldNames, String fileName) {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("数据导出");
// 创建样式
CellStyle headerStyle = createHeaderStyle(workbook);
CellStyle dataStyle = createDataStyle(workbook);
// 创建表头
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 填充数据
if (data != null && !data.isEmpty()) {
for (int i = 0; i < data.size(); i++) {
Row row = sheet.createRow(i + 1);
Object item = data.get(i);
fillRowData(row, item, fieldNames, dataStyle);
}
}
// 自动调整列宽
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 转换为字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
byte[] bytes = outputStream.toByteArray();
// 设置响应头
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 对文件名进行URL编码以支持中文
String encodedFileName;
try {
encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8");
} catch (Exception e) {
encodedFileName = fileName + ".xlsx";
}
httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
httpHeaders.setContentLength(bytes.length);
return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
} catch (IOException e) {
throw new RuntimeException("Excel导出失败", e);
}
}
/**
* 创建表头样式
*/
private static CellStyle createHeaderStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 12);
style.setFont(font);
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
/**
* 创建数据样式
*/
private static CellStyle createDataStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setBorderBottom(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
/**
* 填充行数据
*/
private static void fillRowData(Row row, Object item, String[] fieldNames, CellStyle dataStyle) {
try {
Class<?> clazz = item.getClass();
for (int i = 0; i < fieldNames.length; i++) {
Cell cell = row.createCell(i);
cell.setCellStyle(dataStyle);
Object value = getFieldValue(clazz, item, fieldNames[i]);
// 对特定字段进行字典值转换
String convertedValue = convertDictValue(value, fieldNames[i]);
System.out.println("字段转换: " + fieldNames[i] + " = " + value + " -> " + convertedValue);
if (convertedValue != null && !convertedValue.equals(value != null ? value.toString() : "")) {
// 如果字典转换成功,使用转换后的值
cell.setCellValue(convertedValue);
} else {
// 如果字典转换失败或没有转换,使用原始值
setCellValue(cell, value);
}
}
} catch (Exception e) {
System.out.println("填充行数据失败: " + e.getMessage());
}
}
/**
* 获取字段值(支持getter方法和直接字段访问)
*/
private static Object getFieldValue(Class<?> clazz, Object item, String fieldName) {
try {
// 首先尝试getter方法
String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
try {
return clazz.getMethod(getterName).invoke(item);
} catch (NoSuchMethodException e) {
// 如果getter方法不存在,尝试直接访问字段
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(item);
}
} catch (Exception e) {
return null;
}
}
/**
* 设置单元格值
*/
private static void setCellValue(Cell cell, Object value) {
if (value == null) {
cell.setCellValue("");
} else if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
} else if (value instanceof LocalDateTime) {
cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER));
} else if (value instanceof java.time.LocalDate) {
cell.setCellValue(((java.time.LocalDate) value).format(DATE_FORMATTER));
} else if (value instanceof Boolean) {
cell.setCellValue((Boolean) value ? "是" : "否");
} else {
cell.setCellValue(value.toString());
}
}
/**
* 转换字典值 - 使用动态字典转换器
*/
private static String convertDictValue(Object value, String fieldName) {
String result = DictValueConverter.convert(value, fieldName);
// 调试日志:输出字典转换过程
if (value != null && !value.toString().equals(result)) {
System.out.println("字典转换: " + fieldName + " = " + value + " -> " + result);
}
return result;
}
}
......@@ -5,5 +5,5 @@
// Generated by unplugin-auto-import
export {}
declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage']
}
......
......@@ -121,5 +121,10 @@ export const deliveryApi = {
// 修改出库状态
updateDeliveryStatus: (deliveryId: number, deliveryStatus: number) => {
return request.post(`/api/delivery/${deliveryId}/deliveryStatus`, { deliveryStatus })
},
// 导出出库数据
exportDeliveries: (params: DeliveryQueryReq) => {
return request.post('/api/delivery/export', params, { responseType: 'blob' })
}
}
......
import axios from 'axios'
import request from '@/utils/request'
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8083'
/**
* 字典管理API
*/
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
api.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
console.error('API请求错误:', error)
return Promise.reject(error)
}
)
export interface DictType {
dictTypeId: number
dictType: string
dictName: string
status: number
statusText: string
remark?: string
createBy?: string
createTime: string
updateBy?: string
updateTime: string
}
export interface DictItem {
dictItemId: number
dictTypeId: number
dictType: string
dictLabel: string
dictValue: string
sort: number
remark?: string
createBy?: string
createTime: string
updateBy?: string
updateTime: string
}
export interface DictTypeSearchParams {
dictType?: string
dictName?: string
status?: number
pageNum?: number
pageSize?: number
}
export interface DictItemSearchParams {
dictTypeId?: number
dictLabel?: string
dictValue?: string
pageNum?: number
pageSize?: number
}
export interface DictTypeAddReq {
dictType: string
dictName: string
status: number
remark?: string
}
export interface DictTypeUpdateReq extends DictTypeAddReq {
dictTypeId: number
}
export interface DictItemAddReq {
dictTypeId: number
dictLabel: string
dictValue: string
sort?: number
remark?: string
// 获取字典项
export const getDictItems = (dictType: string) => {
return request.get(`/api/dict/${dictType}`)
}
export interface DictItemUpdateReq extends DictItemAddReq {
dictItemId: number
// 获取所有字典项
export const getAllDictItems = () => {
return request.get('/api/dict/all')
}
export interface ApiResponse<T = any> {
code: number
message: string
data: T
}
export interface PageResponse<T> {
records: T[]
total: number
size: number
current: number
orders: any[]
optimizeCountSql: boolean
searchCount: boolean
maxLimit: any
countId: any
pages: number
}
export const dictApi = {
// 字典类型管理
// 获取字典类型列表
getDictTypes: (params: DictTypeSearchParams): Promise<ApiResponse<PageResponse<DictType>>> => {
return api.get('/api/system/dict/type/list', { params })
},
// 获取字典类型详情
getDictTypeById: (dictTypeId: number): Promise<ApiResponse<DictType>> => {
return api.get(`/api/system/dict/type/${dictTypeId}`)
},
// 新增字典类型
createDictType: (dictTypeData: DictTypeAddReq): Promise<ApiResponse<any>> => {
return api.post('/api/system/dict/type', dictTypeData)
},
// 修改字典类型
updateDictType: (dictTypeData: DictTypeUpdateReq): Promise<ApiResponse<any>> => {
return api.put('/api/system/dict/type', dictTypeData)
},
// 删除字典类型
deleteDictTypes: (dictTypeIds: number[]): Promise<ApiResponse<any>> => {
return api.delete(`/api/system/dict/type/${dictTypeIds.join(',')}`)
},
// 获取字典类型选择框列表
getDictTypeOptions: (): Promise<ApiResponse<DictType[]>> => {
return api.get('/api/system/dict/type/optionselect')
},
// 刷新字典缓存
refreshDictCache: (): Promise<ApiResponse<any>> => {
return api.delete('/api/system/dict/type/refreshCache')
},
// 字典项管理
// 获取字典项列表
getDictItems: (params: DictItemSearchParams): Promise<ApiResponse<PageResponse<DictItem>>> => {
return api.get('/api/system/dict/item/list', { params })
},
// 根据字典类型获取字典项列表
getDictItemsByType: (dictType: string): Promise<ApiResponse<DictItem[]>> => {
return api.get(`/api/system/dict/item/type/${dictType}`)
},
// 获取字典项详情
getDictItemById: (dictItemId: number): Promise<ApiResponse<DictItem>> => {
return api.get(`/api/system/dict/item/${dictItemId}`)
},
// 新增字典项
createDictItem: (dictItemData: DictItemAddReq): Promise<ApiResponse<any>> => {
return api.post('/api/system/dict/item', dictItemData)
},
// 修改字典项
updateDictItem: (dictItemData: DictItemUpdateReq): Promise<ApiResponse<any>> => {
return api.put('/api/system/dict/item', dictItemData)
},
// 删除字典项
deleteDictItems: (dictItemIds: number[]): Promise<ApiResponse<any>> => {
return api.delete(`/api/system/dict/item/${dictItemIds.join(',')}`)
},
// 刷新字典缓存
export const refreshDictCache = () => {
return request.post('/api/dict/refresh')
}
\ No newline at end of file
......
......@@ -122,5 +122,10 @@ export const invoiceApi = {
},
updateInvoiceStatus: (invoiceId: number, invoiceStatus: number) => {
return request.post(`/api/invoice/${invoiceId}/invoiceStatus`, { invoiceStatus })
},
// 导出发票数据
exportInvoices: (params: InvoiceQueryReq) => {
return request.post('/api/invoice/export', params, { responseType: 'blob' })
}
}
......
......@@ -150,5 +150,10 @@ export const orderApi = {
return request.post(`/order/${orderId}/rebateCalcFlag`, null, {
params: { rebateCalcFlag }
})
},
// 导出订单数据
exportOrders: (params: OrderQueryReq) => {
return request.post('/order/export', params, { responseType: 'blob' })
}
}
......
......@@ -209,6 +209,11 @@ export const getRebateTrendStats = (startDate?: string, endDate?: string) => {
})
}
// 导出返利数据
export const exportRebates = (params: RebateSearchParams) => {
return request.post('/api/rebate/export', params, { responseType: 'blob' })
}
export default {
getRebatePage,
getRebateById,
......@@ -227,5 +232,6 @@ export default {
exportRebate,
getRebateMonthlyStats,
getRebateStatusStats,
getRebateTrendStats
getRebateTrendStats,
exportRebates
}
......
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from './router'
......@@ -10,5 +12,6 @@ const pinia = createPinia()
app.use(pinia)
app.use(router)
app.use(ElementPlus)
// 挂载应用
app.mount('#app')
......
......@@ -46,6 +46,11 @@ service.interceptors.request.use(
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
// 如果是blob响应(文件下载),直接返回
if (response.config.responseType === 'blob') {
return response.data
}
const { code, message, data } = response.data
// 请求成功
......@@ -121,8 +126,8 @@ export const request = {
return service.get(url, { params })
},
post<T = any>(url: string, data?: any): Promise<T> {
return service.post(url, data)
post<T = any>(url: string, data?: any, config?: any): Promise<T> {
return service.post(url, data, config)
},
put<T = any>(url: string, data?: any): Promise<T> {
......
......@@ -68,10 +68,10 @@
<div class="dashboard-card">
<h3>快速操作</h3>
<div class="quick-actions">
<button class="action-btn">👤 用户管理</button>
<button class="action-btn">🛡️ 角色管理</button>
<button class="action-btn">⚙️ 系统设置</button>
<button class="action-btn">📊 查看日志</button>
<button class="action-btn" @click="navigateTo('/main/users')">👤 用户管理</button>
<button class="action-btn" @click="navigateTo('/main/sys/role')">🛡️ 角色管理</button>
<button class="action-btn" @click="navigateTo('/main/settings')">⚙️ 系统设置</button>
<button class="action-btn" @click="navigateTo('/main/sys/log')">📊 查看日志</button>
</div>
</div>
</div>
......@@ -153,6 +153,13 @@ onMounted(() => {
fetchUserInfo()
})
// 页面跳转函数
const navigateTo = (path: string) => {
router.push(path).catch(err => {
console.error('页面跳转失败:', err)
})
}
const logout = () => {
// 清除本地存储
localStorage.removeItem('token')
......
......@@ -429,6 +429,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery'
// 响应式数据
......@@ -514,7 +515,12 @@ const formatCurrency = (amount: number) => {
// 消息提示
const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
console.log(`${type}: ${message}`)
alert(message)
ElMessage({
message,
type,
duration: 3000,
showClose: true
})
}
// 获取页码数组
......@@ -608,8 +614,78 @@ const handleDelete = async (delivery: DeliveryInfo) => {
}
const handleExport = () => {
showMessage('导出功能开发中...', 'warning')
const handleExport = async () => {
try {
// 显示确认弹窗
await ElMessageBox.confirm(
'确定要导出出库数据吗?导出将包含当前筛选条件下的所有数据。',
'确认导出',
{
confirmButtonText: '确定导出',
cancelButtonText: '取消',
type: 'warning',
center: true
}
)
// 用户确认后显示加载提示
const loadingMessage = ElMessage({
message: '正在导出数据,请稍候...',
type: 'warning',
duration: 0, // 不自动关闭
showClose: false
})
try {
// 准备导出参数
const exportParams = {
deliveryNo: searchParams.deliveryNo,
dealerCode: searchParams.dealerCode,
dealerName: searchParams.dealerName,
deliveryStatus: searchParams.deliveryStatus,
warehouseCode: searchParams.warehouseCode,
dataSource: searchParams.dataSource,
deliveryStartDate: searchParams.deliveryStartDate,
deliveryEndDate: searchParams.deliveryEndDate
}
// 调用导出接口
const response = await deliveryApi.exportDeliveries(exportParams)
// 创建下载链接
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 生成文件名
const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
link.download = `出库数据_${timestamp}.xlsx`
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 关闭加载提示,显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!文件已开始下载')
} catch (exportError) {
// 关闭加载提示
loadingMessage.close()
throw exportError
}
} catch (error) {
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败,请重试')
}
}
}
// 新增出库相关函数
......
......@@ -507,6 +507,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice'
// 响应式数据
......@@ -594,7 +595,12 @@ const formatCurrency = (amount: number) => {
// 消息提示
const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
console.log(`${type}: ${message}`)
alert(message)
ElMessage({
message,
type,
duration: 3000,
showClose: true
})
}
// 获取页码数组
......@@ -825,8 +831,81 @@ const handleView = async (order: InvoiceInfo) => {
}
const handleExport = () => {
showMessage('导出功能开发中...', 'warning')
const handleExport = async () => {
try {
// 显示确认弹窗
await ElMessageBox.confirm(
'确定要导出发票数据吗?导出将包含当前筛选条件下的所有数据。',
'确认导出',
{
confirmButtonText: '确定导出',
cancelButtonText: '取消',
type: 'warning',
center: true
}
)
// 用户确认后显示加载提示
const loadingMessage = ElMessage({
message: '正在导出数据,请稍候...',
type: 'warning',
duration: 0, // 不自动关闭
showClose: false
})
try {
// 准备导出参数
const exportParams = {
invoiceNo: searchParams.invoiceNo,
orderNo: searchParams.orderNo,
deliveryNo: searchParams.deliveryNo,
dealerCode: searchParams.dealerCode,
dealerName: searchParams.dealerName,
invoiceStatus: searchParams.invoiceStatus,
dataSource: searchParams.dataSource,
invoiceStartDate: searchParams.invoiceStartDate,
invoiceEndDate: searchParams.invoiceEndDate,
minAmount: searchParams.minAmount,
maxAmount: searchParams.maxAmount
}
// 调用导出接口
const response = await invoiceApi.exportInvoices(exportParams)
// 创建下载链接
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 生成文件名
const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
link.download = `发票数据_${timestamp}.xlsx`
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 关闭加载提示,显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!文件已开始下载')
} catch (exportError) {
// 关闭加载提示
loadingMessage.close()
throw exportError
}
} catch (error) {
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败,请重试')
}
}
}
const handlePrintInvoice = (invoice: InvoiceInfo) => {
......
......@@ -447,6 +447,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order'
// 响应式数据
......@@ -586,9 +587,12 @@ const formatCurrency = (amount: number) => {
// 消息提示
const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
// 这里可以集成消息提示组件
console.log(`${type}: ${message}`)
alert(message)
ElMessage({
message,
type,
duration: 3000,
showClose: true
})
}
// 方法
......@@ -757,8 +761,82 @@ const handleBatchDelete = async () => {
}
const handleExport = () => {
showMessage('导出功能开发中...', 'warning')
const handleExport = async () => {
try {
// 显示确认弹窗
await ElMessageBox.confirm(
'确定要导出订单数据吗?导出将包含当前筛选条件下的所有数据。',
'确认导出',
{
confirmButtonText: '确定导出',
cancelButtonText: '取消',
type: 'warning',
center: true
}
)
// 用户确认后显示加载提示
const loadingMessage = ElMessage({
message: '正在导出数据,请稍候...',
type: 'warning',
duration: 0, // 不自动关闭
showClose: false
})
try {
// 准备导出参数
const exportParams = {
orderNo: searchParams.orderNo,
dealerCode: searchParams.dealerCode,
dealerName: searchParams.dealerName,
deliveryStatus: searchParams.deliveryStatus,
invoiceStatus: searchParams.invoiceStatus,
rebateCalcFlag: searchParams.rebateCalcFlag,
dataSource: searchParams.dataSource,
verifyStatus: searchParams.verifyStatus,
orderStartDate: searchParams.orderStartDate,
orderEndDate: searchParams.orderEndDate,
minAmount: searchParams.minAmount,
maxAmount: searchParams.maxAmount
}
// 调用导出接口
const response = await orderApi.exportOrders(exportParams)
// 创建下载链接
const blob = new Blob([response], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 生成文件名
const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14)
link.download = `订单数据_${timestamp}.xlsx`
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 关闭加载提示,显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!文件已开始下载')
} catch (exportError) {
// 关闭加载提示
loadingMessage.close()
throw exportError
}
} catch (error) {
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败,请重试')
}
}
}
const handleSelectAll = () => {
......
......@@ -102,8 +102,14 @@
<div class="table-title">
返利记录
</div>
<div class="table-info">
数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }}
<div class="table-actions">
<el-button type="primary" size="small" @click="handleExport" :loading="exportLoading">
<el-icon><Download /></el-icon>
导出
</el-button>
<div class="table-info">
数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }}
</div>
</div>
</div>
......@@ -384,12 +390,13 @@ import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import rebateApi, { type Rebate, type RebateSearchParams } from '@/api/rebate'
import rebateApi, { type Rebate, type RebateSearchParams, exportRebates } from '@/api/rebate'
import { formatDate } from '@/utils/index'
// 响应式数据
const loading = ref(false)
const submitLoading = ref(false)
const exportLoading = ref(false)
const auditLoading = ref(false)
const rebateList = ref<Rebate[]>([])
const selectedRebates = ref<Rebate[]>([])
......@@ -1039,30 +1046,62 @@ const handleBatchRelease = async () => {
// 导出
const handleExport = async () => {
try {
loading.value = true
const params = { ...searchForm }
const response = await rebateApi.exportRebate(params)
// 显示确认弹窗
await ElMessageBox.confirm(
'确定要导出返利数据吗?导出将包含当前筛选条件下的所有数据。',
'确认导出',
{
confirmButtonText: '确定导出',
cancelButtonText: '取消',
type: 'warning',
center: true
}
)
// 创建下载链接
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
// 用户确认后显示加载提示
const loadingMessage = ElMessage({
message: '正在导出数据,请稍候...',
type: 'warning',
duration: 0, // 不自动关闭
showClose: false
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `返利数据_${new Date().toISOString().split('T')[0]}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
ElMessage.success('导出成功')
try {
exportLoading.value = true
const params = { ...searchForm }
const response = await exportRebates(params)
// 创建下载链接
const blob = new Blob([response as unknown as BlobPart], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `返利数据_${new Date().toISOString().split('T')[0]}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 关闭加载提示,显示成功消息
loadingMessage.close()
ElMessage.success('导出成功!文件已开始下载')
} catch (exportError) {
// 关闭加载提示
loadingMessage.close()
throw exportError
}
} catch (error) {
console.error('导出失败:', error)
ElMessage.error('导出失败')
if (error !== 'cancel') {
console.error('导出失败:', error)
ElMessage.error('导出失败,请重试')
}
} finally {
loading.value = false
exportLoading.value = false
}
}
......@@ -1546,9 +1585,9 @@ onMounted(async () => {
overflow: hidden;
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 0 20px;
margin-bottom: 16px;
......@@ -1558,9 +1597,15 @@ onMounted(async () => {
color: #333;
}
.table-info {
font-size: 12px;
color: #999;
.table-actions {
display: flex;
align-items: center;
gap: 12px;
.table-info {
font-size: 12px;
color: #999;
}
}
}
......
<template>
<div class="settings-container">
<!-- 页面标题 -->
<div class="page-header">
<h2>系统设置</h2>
<p>管理系统配置和参数</p>
</div>
<!-- 设置内容 -->
<div class="settings-content">
<div class="settings-card">
<h3>基本设置</h3>
<div class="setting-item">
<label>系统名称:</label>
<input v-model="settings.systemName" class="setting-input" placeholder="请输入系统名称" />
</div>
<div class="setting-item">
<label>系统版本:</label>
<input v-model="settings.systemVersion" class="setting-input" placeholder="请输入系统版本" />
</div>
<div class="setting-item">
<label>系统描述:</label>
<textarea v-model="settings.systemDescription" class="setting-textarea" placeholder="请输入系统描述"></textarea>
</div>
</div>
<div class="settings-card">
<h3>业务设置</h3>
<div class="setting-item">
<label>默认分页大小:</label>
<select v-model="settings.defaultPageSize" class="setting-select">
<option value="10">10条/页</option>
<option value="20">20条/页</option>
<option value="50">50条/页</option>
<option value="100">100条/页</option>
</select>
</div>
<div class="setting-item">
<label>数据保留天数:</label>
<input v-model="settings.dataRetentionDays" type="number" class="setting-input" placeholder="请输入数据保留天数" />
</div>
<div class="setting-item">
<label>自动备份:</label>
<label class="checkbox-label">
<input v-model="settings.autoBackup" type="checkbox" />
<span>启用自动备份</span>
</label>
</div>
</div>
<div class="settings-card">
<h3>安全设置</h3>
<div class="setting-item">
<label>会话超时时间(分钟):</label>
<input v-model="settings.sessionTimeout" type="number" class="setting-input" placeholder="请输入会话超时时间" />
</div>
<div class="setting-item">
<label>密码复杂度:</label>
<select v-model="settings.passwordComplexity" class="setting-select">
<option value="low">低</option>
<option value="medium">中</option>
<option value="high">高</option>
</select>
</div>
<div class="setting-item">
<label>登录失败锁定:</label>
<label class="checkbox-label">
<input v-model="settings.loginLock" type="checkbox" />
<span>启用登录失败锁定</span>
</label>
</div>
</div>
<div class="settings-card">
<h3>通知设置</h3>
<div class="setting-item">
<label>邮件通知:</label>
<label class="checkbox-label">
<input v-model="settings.emailNotification" type="checkbox" />
<span>启用邮件通知</span>
</label>
</div>
<div class="setting-item">
<label>短信通知:</label>
<label class="checkbox-label">
<input v-model="settings.smsNotification" type="checkbox" />
<span>启用短信通知</span>
</label>
</div>
<div class="setting-item">
<label>系统消息:</label>
<label class="checkbox-label">
<input v-model="settings.systemMessage" type="checkbox" />
<span>启用系统消息</span>
</label>
</div>
</div>
<!-- 操作按钮 -->
<div class="settings-actions">
<button @click="handleSave" class="action-btn primary">💾 保存设置</button>
<button @click="handleReset" class="action-btn secondary">🔄 重置</button>
<button @click="handleTest" class="action-btn info">🧪 测试连接</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
// 设置数据
const settings = reactive({
systemName: 'Apple ERP系统',
systemVersion: '1.0.0',
systemDescription: '企业资源规划管理系统',
defaultPageSize: 20,
dataRetentionDays: 365,
autoBackup: true,
sessionTimeout: 30,
passwordComplexity: 'medium',
loginLock: true,
emailNotification: true,
smsNotification: false,
systemMessage: true
})
// 原始设置(用于重置)
const originalSettings = ref({})
// 保存设置
const handleSave = () => {
// 这里可以调用后端API保存设置
console.log('保存设置:', settings)
showMessage('设置保存成功', 'success')
}
// 重置设置
const handleReset = () => {
Object.assign(settings, originalSettings.value)
showMessage('设置已重置', 'warning')
}
// 测试连接
const handleTest = () => {
showMessage('连接测试成功', 'success')
}
// 显示消息
const showMessage = (message: string, type: 'success' | 'warning' | 'error') => {
// 这里可以集成Element Plus的消息组件
console.log(`${type}: ${message}`)
}
// 组件挂载时加载设置
onMounted(() => {
// 这里可以调用后端API加载设置
originalSettings.value = { ...settings }
})
</script>
<style scoped>
.settings-container {
padding: 20px;
background-color: #f5f5f5;
min-height: 100vh;
}
.page-header {
margin-bottom: 30px;
text-align: center;
}
.page-header h2 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.page-header p {
color: #666;
font-size: 16px;
}
.settings-content {
max-width: 1200px;
margin: 0 auto;
}
.settings-card {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.settings-card h3 {
color: #333;
margin-bottom: 20px;
font-size: 18px;
border-bottom: 2px solid #e9ecef;
padding-bottom: 10px;
}
.setting-item {
display: flex;
align-items: center;
margin-bottom: 20px;
gap: 16px;
}
.setting-item label {
min-width: 150px;
color: #333;
font-weight: 500;
}
.setting-input,
.setting-select,
.setting-textarea {
flex: 1;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s;
}
.setting-input:focus,
.setting-select:focus,
.setting-textarea:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.setting-textarea {
min-height: 80px;
resize: vertical;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.checkbox-label input[type="checkbox"] {
width: 16px;
height: 16px;
}
.settings-actions {
display: flex;
gap: 16px;
justify-content: center;
margin-top: 30px;
}
.action-btn {
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
min-width: 120px;
}
.action-btn.primary {
background-color: #007bff;
color: white;
}
.action-btn.primary:hover {
background-color: #0056b3;
}
.action-btn.secondary {
background-color: #6c757d;
color: white;
}
.action-btn.secondary:hover {
background-color: #545b62;
}
.action-btn.info {
background-color: #17a2b8;
color: white;
}
.action-btn.info:hover {
background-color: #138496;
}
@media (max-width: 768px) {
.setting-item {
flex-direction: column;
align-items: flex-start;
}
.setting-item label {
min-width: auto;
margin-bottom: 8px;
}
.settings-actions {
flex-direction: column;
align-items: center;
}
}
</style>