jiaxing.zhou

Merge remote-tracking branch 'origin/dev'

Showing 32 changed files with 1541 additions and 234 deletions
......@@ -103,7 +103,7 @@
**接口路径:** `GET /api/auth/userinfo`
**功能描述:** 获取当前登录用户的详细信息
**功能描述:** 获取当前登录用户的详细信息,包括用户名、真实姓名、角色列表和权限列表
**请求头:** `Authorization: Bearer {token}`
......@@ -114,11 +114,46 @@
"message": "获取用户信息成功",
"data": {
"username": "admin",
"authorities": ["ROLE_ADMIN"]
"realName": "系统管理员",
"roles": [
{
"roleId": 1,
"roleCode": "ADMIN",
"roleName": "系统管理员",
"status": 1,
"statusText": "正常",
"remark": "系统管理员角色",
"createBy": "admin",
"createTime": "2024-01-01T00:00:00",
"updateBy": "admin",
"updateTime": "2024-01-01T00:00:00"
}
],
"authorities": [
{
"authority": "ROLE_ADMIN"
}
]
}
}
```
**响应字段说明:**
- `username`: 用户名
- `realName`: 真实姓名
- `roles`: 用户角色列表
- `roleId`: 角色ID
- `roleCode`: 角色编码
- `roleName`: 角色名称
- `status`: 角色状态(0-停用/1-启用)
- `statusText`: 角色状态文本描述
- `remark`: 备注
- `createBy`: 创建者
- `createTime`: 创建时间
- `updateBy`: 更新者
- `updateTime`: 更新时间
- `authorities`: 用户权限列表(Spring Security权限对象)
## 2. 用户管理 (SysUserController)
### 2.1 获取用户列表
......@@ -2296,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"
});
}};
}
......@@ -4,6 +4,7 @@ import com.apple.erp.dto.request.LoginReq;
import com.apple.erp.dto.request.RefreshTokenReq;
import com.apple.erp.dto.response.ApiRes;
import com.apple.erp.dto.response.LoginRes;
import com.apple.erp.dto.response.RoleRes;
import com.apple.erp.dto.response.UserInfoRes;
import com.apple.erp.entity.SysUser;
import com.apple.erp.service.SysUserService;
......@@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
/**
* 认证控制器
......@@ -214,9 +216,34 @@ public class AuthController {
if (authentication != null && authentication.isAuthenticated()) {
String username = authentication.getName();
try {
// 获取用户详细信息
SysUser user = sysUserService.findByUsername(username);
if (user != null) {
// 获取用户角色信息
List<RoleRes> roles = sysUserService.getUserRoles(user.getUserId());
UserInfoRes userInfo = new UserInfoRes(
user.getUsername(),
user.getRealName(),
roles,
authentication.getAuthorities()
);
ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo);
return ResponseEntity.ok(response);
} else {
// 如果找不到用户信息,返回基本信息
UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities());
ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo);
return ResponseEntity.ok(response);
}
} catch (Exception e) {
log.error("获取用户详细信息失败: " + e.getMessage(), e);
// 如果获取详细信息失败,返回基本信息
UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities());
ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo);
return ResponseEntity.ok(response);
}
} else {
ApiRes<UserInfoRes> response = ApiRes.error("未认证");
return ResponseEntity.status(401).body(response);
......
......@@ -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;
/**
......@@ -33,6 +37,9 @@ public class DeliveryMainController {
@Autowired
private DeliveryMainService deliveryMainService;
@Autowired
private ExcelExportService excelExportService;
@Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表")
@GetMapping("/list")
@PreAuthorize("hasAuthority('delivery: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;
/**
......@@ -33,6 +37,9 @@ public class InvoiceMainController {
@Autowired
private InvoiceMainService invoiceMainService;
@Autowired
private ExcelExportService excelExportService;
@Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表")
@GetMapping("/list")
@PreAuthorize("hasAuthority('invoice: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;
/**
......@@ -33,6 +37,9 @@ public class OrderMainController {
@Autowired
private OrderMainService orderMainService;
@Autowired
private ExcelExportService excelExportService;
@Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表")
@GetMapping("/list")
@PreAuthorize("hasAuthority('order: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;
/**
* 返利台账明细管理控制器
......@@ -37,6 +42,9 @@ public class RebateController {
@Autowired
private RebateService rebateService;
@Autowired
private ExcelExportService excelExportService;
@Operation(summary = "分页查询返利明细列表")
@PostMapping("/page")
public ApiRes<IPage<RebateRes>> getRebatePage(@Valid @RequestBody RebateQueryReq queryReq) {
......@@ -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;
......
......@@ -5,6 +5,7 @@ import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
import java.util.List;
/**
* 用户信息响应对象
......@@ -20,6 +21,12 @@ public class UserInfoRes {
@Schema(description = "用户名", example = "admin")
private String username;
@Schema(description = "真实姓名", example = "管理员")
private String realName;
@Schema(description = "用户角色列表")
private List<RoleRes> roles;
@Schema(description = "用户权限列表")
private Collection<? extends GrantedAuthority> authorities;
......@@ -29,4 +36,11 @@ public class UserInfoRes {
this.username = username;
this.authorities = authorities;
}
public UserInfoRes(String username, String realName, List<RoleRes> roles, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.realName = realName;
this.roles = roles;
this.authorities = authorities;
}
}
......
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);
}
}
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
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']
}
......
......@@ -26,6 +26,7 @@ declare module 'vue' {
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
Header: typeof import('./src/components/layout/Header.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
......
......@@ -120,8 +120,11 @@ export const deliveryApi = {
// 修改出库状态
updateDeliveryStatus: (deliveryId: number, deliveryStatus: number) => {
return request.post(`/api/delivery/${deliveryId}/deliveryStatus`, null, {
params: { deliveryStatus }
})
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
......
......@@ -121,8 +121,11 @@ export const invoiceApi = {
return request.post('/api/invoice/batchDelete', invoiceIds)
},
updateInvoiceStatus: (invoiceId: number, invoiceStatus: number) => {
return request.post(`/api/invoice/${invoiceId}/invoiceStatus`, null, {
params: { invoiceStatus }
})
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
}
......
......@@ -72,7 +72,8 @@
<div class="header-right">
<div class="user-info">
<span class="welcome-text">欢迎,{{ userInfo?.username || '用户' }}</span>
<span class="welcome-text" v-if="userInfoLoading">加载中...</span>
<span class="welcome-text" v-else>欢迎,{{ userInfo?.username || '未知' }}</span>
<div class="user-actions">
<button class="logout-btn" @click="handleLogout">退出登录</button>
</div>
......@@ -125,6 +126,7 @@
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { logoutApi } from '../api/auth'
import { request } from '../utils/request'
const router = useRouter()
const route = useRoute()
......@@ -134,6 +136,26 @@ const sidebarCollapsed = ref(false)
// 用户信息
const userInfo = ref<any>(null)
const userInfoLoading = ref(false)
// 获取用户信息
const fetchUserInfo = async () => {
try {
userInfoLoading.value = true
const response = await request.get('/api/auth/userinfo')
userInfo.value = response
console.log('顶部栏用户信息:', response)
} catch (error) {
console.error('获取用户信息失败:', error)
// 如果获取失败,尝试从本地存储获取
const storedUserInfo = localStorage.getItem('userInfo')
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo)
}
} finally {
userInfoLoading.value = false
}
}
// 标签页容器引用
const tabContainer = ref<HTMLElement>()
......@@ -371,10 +393,18 @@ watch(() => route.path, (newPath) => {
// 组件挂载时获取用户信息
onMounted(() => {
// 检查是否已登录
const token = localStorage.getItem('token')
if (token) {
// 调用API获取用户信息
fetchUserInfo()
} else {
// 如果没有token,尝试从本地存储获取
const storedUserInfo = localStorage.getItem('userInfo')
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo)
}
}
})
</script>
......
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> {
......
......@@ -2,7 +2,8 @@
<div class="dashboard-container">
<div class="dashboard-header">
<h2>系统概览</h2>
<p>欢迎回来,{{ userInfo?.username || '用户' }}!</p>
<p v-if="loading">正在加载用户信息...</p>
<p v-else>欢迎回来,{{ userInfo?.username || '未知' }}!</p>
</div>
<div class="dashboard-stats">
......@@ -45,11 +46,13 @@
<div class="info-grid">
<div class="info-item">
<label>用户名:</label>
<span>{{ userInfo?.username || '未知' }}</span>
<span v-if="loading">加载中...</span>
<span v-else>{{ userInfo?.username || '未知' }}</span>
</div>
<div class="info-item">
<label>角色:</label>
<span>{{ userInfo?.roles?.[0]?.roleName || '普通用户' }}</span>
<span v-if="loading">加载中...</span>
<span v-else>{{ getRoleName(userInfo) || '普通用户' }}</span>
</div>
<div class="info-item">
<label>登录时间:</label>
......@@ -65,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>
......@@ -78,28 +81,85 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { request } from '@/utils/request'
const router = useRouter()
const currentTime = ref('')
const userInfo = ref<any>(null)
const loading = ref(false)
onMounted(() => {
// 获取当前时间
currentTime.value = new Date().toLocaleString()
// 获取角色名称
const getRoleName = (userInfo: any) => {
// 优先使用roles字段中的角色信息
if (userInfo?.roles && Array.isArray(userInfo.roles) && userInfo.roles.length > 0) {
return userInfo.roles[0].roleName || '普通用户'
}
// 获取用户信息
// 如果没有roles字段,回退到authorities
const authorities = userInfo?.authorities
if (authorities && Array.isArray(authorities)) {
// 查找ROLE_开头的权限
const roleAuthority = authorities.find(auth =>
auth.authority && auth.authority.startsWith('ROLE_')
)
if (roleAuthority) {
// 移除ROLE_前缀并转换为中文
const roleName = roleAuthority.authority.replace('ROLE_', '')
const roleMap: { [key: string]: string } = {
'ADMIN': '管理员',
'USER': '普通用户',
'MANAGER': '经理',
'OPERATOR': '操作员'
}
return roleMap[roleName] || roleName
}
}
return '普通用户'
}
// 获取用户信息
const fetchUserInfo = async () => {
try {
loading.value = true
const response = await request.get('/api/auth/userinfo')
userInfo.value = response
console.log('用户信息:', response)
} catch (error) {
console.error('获取用户信息失败:', error)
// 如果获取失败,尝试从本地存储获取
const storedUserInfo = localStorage.getItem('userInfo')
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo)
}
} finally {
loading.value = false
}
}
onMounted(() => {
// 获取当前时间
currentTime.value = new Date().toLocaleString()
// 检查是否已登录
const token = localStorage.getItem('token')
if (!token) {
router.push('/')
return
}
// 获取用户信息
fetchUserInfo()
})
// 页面跳转函数
const navigateTo = (path: string) => {
router.push(path).catch(err => {
console.error('页面跳转失败:', err)
})
}
const logout = () => {
// 清除本地存储
localStorage.removeItem('token')
......
......@@ -179,15 +179,11 @@
<div class="detail-grid">
<div class="detail-item">
<label>出库单ID:</label>
<span>{{ orderDetail.deliveryId }}</span>
<span>{{ deliveryDetail.deliveryId }}</span>
</div>
<div class="detail-item">
<label>出库单编号:</label>
<span>{{ orderDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
<span>{{ orderDetail.orderNo }}</span>
<span>{{ deliveryDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
......@@ -203,7 +199,7 @@
</div>
<div class="detail-item">
<label>出库日期:</label>
<span>{{ formatDate(orderDetail.deliveryDate) }}</span>
<span>{{ formatDate(deliveryDetail.deliveryDate) }}</span>
</div>
<div class="detail-item">
<label>出库状态:</label>
......@@ -213,28 +209,28 @@
</div>
<div class="detail-item">
<label>仓库编码:</label>
<span>{{ orderDetail.warehouseCode }}</span>
<span>{{ deliveryDetail.warehouseCode }}</span>
</div>
<div class="detail-item">
<label>数据来源:</label>
<span>{{ orderDetail.dataSource || '-' }}</span>
<span>{{ deliveryDetail.dataSource || '-' }}</span>
</div>
<div class="detail-item">
<label>上传时间:</label>
<span>{{ formatTime(orderDetail.uploadTime || '') }}</span>
<span>{{ formatTime(deliveryDetail.uploadTime || '') }}</span>
</div>
<div class="detail-item">
<label>创建时间:</label>
<span>{{ formatTime(orderDetail.createTime || '') }}</span>
<span>{{ formatTime(deliveryDetail.createTime || '') }}</span>
</div>
<div class="detail-item">
<label>更新时间:</label>
<span>{{ formatTime(orderDetail.updateTime || '') }}</span>
<span>{{ formatTime(deliveryDetail.updateTime || '') }}</span>
</div>
</div>
</div>
<div class="detail-section" v-if="orderDetail.deliveryItems && orderDetail.deliveryItems.length > 0">
<div class="detail-section" v-if="deliveryDetail?.deliveryItems && deliveryDetail.deliveryItems.length > 0">
<h4>出库明细</h4>
<table class="detail-table">
<thead>
......@@ -247,7 +243,7 @@
</tr>
</thead>
<tbody>
<tr v-for="item in orderDetail.deliveryItems" :key="item.deliveryItemId">
<tr v-for="item in deliveryDetail.deliveryItems" :key="item.itemId || item.deliveryId">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.deliveryQty }}</td>
......@@ -433,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'
// 响应式数据
......@@ -518,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
})
}
// 获取页码数组
......@@ -612,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('导出失败,请重试')
}
}
}
// 新增出库相关函数
......@@ -680,9 +752,9 @@ const handleSubmitAdd = async () => {
deliveryItems: addFormData.deliveryItems.map(item => ({
productCode: item.productCode,
productName: item.productName,
productQty: item.deliveryQty,
unitPrice: item.deliveryPrice,
totalPrice: item.deliveryAmount
deliveryQty: item.deliveryQty,
deliveryPrice: item.deliveryPrice,
deliveryAmount: item.deliveryAmount
}))
}
......
......@@ -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 = () => {
......
This diff is collapsed. Click to expand it.
<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>
......@@ -7,7 +7,7 @@
"auto-imports.d.ts",
"components.d.ts"
],
"exclude": ["src/**/__tests__/*"],
"exclude": ["src/**/__tests__/*", "src/views/test-menu.vue"],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
......