zhouhui.jiang

Merge branch 'master' of http://gitlab.erry.com/zhouhui.jiang/test_cursor

# Conflicts:
#	frontend/components.d.ts
#	frontend/src/views/delivery/index.vue
Showing 51 changed files with 3159 additions and 233 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 获取用户列表
......@@ -2412,6 +2447,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();
UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities());
ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo);
return ResponseEntity.ok(response);
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;
/**
......@@ -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);
}
}
}
......
package com.apple.erp.controller;
import com.apple.erp.dto.ExceptionWorkorderAddReq;
import com.apple.erp.dto.ExceptionWorkorderQueryReq;
import com.apple.erp.dto.ExceptionWorkorderRes;
import com.apple.erp.dto.ExceptionWorkorderStatusUpdateReq;
import com.apple.erp.dto.ExceptionWorkorderUpdateReq;
import com.apple.erp.service.ExceptionWorkorderService;
import com.apple.erp.dto.response.ApiRes;
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.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 异常工单Controller
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Tag(name = "异常工单管理", description = "异常工单相关接口")
@RestController
@RequestMapping("/api/exception-workorder")
@Validated
public class ExceptionWorkorderController {
@Autowired
private ExceptionWorkorderService exceptionWorkorderService;
@Operation(summary = "分页查询异常工单列表", description = "根据条件分页查询异常工单列表")
@GetMapping("/list")
@PreAuthorize("hasAuthority('exception:workorder:list')")
public ApiRes<Page<ExceptionWorkorderRes>> getExceptionWorkorderList(@Valid ExceptionWorkorderQueryReq queryReq) {
Page<ExceptionWorkorderRes> page = exceptionWorkorderService.getExceptionWorkorderList(queryReq);
return ApiRes.success(page);
}
@Operation(summary = "获取异常工单详情", description = "根据工单ID获取异常工单详细信息,包含处理日志")
@GetMapping("/{workorderId}")
@PreAuthorize("hasAuthority('exception:workorder:detail')")
public ApiRes<ExceptionWorkorderRes> getExceptionWorkorderDetail(
@Parameter(description = "工单ID", required = true) @PathVariable Long workorderId) {
ExceptionWorkorderRes workorderRes = exceptionWorkorderService.getExceptionWorkorderDetail(workorderId);
if (workorderRes != null) {
return ApiRes.success(workorderRes);
}
return ApiRes.error("异常工单不存在或已删除");
}
@Operation(summary = "新增异常工单", description = "创建新的异常工单")
@PostMapping
@PreAuthorize("hasAuthority('exception:workorder:add')")
public ApiRes<Void> addExceptionWorkorder(@Valid @RequestBody ExceptionWorkorderAddReq addReq) {
try {
boolean result = exceptionWorkorderService.addExceptionWorkorder(addReq);
if (result) {
return ApiRes.success("新增异常工单成功", null);
} else {
return ApiRes.error("新增异常工单失败");
}
} catch (Exception e) {
return ApiRes.error("新增异常工单失败: " + e.getMessage());
}
}
@Operation(summary = "更新异常工单", description = "更新异常工单信息")
@PutMapping
@PreAuthorize("hasAuthority('exception:workorder:edit')")
public ApiRes<Void> updateExceptionWorkorder(@Valid @RequestBody ExceptionWorkorderUpdateReq updateReq) {
try {
boolean result = exceptionWorkorderService.updateExceptionWorkorder(updateReq);
if (result) {
return ApiRes.success("更新异常工单成功", null);
} else {
return ApiRes.error("更新异常工单失败");
}
} catch (Exception e) {
return ApiRes.error("更新异常工单失败: " + e.getMessage());
}
}
@Operation(summary = "更新工单状态", description = "更新异常工单状态并记录处理日志")
@PostMapping("/status")
@PreAuthorize("hasAuthority('exception:workorder:edit')")
public ApiRes<Void> updateWorkorderStatus(@Valid @RequestBody ExceptionWorkorderStatusUpdateReq statusUpdateReq) {
try {
boolean result = exceptionWorkorderService.updateWorkorderStatus(statusUpdateReq);
if (result) {
return ApiRes.success("更新工单状态成功", null);
} else {
return ApiRes.error("更新工单状态失败");
}
} catch (Exception e) {
return ApiRes.error("更新工单状态失败: " + e.getMessage());
}
}
@Operation(summary = "删除异常工单", description = "根据工单ID逻辑删除异常工单")
@DeleteMapping("/{workorderId}")
@PreAuthorize("hasAuthority('exception:workorder:delete')")
public ApiRes<Void> deleteExceptionWorkorder(
@Parameter(description = "工单ID", required = true) @PathVariable Long workorderId) {
try {
boolean result = exceptionWorkorderService.deleteExceptionWorkorder(workorderId);
if (result) {
return ApiRes.success("删除异常工单成功", null);
} else {
return ApiRes.error("删除异常工单失败");
}
} catch (Exception e) {
return ApiRes.error("删除异常工单失败: " + e.getMessage());
}
}
@Operation(summary = "批量删除异常工单", description = "根据工单ID列表批量逻辑删除异常工单")
@DeleteMapping("/batch")
@PreAuthorize("hasAuthority('exception:workorder:delete')")
public ApiRes<Void> batchDeleteExceptionWorkorders(
@Parameter(description = "工单ID列表", required = true) @RequestBody List<Long> workorderIds) {
try {
if (workorderIds == null || workorderIds.isEmpty()) {
return ApiRes.error("工单ID列表不能为空");
}
boolean result = exceptionWorkorderService.batchDeleteExceptionWorkorders(workorderIds);
if (result) {
return ApiRes.success("批量删除异常工单成功", null);
} else {
return ApiRes.error("批量删除异常工单失败");
}
} catch (Exception e) {
return ApiRes.error("批量删除异常工单失败: " + e.getMessage());
}
}
@Operation(summary = "批量更新工单状态", description = "批量更新异常工单状态")
@PostMapping("/batch-status")
@PreAuthorize("hasAuthority('exception:workorder:edit')")
public ApiRes<Void> batchUpdateWorkorderStatus(
@Parameter(description = "工单ID列表", required = true) @RequestParam List<Long> workorderIds,
@Parameter(description = "工单状态", required = true) @RequestParam Integer workorderStatus,
@Parameter(description = "处理人", required = true) @RequestParam String handlerUser) {
try {
if (workorderIds == null || workorderIds.isEmpty()) {
return ApiRes.error("工单ID列表不能为空");
}
boolean result = exceptionWorkorderService.batchUpdateWorkorderStatus(workorderIds, workorderStatus, handlerUser);
if (result) {
return ApiRes.success("批量更新工单状态成功", null);
} else {
return ApiRes.error("批量更新工单状态失败");
}
} catch (Exception e) {
return ApiRes.error("批量更新工单状态失败: " + e.getMessage());
}
}
@Operation(summary = "获取异常工单统计信息", description = "获取异常工单的统计信息")
@GetMapping("/stats")
@PreAuthorize("hasAuthority('exception:workorder:list')")
public ApiRes<List<ExceptionWorkorderRes>> getExceptionWorkorderStats() {
List<ExceptionWorkorderRes> stats = exceptionWorkorderService.getExceptionWorkorderStats();
return ApiRes.success(stats);
}
}
......@@ -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 字典项实体
......
package com.apple.erp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 异常工单新增请求DTO
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Data
@Schema(description = "异常工单新增请求")
public class ExceptionWorkorderAddReq {
@NotBlank(message = "工单编号不能为空")
@Schema(description = "工单编号", required = true)
private String workorderNo;
@Schema(description = "关联订单编号")
private String orderNo;
@Schema(description = "经销商编码")
private String dealerCode;
@Schema(description = "经销商名称")
private String dealerName;
@NotNull(message = "异常类型不能为空")
@Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)", required = true)
private Integer exceptionType;
@NotNull(message = "严重程度不能为空")
@Schema(description = "严重程度(1-高/2-中/3-低)", required = true)
private Integer severityLevel;
@Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)")
private Integer workorderStatus = 1;
@Schema(description = "预计处理完成时间")
private LocalDateTime expectCompleteTime;
@Schema(description = "处理人")
private String handlerUser;
@Schema(description = "异常描述")
private String exceptionDesc;
@Schema(description = "处理建议")
private String handleSuggest;
@Schema(description = "数据来源")
private String dataSource;
}
package com.apple.erp.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 异常工单处理日志响应DTO
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Data
@Schema(description = "异常工单处理日志响应")
public class ExceptionWorkorderLogRes {
@Schema(description = "日志ID")
private Long logId;
@Schema(description = "关联工单ID")
private Long workorderId;
@Schema(description = "关联工单编号")
private String workorderNo;
@Schema(description = "处理人")
private String handleUser;
@Schema(description = "处理时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime handleTime;
@Schema(description = "处理前状态")
private Integer beforeStatus;
@Schema(description = "处理前状态名称")
private String beforeStatusName;
@Schema(description = "处理后状态")
private Integer afterStatus;
@Schema(description = "处理后状态名称")
private String afterStatusName;
@Schema(description = "处理意见")
private String handleOpinion;
@Schema(description = "附件URL")
private String attachUrl;
@Schema(description = "创建者")
private String createBy;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}
package com.apple.erp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 异常工单查询请求DTO
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Data
@Schema(description = "异常工单查询请求")
public class ExceptionWorkorderQueryReq {
@Schema(description = "工单编号")
private String workorderNo;
@Schema(description = "关联订单编号")
private String orderNo;
@Schema(description = "经销商编码")
private String dealerCode;
@Schema(description = "经销商名称")
private String dealerName;
@Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)")
private Integer workorderStatus;
@Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)")
private Integer exceptionType;
@Schema(description = "严重程度(1-高/2-中/3-低)")
private Integer severityLevel;
@Schema(description = "处理人")
private String handlerUser;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "页码")
private Integer pageNum = 1;
@Schema(description = "每页大小")
private Integer pageSize = 10;
}
package com.apple.erp.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 异常工单响应DTO
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Data
@Schema(description = "异常工单响应")
public class ExceptionWorkorderRes {
@Schema(description = "工单ID")
private Long workorderId;
@Schema(description = "工单编号")
private String workorderNo;
@Schema(description = "关联订单编号")
private String orderNo;
@Schema(description = "经销商编码")
private String dealerCode;
@Schema(description = "经销商名称")
private String dealerName;
@Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)")
private Integer exceptionType;
@Schema(description = "异常类型名称")
private String exceptionTypeName;
@Schema(description = "严重程度(1-高/2-中/3-低)")
private Integer severityLevel;
@Schema(description = "严重程度名称")
private String severityLevelName;
@Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)")
private Integer workorderStatus;
@Schema(description = "工单状态名称")
private String workorderStatusName;
@Schema(description = "工单创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@Schema(description = "预计处理完成时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime expectCompleteTime;
@Schema(description = "处理人")
private String handlerUser;
@Schema(description = "异常描述")
private String exceptionDesc;
@Schema(description = "处理建议")
private String handleSuggest;
@Schema(description = "数据来源")
private String dataSource;
@Schema(description = "创建者")
private String createBy;
@Schema(description = "更新者")
private String updateBy;
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
@Schema(description = "处理日志列表")
private List<ExceptionWorkorderLogRes> workorderLogs;
}
package com.apple.erp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 异常工单状态更新请求DTO
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Data
@Schema(description = "异常工单状态更新请求")
public class ExceptionWorkorderStatusUpdateReq {
@NotNull(message = "工单ID不能为空")
@Schema(description = "工单ID", required = true)
private Long workorderId;
@NotNull(message = "工单状态不能为空")
@Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)", required = true)
private Integer workorderStatus;
@Schema(description = "处理人")
private String handlerUser;
@Schema(description = "处理意见")
private String handleOpinion;
@Schema(description = "附件URL")
private String attachUrl;
}
package com.apple.erp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* 异常工单更新请求DTO
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Data
@Schema(description = "异常工单更新请求")
public class ExceptionWorkorderUpdateReq {
@NotNull(message = "工单ID不能为空")
@Schema(description = "工单ID", required = true)
private Long workorderId;
@Schema(description = "工单编号")
private String workorderNo;
@Schema(description = "关联订单编号")
private String orderNo;
@Schema(description = "经销商编码")
private String dealerCode;
@Schema(description = "经销商名称")
private String dealerName;
@Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)")
private Integer exceptionType;
@Schema(description = "严重程度(1-高/2-中/3-低)")
private Integer severityLevel;
@Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)")
private Integer workorderStatus;
@Schema(description = "预计处理完成时间")
private LocalDateTime expectCompleteTime;
@Schema(description = "处理人")
private String handlerUser;
@Schema(description = "异常描述")
private String exceptionDesc;
@Schema(description = "处理建议")
private String handleSuggest;
@Schema(description = "数据来源")
private String dataSource;
}
......@@ -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.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 异常工单表实体类
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_exception_workorder")
@Schema(description = "异常工单表")
public class ExceptionWorkorder {
@TableId(value = "workorder_id", type = IdType.AUTO)
@Schema(description = "工单ID")
private Long workorderId;
@Schema(description = "工单编号")
private String workorderNo;
@Schema(description = "关联订单编号")
private String orderNo;
@Schema(description = "经销商编码")
private String dealerCode;
@Schema(description = "经销商名称")
private String dealerName;
@Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)")
private Integer exceptionType;
@Schema(description = "严重程度(1-高/2-中/3-低)")
private Integer severityLevel;
@Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)")
private Integer workorderStatus;
@Schema(description = "工单创建时间")
private LocalDateTime createTime;
@Schema(description = "预计处理完成时间")
private LocalDateTime expectCompleteTime;
@Schema(description = "处理人")
private String handlerUser;
@Schema(description = "异常描述")
private String exceptionDesc;
@Schema(description = "处理建议")
private String handleSuggest;
@Schema(description = "数据来源")
private String dataSource;
@Schema(description = "创建者")
private String createBy;
@Schema(description = "更新者")
private String updateBy;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "删除标志(0代表存在 2代表删除)")
private String delFlag;
}
package com.apple.erp.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 异常工单处理日志表实体类
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_exception_workorder_log")
@Schema(description = "异常工单处理日志表")
public class ExceptionWorkorderLog {
@TableId(value = "log_id", type = IdType.AUTO)
@Schema(description = "日志ID")
private Long logId;
@Schema(description = "关联工单ID")
private Long workorderId;
@Schema(description = "关联工单编号")
private String workorderNo;
@Schema(description = "处理人")
private String handleUser;
@Schema(description = "处理时间")
private LocalDateTime handleTime;
@Schema(description = "处理前状态")
private Integer beforeStatus;
@Schema(description = "处理后状态")
private Integer afterStatus;
@Schema(description = "处理意见")
private String handleOpinion;
@Schema(description = "附件URL")
private String attachUrl;
@Schema(description = "创建者")
private String createBy;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新者")
private String updateBy;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "删除标志(0代表存在 2代表删除)")
private String delFlag;
}
package com.apple.erp.mapper;
import com.apple.erp.dto.ExceptionWorkorderLogRes;
import com.apple.erp.entity.ExceptionWorkorderLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 异常工单处理日志Mapper接口
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Mapper
public interface ExceptionWorkorderLogMapper extends BaseMapper<ExceptionWorkorderLog> {
/**
* 根据工单ID获取处理日志列表
*
* @param workorderId 工单ID
* @return 处理日志列表
*/
List<ExceptionWorkorderLogRes> selectWorkorderLogsByWorkorderId(@Param("workorderId") Long workorderId);
/**
* 根据工单ID列表批量获取处理日志
*
* @param workorderIds 工单ID列表
* @return 处理日志列表
*/
List<ExceptionWorkorderLogRes> selectWorkorderLogsByWorkorderIds(@Param("workorderIds") List<Long> workorderIds);
}
package com.apple.erp.mapper;
import com.apple.erp.dto.ExceptionWorkorderQueryReq;
import com.apple.erp.dto.ExceptionWorkorderRes;
import com.apple.erp.entity.ExceptionWorkorder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 异常工单Mapper接口
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Mapper
public interface ExceptionWorkorderMapper extends BaseMapper<ExceptionWorkorder> {
/**
* 分页查询异常工单列表
*
* @param page 分页参数
* @param queryReq 查询条件
* @return 异常工单列表
*/
Page<ExceptionWorkorderRes> selectExceptionWorkorderPage(Page<ExceptionWorkorderRes> page, @Param("query") ExceptionWorkorderQueryReq queryReq);
/**
* 根据工单ID获取异常工单详情
*
* @param workorderId 工单ID
* @return 异常工单详情
*/
ExceptionWorkorderRes selectExceptionWorkorderDetail(@Param("workorderId") Long workorderId);
/**
* 获取异常工单统计信息
*
* @return 统计信息
*/
List<ExceptionWorkorderRes> selectExceptionWorkorderStats();
/**
* 批量更新工单状态
*
* @param workorderIds 工单ID列表
* @param workorderStatus 工单状态
* @param handlerUser 处理人
* @return 更新数量
*/
int batchUpdateWorkorderStatus(@Param("workorderIds") List<Long> workorderIds,
@Param("workorderStatus") Integer workorderStatus,
@Param("handlerUser") String handlerUser);
}
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.service;
import com.apple.erp.dto.ExceptionWorkorderAddReq;
import com.apple.erp.dto.ExceptionWorkorderQueryReq;
import com.apple.erp.dto.ExceptionWorkorderRes;
import com.apple.erp.dto.ExceptionWorkorderStatusUpdateReq;
import com.apple.erp.dto.ExceptionWorkorderUpdateReq;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.apple.erp.entity.ExceptionWorkorder;
import java.util.List;
/**
* 异常工单Service接口
*
* @author Apple ERP System
* @since 2025-01-27
*/
public interface ExceptionWorkorderService extends IService<ExceptionWorkorder> {
/**
* 分页查询异常工单列表
*
* @param queryReq 查询条件
* @return 异常工单列表(带分页信息)
*/
Page<ExceptionWorkorderRes> getExceptionWorkorderList(ExceptionWorkorderQueryReq queryReq);
/**
* 获取异常工单详情
*
* @param workorderId 工单ID
* @return 异常工单详情(包含处理日志)
*/
ExceptionWorkorderRes getExceptionWorkorderDetail(Long workorderId);
/**
* 新增异常工单
*
* @param addReq 异常工单新增请求
* @return 是否成功
*/
boolean addExceptionWorkorder(ExceptionWorkorderAddReq addReq);
/**
* 更新异常工单
*
* @param updateReq 异常工单更新请求
* @return 是否成功
*/
boolean updateExceptionWorkorder(ExceptionWorkorderUpdateReq updateReq);
/**
* 更新工单状态
*
* @param statusUpdateReq 状态更新请求
* @return 是否成功
*/
boolean updateWorkorderStatus(ExceptionWorkorderStatusUpdateReq statusUpdateReq);
/**
* 删除异常工单
*
* @param workorderId 工单ID
* @return 是否成功
*/
boolean deleteExceptionWorkorder(Long workorderId);
/**
* 批量删除异常工单
*
* @param workorderIds 工单ID列表
* @return 是否成功
*/
boolean batchDeleteExceptionWorkorders(List<Long> workorderIds);
/**
* 批量更新工单状态
*
* @param workorderIds 工单ID列表
* @param workorderStatus 工单状态
* @param handlerUser 处理人
* @return 是否成功
*/
boolean batchUpdateWorkorderStatus(List<Long> workorderIds, Integer workorderStatus, String handlerUser);
/**
* 获取异常工单统计信息
*
* @return 统计信息
*/
List<ExceptionWorkorderRes> getExceptionWorkorderStats();
}
package com.apple.erp.service.impl;
import com.apple.erp.dto.ExceptionWorkorderAddReq;
import com.apple.erp.dto.ExceptionWorkorderLogRes;
import com.apple.erp.dto.ExceptionWorkorderQueryReq;
import com.apple.erp.dto.ExceptionWorkorderRes;
import com.apple.erp.dto.ExceptionWorkorderStatusUpdateReq;
import com.apple.erp.dto.ExceptionWorkorderUpdateReq;
import com.apple.erp.entity.ExceptionWorkorder;
import com.apple.erp.entity.ExceptionWorkorderLog;
import com.apple.erp.mapper.ExceptionWorkorderLogMapper;
import com.apple.erp.mapper.ExceptionWorkorderMapper;
import com.apple.erp.service.ExceptionWorkorderService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
/**
* 异常工单Service实现类
*
* @author Apple ERP System
* @since 2025-01-27
*/
@Service
public class ExceptionWorkorderServiceImpl extends ServiceImpl<ExceptionWorkorderMapper, ExceptionWorkorder> implements ExceptionWorkorderService {
@Autowired
private ExceptionWorkorderMapper exceptionWorkorderMapper;
@Autowired
private ExceptionWorkorderLogMapper exceptionWorkorderLogMapper;
@Override
public Page<ExceptionWorkorderRes> getExceptionWorkorderList(ExceptionWorkorderQueryReq queryReq) {
Page<ExceptionWorkorderRes> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize());
return exceptionWorkorderMapper.selectExceptionWorkorderPage(page, queryReq);
}
@Override
public ExceptionWorkorderRes getExceptionWorkorderDetail(Long workorderId) {
ExceptionWorkorderRes workorderRes = exceptionWorkorderMapper.selectExceptionWorkorderDetail(workorderId);
if (workorderRes != null) {
// 获取处理日志
List<ExceptionWorkorderLogRes> logs = exceptionWorkorderLogMapper.selectWorkorderLogsByWorkorderId(workorderId);
workorderRes.setWorkorderLogs(logs);
}
return workorderRes;
}
@Override
@Transactional
public boolean addExceptionWorkorder(ExceptionWorkorderAddReq addReq) {
ExceptionWorkorder workorder = new ExceptionWorkorder();
BeanUtils.copyProperties(addReq, workorder);
workorder.setCreateTime(LocalDateTime.now());
workorder.setDelFlag("0");
int result = exceptionWorkorderMapper.insert(workorder);
// 记录处理日志
if (result > 0) {
ExceptionWorkorderLog log = new ExceptionWorkorderLog();
log.setWorkorderId(workorder.getWorkorderId());
log.setWorkorderNo(workorder.getWorkorderNo());
log.setHandleUser(addReq.getHandlerUser());
log.setHandleTime(LocalDateTime.now());
log.setBeforeStatus(0);
log.setAfterStatus(workorder.getWorkorderStatus());
log.setHandleOpinion("工单创建");
log.setCreateTime(LocalDateTime.now());
log.setDelFlag("0");
exceptionWorkorderLogMapper.insert(log);
}
return result > 0;
}
@Override
@Transactional
public boolean updateExceptionWorkorder(ExceptionWorkorderUpdateReq updateReq) {
ExceptionWorkorder workorder = exceptionWorkorderMapper.selectById(updateReq.getWorkorderId());
if (workorder == null || "2".equals(workorder.getDelFlag())) {
return false;
}
BeanUtils.copyProperties(updateReq, workorder);
workorder.setUpdateTime(LocalDateTime.now());
return exceptionWorkorderMapper.updateById(workorder) > 0;
}
@Override
@Transactional
public boolean updateWorkorderStatus(ExceptionWorkorderStatusUpdateReq statusUpdateReq) {
ExceptionWorkorder workorder = exceptionWorkorderMapper.selectById(statusUpdateReq.getWorkorderId());
if (workorder == null || "2".equals(workorder.getDelFlag())) {
return false;
}
Integer beforeStatus = workorder.getWorkorderStatus();
workorder.setWorkorderStatus(statusUpdateReq.getWorkorderStatus());
workorder.setHandlerUser(statusUpdateReq.getHandlerUser());
workorder.setUpdateTime(LocalDateTime.now());
int result = exceptionWorkorderMapper.updateById(workorder);
// 记录处理日志
if (result > 0) {
ExceptionWorkorderLog log = new ExceptionWorkorderLog();
log.setWorkorderId(workorder.getWorkorderId());
log.setWorkorderNo(workorder.getWorkorderNo());
log.setHandleUser(statusUpdateReq.getHandlerUser());
log.setHandleTime(LocalDateTime.now());
log.setBeforeStatus(beforeStatus);
log.setAfterStatus(statusUpdateReq.getWorkorderStatus());
log.setHandleOpinion(statusUpdateReq.getHandleOpinion());
log.setAttachUrl(statusUpdateReq.getAttachUrl());
log.setCreateTime(LocalDateTime.now());
log.setDelFlag("0");
exceptionWorkorderLogMapper.insert(log);
}
return result > 0;
}
@Override
@Transactional
public boolean deleteExceptionWorkorder(Long workorderId) {
ExceptionWorkorder workorder = exceptionWorkorderMapper.selectById(workorderId);
if (workorder == null || "2".equals(workorder.getDelFlag())) {
return false;
}
workorder.setDelFlag("2");
workorder.setUpdateTime(LocalDateTime.now());
return exceptionWorkorderMapper.updateById(workorder) > 0;
}
@Override
@Transactional
public boolean batchDeleteExceptionWorkorders(List<Long> workorderIds) {
if (workorderIds == null || workorderIds.isEmpty()) {
return false;
}
ExceptionWorkorder workorder = new ExceptionWorkorder();
workorder.setDelFlag("2");
workorder.setUpdateTime(LocalDateTime.now());
LambdaQueryWrapper<ExceptionWorkorder> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(ExceptionWorkorder::getWorkorderId, workorderIds);
queryWrapper.eq(ExceptionWorkorder::getDelFlag, "0");
return exceptionWorkorderMapper.update(workorder, queryWrapper) > 0;
}
@Override
@Transactional
public boolean batchUpdateWorkorderStatus(List<Long> workorderIds, Integer workorderStatus, String handlerUser) {
if (workorderIds == null || workorderIds.isEmpty()) {
return false;
}
int result = exceptionWorkorderMapper.batchUpdateWorkorderStatus(workorderIds, workorderStatus, handlerUser);
// 记录批量处理日志
if (result > 0) {
for (Long workorderId : workorderIds) {
ExceptionWorkorder workorder = exceptionWorkorderMapper.selectById(workorderId);
if (workorder != null && "0".equals(workorder.getDelFlag())) {
ExceptionWorkorderLog log = new ExceptionWorkorderLog();
log.setWorkorderId(workorderId);
log.setWorkorderNo(workorder.getWorkorderNo());
log.setHandleUser(handlerUser);
log.setHandleTime(LocalDateTime.now());
log.setBeforeStatus(workorder.getWorkorderStatus());
log.setAfterStatus(workorderStatus);
log.setHandleOpinion("批量状态更新");
log.setCreateTime(LocalDateTime.now());
log.setDelFlag("0");
exceptionWorkorderLogMapper.insert(log);
}
}
}
return result > 0;
}
@Override
public List<ExceptionWorkorderRes> getExceptionWorkorderStats() {
return exceptionWorkorderMapper.selectExceptionWorkorderStats();
}
}
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;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.apple.erp.mapper.ExceptionWorkorderLogMapper">
<!-- 根据工单ID获取处理日志列表 -->
<select id="selectWorkorderLogsByWorkorderId" resultType="com.apple.erp.dto.ExceptionWorkorderLogRes">
SELECT
ewl.log_id,
ewl.workorder_id,
ewl.workorder_no,
ewl.handle_user,
ewl.handle_time,
ewl.before_status,
CASE ewl.before_status
WHEN 1 THEN '待处理'
WHEN 2 THEN '处理中'
WHEN 3 THEN '已解决'
WHEN 4 THEN '已关闭'
ELSE '未知状态'
END AS before_status_name,
ewl.after_status,
CASE ewl.after_status
WHEN 1 THEN '待处理'
WHEN 2 THEN '处理中'
WHEN 3 THEN '已解决'
WHEN 4 THEN '已关闭'
ELSE '未知状态'
END AS after_status_name,
ewl.handle_opinion,
ewl.attach_url,
ewl.create_by,
ewl.create_time
FROM t_exception_workorder_log ewl
WHERE ewl.workorder_id = #{workorderId} AND ewl.del_flag = '0'
ORDER BY ewl.handle_time DESC
</select>
<!-- 根据工单ID列表批量获取处理日志 -->
<select id="selectWorkorderLogsByWorkorderIds" resultType="com.apple.erp.dto.ExceptionWorkorderLogRes">
SELECT
ewl.log_id,
ewl.workorder_id,
ewl.workorder_no,
ewl.handle_user,
ewl.handle_time,
ewl.before_status,
CASE ewl.before_status
WHEN 1 THEN '待处理'
WHEN 2 THEN '处理中'
WHEN 3 THEN '已解决'
WHEN 4 THEN '已关闭'
ELSE '未知状态'
END AS before_status_name,
ewl.after_status,
CASE ewl.after_status
WHEN 1 THEN '待处理'
WHEN 2 THEN '处理中'
WHEN 3 THEN '已解决'
WHEN 4 THEN '已关闭'
ELSE '未知状态'
END AS after_status_name,
ewl.handle_opinion,
ewl.attach_url,
ewl.create_by,
ewl.create_time
FROM t_exception_workorder_log ewl
WHERE ewl.workorder_id IN
<foreach collection="workorderIds" item="workorderId" open="(" separator="," close=")">
#{workorderId}
</foreach>
AND ewl.del_flag = '0'
ORDER BY ewl.workorder_id, ewl.handle_time DESC
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.apple.erp.mapper.ExceptionWorkorderMapper">
<!-- 分页查询异常工单列表 -->
<select id="selectExceptionWorkorderPage" resultType="com.apple.erp.dto.ExceptionWorkorderRes">
SELECT
ew.workorder_id,
ew.workorder_no,
ew.order_no,
ew.dealer_code,
ew.dealer_name,
ew.exception_type,
CASE ew.exception_type
WHEN 1 THEN '逻辑验证异常'
WHEN 2 THEN '源头验证异常'
WHEN 3 THEN '交叉验证异常'
ELSE '未知类型'
END AS exception_type_name,
ew.severity_level,
CASE ew.severity_level
WHEN 1 THEN '高'
WHEN 2 THEN '中'
WHEN 3 THEN '低'
ELSE '未知'
END AS severity_level_name,
ew.workorder_status,
CASE ew.workorder_status
WHEN 1 THEN '待处理'
WHEN 2 THEN '处理中'
WHEN 3 THEN '已解决'
WHEN 4 THEN '已关闭'
ELSE '未知状态'
END AS workorder_status_name,
ew.create_time,
ew.expect_complete_time,
ew.handler_user,
ew.exception_desc,
ew.handle_suggest,
ew.data_source,
ew.create_by,
ew.update_by,
ew.update_time
FROM t_exception_workorder ew
<where>
ew.del_flag = '0'
<if test="query.workorderNo != null and query.workorderNo != ''">
AND ew.workorder_no LIKE CONCAT('%', #{query.workorderNo}, '%')
</if>
<if test="query.orderNo != null and query.orderNo != ''">
AND ew.order_no LIKE CONCAT('%', #{query.orderNo}, '%')
</if>
<if test="query.dealerCode != null and query.dealerCode != ''">
AND ew.dealer_code = #{query.dealerCode}
</if>
<if test="query.dealerName != null and query.dealerName != ''">
AND ew.dealer_name LIKE CONCAT('%', #{query.dealerName}, '%')
</if>
<if test="query.workorderStatus != null">
AND ew.workorder_status = #{query.workorderStatus}
</if>
<if test="query.exceptionType != null">
AND ew.exception_type = #{query.exceptionType}
</if>
<if test="query.severityLevel != null">
AND ew.severity_level = #{query.severityLevel}
</if>
<if test="query.handlerUser != null and query.handlerUser != ''">
AND ew.handler_user LIKE CONCAT('%', #{query.handlerUser}, '%')
</if>
<if test="query.startTime != null">
AND ew.create_time >= #{query.startTime}
</if>
<if test="query.endTime != null">
AND ew.create_time &lt;= #{query.endTime}
</if>
</where>
ORDER BY ew.create_time DESC
</select>
<!-- 根据工单ID获取异常工单详情 -->
<select id="selectExceptionWorkorderDetail" resultType="com.apple.erp.dto.ExceptionWorkorderRes">
SELECT
ew.workorder_id,
ew.workorder_no,
ew.order_no,
ew.dealer_code,
ew.dealer_name,
ew.exception_type,
CASE ew.exception_type
WHEN 1 THEN '逻辑验证异常'
WHEN 2 THEN '源头验证异常'
WHEN 3 THEN '交叉验证异常'
ELSE '未知类型'
END AS exception_type_name,
ew.severity_level,
CASE ew.severity_level
WHEN 1 THEN '高'
WHEN 2 THEN '中'
WHEN 3 THEN '低'
ELSE '未知'
END AS severity_level_name,
ew.workorder_status,
CASE ew.workorder_status
WHEN 1 THEN '待处理'
WHEN 2 THEN '处理中'
WHEN 3 THEN '已解决'
WHEN 4 THEN '已关闭'
ELSE '未知状态'
END AS workorder_status_name,
ew.create_time,
ew.expect_complete_time,
ew.handler_user,
ew.exception_desc,
ew.handle_suggest,
ew.data_source,
ew.create_by,
ew.update_by,
ew.update_time
FROM t_exception_workorder ew
WHERE ew.workorder_id = #{workorderId} AND ew.del_flag = '0'
</select>
<!-- 获取异常工单统计信息 -->
<select id="selectExceptionWorkorderStats" resultType="com.apple.erp.dto.ExceptionWorkorderRes">
SELECT
'total' AS workorder_no,
COUNT(*) AS workorder_id,
SUM(CASE WHEN workorder_status = 1 THEN 1 ELSE 0 END) AS exception_type,
SUM(CASE WHEN workorder_status = 2 THEN 1 ELSE 0 END) AS severity_level,
SUM(CASE WHEN workorder_status = 3 THEN 1 ELSE 0 END) AS workorder_status,
SUM(CASE WHEN workorder_status = 4 THEN 1 ELSE 0 END) AS handler_user,
SUM(CASE WHEN severity_level = 1 THEN 1 ELSE 0 END) AS exception_desc,
SUM(CASE WHEN severity_level = 2 THEN 1 ELSE 0 END) AS handle_suggest,
SUM(CASE WHEN severity_level = 3 THEN 1 ELSE 0 END) AS data_source
FROM t_exception_workorder
WHERE del_flag = '0'
</select>
<!-- 批量更新工单状态 -->
<update id="batchUpdateWorkorderStatus">
UPDATE t_exception_workorder
SET workorder_status = #{workorderStatus},
handler_user = #{handlerUser},
update_time = NOW()
WHERE workorder_id IN
<foreach collection="workorderIds" item="workorderId" open="(" separator="," close=")">
#{workorderId}
</foreach>
AND del_flag = '0'
</update>
</mapper>
-- 异常工单模块完整初始化脚本
-- 包含表结构创建、权限配置、测试数据插入
-- 1. 创建异常工单表
CREATE TABLE IF NOT EXISTS t_exception_workorder (
workorder_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '工单ID',
workorder_no VARCHAR(30) NOT NULL COMMENT '工单编号',
order_no VARCHAR(30) COMMENT '关联订单编号',
dealer_code VARCHAR(20) COMMENT '经销商编码',
dealer_name VARCHAR(100) COMMENT '经销商名称',
exception_type TINYINT NOT NULL COMMENT '异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)',
severity_level TINYINT NOT NULL COMMENT '严重程度(1-高/2-中/3-低)',
workorder_status TINYINT DEFAULT 1 COMMENT '工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '工单创建时间',
expect_complete_time DATETIME COMMENT '预计处理完成时间',
handler_user VARCHAR(20) COMMENT '处理人',
exception_desc VARCHAR(500) COMMENT '异常描述',
handle_suggest VARCHAR(500) COMMENT '处理建议',
data_source VARCHAR(20) COMMENT '数据来源',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='异常工单表';
-- 2. 创建异常工单处理日志表
CREATE TABLE IF NOT EXISTS t_exception_workorder_log (
log_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '日志ID',
workorder_id BIGINT NOT NULL COMMENT '关联工单ID',
workorder_no VARCHAR(30) NOT NULL COMMENT '关联工单编号',
handle_user VARCHAR(20) NOT NULL COMMENT '处理人',
handle_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '处理时间',
before_status TINYINT NOT NULL COMMENT '处理前状态',
after_status TINYINT NOT NULL COMMENT '处理后状态',
handle_opinion VARCHAR(500) COMMENT '处理意见',
attach_url VARCHAR(200) COMMENT '附件URL',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='异常工单处理日志表';
-- 3. 创建索引
-- 异常工单表索引
CREATE UNIQUE INDEX IF NOT EXISTS uk_workorder_no ON t_exception_workorder(workorder_no);
CREATE INDEX IF NOT EXISTS idx_workorder_dealer_code ON t_exception_workorder(dealer_code);
CREATE INDEX IF NOT EXISTS idx_workorder_order_no ON t_exception_workorder(order_no);
CREATE INDEX IF NOT EXISTS idx_workorder_status ON t_exception_workorder(workorder_status);
CREATE INDEX IF NOT EXISTS idx_workorder_exception_type ON t_exception_workorder(exception_type);
CREATE INDEX IF NOT EXISTS idx_workorder_severity_level ON t_exception_workorder(severity_level);
CREATE INDEX IF NOT EXISTS idx_workorder_create_time ON t_exception_workorder(create_time);
CREATE INDEX IF NOT EXISTS idx_workorder_handler_user ON t_exception_workorder(handler_user);
-- 异常工单处理日志表索引
CREATE INDEX IF NOT EXISTS idx_log_workorder_id ON t_exception_workorder_log(workorder_id);
CREATE INDEX IF NOT EXISTS idx_log_workorder_no ON t_exception_workorder_log(workorder_no);
CREATE INDEX IF NOT EXISTS idx_log_handle_user ON t_exception_workorder_log(handle_user);
CREATE INDEX IF NOT EXISTS idx_log_handle_time ON t_exception_workorder_log(handle_time);
-- 4. 创建外键约束
ALTER TABLE t_exception_workorder_log
ADD CONSTRAINT IF NOT EXISTS fk_workorder_log_workorder_id
FOREIGN KEY (workorder_id) REFERENCES t_exception_workorder(workorder_id)
ON DELETE CASCADE ON UPDATE CASCADE;
-- 5. 插入异常工单菜单
INSERT IGNORE INTO t_sys_menu (parent_id, menu_name, menu_type, path, icon, sort, status, perms, create_by, create_time) VALUES
(0, '异常工单', 1, '/main/exception-workorder', '⚠️', 7, 1, 'exception:workorder:view', 'admin', NOW());
-- 获取异常工单菜单ID
SET @exception_menu_id = LAST_INSERT_ID();
-- 6. 插入异常工单子菜单
INSERT IGNORE INTO t_sys_menu (parent_id, menu_name, menu_type, path, icon, sort, status, perms, create_by, create_time) VALUES
(@exception_menu_id, '工单查询', 2, '', '', 1, 1, 'exception:workorder:list', 'admin', NOW()),
(@exception_menu_id, '工单详情', 2, '', '', 2, 1, 'exception:workorder:detail', 'admin', NOW()),
(@exception_menu_id, '工单新增', 2, '', '', 3, 1, 'exception:workorder:add', 'admin', NOW()),
(@exception_menu_id, '工单编辑', 2, '', '', 4, 1, 'exception:workorder:edit', 'admin', NOW()),
(@exception_menu_id, '工单删除', 2, '', '', 5, 1, 'exception:workorder:delete', 'admin', NOW()),
(@exception_menu_id, '状态更新', 2, '', '', 6, 1, 'exception:workorder:status', 'admin', NOW()),
(@exception_menu_id, '批量处理', 2, '', '', 7, 1, 'exception:workorder:batch', 'admin', NOW()),
(@exception_menu_id, '统计查看', 2, '', '', 8, 1, 'exception:workorder:stats', 'admin', NOW());
-- 12. 为管理员角色分配异常工单权限
INSERT IGNORE INTO t_sys_role_menu (role_id, menu_id, create_by, create_time)
SELECT r.role_id, @exception_menu_id, 'admin', NOW()
FROM t_sys_role r
WHERE r.role_code = 'ADMIN';
-- 13. 为管理员角色分配异常工单子菜单权限
INSERT IGNORE INTO t_sys_role_menu (role_id, menu_id, create_by, create_time)
SELECT r.role_id, m.menu_id, 'admin', NOW()
FROM t_sys_role r, t_sys_menu m
WHERE r.role_code = 'ADMIN'
AND m.parent_id = @exception_menu_id;
-- 完成初始化
SELECT '异常工单模块初始化完成' AS message;
......@@ -5,5 +5,5 @@
// Generated by unplugin-auto-import
export {}
declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage']
}
......
......@@ -9,7 +9,9 @@ declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElCard: typeof import('element-plus/es')['ElCard']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
......@@ -23,7 +25,9 @@ declare module 'vue' {
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElSelect: typeof import('element-plus/es')['ElSelect']
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
......
import { request } from '../utils/request'
// 异常工单相关类型定义
export interface ExceptionWorkorderInfo {
workorderId: number
workorderNo: string
orderNo: string
dealerCode: string
dealerName: string
exceptionType: number
exceptionTypeName: string
severityLevel: number
severityLevelName: string
workorderStatus: number
workorderStatusName: string
createTime: string
expectCompleteTime?: string
handlerUser?: string
exceptionDesc?: string
handleSuggest?: string
dataSource?: string
createBy?: string
updateBy?: string
updateTime?: string
workorderLogs?: ExceptionWorkorderLogInfo[]
}
export interface ExceptionWorkorderLogInfo {
logId: number
workorderId: number
workorderNo: string
handleUser: string
handleTime: string
beforeStatus: number
beforeStatusName: string
afterStatus: number
afterStatusName: string
handleOpinion?: string
attachUrl?: string
createBy?: string
createTime: string
}
export interface ExceptionWorkorderQueryReq {
workorderNo?: string
orderNo?: string
dealerCode?: string
dealerName?: string
workorderStatus?: number
exceptionType?: number
severityLevel?: number
handlerUser?: string
startTime?: string
endTime?: string
pageNum?: number
pageSize?: number
}
export interface ExceptionWorkorderAddReq {
workorderNo: string
orderNo?: string
dealerCode?: string
dealerName?: string
exceptionType: number
severityLevel: number
workorderStatus?: number
expectCompleteTime?: string
handlerUser?: string
exceptionDesc?: string
handleSuggest?: string
dataSource?: string
}
export interface ExceptionWorkorderUpdateReq {
workorderId: number
workorderNo?: string
orderNo?: string
dealerCode?: string
dealerName?: string
exceptionType?: number
severityLevel?: number
workorderStatus?: number
expectCompleteTime?: string
handlerUser?: string
exceptionDesc?: string
handleSuggest?: string
dataSource?: string
}
export interface ExceptionWorkorderStatusUpdateReq {
workorderId: number
workorderStatus: number
handlerUser?: string
handleOpinion?: string
attachUrl?: string
}
// 异常工单API接口
export const exceptionWorkorderApi = {
// 分页查询异常工单列表
getExceptionWorkorderList: (params: ExceptionWorkorderQueryReq) => {
return request.get('/api/exception-workorder/list', params)
},
// 获取异常工单详情
getExceptionWorkorderDetail: (workorderId: number) => {
return request.get(`/api/exception-workorder/${workorderId}`)
},
// 新增异常工单
addExceptionWorkorder: (data: ExceptionWorkorderAddReq) => {
return request.post('/api/exception-workorder', data)
},
// 更新异常工单
updateExceptionWorkorder: (data: ExceptionWorkorderUpdateReq) => {
return request.put('/api/exception-workorder', data)
},
// 更新工单状态
updateWorkorderStatus: (data: ExceptionWorkorderStatusUpdateReq) => {
return request.post('/api/exception-workorder/status', data)
},
// 删除异常工单
deleteExceptionWorkorder: (workorderId: number) => {
return request.delete(`/api/exception-workorder/${workorderId}`)
},
// 批量删除异常工单
batchDeleteExceptionWorkorders: (workorderIds: number[]) => {
return request.delete('/api/exception-workorder/batch', workorderIds)
},
// 批量更新工单状态
batchUpdateWorkorderStatus: (workorderIds: number[], workorderStatus: number, handlerUser: string) => {
return request.post('/api/exception-workorder/batch-status', null, {
params: {
workorderIds: workorderIds.join(','),
workorderStatus,
handlerUser
}
})
},
// 获取异常工单统计信息
getExceptionWorkorderStats: () => {
return request.get('/api/exception-workorder/stats')
}
}
// 异常类型枚举
export const EXCEPTION_TYPE = {
1: '逻辑验证异常',
2: '源头验证异常',
3: '交叉验证异常'
}
// 严重程度枚举
export const SEVERITY_LEVEL = {
1: '高',
2: '中',
3: '低'
}
// 工单状态枚举
export const WORKORDER_STATUS = {
1: '待处理',
2: '处理中',
3: '已解决',
4: '已关闭'
}
// 获取异常类型名称
export const getExceptionTypeName = (type: number): string => {
return EXCEPTION_TYPE[type as keyof typeof EXCEPTION_TYPE] || '未知类型'
}
// 获取严重程度名称
export const getSeverityLevelName = (level: number): string => {
return SEVERITY_LEVEL[level as keyof typeof SEVERITY_LEVEL] || '未知'
}
// 获取工单状态名称
export const getWorkorderStatusName = (status: number): string => {
return WORKORDER_STATUS[status as keyof typeof WORKORDER_STATUS] || '未知状态'
}
// 获取严重程度颜色
export const getSeverityLevelColor = (level: number): string => {
const colors = {
1: '#f56c6c', // 高 - 红色
2: '#e6a23c', // 中 - 橙色
3: '#409eff' // 低 - 蓝色
}
return colors[level as keyof typeof colors] || '#909399'
}
// 获取工单状态颜色
export const getWorkorderStatusColor = (status: number): string => {
const colors = {
1: '#909399', // 待处理 - 灰色
2: '#e6a23c', // 处理中 - 橙色
3: '#67c23a', // 已解决 - 绿色
4: '#f56c6c' // 已关闭 - 红色
}
return colors[status as keyof typeof colors] || '#909399'
}
......@@ -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>()
......@@ -147,6 +169,7 @@ const menuItems = ref([
{ name: '产品管理', path: '/main/product', icon: '📦' },
{ name: '经销商管理', path: '/main/dealer', icon: '🏢' },
{ name: '返利管理', path: '/main/rebate', icon: '💰' },
{ name: '异常工单', path: '/main/exception-workorder', icon: '⚠️' },
{
name: '系统设置',
icon: '⚙️',
......@@ -370,9 +393,17 @@ watch(() => route.path, (newPath) => {
// 组件挂载时获取用户信息
onMounted(() => {
const storedUserInfo = localStorage.getItem('userInfo')
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo)
// 检查是否已登录
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')
......
......@@ -153,6 +153,24 @@ const staticRoutes: RouteRecordRaw[] = [
}
},
{
path: 'exception-workorder',
name: 'ExceptionWorkorder',
component: () => import('@/views/exceptionWorkorder/index.vue'),
meta: {
title: '异常工单',
requiresAuth: true
}
},
{
path: 'test-menu',
name: 'TestMenu',
component: () => import('@/views/test-menu.vue'),
meta: {
title: '菜单测试',
requiresAuth: true
}
},
{
path: 'settings',
name: 'Settings',
component: () => import('@/views/settings/index.vue'),
......
......@@ -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">
......@@ -43,32 +44,34 @@
<div class="dashboard-card">
<h3>系统信息</h3>
<div class="info-grid">
<div class="info-item">
<div class="info-item">
<label>用户名:</label>
<span>{{ userInfo?.username || '未知' }}</span>
</div>
<div class="info-item">
<span v-if="loading">加载中...</span>
<span v-else>{{ userInfo?.username || '未知' }}</span>
</div>
<div class="info-item">
<label>角色:</label>
<span>{{ userInfo?.roles?.[0]?.roleName || '普通用户' }}</span>
</div>
<div class="info-item">
<span v-if="loading">加载中...</span>
<span v-else>{{ getRoleName(userInfo) || '普通用户' }}</span>
</div>
<div class="info-item">
<label>登录时间:</label>
<span>{{ currentTime }}</span>
</div>
<div class="info-item">
</div>
<div class="info-item">
<label>系统版本:</label>
<span>v1.0.0</span>
</div>
</div>
</div>
</div>
<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)
// 获取角色名称
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 storedUserInfo = localStorage.getItem('userInfo')
if (storedUserInfo) {
userInfo.value = JSON.parse(storedUserInfo)
}
// 检查是否已登录
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')
......
......@@ -244,8 +244,13 @@
</tr>
</thead>
<tbody>
<<<<<<< .mine
<tr v-for="item in deliveryDetail.deliveryItems" :key="item.deliveryItemId">
<td>{{ item.productName }}</td>
=======
<tr v-for="item in deliveryDetail.deliveryItems" :key="item.itemId || item.deliveryId">
>>>>>>> .theirs
<td>{{ item.productCode }}</td>
<td>{{ item.deliveryQty }}</td>
<td>{{ formatCurrency(item.deliveryPrice) }}</td>
......@@ -627,6 +632,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'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
......@@ -729,7 +735,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
})
}
// 获取页码数组
......@@ -868,8 +879,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('导出失败,请重试')
}
}
}
// 新增出库相关函数
......
This diff is collapsed. Click to expand it.
......@@ -517,6 +517,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'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
......@@ -609,7 +610,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
})
}
// 获取页码数组
......@@ -840,8 +846,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) => {
......
......@@ -462,6 +462,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'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
......@@ -606,9 +607,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
})
}
// 方法
......@@ -786,8 +790,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": ".",
......