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
5336 additions
and
324 deletions
| ... | @@ -103,7 +103,7 @@ | ... | @@ -103,7 +103,7 @@ |
| 103 | 103 | ||
| 104 | **接口路径:** `GET /api/auth/userinfo` | 104 | **接口路径:** `GET /api/auth/userinfo` |
| 105 | 105 | ||
| 106 | -**功能描述:** 获取当前登录用户的详细信息 | 106 | +**功能描述:** 获取当前登录用户的详细信息,包括用户名、真实姓名、角色列表和权限列表 |
| 107 | 107 | ||
| 108 | **请求头:** `Authorization: Bearer {token}` | 108 | **请求头:** `Authorization: Bearer {token}` |
| 109 | 109 | ||
| ... | @@ -114,11 +114,46 @@ | ... | @@ -114,11 +114,46 @@ |
| 114 | "message": "获取用户信息成功", | 114 | "message": "获取用户信息成功", |
| 115 | "data": { | 115 | "data": { |
| 116 | "username": "admin", | 116 | "username": "admin", |
| 117 | - "authorities": ["ROLE_ADMIN"] | 117 | + "realName": "系统管理员", |
| 118 | + "roles": [ | ||
| 119 | + { | ||
| 120 | + "roleId": 1, | ||
| 121 | + "roleCode": "ADMIN", | ||
| 122 | + "roleName": "系统管理员", | ||
| 123 | + "status": 1, | ||
| 124 | + "statusText": "正常", | ||
| 125 | + "remark": "系统管理员角色", | ||
| 126 | + "createBy": "admin", | ||
| 127 | + "createTime": "2024-01-01T00:00:00", | ||
| 128 | + "updateBy": "admin", | ||
| 129 | + "updateTime": "2024-01-01T00:00:00" | ||
| 130 | + } | ||
| 131 | + ], | ||
| 132 | + "authorities": [ | ||
| 133 | + { | ||
| 134 | + "authority": "ROLE_ADMIN" | ||
| 135 | + } | ||
| 136 | + ] | ||
| 118 | } | 137 | } |
| 119 | } | 138 | } |
| 120 | ``` | 139 | ``` |
| 121 | 140 | ||
| 141 | +**响应字段说明:** | ||
| 142 | +- `username`: 用户名 | ||
| 143 | +- `realName`: 真实姓名 | ||
| 144 | +- `roles`: 用户角色列表 | ||
| 145 | + - `roleId`: 角色ID | ||
| 146 | + - `roleCode`: 角色编码 | ||
| 147 | + - `roleName`: 角色名称 | ||
| 148 | + - `status`: 角色状态(0-停用/1-启用) | ||
| 149 | + - `statusText`: 角色状态文本描述 | ||
| 150 | + - `remark`: 备注 | ||
| 151 | + - `createBy`: 创建者 | ||
| 152 | + - `createTime`: 创建时间 | ||
| 153 | + - `updateBy`: 更新者 | ||
| 154 | + - `updateTime`: 更新时间 | ||
| 155 | +- `authorities`: 用户权限列表(Spring Security权限对象) | ||
| 156 | + | ||
| 122 | ## 2. 用户管理 (SysUserController) | 157 | ## 2. 用户管理 (SysUserController) |
| 123 | 158 | ||
| 124 | ### 2.1 获取用户列表 | 159 | ### 2.1 获取用户列表 |
| ... | @@ -2412,6 +2447,50 @@ | ... | @@ -2412,6 +2447,50 @@ |
| 2412 | } | 2447 | } |
| 2413 | ``` | 2448 | ``` |
| 2414 | 2449 | ||
| 2450 | +### 8. 导出发票数据 | ||
| 2451 | + | ||
| 2452 | +**接口路径:** `POST /api/invoice/export` | ||
| 2453 | +**请求方法:** POST | ||
| 2454 | +**权限要求:** `invoice:export` | ||
| 2455 | + | ||
| 2456 | +**请求参数:** 同发票查询接口参数 | ||
| 2457 | + | ||
| 2458 | +**响应:** 返回Excel文件流 | ||
| 2459 | + | ||
| 2460 | +--- | ||
| 2461 | + | ||
| 2462 | +## 八、数据导出接口 | ||
| 2463 | + | ||
| 2464 | +### 1. 订单数据导出 | ||
| 2465 | + | ||
| 2466 | +**接口路径:** `POST /order/export` | ||
| 2467 | +**请求方法:** POST | ||
| 2468 | +**权限要求:** `order:export` | ||
| 2469 | + | ||
| 2470 | +**请求参数:** 同订单查询接口参数 | ||
| 2471 | + | ||
| 2472 | +**响应:** 返回Excel文件流,文件名格式:`订单数据_yyyyMMdd_HHmmss.xlsx` | ||
| 2473 | + | ||
| 2474 | +### 2. 出库数据导出 | ||
| 2475 | + | ||
| 2476 | +**接口路径:** `POST /api/delivery/export` | ||
| 2477 | +**请求方法:** POST | ||
| 2478 | +**权限要求:** `delivery:export` | ||
| 2479 | + | ||
| 2480 | +**请求参数:** 同出库查询接口参数 | ||
| 2481 | + | ||
| 2482 | +**响应:** 返回Excel文件流,文件名格式:`出库数据_yyyyMMdd_HHmmss.xlsx` | ||
| 2483 | + | ||
| 2484 | +### 3. 发票数据导出 | ||
| 2485 | + | ||
| 2486 | +**接口路径:** `POST /api/invoice/export` | ||
| 2487 | +**请求方法:** POST | ||
| 2488 | +**权限要求:** `invoice:export` | ||
| 2489 | + | ||
| 2490 | +**请求参数:** 同发票查询接口参数 | ||
| 2491 | + | ||
| 2492 | +**响应:** 返回Excel文件流,文件名格式:`发票数据_yyyyMMdd_HHmmss.xlsx` | ||
| 2493 | + | ||
| 2415 | --- | 2494 | --- |
| 2416 | 2495 | ||
| 2417 | **文档版本:** 1.0.0 | 2496 | **文档版本:** 1.0.0 | ... | ... |
| 1 | +package com.apple.erp.config; | ||
| 2 | + | ||
| 3 | +import java.util.HashMap; | ||
| 4 | +import java.util.Map; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 字典值配置类 | ||
| 8 | + * 集中管理所有字典值转换配置 | ||
| 9 | + * | ||
| 10 | + * @author Apple ERP System | ||
| 11 | + * @since 2025-01-01 | ||
| 12 | + */ | ||
| 13 | +public class DictConfig { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 操作类型字典 | ||
| 17 | + */ | ||
| 18 | + public static final Map<Integer, String> OPERATE_TYPE = new HashMap<Integer, String>() {{ | ||
| 19 | + put(1, "新增"); | ||
| 20 | + put(2, "修改"); | ||
| 21 | + put(3, "删除"); | ||
| 22 | + }}; | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * 计算状态字典 | ||
| 26 | + */ | ||
| 27 | + public static final Map<Integer, String> CALC_FLAG = new HashMap<Integer, String>() {{ | ||
| 28 | + put(0, "未计算"); | ||
| 29 | + put(1, "已计算"); | ||
| 30 | + }}; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 审核状态字典 | ||
| 34 | + */ | ||
| 35 | + public static final Map<Integer, String> AUDIT_STATUS = new HashMap<Integer, String>() {{ | ||
| 36 | + put(0, "待审核"); | ||
| 37 | + put(1, "审核通过"); | ||
| 38 | + put(2, "审核中"); | ||
| 39 | + put(3, "审核拒绝"); | ||
| 40 | + }}; | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 出库状态字典 | ||
| 44 | + */ | ||
| 45 | + public static final Map<Integer, String> DELIVERY_STATUS = new HashMap<Integer, String>() {{ | ||
| 46 | + put(0, "未出库"); | ||
| 47 | + put(1, "已出库"); | ||
| 48 | + put(2, "部分出库"); | ||
| 49 | + }}; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 发票状态字典 | ||
| 53 | + */ | ||
| 54 | + public static final Map<Integer, String> INVOICE_STATUS = new HashMap<Integer, String>() {{ | ||
| 55 | + put(0, "未开票"); | ||
| 56 | + put(1, "已开票"); | ||
| 57 | + put(2, "部分开票"); | ||
| 58 | + }}; | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * 返利计算状态字典 | ||
| 62 | + */ | ||
| 63 | + public static final Map<Integer, String> REBATE_CALC_FLAG = new HashMap<Integer, String>() {{ | ||
| 64 | + put(0, "未计算"); | ||
| 65 | + put(1, "已计算"); | ||
| 66 | + }}; | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * 审核状态字典 | ||
| 70 | + */ | ||
| 71 | + public static final Map<Integer, String> VERIFY_STATUS = new HashMap<Integer, String>() {{ | ||
| 72 | + put(0, "待审核"); | ||
| 73 | + put(1, "审核通过"); | ||
| 74 | + put(2, "审核中"); | ||
| 75 | + put(3, "审核拒绝"); | ||
| 76 | + }}; | ||
| 77 | + | ||
| 78 | + /** | ||
| 79 | + * 工单状态字典 | ||
| 80 | + */ | ||
| 81 | + public static final Map<Integer, String> WORKORDER_STATUS = new HashMap<Integer, String>() {{ | ||
| 82 | + put(0, "待处理"); | ||
| 83 | + put(1, "处理中"); | ||
| 84 | + put(2, "已完成"); | ||
| 85 | + put(3, "已关闭"); | ||
| 86 | + }}; | ||
| 87 | + | ||
| 88 | + /** | ||
| 89 | + * 严重程度字典 | ||
| 90 | + */ | ||
| 91 | + public static final Map<Integer, String> SEVERITY_LEVEL = new HashMap<Integer, String>() {{ | ||
| 92 | + put(1, "低"); | ||
| 93 | + put(2, "中"); | ||
| 94 | + put(3, "高"); | ||
| 95 | + put(4, "紧急"); | ||
| 96 | + }}; | ||
| 97 | +} |
| 1 | +package com.apple.erp.config; | ||
| 2 | + | ||
| 3 | +import java.util.HashMap; | ||
| 4 | +import java.util.Map; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 导出配置类 | ||
| 8 | + * 定义各模块的导出字段映射配置 | ||
| 9 | + * | ||
| 10 | + * @author Apple ERP System | ||
| 11 | + * @since 2025-01-01 | ||
| 12 | + */ | ||
| 13 | +public class ExportConfig { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 订单导出配置 | ||
| 17 | + */ | ||
| 18 | + public static final Map<String, String[]> ORDER_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 19 | + put("headers", new String[]{ | ||
| 20 | + "订单ID", "订单编号", "经销商编码", "经销商名称", "订单日期", | ||
| 21 | + "订单金额", "返利金额", "出库状态", "开票状态", "返利计算状态", | ||
| 22 | + "数据来源", "审核状态", "上传时间", "创建时间" | ||
| 23 | + }); | ||
| 24 | + put("fields", new String[]{ | ||
| 25 | + "orderId", "orderNo", "dealerCode", "dealerName", "orderDate", | ||
| 26 | + "totalAmount", "rebateAmount", "deliveryStatus", "invoiceStatus", "rebateCalcFlag", | ||
| 27 | + "dataSource", "verifyStatus", "uploadTime", "createTime" | ||
| 28 | + }); | ||
| 29 | + }}; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 出库导出配置 | ||
| 33 | + */ | ||
| 34 | + public static final Map<String, String[]> DELIVERY_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 35 | + put("headers", new String[]{ | ||
| 36 | + "出库单ID", "出库单编号", "经销商编码", "经销商名称", "出库日期", | ||
| 37 | + "关联订单编号", "出库状态", "仓库编码", "数据来源", "创建时间" | ||
| 38 | + }); | ||
| 39 | + put("fields", new String[]{ | ||
| 40 | + "deliveryId", "deliveryNo", "dealerCode", "dealerName", "deliveryDate", | ||
| 41 | + "orderNo", "deliveryStatus", "warehouseCode", "dataSource", "createTime" | ||
| 42 | + }); | ||
| 43 | + }}; | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 发票导出配置 | ||
| 47 | + */ | ||
| 48 | + public static final Map<String, String[]> INVOICE_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 49 | + put("headers", new String[]{ | ||
| 50 | + "发票ID", "发票编号", "订单编号", "出库单编号", "经销商编码", | ||
| 51 | + "经销商名称", "发票金额", "发票日期", "开票状态", "税率", | ||
| 52 | + "数据来源", "创建时间" | ||
| 53 | + }); | ||
| 54 | + put("fields", new String[]{ | ||
| 55 | + "invoiceId", "invoiceNo", "orderNo", "deliveryNo", "dealerCode", | ||
| 56 | + "dealerName", "totalAmount", "invoiceDate", "invoiceStatus", "taxRate", | ||
| 57 | + "dataSource", "createTime" | ||
| 58 | + }); | ||
| 59 | + }}; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * 返利导出配置 | ||
| 63 | + */ | ||
| 64 | + public static final Map<String, String[]> REBATE_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 65 | + put("headers", new String[]{ | ||
| 66 | + "返利ID", "返利编号", "订单编号", "经销商编码", "经销商名称", | ||
| 67 | + "返利金额", "返利类型", "计算状态", "审核状态", "数据来源", | ||
| 68 | + "创建时间", "更新时间" | ||
| 69 | + }); | ||
| 70 | + put("fields", new String[]{ | ||
| 71 | + "rebateId", "rebateNo", "orderNo", "dealerCode", "dealerName", | ||
| 72 | + "rebateAmount", "operateType", "calcFlag", "auditStatus", "dataSource", | ||
| 73 | + "createTime", "updateTime" | ||
| 74 | + }); | ||
| 75 | + }}; | ||
| 76 | + | ||
| 77 | + /** | ||
| 78 | + * 异常工单导出配置 | ||
| 79 | + */ | ||
| 80 | + public static final Map<String, String[]> EXCEPTION_WORKORDER_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 81 | + put("headers", new String[]{ | ||
| 82 | + "工单ID", "工单编号", "工单类型", "严重程度", "工单状态", | ||
| 83 | + "问题描述", "处理人", "创建人", "创建时间", "更新时间" | ||
| 84 | + }); | ||
| 85 | + put("fields", new String[]{ | ||
| 86 | + "workorderId", "workorderNo", "workorderType", "severityLevel", "workorderStatus", | ||
| 87 | + "problemDescription", "assignee", "creator", "createTime", "updateTime" | ||
| 88 | + }); | ||
| 89 | + }}; | ||
| 90 | +} |
| ... | @@ -4,6 +4,7 @@ import com.apple.erp.dto.request.LoginReq; | ... | @@ -4,6 +4,7 @@ import com.apple.erp.dto.request.LoginReq; |
| 4 | import com.apple.erp.dto.request.RefreshTokenReq; | 4 | import com.apple.erp.dto.request.RefreshTokenReq; |
| 5 | import com.apple.erp.dto.response.ApiRes; | 5 | import com.apple.erp.dto.response.ApiRes; |
| 6 | import com.apple.erp.dto.response.LoginRes; | 6 | import com.apple.erp.dto.response.LoginRes; |
| 7 | +import com.apple.erp.dto.response.RoleRes; | ||
| 7 | import com.apple.erp.dto.response.UserInfoRes; | 8 | import com.apple.erp.dto.response.UserInfoRes; |
| 8 | import com.apple.erp.entity.SysUser; | 9 | import com.apple.erp.entity.SysUser; |
| 9 | import com.apple.erp.service.SysUserService; | 10 | import com.apple.erp.service.SysUserService; |
| ... | @@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.*; | ... | @@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.*; |
| 23 | 24 | ||
| 24 | import javax.servlet.http.HttpServletRequest; | 25 | import javax.servlet.http.HttpServletRequest; |
| 25 | import javax.validation.Valid; | 26 | import javax.validation.Valid; |
| 27 | +import java.util.List; | ||
| 26 | 28 | ||
| 27 | /** | 29 | /** |
| 28 | * 认证控制器 | 30 | * 认证控制器 |
| ... | @@ -214,9 +216,34 @@ public class AuthController { | ... | @@ -214,9 +216,34 @@ public class AuthController { |
| 214 | if (authentication != null && authentication.isAuthenticated()) { | 216 | if (authentication != null && authentication.isAuthenticated()) { |
| 215 | String username = authentication.getName(); | 217 | String username = authentication.getName(); |
| 216 | 218 | ||
| 217 | - UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities()); | 219 | + try { |
| 218 | - ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); | 220 | + // 获取用户详细信息 |
| 219 | - return ResponseEntity.ok(response); | 221 | + SysUser user = sysUserService.findByUsername(username); |
| 222 | + if (user != null) { | ||
| 223 | + // 获取用户角色信息 | ||
| 224 | + List<RoleRes> roles = sysUserService.getUserRoles(user.getUserId()); | ||
| 225 | + | ||
| 226 | + UserInfoRes userInfo = new UserInfoRes( | ||
| 227 | + user.getUsername(), | ||
| 228 | + user.getRealName(), | ||
| 229 | + roles, | ||
| 230 | + authentication.getAuthorities() | ||
| 231 | + ); | ||
| 232 | + ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); | ||
| 233 | + return ResponseEntity.ok(response); | ||
| 234 | + } else { | ||
| 235 | + // 如果找不到用户信息,返回基本信息 | ||
| 236 | + UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities()); | ||
| 237 | + ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); | ||
| 238 | + return ResponseEntity.ok(response); | ||
| 239 | + } | ||
| 240 | + } catch (Exception e) { | ||
| 241 | + log.error("获取用户详细信息失败: " + e.getMessage(), e); | ||
| 242 | + // 如果获取详细信息失败,返回基本信息 | ||
| 243 | + UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities()); | ||
| 244 | + ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); | ||
| 245 | + return ResponseEntity.ok(response); | ||
| 246 | + } | ||
| 220 | } else { | 247 | } else { |
| 221 | ApiRes<UserInfoRes> response = ApiRes.error("未认证"); | 248 | ApiRes<UserInfoRes> response = ApiRes.error("未认证"); |
| 222 | return ResponseEntity.status(401).body(response); | 249 | return ResponseEntity.status(401).body(response); | ... | ... |
| ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.DeliveryRes; | ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.DeliveryRes; |
| 6 | import com.apple.erp.dto.DeliveryUpdateReq; | 6 | import com.apple.erp.dto.DeliveryUpdateReq; |
| 7 | import com.apple.erp.service.DeliveryMainService; | 7 | import com.apple.erp.service.DeliveryMainService; |
| 8 | import com.apple.erp.dto.response.ApiRes; | 8 | import com.apple.erp.dto.response.ApiRes; |
| 9 | +import com.apple.erp.service.ExcelExportService; | ||
| 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 10 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 10 | import io.swagger.v3.oas.annotations.Operation; | 11 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | import io.swagger.v3.oas.annotations.Parameter; | 12 | import io.swagger.v3.oas.annotations.Parameter; |
| 12 | import io.swagger.v3.oas.annotations.tags.Tag; | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 13 | import org.springframework.beans.factory.annotation.Autowired; | 14 | import org.springframework.beans.factory.annotation.Autowired; |
| 15 | +import org.springframework.http.ResponseEntity; | ||
| 14 | import org.springframework.security.access.prepost.PreAuthorize; | 16 | import org.springframework.security.access.prepost.PreAuthorize; |
| 15 | import org.springframework.validation.annotation.Validated; | 17 | import org.springframework.validation.annotation.Validated; |
| 16 | import org.springframework.web.bind.annotation.*; | 18 | import org.springframework.web.bind.annotation.*; |
| 17 | 19 | ||
| 18 | import javax.validation.Valid; | 20 | import javax.validation.Valid; |
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.format.DateTimeFormatter; | ||
| 19 | import java.util.List; | 23 | import java.util.List; |
| 20 | 24 | ||
| 21 | /** | 25 | /** |
| ... | @@ -32,6 +36,9 @@ public class DeliveryMainController { | ... | @@ -32,6 +36,9 @@ public class DeliveryMainController { |
| 32 | 36 | ||
| 33 | @Autowired | 37 | @Autowired |
| 34 | private DeliveryMainService deliveryMainService; | 38 | private DeliveryMainService deliveryMainService; |
| 39 | + | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 35 | 42 | ||
| 36 | @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表") | 43 | @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| ... | @@ -139,6 +146,40 @@ public class DeliveryMainController { | ... | @@ -139,6 +146,40 @@ public class DeliveryMainController { |
| 139 | return ApiRes.error("修改出库状态失败: " + e.getMessage()); | 146 | return ApiRes.error("修改出库状态失败: " + e.getMessage()); |
| 140 | } | 147 | } |
| 141 | } | 148 | } |
| 149 | + | ||
| 150 | + @Operation(summary = "导出出库数据", description = "根据查询条件导出出库数据到Excel") | ||
| 151 | + @PostMapping("/export") | ||
| 152 | + @PreAuthorize("hasAuthority('delivery:export')") | ||
| 153 | + public ResponseEntity<byte[]> exportDeliveries(@Valid @RequestBody DeliveryQueryReq queryReq) { | ||
| 154 | + try { | ||
| 155 | + // 获取所有符合条件的数据(不分页) | ||
| 156 | + DeliveryQueryReq exportQuery = new DeliveryQueryReq(); | ||
| 157 | + exportQuery.setDeliveryNo(queryReq.getDeliveryNo()); | ||
| 158 | + exportQuery.setDealerCode(queryReq.getDealerCode()); | ||
| 159 | + exportQuery.setDealerName(queryReq.getDealerName()); | ||
| 160 | + exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus()); | ||
| 161 | + exportQuery.setWarehouseCode(queryReq.getWarehouseCode()); | ||
| 162 | + exportQuery.setDataSource(queryReq.getDataSource()); | ||
| 163 | + exportQuery.setDeliveryStartDate(queryReq.getDeliveryStartDate()); | ||
| 164 | + exportQuery.setDeliveryEndDate(queryReq.getDeliveryEndDate()); | ||
| 165 | + // 设置大分页获取所有数据 | ||
| 166 | + exportQuery.setPageNum(1); | ||
| 167 | + exportQuery.setPageSize(10000); | ||
| 168 | + | ||
| 169 | + Page<DeliveryRes> result = deliveryMainService.getDeliveryList(exportQuery); | ||
| 170 | + List<DeliveryRes> deliveries = result.getRecords(); | ||
| 171 | + | ||
| 172 | + // 生成文件名 | ||
| 173 | + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); | ||
| 174 | + String fileName = "出库数据_" + timestamp; | ||
| 175 | + | ||
| 176 | + // 导出到Excel | ||
| 177 | + return excelExportService.exportDeliveries(deliveries); | ||
| 178 | + | ||
| 179 | + } catch (Exception e) { | ||
| 180 | + throw new RuntimeException("导出出库数据失败: " + e.getMessage(), e); | ||
| 181 | + } | ||
| 182 | + } | ||
| 142 | } | 183 | } |
| 143 | 184 | ||
| 144 | 185 | ... | ... |
| 1 | +package com.apple.erp.controller; | ||
| 2 | + | ||
| 3 | +import com.apple.erp.dto.ExceptionWorkorderAddReq; | ||
| 4 | +import com.apple.erp.dto.ExceptionWorkorderQueryReq; | ||
| 5 | +import com.apple.erp.dto.ExceptionWorkorderRes; | ||
| 6 | +import com.apple.erp.dto.ExceptionWorkorderStatusUpdateReq; | ||
| 7 | +import com.apple.erp.dto.ExceptionWorkorderUpdateReq; | ||
| 8 | +import com.apple.erp.service.ExceptionWorkorderService; | ||
| 9 | +import com.apple.erp.dto.response.ApiRes; | ||
| 10 | +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||
| 11 | +import io.swagger.v3.oas.annotations.Operation; | ||
| 12 | +import io.swagger.v3.oas.annotations.Parameter; | ||
| 13 | +import io.swagger.v3.oas.annotations.tags.Tag; | ||
| 14 | +import org.springframework.beans.factory.annotation.Autowired; | ||
| 15 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
| 16 | +import org.springframework.validation.annotation.Validated; | ||
| 17 | +import org.springframework.web.bind.annotation.*; | ||
| 18 | + | ||
| 19 | +import javax.validation.Valid; | ||
| 20 | +import java.util.List; | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * 异常工单Controller | ||
| 24 | + * | ||
| 25 | + * @author Apple ERP System | ||
| 26 | + * @since 2025-01-27 | ||
| 27 | + */ | ||
| 28 | +@Tag(name = "异常工单管理", description = "异常工单相关接口") | ||
| 29 | +@RestController | ||
| 30 | +@RequestMapping("/api/exception-workorder") | ||
| 31 | +@Validated | ||
| 32 | +public class ExceptionWorkorderController { | ||
| 33 | + | ||
| 34 | + @Autowired | ||
| 35 | + private ExceptionWorkorderService exceptionWorkorderService; | ||
| 36 | + | ||
| 37 | + @Operation(summary = "分页查询异常工单列表", description = "根据条件分页查询异常工单列表") | ||
| 38 | + @GetMapping("/list") | ||
| 39 | + @PreAuthorize("hasAuthority('exception:workorder:list')") | ||
| 40 | + public ApiRes<Page<ExceptionWorkorderRes>> getExceptionWorkorderList(@Valid ExceptionWorkorderQueryReq queryReq) { | ||
| 41 | + Page<ExceptionWorkorderRes> page = exceptionWorkorderService.getExceptionWorkorderList(queryReq); | ||
| 42 | + return ApiRes.success(page); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @Operation(summary = "获取异常工单详情", description = "根据工单ID获取异常工单详细信息,包含处理日志") | ||
| 46 | + @GetMapping("/{workorderId}") | ||
| 47 | + @PreAuthorize("hasAuthority('exception:workorder:detail')") | ||
| 48 | + public ApiRes<ExceptionWorkorderRes> getExceptionWorkorderDetail( | ||
| 49 | + @Parameter(description = "工单ID", required = true) @PathVariable Long workorderId) { | ||
| 50 | + ExceptionWorkorderRes workorderRes = exceptionWorkorderService.getExceptionWorkorderDetail(workorderId); | ||
| 51 | + if (workorderRes != null) { | ||
| 52 | + return ApiRes.success(workorderRes); | ||
| 53 | + } | ||
| 54 | + return ApiRes.error("异常工单不存在或已删除"); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + @Operation(summary = "新增异常工单", description = "创建新的异常工单") | ||
| 58 | + @PostMapping | ||
| 59 | + @PreAuthorize("hasAuthority('exception:workorder:add')") | ||
| 60 | + public ApiRes<Void> addExceptionWorkorder(@Valid @RequestBody ExceptionWorkorderAddReq addReq) { | ||
| 61 | + try { | ||
| 62 | + boolean result = exceptionWorkorderService.addExceptionWorkorder(addReq); | ||
| 63 | + if (result) { | ||
| 64 | + return ApiRes.success("新增异常工单成功", null); | ||
| 65 | + } else { | ||
| 66 | + return ApiRes.error("新增异常工单失败"); | ||
| 67 | + } | ||
| 68 | + } catch (Exception e) { | ||
| 69 | + return ApiRes.error("新增异常工单失败: " + e.getMessage()); | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + @Operation(summary = "更新异常工单", description = "更新异常工单信息") | ||
| 74 | + @PutMapping | ||
| 75 | + @PreAuthorize("hasAuthority('exception:workorder:edit')") | ||
| 76 | + public ApiRes<Void> updateExceptionWorkorder(@Valid @RequestBody ExceptionWorkorderUpdateReq updateReq) { | ||
| 77 | + try { | ||
| 78 | + boolean result = exceptionWorkorderService.updateExceptionWorkorder(updateReq); | ||
| 79 | + if (result) { | ||
| 80 | + return ApiRes.success("更新异常工单成功", null); | ||
| 81 | + } else { | ||
| 82 | + return ApiRes.error("更新异常工单失败"); | ||
| 83 | + } | ||
| 84 | + } catch (Exception e) { | ||
| 85 | + return ApiRes.error("更新异常工单失败: " + e.getMessage()); | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + @Operation(summary = "更新工单状态", description = "更新异常工单状态并记录处理日志") | ||
| 90 | + @PostMapping("/status") | ||
| 91 | + @PreAuthorize("hasAuthority('exception:workorder:edit')") | ||
| 92 | + public ApiRes<Void> updateWorkorderStatus(@Valid @RequestBody ExceptionWorkorderStatusUpdateReq statusUpdateReq) { | ||
| 93 | + try { | ||
| 94 | + boolean result = exceptionWorkorderService.updateWorkorderStatus(statusUpdateReq); | ||
| 95 | + if (result) { | ||
| 96 | + return ApiRes.success("更新工单状态成功", null); | ||
| 97 | + } else { | ||
| 98 | + return ApiRes.error("更新工单状态失败"); | ||
| 99 | + } | ||
| 100 | + } catch (Exception e) { | ||
| 101 | + return ApiRes.error("更新工单状态失败: " + e.getMessage()); | ||
| 102 | + } | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + @Operation(summary = "删除异常工单", description = "根据工单ID逻辑删除异常工单") | ||
| 106 | + @DeleteMapping("/{workorderId}") | ||
| 107 | + @PreAuthorize("hasAuthority('exception:workorder:delete')") | ||
| 108 | + public ApiRes<Void> deleteExceptionWorkorder( | ||
| 109 | + @Parameter(description = "工单ID", required = true) @PathVariable Long workorderId) { | ||
| 110 | + try { | ||
| 111 | + boolean result = exceptionWorkorderService.deleteExceptionWorkorder(workorderId); | ||
| 112 | + if (result) { | ||
| 113 | + return ApiRes.success("删除异常工单成功", null); | ||
| 114 | + } else { | ||
| 115 | + return ApiRes.error("删除异常工单失败"); | ||
| 116 | + } | ||
| 117 | + } catch (Exception e) { | ||
| 118 | + return ApiRes.error("删除异常工单失败: " + e.getMessage()); | ||
| 119 | + } | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + @Operation(summary = "批量删除异常工单", description = "根据工单ID列表批量逻辑删除异常工单") | ||
| 123 | + @DeleteMapping("/batch") | ||
| 124 | + @PreAuthorize("hasAuthority('exception:workorder:delete')") | ||
| 125 | + public ApiRes<Void> batchDeleteExceptionWorkorders( | ||
| 126 | + @Parameter(description = "工单ID列表", required = true) @RequestBody List<Long> workorderIds) { | ||
| 127 | + try { | ||
| 128 | + if (workorderIds == null || workorderIds.isEmpty()) { | ||
| 129 | + return ApiRes.error("工单ID列表不能为空"); | ||
| 130 | + } | ||
| 131 | + boolean result = exceptionWorkorderService.batchDeleteExceptionWorkorders(workorderIds); | ||
| 132 | + if (result) { | ||
| 133 | + return ApiRes.success("批量删除异常工单成功", null); | ||
| 134 | + } else { | ||
| 135 | + return ApiRes.error("批量删除异常工单失败"); | ||
| 136 | + } | ||
| 137 | + } catch (Exception e) { | ||
| 138 | + return ApiRes.error("批量删除异常工单失败: " + e.getMessage()); | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + @Operation(summary = "批量更新工单状态", description = "批量更新异常工单状态") | ||
| 143 | + @PostMapping("/batch-status") | ||
| 144 | + @PreAuthorize("hasAuthority('exception:workorder:edit')") | ||
| 145 | + public ApiRes<Void> batchUpdateWorkorderStatus( | ||
| 146 | + @Parameter(description = "工单ID列表", required = true) @RequestParam List<Long> workorderIds, | ||
| 147 | + @Parameter(description = "工单状态", required = true) @RequestParam Integer workorderStatus, | ||
| 148 | + @Parameter(description = "处理人", required = true) @RequestParam String handlerUser) { | ||
| 149 | + try { | ||
| 150 | + if (workorderIds == null || workorderIds.isEmpty()) { | ||
| 151 | + return ApiRes.error("工单ID列表不能为空"); | ||
| 152 | + } | ||
| 153 | + boolean result = exceptionWorkorderService.batchUpdateWorkorderStatus(workorderIds, workorderStatus, handlerUser); | ||
| 154 | + if (result) { | ||
| 155 | + return ApiRes.success("批量更新工单状态成功", null); | ||
| 156 | + } else { | ||
| 157 | + return ApiRes.error("批量更新工单状态失败"); | ||
| 158 | + } | ||
| 159 | + } catch (Exception e) { | ||
| 160 | + return ApiRes.error("批量更新工单状态失败: " + e.getMessage()); | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + @Operation(summary = "获取异常工单统计信息", description = "获取异常工单的统计信息") | ||
| 165 | + @GetMapping("/stats") | ||
| 166 | + @PreAuthorize("hasAuthority('exception:workorder:list')") | ||
| 167 | + public ApiRes<List<ExceptionWorkorderRes>> getExceptionWorkorderStats() { | ||
| 168 | + List<ExceptionWorkorderRes> stats = exceptionWorkorderService.getExceptionWorkorderStats(); | ||
| 169 | + return ApiRes.success(stats); | ||
| 170 | + } | ||
| 171 | +} |
| ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.InvoiceRes; | ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.InvoiceRes; |
| 6 | import com.apple.erp.dto.InvoiceUpdateReq; | 6 | import com.apple.erp.dto.InvoiceUpdateReq; |
| 7 | import com.apple.erp.service.InvoiceMainService; | 7 | import com.apple.erp.service.InvoiceMainService; |
| 8 | import com.apple.erp.dto.response.ApiRes; | 8 | import com.apple.erp.dto.response.ApiRes; |
| 9 | +import com.apple.erp.service.ExcelExportService; | ||
| 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 10 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 10 | import io.swagger.v3.oas.annotations.Operation; | 11 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | import io.swagger.v3.oas.annotations.Parameter; | 12 | import io.swagger.v3.oas.annotations.Parameter; |
| 12 | import io.swagger.v3.oas.annotations.tags.Tag; | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 13 | import org.springframework.beans.factory.annotation.Autowired; | 14 | import org.springframework.beans.factory.annotation.Autowired; |
| 15 | +import org.springframework.http.ResponseEntity; | ||
| 14 | import org.springframework.security.access.prepost.PreAuthorize; | 16 | import org.springframework.security.access.prepost.PreAuthorize; |
| 15 | import org.springframework.validation.annotation.Validated; | 17 | import org.springframework.validation.annotation.Validated; |
| 16 | import org.springframework.web.bind.annotation.*; | 18 | import org.springframework.web.bind.annotation.*; |
| 17 | 19 | ||
| 18 | import javax.validation.Valid; | 20 | import javax.validation.Valid; |
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.format.DateTimeFormatter; | ||
| 19 | import java.util.List; | 23 | import java.util.List; |
| 20 | 24 | ||
| 21 | /** | 25 | /** |
| ... | @@ -32,6 +36,9 @@ public class InvoiceMainController { | ... | @@ -32,6 +36,9 @@ public class InvoiceMainController { |
| 32 | 36 | ||
| 33 | @Autowired | 37 | @Autowired |
| 34 | private InvoiceMainService invoiceMainService; | 38 | private InvoiceMainService invoiceMainService; |
| 39 | + | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 35 | 42 | ||
| 36 | @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表") | 43 | @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| ... | @@ -139,6 +146,43 @@ public class InvoiceMainController { | ... | @@ -139,6 +146,43 @@ public class InvoiceMainController { |
| 139 | return ApiRes.error("修改发票状态失败: " + e.getMessage()); | 146 | return ApiRes.error("修改发票状态失败: " + e.getMessage()); |
| 140 | } | 147 | } |
| 141 | } | 148 | } |
| 149 | + | ||
| 150 | + @Operation(summary = "导出发票数据", description = "根据查询条件导出发票数据到Excel") | ||
| 151 | + @PostMapping("/export") | ||
| 152 | + @PreAuthorize("hasAuthority('invoice:export')") | ||
| 153 | + public ResponseEntity<byte[]> exportInvoices(@Valid @RequestBody InvoiceQueryReq queryReq) { | ||
| 154 | + try { | ||
| 155 | + // 获取所有符合条件的数据(不分页) | ||
| 156 | + InvoiceQueryReq exportQuery = new InvoiceQueryReq(); | ||
| 157 | + exportQuery.setInvoiceNo(queryReq.getInvoiceNo()); | ||
| 158 | + exportQuery.setOrderNo(queryReq.getOrderNo()); | ||
| 159 | + exportQuery.setDeliveryNo(queryReq.getDeliveryNo()); | ||
| 160 | + exportQuery.setDealerCode(queryReq.getDealerCode()); | ||
| 161 | + exportQuery.setDealerName(queryReq.getDealerName()); | ||
| 162 | + exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus()); | ||
| 163 | + exportQuery.setDataSource(queryReq.getDataSource()); | ||
| 164 | + exportQuery.setInvoiceStartDate(queryReq.getInvoiceStartDate()); | ||
| 165 | + exportQuery.setInvoiceEndDate(queryReq.getInvoiceEndDate()); | ||
| 166 | + exportQuery.setMinAmount(queryReq.getMinAmount()); | ||
| 167 | + exportQuery.setMaxAmount(queryReq.getMaxAmount()); | ||
| 168 | + // 设置大分页获取所有数据 | ||
| 169 | + exportQuery.setPageNum(1); | ||
| 170 | + exportQuery.setPageSize(10000); | ||
| 171 | + | ||
| 172 | + Page<InvoiceRes> result = invoiceMainService.getInvoiceList(exportQuery); | ||
| 173 | + List<InvoiceRes> invoices = result.getRecords(); | ||
| 174 | + | ||
| 175 | + // 生成文件名 | ||
| 176 | + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); | ||
| 177 | + String fileName = "发票数据_" + timestamp; | ||
| 178 | + | ||
| 179 | + // 导出到Excel | ||
| 180 | + return excelExportService.exportInvoices(invoices); | ||
| 181 | + | ||
| 182 | + } catch (Exception e) { | ||
| 183 | + throw new RuntimeException("导出发票数据失败: " + e.getMessage(), e); | ||
| 184 | + } | ||
| 185 | + } | ||
| 142 | } | 186 | } |
| 143 | 187 | ||
| 144 | 188 | ... | ... |
| ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes; | ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes; |
| 6 | import com.apple.erp.dto.OrderUpdateReq; | 6 | import com.apple.erp.dto.OrderUpdateReq; |
| 7 | import com.apple.erp.service.OrderMainService; | 7 | import com.apple.erp.service.OrderMainService; |
| 8 | import com.apple.erp.dto.response.ApiRes; | 8 | import com.apple.erp.dto.response.ApiRes; |
| 9 | +import com.apple.erp.service.ExcelExportService; | ||
| 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 10 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 10 | import io.swagger.v3.oas.annotations.Operation; | 11 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | import io.swagger.v3.oas.annotations.Parameter; | 12 | import io.swagger.v3.oas.annotations.Parameter; |
| 12 | import io.swagger.v3.oas.annotations.tags.Tag; | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 13 | import org.springframework.beans.factory.annotation.Autowired; | 14 | import org.springframework.beans.factory.annotation.Autowired; |
| 15 | +import org.springframework.http.ResponseEntity; | ||
| 14 | import org.springframework.security.access.prepost.PreAuthorize; | 16 | import org.springframework.security.access.prepost.PreAuthorize; |
| 15 | import org.springframework.validation.annotation.Validated; | 17 | import org.springframework.validation.annotation.Validated; |
| 16 | import org.springframework.web.bind.annotation.*; | 18 | import org.springframework.web.bind.annotation.*; |
| 17 | 19 | ||
| 18 | import javax.validation.Valid; | 20 | import javax.validation.Valid; |
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.format.DateTimeFormatter; | ||
| 19 | import java.util.List; | 23 | import java.util.List; |
| 20 | 24 | ||
| 21 | /** | 25 | /** |
| ... | @@ -32,6 +36,9 @@ public class OrderMainController { | ... | @@ -32,6 +36,9 @@ public class OrderMainController { |
| 32 | 36 | ||
| 33 | @Autowired | 37 | @Autowired |
| 34 | private OrderMainService orderMainService; | 38 | private OrderMainService orderMainService; |
| 39 | + | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 35 | 42 | ||
| 36 | @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表") | 43 | @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| ... | @@ -182,4 +189,42 @@ public class OrderMainController { | ... | @@ -182,4 +189,42 @@ public class OrderMainController { |
| 182 | return ApiRes.error("修改返利计算状态失败: " + e.getMessage()); | 189 | return ApiRes.error("修改返利计算状态失败: " + e.getMessage()); |
| 183 | } | 190 | } |
| 184 | } | 191 | } |
| 192 | + | ||
| 193 | + @Operation(summary = "导出订单数据", description = "根据查询条件导出订单数据到Excel") | ||
| 194 | + @PostMapping("/export") | ||
| 195 | + @PreAuthorize("hasAuthority('order:export')") | ||
| 196 | + public ResponseEntity<byte[]> exportOrders(@Valid @RequestBody OrderQueryReq queryReq) { | ||
| 197 | + try { | ||
| 198 | + // 获取所有符合条件的数据(不分页) | ||
| 199 | + OrderQueryReq exportQuery = new OrderQueryReq(); | ||
| 200 | + exportQuery.setOrderNo(queryReq.getOrderNo()); | ||
| 201 | + exportQuery.setDealerCode(queryReq.getDealerCode()); | ||
| 202 | + exportQuery.setDealerName(queryReq.getDealerName()); | ||
| 203 | + exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus()); | ||
| 204 | + exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus()); | ||
| 205 | + exportQuery.setRebateCalcFlag(queryReq.getRebateCalcFlag()); | ||
| 206 | + exportQuery.setDataSource(queryReq.getDataSource()); | ||
| 207 | + exportQuery.setVerifyStatus(queryReq.getVerifyStatus()); | ||
| 208 | + exportQuery.setOrderStartDate(queryReq.getOrderStartDate()); | ||
| 209 | + exportQuery.setOrderEndDate(queryReq.getOrderEndDate()); | ||
| 210 | + exportQuery.setMinAmount(queryReq.getMinAmount()); | ||
| 211 | + exportQuery.setMaxAmount(queryReq.getMaxAmount()); | ||
| 212 | + // 设置大分页获取所有数据 | ||
| 213 | + exportQuery.setPageNum(1); | ||
| 214 | + exportQuery.setPageSize(10000); | ||
| 215 | + | ||
| 216 | + Page<OrderRes> result = orderMainService.getOrderList(exportQuery); | ||
| 217 | + List<OrderRes> orders = result.getRecords(); | ||
| 218 | + | ||
| 219 | + // 生成文件名 | ||
| 220 | + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); | ||
| 221 | + String fileName = "订单数据_" + timestamp; | ||
| 222 | + | ||
| 223 | + // 导出到Excel | ||
| 224 | + return excelExportService.exportOrders(orders); | ||
| 225 | + | ||
| 226 | + } catch (Exception e) { | ||
| 227 | + throw new RuntimeException("导出订单数据失败: " + e.getMessage(), e); | ||
| 228 | + } | ||
| 229 | + } | ||
| 185 | } | 230 | } | ... | ... |
| ... | @@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes; | ... | @@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes; |
| 7 | import com.apple.erp.dto.response.RebateRes; | 7 | import com.apple.erp.dto.response.RebateRes; |
| 8 | import com.apple.erp.entity.Rebate; | 8 | import com.apple.erp.entity.Rebate; |
| 9 | import com.apple.erp.service.RebateService; | 9 | import com.apple.erp.service.RebateService; |
| 10 | +import com.apple.erp.service.ExcelExportService; | ||
| 10 | import com.baomidou.mybatisplus.core.metadata.IPage; | 11 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 11 | import io.swagger.v3.oas.annotations.Operation; | 12 | import io.swagger.v3.oas.annotations.Operation; |
| 12 | import io.swagger.v3.oas.annotations.tags.Tag; | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | @@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*; | ... | @@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*; |
| 17 | 18 | ||
| 18 | import javax.validation.Valid; | 19 | import javax.validation.Valid; |
| 19 | import java.math.BigDecimal; | 20 | import java.math.BigDecimal; |
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.format.DateTimeFormatter; | ||
| 20 | import java.util.List; | 23 | import java.util.List; |
| 21 | import java.util.Map; | 24 | import java.util.Map; |
| 25 | +import org.springframework.http.ResponseEntity; | ||
| 26 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
| 22 | 27 | ||
| 23 | /** | 28 | /** |
| 24 | * 返利台账明细管理控制器 | 29 | * 返利台账明细管理控制器 |
| ... | @@ -36,6 +41,9 @@ public class RebateController { | ... | @@ -36,6 +41,9 @@ public class RebateController { |
| 36 | 41 | ||
| 37 | @Autowired | 42 | @Autowired |
| 38 | private RebateService rebateService; | 43 | private RebateService rebateService; |
| 44 | + | ||
| 45 | + @Autowired | ||
| 46 | + private ExcelExportService excelExportService; | ||
| 39 | 47 | ||
| 40 | @Operation(summary = "分页查询返利明细列表") | 48 | @Operation(summary = "分页查询返利明细列表") |
| 41 | @PostMapping("/page") | 49 | @PostMapping("/page") |
| ... | @@ -277,4 +285,40 @@ public class RebateController { | ... | @@ -277,4 +285,40 @@ public class RebateController { |
| 277 | return ApiRes.error(e.getMessage()); | 285 | return ApiRes.error(e.getMessage()); |
| 278 | } | 286 | } |
| 279 | } | 287 | } |
| 288 | + | ||
| 289 | + @Operation(summary = "导出返利数据", description = "根据查询条件导出返利数据到Excel") | ||
| 290 | + @PostMapping("/export") | ||
| 291 | + @PreAuthorize("hasAuthority('rebate:export')") | ||
| 292 | + public ResponseEntity<byte[]> exportRebates(@Valid @RequestBody RebateQueryReq queryReq) { | ||
| 293 | + log.info("导出返利数据,参数:{}", queryReq); | ||
| 294 | + try { | ||
| 295 | + // 获取所有符合条件的数据(不分页) | ||
| 296 | + RebateQueryReq exportQuery = new RebateQueryReq(); | ||
| 297 | + exportQuery.setRebateNo(queryReq.getRebateNo()); | ||
| 298 | + exportQuery.setOrderNo(queryReq.getOrderNo()); | ||
| 299 | + exportQuery.setDealerCode(queryReq.getDealerCode()); | ||
| 300 | + exportQuery.setDealerName(queryReq.getDealerName()); | ||
| 301 | + exportQuery.setOperateType(queryReq.getOperateType()); | ||
| 302 | + exportQuery.setCalcFlag(queryReq.getCalcFlag()); | ||
| 303 | + exportQuery.setAuditStatus(queryReq.getAuditStatus()); | ||
| 304 | + exportQuery.setDataSource(queryReq.getDataSource()); | ||
| 305 | + exportQuery.setStartDate(queryReq.getStartDate()); | ||
| 306 | + exportQuery.setEndDate(queryReq.getEndDate()); | ||
| 307 | + exportQuery.setMinAmount(queryReq.getMinAmount()); | ||
| 308 | + exportQuery.setMaxAmount(queryReq.getMaxAmount()); | ||
| 309 | + // 设置大分页获取所有数据 | ||
| 310 | + exportQuery.setPageNum(1); | ||
| 311 | + exportQuery.setPageSize(10000); | ||
| 312 | + | ||
| 313 | + IPage<RebateRes> result = rebateService.getRebatePage(exportQuery); | ||
| 314 | + List<RebateRes> rebates = result.getRecords(); | ||
| 315 | + | ||
| 316 | + // 导出到Excel | ||
| 317 | + return excelExportService.exportRebates(rebates); | ||
| 318 | + | ||
| 319 | + } catch (Exception e) { | ||
| 320 | + log.error("导出返利数据失败", e); | ||
| 321 | + throw new RuntimeException("导出返利数据失败: " + e.getMessage(), e); | ||
| 322 | + } | ||
| 323 | + } | ||
| 280 | } | 324 | } | ... | ... |
| ... | @@ -193,6 +193,25 @@ public class SysDictItemController { | ... | @@ -193,6 +193,25 @@ public class SysDictItemController { |
| 193 | } | 193 | } |
| 194 | 194 | ||
| 195 | /** | 195 | /** |
| 196 | + * 刷新字典缓存 | ||
| 197 | + * 当字典项发生变化时,刷新Redis缓存以保持数据一致性 | ||
| 198 | + * | ||
| 199 | + * @return 操作结果 | ||
| 200 | + */ | ||
| 201 | + @Operation(summary = "刷新字典缓存", description = "刷新Redis字典缓存以保持数据一致性") | ||
| 202 | + @PostMapping("/refreshCache") | ||
| 203 | + @PreAuthorize("hasAuthority('sys:dict:edit')") | ||
| 204 | + public ApiRes<Void> refreshCache() { | ||
| 205 | + try { | ||
| 206 | + // 调用字典值转换器的刷新方法 | ||
| 207 | + com.apple.erp.util.DictValueConverter.refreshCache(); | ||
| 208 | + return ApiRes.success("字典缓存刷新成功", null); | ||
| 209 | + } catch (Exception e) { | ||
| 210 | + return ApiRes.error("刷新字典缓存失败: " + e.getMessage()); | ||
| 211 | + } | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + /** | ||
| 196 | * 转换SysDictItem为DictItemRes | 215 | * 转换SysDictItem为DictItemRes |
| 197 | * | 216 | * |
| 198 | * @param dictItem 字典项实体 | 217 | * @param dictItem 字典项实体 | ... | ... |
| 1 | +package com.apple.erp.dto; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import javax.validation.constraints.NotBlank; | ||
| 7 | +import javax.validation.constraints.NotNull; | ||
| 8 | +import java.time.LocalDateTime; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 异常工单新增请求DTO | ||
| 12 | + * | ||
| 13 | + * @author Apple ERP System | ||
| 14 | + * @since 2025-01-27 | ||
| 15 | + */ | ||
| 16 | +@Data | ||
| 17 | +@Schema(description = "异常工单新增请求") | ||
| 18 | +public class ExceptionWorkorderAddReq { | ||
| 19 | + | ||
| 20 | + @NotBlank(message = "工单编号不能为空") | ||
| 21 | + @Schema(description = "工单编号", required = true) | ||
| 22 | + private String workorderNo; | ||
| 23 | + | ||
| 24 | + @Schema(description = "关联订单编号") | ||
| 25 | + private String orderNo; | ||
| 26 | + | ||
| 27 | + @Schema(description = "经销商编码") | ||
| 28 | + private String dealerCode; | ||
| 29 | + | ||
| 30 | + @Schema(description = "经销商名称") | ||
| 31 | + private String dealerName; | ||
| 32 | + | ||
| 33 | + @NotNull(message = "异常类型不能为空") | ||
| 34 | + @Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)", required = true) | ||
| 35 | + private Integer exceptionType; | ||
| 36 | + | ||
| 37 | + @NotNull(message = "严重程度不能为空") | ||
| 38 | + @Schema(description = "严重程度(1-高/2-中/3-低)", required = true) | ||
| 39 | + private Integer severityLevel; | ||
| 40 | + | ||
| 41 | + @Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)") | ||
| 42 | + private Integer workorderStatus = 1; | ||
| 43 | + | ||
| 44 | + @Schema(description = "预计处理完成时间") | ||
| 45 | + private LocalDateTime expectCompleteTime; | ||
| 46 | + | ||
| 47 | + @Schema(description = "处理人") | ||
| 48 | + private String handlerUser; | ||
| 49 | + | ||
| 50 | + @Schema(description = "异常描述") | ||
| 51 | + private String exceptionDesc; | ||
| 52 | + | ||
| 53 | + @Schema(description = "处理建议") | ||
| 54 | + private String handleSuggest; | ||
| 55 | + | ||
| 56 | + @Schema(description = "数据来源") | ||
| 57 | + private String dataSource; | ||
| 58 | +} |
| 1 | +package com.apple.erp.dto; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonFormat; | ||
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +import java.time.LocalDateTime; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 异常工单处理日志响应DTO | ||
| 11 | + * | ||
| 12 | + * @author Apple ERP System | ||
| 13 | + * @since 2025-01-27 | ||
| 14 | + */ | ||
| 15 | +@Data | ||
| 16 | +@Schema(description = "异常工单处理日志响应") | ||
| 17 | +public class ExceptionWorkorderLogRes { | ||
| 18 | + | ||
| 19 | + @Schema(description = "日志ID") | ||
| 20 | + private Long logId; | ||
| 21 | + | ||
| 22 | + @Schema(description = "关联工单ID") | ||
| 23 | + private Long workorderId; | ||
| 24 | + | ||
| 25 | + @Schema(description = "关联工单编号") | ||
| 26 | + private String workorderNo; | ||
| 27 | + | ||
| 28 | + @Schema(description = "处理人") | ||
| 29 | + private String handleUser; | ||
| 30 | + | ||
| 31 | + @Schema(description = "处理时间") | ||
| 32 | + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||
| 33 | + private LocalDateTime handleTime; | ||
| 34 | + | ||
| 35 | + @Schema(description = "处理前状态") | ||
| 36 | + private Integer beforeStatus; | ||
| 37 | + | ||
| 38 | + @Schema(description = "处理前状态名称") | ||
| 39 | + private String beforeStatusName; | ||
| 40 | + | ||
| 41 | + @Schema(description = "处理后状态") | ||
| 42 | + private Integer afterStatus; | ||
| 43 | + | ||
| 44 | + @Schema(description = "处理后状态名称") | ||
| 45 | + private String afterStatusName; | ||
| 46 | + | ||
| 47 | + @Schema(description = "处理意见") | ||
| 48 | + private String handleOpinion; | ||
| 49 | + | ||
| 50 | + @Schema(description = "附件URL") | ||
| 51 | + private String attachUrl; | ||
| 52 | + | ||
| 53 | + @Schema(description = "创建者") | ||
| 54 | + private String createBy; | ||
| 55 | + | ||
| 56 | + @Schema(description = "创建时间") | ||
| 57 | + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||
| 58 | + private LocalDateTime createTime; | ||
| 59 | +} |
| 1 | +package com.apple.erp.dto; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import java.time.LocalDateTime; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 异常工单查询请求DTO | ||
| 10 | + * | ||
| 11 | + * @author Apple ERP System | ||
| 12 | + * @since 2025-01-27 | ||
| 13 | + */ | ||
| 14 | +@Data | ||
| 15 | +@Schema(description = "异常工单查询请求") | ||
| 16 | +public class ExceptionWorkorderQueryReq { | ||
| 17 | + | ||
| 18 | + @Schema(description = "工单编号") | ||
| 19 | + private String workorderNo; | ||
| 20 | + | ||
| 21 | + @Schema(description = "关联订单编号") | ||
| 22 | + private String orderNo; | ||
| 23 | + | ||
| 24 | + @Schema(description = "经销商编码") | ||
| 25 | + private String dealerCode; | ||
| 26 | + | ||
| 27 | + @Schema(description = "经销商名称") | ||
| 28 | + private String dealerName; | ||
| 29 | + | ||
| 30 | + @Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)") | ||
| 31 | + private Integer workorderStatus; | ||
| 32 | + | ||
| 33 | + @Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)") | ||
| 34 | + private Integer exceptionType; | ||
| 35 | + | ||
| 36 | + @Schema(description = "严重程度(1-高/2-中/3-低)") | ||
| 37 | + private Integer severityLevel; | ||
| 38 | + | ||
| 39 | + @Schema(description = "处理人") | ||
| 40 | + private String handlerUser; | ||
| 41 | + | ||
| 42 | + @Schema(description = "开始时间") | ||
| 43 | + private LocalDateTime startTime; | ||
| 44 | + | ||
| 45 | + @Schema(description = "结束时间") | ||
| 46 | + private LocalDateTime endTime; | ||
| 47 | + | ||
| 48 | + @Schema(description = "页码") | ||
| 49 | + private Integer pageNum = 1; | ||
| 50 | + | ||
| 51 | + @Schema(description = "每页大小") | ||
| 52 | + private Integer pageSize = 10; | ||
| 53 | +} |
| 1 | +package com.apple.erp.dto; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonFormat; | ||
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +import java.time.LocalDateTime; | ||
| 8 | +import java.util.List; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 异常工单响应DTO | ||
| 12 | + * | ||
| 13 | + * @author Apple ERP System | ||
| 14 | + * @since 2025-01-27 | ||
| 15 | + */ | ||
| 16 | +@Data | ||
| 17 | +@Schema(description = "异常工单响应") | ||
| 18 | +public class ExceptionWorkorderRes { | ||
| 19 | + | ||
| 20 | + @Schema(description = "工单ID") | ||
| 21 | + private Long workorderId; | ||
| 22 | + | ||
| 23 | + @Schema(description = "工单编号") | ||
| 24 | + private String workorderNo; | ||
| 25 | + | ||
| 26 | + @Schema(description = "关联订单编号") | ||
| 27 | + private String orderNo; | ||
| 28 | + | ||
| 29 | + @Schema(description = "经销商编码") | ||
| 30 | + private String dealerCode; | ||
| 31 | + | ||
| 32 | + @Schema(description = "经销商名称") | ||
| 33 | + private String dealerName; | ||
| 34 | + | ||
| 35 | + @Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)") | ||
| 36 | + private Integer exceptionType; | ||
| 37 | + | ||
| 38 | + @Schema(description = "异常类型名称") | ||
| 39 | + private String exceptionTypeName; | ||
| 40 | + | ||
| 41 | + @Schema(description = "严重程度(1-高/2-中/3-低)") | ||
| 42 | + private Integer severityLevel; | ||
| 43 | + | ||
| 44 | + @Schema(description = "严重程度名称") | ||
| 45 | + private String severityLevelName; | ||
| 46 | + | ||
| 47 | + @Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)") | ||
| 48 | + private Integer workorderStatus; | ||
| 49 | + | ||
| 50 | + @Schema(description = "工单状态名称") | ||
| 51 | + private String workorderStatusName; | ||
| 52 | + | ||
| 53 | + @Schema(description = "工单创建时间") | ||
| 54 | + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||
| 55 | + private LocalDateTime createTime; | ||
| 56 | + | ||
| 57 | + @Schema(description = "预计处理完成时间") | ||
| 58 | + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||
| 59 | + private LocalDateTime expectCompleteTime; | ||
| 60 | + | ||
| 61 | + @Schema(description = "处理人") | ||
| 62 | + private String handlerUser; | ||
| 63 | + | ||
| 64 | + @Schema(description = "异常描述") | ||
| 65 | + private String exceptionDesc; | ||
| 66 | + | ||
| 67 | + @Schema(description = "处理建议") | ||
| 68 | + private String handleSuggest; | ||
| 69 | + | ||
| 70 | + @Schema(description = "数据来源") | ||
| 71 | + private String dataSource; | ||
| 72 | + | ||
| 73 | + @Schema(description = "创建者") | ||
| 74 | + private String createBy; | ||
| 75 | + | ||
| 76 | + @Schema(description = "更新者") | ||
| 77 | + private String updateBy; | ||
| 78 | + | ||
| 79 | + @Schema(description = "更新时间") | ||
| 80 | + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||
| 81 | + private LocalDateTime updateTime; | ||
| 82 | + | ||
| 83 | + @Schema(description = "处理日志列表") | ||
| 84 | + private List<ExceptionWorkorderLogRes> workorderLogs; | ||
| 85 | +} |
| 1 | +package com.apple.erp.dto; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import javax.validation.constraints.NotNull; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 异常工单状态更新请求DTO | ||
| 10 | + * | ||
| 11 | + * @author Apple ERP System | ||
| 12 | + * @since 2025-01-27 | ||
| 13 | + */ | ||
| 14 | +@Data | ||
| 15 | +@Schema(description = "异常工单状态更新请求") | ||
| 16 | +public class ExceptionWorkorderStatusUpdateReq { | ||
| 17 | + | ||
| 18 | + @NotNull(message = "工单ID不能为空") | ||
| 19 | + @Schema(description = "工单ID", required = true) | ||
| 20 | + private Long workorderId; | ||
| 21 | + | ||
| 22 | + @NotNull(message = "工单状态不能为空") | ||
| 23 | + @Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)", required = true) | ||
| 24 | + private Integer workorderStatus; | ||
| 25 | + | ||
| 26 | + @Schema(description = "处理人") | ||
| 27 | + private String handlerUser; | ||
| 28 | + | ||
| 29 | + @Schema(description = "处理意见") | ||
| 30 | + private String handleOpinion; | ||
| 31 | + | ||
| 32 | + @Schema(description = "附件URL") | ||
| 33 | + private String attachUrl; | ||
| 34 | +} |
| 1 | +package com.apple.erp.dto; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import javax.validation.constraints.NotNull; | ||
| 7 | +import java.time.LocalDateTime; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 异常工单更新请求DTO | ||
| 11 | + * | ||
| 12 | + * @author Apple ERP System | ||
| 13 | + * @since 2025-01-27 | ||
| 14 | + */ | ||
| 15 | +@Data | ||
| 16 | +@Schema(description = "异常工单更新请求") | ||
| 17 | +public class ExceptionWorkorderUpdateReq { | ||
| 18 | + | ||
| 19 | + @NotNull(message = "工单ID不能为空") | ||
| 20 | + @Schema(description = "工单ID", required = true) | ||
| 21 | + private Long workorderId; | ||
| 22 | + | ||
| 23 | + @Schema(description = "工单编号") | ||
| 24 | + private String workorderNo; | ||
| 25 | + | ||
| 26 | + @Schema(description = "关联订单编号") | ||
| 27 | + private String orderNo; | ||
| 28 | + | ||
| 29 | + @Schema(description = "经销商编码") | ||
| 30 | + private String dealerCode; | ||
| 31 | + | ||
| 32 | + @Schema(description = "经销商名称") | ||
| 33 | + private String dealerName; | ||
| 34 | + | ||
| 35 | + @Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)") | ||
| 36 | + private Integer exceptionType; | ||
| 37 | + | ||
| 38 | + @Schema(description = "严重程度(1-高/2-中/3-低)") | ||
| 39 | + private Integer severityLevel; | ||
| 40 | + | ||
| 41 | + @Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)") | ||
| 42 | + private Integer workorderStatus; | ||
| 43 | + | ||
| 44 | + @Schema(description = "预计处理完成时间") | ||
| 45 | + private LocalDateTime expectCompleteTime; | ||
| 46 | + | ||
| 47 | + @Schema(description = "处理人") | ||
| 48 | + private String handlerUser; | ||
| 49 | + | ||
| 50 | + @Schema(description = "异常描述") | ||
| 51 | + private String exceptionDesc; | ||
| 52 | + | ||
| 53 | + @Schema(description = "处理建议") | ||
| 54 | + private String handleSuggest; | ||
| 55 | + | ||
| 56 | + @Schema(description = "数据来源") | ||
| 57 | + private String dataSource; | ||
| 58 | +} |
| ... | @@ -58,6 +58,36 @@ public class RebateQueryReq { | ... | @@ -58,6 +58,36 @@ public class RebateQueryReq { |
| 58 | private String rebateEndDate; | 58 | private String rebateEndDate; |
| 59 | 59 | ||
| 60 | /** | 60 | /** |
| 61 | + * 审核状态 | ||
| 62 | + */ | ||
| 63 | + private Integer auditStatus; | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * 数据来源 | ||
| 67 | + */ | ||
| 68 | + private String dataSource; | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * 开始日期 | ||
| 72 | + */ | ||
| 73 | + private String startDate; | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * 结束日期 | ||
| 77 | + */ | ||
| 78 | + private String endDate; | ||
| 79 | + | ||
| 80 | + /** | ||
| 81 | + * 最小金额 | ||
| 82 | + */ | ||
| 83 | + private java.math.BigDecimal minAmount; | ||
| 84 | + | ||
| 85 | + /** | ||
| 86 | + * 最大金额 | ||
| 87 | + */ | ||
| 88 | + private java.math.BigDecimal maxAmount; | ||
| 89 | + | ||
| 90 | + /** | ||
| 61 | * 页码 | 91 | * 页码 |
| 62 | */ | 92 | */ |
| 63 | private Integer pageNum = 1; | 93 | private Integer pageNum = 1; | ... | ... |
| ... | @@ -5,6 +5,7 @@ import lombok.Data; | ... | @@ -5,6 +5,7 @@ import lombok.Data; |
| 5 | import org.springframework.security.core.GrantedAuthority; | 5 | import org.springframework.security.core.GrantedAuthority; |
| 6 | 6 | ||
| 7 | import java.util.Collection; | 7 | import java.util.Collection; |
| 8 | +import java.util.List; | ||
| 8 | 9 | ||
| 9 | /** | 10 | /** |
| 10 | * 用户信息响应对象 | 11 | * 用户信息响应对象 |
| ... | @@ -20,6 +21,12 @@ public class UserInfoRes { | ... | @@ -20,6 +21,12 @@ public class UserInfoRes { |
| 20 | @Schema(description = "用户名", example = "admin") | 21 | @Schema(description = "用户名", example = "admin") |
| 21 | private String username; | 22 | private String username; |
| 22 | 23 | ||
| 24 | + @Schema(description = "真实姓名", example = "管理员") | ||
| 25 | + private String realName; | ||
| 26 | + | ||
| 27 | + @Schema(description = "用户角色列表") | ||
| 28 | + private List<RoleRes> roles; | ||
| 29 | + | ||
| 23 | @Schema(description = "用户权限列表") | 30 | @Schema(description = "用户权限列表") |
| 24 | private Collection<? extends GrantedAuthority> authorities; | 31 | private Collection<? extends GrantedAuthority> authorities; |
| 25 | 32 | ||
| ... | @@ -29,4 +36,11 @@ public class UserInfoRes { | ... | @@ -29,4 +36,11 @@ public class UserInfoRes { |
| 29 | this.username = username; | 36 | this.username = username; |
| 30 | this.authorities = authorities; | 37 | this.authorities = authorities; |
| 31 | } | 38 | } |
| 39 | + | ||
| 40 | + public UserInfoRes(String username, String realName, List<RoleRes> roles, Collection<? extends GrantedAuthority> authorities) { | ||
| 41 | + this.username = username; | ||
| 42 | + this.realName = realName; | ||
| 43 | + this.roles = roles; | ||
| 44 | + this.authorities = authorities; | ||
| 45 | + } | ||
| 32 | } | 46 | } | ... | ... |
| 1 | +package com.apple.erp.entity; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | ||
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 6 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 7 | +import lombok.Data; | ||
| 8 | +import lombok.EqualsAndHashCode; | ||
| 9 | + | ||
| 10 | +import java.time.LocalDateTime; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 异常工单表实体类 | ||
| 14 | + * | ||
| 15 | + * @author Apple ERP System | ||
| 16 | + * @since 2025-01-27 | ||
| 17 | + */ | ||
| 18 | +@Data | ||
| 19 | +@EqualsAndHashCode(callSuper = false) | ||
| 20 | +@TableName("t_exception_workorder") | ||
| 21 | +@Schema(description = "异常工单表") | ||
| 22 | +public class ExceptionWorkorder { | ||
| 23 | + | ||
| 24 | + @TableId(value = "workorder_id", type = IdType.AUTO) | ||
| 25 | + @Schema(description = "工单ID") | ||
| 26 | + private Long workorderId; | ||
| 27 | + | ||
| 28 | + @Schema(description = "工单编号") | ||
| 29 | + private String workorderNo; | ||
| 30 | + | ||
| 31 | + @Schema(description = "关联订单编号") | ||
| 32 | + private String orderNo; | ||
| 33 | + | ||
| 34 | + @Schema(description = "经销商编码") | ||
| 35 | + private String dealerCode; | ||
| 36 | + | ||
| 37 | + @Schema(description = "经销商名称") | ||
| 38 | + private String dealerName; | ||
| 39 | + | ||
| 40 | + @Schema(description = "异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)") | ||
| 41 | + private Integer exceptionType; | ||
| 42 | + | ||
| 43 | + @Schema(description = "严重程度(1-高/2-中/3-低)") | ||
| 44 | + private Integer severityLevel; | ||
| 45 | + | ||
| 46 | + @Schema(description = "工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)") | ||
| 47 | + private Integer workorderStatus; | ||
| 48 | + | ||
| 49 | + @Schema(description = "工单创建时间") | ||
| 50 | + private LocalDateTime createTime; | ||
| 51 | + | ||
| 52 | + @Schema(description = "预计处理完成时间") | ||
| 53 | + private LocalDateTime expectCompleteTime; | ||
| 54 | + | ||
| 55 | + @Schema(description = "处理人") | ||
| 56 | + private String handlerUser; | ||
| 57 | + | ||
| 58 | + @Schema(description = "异常描述") | ||
| 59 | + private String exceptionDesc; | ||
| 60 | + | ||
| 61 | + @Schema(description = "处理建议") | ||
| 62 | + private String handleSuggest; | ||
| 63 | + | ||
| 64 | + @Schema(description = "数据来源") | ||
| 65 | + private String dataSource; | ||
| 66 | + | ||
| 67 | + @Schema(description = "创建者") | ||
| 68 | + private String createBy; | ||
| 69 | + | ||
| 70 | + @Schema(description = "更新者") | ||
| 71 | + private String updateBy; | ||
| 72 | + | ||
| 73 | + @Schema(description = "更新时间") | ||
| 74 | + private LocalDateTime updateTime; | ||
| 75 | + | ||
| 76 | + @Schema(description = "删除标志(0代表存在 2代表删除)") | ||
| 77 | + private String delFlag; | ||
| 78 | +} |
| 1 | +package com.apple.erp.entity; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | ||
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 6 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 7 | +import lombok.Data; | ||
| 8 | +import lombok.EqualsAndHashCode; | ||
| 9 | + | ||
| 10 | +import java.time.LocalDateTime; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 异常工单处理日志表实体类 | ||
| 14 | + * | ||
| 15 | + * @author Apple ERP System | ||
| 16 | + * @since 2025-01-27 | ||
| 17 | + */ | ||
| 18 | +@Data | ||
| 19 | +@EqualsAndHashCode(callSuper = false) | ||
| 20 | +@TableName("t_exception_workorder_log") | ||
| 21 | +@Schema(description = "异常工单处理日志表") | ||
| 22 | +public class ExceptionWorkorderLog { | ||
| 23 | + | ||
| 24 | + @TableId(value = "log_id", type = IdType.AUTO) | ||
| 25 | + @Schema(description = "日志ID") | ||
| 26 | + private Long logId; | ||
| 27 | + | ||
| 28 | + @Schema(description = "关联工单ID") | ||
| 29 | + private Long workorderId; | ||
| 30 | + | ||
| 31 | + @Schema(description = "关联工单编号") | ||
| 32 | + private String workorderNo; | ||
| 33 | + | ||
| 34 | + @Schema(description = "处理人") | ||
| 35 | + private String handleUser; | ||
| 36 | + | ||
| 37 | + @Schema(description = "处理时间") | ||
| 38 | + private LocalDateTime handleTime; | ||
| 39 | + | ||
| 40 | + @Schema(description = "处理前状态") | ||
| 41 | + private Integer beforeStatus; | ||
| 42 | + | ||
| 43 | + @Schema(description = "处理后状态") | ||
| 44 | + private Integer afterStatus; | ||
| 45 | + | ||
| 46 | + @Schema(description = "处理意见") | ||
| 47 | + private String handleOpinion; | ||
| 48 | + | ||
| 49 | + @Schema(description = "附件URL") | ||
| 50 | + private String attachUrl; | ||
| 51 | + | ||
| 52 | + @Schema(description = "创建者") | ||
| 53 | + private String createBy; | ||
| 54 | + | ||
| 55 | + @Schema(description = "创建时间") | ||
| 56 | + private LocalDateTime createTime; | ||
| 57 | + | ||
| 58 | + @Schema(description = "更新者") | ||
| 59 | + private String updateBy; | ||
| 60 | + | ||
| 61 | + @Schema(description = "更新时间") | ||
| 62 | + private LocalDateTime updateTime; | ||
| 63 | + | ||
| 64 | + @Schema(description = "删除标志(0代表存在 2代表删除)") | ||
| 65 | + private String delFlag; | ||
| 66 | +} |
| 1 | +package com.apple.erp.mapper; | ||
| 2 | + | ||
| 3 | +import com.apple.erp.dto.ExceptionWorkorderLogRes; | ||
| 4 | +import com.apple.erp.entity.ExceptionWorkorderLog; | ||
| 5 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||
| 6 | +import org.apache.ibatis.annotations.Mapper; | ||
| 7 | +import org.apache.ibatis.annotations.Param; | ||
| 8 | + | ||
| 9 | +import java.util.List; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 异常工单处理日志Mapper接口 | ||
| 13 | + * | ||
| 14 | + * @author Apple ERP System | ||
| 15 | + * @since 2025-01-27 | ||
| 16 | + */ | ||
| 17 | +@Mapper | ||
| 18 | +public interface ExceptionWorkorderLogMapper extends BaseMapper<ExceptionWorkorderLog> { | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 根据工单ID获取处理日志列表 | ||
| 22 | + * | ||
| 23 | + * @param workorderId 工单ID | ||
| 24 | + * @return 处理日志列表 | ||
| 25 | + */ | ||
| 26 | + List<ExceptionWorkorderLogRes> selectWorkorderLogsByWorkorderId(@Param("workorderId") Long workorderId); | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 根据工单ID列表批量获取处理日志 | ||
| 30 | + * | ||
| 31 | + * @param workorderIds 工单ID列表 | ||
| 32 | + * @return 处理日志列表 | ||
| 33 | + */ | ||
| 34 | + List<ExceptionWorkorderLogRes> selectWorkorderLogsByWorkorderIds(@Param("workorderIds") List<Long> workorderIds); | ||
| 35 | +} |
| 1 | +package com.apple.erp.mapper; | ||
| 2 | + | ||
| 3 | +import com.apple.erp.dto.ExceptionWorkorderQueryReq; | ||
| 4 | +import com.apple.erp.dto.ExceptionWorkorderRes; | ||
| 5 | +import com.apple.erp.entity.ExceptionWorkorder; | ||
| 6 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||
| 7 | +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||
| 8 | +import org.apache.ibatis.annotations.Mapper; | ||
| 9 | +import org.apache.ibatis.annotations.Param; | ||
| 10 | + | ||
| 11 | +import java.util.List; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * 异常工单Mapper接口 | ||
| 15 | + * | ||
| 16 | + * @author Apple ERP System | ||
| 17 | + * @since 2025-01-27 | ||
| 18 | + */ | ||
| 19 | +@Mapper | ||
| 20 | +public interface ExceptionWorkorderMapper extends BaseMapper<ExceptionWorkorder> { | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 分页查询异常工单列表 | ||
| 24 | + * | ||
| 25 | + * @param page 分页参数 | ||
| 26 | + * @param queryReq 查询条件 | ||
| 27 | + * @return 异常工单列表 | ||
| 28 | + */ | ||
| 29 | + Page<ExceptionWorkorderRes> selectExceptionWorkorderPage(Page<ExceptionWorkorderRes> page, @Param("query") ExceptionWorkorderQueryReq queryReq); | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 根据工单ID获取异常工单详情 | ||
| 33 | + * | ||
| 34 | + * @param workorderId 工单ID | ||
| 35 | + * @return 异常工单详情 | ||
| 36 | + */ | ||
| 37 | + ExceptionWorkorderRes selectExceptionWorkorderDetail(@Param("workorderId") Long workorderId); | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * 获取异常工单统计信息 | ||
| 41 | + * | ||
| 42 | + * @return 统计信息 | ||
| 43 | + */ | ||
| 44 | + List<ExceptionWorkorderRes> selectExceptionWorkorderStats(); | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 批量更新工单状态 | ||
| 48 | + * | ||
| 49 | + * @param workorderIds 工单ID列表 | ||
| 50 | + * @param workorderStatus 工单状态 | ||
| 51 | + * @param handlerUser 处理人 | ||
| 52 | + * @return 更新数量 | ||
| 53 | + */ | ||
| 54 | + int batchUpdateWorkorderStatus(@Param("workorderIds") List<Long> workorderIds, | ||
| 55 | + @Param("workorderStatus") Integer workorderStatus, | ||
| 56 | + @Param("handlerUser") String handlerUser); | ||
| 57 | +} |
| 1 | +package com.apple.erp.service; | ||
| 2 | + | ||
| 3 | +import com.apple.erp.config.ExportConfig; | ||
| 4 | +import com.apple.erp.util.GenericExcelExportUtil; | ||
| 5 | +import org.springframework.stereotype.Service; | ||
| 6 | + | ||
| 7 | +import java.time.LocalDateTime; | ||
| 8 | +import java.time.format.DateTimeFormatter; | ||
| 9 | +import java.util.List; | ||
| 10 | +import java.util.Map; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * Excel导出服务类 | ||
| 14 | + * 提供统一的导出接口,各模块可独立使用 | ||
| 15 | + * | ||
| 16 | + * @author Apple ERP System | ||
| 17 | + * @since 2025-01-01 | ||
| 18 | + */ | ||
| 19 | +@Service | ||
| 20 | +public class ExcelExportService { | ||
| 21 | + | ||
| 22 | + private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * 导出订单数据 | ||
| 26 | + */ | ||
| 27 | + public org.springframework.http.ResponseEntity<byte[]> exportOrders(List<?> data) { | ||
| 28 | + Map<String, String[]> config = ExportConfig.ORDER_EXPORT_CONFIG; | ||
| 29 | + String fileName = "订单数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER); | ||
| 30 | + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * 导出出库数据 | ||
| 35 | + */ | ||
| 36 | + public org.springframework.http.ResponseEntity<byte[]> exportDeliveries(List<?> data) { | ||
| 37 | + Map<String, String[]> config = ExportConfig.DELIVERY_EXPORT_CONFIG; | ||
| 38 | + String fileName = "出库数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER); | ||
| 39 | + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName); | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 导出发票数据 | ||
| 44 | + */ | ||
| 45 | + public org.springframework.http.ResponseEntity<byte[]> exportInvoices(List<?> data) { | ||
| 46 | + Map<String, String[]> config = ExportConfig.INVOICE_EXPORT_CONFIG; | ||
| 47 | + String fileName = "发票数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER); | ||
| 48 | + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 导出返利数据 | ||
| 53 | + */ | ||
| 54 | + public org.springframework.http.ResponseEntity<byte[]> exportRebates(List<?> data) { | ||
| 55 | + Map<String, String[]> config = ExportConfig.REBATE_EXPORT_CONFIG; | ||
| 56 | + String fileName = "返利数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER); | ||
| 57 | + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * 导出异常工单数据 | ||
| 62 | + */ | ||
| 63 | + public org.springframework.http.ResponseEntity<byte[]> exportExceptionWorkorders(List<?> data) { | ||
| 64 | + Map<String, String[]> config = ExportConfig.EXCEPTION_WORKORDER_EXPORT_CONFIG; | ||
| 65 | + String fileName = "异常工单数据_" + LocalDateTime.now().format(TIMESTAMP_FORMATTER); | ||
| 66 | + return GenericExcelExportUtil.exportToExcel(data, config.get("headers"), config.get("fields"), fileName); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + /** | ||
| 70 | + * 通用导出方法 | ||
| 71 | + * 支持自定义表头和字段映射 | ||
| 72 | + */ | ||
| 73 | + public org.springframework.http.ResponseEntity<byte[]> exportCustom(List<?> data, String[] headers, String[] fieldNames, String fileName) { | ||
| 74 | + return GenericExcelExportUtil.exportToExcel(data, headers, fieldNames, fileName); | ||
| 75 | + } | ||
| 76 | +} |
| 1 | +package com.apple.erp.service; | ||
| 2 | + | ||
| 3 | +import com.apple.erp.dto.ExceptionWorkorderAddReq; | ||
| 4 | +import com.apple.erp.dto.ExceptionWorkorderQueryReq; | ||
| 5 | +import com.apple.erp.dto.ExceptionWorkorderRes; | ||
| 6 | +import com.apple.erp.dto.ExceptionWorkorderStatusUpdateReq; | ||
| 7 | +import com.apple.erp.dto.ExceptionWorkorderUpdateReq; | ||
| 8 | +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||
| 9 | +import com.baomidou.mybatisplus.extension.service.IService; | ||
| 10 | +import com.apple.erp.entity.ExceptionWorkorder; | ||
| 11 | + | ||
| 12 | +import java.util.List; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * 异常工单Service接口 | ||
| 16 | + * | ||
| 17 | + * @author Apple ERP System | ||
| 18 | + * @since 2025-01-27 | ||
| 19 | + */ | ||
| 20 | +public interface ExceptionWorkorderService extends IService<ExceptionWorkorder> { | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 分页查询异常工单列表 | ||
| 24 | + * | ||
| 25 | + * @param queryReq 查询条件 | ||
| 26 | + * @return 异常工单列表(带分页信息) | ||
| 27 | + */ | ||
| 28 | + Page<ExceptionWorkorderRes> getExceptionWorkorderList(ExceptionWorkorderQueryReq queryReq); | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 获取异常工单详情 | ||
| 32 | + * | ||
| 33 | + * @param workorderId 工单ID | ||
| 34 | + * @return 异常工单详情(包含处理日志) | ||
| 35 | + */ | ||
| 36 | + ExceptionWorkorderRes getExceptionWorkorderDetail(Long workorderId); | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * 新增异常工单 | ||
| 40 | + * | ||
| 41 | + * @param addReq 异常工单新增请求 | ||
| 42 | + * @return 是否成功 | ||
| 43 | + */ | ||
| 44 | + boolean addExceptionWorkorder(ExceptionWorkorderAddReq addReq); | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 更新异常工单 | ||
| 48 | + * | ||
| 49 | + * @param updateReq 异常工单更新请求 | ||
| 50 | + * @return 是否成功 | ||
| 51 | + */ | ||
| 52 | + boolean updateExceptionWorkorder(ExceptionWorkorderUpdateReq updateReq); | ||
| 53 | + | ||
| 54 | + /** | ||
| 55 | + * 更新工单状态 | ||
| 56 | + * | ||
| 57 | + * @param statusUpdateReq 状态更新请求 | ||
| 58 | + * @return 是否成功 | ||
| 59 | + */ | ||
| 60 | + boolean updateWorkorderStatus(ExceptionWorkorderStatusUpdateReq statusUpdateReq); | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * 删除异常工单 | ||
| 64 | + * | ||
| 65 | + * @param workorderId 工单ID | ||
| 66 | + * @return 是否成功 | ||
| 67 | + */ | ||
| 68 | + boolean deleteExceptionWorkorder(Long workorderId); | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * 批量删除异常工单 | ||
| 72 | + * | ||
| 73 | + * @param workorderIds 工单ID列表 | ||
| 74 | + * @return 是否成功 | ||
| 75 | + */ | ||
| 76 | + boolean batchDeleteExceptionWorkorders(List<Long> workorderIds); | ||
| 77 | + | ||
| 78 | + /** | ||
| 79 | + * 批量更新工单状态 | ||
| 80 | + * | ||
| 81 | + * @param workorderIds 工单ID列表 | ||
| 82 | + * @param workorderStatus 工单状态 | ||
| 83 | + * @param handlerUser 处理人 | ||
| 84 | + * @return 是否成功 | ||
| 85 | + */ | ||
| 86 | + boolean batchUpdateWorkorderStatus(List<Long> workorderIds, Integer workorderStatus, String handlerUser); | ||
| 87 | + | ||
| 88 | + /** | ||
| 89 | + * 获取异常工单统计信息 | ||
| 90 | + * | ||
| 91 | + * @return 统计信息 | ||
| 92 | + */ | ||
| 93 | + List<ExceptionWorkorderRes> getExceptionWorkorderStats(); | ||
| 94 | +} |
| 1 | +package com.apple.erp.service.impl; | ||
| 2 | + | ||
| 3 | +import com.apple.erp.dto.ExceptionWorkorderAddReq; | ||
| 4 | +import com.apple.erp.dto.ExceptionWorkorderLogRes; | ||
| 5 | +import com.apple.erp.dto.ExceptionWorkorderQueryReq; | ||
| 6 | +import com.apple.erp.dto.ExceptionWorkorderRes; | ||
| 7 | +import com.apple.erp.dto.ExceptionWorkorderStatusUpdateReq; | ||
| 8 | +import com.apple.erp.dto.ExceptionWorkorderUpdateReq; | ||
| 9 | +import com.apple.erp.entity.ExceptionWorkorder; | ||
| 10 | +import com.apple.erp.entity.ExceptionWorkorderLog; | ||
| 11 | +import com.apple.erp.mapper.ExceptionWorkorderLogMapper; | ||
| 12 | +import com.apple.erp.mapper.ExceptionWorkorderMapper; | ||
| 13 | +import com.apple.erp.service.ExceptionWorkorderService; | ||
| 14 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||
| 15 | +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||
| 16 | +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; | ||
| 17 | +import org.springframework.beans.BeanUtils; | ||
| 18 | +import org.springframework.beans.factory.annotation.Autowired; | ||
| 19 | +import org.springframework.stereotype.Service; | ||
| 20 | +import org.springframework.transaction.annotation.Transactional; | ||
| 21 | +import org.springframework.util.StringUtils; | ||
| 22 | + | ||
| 23 | +import java.time.LocalDateTime; | ||
| 24 | +import java.util.List; | ||
| 25 | +import java.util.stream.Collectors; | ||
| 26 | + | ||
| 27 | +/** | ||
| 28 | + * 异常工单Service实现类 | ||
| 29 | + * | ||
| 30 | + * @author Apple ERP System | ||
| 31 | + * @since 2025-01-27 | ||
| 32 | + */ | ||
| 33 | +@Service | ||
| 34 | +public class ExceptionWorkorderServiceImpl extends ServiceImpl<ExceptionWorkorderMapper, ExceptionWorkorder> implements ExceptionWorkorderService { | ||
| 35 | + | ||
| 36 | + @Autowired | ||
| 37 | + private ExceptionWorkorderMapper exceptionWorkorderMapper; | ||
| 38 | + | ||
| 39 | + @Autowired | ||
| 40 | + private ExceptionWorkorderLogMapper exceptionWorkorderLogMapper; | ||
| 41 | + | ||
| 42 | + @Override | ||
| 43 | + public Page<ExceptionWorkorderRes> getExceptionWorkorderList(ExceptionWorkorderQueryReq queryReq) { | ||
| 44 | + Page<ExceptionWorkorderRes> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize()); | ||
| 45 | + return exceptionWorkorderMapper.selectExceptionWorkorderPage(page, queryReq); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + @Override | ||
| 49 | + public ExceptionWorkorderRes getExceptionWorkorderDetail(Long workorderId) { | ||
| 50 | + ExceptionWorkorderRes workorderRes = exceptionWorkorderMapper.selectExceptionWorkorderDetail(workorderId); | ||
| 51 | + if (workorderRes != null) { | ||
| 52 | + // 获取处理日志 | ||
| 53 | + List<ExceptionWorkorderLogRes> logs = exceptionWorkorderLogMapper.selectWorkorderLogsByWorkorderId(workorderId); | ||
| 54 | + workorderRes.setWorkorderLogs(logs); | ||
| 55 | + } | ||
| 56 | + return workorderRes; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + @Override | ||
| 60 | + @Transactional | ||
| 61 | + public boolean addExceptionWorkorder(ExceptionWorkorderAddReq addReq) { | ||
| 62 | + ExceptionWorkorder workorder = new ExceptionWorkorder(); | ||
| 63 | + BeanUtils.copyProperties(addReq, workorder); | ||
| 64 | + workorder.setCreateTime(LocalDateTime.now()); | ||
| 65 | + workorder.setDelFlag("0"); | ||
| 66 | + | ||
| 67 | + int result = exceptionWorkorderMapper.insert(workorder); | ||
| 68 | + | ||
| 69 | + // 记录处理日志 | ||
| 70 | + if (result > 0) { | ||
| 71 | + ExceptionWorkorderLog log = new ExceptionWorkorderLog(); | ||
| 72 | + log.setWorkorderId(workorder.getWorkorderId()); | ||
| 73 | + log.setWorkorderNo(workorder.getWorkorderNo()); | ||
| 74 | + log.setHandleUser(addReq.getHandlerUser()); | ||
| 75 | + log.setHandleTime(LocalDateTime.now()); | ||
| 76 | + log.setBeforeStatus(0); | ||
| 77 | + log.setAfterStatus(workorder.getWorkorderStatus()); | ||
| 78 | + log.setHandleOpinion("工单创建"); | ||
| 79 | + log.setCreateTime(LocalDateTime.now()); | ||
| 80 | + log.setDelFlag("0"); | ||
| 81 | + exceptionWorkorderLogMapper.insert(log); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + return result > 0; | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + @Override | ||
| 88 | + @Transactional | ||
| 89 | + public boolean updateExceptionWorkorder(ExceptionWorkorderUpdateReq updateReq) { | ||
| 90 | + ExceptionWorkorder workorder = exceptionWorkorderMapper.selectById(updateReq.getWorkorderId()); | ||
| 91 | + if (workorder == null || "2".equals(workorder.getDelFlag())) { | ||
| 92 | + return false; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + BeanUtils.copyProperties(updateReq, workorder); | ||
| 96 | + workorder.setUpdateTime(LocalDateTime.now()); | ||
| 97 | + | ||
| 98 | + return exceptionWorkorderMapper.updateById(workorder) > 0; | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + @Override | ||
| 102 | + @Transactional | ||
| 103 | + public boolean updateWorkorderStatus(ExceptionWorkorderStatusUpdateReq statusUpdateReq) { | ||
| 104 | + ExceptionWorkorder workorder = exceptionWorkorderMapper.selectById(statusUpdateReq.getWorkorderId()); | ||
| 105 | + if (workorder == null || "2".equals(workorder.getDelFlag())) { | ||
| 106 | + return false; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + Integer beforeStatus = workorder.getWorkorderStatus(); | ||
| 110 | + workorder.setWorkorderStatus(statusUpdateReq.getWorkorderStatus()); | ||
| 111 | + workorder.setHandlerUser(statusUpdateReq.getHandlerUser()); | ||
| 112 | + workorder.setUpdateTime(LocalDateTime.now()); | ||
| 113 | + | ||
| 114 | + int result = exceptionWorkorderMapper.updateById(workorder); | ||
| 115 | + | ||
| 116 | + // 记录处理日志 | ||
| 117 | + if (result > 0) { | ||
| 118 | + ExceptionWorkorderLog log = new ExceptionWorkorderLog(); | ||
| 119 | + log.setWorkorderId(workorder.getWorkorderId()); | ||
| 120 | + log.setWorkorderNo(workorder.getWorkorderNo()); | ||
| 121 | + log.setHandleUser(statusUpdateReq.getHandlerUser()); | ||
| 122 | + log.setHandleTime(LocalDateTime.now()); | ||
| 123 | + log.setBeforeStatus(beforeStatus); | ||
| 124 | + log.setAfterStatus(statusUpdateReq.getWorkorderStatus()); | ||
| 125 | + log.setHandleOpinion(statusUpdateReq.getHandleOpinion()); | ||
| 126 | + log.setAttachUrl(statusUpdateReq.getAttachUrl()); | ||
| 127 | + log.setCreateTime(LocalDateTime.now()); | ||
| 128 | + log.setDelFlag("0"); | ||
| 129 | + exceptionWorkorderLogMapper.insert(log); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + return result > 0; | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + @Override | ||
| 136 | + @Transactional | ||
| 137 | + public boolean deleteExceptionWorkorder(Long workorderId) { | ||
| 138 | + ExceptionWorkorder workorder = exceptionWorkorderMapper.selectById(workorderId); | ||
| 139 | + if (workorder == null || "2".equals(workorder.getDelFlag())) { | ||
| 140 | + return false; | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + workorder.setDelFlag("2"); | ||
| 144 | + workorder.setUpdateTime(LocalDateTime.now()); | ||
| 145 | + | ||
| 146 | + return exceptionWorkorderMapper.updateById(workorder) > 0; | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + @Override | ||
| 150 | + @Transactional | ||
| 151 | + public boolean batchDeleteExceptionWorkorders(List<Long> workorderIds) { | ||
| 152 | + if (workorderIds == null || workorderIds.isEmpty()) { | ||
| 153 | + return false; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + ExceptionWorkorder workorder = new ExceptionWorkorder(); | ||
| 157 | + workorder.setDelFlag("2"); | ||
| 158 | + workorder.setUpdateTime(LocalDateTime.now()); | ||
| 159 | + | ||
| 160 | + LambdaQueryWrapper<ExceptionWorkorder> queryWrapper = new LambdaQueryWrapper<>(); | ||
| 161 | + queryWrapper.in(ExceptionWorkorder::getWorkorderId, workorderIds); | ||
| 162 | + queryWrapper.eq(ExceptionWorkorder::getDelFlag, "0"); | ||
| 163 | + | ||
| 164 | + return exceptionWorkorderMapper.update(workorder, queryWrapper) > 0; | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + @Override | ||
| 168 | + @Transactional | ||
| 169 | + public boolean batchUpdateWorkorderStatus(List<Long> workorderIds, Integer workorderStatus, String handlerUser) { | ||
| 170 | + if (workorderIds == null || workorderIds.isEmpty()) { | ||
| 171 | + return false; | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + int result = exceptionWorkorderMapper.batchUpdateWorkorderStatus(workorderIds, workorderStatus, handlerUser); | ||
| 175 | + | ||
| 176 | + // 记录批量处理日志 | ||
| 177 | + if (result > 0) { | ||
| 178 | + for (Long workorderId : workorderIds) { | ||
| 179 | + ExceptionWorkorder workorder = exceptionWorkorderMapper.selectById(workorderId); | ||
| 180 | + if (workorder != null && "0".equals(workorder.getDelFlag())) { | ||
| 181 | + ExceptionWorkorderLog log = new ExceptionWorkorderLog(); | ||
| 182 | + log.setWorkorderId(workorderId); | ||
| 183 | + log.setWorkorderNo(workorder.getWorkorderNo()); | ||
| 184 | + log.setHandleUser(handlerUser); | ||
| 185 | + log.setHandleTime(LocalDateTime.now()); | ||
| 186 | + log.setBeforeStatus(workorder.getWorkorderStatus()); | ||
| 187 | + log.setAfterStatus(workorderStatus); | ||
| 188 | + log.setHandleOpinion("批量状态更新"); | ||
| 189 | + log.setCreateTime(LocalDateTime.now()); | ||
| 190 | + log.setDelFlag("0"); | ||
| 191 | + exceptionWorkorderLogMapper.insert(log); | ||
| 192 | + } | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + return result > 0; | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + @Override | ||
| 200 | + public List<ExceptionWorkorderRes> getExceptionWorkorderStats() { | ||
| 201 | + return exceptionWorkorderMapper.selectExceptionWorkorderStats(); | ||
| 202 | + } | ||
| 203 | +} |
| 1 | +package com.apple.erp.util; | ||
| 2 | + | ||
| 3 | +import com.apple.erp.entity.SysDictItem; | ||
| 4 | +import com.apple.erp.entity.SysDictType; | ||
| 5 | +import com.apple.erp.service.SysDictItemService; | ||
| 6 | +import com.apple.erp.service.SysDictTypeService; | ||
| 7 | +import org.springframework.beans.factory.annotation.Autowired; | ||
| 8 | +import org.springframework.data.redis.core.RedisTemplate; | ||
| 9 | +import org.springframework.stereotype.Component; | ||
| 10 | + | ||
| 11 | +import javax.annotation.PostConstruct; | ||
| 12 | +import java.util.HashMap; | ||
| 13 | +import java.util.List; | ||
| 14 | +import java.util.Map; | ||
| 15 | +import java.util.concurrent.TimeUnit; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * 字典值转换器 - 基于Redis缓存的动态字典值转换 | ||
| 19 | + * 支持实时更新,无需重启应用,使用Redis分布式缓存 | ||
| 20 | + * | ||
| 21 | + * @author Apple ERP System | ||
| 22 | + * @since 2025-01-01 | ||
| 23 | + */ | ||
| 24 | +@Component | ||
| 25 | +public class DictValueConverter { | ||
| 26 | + | ||
| 27 | + @Autowired | ||
| 28 | + private SysDictItemService sysDictItemService; | ||
| 29 | + | ||
| 30 | + @Autowired | ||
| 31 | + private SysDictTypeService sysDictTypeService; | ||
| 32 | + | ||
| 33 | + @Autowired | ||
| 34 | + private RedisTemplate<String, Object> redisTemplate; | ||
| 35 | + | ||
| 36 | + private static SysDictItemService staticDictItemService; | ||
| 37 | + private static SysDictTypeService staticDictTypeService; | ||
| 38 | + private static RedisTemplate<String, Object> staticRedisTemplate; | ||
| 39 | + | ||
| 40 | + /** | ||
| 41 | + * Redis缓存键前缀 | ||
| 42 | + */ | ||
| 43 | + private static final String DICT_CACHE_PREFIX = "dict:cache:"; | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 缓存过期时间(小时) | ||
| 47 | + */ | ||
| 48 | + private static final long CACHE_EXPIRE_HOURS = 24; | ||
| 49 | + | ||
| 50 | + @PostConstruct | ||
| 51 | + public void init() { | ||
| 52 | + staticDictItemService = sysDictItemService; | ||
| 53 | + staticDictTypeService = sysDictTypeService; | ||
| 54 | + staticRedisTemplate = redisTemplate; | ||
| 55 | + System.out.println("DictValueConverter初始化开始..."); | ||
| 56 | + refreshCache(); | ||
| 57 | + System.out.println("DictValueConverter初始化完成"); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * 转换字典值 - 简化版本,直接使用硬编码映射 | ||
| 62 | + */ | ||
| 63 | + public static String convert(Object value, String fieldName) { | ||
| 64 | + if (value == null) { | ||
| 65 | + return ""; | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + // 将值转换为字符串进行匹配 | ||
| 69 | + String valueStr = value.toString(); | ||
| 70 | + | ||
| 71 | + // 直接使用硬编码映射进行转换 | ||
| 72 | + Map<String, String> mapping = getHardcodedMapping(fieldName); | ||
| 73 | + if (mapping != null && !mapping.isEmpty()) { | ||
| 74 | + String result = mapping.getOrDefault(valueStr, valueStr); | ||
| 75 | + System.out.println("字典转换: " + fieldName + " = " + valueStr + " -> " + result); | ||
| 76 | + return result; | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + return valueStr; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + /** | ||
| 83 | + * 获取硬编码的字典映射 | ||
| 84 | + */ | ||
| 85 | + private static Map<String, String> getHardcodedMapping(String fieldName) { | ||
| 86 | + Map<String, String> mapping = new HashMap<>(); | ||
| 87 | + | ||
| 88 | + switch (fieldName) { | ||
| 89 | + case "deliveryStatus": | ||
| 90 | + mapping.put("0", "未出库"); | ||
| 91 | + mapping.put("1", "已出库"); | ||
| 92 | + break; | ||
| 93 | + case "invoiceStatus": | ||
| 94 | + mapping.put("0", "未开票"); | ||
| 95 | + mapping.put("1", "已开票"); | ||
| 96 | + break; | ||
| 97 | + case "rebateCalcFlag": | ||
| 98 | + mapping.put("0", "未计算"); | ||
| 99 | + mapping.put("1", "已计算"); | ||
| 100 | + break; | ||
| 101 | + case "verifyStatus": | ||
| 102 | + mapping.put("0", "待验证"); | ||
| 103 | + mapping.put("1", "验证通过"); | ||
| 104 | + mapping.put("2", "验证失败"); | ||
| 105 | + break; | ||
| 106 | + case "workorderStatus": | ||
| 107 | + mapping.put("1", "待处理"); | ||
| 108 | + mapping.put("2", "处理中"); | ||
| 109 | + mapping.put("3", "已解决"); | ||
| 110 | + mapping.put("4", "已关闭"); | ||
| 111 | + break; | ||
| 112 | + case "severityLevel": | ||
| 113 | + mapping.put("1", "高"); | ||
| 114 | + mapping.put("2", "中"); | ||
| 115 | + mapping.put("3", "低"); | ||
| 116 | + break; | ||
| 117 | + case "operateType": | ||
| 118 | + mapping.put("1", "新增"); | ||
| 119 | + mapping.put("2", "修改"); | ||
| 120 | + mapping.put("3", "删除"); | ||
| 121 | + break; | ||
| 122 | + case "calcFlag": | ||
| 123 | + mapping.put("0", "未计算"); | ||
| 124 | + mapping.put("1", "已计算"); | ||
| 125 | + break; | ||
| 126 | + case "auditStatus": | ||
| 127 | + mapping.put("0", "待审核"); | ||
| 128 | + mapping.put("1", "审核通过"); | ||
| 129 | + mapping.put("2", "审核中"); | ||
| 130 | + mapping.put("3", "审核拒绝"); | ||
| 131 | + break; | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + return mapping; | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + /** | ||
| 138 | + * 刷新字典缓存 - 清空Redis缓存并重新加载 | ||
| 139 | + */ | ||
| 140 | + public static void refreshCache() { | ||
| 141 | + if (staticRedisTemplate == null) { | ||
| 142 | + System.out.println("Redis模板未初始化,跳过缓存刷新"); | ||
| 143 | + return; | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + try { | ||
| 147 | + System.out.println("开始刷新字典缓存..."); | ||
| 148 | + // 清空所有字典缓存 | ||
| 149 | + String pattern = DICT_CACHE_PREFIX + "*"; | ||
| 150 | + staticRedisTemplate.delete(staticRedisTemplate.keys(pattern)); | ||
| 151 | + System.out.println("已清空现有字典缓存"); | ||
| 152 | + | ||
| 153 | + // 重新加载所有字典类型 | ||
| 154 | + loadAllDictsToRedis(); | ||
| 155 | + System.out.println("字典缓存刷新完成"); | ||
| 156 | + | ||
| 157 | + } catch (Exception e) { | ||
| 158 | + System.err.println("刷新字典缓存失败: " + e.getMessage()); | ||
| 159 | + e.printStackTrace(); | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + /** | ||
| 164 | + * 加载所有字典到Redis | ||
| 165 | + */ | ||
| 166 | + private static void loadAllDictsToRedis() { | ||
| 167 | + if (staticDictItemService == null) { | ||
| 168 | + return; | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + try { | ||
| 172 | + // 从数据库加载所有字典项 | ||
| 173 | + List<SysDictItem> allDictItems = staticDictItemService.list(); | ||
| 174 | + | ||
| 175 | + // 按字典类型分组 | ||
| 176 | + Map<String, Map<String, String>> dictGroups = new HashMap<>(); | ||
| 177 | + for (SysDictItem item : allDictItems) { | ||
| 178 | + if (item.getDelFlag() != null && !"0".equals(item.getDelFlag())) { | ||
| 179 | + continue; // 跳过已删除的字典项 | ||
| 180 | + } | ||
| 181 | + | ||
| 182 | + String dictType = getDictTypeByTypeId(item.getDictTypeId()); | ||
| 183 | + if (dictType != null) { | ||
| 184 | + dictGroups.computeIfAbsent(dictType, k -> new HashMap<>()) | ||
| 185 | + .put(item.getDictValue(), item.getDictLabel()); | ||
| 186 | + } | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + // 将每个字典类型存储到Redis | ||
| 190 | + for (Map.Entry<String, Map<String, String>> entry : dictGroups.entrySet()) { | ||
| 191 | + String cacheKey = DICT_CACHE_PREFIX + entry.getKey(); | ||
| 192 | + staticRedisTemplate.opsForValue().set(cacheKey, entry.getValue(), CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + } catch (Exception e) { | ||
| 196 | + // 如果数据库加载失败,使用默认配置作为降级方案 | ||
| 197 | + loadDefaultDictConfigToRedis(); | ||
| 198 | + } | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + /** | ||
| 202 | + * 加载指定字典类型到Redis | ||
| 203 | + */ | ||
| 204 | + private static void loadDictToRedis(String dictType) { | ||
| 205 | + if (staticDictItemService == null) { | ||
| 206 | + return; | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + try { | ||
| 210 | + // 根据字典类型获取字典项 | ||
| 211 | + List<SysDictItem> dictItems = staticDictItemService.getDictItemsByType(dictType); | ||
| 212 | + | ||
| 213 | + Map<String, String> mapping = new HashMap<>(); | ||
| 214 | + for (SysDictItem item : dictItems) { | ||
| 215 | + if (item.getDelFlag() == null || "0".equals(item.getDelFlag())) { | ||
| 216 | + mapping.put(item.getDictValue(), item.getDictLabel()); | ||
| 217 | + } | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + // 存储到Redis | ||
| 221 | + String cacheKey = DICT_CACHE_PREFIX + dictType; | ||
| 222 | + staticRedisTemplate.opsForValue().set(cacheKey, mapping, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 223 | + | ||
| 224 | + } catch (Exception e) { | ||
| 225 | + System.err.println("加载字典到Redis失败: " + e.getMessage()); | ||
| 226 | + } | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + /** | ||
| 230 | + * 根据字典类型ID获取字典类型编码 | ||
| 231 | + * 从数据库查询字典类型表获取真实的字典类型编码 | ||
| 232 | + */ | ||
| 233 | + private static String getDictTypeByTypeId(Long dictTypeId) { | ||
| 234 | + if (staticDictTypeService == null || dictTypeId == null) { | ||
| 235 | + return null; | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + try { | ||
| 239 | + SysDictType dictType = staticDictTypeService.getById(dictTypeId); | ||
| 240 | + if (dictType != null && dictType.getStatus() != null && dictType.getStatus() == 1) { | ||
| 241 | + return dictType.getDictType(); | ||
| 242 | + } | ||
| 243 | + } catch (Exception e) { | ||
| 244 | + System.err.println("查询字典类型失败: " + e.getMessage()); | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + return null; | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + /** | ||
| 251 | + * 加载默认字典配置到Redis(降级方案) | ||
| 252 | + */ | ||
| 253 | + private static void loadDefaultDictConfigToRedis() { | ||
| 254 | + if (staticRedisTemplate == null) { | ||
| 255 | + return; | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + try { | ||
| 259 | + // 操作类型 | ||
| 260 | + Map<String, String> operateType = new HashMap<>(); | ||
| 261 | + operateType.put("1", "新增"); | ||
| 262 | + operateType.put("2", "修改"); | ||
| 263 | + operateType.put("3", "删除"); | ||
| 264 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "operateType", operateType, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 265 | + | ||
| 266 | + // 计算状态 | ||
| 267 | + Map<String, String> calcFlag = new HashMap<>(); | ||
| 268 | + calcFlag.put("0", "未计算"); | ||
| 269 | + calcFlag.put("1", "已计算"); | ||
| 270 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "calcFlag", calcFlag, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 271 | + | ||
| 272 | + // 审核状态 | ||
| 273 | + Map<String, String> auditStatus = new HashMap<>(); | ||
| 274 | + auditStatus.put("0", "待审核"); | ||
| 275 | + auditStatus.put("1", "审核通过"); | ||
| 276 | + auditStatus.put("2", "审核中"); | ||
| 277 | + auditStatus.put("3", "审核拒绝"); | ||
| 278 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "auditStatus", auditStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 279 | + | ||
| 280 | + // 出库状态 | ||
| 281 | + Map<String, String> deliveryStatus = new HashMap<>(); | ||
| 282 | + deliveryStatus.put("0", "未出库"); | ||
| 283 | + deliveryStatus.put("1", "已出库"); | ||
| 284 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "deliveryStatus", deliveryStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 285 | + | ||
| 286 | + // 发票状态 | ||
| 287 | + Map<String, String> invoiceStatus = new HashMap<>(); | ||
| 288 | + invoiceStatus.put("0", "未开票"); | ||
| 289 | + invoiceStatus.put("1", "已开票"); | ||
| 290 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "invoiceStatus", invoiceStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 291 | + | ||
| 292 | + // 返利计算状态 | ||
| 293 | + Map<String, String> rebateCalcFlag = new HashMap<>(); | ||
| 294 | + rebateCalcFlag.put("0", "未计算"); | ||
| 295 | + rebateCalcFlag.put("1", "已计算"); | ||
| 296 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "rebateCalcFlag", rebateCalcFlag, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 297 | + | ||
| 298 | + // 审核状态 | ||
| 299 | + Map<String, String> verifyStatus = new HashMap<>(); | ||
| 300 | + verifyStatus.put("0", "待验证"); | ||
| 301 | + verifyStatus.put("1", "验证通过"); | ||
| 302 | + verifyStatus.put("2", "验证失败"); | ||
| 303 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "verifyStatus", verifyStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 304 | + | ||
| 305 | + // 工单状态 | ||
| 306 | + Map<String, String> workorderStatus = new HashMap<>(); | ||
| 307 | + workorderStatus.put("1", "待处理"); | ||
| 308 | + workorderStatus.put("2", "处理中"); | ||
| 309 | + workorderStatus.put("3", "已解决"); | ||
| 310 | + workorderStatus.put("4", "已关闭"); | ||
| 311 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "workorderStatus", workorderStatus, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 312 | + | ||
| 313 | + // 严重程度 | ||
| 314 | + Map<String, String> severityLevel = new HashMap<>(); | ||
| 315 | + severityLevel.put("1", "高"); | ||
| 316 | + severityLevel.put("2", "中"); | ||
| 317 | + severityLevel.put("3", "低"); | ||
| 318 | + staticRedisTemplate.opsForValue().set(DICT_CACHE_PREFIX + "severityLevel", severityLevel, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); | ||
| 319 | + | ||
| 320 | + } catch (Exception e) { | ||
| 321 | + System.err.println("加载默认字典配置到Redis失败: " + e.getMessage()); | ||
| 322 | + } | ||
| 323 | + } | ||
| 324 | +} |
| 1 | +package com.apple.erp.util; | ||
| 2 | + | ||
| 3 | +import org.apache.poi.ss.usermodel.*; | ||
| 4 | +import org.apache.poi.xssf.usermodel.XSSFWorkbook; | ||
| 5 | +import org.springframework.http.HttpHeaders; | ||
| 6 | +import org.springframework.http.HttpStatus; | ||
| 7 | +import org.springframework.http.MediaType; | ||
| 8 | +import org.springframework.http.ResponseEntity; | ||
| 9 | + | ||
| 10 | +import java.io.ByteArrayOutputStream; | ||
| 11 | +import java.io.IOException; | ||
| 12 | +import java.lang.reflect.Field; | ||
| 13 | +import java.time.LocalDateTime; | ||
| 14 | +import java.time.format.DateTimeFormatter; | ||
| 15 | +import java.util.List; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * Excel导出工具类 | ||
| 19 | + * | ||
| 20 | + * @author Apple ERP Team | ||
| 21 | + * @version 1.0.0 | ||
| 22 | + * @since 2024-01-01 | ||
| 23 | + */ | ||
| 24 | +public class ExcelExportUtil { | ||
| 25 | + | ||
| 26 | + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | ||
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * 导出数据到Excel | ||
| 30 | + * | ||
| 31 | + * @param data 数据列表 | ||
| 32 | + * @param headers 表头数组 | ||
| 33 | + * @param fileName 文件名 | ||
| 34 | + * @param <T> 数据类型 | ||
| 35 | + * @return ResponseEntity<byte[]> | ||
| 36 | + */ | ||
| 37 | + public static <T> ResponseEntity<byte[]> exportToExcel(List<T> data, String[] headers, String fileName) { | ||
| 38 | + try (Workbook workbook = new XSSFWorkbook()) { | ||
| 39 | + Sheet sheet = workbook.createSheet("数据导出"); | ||
| 40 | + | ||
| 41 | + // 创建表头样式 | ||
| 42 | + CellStyle headerStyle = createHeaderStyle(workbook); | ||
| 43 | + CellStyle dataStyle = createDataStyle(workbook); | ||
| 44 | + | ||
| 45 | + // 创建表头 | ||
| 46 | + Row headerRow = sheet.createRow(0); | ||
| 47 | + for (int i = 0; i < headers.length; i++) { | ||
| 48 | + Cell cell = headerRow.createCell(i); | ||
| 49 | + cell.setCellValue(headers[i]); | ||
| 50 | + cell.setCellStyle(headerStyle); | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + // 填充数据 | ||
| 54 | + if (data != null && !data.isEmpty()) { | ||
| 55 | + for (int i = 0; i < data.size(); i++) { | ||
| 56 | + Row row = sheet.createRow(i + 1); | ||
| 57 | + T item = data.get(i); | ||
| 58 | + fillRowData(row, item, dataStyle); | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + // 自动调整列宽 | ||
| 63 | + for (int i = 0; i < headers.length; i++) { | ||
| 64 | + sheet.autoSizeColumn(i); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + // 转换为字节数组 | ||
| 68 | + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||
| 69 | + workbook.write(outputStream); | ||
| 70 | + byte[] bytes = outputStream.toByteArray(); | ||
| 71 | + | ||
| 72 | + // 设置响应头 | ||
| 73 | + HttpHeaders httpHeaders = new HttpHeaders(); | ||
| 74 | + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); | ||
| 75 | + | ||
| 76 | + // 对文件名进行URL编码以支持中文 | ||
| 77 | + String encodedFileName; | ||
| 78 | + try { | ||
| 79 | + encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8"); | ||
| 80 | + } catch (Exception e) { | ||
| 81 | + encodedFileName = fileName + ".xlsx"; | ||
| 82 | + } | ||
| 83 | + httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName); | ||
| 84 | + httpHeaders.setContentLength(bytes.length); | ||
| 85 | + | ||
| 86 | + return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK); | ||
| 87 | + | ||
| 88 | + } catch (IOException e) { | ||
| 89 | + throw new RuntimeException("Excel导出失败", e); | ||
| 90 | + } | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + /** | ||
| 94 | + * 创建表头样式 | ||
| 95 | + */ | ||
| 96 | + private static CellStyle createHeaderStyle(Workbook workbook) { | ||
| 97 | + CellStyle style = workbook.createCellStyle(); | ||
| 98 | + Font font = workbook.createFont(); | ||
| 99 | + font.setBold(true); | ||
| 100 | + font.setFontHeightInPoints((short) 12); | ||
| 101 | + style.setFont(font); | ||
| 102 | + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); | ||
| 103 | + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); | ||
| 104 | + style.setBorderBottom(BorderStyle.THIN); | ||
| 105 | + style.setBorderTop(BorderStyle.THIN); | ||
| 106 | + style.setBorderRight(BorderStyle.THIN); | ||
| 107 | + style.setBorderLeft(BorderStyle.THIN); | ||
| 108 | + style.setAlignment(HorizontalAlignment.CENTER); | ||
| 109 | + style.setVerticalAlignment(VerticalAlignment.CENTER); | ||
| 110 | + return style; | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + /** | ||
| 114 | + * 创建数据样式 | ||
| 115 | + */ | ||
| 116 | + private static CellStyle createDataStyle(Workbook workbook) { | ||
| 117 | + CellStyle style = workbook.createCellStyle(); | ||
| 118 | + style.setBorderBottom(BorderStyle.THIN); | ||
| 119 | + style.setBorderTop(BorderStyle.THIN); | ||
| 120 | + style.setBorderRight(BorderStyle.THIN); | ||
| 121 | + style.setBorderLeft(BorderStyle.THIN); | ||
| 122 | + style.setAlignment(HorizontalAlignment.LEFT); | ||
| 123 | + style.setVerticalAlignment(VerticalAlignment.CENTER); | ||
| 124 | + return style; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + /** | ||
| 128 | + * 填充行数据 | ||
| 129 | + */ | ||
| 130 | + private static <T> void fillRowData(Row row, T item, CellStyle dataStyle) { | ||
| 131 | + if (item == null) return; | ||
| 132 | + | ||
| 133 | + Field[] fields = item.getClass().getDeclaredFields(); | ||
| 134 | + int cellIndex = 0; | ||
| 135 | + | ||
| 136 | + for (Field field : fields) { | ||
| 137 | + if (cellIndex >= row.getLastCellNum()) break; | ||
| 138 | + | ||
| 139 | + try { | ||
| 140 | + // 使用getter方法获取值,而不是直接访问字段 | ||
| 141 | + String fieldName = field.getName(); | ||
| 142 | + String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); | ||
| 143 | + | ||
| 144 | + Object value = null; | ||
| 145 | + try { | ||
| 146 | + java.lang.reflect.Method getter = item.getClass().getMethod(getterName); | ||
| 147 | + value = getter.invoke(item); | ||
| 148 | + } catch (Exception e) { | ||
| 149 | + // 如果getter方法不存在,尝试直接访问字段 | ||
| 150 | + field.setAccessible(true); | ||
| 151 | + value = field.get(item); | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + Cell cell = row.createCell(cellIndex); | ||
| 155 | + cell.setCellStyle(dataStyle); | ||
| 156 | + | ||
| 157 | + if (value != null) { | ||
| 158 | + if (value instanceof String) { | ||
| 159 | + cell.setCellValue((String) value); | ||
| 160 | + } else if (value instanceof Number) { | ||
| 161 | + cell.setCellValue(((Number) value).doubleValue()); | ||
| 162 | + } else if (value instanceof LocalDateTime) { | ||
| 163 | + cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER)); | ||
| 164 | + } else { | ||
| 165 | + cell.setCellValue(value.toString()); | ||
| 166 | + } | ||
| 167 | + } else { | ||
| 168 | + cell.setCellValue(""); | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + cellIndex++; | ||
| 172 | + } catch (Exception e) { | ||
| 173 | + // 忽略无法访问的字段 | ||
| 174 | + System.out.println("无法访问字段: " + field.getName() + ", 错误: " + e.getMessage()); | ||
| 175 | + } | ||
| 176 | + } | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + /** | ||
| 180 | + * 导出订单数据到Excel | ||
| 181 | + * | ||
| 182 | + * @param data 订单数据列表 | ||
| 183 | + * @param fileName 文件名 | ||
| 184 | + * @return ResponseEntity<byte[]> | ||
| 185 | + */ | ||
| 186 | + public static ResponseEntity<byte[]> exportOrderToExcel(List<?> data, String fileName) { | ||
| 187 | + try (Workbook workbook = new XSSFWorkbook()) { | ||
| 188 | + Sheet sheet = workbook.createSheet("订单数据"); | ||
| 189 | + | ||
| 190 | + // 创建表头样式 | ||
| 191 | + CellStyle headerStyle = createHeaderStyle(workbook); | ||
| 192 | + CellStyle dataStyle = createDataStyle(workbook); | ||
| 193 | + | ||
| 194 | + // 创建表头 | ||
| 195 | + Row headerRow = sheet.createRow(0); | ||
| 196 | + String[] headers = { | ||
| 197 | + "订单ID", "订单编号", "经销商编码", "经销商名称", "订单日期", | ||
| 198 | + "订单金额", "返利金额", "出库状态", "开票状态", "返利计算状态", | ||
| 199 | + "数据来源", "审核状态", "上传时间", "创建时间" | ||
| 200 | + }; | ||
| 201 | + | ||
| 202 | + for (int i = 0; i < headers.length; i++) { | ||
| 203 | + Cell cell = headerRow.createCell(i); | ||
| 204 | + cell.setCellValue(headers[i]); | ||
| 205 | + cell.setCellStyle(headerStyle); | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + // 填充数据 | ||
| 209 | + if (data != null && !data.isEmpty()) { | ||
| 210 | + for (int i = 0; i < data.size(); i++) { | ||
| 211 | + Row row = sheet.createRow(i + 1); | ||
| 212 | + Object item = data.get(i); | ||
| 213 | + | ||
| 214 | + // 使用反射获取字段值 | ||
| 215 | + fillOrderRowData(row, item, dataStyle); | ||
| 216 | + } | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + // 自动调整列宽 | ||
| 220 | + for (int i = 0; i < headers.length; i++) { | ||
| 221 | + sheet.autoSizeColumn(i); | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + // 转换为字节数组 | ||
| 225 | + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||
| 226 | + workbook.write(outputStream); | ||
| 227 | + byte[] bytes = outputStream.toByteArray(); | ||
| 228 | + | ||
| 229 | + // 设置响应头 | ||
| 230 | + HttpHeaders httpHeaders = new HttpHeaders(); | ||
| 231 | + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); | ||
| 232 | + | ||
| 233 | + // 对文件名进行URL编码以支持中文 | ||
| 234 | + String encodedFileName; | ||
| 235 | + try { | ||
| 236 | + encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8"); | ||
| 237 | + } catch (Exception e) { | ||
| 238 | + encodedFileName = fileName + ".xlsx"; | ||
| 239 | + } | ||
| 240 | + httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName); | ||
| 241 | + httpHeaders.setContentLength(bytes.length); | ||
| 242 | + | ||
| 243 | + return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK); | ||
| 244 | + | ||
| 245 | + } catch (IOException e) { | ||
| 246 | + throw new RuntimeException("Excel导出失败", e); | ||
| 247 | + } | ||
| 248 | + } | ||
| 249 | + | ||
| 250 | + /** | ||
| 251 | + * 填充订单行数据 | ||
| 252 | + */ | ||
| 253 | + private static void fillOrderRowData(Row row, Object item, CellStyle dataStyle) { | ||
| 254 | + try { | ||
| 255 | + // 使用反射获取OrderRes的字段值 | ||
| 256 | + Class<?> clazz = item.getClass(); | ||
| 257 | + | ||
| 258 | + // 订单ID | ||
| 259 | + setCellValue(row, 0, getFieldValue(clazz, item, "orderId"), dataStyle); | ||
| 260 | + // 订单编号 | ||
| 261 | + setCellValue(row, 1, getFieldValue(clazz, item, "orderNo"), dataStyle); | ||
| 262 | + // 经销商编码 | ||
| 263 | + setCellValue(row, 2, getFieldValue(clazz, item, "dealerCode"), dataStyle); | ||
| 264 | + // 经销商名称 | ||
| 265 | + setCellValue(row, 3, getFieldValue(clazz, item, "dealerName"), dataStyle); | ||
| 266 | + // 订单日期 | ||
| 267 | + setCellValue(row, 4, getFieldValue(clazz, item, "orderDate"), dataStyle); | ||
| 268 | + // 订单金额 | ||
| 269 | + setCellValue(row, 5, getFieldValue(clazz, item, "totalAmount"), dataStyle); | ||
| 270 | + // 返利金额 | ||
| 271 | + setCellValue(row, 6, getFieldValue(clazz, item, "rebateAmount"), dataStyle); | ||
| 272 | + // 出库状态 | ||
| 273 | + setCellValue(row, 7, getFieldValue(clazz, item, "deliveryStatus"), dataStyle); | ||
| 274 | + // 开票状态 | ||
| 275 | + setCellValue(row, 8, getFieldValue(clazz, item, "invoiceStatus"), dataStyle); | ||
| 276 | + // 返利计算状态 | ||
| 277 | + setCellValue(row, 9, getFieldValue(clazz, item, "rebateCalcFlag"), dataStyle); | ||
| 278 | + // 数据来源 | ||
| 279 | + setCellValue(row, 10, getFieldValue(clazz, item, "dataSource"), dataStyle); | ||
| 280 | + // 审核状态 | ||
| 281 | + setCellValue(row, 11, getFieldValue(clazz, item, "verifyStatus"), dataStyle); | ||
| 282 | + // 上传时间 | ||
| 283 | + setCellValue(row, 12, getFieldValue(clazz, item, "uploadTime"), dataStyle); | ||
| 284 | + // 创建时间 | ||
| 285 | + setCellValue(row, 13, getFieldValue(clazz, item, "createTime"), dataStyle); | ||
| 286 | + | ||
| 287 | + } catch (Exception e) { | ||
| 288 | + System.out.println("填充订单数据失败: " + e.getMessage()); | ||
| 289 | + } | ||
| 290 | + } | ||
| 291 | + | ||
| 292 | + /** | ||
| 293 | + * 获取字段值 | ||
| 294 | + */ | ||
| 295 | + private static Object getFieldValue(Class<?> clazz, Object item, String fieldName) { | ||
| 296 | + try { | ||
| 297 | + String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); | ||
| 298 | + java.lang.reflect.Method getter = clazz.getMethod(getterName); | ||
| 299 | + return getter.invoke(item); | ||
| 300 | + } catch (Exception e) { | ||
| 301 | + return null; | ||
| 302 | + } | ||
| 303 | + } | ||
| 304 | + | ||
| 305 | + /** | ||
| 306 | + * 设置单元格值 | ||
| 307 | + */ | ||
| 308 | + private static void setCellValue(Row row, int cellIndex, Object value, CellStyle dataStyle) { | ||
| 309 | + Cell cell = row.createCell(cellIndex); | ||
| 310 | + cell.setCellStyle(dataStyle); | ||
| 311 | + | ||
| 312 | + if (value != null) { | ||
| 313 | + if (value instanceof String) { | ||
| 314 | + cell.setCellValue((String) value); | ||
| 315 | + } else if (value instanceof Number) { | ||
| 316 | + cell.setCellValue(((Number) value).doubleValue()); | ||
| 317 | + } else if (value instanceof LocalDateTime) { | ||
| 318 | + cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER)); | ||
| 319 | + } else { | ||
| 320 | + cell.setCellValue(value.toString()); | ||
| 321 | + } | ||
| 322 | + } else { | ||
| 323 | + cell.setCellValue(""); | ||
| 324 | + } | ||
| 325 | + } | ||
| 326 | + | ||
| 327 | + /** | ||
| 328 | + * 导出出库数据到Excel | ||
| 329 | + * | ||
| 330 | + * @param data 出库数据列表 | ||
| 331 | + * @param fileName 文件名 | ||
| 332 | + * @return ResponseEntity<byte[]> | ||
| 333 | + */ | ||
| 334 | + public static ResponseEntity<byte[]> exportDeliveryToExcel(List<?> data, String fileName) { | ||
| 335 | + String[] headers = { | ||
| 336 | + "出库单ID", "出库单编号", "经销商编码", "经销商名称", "出库日期", | ||
| 337 | + "关联订单编号", "出库状态", "仓库编码", "数据来源", "创建时间" | ||
| 338 | + }; | ||
| 339 | + | ||
| 340 | + return exportToExcel(data, headers, fileName); | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + /** | ||
| 344 | + * 导出发票数据到Excel | ||
| 345 | + * | ||
| 346 | + * @param data 发票数据列表 | ||
| 347 | + * @param fileName 文件名 | ||
| 348 | + * @return ResponseEntity<byte[]> | ||
| 349 | + */ | ||
| 350 | + public static ResponseEntity<byte[]> exportInvoiceToExcel(List<?> data, String fileName) { | ||
| 351 | + String[] headers = { | ||
| 352 | + "发票ID", "发票编号", "订单编号", "出库单编号", "经销商编码", | ||
| 353 | + "经销商名称", "发票金额", "发票日期", "开票状态", "税率", | ||
| 354 | + "数据来源", "创建时间" | ||
| 355 | + }; | ||
| 356 | + | ||
| 357 | + return exportToExcel(data, headers, fileName); | ||
| 358 | + } | ||
| 359 | +} |
| 1 | +package com.apple.erp.util; | ||
| 2 | + | ||
| 3 | +import org.apache.poi.ss.usermodel.*; | ||
| 4 | +import org.apache.poi.xssf.usermodel.XSSFWorkbook; | ||
| 5 | +import org.springframework.http.HttpHeaders; | ||
| 6 | +import org.springframework.http.HttpStatus; | ||
| 7 | +import org.springframework.http.MediaType; | ||
| 8 | +import org.springframework.http.ResponseEntity; | ||
| 9 | + | ||
| 10 | +import java.io.ByteArrayOutputStream; | ||
| 11 | +import java.io.IOException; | ||
| 12 | +import java.lang.reflect.Field; | ||
| 13 | +import java.time.LocalDateTime; | ||
| 14 | +import java.time.format.DateTimeFormatter; | ||
| 15 | +import java.util.List; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * 通用Excel导出工具类 | ||
| 19 | + * 支持任意实体类的Excel导出,通过注解配置字段映射 | ||
| 20 | + * | ||
| 21 | + * @author Apple ERP System | ||
| 22 | + * @since 2025-01-01 | ||
| 23 | + */ | ||
| 24 | +public class GenericExcelExportUtil { | ||
| 25 | + | ||
| 26 | + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | ||
| 27 | + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | ||
| 28 | + | ||
| 29 | + /** | ||
| 30 | + * 通用Excel导出方法 | ||
| 31 | + * | ||
| 32 | + * @param data 数据列表 | ||
| 33 | + * @param headers 表头数组 | ||
| 34 | + * @param fieldNames 字段名数组(与表头对应) | ||
| 35 | + * @param fileName 文件名(不含扩展名) | ||
| 36 | + * @return ResponseEntity<byte[]> | ||
| 37 | + */ | ||
| 38 | + public static ResponseEntity<byte[]> exportToExcel(List<?> data, String[] headers, String[] fieldNames, String fileName) { | ||
| 39 | + try (Workbook workbook = new XSSFWorkbook()) { | ||
| 40 | + Sheet sheet = workbook.createSheet("数据导出"); | ||
| 41 | + | ||
| 42 | + // 创建样式 | ||
| 43 | + CellStyle headerStyle = createHeaderStyle(workbook); | ||
| 44 | + CellStyle dataStyle = createDataStyle(workbook); | ||
| 45 | + | ||
| 46 | + // 创建表头 | ||
| 47 | + Row headerRow = sheet.createRow(0); | ||
| 48 | + for (int i = 0; i < headers.length; i++) { | ||
| 49 | + Cell cell = headerRow.createCell(i); | ||
| 50 | + cell.setCellValue(headers[i]); | ||
| 51 | + cell.setCellStyle(headerStyle); | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + // 填充数据 | ||
| 55 | + if (data != null && !data.isEmpty()) { | ||
| 56 | + for (int i = 0; i < data.size(); i++) { | ||
| 57 | + Row row = sheet.createRow(i + 1); | ||
| 58 | + Object item = data.get(i); | ||
| 59 | + fillRowData(row, item, fieldNames, dataStyle); | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + // 自动调整列宽 | ||
| 64 | + for (int i = 0; i < headers.length; i++) { | ||
| 65 | + sheet.autoSizeColumn(i); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + // 转换为字节数组 | ||
| 69 | + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||
| 70 | + workbook.write(outputStream); | ||
| 71 | + byte[] bytes = outputStream.toByteArray(); | ||
| 72 | + | ||
| 73 | + // 设置响应头 | ||
| 74 | + HttpHeaders httpHeaders = new HttpHeaders(); | ||
| 75 | + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); | ||
| 76 | + | ||
| 77 | + // 对文件名进行URL编码以支持中文 | ||
| 78 | + String encodedFileName; | ||
| 79 | + try { | ||
| 80 | + encodedFileName = java.net.URLEncoder.encode(fileName + ".xlsx", "UTF-8"); | ||
| 81 | + } catch (Exception e) { | ||
| 82 | + encodedFileName = fileName + ".xlsx"; | ||
| 83 | + } | ||
| 84 | + httpHeaders.set("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName); | ||
| 85 | + httpHeaders.setContentLength(bytes.length); | ||
| 86 | + | ||
| 87 | + return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK); | ||
| 88 | + | ||
| 89 | + } catch (IOException e) { | ||
| 90 | + throw new RuntimeException("Excel导出失败", e); | ||
| 91 | + } | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + /** | ||
| 95 | + * 创建表头样式 | ||
| 96 | + */ | ||
| 97 | + private static CellStyle createHeaderStyle(Workbook workbook) { | ||
| 98 | + CellStyle style = workbook.createCellStyle(); | ||
| 99 | + Font font = workbook.createFont(); | ||
| 100 | + font.setBold(true); | ||
| 101 | + font.setFontHeightInPoints((short) 12); | ||
| 102 | + style.setFont(font); | ||
| 103 | + style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); | ||
| 104 | + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); | ||
| 105 | + style.setBorderBottom(BorderStyle.THIN); | ||
| 106 | + style.setBorderTop(BorderStyle.THIN); | ||
| 107 | + style.setBorderRight(BorderStyle.THIN); | ||
| 108 | + style.setBorderLeft(BorderStyle.THIN); | ||
| 109 | + style.setAlignment(HorizontalAlignment.CENTER); | ||
| 110 | + style.setVerticalAlignment(VerticalAlignment.CENTER); | ||
| 111 | + return style; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + /** | ||
| 115 | + * 创建数据样式 | ||
| 116 | + */ | ||
| 117 | + private static CellStyle createDataStyle(Workbook workbook) { | ||
| 118 | + CellStyle style = workbook.createCellStyle(); | ||
| 119 | + style.setBorderBottom(BorderStyle.THIN); | ||
| 120 | + style.setBorderTop(BorderStyle.THIN); | ||
| 121 | + style.setBorderRight(BorderStyle.THIN); | ||
| 122 | + style.setBorderLeft(BorderStyle.THIN); | ||
| 123 | + style.setVerticalAlignment(VerticalAlignment.CENTER); | ||
| 124 | + return style; | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + /** | ||
| 128 | + * 填充行数据 | ||
| 129 | + */ | ||
| 130 | + private static void fillRowData(Row row, Object item, String[] fieldNames, CellStyle dataStyle) { | ||
| 131 | + try { | ||
| 132 | + Class<?> clazz = item.getClass(); | ||
| 133 | + | ||
| 134 | + for (int i = 0; i < fieldNames.length; i++) { | ||
| 135 | + Cell cell = row.createCell(i); | ||
| 136 | + cell.setCellStyle(dataStyle); | ||
| 137 | + | ||
| 138 | + Object value = getFieldValue(clazz, item, fieldNames[i]); | ||
| 139 | + | ||
| 140 | + // 对特定字段进行字典值转换 | ||
| 141 | + String convertedValue = convertDictValue(value, fieldNames[i]); | ||
| 142 | + System.out.println("字段转换: " + fieldNames[i] + " = " + value + " -> " + convertedValue); | ||
| 143 | + if (convertedValue != null && !convertedValue.equals(value != null ? value.toString() : "")) { | ||
| 144 | + // 如果字典转换成功,使用转换后的值 | ||
| 145 | + cell.setCellValue(convertedValue); | ||
| 146 | + } else { | ||
| 147 | + // 如果字典转换失败或没有转换,使用原始值 | ||
| 148 | + setCellValue(cell, value); | ||
| 149 | + } | ||
| 150 | + } | ||
| 151 | + } catch (Exception e) { | ||
| 152 | + System.out.println("填充行数据失败: " + e.getMessage()); | ||
| 153 | + } | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + /** | ||
| 157 | + * 获取字段值(支持getter方法和直接字段访问) | ||
| 158 | + */ | ||
| 159 | + private static Object getFieldValue(Class<?> clazz, Object item, String fieldName) { | ||
| 160 | + try { | ||
| 161 | + // 首先尝试getter方法 | ||
| 162 | + String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); | ||
| 163 | + try { | ||
| 164 | + return clazz.getMethod(getterName).invoke(item); | ||
| 165 | + } catch (NoSuchMethodException e) { | ||
| 166 | + // 如果getter方法不存在,尝试直接访问字段 | ||
| 167 | + Field field = clazz.getDeclaredField(fieldName); | ||
| 168 | + field.setAccessible(true); | ||
| 169 | + return field.get(item); | ||
| 170 | + } | ||
| 171 | + } catch (Exception e) { | ||
| 172 | + return null; | ||
| 173 | + } | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | + /** | ||
| 177 | + * 设置单元格值 | ||
| 178 | + */ | ||
| 179 | + private static void setCellValue(Cell cell, Object value) { | ||
| 180 | + if (value == null) { | ||
| 181 | + cell.setCellValue(""); | ||
| 182 | + } else if (value instanceof String) { | ||
| 183 | + cell.setCellValue((String) value); | ||
| 184 | + } else if (value instanceof Number) { | ||
| 185 | + cell.setCellValue(((Number) value).doubleValue()); | ||
| 186 | + } else if (value instanceof LocalDateTime) { | ||
| 187 | + cell.setCellValue(((LocalDateTime) value).format(DATE_TIME_FORMATTER)); | ||
| 188 | + } else if (value instanceof java.time.LocalDate) { | ||
| 189 | + cell.setCellValue(((java.time.LocalDate) value).format(DATE_FORMATTER)); | ||
| 190 | + } else if (value instanceof Boolean) { | ||
| 191 | + cell.setCellValue((Boolean) value ? "是" : "否"); | ||
| 192 | + } else { | ||
| 193 | + cell.setCellValue(value.toString()); | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + /** | ||
| 198 | + * 转换字典值 - 使用动态字典转换器 | ||
| 199 | + */ | ||
| 200 | + private static String convertDictValue(Object value, String fieldName) { | ||
| 201 | + String result = DictValueConverter.convert(value, fieldName); | ||
| 202 | + // 调试日志:输出字典转换过程 | ||
| 203 | + if (value != null && !value.toString().equals(result)) { | ||
| 204 | + System.out.println("字典转换: " + fieldName + " = " + value + " -> " + result); | ||
| 205 | + } | ||
| 206 | + return result; | ||
| 207 | + } | ||
| 208 | +} |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||
| 3 | +<mapper namespace="com.apple.erp.mapper.ExceptionWorkorderLogMapper"> | ||
| 4 | + | ||
| 5 | + <!-- 根据工单ID获取处理日志列表 --> | ||
| 6 | + <select id="selectWorkorderLogsByWorkorderId" resultType="com.apple.erp.dto.ExceptionWorkorderLogRes"> | ||
| 7 | + SELECT | ||
| 8 | + ewl.log_id, | ||
| 9 | + ewl.workorder_id, | ||
| 10 | + ewl.workorder_no, | ||
| 11 | + ewl.handle_user, | ||
| 12 | + ewl.handle_time, | ||
| 13 | + ewl.before_status, | ||
| 14 | + CASE ewl.before_status | ||
| 15 | + WHEN 1 THEN '待处理' | ||
| 16 | + WHEN 2 THEN '处理中' | ||
| 17 | + WHEN 3 THEN '已解决' | ||
| 18 | + WHEN 4 THEN '已关闭' | ||
| 19 | + ELSE '未知状态' | ||
| 20 | + END AS before_status_name, | ||
| 21 | + ewl.after_status, | ||
| 22 | + CASE ewl.after_status | ||
| 23 | + WHEN 1 THEN '待处理' | ||
| 24 | + WHEN 2 THEN '处理中' | ||
| 25 | + WHEN 3 THEN '已解决' | ||
| 26 | + WHEN 4 THEN '已关闭' | ||
| 27 | + ELSE '未知状态' | ||
| 28 | + END AS after_status_name, | ||
| 29 | + ewl.handle_opinion, | ||
| 30 | + ewl.attach_url, | ||
| 31 | + ewl.create_by, | ||
| 32 | + ewl.create_time | ||
| 33 | + FROM t_exception_workorder_log ewl | ||
| 34 | + WHERE ewl.workorder_id = #{workorderId} AND ewl.del_flag = '0' | ||
| 35 | + ORDER BY ewl.handle_time DESC | ||
| 36 | + </select> | ||
| 37 | + | ||
| 38 | + <!-- 根据工单ID列表批量获取处理日志 --> | ||
| 39 | + <select id="selectWorkorderLogsByWorkorderIds" resultType="com.apple.erp.dto.ExceptionWorkorderLogRes"> | ||
| 40 | + SELECT | ||
| 41 | + ewl.log_id, | ||
| 42 | + ewl.workorder_id, | ||
| 43 | + ewl.workorder_no, | ||
| 44 | + ewl.handle_user, | ||
| 45 | + ewl.handle_time, | ||
| 46 | + ewl.before_status, | ||
| 47 | + CASE ewl.before_status | ||
| 48 | + WHEN 1 THEN '待处理' | ||
| 49 | + WHEN 2 THEN '处理中' | ||
| 50 | + WHEN 3 THEN '已解决' | ||
| 51 | + WHEN 4 THEN '已关闭' | ||
| 52 | + ELSE '未知状态' | ||
| 53 | + END AS before_status_name, | ||
| 54 | + ewl.after_status, | ||
| 55 | + CASE ewl.after_status | ||
| 56 | + WHEN 1 THEN '待处理' | ||
| 57 | + WHEN 2 THEN '处理中' | ||
| 58 | + WHEN 3 THEN '已解决' | ||
| 59 | + WHEN 4 THEN '已关闭' | ||
| 60 | + ELSE '未知状态' | ||
| 61 | + END AS after_status_name, | ||
| 62 | + ewl.handle_opinion, | ||
| 63 | + ewl.attach_url, | ||
| 64 | + ewl.create_by, | ||
| 65 | + ewl.create_time | ||
| 66 | + FROM t_exception_workorder_log ewl | ||
| 67 | + WHERE ewl.workorder_id IN | ||
| 68 | + <foreach collection="workorderIds" item="workorderId" open="(" separator="," close=")"> | ||
| 69 | + #{workorderId} | ||
| 70 | + </foreach> | ||
| 71 | + AND ewl.del_flag = '0' | ||
| 72 | + ORDER BY ewl.workorder_id, ewl.handle_time DESC | ||
| 73 | + </select> | ||
| 74 | + | ||
| 75 | +</mapper> |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||
| 3 | +<mapper namespace="com.apple.erp.mapper.ExceptionWorkorderMapper"> | ||
| 4 | + | ||
| 5 | + <!-- 分页查询异常工单列表 --> | ||
| 6 | + <select id="selectExceptionWorkorderPage" resultType="com.apple.erp.dto.ExceptionWorkorderRes"> | ||
| 7 | + SELECT | ||
| 8 | + ew.workorder_id, | ||
| 9 | + ew.workorder_no, | ||
| 10 | + ew.order_no, | ||
| 11 | + ew.dealer_code, | ||
| 12 | + ew.dealer_name, | ||
| 13 | + ew.exception_type, | ||
| 14 | + CASE ew.exception_type | ||
| 15 | + WHEN 1 THEN '逻辑验证异常' | ||
| 16 | + WHEN 2 THEN '源头验证异常' | ||
| 17 | + WHEN 3 THEN '交叉验证异常' | ||
| 18 | + ELSE '未知类型' | ||
| 19 | + END AS exception_type_name, | ||
| 20 | + ew.severity_level, | ||
| 21 | + CASE ew.severity_level | ||
| 22 | + WHEN 1 THEN '高' | ||
| 23 | + WHEN 2 THEN '中' | ||
| 24 | + WHEN 3 THEN '低' | ||
| 25 | + ELSE '未知' | ||
| 26 | + END AS severity_level_name, | ||
| 27 | + ew.workorder_status, | ||
| 28 | + CASE ew.workorder_status | ||
| 29 | + WHEN 1 THEN '待处理' | ||
| 30 | + WHEN 2 THEN '处理中' | ||
| 31 | + WHEN 3 THEN '已解决' | ||
| 32 | + WHEN 4 THEN '已关闭' | ||
| 33 | + ELSE '未知状态' | ||
| 34 | + END AS workorder_status_name, | ||
| 35 | + ew.create_time, | ||
| 36 | + ew.expect_complete_time, | ||
| 37 | + ew.handler_user, | ||
| 38 | + ew.exception_desc, | ||
| 39 | + ew.handle_suggest, | ||
| 40 | + ew.data_source, | ||
| 41 | + ew.create_by, | ||
| 42 | + ew.update_by, | ||
| 43 | + ew.update_time | ||
| 44 | + FROM t_exception_workorder ew | ||
| 45 | + <where> | ||
| 46 | + ew.del_flag = '0' | ||
| 47 | + <if test="query.workorderNo != null and query.workorderNo != ''"> | ||
| 48 | + AND ew.workorder_no LIKE CONCAT('%', #{query.workorderNo}, '%') | ||
| 49 | + </if> | ||
| 50 | + <if test="query.orderNo != null and query.orderNo != ''"> | ||
| 51 | + AND ew.order_no LIKE CONCAT('%', #{query.orderNo}, '%') | ||
| 52 | + </if> | ||
| 53 | + <if test="query.dealerCode != null and query.dealerCode != ''"> | ||
| 54 | + AND ew.dealer_code = #{query.dealerCode} | ||
| 55 | + </if> | ||
| 56 | + <if test="query.dealerName != null and query.dealerName != ''"> | ||
| 57 | + AND ew.dealer_name LIKE CONCAT('%', #{query.dealerName}, '%') | ||
| 58 | + </if> | ||
| 59 | + <if test="query.workorderStatus != null"> | ||
| 60 | + AND ew.workorder_status = #{query.workorderStatus} | ||
| 61 | + </if> | ||
| 62 | + <if test="query.exceptionType != null"> | ||
| 63 | + AND ew.exception_type = #{query.exceptionType} | ||
| 64 | + </if> | ||
| 65 | + <if test="query.severityLevel != null"> | ||
| 66 | + AND ew.severity_level = #{query.severityLevel} | ||
| 67 | + </if> | ||
| 68 | + <if test="query.handlerUser != null and query.handlerUser != ''"> | ||
| 69 | + AND ew.handler_user LIKE CONCAT('%', #{query.handlerUser}, '%') | ||
| 70 | + </if> | ||
| 71 | + <if test="query.startTime != null"> | ||
| 72 | + AND ew.create_time >= #{query.startTime} | ||
| 73 | + </if> | ||
| 74 | + <if test="query.endTime != null"> | ||
| 75 | + AND ew.create_time <= #{query.endTime} | ||
| 76 | + </if> | ||
| 77 | + </where> | ||
| 78 | + ORDER BY ew.create_time DESC | ||
| 79 | + </select> | ||
| 80 | + | ||
| 81 | + <!-- 根据工单ID获取异常工单详情 --> | ||
| 82 | + <select id="selectExceptionWorkorderDetail" resultType="com.apple.erp.dto.ExceptionWorkorderRes"> | ||
| 83 | + SELECT | ||
| 84 | + ew.workorder_id, | ||
| 85 | + ew.workorder_no, | ||
| 86 | + ew.order_no, | ||
| 87 | + ew.dealer_code, | ||
| 88 | + ew.dealer_name, | ||
| 89 | + ew.exception_type, | ||
| 90 | + CASE ew.exception_type | ||
| 91 | + WHEN 1 THEN '逻辑验证异常' | ||
| 92 | + WHEN 2 THEN '源头验证异常' | ||
| 93 | + WHEN 3 THEN '交叉验证异常' | ||
| 94 | + ELSE '未知类型' | ||
| 95 | + END AS exception_type_name, | ||
| 96 | + ew.severity_level, | ||
| 97 | + CASE ew.severity_level | ||
| 98 | + WHEN 1 THEN '高' | ||
| 99 | + WHEN 2 THEN '中' | ||
| 100 | + WHEN 3 THEN '低' | ||
| 101 | + ELSE '未知' | ||
| 102 | + END AS severity_level_name, | ||
| 103 | + ew.workorder_status, | ||
| 104 | + CASE ew.workorder_status | ||
| 105 | + WHEN 1 THEN '待处理' | ||
| 106 | + WHEN 2 THEN '处理中' | ||
| 107 | + WHEN 3 THEN '已解决' | ||
| 108 | + WHEN 4 THEN '已关闭' | ||
| 109 | + ELSE '未知状态' | ||
| 110 | + END AS workorder_status_name, | ||
| 111 | + ew.create_time, | ||
| 112 | + ew.expect_complete_time, | ||
| 113 | + ew.handler_user, | ||
| 114 | + ew.exception_desc, | ||
| 115 | + ew.handle_suggest, | ||
| 116 | + ew.data_source, | ||
| 117 | + ew.create_by, | ||
| 118 | + ew.update_by, | ||
| 119 | + ew.update_time | ||
| 120 | + FROM t_exception_workorder ew | ||
| 121 | + WHERE ew.workorder_id = #{workorderId} AND ew.del_flag = '0' | ||
| 122 | + </select> | ||
| 123 | + | ||
| 124 | + <!-- 获取异常工单统计信息 --> | ||
| 125 | + <select id="selectExceptionWorkorderStats" resultType="com.apple.erp.dto.ExceptionWorkorderRes"> | ||
| 126 | + SELECT | ||
| 127 | + 'total' AS workorder_no, | ||
| 128 | + COUNT(*) AS workorder_id, | ||
| 129 | + SUM(CASE WHEN workorder_status = 1 THEN 1 ELSE 0 END) AS exception_type, | ||
| 130 | + SUM(CASE WHEN workorder_status = 2 THEN 1 ELSE 0 END) AS severity_level, | ||
| 131 | + SUM(CASE WHEN workorder_status = 3 THEN 1 ELSE 0 END) AS workorder_status, | ||
| 132 | + SUM(CASE WHEN workorder_status = 4 THEN 1 ELSE 0 END) AS handler_user, | ||
| 133 | + SUM(CASE WHEN severity_level = 1 THEN 1 ELSE 0 END) AS exception_desc, | ||
| 134 | + SUM(CASE WHEN severity_level = 2 THEN 1 ELSE 0 END) AS handle_suggest, | ||
| 135 | + SUM(CASE WHEN severity_level = 3 THEN 1 ELSE 0 END) AS data_source | ||
| 136 | + FROM t_exception_workorder | ||
| 137 | + WHERE del_flag = '0' | ||
| 138 | + </select> | ||
| 139 | + | ||
| 140 | + <!-- 批量更新工单状态 --> | ||
| 141 | + <update id="batchUpdateWorkorderStatus"> | ||
| 142 | + UPDATE t_exception_workorder | ||
| 143 | + SET workorder_status = #{workorderStatus}, | ||
| 144 | + handler_user = #{handlerUser}, | ||
| 145 | + update_time = NOW() | ||
| 146 | + WHERE workorder_id IN | ||
| 147 | + <foreach collection="workorderIds" item="workorderId" open="(" separator="," close=")"> | ||
| 148 | + #{workorderId} | ||
| 149 | + </foreach> | ||
| 150 | + AND del_flag = '0' | ||
| 151 | + </update> | ||
| 152 | + | ||
| 153 | +</mapper> |
| 1 | +-- 异常工单模块完整初始化脚本 | ||
| 2 | +-- 包含表结构创建、权限配置、测试数据插入 | ||
| 3 | + | ||
| 4 | +-- 1. 创建异常工单表 | ||
| 5 | +CREATE TABLE IF NOT EXISTS t_exception_workorder ( | ||
| 6 | + workorder_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '工单ID', | ||
| 7 | + workorder_no VARCHAR(30) NOT NULL COMMENT '工单编号', | ||
| 8 | + order_no VARCHAR(30) COMMENT '关联订单编号', | ||
| 9 | + dealer_code VARCHAR(20) COMMENT '经销商编码', | ||
| 10 | + dealer_name VARCHAR(100) COMMENT '经销商名称', | ||
| 11 | + exception_type TINYINT NOT NULL COMMENT '异常类型(1-逻辑验证异常/2-源头验证异常/3-交叉验证异常)', | ||
| 12 | + severity_level TINYINT NOT NULL COMMENT '严重程度(1-高/2-中/3-低)', | ||
| 13 | + workorder_status TINYINT DEFAULT 1 COMMENT '工单状态(1-待处理/2-处理中/3-已解决/4-已关闭)', | ||
| 14 | + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '工单创建时间', | ||
| 15 | + expect_complete_time DATETIME COMMENT '预计处理完成时间', | ||
| 16 | + handler_user VARCHAR(20) COMMENT '处理人', | ||
| 17 | + exception_desc VARCHAR(500) COMMENT '异常描述', | ||
| 18 | + handle_suggest VARCHAR(500) COMMENT '处理建议', | ||
| 19 | + data_source VARCHAR(20) COMMENT '数据来源', | ||
| 20 | + create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', | ||
| 21 | + update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', | ||
| 22 | + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', | ||
| 23 | + del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)' | ||
| 24 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='异常工单表'; | ||
| 25 | + | ||
| 26 | +-- 2. 创建异常工单处理日志表 | ||
| 27 | +CREATE TABLE IF NOT EXISTS t_exception_workorder_log ( | ||
| 28 | + log_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '日志ID', | ||
| 29 | + workorder_id BIGINT NOT NULL COMMENT '关联工单ID', | ||
| 30 | + workorder_no VARCHAR(30) NOT NULL COMMENT '关联工单编号', | ||
| 31 | + handle_user VARCHAR(20) NOT NULL COMMENT '处理人', | ||
| 32 | + handle_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '处理时间', | ||
| 33 | + before_status TINYINT NOT NULL COMMENT '处理前状态', | ||
| 34 | + after_status TINYINT NOT NULL COMMENT '处理后状态', | ||
| 35 | + handle_opinion VARCHAR(500) COMMENT '处理意见', | ||
| 36 | + attach_url VARCHAR(200) COMMENT '附件URL', | ||
| 37 | + create_by VARCHAR(64) DEFAULT '' COMMENT '创建者', | ||
| 38 | + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | ||
| 39 | + update_by VARCHAR(64) DEFAULT '' COMMENT '更新者', | ||
| 40 | + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', | ||
| 41 | + del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)' | ||
| 42 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='异常工单处理日志表'; | ||
| 43 | + | ||
| 44 | +-- 3. 创建索引 | ||
| 45 | +-- 异常工单表索引 | ||
| 46 | +CREATE UNIQUE INDEX IF NOT EXISTS uk_workorder_no ON t_exception_workorder(workorder_no); | ||
| 47 | +CREATE INDEX IF NOT EXISTS idx_workorder_dealer_code ON t_exception_workorder(dealer_code); | ||
| 48 | +CREATE INDEX IF NOT EXISTS idx_workorder_order_no ON t_exception_workorder(order_no); | ||
| 49 | +CREATE INDEX IF NOT EXISTS idx_workorder_status ON t_exception_workorder(workorder_status); | ||
| 50 | +CREATE INDEX IF NOT EXISTS idx_workorder_exception_type ON t_exception_workorder(exception_type); | ||
| 51 | +CREATE INDEX IF NOT EXISTS idx_workorder_severity_level ON t_exception_workorder(severity_level); | ||
| 52 | +CREATE INDEX IF NOT EXISTS idx_workorder_create_time ON t_exception_workorder(create_time); | ||
| 53 | +CREATE INDEX IF NOT EXISTS idx_workorder_handler_user ON t_exception_workorder(handler_user); | ||
| 54 | + | ||
| 55 | +-- 异常工单处理日志表索引 | ||
| 56 | +CREATE INDEX IF NOT EXISTS idx_log_workorder_id ON t_exception_workorder_log(workorder_id); | ||
| 57 | +CREATE INDEX IF NOT EXISTS idx_log_workorder_no ON t_exception_workorder_log(workorder_no); | ||
| 58 | +CREATE INDEX IF NOT EXISTS idx_log_handle_user ON t_exception_workorder_log(handle_user); | ||
| 59 | +CREATE INDEX IF NOT EXISTS idx_log_handle_time ON t_exception_workorder_log(handle_time); | ||
| 60 | + | ||
| 61 | +-- 4. 创建外键约束 | ||
| 62 | +ALTER TABLE t_exception_workorder_log | ||
| 63 | +ADD CONSTRAINT IF NOT EXISTS fk_workorder_log_workorder_id | ||
| 64 | +FOREIGN KEY (workorder_id) REFERENCES t_exception_workorder(workorder_id) | ||
| 65 | +ON DELETE CASCADE ON UPDATE CASCADE; | ||
| 66 | + | ||
| 67 | +-- 5. 插入异常工单菜单 | ||
| 68 | +INSERT IGNORE INTO t_sys_menu (parent_id, menu_name, menu_type, path, icon, sort, status, perms, create_by, create_time) VALUES | ||
| 69 | +(0, '异常工单', 1, '/main/exception-workorder', '⚠️', 7, 1, 'exception:workorder:view', 'admin', NOW()); | ||
| 70 | + | ||
| 71 | +-- 获取异常工单菜单ID | ||
| 72 | +SET @exception_menu_id = LAST_INSERT_ID(); | ||
| 73 | + | ||
| 74 | +-- 6. 插入异常工单子菜单 | ||
| 75 | +INSERT IGNORE INTO t_sys_menu (parent_id, menu_name, menu_type, path, icon, sort, status, perms, create_by, create_time) VALUES | ||
| 76 | +(@exception_menu_id, '工单查询', 2, '', '', 1, 1, 'exception:workorder:list', 'admin', NOW()), | ||
| 77 | +(@exception_menu_id, '工单详情', 2, '', '', 2, 1, 'exception:workorder:detail', 'admin', NOW()), | ||
| 78 | +(@exception_menu_id, '工单新增', 2, '', '', 3, 1, 'exception:workorder:add', 'admin', NOW()), | ||
| 79 | +(@exception_menu_id, '工单编辑', 2, '', '', 4, 1, 'exception:workorder:edit', 'admin', NOW()), | ||
| 80 | +(@exception_menu_id, '工单删除', 2, '', '', 5, 1, 'exception:workorder:delete', 'admin', NOW()), | ||
| 81 | +(@exception_menu_id, '状态更新', 2, '', '', 6, 1, 'exception:workorder:status', 'admin', NOW()), | ||
| 82 | +(@exception_menu_id, '批量处理', 2, '', '', 7, 1, 'exception:workorder:batch', 'admin', NOW()), | ||
| 83 | +(@exception_menu_id, '统计查看', 2, '', '', 8, 1, 'exception:workorder:stats', 'admin', NOW()); | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +-- 12. 为管理员角色分配异常工单权限 | ||
| 87 | +INSERT IGNORE INTO t_sys_role_menu (role_id, menu_id, create_by, create_time) | ||
| 88 | +SELECT r.role_id, @exception_menu_id, 'admin', NOW() | ||
| 89 | +FROM t_sys_role r | ||
| 90 | +WHERE r.role_code = 'ADMIN'; | ||
| 91 | + | ||
| 92 | +-- 13. 为管理员角色分配异常工单子菜单权限 | ||
| 93 | +INSERT IGNORE INTO t_sys_role_menu (role_id, menu_id, create_by, create_time) | ||
| 94 | +SELECT r.role_id, m.menu_id, 'admin', NOW() | ||
| 95 | +FROM t_sys_role r, t_sys_menu m | ||
| 96 | +WHERE r.role_code = 'ADMIN' | ||
| 97 | +AND m.parent_id = @exception_menu_id; | ||
| 98 | + | ||
| 99 | + | ||
| 100 | +-- 完成初始化 | ||
| 101 | +SELECT '异常工单模块初始化完成' AS message; |
| ... | @@ -9,7 +9,9 @@ declare module 'vue' { | ... | @@ -9,7 +9,9 @@ declare module 'vue' { |
| 9 | export interface GlobalComponents { | 9 | export interface GlobalComponents { |
| 10 | ElButton: typeof import('element-plus/es')['ElButton'] | 10 | ElButton: typeof import('element-plus/es')['ElButton'] |
| 11 | ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] | 11 | ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] |
| 12 | + ElCard: typeof import('element-plus/es')['ElCard'] | ||
| 12 | ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] | 13 | ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] |
| 14 | + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] | ||
| 13 | ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] | 15 | ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] |
| 14 | ElDialog: typeof import('element-plus/es')['ElDialog'] | 16 | ElDialog: typeof import('element-plus/es')['ElDialog'] |
| 15 | ElForm: typeof import('element-plus/es')['ElForm'] | 17 | ElForm: typeof import('element-plus/es')['ElForm'] |
| ... | @@ -23,7 +25,9 @@ declare module 'vue' { | ... | @@ -23,7 +25,9 @@ declare module 'vue' { |
| 23 | ElSelect: typeof import('element-plus/es')['ElSelect'] | 25 | ElSelect: typeof import('element-plus/es')['ElSelect'] |
| 24 | ElTable: typeof import('element-plus/es')['ElTable'] | 26 | ElTable: typeof import('element-plus/es')['ElTable'] |
| 25 | ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] | 27 | ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] |
| 28 | + ElSelect: typeof import('element-plus/es')['ElSelect'] | ||
| 26 | ElTag: typeof import('element-plus/es')['ElTag'] | 29 | ElTag: typeof import('element-plus/es')['ElTag'] |
| 30 | + ElText: typeof import('element-plus/es')['ElText'] | ||
| 27 | Header: typeof import('./src/components/layout/Header.vue')['default'] | 31 | Header: typeof import('./src/components/layout/Header.vue')['default'] |
| 28 | RouterLink: typeof import('vue-router')['RouterLink'] | 32 | RouterLink: typeof import('vue-router')['RouterLink'] |
| 29 | RouterView: typeof import('vue-router')['RouterView'] | 33 | RouterView: typeof import('vue-router')['RouterView'] | ... | ... |
| ... | @@ -120,8 +120,11 @@ export const deliveryApi = { | ... | @@ -120,8 +120,11 @@ export const deliveryApi = { |
| 120 | 120 | ||
| 121 | // 修改出库状态 | 121 | // 修改出库状态 |
| 122 | updateDeliveryStatus: (deliveryId: number, deliveryStatus: number) => { | 122 | updateDeliveryStatus: (deliveryId: number, deliveryStatus: number) => { |
| 123 | - return request.post(`/api/delivery/${deliveryId}/deliveryStatus`, null, { | 123 | + return request.post(`/api/delivery/${deliveryId}/deliveryStatus`, { deliveryStatus }) |
| 124 | - params: { deliveryStatus } | 124 | + }, |
| 125 | - }) | 125 | + |
| 126 | + // 导出出库数据 | ||
| 127 | + exportDeliveries: (params: DeliveryQueryReq) => { | ||
| 128 | + return request.post('/api/delivery/export', params, { responseType: 'blob' }) | ||
| 126 | } | 129 | } |
| 127 | } | 130 | } | ... | ... |
| 1 | -import axios from 'axios' | 1 | +import request from '@/utils/request' |
| 2 | 2 | ||
| 3 | -const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8083' | 3 | +/** |
| 4 | + * 字典管理API | ||
| 5 | + */ | ||
| 4 | 6 | ||
| 5 | -const api = axios.create({ | 7 | +// 获取字典项 |
| 6 | - baseURL: API_BASE_URL, | 8 | +export const getDictItems = (dictType: string) => { |
| 7 | - timeout: 10000, | 9 | + return request.get(`/api/dict/${dictType}`) |
| 8 | - headers: { | ||
| 9 | - 'Content-Type': 'application/json' | ||
| 10 | - } | ||
| 11 | -}) | ||
| 12 | - | ||
| 13 | -api.interceptors.request.use( | ||
| 14 | - (config) => { | ||
| 15 | - const token = localStorage.getItem('token') | ||
| 16 | - if (token) { | ||
| 17 | - config.headers.Authorization = `Bearer ${token}` | ||
| 18 | - } | ||
| 19 | - return config | ||
| 20 | - }, | ||
| 21 | - (error) => { | ||
| 22 | - return Promise.reject(error) | ||
| 23 | - } | ||
| 24 | -) | ||
| 25 | - | ||
| 26 | -api.interceptors.response.use( | ||
| 27 | - (response) => { | ||
| 28 | - return response.data | ||
| 29 | - }, | ||
| 30 | - (error) => { | ||
| 31 | - console.error('API请求错误:', error) | ||
| 32 | - return Promise.reject(error) | ||
| 33 | - } | ||
| 34 | -) | ||
| 35 | - | ||
| 36 | -export interface DictType { | ||
| 37 | - dictTypeId: number | ||
| 38 | - dictType: string | ||
| 39 | - dictName: string | ||
| 40 | - status: number | ||
| 41 | - statusText: string | ||
| 42 | - remark?: string | ||
| 43 | - createBy?: string | ||
| 44 | - createTime: string | ||
| 45 | - updateBy?: string | ||
| 46 | - updateTime: string | ||
| 47 | -} | ||
| 48 | - | ||
| 49 | -export interface DictItem { | ||
| 50 | - dictItemId: number | ||
| 51 | - dictTypeId: number | ||
| 52 | - dictType: string | ||
| 53 | - dictLabel: string | ||
| 54 | - dictValue: string | ||
| 55 | - sort: number | ||
| 56 | - remark?: string | ||
| 57 | - createBy?: string | ||
| 58 | - createTime: string | ||
| 59 | - updateBy?: string | ||
| 60 | - updateTime: string | ||
| 61 | -} | ||
| 62 | - | ||
| 63 | -export interface DictTypeSearchParams { | ||
| 64 | - dictType?: string | ||
| 65 | - dictName?: string | ||
| 66 | - status?: number | ||
| 67 | - pageNum?: number | ||
| 68 | - pageSize?: number | ||
| 69 | -} | ||
| 70 | - | ||
| 71 | -export interface DictItemSearchParams { | ||
| 72 | - dictTypeId?: number | ||
| 73 | - dictLabel?: string | ||
| 74 | - dictValue?: string | ||
| 75 | - pageNum?: number | ||
| 76 | - pageSize?: number | ||
| 77 | -} | ||
| 78 | - | ||
| 79 | -export interface DictTypeAddReq { | ||
| 80 | - dictType: string | ||
| 81 | - dictName: string | ||
| 82 | - status: number | ||
| 83 | - remark?: string | ||
| 84 | -} | ||
| 85 | - | ||
| 86 | -export interface DictTypeUpdateReq extends DictTypeAddReq { | ||
| 87 | - dictTypeId: number | ||
| 88 | -} | ||
| 89 | - | ||
| 90 | -export interface DictItemAddReq { | ||
| 91 | - dictTypeId: number | ||
| 92 | - dictLabel: string | ||
| 93 | - dictValue: string | ||
| 94 | - sort?: number | ||
| 95 | - remark?: string | ||
| 96 | } | 10 | } |
| 97 | 11 | ||
| 98 | -export interface DictItemUpdateReq extends DictItemAddReq { | 12 | +// 获取所有字典项 |
| 99 | - dictItemId: number | 13 | +export const getAllDictItems = () => { |
| 14 | + return request.get('/api/dict/all') | ||
| 100 | } | 15 | } |
| 101 | 16 | ||
| 102 | -export interface ApiResponse<T = any> { | 17 | +// 刷新字典缓存 |
| 103 | - code: number | 18 | +export const refreshDictCache = () => { |
| 104 | - message: string | 19 | + return request.post('/api/dict/refresh') |
| 105 | - data: T | ||
| 106 | -} | ||
| 107 | - | ||
| 108 | -export interface PageResponse<T> { | ||
| 109 | - records: T[] | ||
| 110 | - total: number | ||
| 111 | - size: number | ||
| 112 | - current: number | ||
| 113 | - orders: any[] | ||
| 114 | - optimizeCountSql: boolean | ||
| 115 | - searchCount: boolean | ||
| 116 | - maxLimit: any | ||
| 117 | - countId: any | ||
| 118 | - pages: number | ||
| 119 | -} | ||
| 120 | - | ||
| 121 | -export const dictApi = { | ||
| 122 | - // 字典类型管理 | ||
| 123 | - // 获取字典类型列表 | ||
| 124 | - getDictTypes: (params: DictTypeSearchParams): Promise<ApiResponse<PageResponse<DictType>>> => { | ||
| 125 | - return api.get('/api/system/dict/type/list', { params }) | ||
| 126 | - }, | ||
| 127 | - | ||
| 128 | - // 获取字典类型详情 | ||
| 129 | - getDictTypeById: (dictTypeId: number): Promise<ApiResponse<DictType>> => { | ||
| 130 | - return api.get(`/api/system/dict/type/${dictTypeId}`) | ||
| 131 | - }, | ||
| 132 | - | ||
| 133 | - // 新增字典类型 | ||
| 134 | - createDictType: (dictTypeData: DictTypeAddReq): Promise<ApiResponse<any>> => { | ||
| 135 | - return api.post('/api/system/dict/type', dictTypeData) | ||
| 136 | - }, | ||
| 137 | - | ||
| 138 | - // 修改字典类型 | ||
| 139 | - updateDictType: (dictTypeData: DictTypeUpdateReq): Promise<ApiResponse<any>> => { | ||
| 140 | - return api.put('/api/system/dict/type', dictTypeData) | ||
| 141 | - }, | ||
| 142 | - | ||
| 143 | - // 删除字典类型 | ||
| 144 | - deleteDictTypes: (dictTypeIds: number[]): Promise<ApiResponse<any>> => { | ||
| 145 | - return api.delete(`/api/system/dict/type/${dictTypeIds.join(',')}`) | ||
| 146 | - }, | ||
| 147 | - | ||
| 148 | - // 获取字典类型选择框列表 | ||
| 149 | - getDictTypeOptions: (): Promise<ApiResponse<DictType[]>> => { | ||
| 150 | - return api.get('/api/system/dict/type/optionselect') | ||
| 151 | - }, | ||
| 152 | - | ||
| 153 | - // 刷新字典缓存 | ||
| 154 | - refreshDictCache: (): Promise<ApiResponse<any>> => { | ||
| 155 | - return api.delete('/api/system/dict/type/refreshCache') | ||
| 156 | - }, | ||
| 157 | - | ||
| 158 | - // 字典项管理 | ||
| 159 | - // 获取字典项列表 | ||
| 160 | - getDictItems: (params: DictItemSearchParams): Promise<ApiResponse<PageResponse<DictItem>>> => { | ||
| 161 | - return api.get('/api/system/dict/item/list', { params }) | ||
| 162 | - }, | ||
| 163 | - | ||
| 164 | - // 根据字典类型获取字典项列表 | ||
| 165 | - getDictItemsByType: (dictType: string): Promise<ApiResponse<DictItem[]>> => { | ||
| 166 | - return api.get(`/api/system/dict/item/type/${dictType}`) | ||
| 167 | - }, | ||
| 168 | - | ||
| 169 | - // 获取字典项详情 | ||
| 170 | - getDictItemById: (dictItemId: number): Promise<ApiResponse<DictItem>> => { | ||
| 171 | - return api.get(`/api/system/dict/item/${dictItemId}`) | ||
| 172 | - }, | ||
| 173 | - | ||
| 174 | - // 新增字典项 | ||
| 175 | - createDictItem: (dictItemData: DictItemAddReq): Promise<ApiResponse<any>> => { | ||
| 176 | - return api.post('/api/system/dict/item', dictItemData) | ||
| 177 | - }, | ||
| 178 | - | ||
| 179 | - // 修改字典项 | ||
| 180 | - updateDictItem: (dictItemData: DictItemUpdateReq): Promise<ApiResponse<any>> => { | ||
| 181 | - return api.put('/api/system/dict/item', dictItemData) | ||
| 182 | - }, | ||
| 183 | - | ||
| 184 | - // 删除字典项 | ||
| 185 | - deleteDictItems: (dictItemIds: number[]): Promise<ApiResponse<any>> => { | ||
| 186 | - return api.delete(`/api/system/dict/item/${dictItemIds.join(',')}`) | ||
| 187 | - }, | ||
| 188 | } | 20 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
frontend/src/api/exceptionWorkorder.ts
0 → 100644
| 1 | +import { request } from '../utils/request' | ||
| 2 | + | ||
| 3 | +// 异常工单相关类型定义 | ||
| 4 | +export interface ExceptionWorkorderInfo { | ||
| 5 | + workorderId: number | ||
| 6 | + workorderNo: string | ||
| 7 | + orderNo: string | ||
| 8 | + dealerCode: string | ||
| 9 | + dealerName: string | ||
| 10 | + exceptionType: number | ||
| 11 | + exceptionTypeName: string | ||
| 12 | + severityLevel: number | ||
| 13 | + severityLevelName: string | ||
| 14 | + workorderStatus: number | ||
| 15 | + workorderStatusName: string | ||
| 16 | + createTime: string | ||
| 17 | + expectCompleteTime?: string | ||
| 18 | + handlerUser?: string | ||
| 19 | + exceptionDesc?: string | ||
| 20 | + handleSuggest?: string | ||
| 21 | + dataSource?: string | ||
| 22 | + createBy?: string | ||
| 23 | + updateBy?: string | ||
| 24 | + updateTime?: string | ||
| 25 | + workorderLogs?: ExceptionWorkorderLogInfo[] | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +export interface ExceptionWorkorderLogInfo { | ||
| 29 | + logId: number | ||
| 30 | + workorderId: number | ||
| 31 | + workorderNo: string | ||
| 32 | + handleUser: string | ||
| 33 | + handleTime: string | ||
| 34 | + beforeStatus: number | ||
| 35 | + beforeStatusName: string | ||
| 36 | + afterStatus: number | ||
| 37 | + afterStatusName: string | ||
| 38 | + handleOpinion?: string | ||
| 39 | + attachUrl?: string | ||
| 40 | + createBy?: string | ||
| 41 | + createTime: string | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +export interface ExceptionWorkorderQueryReq { | ||
| 45 | + workorderNo?: string | ||
| 46 | + orderNo?: string | ||
| 47 | + dealerCode?: string | ||
| 48 | + dealerName?: string | ||
| 49 | + workorderStatus?: number | ||
| 50 | + exceptionType?: number | ||
| 51 | + severityLevel?: number | ||
| 52 | + handlerUser?: string | ||
| 53 | + startTime?: string | ||
| 54 | + endTime?: string | ||
| 55 | + pageNum?: number | ||
| 56 | + pageSize?: number | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +export interface ExceptionWorkorderAddReq { | ||
| 60 | + workorderNo: string | ||
| 61 | + orderNo?: string | ||
| 62 | + dealerCode?: string | ||
| 63 | + dealerName?: string | ||
| 64 | + exceptionType: number | ||
| 65 | + severityLevel: number | ||
| 66 | + workorderStatus?: number | ||
| 67 | + expectCompleteTime?: string | ||
| 68 | + handlerUser?: string | ||
| 69 | + exceptionDesc?: string | ||
| 70 | + handleSuggest?: string | ||
| 71 | + dataSource?: string | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +export interface ExceptionWorkorderUpdateReq { | ||
| 75 | + workorderId: number | ||
| 76 | + workorderNo?: string | ||
| 77 | + orderNo?: string | ||
| 78 | + dealerCode?: string | ||
| 79 | + dealerName?: string | ||
| 80 | + exceptionType?: number | ||
| 81 | + severityLevel?: number | ||
| 82 | + workorderStatus?: number | ||
| 83 | + expectCompleteTime?: string | ||
| 84 | + handlerUser?: string | ||
| 85 | + exceptionDesc?: string | ||
| 86 | + handleSuggest?: string | ||
| 87 | + dataSource?: string | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +export interface ExceptionWorkorderStatusUpdateReq { | ||
| 91 | + workorderId: number | ||
| 92 | + workorderStatus: number | ||
| 93 | + handlerUser?: string | ||
| 94 | + handleOpinion?: string | ||
| 95 | + attachUrl?: string | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +// 异常工单API接口 | ||
| 99 | +export const exceptionWorkorderApi = { | ||
| 100 | + // 分页查询异常工单列表 | ||
| 101 | + getExceptionWorkorderList: (params: ExceptionWorkorderQueryReq) => { | ||
| 102 | + return request.get('/api/exception-workorder/list', params) | ||
| 103 | + }, | ||
| 104 | + | ||
| 105 | + // 获取异常工单详情 | ||
| 106 | + getExceptionWorkorderDetail: (workorderId: number) => { | ||
| 107 | + return request.get(`/api/exception-workorder/${workorderId}`) | ||
| 108 | + }, | ||
| 109 | + | ||
| 110 | + // 新增异常工单 | ||
| 111 | + addExceptionWorkorder: (data: ExceptionWorkorderAddReq) => { | ||
| 112 | + return request.post('/api/exception-workorder', data) | ||
| 113 | + }, | ||
| 114 | + | ||
| 115 | + // 更新异常工单 | ||
| 116 | + updateExceptionWorkorder: (data: ExceptionWorkorderUpdateReq) => { | ||
| 117 | + return request.put('/api/exception-workorder', data) | ||
| 118 | + }, | ||
| 119 | + | ||
| 120 | + // 更新工单状态 | ||
| 121 | + updateWorkorderStatus: (data: ExceptionWorkorderStatusUpdateReq) => { | ||
| 122 | + return request.post('/api/exception-workorder/status', data) | ||
| 123 | + }, | ||
| 124 | + | ||
| 125 | + // 删除异常工单 | ||
| 126 | + deleteExceptionWorkorder: (workorderId: number) => { | ||
| 127 | + return request.delete(`/api/exception-workorder/${workorderId}`) | ||
| 128 | + }, | ||
| 129 | + | ||
| 130 | + // 批量删除异常工单 | ||
| 131 | + batchDeleteExceptionWorkorders: (workorderIds: number[]) => { | ||
| 132 | + return request.delete('/api/exception-workorder/batch', workorderIds) | ||
| 133 | + }, | ||
| 134 | + | ||
| 135 | + // 批量更新工单状态 | ||
| 136 | + batchUpdateWorkorderStatus: (workorderIds: number[], workorderStatus: number, handlerUser: string) => { | ||
| 137 | + return request.post('/api/exception-workorder/batch-status', null, { | ||
| 138 | + params: { | ||
| 139 | + workorderIds: workorderIds.join(','), | ||
| 140 | + workorderStatus, | ||
| 141 | + handlerUser | ||
| 142 | + } | ||
| 143 | + }) | ||
| 144 | + }, | ||
| 145 | + | ||
| 146 | + // 获取异常工单统计信息 | ||
| 147 | + getExceptionWorkorderStats: () => { | ||
| 148 | + return request.get('/api/exception-workorder/stats') | ||
| 149 | + } | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +// 异常类型枚举 | ||
| 153 | +export const EXCEPTION_TYPE = { | ||
| 154 | + 1: '逻辑验证异常', | ||
| 155 | + 2: '源头验证异常', | ||
| 156 | + 3: '交叉验证异常' | ||
| 157 | +} | ||
| 158 | + | ||
| 159 | +// 严重程度枚举 | ||
| 160 | +export const SEVERITY_LEVEL = { | ||
| 161 | + 1: '高', | ||
| 162 | + 2: '中', | ||
| 163 | + 3: '低' | ||
| 164 | +} | ||
| 165 | + | ||
| 166 | +// 工单状态枚举 | ||
| 167 | +export const WORKORDER_STATUS = { | ||
| 168 | + 1: '待处理', | ||
| 169 | + 2: '处理中', | ||
| 170 | + 3: '已解决', | ||
| 171 | + 4: '已关闭' | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +// 获取异常类型名称 | ||
| 175 | +export const getExceptionTypeName = (type: number): string => { | ||
| 176 | + return EXCEPTION_TYPE[type as keyof typeof EXCEPTION_TYPE] || '未知类型' | ||
| 177 | +} | ||
| 178 | + | ||
| 179 | +// 获取严重程度名称 | ||
| 180 | +export const getSeverityLevelName = (level: number): string => { | ||
| 181 | + return SEVERITY_LEVEL[level as keyof typeof SEVERITY_LEVEL] || '未知' | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +// 获取工单状态名称 | ||
| 185 | +export const getWorkorderStatusName = (status: number): string => { | ||
| 186 | + return WORKORDER_STATUS[status as keyof typeof WORKORDER_STATUS] || '未知状态' | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +// 获取严重程度颜色 | ||
| 190 | +export const getSeverityLevelColor = (level: number): string => { | ||
| 191 | + const colors = { | ||
| 192 | + 1: '#f56c6c', // 高 - 红色 | ||
| 193 | + 2: '#e6a23c', // 中 - 橙色 | ||
| 194 | + 3: '#409eff' // 低 - 蓝色 | ||
| 195 | + } | ||
| 196 | + return colors[level as keyof typeof colors] || '#909399' | ||
| 197 | +} | ||
| 198 | + | ||
| 199 | +// 获取工单状态颜色 | ||
| 200 | +export const getWorkorderStatusColor = (status: number): string => { | ||
| 201 | + const colors = { | ||
| 202 | + 1: '#909399', // 待处理 - 灰色 | ||
| 203 | + 2: '#e6a23c', // 处理中 - 橙色 | ||
| 204 | + 3: '#67c23a', // 已解决 - 绿色 | ||
| 205 | + 4: '#f56c6c' // 已关闭 - 红色 | ||
| 206 | + } | ||
| 207 | + return colors[status as keyof typeof colors] || '#909399' | ||
| 208 | +} |
| ... | @@ -121,8 +121,11 @@ export const invoiceApi = { | ... | @@ -121,8 +121,11 @@ export const invoiceApi = { |
| 121 | return request.post('/api/invoice/batchDelete', invoiceIds) | 121 | return request.post('/api/invoice/batchDelete', invoiceIds) |
| 122 | }, | 122 | }, |
| 123 | updateInvoiceStatus: (invoiceId: number, invoiceStatus: number) => { | 123 | updateInvoiceStatus: (invoiceId: number, invoiceStatus: number) => { |
| 124 | - return request.post(`/api/invoice/${invoiceId}/invoiceStatus`, null, { | 124 | + return request.post(`/api/invoice/${invoiceId}/invoiceStatus`, { invoiceStatus }) |
| 125 | - params: { invoiceStatus } | 125 | + }, |
| 126 | - }) | 126 | + |
| 127 | + // 导出发票数据 | ||
| 128 | + exportInvoices: (params: InvoiceQueryReq) => { | ||
| 129 | + return request.post('/api/invoice/export', params, { responseType: 'blob' }) | ||
| 127 | } | 130 | } |
| 128 | } | 131 | } | ... | ... |
| ... | @@ -150,5 +150,10 @@ export const orderApi = { | ... | @@ -150,5 +150,10 @@ export const orderApi = { |
| 150 | return request.post(`/order/${orderId}/rebateCalcFlag`, null, { | 150 | return request.post(`/order/${orderId}/rebateCalcFlag`, null, { |
| 151 | params: { rebateCalcFlag } | 151 | params: { rebateCalcFlag } |
| 152 | }) | 152 | }) |
| 153 | + }, | ||
| 154 | + | ||
| 155 | + // 导出订单数据 | ||
| 156 | + exportOrders: (params: OrderQueryReq) => { | ||
| 157 | + return request.post('/order/export', params, { responseType: 'blob' }) | ||
| 153 | } | 158 | } |
| 154 | } | 159 | } | ... | ... |
| ... | @@ -209,6 +209,11 @@ export const getRebateTrendStats = (startDate?: string, endDate?: string) => { | ... | @@ -209,6 +209,11 @@ export const getRebateTrendStats = (startDate?: string, endDate?: string) => { |
| 209 | }) | 209 | }) |
| 210 | } | 210 | } |
| 211 | 211 | ||
| 212 | +// 导出返利数据 | ||
| 213 | +export const exportRebates = (params: RebateSearchParams) => { | ||
| 214 | + return request.post('/api/rebate/export', params, { responseType: 'blob' }) | ||
| 215 | +} | ||
| 216 | + | ||
| 212 | export default { | 217 | export default { |
| 213 | getRebatePage, | 218 | getRebatePage, |
| 214 | getRebateById, | 219 | getRebateById, |
| ... | @@ -227,5 +232,6 @@ export default { | ... | @@ -227,5 +232,6 @@ export default { |
| 227 | exportRebate, | 232 | exportRebate, |
| 228 | getRebateMonthlyStats, | 233 | getRebateMonthlyStats, |
| 229 | getRebateStatusStats, | 234 | getRebateStatusStats, |
| 230 | - getRebateTrendStats | 235 | + getRebateTrendStats, |
| 236 | + exportRebates | ||
| 231 | } | 237 | } | ... | ... |
| ... | @@ -72,7 +72,8 @@ | ... | @@ -72,7 +72,8 @@ |
| 72 | 72 | ||
| 73 | <div class="header-right"> | 73 | <div class="header-right"> |
| 74 | <div class="user-info"> | 74 | <div class="user-info"> |
| 75 | - <span class="welcome-text">欢迎,{{ userInfo?.username || '用户' }}</span> | 75 | + <span class="welcome-text" v-if="userInfoLoading">加载中...</span> |
| 76 | + <span class="welcome-text" v-else>欢迎,{{ userInfo?.username || '未知' }}</span> | ||
| 76 | <div class="user-actions"> | 77 | <div class="user-actions"> |
| 77 | <button class="logout-btn" @click="handleLogout">退出登录</button> | 78 | <button class="logout-btn" @click="handleLogout">退出登录</button> |
| 78 | </div> | 79 | </div> |
| ... | @@ -125,6 +126,7 @@ | ... | @@ -125,6 +126,7 @@ |
| 125 | import { ref, computed, onMounted, watch, nextTick } from 'vue' | 126 | import { ref, computed, onMounted, watch, nextTick } from 'vue' |
| 126 | import { useRouter, useRoute } from 'vue-router' | 127 | import { useRouter, useRoute } from 'vue-router' |
| 127 | import { logoutApi } from '../api/auth' | 128 | import { logoutApi } from '../api/auth' |
| 129 | +import { request } from '../utils/request' | ||
| 128 | 130 | ||
| 129 | const router = useRouter() | 131 | const router = useRouter() |
| 130 | const route = useRoute() | 132 | const route = useRoute() |
| ... | @@ -134,6 +136,26 @@ const sidebarCollapsed = ref(false) | ... | @@ -134,6 +136,26 @@ const sidebarCollapsed = ref(false) |
| 134 | 136 | ||
| 135 | // 用户信息 | 137 | // 用户信息 |
| 136 | const userInfo = ref<any>(null) | 138 | const userInfo = ref<any>(null) |
| 139 | +const userInfoLoading = ref(false) | ||
| 140 | + | ||
| 141 | +// 获取用户信息 | ||
| 142 | +const fetchUserInfo = async () => { | ||
| 143 | + try { | ||
| 144 | + userInfoLoading.value = true | ||
| 145 | + const response = await request.get('/api/auth/userinfo') | ||
| 146 | + userInfo.value = response | ||
| 147 | + console.log('顶部栏用户信息:', response) | ||
| 148 | + } catch (error) { | ||
| 149 | + console.error('获取用户信息失败:', error) | ||
| 150 | + // 如果获取失败,尝试从本地存储获取 | ||
| 151 | + const storedUserInfo = localStorage.getItem('userInfo') | ||
| 152 | + if (storedUserInfo) { | ||
| 153 | + userInfo.value = JSON.parse(storedUserInfo) | ||
| 154 | + } | ||
| 155 | + } finally { | ||
| 156 | + userInfoLoading.value = false | ||
| 157 | + } | ||
| 158 | +} | ||
| 137 | 159 | ||
| 138 | // 标签页容器引用 | 160 | // 标签页容器引用 |
| 139 | const tabContainer = ref<HTMLElement>() | 161 | const tabContainer = ref<HTMLElement>() |
| ... | @@ -147,6 +169,7 @@ const menuItems = ref([ | ... | @@ -147,6 +169,7 @@ const menuItems = ref([ |
| 147 | { name: '产品管理', path: '/main/product', icon: '📦' }, | 169 | { name: '产品管理', path: '/main/product', icon: '📦' }, |
| 148 | { name: '经销商管理', path: '/main/dealer', icon: '🏢' }, | 170 | { name: '经销商管理', path: '/main/dealer', icon: '🏢' }, |
| 149 | { name: '返利管理', path: '/main/rebate', icon: '💰' }, | 171 | { name: '返利管理', path: '/main/rebate', icon: '💰' }, |
| 172 | + { name: '异常工单', path: '/main/exception-workorder', icon: '⚠️' }, | ||
| 150 | { | 173 | { |
| 151 | name: '系统设置', | 174 | name: '系统设置', |
| 152 | icon: '⚙️', | 175 | icon: '⚙️', |
| ... | @@ -370,9 +393,17 @@ watch(() => route.path, (newPath) => { | ... | @@ -370,9 +393,17 @@ watch(() => route.path, (newPath) => { |
| 370 | 393 | ||
| 371 | // 组件挂载时获取用户信息 | 394 | // 组件挂载时获取用户信息 |
| 372 | onMounted(() => { | 395 | onMounted(() => { |
| 373 | - const storedUserInfo = localStorage.getItem('userInfo') | 396 | + // 检查是否已登录 |
| 374 | - if (storedUserInfo) { | 397 | + const token = localStorage.getItem('token') |
| 375 | - userInfo.value = JSON.parse(storedUserInfo) | 398 | + if (token) { |
| 399 | + // 调用API获取用户信息 | ||
| 400 | + fetchUserInfo() | ||
| 401 | + } else { | ||
| 402 | + // 如果没有token,尝试从本地存储获取 | ||
| 403 | + const storedUserInfo = localStorage.getItem('userInfo') | ||
| 404 | + if (storedUserInfo) { | ||
| 405 | + userInfo.value = JSON.parse(storedUserInfo) | ||
| 406 | + } | ||
| 376 | } | 407 | } |
| 377 | }) | 408 | }) |
| 378 | </script> | 409 | </script> | ... | ... |
| 1 | import { createApp } from 'vue' | 1 | import { createApp } from 'vue' |
| 2 | import { createPinia } from 'pinia' | 2 | import { createPinia } from 'pinia' |
| 3 | import App from './App.vue' | 3 | import App from './App.vue' |
| 4 | +import ElementPlus from 'element-plus' | ||
| 5 | +import 'element-plus/dist/index.css' | ||
| 4 | 6 | ||
| 5 | import router from './router' | 7 | import router from './router' |
| 6 | 8 | ||
| ... | @@ -10,5 +12,6 @@ const pinia = createPinia() | ... | @@ -10,5 +12,6 @@ const pinia = createPinia() |
| 10 | 12 | ||
| 11 | app.use(pinia) | 13 | app.use(pinia) |
| 12 | app.use(router) | 14 | app.use(router) |
| 15 | +app.use(ElementPlus) | ||
| 13 | // 挂载应用 | 16 | // 挂载应用 |
| 14 | app.mount('#app') | 17 | app.mount('#app') | ... | ... |
| ... | @@ -153,6 +153,24 @@ const staticRoutes: RouteRecordRaw[] = [ | ... | @@ -153,6 +153,24 @@ const staticRoutes: RouteRecordRaw[] = [ |
| 153 | } | 153 | } |
| 154 | }, | 154 | }, |
| 155 | { | 155 | { |
| 156 | + path: 'exception-workorder', | ||
| 157 | + name: 'ExceptionWorkorder', | ||
| 158 | + component: () => import('@/views/exceptionWorkorder/index.vue'), | ||
| 159 | + meta: { | ||
| 160 | + title: '异常工单', | ||
| 161 | + requiresAuth: true | ||
| 162 | + } | ||
| 163 | + }, | ||
| 164 | + { | ||
| 165 | + path: 'test-menu', | ||
| 166 | + name: 'TestMenu', | ||
| 167 | + component: () => import('@/views/test-menu.vue'), | ||
| 168 | + meta: { | ||
| 169 | + title: '菜单测试', | ||
| 170 | + requiresAuth: true | ||
| 171 | + } | ||
| 172 | + }, | ||
| 173 | + { | ||
| 156 | path: 'settings', | 174 | path: 'settings', |
| 157 | name: 'Settings', | 175 | name: 'Settings', |
| 158 | component: () => import('@/views/settings/index.vue'), | 176 | component: () => import('@/views/settings/index.vue'), | ... | ... |
| ... | @@ -46,6 +46,11 @@ service.interceptors.request.use( | ... | @@ -46,6 +46,11 @@ service.interceptors.request.use( |
| 46 | // 响应拦截器 | 46 | // 响应拦截器 |
| 47 | service.interceptors.response.use( | 47 | service.interceptors.response.use( |
| 48 | (response: AxiosResponse) => { | 48 | (response: AxiosResponse) => { |
| 49 | + // 如果是blob响应(文件下载),直接返回 | ||
| 50 | + if (response.config.responseType === 'blob') { | ||
| 51 | + return response.data | ||
| 52 | + } | ||
| 53 | + | ||
| 49 | const { code, message, data } = response.data | 54 | const { code, message, data } = response.data |
| 50 | 55 | ||
| 51 | // 请求成功 | 56 | // 请求成功 |
| ... | @@ -121,8 +126,8 @@ export const request = { | ... | @@ -121,8 +126,8 @@ export const request = { |
| 121 | return service.get(url, { params }) | 126 | return service.get(url, { params }) |
| 122 | }, | 127 | }, |
| 123 | 128 | ||
| 124 | - post<T = any>(url: string, data?: any): Promise<T> { | 129 | + post<T = any>(url: string, data?: any, config?: any): Promise<T> { |
| 125 | - return service.post(url, data) | 130 | + return service.post(url, data, config) |
| 126 | }, | 131 | }, |
| 127 | 132 | ||
| 128 | put<T = any>(url: string, data?: any): Promise<T> { | 133 | put<T = any>(url: string, data?: any): Promise<T> { | ... | ... |
| ... | @@ -2,7 +2,8 @@ | ... | @@ -2,7 +2,8 @@ |
| 2 | <div class="dashboard-container"> | 2 | <div class="dashboard-container"> |
| 3 | <div class="dashboard-header"> | 3 | <div class="dashboard-header"> |
| 4 | <h2>系统概览</h2> | 4 | <h2>系统概览</h2> |
| 5 | - <p>欢迎回来,{{ userInfo?.username || '用户' }}!</p> | 5 | + <p v-if="loading">正在加载用户信息...</p> |
| 6 | + <p v-else>欢迎回来,{{ userInfo?.username || '未知' }}!</p> | ||
| 6 | </div> | 7 | </div> |
| 7 | 8 | ||
| 8 | <div class="dashboard-stats"> | 9 | <div class="dashboard-stats"> |
| ... | @@ -43,32 +44,34 @@ | ... | @@ -43,32 +44,34 @@ |
| 43 | <div class="dashboard-card"> | 44 | <div class="dashboard-card"> |
| 44 | <h3>系统信息</h3> | 45 | <h3>系统信息</h3> |
| 45 | <div class="info-grid"> | 46 | <div class="info-grid"> |
| 46 | - <div class="info-item"> | 47 | + <div class="info-item"> |
| 47 | <label>用户名:</label> | 48 | <label>用户名:</label> |
| 48 | - <span>{{ userInfo?.username || '未知' }}</span> | 49 | + <span v-if="loading">加载中...</span> |
| 49 | - </div> | 50 | + <span v-else>{{ userInfo?.username || '未知' }}</span> |
| 50 | - <div class="info-item"> | 51 | + </div> |
| 52 | + <div class="info-item"> | ||
| 51 | <label>角色:</label> | 53 | <label>角色:</label> |
| 52 | - <span>{{ userInfo?.roles?.[0]?.roleName || '普通用户' }}</span> | 54 | + <span v-if="loading">加载中...</span> |
| 53 | - </div> | 55 | + <span v-else>{{ getRoleName(userInfo) || '普通用户' }}</span> |
| 54 | - <div class="info-item"> | 56 | + </div> |
| 57 | + <div class="info-item"> | ||
| 55 | <label>登录时间:</label> | 58 | <label>登录时间:</label> |
| 56 | <span>{{ currentTime }}</span> | 59 | <span>{{ currentTime }}</span> |
| 57 | - </div> | 60 | + </div> |
| 58 | - <div class="info-item"> | 61 | + <div class="info-item"> |
| 59 | <label>系统版本:</label> | 62 | <label>系统版本:</label> |
| 60 | <span>v1.0.0</span> | 63 | <span>v1.0.0</span> |
| 61 | </div> | 64 | </div> |
| 62 | - </div> | 65 | + </div> |
| 63 | </div> | 66 | </div> |
| 64 | 67 | ||
| 65 | <div class="dashboard-card"> | 68 | <div class="dashboard-card"> |
| 66 | <h3>快速操作</h3> | 69 | <h3>快速操作</h3> |
| 67 | <div class="quick-actions"> | 70 | <div class="quick-actions"> |
| 68 | - <button class="action-btn">👤 用户管理</button> | 71 | + <button class="action-btn" @click="navigateTo('/main/users')">👤 用户管理</button> |
| 69 | - <button class="action-btn">🛡️ 角色管理</button> | 72 | + <button class="action-btn" @click="navigateTo('/main/sys/role')">🛡️ 角色管理</button> |
| 70 | - <button class="action-btn">⚙️ 系统设置</button> | 73 | + <button class="action-btn" @click="navigateTo('/main/settings')">⚙️ 系统设置</button> |
| 71 | - <button class="action-btn">📊 查看日志</button> | 74 | + <button class="action-btn" @click="navigateTo('/main/sys/log')">📊 查看日志</button> |
| 72 | </div> | 75 | </div> |
| 73 | </div> | 76 | </div> |
| 74 | </div> | 77 | </div> |
| ... | @@ -78,28 +81,85 @@ | ... | @@ -78,28 +81,85 @@ |
| 78 | <script setup lang="ts"> | 81 | <script setup lang="ts"> |
| 79 | import { ref, onMounted } from 'vue' | 82 | import { ref, onMounted } from 'vue' |
| 80 | import { useRouter } from 'vue-router' | 83 | import { useRouter } from 'vue-router' |
| 84 | +import { request } from '@/utils/request' | ||
| 81 | 85 | ||
| 82 | const router = useRouter() | 86 | const router = useRouter() |
| 83 | const currentTime = ref('') | 87 | const currentTime = ref('') |
| 84 | const userInfo = ref<any>(null) | 88 | const userInfo = ref<any>(null) |
| 89 | +const loading = ref(false) | ||
| 90 | + | ||
| 91 | +// 获取角色名称 | ||
| 92 | +const getRoleName = (userInfo: any) => { | ||
| 93 | + // 优先使用roles字段中的角色信息 | ||
| 94 | + if (userInfo?.roles && Array.isArray(userInfo.roles) && userInfo.roles.length > 0) { | ||
| 95 | + return userInfo.roles[0].roleName || '普通用户' | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + // 如果没有roles字段,回退到authorities | ||
| 99 | + const authorities = userInfo?.authorities | ||
| 100 | + if (authorities && Array.isArray(authorities)) { | ||
| 101 | + // 查找ROLE_开头的权限 | ||
| 102 | + const roleAuthority = authorities.find(auth => | ||
| 103 | + auth.authority && auth.authority.startsWith('ROLE_') | ||
| 104 | + ) | ||
| 105 | + | ||
| 106 | + if (roleAuthority) { | ||
| 107 | + // 移除ROLE_前缀并转换为中文 | ||
| 108 | + const roleName = roleAuthority.authority.replace('ROLE_', '') | ||
| 109 | + const roleMap: { [key: string]: string } = { | ||
| 110 | + 'ADMIN': '管理员', | ||
| 111 | + 'USER': '普通用户', | ||
| 112 | + 'MANAGER': '经理', | ||
| 113 | + 'OPERATOR': '操作员' | ||
| 114 | + } | ||
| 115 | + return roleMap[roleName] || roleName | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + return '普通用户' | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +// 获取用户信息 | ||
| 123 | +const fetchUserInfo = async () => { | ||
| 124 | + try { | ||
| 125 | + loading.value = true | ||
| 126 | + const response = await request.get('/api/auth/userinfo') | ||
| 127 | + userInfo.value = response | ||
| 128 | + console.log('用户信息:', response) | ||
| 129 | + } catch (error) { | ||
| 130 | + console.error('获取用户信息失败:', error) | ||
| 131 | + // 如果获取失败,尝试从本地存储获取 | ||
| 132 | + const storedUserInfo = localStorage.getItem('userInfo') | ||
| 133 | + if (storedUserInfo) { | ||
| 134 | + userInfo.value = JSON.parse(storedUserInfo) | ||
| 135 | + } | ||
| 136 | + } finally { | ||
| 137 | + loading.value = false | ||
| 138 | + } | ||
| 139 | +} | ||
| 85 | 140 | ||
| 86 | onMounted(() => { | 141 | onMounted(() => { |
| 87 | // 获取当前时间 | 142 | // 获取当前时间 |
| 88 | currentTime.value = new Date().toLocaleString() | 143 | currentTime.value = new Date().toLocaleString() |
| 89 | 144 | ||
| 90 | - // 获取用户信息 | ||
| 91 | - const storedUserInfo = localStorage.getItem('userInfo') | ||
| 92 | - if (storedUserInfo) { | ||
| 93 | - userInfo.value = JSON.parse(storedUserInfo) | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | // 检查是否已登录 | 145 | // 检查是否已登录 |
| 97 | const token = localStorage.getItem('token') | 146 | const token = localStorage.getItem('token') |
| 98 | if (!token) { | 147 | if (!token) { |
| 99 | router.push('/') | 148 | router.push('/') |
| 149 | + return | ||
| 100 | } | 150 | } |
| 151 | + | ||
| 152 | + // 获取用户信息 | ||
| 153 | + fetchUserInfo() | ||
| 101 | }) | 154 | }) |
| 102 | 155 | ||
| 156 | +// 页面跳转函数 | ||
| 157 | +const navigateTo = (path: string) => { | ||
| 158 | + router.push(path).catch(err => { | ||
| 159 | + console.error('页面跳转失败:', err) | ||
| 160 | + }) | ||
| 161 | +} | ||
| 162 | + | ||
| 103 | const logout = () => { | 163 | const logout = () => { |
| 104 | // 清除本地存储 | 164 | // 清除本地存储 |
| 105 | localStorage.removeItem('token') | 165 | localStorage.removeItem('token') | ... | ... |
| ... | @@ -244,8 +244,13 @@ | ... | @@ -244,8 +244,13 @@ |
| 244 | </tr> | 244 | </tr> |
| 245 | </thead> | 245 | </thead> |
| 246 | <tbody> | 246 | <tbody> |
| 247 | +<<<<<<< .mine | ||
| 247 | <tr v-for="item in deliveryDetail.deliveryItems" :key="item.deliveryItemId"> | 248 | <tr v-for="item in deliveryDetail.deliveryItems" :key="item.deliveryItemId"> |
| 248 | <td>{{ item.productName }}</td> | 249 | <td>{{ item.productName }}</td> |
| 250 | +======= | ||
| 251 | + <tr v-for="item in deliveryDetail.deliveryItems" :key="item.itemId || item.deliveryId"> | ||
| 252 | + | ||
| 253 | +>>>>>>> .theirs | ||
| 249 | <td>{{ item.productCode }}</td> | 254 | <td>{{ item.productCode }}</td> |
| 250 | <td>{{ item.deliveryQty }}</td> | 255 | <td>{{ item.deliveryQty }}</td> |
| 251 | <td>{{ formatCurrency(item.deliveryPrice) }}</td> | 256 | <td>{{ formatCurrency(item.deliveryPrice) }}</td> |
| ... | @@ -627,6 +632,7 @@ | ... | @@ -627,6 +632,7 @@ |
| 627 | 632 | ||
| 628 | <script setup lang="ts"> | 633 | <script setup lang="ts"> |
| 629 | import { ref, reactive, computed, onMounted } from 'vue' | 634 | import { ref, reactive, computed, onMounted } from 'vue' |
| 635 | +import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 630 | import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery' | 636 | import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery' |
| 631 | import { dealerApi, type DealerInfo } from '../../api/dealer' | 637 | import { dealerApi, type DealerInfo } from '../../api/dealer' |
| 632 | import { productApi } from '../../api/product' | 638 | import { productApi } from '../../api/product' |
| ... | @@ -729,7 +735,12 @@ const formatCurrency = (amount: number) => { | ... | @@ -729,7 +735,12 @@ const formatCurrency = (amount: number) => { |
| 729 | // 消息提示 | 735 | // 消息提示 |
| 730 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { | 736 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { |
| 731 | console.log(`${type}: ${message}`) | 737 | console.log(`${type}: ${message}`) |
| 732 | - alert(message) | 738 | + ElMessage({ |
| 739 | + message, | ||
| 740 | + type, | ||
| 741 | + duration: 3000, | ||
| 742 | + showClose: true | ||
| 743 | + }) | ||
| 733 | } | 744 | } |
| 734 | 745 | ||
| 735 | // 获取页码数组 | 746 | // 获取页码数组 |
| ... | @@ -868,8 +879,78 @@ const handleDelete = async (delivery: DeliveryInfo) => { | ... | @@ -868,8 +879,78 @@ const handleDelete = async (delivery: DeliveryInfo) => { |
| 868 | } | 879 | } |
| 869 | 880 | ||
| 870 | 881 | ||
| 871 | -const handleExport = () => { | 882 | +const handleExport = async () => { |
| 872 | - showMessage('导出功能开发中...', 'warning') | 883 | + try { |
| 884 | + // 显示确认弹窗 | ||
| 885 | + await ElMessageBox.confirm( | ||
| 886 | + '确定要导出出库数据吗?导出将包含当前筛选条件下的所有数据。', | ||
| 887 | + '确认导出', | ||
| 888 | + { | ||
| 889 | + confirmButtonText: '确定导出', | ||
| 890 | + cancelButtonText: '取消', | ||
| 891 | + type: 'warning', | ||
| 892 | + center: true | ||
| 893 | + } | ||
| 894 | + ) | ||
| 895 | + | ||
| 896 | + // 用户确认后显示加载提示 | ||
| 897 | + const loadingMessage = ElMessage({ | ||
| 898 | + message: '正在导出数据,请稍候...', | ||
| 899 | + type: 'warning', | ||
| 900 | + duration: 0, // 不自动关闭 | ||
| 901 | + showClose: false | ||
| 902 | + }) | ||
| 903 | + | ||
| 904 | + try { | ||
| 905 | + // 准备导出参数 | ||
| 906 | + const exportParams = { | ||
| 907 | + deliveryNo: searchParams.deliveryNo, | ||
| 908 | + dealerCode: searchParams.dealerCode, | ||
| 909 | + dealerName: searchParams.dealerName, | ||
| 910 | + deliveryStatus: searchParams.deliveryStatus, | ||
| 911 | + warehouseCode: searchParams.warehouseCode, | ||
| 912 | + dataSource: searchParams.dataSource, | ||
| 913 | + deliveryStartDate: searchParams.deliveryStartDate, | ||
| 914 | + deliveryEndDate: searchParams.deliveryEndDate | ||
| 915 | + } | ||
| 916 | + | ||
| 917 | + // 调用导出接口 | ||
| 918 | + const response = await deliveryApi.exportDeliveries(exportParams) | ||
| 919 | + | ||
| 920 | + // 创建下载链接 | ||
| 921 | + const blob = new Blob([response], { | ||
| 922 | + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
| 923 | + }) | ||
| 924 | + const url = window.URL.createObjectURL(blob) | ||
| 925 | + const link = document.createElement('a') | ||
| 926 | + link.href = url | ||
| 927 | + | ||
| 928 | + // 生成文件名 | ||
| 929 | + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14) | ||
| 930 | + link.download = `出库数据_${timestamp}.xlsx` | ||
| 931 | + | ||
| 932 | + // 触发下载 | ||
| 933 | + document.body.appendChild(link) | ||
| 934 | + link.click() | ||
| 935 | + document.body.removeChild(link) | ||
| 936 | + window.URL.revokeObjectURL(url) | ||
| 937 | + | ||
| 938 | + // 关闭加载提示,显示成功消息 | ||
| 939 | + loadingMessage.close() | ||
| 940 | + ElMessage.success('导出成功!文件已开始下载') | ||
| 941 | + | ||
| 942 | + } catch (exportError) { | ||
| 943 | + // 关闭加载提示 | ||
| 944 | + loadingMessage.close() | ||
| 945 | + throw exportError | ||
| 946 | + } | ||
| 947 | + | ||
| 948 | + } catch (error) { | ||
| 949 | + if (error !== 'cancel') { | ||
| 950 | + console.error('导出失败:', error) | ||
| 951 | + ElMessage.error('导出失败,请重试') | ||
| 952 | + } | ||
| 953 | + } | ||
| 873 | } | 954 | } |
| 874 | 955 | ||
| 875 | // 新增出库相关函数 | 956 | // 新增出库相关函数 | ... | ... |
| 1 | +<template> | ||
| 2 | + <div class="exception-workorder-container"> | ||
| 3 | + <!-- 搜索区域 --> | ||
| 4 | + <div class="search-section"> | ||
| 5 | + <div class="search-row"> | ||
| 6 | + <div class="search-item"> | ||
| 7 | + <label>工单编号:</label> | ||
| 8 | + <input | ||
| 9 | + v-model="searchParams.workorderNo" | ||
| 10 | + class="search-input" | ||
| 11 | + placeholder="请输入工单编号" | ||
| 12 | + /> | ||
| 13 | + </div> | ||
| 14 | + <div class="search-item"> | ||
| 15 | + <label>关联订单号:</label> | ||
| 16 | + <input | ||
| 17 | + v-model="searchParams.orderNo" | ||
| 18 | + class="search-input" | ||
| 19 | + placeholder="请输入关联订单号" | ||
| 20 | + /> | ||
| 21 | + </div> | ||
| 22 | + <div class="search-item"> | ||
| 23 | + <label>经销商编码:</label> | ||
| 24 | + <input | ||
| 25 | + v-model="searchParams.dealerCode" | ||
| 26 | + class="search-input" | ||
| 27 | + placeholder="请输入经销商编码" | ||
| 28 | + /> | ||
| 29 | + </div> | ||
| 30 | + <div class="search-item"> | ||
| 31 | + <label>经销商名称:</label> | ||
| 32 | + <input | ||
| 33 | + v-model="searchParams.dealerName" | ||
| 34 | + class="search-input" | ||
| 35 | + placeholder="请输入经销商名称" | ||
| 36 | + /> | ||
| 37 | + </div> | ||
| 38 | + </div> | ||
| 39 | + <div class="search-row"> | ||
| 40 | + <div class="search-item"> | ||
| 41 | + <label>工单状态:</label> | ||
| 42 | + <select v-model="searchParams.workorderStatus" class="search-select"> | ||
| 43 | + <option value="">所有</option> | ||
| 44 | + <option v-for="(name, value) in WORKORDER_STATUS" :key="value" :value="Number(value)">{{ name }}</option> | ||
| 45 | + </select> | ||
| 46 | + </div> | ||
| 47 | + <div class="search-item"> | ||
| 48 | + <label>异常类型:</label> | ||
| 49 | + <select v-model="searchParams.exceptionType" class="search-select"> | ||
| 50 | + <option value="">所有</option> | ||
| 51 | + <option v-for="(name, value) in EXCEPTION_TYPE" :key="value" :value="Number(value)">{{ name }}</option> | ||
| 52 | + </select> | ||
| 53 | + </div> | ||
| 54 | + <div class="search-item"> | ||
| 55 | + <label>严重程度:</label> | ||
| 56 | + <select v-model="searchParams.severityLevel" class="search-select"> | ||
| 57 | + <option value="">所有</option> | ||
| 58 | + <option v-for="(name, value) in SEVERITY_LEVEL" :key="value" :value="Number(value)">{{ name }}</option> | ||
| 59 | + </select> | ||
| 60 | + </div> | ||
| 61 | + <div class="search-item"> | ||
| 62 | + <label>处理人:</label> | ||
| 63 | + <input | ||
| 64 | + v-model="searchParams.handlerUser" | ||
| 65 | + class="search-input" | ||
| 66 | + placeholder="请输入处理人" | ||
| 67 | + /> | ||
| 68 | + </div> | ||
| 69 | + <div class="search-actions"> | ||
| 70 | + <button @click="handleSearch" class="search-btn">🔍 搜索</button> | ||
| 71 | + <button @click="handleReset" class="reset-btn">🔄 重置</button> | ||
| 72 | + </div> | ||
| 73 | + </div> | ||
| 74 | + </div> | ||
| 75 | + | ||
| 76 | + <!-- 操作按钮区域 --> | ||
| 77 | + <div class="action-section"> | ||
| 78 | + <div class="action-buttons"> | ||
| 79 | + <button @click="handleAdd" class="action-btn primary">✨ 新增</button> | ||
| 80 | + <button @click="handleBatchProcess" class="action-btn success">🔧 批量处理</button> | ||
| 81 | + <button @click="handleExport" class="action-btn secondary">📋 导出</button> | ||
| 82 | + </div> | ||
| 83 | + </div> | ||
| 84 | + | ||
| 85 | + <!-- 数据表格 --> | ||
| 86 | + <div class="table-section"> | ||
| 87 | + <div class="table-header"> | ||
| 88 | + <div class="table-controls"> | ||
| 89 | + <button @click="handleTableSearch" class="control-btn">🔍</button> | ||
| 90 | + <button @click="handleTableRefresh" class="control-btn">🔄</button> | ||
| 91 | + <button @click="handleTableExport" class="control-btn">📋</button> | ||
| 92 | + <button @click="handleTableViewToggle" class="control-btn">⊞</button> | ||
| 93 | + </div> | ||
| 94 | + </div> | ||
| 95 | + | ||
| 96 | + <div class="table-container"> | ||
| 97 | + <div v-if="loading" class="loading-overlay"> | ||
| 98 | + <div class="loading-spinner">加载中...</div> | ||
| 99 | + </div> | ||
| 100 | + <table class="data-table"> | ||
| 101 | + <thead> | ||
| 102 | + <tr> | ||
| 103 | + <th> | ||
| 104 | + <input | ||
| 105 | + type="checkbox" | ||
| 106 | + class="select-all" | ||
| 107 | + v-model="selectAll" | ||
| 108 | + @change="handleSelectAll" | ||
| 109 | + /> | ||
| 110 | + </th> | ||
| 111 | + <th>工单ID</th> | ||
| 112 | + <th class="sortable">工单编号 ↕️</th> | ||
| 113 | + <th>关联订单号</th> | ||
| 114 | + <th>经销商名称</th> | ||
| 115 | + <th>工单状态</th> | ||
| 116 | + <th>异常类型</th> | ||
| 117 | + <th>严重程度</th> | ||
| 118 | + <th class="sortable">日期 ↕️</th> | ||
| 119 | + <th>操作</th> | ||
| 120 | + </tr> | ||
| 121 | + </thead> | ||
| 122 | + <tbody> | ||
| 123 | + <tr v-for="workorder in workorderList" :key="workorder.workorderId"> | ||
| 124 | + <td> | ||
| 125 | + <input | ||
| 126 | + type="checkbox" | ||
| 127 | + class="row-select" | ||
| 128 | + :value="workorder" | ||
| 129 | + v-model="selectedWorkorders" | ||
| 130 | + /> | ||
| 131 | + </td> | ||
| 132 | + <td>{{ workorder.workorderId }}</td> | ||
| 133 | + <td>{{ workorder.workorderNo }}</td> | ||
| 134 | + <td>{{ workorder.orderNo }}</td> | ||
| 135 | + <td>{{ workorder.dealerName }}</td> | ||
| 136 | + <td> | ||
| 137 | + <span | ||
| 138 | + class="status-badge" | ||
| 139 | + :class="getWorkorderStatusClass(workorder.workorderStatus)" | ||
| 140 | + > | ||
| 141 | + {{ workorder.workorderStatusName }} | ||
| 142 | + </span> | ||
| 143 | + </td> | ||
| 144 | + <td>{{ workorder.exceptionTypeName }}</td> | ||
| 145 | + <td> | ||
| 146 | + <span | ||
| 147 | + class="severity-badge" | ||
| 148 | + :class="getSeverityLevelClass(workorder.severityLevel)" | ||
| 149 | + > | ||
| 150 | + {{ workorder.severityLevelName }} | ||
| 151 | + </span> | ||
| 152 | + </td> | ||
| 153 | + <td>{{ formatDateTime(workorder.createTime) }}</td> | ||
| 154 | + <td> | ||
| 155 | + <div class="table-actions"> | ||
| 156 | + <button @click="handleEditStatus(workorder)" class="table-btn edit">编辑状态</button> | ||
| 157 | + <button @click="handleViewLogs(workorder)" class="table-btn info">处理日志</button> | ||
| 158 | + <button @click="handleViewDetail(workorder)" class="table-btn view">详情</button> | ||
| 159 | + </div> | ||
| 160 | + </td> | ||
| 161 | + </tr> | ||
| 162 | + </tbody> | ||
| 163 | + </table> | ||
| 164 | + </div> | ||
| 165 | + </div> | ||
| 166 | + | ||
| 167 | + <!-- 分页 --> | ||
| 168 | + <div class="pagination-section"> | ||
| 169 | + <div class="pagination-info"> | ||
| 170 | + <span>共 {{ pagination.total }} 条</span> | ||
| 171 | + <select v-model="pagination.pageSize" @change="handlePageSizeChange" class="page-size-select"> | ||
| 172 | + <option value="10">10条/页</option> | ||
| 173 | + <option value="20">20条/页</option> | ||
| 174 | + <option value="50">50条/页</option> | ||
| 175 | + <option value="100">100条/页</option> | ||
| 176 | + </select> | ||
| 177 | + </div> | ||
| 178 | + <div class="pagination-controls"> | ||
| 179 | + <button | ||
| 180 | + @click="handleCurrentChange(1)" | ||
| 181 | + :disabled="pagination.pageNum === 1" | ||
| 182 | + class="page-btn" | ||
| 183 | + >首页</button> | ||
| 184 | + <button | ||
| 185 | + @click="handleCurrentChange(pagination.pageNum - 1)" | ||
| 186 | + :disabled="pagination.pageNum === 1" | ||
| 187 | + class="page-btn" | ||
| 188 | + >上一页</button> | ||
| 189 | + <span class="page-info">{{ pagination.pageNum }} / {{ Math.ceil(pagination.total / pagination.pageSize) }}</span> | ||
| 190 | + <button | ||
| 191 | + @click="handleCurrentChange(pagination.pageNum + 1)" | ||
| 192 | + :disabled="pagination.pageNum >= Math.ceil(pagination.total / pagination.pageSize)" | ||
| 193 | + class="page-btn" | ||
| 194 | + >下一页</button> | ||
| 195 | + <button | ||
| 196 | + @click="handleCurrentChange(Math.ceil(pagination.total / pagination.pageSize))" | ||
| 197 | + :disabled="pagination.pageNum >= Math.ceil(pagination.total / pagination.pageSize)" | ||
| 198 | + class="page-btn" | ||
| 199 | + >末页</button> | ||
| 200 | + <span>跳至</span> | ||
| 201 | + <input | ||
| 202 | + v-model="jumpPage" | ||
| 203 | + @keyup.enter="handleJumpPage" | ||
| 204 | + class="jump-input" | ||
| 205 | + placeholder="页码" | ||
| 206 | + /> | ||
| 207 | + <span>页</span> | ||
| 208 | + </div> | ||
| 209 | + </div> | ||
| 210 | + | ||
| 211 | + <!-- 新增/编辑对话框 --> | ||
| 212 | + <el-dialog | ||
| 213 | + v-model="addDialogVisible" | ||
| 214 | + :title="isEdit ? '编辑异常工单' : '新增异常工单'" | ||
| 215 | + width="600px" | ||
| 216 | + class="add-dialog" | ||
| 217 | + > | ||
| 218 | + <el-form | ||
| 219 | + ref="addFormRef" | ||
| 220 | + :model="addFormData" | ||
| 221 | + :rules="addFormRules" | ||
| 222 | + label-width="120px" | ||
| 223 | + class="add-form" | ||
| 224 | + > | ||
| 225 | + <el-form-item label="工单编号" prop="workorderNo"> | ||
| 226 | + <el-input v-model="addFormData.workorderNo" placeholder="请输入工单编号" /> | ||
| 227 | + </el-form-item> | ||
| 228 | + <el-form-item label="关联订单号" prop="orderNo"> | ||
| 229 | + <el-input v-model="addFormData.orderNo" placeholder="请输入关联订单号" /> | ||
| 230 | + </el-form-item> | ||
| 231 | + <el-form-item label="经销商编码" prop="dealerCode"> | ||
| 232 | + <el-input v-model="addFormData.dealerCode" placeholder="请输入经销商编码" /> | ||
| 233 | + </el-form-item> | ||
| 234 | + <el-form-item label="经销商名称" prop="dealerName"> | ||
| 235 | + <el-input v-model="addFormData.dealerName" placeholder="请输入经销商名称" /> | ||
| 236 | + </el-form-item> | ||
| 237 | + <el-form-item label="异常类型" prop="exceptionType"> | ||
| 238 | + <el-select v-model="addFormData.exceptionType" placeholder="请选择异常类型"> | ||
| 239 | + <el-option | ||
| 240 | + v-for="(name, value) in EXCEPTION_TYPE" | ||
| 241 | + :key="value" | ||
| 242 | + :label="name" | ||
| 243 | + :value="Number(value)" | ||
| 244 | + /> | ||
| 245 | + </el-select> | ||
| 246 | + </el-form-item> | ||
| 247 | + <el-form-item label="严重程度" prop="severityLevel"> | ||
| 248 | + <el-select v-model="addFormData.severityLevel" placeholder="请选择严重程度"> | ||
| 249 | + <el-option | ||
| 250 | + v-for="(name, value) in SEVERITY_LEVEL" | ||
| 251 | + :key="value" | ||
| 252 | + :label="name" | ||
| 253 | + :value="Number(value)" | ||
| 254 | + /> | ||
| 255 | + </el-select> | ||
| 256 | + </el-form-item> | ||
| 257 | + <el-form-item label="工单状态" prop="workorderStatus"> | ||
| 258 | + <el-select v-model="addFormData.workorderStatus" placeholder="请选择工单状态"> | ||
| 259 | + <el-option | ||
| 260 | + v-for="(name, value) in WORKORDER_STATUS" | ||
| 261 | + :key="value" | ||
| 262 | + :label="name" | ||
| 263 | + :value="Number(value)" | ||
| 264 | + /> | ||
| 265 | + </el-select> | ||
| 266 | + </el-form-item> | ||
| 267 | + <el-form-item label="处理人" prop="handlerUser"> | ||
| 268 | + <el-input v-model="addFormData.handlerUser" placeholder="请输入处理人" /> | ||
| 269 | + </el-form-item> | ||
| 270 | + <el-form-item label="预计完成时间" prop="expectCompleteTime"> | ||
| 271 | + <el-date-picker | ||
| 272 | + v-model="addFormData.expectCompleteTime" | ||
| 273 | + type="datetime" | ||
| 274 | + placeholder="选择预计完成时间" | ||
| 275 | + format="YYYY-MM-DD HH:mm:ss" | ||
| 276 | + value-format="YYYY-MM-DD HH:mm:ss" | ||
| 277 | + style="width: 100%" | ||
| 278 | + /> | ||
| 279 | + </el-form-item> | ||
| 280 | + <el-form-item label="异常描述" prop="exceptionDesc"> | ||
| 281 | + <el-input | ||
| 282 | + v-model="addFormData.exceptionDesc" | ||
| 283 | + type="textarea" | ||
| 284 | + :rows="3" | ||
| 285 | + placeholder="请输入异常描述" | ||
| 286 | + /> | ||
| 287 | + </el-form-item> | ||
| 288 | + <el-form-item label="处理建议" prop="handleSuggest"> | ||
| 289 | + <el-input | ||
| 290 | + v-model="addFormData.handleSuggest" | ||
| 291 | + type="textarea" | ||
| 292 | + :rows="3" | ||
| 293 | + placeholder="请输入处理建议" | ||
| 294 | + /> | ||
| 295 | + </el-form-item> | ||
| 296 | + <el-form-item label="数据来源" prop="dataSource"> | ||
| 297 | + <el-input v-model="addFormData.dataSource" placeholder="请输入数据来源" /> | ||
| 298 | + </el-form-item> | ||
| 299 | + </el-form> | ||
| 300 | + <template #footer> | ||
| 301 | + <div class="dialog-footer"> | ||
| 302 | + <el-button @click="closeAddDialog">取消</el-button> | ||
| 303 | + <el-button type="primary" @click="handleSubmitAdd">确定</el-button> | ||
| 304 | + </div> | ||
| 305 | + </template> | ||
| 306 | + </el-dialog> | ||
| 307 | + | ||
| 308 | + <!-- 状态更新对话框 --> | ||
| 309 | + <el-dialog | ||
| 310 | + v-model="statusDialogVisible" | ||
| 311 | + title="编辑状态" | ||
| 312 | + width="500px" | ||
| 313 | + class="status-dialog" | ||
| 314 | + > | ||
| 315 | + <el-form | ||
| 316 | + ref="statusFormRef" | ||
| 317 | + :model="statusFormData" | ||
| 318 | + :rules="statusFormRules" | ||
| 319 | + label-width="100px" | ||
| 320 | + class="status-form" | ||
| 321 | + > | ||
| 322 | + <el-form-item label="工单状态" prop="workorderStatus"> | ||
| 323 | + <el-select v-model="statusFormData.workorderStatus" placeholder="请选择工单状态"> | ||
| 324 | + <el-option | ||
| 325 | + v-for="(name, value) in WORKORDER_STATUS" | ||
| 326 | + :key="value" | ||
| 327 | + :label="name" | ||
| 328 | + :value="Number(value)" | ||
| 329 | + /> | ||
| 330 | + </el-select> | ||
| 331 | + </el-form-item> | ||
| 332 | + <el-form-item label="处理人" prop="handlerUser"> | ||
| 333 | + <el-input v-model="statusFormData.handlerUser" placeholder="请输入处理人" /> | ||
| 334 | + </el-form-item> | ||
| 335 | + <el-form-item label="处理意见" prop="handleOpinion"> | ||
| 336 | + <el-input | ||
| 337 | + v-model="statusFormData.handleOpinion" | ||
| 338 | + type="textarea" | ||
| 339 | + :rows="3" | ||
| 340 | + placeholder="请输入处理意见" | ||
| 341 | + /> | ||
| 342 | + </el-form-item> | ||
| 343 | + <el-form-item label="附件URL" prop="attachUrl"> | ||
| 344 | + <el-input v-model="statusFormData.attachUrl" placeholder="请输入附件URL" /> | ||
| 345 | + </el-form-item> | ||
| 346 | + </el-form> | ||
| 347 | + <template #footer> | ||
| 348 | + <div class="dialog-footer"> | ||
| 349 | + <el-button @click="closeStatusDialog">取消</el-button> | ||
| 350 | + <el-button type="primary" @click="handleSubmitStatus">确定</el-button> | ||
| 351 | + </div> | ||
| 352 | + </template> | ||
| 353 | + </el-dialog> | ||
| 354 | + | ||
| 355 | + <!-- 详情对话框 --> | ||
| 356 | + <el-dialog | ||
| 357 | + v-model="detailDialogVisible" | ||
| 358 | + title="异常工单详情" | ||
| 359 | + width="800px" | ||
| 360 | + class="detail-dialog" | ||
| 361 | + > | ||
| 362 | + <div v-if="workorderDetail" class="detail-content"> | ||
| 363 | + <div class="detail-section"> | ||
| 364 | + <h4>工单基本信息</h4> | ||
| 365 | + <div class="detail-grid"> | ||
| 366 | + <div class="detail-item"> | ||
| 367 | + <label>工单编号:</label> | ||
| 368 | + <span>{{ workorderDetail.workorderNo }}</span> | ||
| 369 | + </div> | ||
| 370 | + <div class="detail-item"> | ||
| 371 | + <label>关联订单号:</label> | ||
| 372 | + <span>{{ workorderDetail.orderNo }}</span> | ||
| 373 | + </div> | ||
| 374 | + <div class="detail-item"> | ||
| 375 | + <label>经销商编码:</label> | ||
| 376 | + <span>{{ workorderDetail.dealerCode }}</span> | ||
| 377 | + </div> | ||
| 378 | + <div class="detail-item"> | ||
| 379 | + <label>经销商名称:</label> | ||
| 380 | + <span>{{ workorderDetail.dealerName }}</span> | ||
| 381 | + </div> | ||
| 382 | + <div class="detail-item"> | ||
| 383 | + <label>异常类型:</label> | ||
| 384 | + <span>{{ workorderDetail.exceptionTypeName }}</span> | ||
| 385 | + </div> | ||
| 386 | + <div class="detail-item"> | ||
| 387 | + <label>严重程度:</label> | ||
| 388 | + <span>{{ workorderDetail.severityLevelName }}</span> | ||
| 389 | + </div> | ||
| 390 | + <div class="detail-item"> | ||
| 391 | + <label>工单状态:</label> | ||
| 392 | + <span>{{ workorderDetail.workorderStatusName }}</span> | ||
| 393 | + </div> | ||
| 394 | + <div class="detail-item"> | ||
| 395 | + <label>处理人:</label> | ||
| 396 | + <span>{{ workorderDetail.handlerUser || '未分配' }}</span> | ||
| 397 | + </div> | ||
| 398 | + <div class="detail-item"> | ||
| 399 | + <label>创建时间:</label> | ||
| 400 | + <span>{{ formatDateTime(workorderDetail.createTime) }}</span> | ||
| 401 | + </div> | ||
| 402 | + <div class="detail-item"> | ||
| 403 | + <label>预计完成时间:</label> | ||
| 404 | + <span>{{ workorderDetail.expectCompleteTime ? formatDateTime(workorderDetail.expectCompleteTime) : '未设置' }}</span> | ||
| 405 | + </div> | ||
| 406 | + </div> | ||
| 407 | + </div> | ||
| 408 | + | ||
| 409 | + <div class="detail-section" v-if="workorderDetail.exceptionDesc"> | ||
| 410 | + <h4>异常描述</h4> | ||
| 411 | + <p class="detail-text">{{ workorderDetail.exceptionDesc }}</p> | ||
| 412 | + </div> | ||
| 413 | + | ||
| 414 | + <div class="detail-section" v-if="workorderDetail.handleSuggest"> | ||
| 415 | + <h4>处理建议</h4> | ||
| 416 | + <p class="detail-text">{{ workorderDetail.handleSuggest }}</p> | ||
| 417 | + </div> | ||
| 418 | + | ||
| 419 | + <div class="detail-section" v-if="workorderDetail.workorderLogs && workorderDetail.workorderLogs.length > 0"> | ||
| 420 | + <h4>处理日志</h4> | ||
| 421 | + <el-table :data="workorderDetail.workorderLogs" class="log-table"> | ||
| 422 | + <el-table-column prop="handleUser" label="处理人" width="120" /> | ||
| 423 | + <el-table-column prop="handleTime" label="处理时间" width="180"> | ||
| 424 | + <template #default="{ row }"> | ||
| 425 | + {{ formatDateTime(row.handleTime) }} | ||
| 426 | + </template> | ||
| 427 | + </el-table-column> | ||
| 428 | + <el-table-column prop="beforeStatusName" label="处理前状态" width="120" /> | ||
| 429 | + <el-table-column prop="afterStatusName" label="处理后状态" width="120" /> | ||
| 430 | + <el-table-column prop="handleOpinion" label="处理意见" /> | ||
| 431 | + </el-table> | ||
| 432 | + </div> | ||
| 433 | + </div> | ||
| 434 | + </el-dialog> | ||
| 435 | + | ||
| 436 | + <!-- 处理日志对话框 --> | ||
| 437 | + <el-dialog | ||
| 438 | + v-model="logsDialogVisible" | ||
| 439 | + title="处理日志" | ||
| 440 | + width="900px" | ||
| 441 | + class="logs-dialog" | ||
| 442 | + > | ||
| 443 | + <el-table :data="workorderLogs" class="logs-table"> | ||
| 444 | + <el-table-column prop="handleUser" label="处理人" width="120" /> | ||
| 445 | + <el-table-column prop="handleTime" label="处理时间" width="180"> | ||
| 446 | + <template #default="{ row }"> | ||
| 447 | + {{ formatDateTime(row.handleTime) }} | ||
| 448 | + </template> | ||
| 449 | + </el-table-column> | ||
| 450 | + <el-table-column prop="beforeStatusName" label="处理前状态" width="120" /> | ||
| 451 | + <el-table-column prop="afterStatusName" label="处理后状态" width="120" /> | ||
| 452 | + <el-table-column prop="handleOpinion" label="处理意见" /> | ||
| 453 | + <el-table-column prop="attachUrl" label="附件" width="100"> | ||
| 454 | + <template #default="{ row }"> | ||
| 455 | + <el-button v-if="row.attachUrl" type="primary" size="small" @click="handleDownload(row.attachUrl)"> | ||
| 456 | + 下载 | ||
| 457 | + </el-button> | ||
| 458 | + </template> | ||
| 459 | + </el-table-column> | ||
| 460 | + </el-table> | ||
| 461 | + </el-dialog> | ||
| 462 | + </div> | ||
| 463 | +</template> | ||
| 464 | + | ||
| 465 | +<script setup lang="ts"> | ||
| 466 | +import { ref, reactive, computed, onMounted } from 'vue' | ||
| 467 | +import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 468 | +import { Search, Refresh, Plus, Operation, Download } from '@element-plus/icons-vue' | ||
| 469 | +import { | ||
| 470 | + exceptionWorkorderApi, | ||
| 471 | + type ExceptionWorkorderInfo, | ||
| 472 | + type ExceptionWorkorderQueryReq, | ||
| 473 | + type ExceptionWorkorderAddReq, | ||
| 474 | + type ExceptionWorkorderStatusUpdateReq, | ||
| 475 | + EXCEPTION_TYPE, | ||
| 476 | + SEVERITY_LEVEL, | ||
| 477 | + WORKORDER_STATUS, | ||
| 478 | + getSeverityLevelColor, | ||
| 479 | + getWorkorderStatusColor | ||
| 480 | +} from '../../api/exceptionWorkorder' | ||
| 481 | +import dayjs from 'dayjs' | ||
| 482 | + | ||
| 483 | +// 响应式数据 | ||
| 484 | +const loading = ref(false) | ||
| 485 | +const workorderList = ref<ExceptionWorkorderInfo[]>([]) | ||
| 486 | +const selectedWorkorders = ref<ExceptionWorkorderInfo[]>([]) | ||
| 487 | +const workorderDetail = ref<ExceptionWorkorderInfo | null>(null) | ||
| 488 | +const workorderLogs = ref<any[]>([]) | ||
| 489 | +const selectAll = ref(false) | ||
| 490 | +const jumpPage = ref<number | string>('') | ||
| 491 | + | ||
| 492 | +// 分页数据 | ||
| 493 | +const pagination = reactive({ | ||
| 494 | + pageNum: 1, | ||
| 495 | + pageSize: 10, | ||
| 496 | + total: 0 | ||
| 497 | +}) | ||
| 498 | + | ||
| 499 | +// 搜索参数 | ||
| 500 | +const searchParams = reactive<ExceptionWorkorderQueryReq>({ | ||
| 501 | + workorderNo: '', | ||
| 502 | + orderNo: '', | ||
| 503 | + dealerCode: '', | ||
| 504 | + dealerName: '', | ||
| 505 | + workorderStatus: undefined, | ||
| 506 | + exceptionType: undefined, | ||
| 507 | + severityLevel: undefined, | ||
| 508 | + handlerUser: '', | ||
| 509 | + startTime: '', | ||
| 510 | + endTime: '', | ||
| 511 | + pageNum: 1, | ||
| 512 | + pageSize: 10 | ||
| 513 | +}) | ||
| 514 | + | ||
| 515 | +// 对话框状态 | ||
| 516 | +const addDialogVisible = ref(false) | ||
| 517 | +const statusDialogVisible = ref(false) | ||
| 518 | +const detailDialogVisible = ref(false) | ||
| 519 | +const logsDialogVisible = ref(false) | ||
| 520 | +const isEdit = ref(false) | ||
| 521 | + | ||
| 522 | +// 新增表单数据 | ||
| 523 | +const addFormData = reactive<ExceptionWorkorderAddReq>({ | ||
| 524 | + workorderNo: '', | ||
| 525 | + orderNo: '', | ||
| 526 | + dealerCode: '', | ||
| 527 | + dealerName: '', | ||
| 528 | + exceptionType: 1, | ||
| 529 | + severityLevel: 1, | ||
| 530 | + workorderStatus: 1, | ||
| 531 | + expectCompleteTime: '', | ||
| 532 | + handlerUser: '', | ||
| 533 | + exceptionDesc: '', | ||
| 534 | + handleSuggest: '', | ||
| 535 | + dataSource: '' | ||
| 536 | +}) | ||
| 537 | + | ||
| 538 | +// 状态更新表单数据 | ||
| 539 | +const statusFormData = reactive<ExceptionWorkorderStatusUpdateReq>({ | ||
| 540 | + workorderId: 0, | ||
| 541 | + workorderStatus: 1, | ||
| 542 | + handlerUser: '', | ||
| 543 | + handleOpinion: '', | ||
| 544 | + attachUrl: '' | ||
| 545 | +}) | ||
| 546 | + | ||
| 547 | +// 表单验证规则 | ||
| 548 | +const addFormRules = { | ||
| 549 | + workorderNo: [ | ||
| 550 | + { required: true, message: '请输入工单编号', trigger: 'blur' } | ||
| 551 | + ], | ||
| 552 | + exceptionType: [ | ||
| 553 | + { required: true, message: '请选择异常类型', trigger: 'change' } | ||
| 554 | + ], | ||
| 555 | + severityLevel: [ | ||
| 556 | + { required: true, message: '请选择严重程度', trigger: 'change' } | ||
| 557 | + ] | ||
| 558 | +} | ||
| 559 | + | ||
| 560 | +const statusFormRules = { | ||
| 561 | + workorderStatus: [ | ||
| 562 | + { required: true, message: '请选择工单状态', trigger: 'change' } | ||
| 563 | + ] | ||
| 564 | +} | ||
| 565 | + | ||
| 566 | +// 获取工单状态类型 | ||
| 567 | +const getWorkorderStatusType = (status: number) => { | ||
| 568 | + switch (status) { | ||
| 569 | + case 1: return 'warning' // 待处理 | ||
| 570 | + case 2: return 'primary' // 处理中 | ||
| 571 | + case 3: return 'success' // 已解决 | ||
| 572 | + case 4: return 'info' // 已关闭 | ||
| 573 | + default: return 'info' | ||
| 574 | + } | ||
| 575 | +} | ||
| 576 | + | ||
| 577 | +// 获取严重程度类型 | ||
| 578 | +const getSeverityLevelType = (level: number) => { | ||
| 579 | + switch (level) { | ||
| 580 | + case 1: return 'danger' // 高 | ||
| 581 | + case 2: return 'warning' // 中 | ||
| 582 | + case 3: return 'success' // 低 | ||
| 583 | + default: return 'info' | ||
| 584 | + } | ||
| 585 | +} | ||
| 586 | + | ||
| 587 | +// 获取异常工单列表 | ||
| 588 | +const fetchWorkorders = async () => { | ||
| 589 | + try { | ||
| 590 | + loading.value = true | ||
| 591 | + const params = { | ||
| 592 | + ...searchParams, | ||
| 593 | + pageNum: pagination.pageNum, | ||
| 594 | + pageSize: pagination.pageSize | ||
| 595 | + } | ||
| 596 | + const response = await exceptionWorkorderApi.getExceptionWorkorderList(params) as any | ||
| 597 | + console.log('异常工单列表API响应:', response) | ||
| 598 | + | ||
| 599 | + if (response && response.records) { | ||
| 600 | + workorderList.value = response.records | ||
| 601 | + pagination.total = response.total | ||
| 602 | + } else if (response && Array.isArray(response)) { | ||
| 603 | + // 如果直接返回数组 | ||
| 604 | + workorderList.value = response | ||
| 605 | + pagination.total = response.length | ||
| 606 | + } else { | ||
| 607 | + workorderList.value = [] | ||
| 608 | + pagination.total = 0 | ||
| 609 | + } | ||
| 610 | + } catch (error) { | ||
| 611 | + console.error('获取异常工单列表失败:', error) | ||
| 612 | + ElMessage.error('获取异常工单列表失败') | ||
| 613 | + } finally { | ||
| 614 | + loading.value = false | ||
| 615 | + } | ||
| 616 | +} | ||
| 617 | + | ||
| 618 | +// 搜索 | ||
| 619 | +const handleSearch = () => { | ||
| 620 | + pagination.pageNum = 1 | ||
| 621 | + fetchWorkorders() | ||
| 622 | +} | ||
| 623 | + | ||
| 624 | +// 重置 | ||
| 625 | +const handleReset = () => { | ||
| 626 | + Object.assign(searchParams, { | ||
| 627 | + workorderNo: '', | ||
| 628 | + orderNo: '', | ||
| 629 | + dealerCode: '', | ||
| 630 | + dealerName: '', | ||
| 631 | + workorderStatus: undefined, | ||
| 632 | + exceptionType: undefined, | ||
| 633 | + severityLevel: undefined, | ||
| 634 | + handlerUser: '', | ||
| 635 | + startTime: '', | ||
| 636 | + endTime: '' | ||
| 637 | + }) | ||
| 638 | + pagination.pageNum = 1 | ||
| 639 | + fetchWorkorders() | ||
| 640 | +} | ||
| 641 | + | ||
| 642 | +// 新增 | ||
| 643 | +const handleAdd = () => { | ||
| 644 | + isEdit.value = false | ||
| 645 | + Object.assign(addFormData, { | ||
| 646 | + workorderNo: '', | ||
| 647 | + orderNo: '', | ||
| 648 | + dealerCode: '', | ||
| 649 | + dealerName: '', | ||
| 650 | + exceptionType: 1, | ||
| 651 | + severityLevel: 1, | ||
| 652 | + workorderStatus: 1, | ||
| 653 | + expectCompleteTime: '', | ||
| 654 | + handlerUser: '', | ||
| 655 | + exceptionDesc: '', | ||
| 656 | + handleSuggest: '', | ||
| 657 | + dataSource: '' | ||
| 658 | + }) | ||
| 659 | + addDialogVisible.value = true | ||
| 660 | +} | ||
| 661 | + | ||
| 662 | +// 编辑状态 | ||
| 663 | +const handleEditStatus = (row: ExceptionWorkorderInfo) => { | ||
| 664 | + statusFormData.workorderId = row.workorderId | ||
| 665 | + statusFormData.workorderStatus = row.workorderStatus | ||
| 666 | + statusFormData.handlerUser = row.handlerUser || '' | ||
| 667 | + statusFormData.handleOpinion = '' | ||
| 668 | + statusFormData.attachUrl = '' | ||
| 669 | + statusDialogVisible.value = true | ||
| 670 | +} | ||
| 671 | + | ||
| 672 | +// 查看详情 | ||
| 673 | +const handleViewDetail = async (row: ExceptionWorkorderInfo) => { | ||
| 674 | + try { | ||
| 675 | + const response = await exceptionWorkorderApi.getExceptionWorkorderDetail(row.workorderId) as any | ||
| 676 | + console.log('异常工单详情API响应:', response) | ||
| 677 | + | ||
| 678 | + if (response && response.workorderId) { | ||
| 679 | + workorderDetail.value = response | ||
| 680 | + detailDialogVisible.value = true | ||
| 681 | + } else { | ||
| 682 | + ElMessage.error('获取异常工单详情失败') | ||
| 683 | + } | ||
| 684 | + } catch (error) { | ||
| 685 | + console.error('获取异常工单详情失败:', error) | ||
| 686 | + ElMessage.error('获取异常工单详情失败, 请重试') | ||
| 687 | + } | ||
| 688 | +} | ||
| 689 | + | ||
| 690 | +// 查看处理日志 | ||
| 691 | +const handleViewLogs = async (row: ExceptionWorkorderInfo) => { | ||
| 692 | + try { | ||
| 693 | + const response = await exceptionWorkorderApi.getExceptionWorkorderDetail(row.workorderId) as any | ||
| 694 | + console.log('处理日志API响应:', response) | ||
| 695 | + | ||
| 696 | + if (response && response.workorderLogs) { | ||
| 697 | + workorderLogs.value = response.workorderLogs | ||
| 698 | + logsDialogVisible.value = true | ||
| 699 | + } else { | ||
| 700 | + ElMessage.error('获取处理日志失败') | ||
| 701 | + } | ||
| 702 | + } catch (error) { | ||
| 703 | + console.error('获取处理日志失败:', error) | ||
| 704 | + ElMessage.error('获取处理日志失败, 请重试') | ||
| 705 | + } | ||
| 706 | +} | ||
| 707 | + | ||
| 708 | +// 批量处理 | ||
| 709 | +const handleBatchProcess = () => { | ||
| 710 | + if (selectedWorkorders.value.length === 0) { | ||
| 711 | + ElMessage.warning('请选择要处理的工单') | ||
| 712 | + return | ||
| 713 | + } | ||
| 714 | + | ||
| 715 | + ElMessageBox.confirm( | ||
| 716 | + `确定要批量处理选中的 ${selectedWorkorders.value.length} 个工单吗?`, | ||
| 717 | + '批量处理确认', | ||
| 718 | + { | ||
| 719 | + confirmButtonText: '确定', | ||
| 720 | + cancelButtonText: '取消', | ||
| 721 | + type: 'warning' | ||
| 722 | + } | ||
| 723 | + ).then(async () => { | ||
| 724 | + try { | ||
| 725 | + const workorderIds = selectedWorkorders.value.map(item => item.workorderId) | ||
| 726 | + await exceptionWorkorderApi.batchUpdateWorkorderStatus(workorderIds, 2, '系统管理员') | ||
| 727 | + ElMessage.success('批量处理成功') | ||
| 728 | + fetchWorkorders() | ||
| 729 | + } catch (error) { | ||
| 730 | + console.error('批量处理失败:', error) | ||
| 731 | + ElMessage.error('批量处理失败') | ||
| 732 | + } | ||
| 733 | + }) | ||
| 734 | +} | ||
| 735 | + | ||
| 736 | +// 导出 | ||
| 737 | +const handleExport = () => { | ||
| 738 | + ElMessage.info('导出功能开发中...') | ||
| 739 | +} | ||
| 740 | + | ||
| 741 | +// 提交新增 | ||
| 742 | +const handleSubmitAdd = async () => { | ||
| 743 | + try { | ||
| 744 | + await exceptionWorkorderApi.addExceptionWorkorder(addFormData) | ||
| 745 | + ElMessage.success('新增异常工单成功') | ||
| 746 | + closeAddDialog() | ||
| 747 | + fetchWorkorders() | ||
| 748 | + } catch (error) { | ||
| 749 | + console.error('新增异常工单失败:', error) | ||
| 750 | + ElMessage.error('新增异常工单失败') | ||
| 751 | + } | ||
| 752 | +} | ||
| 753 | + | ||
| 754 | +// 提交状态更新 | ||
| 755 | +const handleSubmitStatus = async () => { | ||
| 756 | + try { | ||
| 757 | + await exceptionWorkorderApi.updateWorkorderStatus(statusFormData) | ||
| 758 | + ElMessage.success('更新工单状态成功') | ||
| 759 | + closeStatusDialog() | ||
| 760 | + fetchWorkorders() | ||
| 761 | + } catch (error) { | ||
| 762 | + console.error('更新工单状态失败:', error) | ||
| 763 | + ElMessage.error('更新工单状态失败') | ||
| 764 | + } | ||
| 765 | +} | ||
| 766 | + | ||
| 767 | +// 关闭对话框 | ||
| 768 | +const closeAddDialog = () => { | ||
| 769 | + addDialogVisible.value = false | ||
| 770 | +} | ||
| 771 | + | ||
| 772 | +const closeStatusDialog = () => { | ||
| 773 | + statusDialogVisible.value = false | ||
| 774 | +} | ||
| 775 | + | ||
| 776 | +// 选择变化 | ||
| 777 | +const handleSelectionChange = (selection: ExceptionWorkorderInfo[]) => { | ||
| 778 | + selectedWorkorders.value = selection | ||
| 779 | +} | ||
| 780 | + | ||
| 781 | +// 分页变化 | ||
| 782 | +const handleSizeChange = (size: number) => { | ||
| 783 | + pagination.pageSize = size | ||
| 784 | + pagination.pageNum = 1 | ||
| 785 | + fetchWorkorders() | ||
| 786 | +} | ||
| 787 | + | ||
| 788 | +const handleCurrentChange = (page: number) => { | ||
| 789 | + pagination.pageNum = page | ||
| 790 | + fetchWorkorders() | ||
| 791 | +} | ||
| 792 | + | ||
| 793 | +// 下载附件 | ||
| 794 | +const handleDownload = (url: string) => { | ||
| 795 | + window.open(url, '_blank') | ||
| 796 | +} | ||
| 797 | + | ||
| 798 | +// 格式化日期时间 | ||
| 799 | +const formatDateTime = (dateTime: string) => { | ||
| 800 | + if (!dateTime) return '-' | ||
| 801 | + return dayjs(dateTime).format('YYYY-MM-DD HH:mm:ss') | ||
| 802 | +} | ||
| 803 | + | ||
| 804 | +// 获取工单状态样式类 | ||
| 805 | +const getWorkorderStatusClass = (status: number) => { | ||
| 806 | + switch (status) { | ||
| 807 | + case 1: return 'pending' // 待处理 | ||
| 808 | + case 2: return 'processing' // 处理中 | ||
| 809 | + case 3: return 'resolved' // 已解决 | ||
| 810 | + case 4: return 'closed' // 已关闭 | ||
| 811 | + default: return 'unknown' | ||
| 812 | + } | ||
| 813 | +} | ||
| 814 | + | ||
| 815 | +// 获取严重程度样式类 | ||
| 816 | +const getSeverityLevelClass = (level: number) => { | ||
| 817 | + switch (level) { | ||
| 818 | + case 1: return 'high' // 高 | ||
| 819 | + case 2: return 'medium' // 中 | ||
| 820 | + case 3: return 'low' // 低 | ||
| 821 | + default: return 'unknown' | ||
| 822 | + } | ||
| 823 | +} | ||
| 824 | + | ||
| 825 | +// 表格操作方法 | ||
| 826 | +const handleSelectAll = () => { | ||
| 827 | + if (selectAll.value) { | ||
| 828 | + selectedWorkorders.value = [...workorderList.value] | ||
| 829 | + } else { | ||
| 830 | + selectedWorkorders.value = [] | ||
| 831 | + } | ||
| 832 | +} | ||
| 833 | + | ||
| 834 | +const handleTableSearch = () => { | ||
| 835 | + handleSearch() | ||
| 836 | +} | ||
| 837 | + | ||
| 838 | +const handleTableRefresh = () => { | ||
| 839 | + fetchWorkorders() | ||
| 840 | +} | ||
| 841 | + | ||
| 842 | +const handleTableExport = () => { | ||
| 843 | + handleExport() | ||
| 844 | +} | ||
| 845 | + | ||
| 846 | +const handleTableViewToggle = () => { | ||
| 847 | + // 表格视图切换逻辑 | ||
| 848 | + console.log('表格视图切换') | ||
| 849 | +} | ||
| 850 | + | ||
| 851 | +const handleJumpPage = () => { | ||
| 852 | + const page = Number(jumpPage.value) | ||
| 853 | + if (page && page > 0 && page <= Math.ceil(pagination.total / pagination.pageSize)) { | ||
| 854 | + handleCurrentChange(page) | ||
| 855 | + jumpPage.value = '' | ||
| 856 | + } | ||
| 857 | +} | ||
| 858 | + | ||
| 859 | +const handlePageSizeChange = () => { | ||
| 860 | + pagination.pageNum = 1 | ||
| 861 | + fetchWorkorders() | ||
| 862 | +} | ||
| 863 | + | ||
| 864 | +// 组件挂载 | ||
| 865 | +onMounted(() => { | ||
| 866 | + fetchWorkorders() | ||
| 867 | +}) | ||
| 868 | +</script> | ||
| 869 | + | ||
| 870 | +<style scoped> | ||
| 871 | +.exception-workorder-container { | ||
| 872 | + padding: 0px; | ||
| 873 | + background: #f5f5f5; | ||
| 874 | + min-height: 100vh; | ||
| 875 | +} | ||
| 876 | + | ||
| 877 | +/* 搜索区域样式 */ | ||
| 878 | +.search-section { | ||
| 879 | + background: white; | ||
| 880 | + padding: 16px 20px; | ||
| 881 | + border-bottom: 1px solid #e0e0e0; | ||
| 882 | +} | ||
| 883 | + | ||
| 884 | +.search-row { | ||
| 885 | + display: flex; | ||
| 886 | + gap: 25px; | ||
| 887 | + align-items: center; | ||
| 888 | + flex-wrap: wrap; | ||
| 889 | + margin-bottom: 12px; | ||
| 890 | +} | ||
| 891 | + | ||
| 892 | +.search-row:last-child { | ||
| 893 | + margin-bottom: 0; | ||
| 894 | +} | ||
| 895 | + | ||
| 896 | +.search-item { | ||
| 897 | + display: flex; | ||
| 898 | + align-items: center; | ||
| 899 | + gap: 8px; | ||
| 900 | + flex: 0 0 auto; | ||
| 901 | +} | ||
| 902 | + | ||
| 903 | +.search-item label { | ||
| 904 | + color: #666; | ||
| 905 | + font-size: 14px; | ||
| 906 | + width: 90px; | ||
| 907 | + white-space: nowrap; | ||
| 908 | + text-align: right; | ||
| 909 | +} | ||
| 910 | + | ||
| 911 | +.search-input { | ||
| 912 | + width: 180px; | ||
| 913 | + height: 32px; | ||
| 914 | + border: 1px solid #ddd; | ||
| 915 | + border-radius: 4px; | ||
| 916 | + padding: 0 8px; | ||
| 917 | + font-size: 14px; | ||
| 918 | +} | ||
| 919 | + | ||
| 920 | +.search-select { | ||
| 921 | + width: 160px; | ||
| 922 | + height: 32px; | ||
| 923 | + border: 1px solid #ddd; | ||
| 924 | + border-radius: 4px; | ||
| 925 | + padding: 0 8px; | ||
| 926 | + font-size: 14px; | ||
| 927 | + background: white; | ||
| 928 | +} | ||
| 929 | + | ||
| 930 | +.search-actions { | ||
| 931 | + margin-left: auto; | ||
| 932 | + display: flex; | ||
| 933 | + gap: 10px; | ||
| 934 | +} | ||
| 935 | + | ||
| 936 | +.search-btn, .reset-btn { | ||
| 937 | + padding: 6px 16px; | ||
| 938 | + border: none; | ||
| 939 | + border-radius: 4px; | ||
| 940 | + cursor: pointer; | ||
| 941 | + font-size: 14px; | ||
| 942 | + height: 32px; | ||
| 943 | +} | ||
| 944 | + | ||
| 945 | +.search-btn { | ||
| 946 | + background: #007bff; | ||
| 947 | + color: white; | ||
| 948 | +} | ||
| 949 | + | ||
| 950 | +.reset-btn { | ||
| 951 | + background: #6c757d; | ||
| 952 | + color: white; | ||
| 953 | +} | ||
| 954 | + | ||
| 955 | +/* 操作按钮区域样式 */ | ||
| 956 | +.action-section { | ||
| 957 | + background: white; | ||
| 958 | + padding: 12px 20px 12px 20px; | ||
| 959 | + border-bottom: 1px solid #e0e0e0; | ||
| 960 | +} | ||
| 961 | + | ||
| 962 | +.action-section .action-buttons { | ||
| 963 | + display: flex; | ||
| 964 | + gap: 10px; | ||
| 965 | + justify-content: flex-start; | ||
| 966 | + align-items: center; | ||
| 967 | + margin: 0; | ||
| 968 | + padding: 0; | ||
| 969 | +} | ||
| 970 | + | ||
| 971 | +.action-btn { | ||
| 972 | + padding: 6px 16px; | ||
| 973 | + border: none; | ||
| 974 | + border-radius: 4px; | ||
| 975 | + cursor: pointer; | ||
| 976 | + font-size: 14px; | ||
| 977 | + height: 32px; | ||
| 978 | + font-weight: 500; | ||
| 979 | +} | ||
| 980 | + | ||
| 981 | +.action-btn.primary { | ||
| 982 | + background: #007bff; | ||
| 983 | + color: white; | ||
| 984 | +} | ||
| 985 | + | ||
| 986 | +.action-btn.success { | ||
| 987 | + background: #28a745; | ||
| 988 | + color: white; | ||
| 989 | +} | ||
| 990 | + | ||
| 991 | +.action-btn.secondary { | ||
| 992 | + background: #6c757d; | ||
| 993 | + color: white; | ||
| 994 | +} | ||
| 995 | + | ||
| 996 | +/* 表格区域样式 */ | ||
| 997 | +.table-section { | ||
| 998 | + background: white; | ||
| 999 | + margin-bottom: 0; | ||
| 1000 | +} | ||
| 1001 | + | ||
| 1002 | +.table-header { | ||
| 1003 | + padding: 10px 20px; | ||
| 1004 | + border-bottom: 1px solid #eee; | ||
| 1005 | + display: flex; | ||
| 1006 | + justify-content: flex-end; | ||
| 1007 | +} | ||
| 1008 | + | ||
| 1009 | +.table-controls { | ||
| 1010 | + display: flex; | ||
| 1011 | + gap: 5px; | ||
| 1012 | +} | ||
| 1013 | + | ||
| 1014 | +.control-btn { | ||
| 1015 | + padding: 4px 8px; | ||
| 1016 | + border: 1px solid #ddd; | ||
| 1017 | + background: white; | ||
| 1018 | + border-radius: 4px; | ||
| 1019 | + cursor: pointer; | ||
| 1020 | + font-size: 12px; | ||
| 1021 | +} | ||
| 1022 | + | ||
| 1023 | +.table-container { | ||
| 1024 | + position: relative; | ||
| 1025 | + overflow-x: auto; | ||
| 1026 | +} | ||
| 1027 | + | ||
| 1028 | +.loading-overlay { | ||
| 1029 | + position: absolute; | ||
| 1030 | + top: 0; | ||
| 1031 | + left: 0; | ||
| 1032 | + right: 0; | ||
| 1033 | + bottom: 0; | ||
| 1034 | + background: rgba(255, 255, 255, 0.8); | ||
| 1035 | + display: flex; | ||
| 1036 | + align-items: center; | ||
| 1037 | + justify-content: center; | ||
| 1038 | + z-index: 10; | ||
| 1039 | +} | ||
| 1040 | + | ||
| 1041 | +.loading-spinner { | ||
| 1042 | + padding: 20px; | ||
| 1043 | + font-size: 16px; | ||
| 1044 | + color: #666; | ||
| 1045 | +} | ||
| 1046 | + | ||
| 1047 | +.data-table { | ||
| 1048 | + width: 100%; | ||
| 1049 | + border-collapse: collapse; | ||
| 1050 | + font-size: 14px; | ||
| 1051 | +} | ||
| 1052 | + | ||
| 1053 | +.data-table th { | ||
| 1054 | + background: #f8f9fa; | ||
| 1055 | + border: 1px solid #ddd; | ||
| 1056 | + padding: 12px 8px; | ||
| 1057 | + text-align: left; | ||
| 1058 | + font-weight: 600; | ||
| 1059 | + color: #333; | ||
| 1060 | + white-space: nowrap; | ||
| 1061 | +} | ||
| 1062 | + | ||
| 1063 | +.data-table td { | ||
| 1064 | + border: 1px solid #ddd; | ||
| 1065 | + padding: 12px 8px; | ||
| 1066 | + vertical-align: middle; | ||
| 1067 | +} | ||
| 1068 | + | ||
| 1069 | +.data-table tbody tr:nth-child(even) { | ||
| 1070 | + background: #f8f9fa; | ||
| 1071 | +} | ||
| 1072 | + | ||
| 1073 | +.data-table tbody tr:hover { | ||
| 1074 | + background: #e9ecef; | ||
| 1075 | +} | ||
| 1076 | + | ||
| 1077 | +.sortable { | ||
| 1078 | + cursor: pointer; | ||
| 1079 | + user-select: none; | ||
| 1080 | +} | ||
| 1081 | + | ||
| 1082 | +.select-all, .row-select { | ||
| 1083 | + margin: 0; | ||
| 1084 | +} | ||
| 1085 | + | ||
| 1086 | +.status-badge, .severity-badge { | ||
| 1087 | + padding: 2px 8px; | ||
| 1088 | + border-radius: 12px; | ||
| 1089 | + font-size: 12px; | ||
| 1090 | + font-weight: 500; | ||
| 1091 | + display: inline-block; | ||
| 1092 | +} | ||
| 1093 | + | ||
| 1094 | +.status-badge.pending { | ||
| 1095 | + background: #fff3cd; | ||
| 1096 | + color: #856404; | ||
| 1097 | +} | ||
| 1098 | + | ||
| 1099 | +.status-badge.processing { | ||
| 1100 | + background: #d1ecf1; | ||
| 1101 | + color: #0c5460; | ||
| 1102 | +} | ||
| 1103 | + | ||
| 1104 | +.status-badge.resolved { | ||
| 1105 | + background: #d4edda; | ||
| 1106 | + color: #155724; | ||
| 1107 | +} | ||
| 1108 | + | ||
| 1109 | +.status-badge.closed { | ||
| 1110 | + background: #f8d7da; | ||
| 1111 | + color: #721c24; | ||
| 1112 | +} | ||
| 1113 | + | ||
| 1114 | +.severity-badge.high { | ||
| 1115 | + background: #f8d7da; | ||
| 1116 | + color: #721c24; | ||
| 1117 | +} | ||
| 1118 | + | ||
| 1119 | +.severity-badge.medium { | ||
| 1120 | + background: #fff3cd; | ||
| 1121 | + color: #856404; | ||
| 1122 | +} | ||
| 1123 | + | ||
| 1124 | +.severity-badge.low { | ||
| 1125 | + background: #d4edda; | ||
| 1126 | + color: #155724; | ||
| 1127 | +} | ||
| 1128 | + | ||
| 1129 | +.table-actions { | ||
| 1130 | + display: flex; | ||
| 1131 | + gap: 4px; | ||
| 1132 | + flex-wrap: wrap; | ||
| 1133 | +} | ||
| 1134 | + | ||
| 1135 | +.table-btn { | ||
| 1136 | + padding: 2px 8px; | ||
| 1137 | + border: none; | ||
| 1138 | + border-radius: 4px; | ||
| 1139 | + cursor: pointer; | ||
| 1140 | + font-size: 12px; | ||
| 1141 | + font-weight: 500; | ||
| 1142 | +} | ||
| 1143 | + | ||
| 1144 | +.table-btn.edit { | ||
| 1145 | + background: #007bff; | ||
| 1146 | + color: white; | ||
| 1147 | +} | ||
| 1148 | + | ||
| 1149 | +.table-btn.info { | ||
| 1150 | + background: #6c757d; | ||
| 1151 | + color: white; | ||
| 1152 | +} | ||
| 1153 | + | ||
| 1154 | +.table-btn.view { | ||
| 1155 | + background: #28a745; | ||
| 1156 | + color: white; | ||
| 1157 | +} | ||
| 1158 | + | ||
| 1159 | +/* 状态标签样式 */ | ||
| 1160 | +.status-tag { | ||
| 1161 | + font-weight: 500; | ||
| 1162 | + border-radius: 4px; | ||
| 1163 | +} | ||
| 1164 | + | ||
| 1165 | +.severity-tag { | ||
| 1166 | + font-weight: 500; | ||
| 1167 | + border-radius: 4px; | ||
| 1168 | +} | ||
| 1169 | + | ||
| 1170 | +/* 表格内操作按钮容器 */ | ||
| 1171 | +.table-actions { | ||
| 1172 | + display: flex; | ||
| 1173 | + gap: 8px; | ||
| 1174 | + justify-content: center; | ||
| 1175 | + align-items: center; | ||
| 1176 | + flex-wrap: wrap; | ||
| 1177 | + padding: 0 4px; | ||
| 1178 | +} | ||
| 1179 | + | ||
| 1180 | +/* 操作按钮样式 */ | ||
| 1181 | +.action-btn { | ||
| 1182 | + padding: 4px 8px; | ||
| 1183 | + font-size: 12px; | ||
| 1184 | + border-radius: 4px; | ||
| 1185 | + font-weight: 500; | ||
| 1186 | + min-width: 60px; | ||
| 1187 | + height: 28px; | ||
| 1188 | + display: inline-flex; | ||
| 1189 | + align-items: center; | ||
| 1190 | + justify-content: center; | ||
| 1191 | + white-space: nowrap; | ||
| 1192 | +} | ||
| 1193 | + | ||
| 1194 | +.action-btn:hover { | ||
| 1195 | + transform: translateY(-1px); | ||
| 1196 | + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | ||
| 1197 | +} | ||
| 1198 | + | ||
| 1199 | +/* 分页区域样式 */ | ||
| 1200 | +.pagination-section { | ||
| 1201 | + background: white; | ||
| 1202 | + padding: 12px 20px; | ||
| 1203 | + border-top: 1px solid #e0e0e0; | ||
| 1204 | + display: flex; | ||
| 1205 | + justify-content: space-between; | ||
| 1206 | + align-items: center; | ||
| 1207 | +} | ||
| 1208 | + | ||
| 1209 | +.pagination-info { | ||
| 1210 | + display: flex; | ||
| 1211 | + align-items: center; | ||
| 1212 | + gap: 10px; | ||
| 1213 | + font-size: 14px; | ||
| 1214 | + color: #666; | ||
| 1215 | +} | ||
| 1216 | + | ||
| 1217 | +.page-size-select { | ||
| 1218 | + height: 28px; | ||
| 1219 | + border: 1px solid #ddd; | ||
| 1220 | + border-radius: 4px; | ||
| 1221 | + padding: 0 8px; | ||
| 1222 | + font-size: 14px; | ||
| 1223 | +} | ||
| 1224 | + | ||
| 1225 | +.pagination-controls { | ||
| 1226 | + display: flex; | ||
| 1227 | + align-items: center; | ||
| 1228 | + gap: 8px; | ||
| 1229 | + font-size: 14px; | ||
| 1230 | +} | ||
| 1231 | + | ||
| 1232 | +.page-btn { | ||
| 1233 | + padding: 4px 12px; | ||
| 1234 | + border: 1px solid #ddd; | ||
| 1235 | + background: white; | ||
| 1236 | + border-radius: 4px; | ||
| 1237 | + cursor: pointer; | ||
| 1238 | + font-size: 14px; | ||
| 1239 | +} | ||
| 1240 | + | ||
| 1241 | +.page-btn:disabled { | ||
| 1242 | + background: #f8f9fa; | ||
| 1243 | + color: #6c757d; | ||
| 1244 | + cursor: not-allowed; | ||
| 1245 | +} | ||
| 1246 | + | ||
| 1247 | +.page-btn:not(:disabled):hover { | ||
| 1248 | + background: #007bff; | ||
| 1249 | + color: white; | ||
| 1250 | + border-color: #007bff; | ||
| 1251 | +} | ||
| 1252 | + | ||
| 1253 | +.page-info { | ||
| 1254 | + margin: 0 8px; | ||
| 1255 | + color: #666; | ||
| 1256 | +} | ||
| 1257 | + | ||
| 1258 | +.jump-input { | ||
| 1259 | + width: 50px; | ||
| 1260 | + height: 28px; | ||
| 1261 | + border: 1px solid #ddd; | ||
| 1262 | + border-radius: 4px; | ||
| 1263 | + padding: 0 8px; | ||
| 1264 | + text-align: center; | ||
| 1265 | + font-size: 14px; | ||
| 1266 | +} | ||
| 1267 | + | ||
| 1268 | +.add-dialog, | ||
| 1269 | +.status-dialog, | ||
| 1270 | +.detail-dialog, | ||
| 1271 | +.logs-dialog { | ||
| 1272 | + .el-dialog__body { | ||
| 1273 | + padding: 20px; | ||
| 1274 | + } | ||
| 1275 | +} | ||
| 1276 | + | ||
| 1277 | +.add-form, | ||
| 1278 | +.status-form { | ||
| 1279 | + .el-form-item { | ||
| 1280 | + margin-bottom: 20px; | ||
| 1281 | + } | ||
| 1282 | +} | ||
| 1283 | + | ||
| 1284 | +.detail-content { | ||
| 1285 | + .detail-section { | ||
| 1286 | + margin-bottom: 30px; | ||
| 1287 | + } | ||
| 1288 | + | ||
| 1289 | + .detail-section h4 { | ||
| 1290 | + color: #333; | ||
| 1291 | + margin-bottom: 15px; | ||
| 1292 | + padding-bottom: 8px; | ||
| 1293 | + border-bottom: 2px solid #409eff; | ||
| 1294 | + } | ||
| 1295 | + | ||
| 1296 | + .detail-grid { | ||
| 1297 | + display: grid; | ||
| 1298 | + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | ||
| 1299 | + gap: 15px; | ||
| 1300 | + } | ||
| 1301 | + | ||
| 1302 | + .detail-item { | ||
| 1303 | + display: flex; | ||
| 1304 | + align-items: center; | ||
| 1305 | + gap: 10px; | ||
| 1306 | + } | ||
| 1307 | + | ||
| 1308 | + .detail-item label { | ||
| 1309 | + font-weight: 500; | ||
| 1310 | + color: #666; | ||
| 1311 | + min-width: 120px; | ||
| 1312 | + } | ||
| 1313 | + | ||
| 1314 | + .detail-item span { | ||
| 1315 | + color: #333; | ||
| 1316 | + } | ||
| 1317 | + | ||
| 1318 | + .detail-text { | ||
| 1319 | + color: #333; | ||
| 1320 | + line-height: 1.6; | ||
| 1321 | + background: #f8f9fa; | ||
| 1322 | + padding: 15px; | ||
| 1323 | + border-radius: 4px; | ||
| 1324 | + border-left: 4px solid #409eff; | ||
| 1325 | + } | ||
| 1326 | + | ||
| 1327 | + .log-table, | ||
| 1328 | + .logs-table { | ||
| 1329 | + margin-top: 15px; | ||
| 1330 | + } | ||
| 1331 | +} | ||
| 1332 | + | ||
| 1333 | +.dialog-footer { | ||
| 1334 | + text-align: right; | ||
| 1335 | +} | ||
| 1336 | + | ||
| 1337 | +.dialog-footer .el-button { | ||
| 1338 | + margin-left: 10px; | ||
| 1339 | +} | ||
| 1340 | +</style> |
| ... | @@ -517,6 +517,7 @@ | ... | @@ -517,6 +517,7 @@ |
| 517 | 517 | ||
| 518 | <script setup lang="ts"> | 518 | <script setup lang="ts"> |
| 519 | import { ref, reactive, computed, onMounted } from 'vue' | 519 | import { ref, reactive, computed, onMounted } from 'vue' |
| 520 | +import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 520 | import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice' | 521 | import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice' |
| 521 | import { dealerApi, type DealerInfo } from '../../api/dealer' | 522 | import { dealerApi, type DealerInfo } from '../../api/dealer' |
| 522 | import { productApi } from '../../api/product' | 523 | import { productApi } from '../../api/product' |
| ... | @@ -609,7 +610,12 @@ const formatCurrency = (amount: number) => { | ... | @@ -609,7 +610,12 @@ const formatCurrency = (amount: number) => { |
| 609 | // 消息提示 | 610 | // 消息提示 |
| 610 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { | 611 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { |
| 611 | console.log(`${type}: ${message}`) | 612 | console.log(`${type}: ${message}`) |
| 612 | - alert(message) | 613 | + ElMessage({ |
| 614 | + message, | ||
| 615 | + type, | ||
| 616 | + duration: 3000, | ||
| 617 | + showClose: true | ||
| 618 | + }) | ||
| 613 | } | 619 | } |
| 614 | 620 | ||
| 615 | // 获取页码数组 | 621 | // 获取页码数组 |
| ... | @@ -840,8 +846,81 @@ const handleView = async (order: InvoiceInfo) => { | ... | @@ -840,8 +846,81 @@ const handleView = async (order: InvoiceInfo) => { |
| 840 | } | 846 | } |
| 841 | 847 | ||
| 842 | 848 | ||
| 843 | -const handleExport = () => { | 849 | +const handleExport = async () => { |
| 844 | - showMessage('导出功能开发中...', 'warning') | 850 | + try { |
| 851 | + // 显示确认弹窗 | ||
| 852 | + await ElMessageBox.confirm( | ||
| 853 | + '确定要导出发票数据吗?导出将包含当前筛选条件下的所有数据。', | ||
| 854 | + '确认导出', | ||
| 855 | + { | ||
| 856 | + confirmButtonText: '确定导出', | ||
| 857 | + cancelButtonText: '取消', | ||
| 858 | + type: 'warning', | ||
| 859 | + center: true | ||
| 860 | + } | ||
| 861 | + ) | ||
| 862 | + | ||
| 863 | + // 用户确认后显示加载提示 | ||
| 864 | + const loadingMessage = ElMessage({ | ||
| 865 | + message: '正在导出数据,请稍候...', | ||
| 866 | + type: 'warning', | ||
| 867 | + duration: 0, // 不自动关闭 | ||
| 868 | + showClose: false | ||
| 869 | + }) | ||
| 870 | + | ||
| 871 | + try { | ||
| 872 | + // 准备导出参数 | ||
| 873 | + const exportParams = { | ||
| 874 | + invoiceNo: searchParams.invoiceNo, | ||
| 875 | + orderNo: searchParams.orderNo, | ||
| 876 | + deliveryNo: searchParams.deliveryNo, | ||
| 877 | + dealerCode: searchParams.dealerCode, | ||
| 878 | + dealerName: searchParams.dealerName, | ||
| 879 | + invoiceStatus: searchParams.invoiceStatus, | ||
| 880 | + dataSource: searchParams.dataSource, | ||
| 881 | + invoiceStartDate: searchParams.invoiceStartDate, | ||
| 882 | + invoiceEndDate: searchParams.invoiceEndDate, | ||
| 883 | + minAmount: searchParams.minAmount, | ||
| 884 | + maxAmount: searchParams.maxAmount | ||
| 885 | + } | ||
| 886 | + | ||
| 887 | + // 调用导出接口 | ||
| 888 | + const response = await invoiceApi.exportInvoices(exportParams) | ||
| 889 | + | ||
| 890 | + // 创建下载链接 | ||
| 891 | + const blob = new Blob([response], { | ||
| 892 | + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
| 893 | + }) | ||
| 894 | + const url = window.URL.createObjectURL(blob) | ||
| 895 | + const link = document.createElement('a') | ||
| 896 | + link.href = url | ||
| 897 | + | ||
| 898 | + // 生成文件名 | ||
| 899 | + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14) | ||
| 900 | + link.download = `发票数据_${timestamp}.xlsx` | ||
| 901 | + | ||
| 902 | + // 触发下载 | ||
| 903 | + document.body.appendChild(link) | ||
| 904 | + link.click() | ||
| 905 | + document.body.removeChild(link) | ||
| 906 | + window.URL.revokeObjectURL(url) | ||
| 907 | + | ||
| 908 | + // 关闭加载提示,显示成功消息 | ||
| 909 | + loadingMessage.close() | ||
| 910 | + ElMessage.success('导出成功!文件已开始下载') | ||
| 911 | + | ||
| 912 | + } catch (exportError) { | ||
| 913 | + // 关闭加载提示 | ||
| 914 | + loadingMessage.close() | ||
| 915 | + throw exportError | ||
| 916 | + } | ||
| 917 | + | ||
| 918 | + } catch (error) { | ||
| 919 | + if (error !== 'cancel') { | ||
| 920 | + console.error('导出失败:', error) | ||
| 921 | + ElMessage.error('导出失败,请重试') | ||
| 922 | + } | ||
| 923 | + } | ||
| 845 | } | 924 | } |
| 846 | 925 | ||
| 847 | const handlePrintInvoice = (invoice: InvoiceInfo) => { | 926 | const handlePrintInvoice = (invoice: InvoiceInfo) => { | ... | ... |
| ... | @@ -462,6 +462,7 @@ | ... | @@ -462,6 +462,7 @@ |
| 462 | 462 | ||
| 463 | <script setup lang="ts"> | 463 | <script setup lang="ts"> |
| 464 | import { ref, reactive, computed, onMounted } from 'vue' | 464 | import { ref, reactive, computed, onMounted } from 'vue' |
| 465 | +import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 465 | import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order' | 466 | import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order' |
| 466 | import { dealerApi, type DealerInfo } from '../../api/dealer' | 467 | import { dealerApi, type DealerInfo } from '../../api/dealer' |
| 467 | import { productApi } from '../../api/product' | 468 | import { productApi } from '../../api/product' |
| ... | @@ -606,9 +607,12 @@ const formatCurrency = (amount: number) => { | ... | @@ -606,9 +607,12 @@ const formatCurrency = (amount: number) => { |
| 606 | 607 | ||
| 607 | // 消息提示 | 608 | // 消息提示 |
| 608 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { | 609 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { |
| 609 | - // 这里可以集成消息提示组件 | 610 | + ElMessage({ |
| 610 | - console.log(`${type}: ${message}`) | 611 | + message, |
| 611 | - alert(message) | 612 | + type, |
| 613 | + duration: 3000, | ||
| 614 | + showClose: true | ||
| 615 | + }) | ||
| 612 | } | 616 | } |
| 613 | 617 | ||
| 614 | // 方法 | 618 | // 方法 |
| ... | @@ -786,8 +790,82 @@ const handleBatchDelete = async () => { | ... | @@ -786,8 +790,82 @@ const handleBatchDelete = async () => { |
| 786 | } | 790 | } |
| 787 | 791 | ||
| 788 | 792 | ||
| 789 | -const handleExport = () => { | 793 | +const handleExport = async () => { |
| 790 | - showMessage('导出功能开发中...', 'warning') | 794 | + try { |
| 795 | + // 显示确认弹窗 | ||
| 796 | + await ElMessageBox.confirm( | ||
| 797 | + '确定要导出订单数据吗?导出将包含当前筛选条件下的所有数据。', | ||
| 798 | + '确认导出', | ||
| 799 | + { | ||
| 800 | + confirmButtonText: '确定导出', | ||
| 801 | + cancelButtonText: '取消', | ||
| 802 | + type: 'warning', | ||
| 803 | + center: true | ||
| 804 | + } | ||
| 805 | + ) | ||
| 806 | + | ||
| 807 | + // 用户确认后显示加载提示 | ||
| 808 | + const loadingMessage = ElMessage({ | ||
| 809 | + message: '正在导出数据,请稍候...', | ||
| 810 | + type: 'warning', | ||
| 811 | + duration: 0, // 不自动关闭 | ||
| 812 | + showClose: false | ||
| 813 | + }) | ||
| 814 | + | ||
| 815 | + try { | ||
| 816 | + // 准备导出参数 | ||
| 817 | + const exportParams = { | ||
| 818 | + orderNo: searchParams.orderNo, | ||
| 819 | + dealerCode: searchParams.dealerCode, | ||
| 820 | + dealerName: searchParams.dealerName, | ||
| 821 | + deliveryStatus: searchParams.deliveryStatus, | ||
| 822 | + invoiceStatus: searchParams.invoiceStatus, | ||
| 823 | + rebateCalcFlag: searchParams.rebateCalcFlag, | ||
| 824 | + dataSource: searchParams.dataSource, | ||
| 825 | + verifyStatus: searchParams.verifyStatus, | ||
| 826 | + orderStartDate: searchParams.orderStartDate, | ||
| 827 | + orderEndDate: searchParams.orderEndDate, | ||
| 828 | + minAmount: searchParams.minAmount, | ||
| 829 | + maxAmount: searchParams.maxAmount | ||
| 830 | + } | ||
| 831 | + | ||
| 832 | + // 调用导出接口 | ||
| 833 | + const response = await orderApi.exportOrders(exportParams) | ||
| 834 | + | ||
| 835 | + // 创建下载链接 | ||
| 836 | + const blob = new Blob([response], { | ||
| 837 | + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
| 838 | + }) | ||
| 839 | + const url = window.URL.createObjectURL(blob) | ||
| 840 | + const link = document.createElement('a') | ||
| 841 | + link.href = url | ||
| 842 | + | ||
| 843 | + // 生成文件名 | ||
| 844 | + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14) | ||
| 845 | + link.download = `订单数据_${timestamp}.xlsx` | ||
| 846 | + | ||
| 847 | + // 触发下载 | ||
| 848 | + document.body.appendChild(link) | ||
| 849 | + link.click() | ||
| 850 | + document.body.removeChild(link) | ||
| 851 | + window.URL.revokeObjectURL(url) | ||
| 852 | + | ||
| 853 | + // 关闭加载提示,显示成功消息 | ||
| 854 | + loadingMessage.close() | ||
| 855 | + ElMessage.success('导出成功!文件已开始下载') | ||
| 856 | + | ||
| 857 | + } catch (exportError) { | ||
| 858 | + // 关闭加载提示 | ||
| 859 | + loadingMessage.close() | ||
| 860 | + throw exportError | ||
| 861 | + } | ||
| 862 | + | ||
| 863 | + } catch (error) { | ||
| 864 | + if (error !== 'cancel') { | ||
| 865 | + console.error('导出失败:', error) | ||
| 866 | + ElMessage.error('导出失败,请重试') | ||
| 867 | + } | ||
| 868 | + } | ||
| 791 | } | 869 | } |
| 792 | 870 | ||
| 793 | const handleSelectAll = () => { | 871 | const handleSelectAll = () => { | ... | ... |
| ... | @@ -6,30 +6,30 @@ | ... | @@ -6,30 +6,30 @@ |
| 6 | <div class="search-row"> | 6 | <div class="search-row"> |
| 7 | <div class="search-item"> | 7 | <div class="search-item"> |
| 8 | <span class="search-label">经销商名称</span> | 8 | <span class="search-label">经销商名称</span> |
| 9 | - <el-input | 9 | + <el-input |
| 10 | - v-model="searchForm.dealerName" | 10 | + v-model="searchForm.dealerName" |
| 11 | - placeholder="请输入经销商名称" | 11 | + placeholder="请输入经销商名称" |
| 12 | - clearable | 12 | + clearable |
| 13 | size="small" | 13 | size="small" |
| 14 | style="width: 160px" | 14 | style="width: 160px" |
| 15 | /> | 15 | /> |
| 16 | </div> | 16 | </div> |
| 17 | <div class="search-item"> | 17 | <div class="search-item"> |
| 18 | <span class="search-label">返利编号</span> | 18 | <span class="search-label">返利编号</span> |
| 19 | - <el-input | 19 | + <el-input |
| 20 | v-model="searchForm.rebateNo" | 20 | v-model="searchForm.rebateNo" |
| 21 | placeholder="请输入返利编号" | 21 | placeholder="请输入返利编号" |
| 22 | - clearable | 22 | + clearable |
| 23 | size="small" | 23 | size="small" |
| 24 | style="width: 160px" | 24 | style="width: 160px" |
| 25 | /> | 25 | /> |
| 26 | </div> | 26 | </div> |
| 27 | <div class="search-item"> | 27 | <div class="search-item"> |
| 28 | <span class="search-label">日期</span> | 28 | <span class="search-label">日期</span> |
| 29 | - <el-date-picker | 29 | + <el-date-picker |
| 30 | v-model="dateRange" | 30 | v-model="dateRange" |
| 31 | type="daterange" | 31 | type="daterange" |
| 32 | - range-separator="至" | 32 | + range-separator="至" |
| 33 | start-placeholder="开始日期" | 33 | start-placeholder="开始日期" |
| 34 | end-placeholder="结束日期" | 34 | end-placeholder="结束日期" |
| 35 | size="small" | 35 | size="small" |
| ... | @@ -42,10 +42,10 @@ | ... | @@ -42,10 +42,10 @@ |
| 42 | <div class="search-actions"> | 42 | <div class="search-actions"> |
| 43 | <el-button type="primary" size="small" @click="handleSearch" :loading="loading"> | 43 | <el-button type="primary" size="small" @click="handleSearch" :loading="loading"> |
| 44 | 查询 | 44 | 查询 |
| 45 | - </el-button> | 45 | + </el-button> |
| 46 | <el-button size="small" @click="handleReset"> | 46 | <el-button size="small" @click="handleReset"> |
| 47 | - 重置 | 47 | + 重置 |
| 48 | - </el-button> | 48 | + </el-button> |
| 49 | </div> | 49 | </div> |
| 50 | </div> | 50 | </div> |
| 51 | </div> | 51 | </div> |
| ... | @@ -61,13 +61,13 @@ | ... | @@ -61,13 +61,13 @@ |
| 61 | <div class="chart-legend"> | 61 | <div class="chart-legend"> |
| 62 | <div class="legend-item"> | 62 | <div class="legend-item"> |
| 63 | <span class="legend-dot" style="background-color: #4CAF50;"></span> | 63 | <span class="legend-dot" style="background-color: #4CAF50;"></span> |
| 64 | - <span>返利金额</span> | 64 | + <span>返利合计</span> |
| 65 | - </div> | 65 | + </div> |
| 66 | <div class="legend-item"> | 66 | <div class="legend-item"> |
| 67 | <span class="legend-dot" style="background-color: #2196F3;"></span> | 67 | <span class="legend-dot" style="background-color: #2196F3;"></span> |
| 68 | - <span>已审核返利</span> | 68 | + <span>已使用返利</span> |
| 69 | - </div> | 69 | + </div> |
| 70 | - </div> | 70 | + </div> |
| 71 | </div> | 71 | </div> |
| 72 | <div class="chart-content"> | 72 | <div class="chart-content"> |
| 73 | <div ref="trendChart" class="chart"></div> | 73 | <div ref="trendChart" class="chart"></div> |
| ... | @@ -77,15 +77,15 @@ | ... | @@ -77,15 +77,15 @@ |
| 77 | <!-- 右侧饼图 --> | 77 | <!-- 右侧饼图 --> |
| 78 | <div class="chart-card pie-chart"> | 78 | <div class="chart-card pie-chart"> |
| 79 | <div class="chart-header"> | 79 | <div class="chart-header"> |
| 80 | - <h3 class="chart-title">计算统计</h3> | 80 | + <h3 class="chart-title">返利统计</h3> |
| 81 | <div class="chart-legend"> | 81 | <div class="chart-legend"> |
| 82 | <div class="legend-item"> | 82 | <div class="legend-item"> |
| 83 | <span class="legend-dot" style="background-color: #2196F3;"></span> | 83 | <span class="legend-dot" style="background-color: #2196F3;"></span> |
| 84 | - <span>已计算</span> | 84 | + <span>剩余返利</span> |
| 85 | </div> | 85 | </div> |
| 86 | <div class="legend-item"> | 86 | <div class="legend-item"> |
| 87 | - <span class="legend-dot" style="background-color: #4CAF50;"></span> | 87 | + <span class="legend-dot" style="background-color: #9CCC65;"></span> |
| 88 | - <span>未计算</span> | 88 | + <span>已使用返利</span> |
| 89 | </div> | 89 | </div> |
| 90 | </div> | 90 | </div> |
| 91 | </div> | 91 | </div> |
| ... | @@ -102,14 +102,20 @@ | ... | @@ -102,14 +102,20 @@ |
| 102 | <div class="table-title"> | 102 | <div class="table-title"> |
| 103 | 返利记录 | 103 | 返利记录 |
| 104 | </div> | 104 | </div> |
| 105 | - <div class="table-info"> | 105 | + <div class="table-actions"> |
| 106 | - 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }} | 106 | + <el-button type="primary" size="small" @click="handleExport" :loading="exportLoading"> |
| 107 | + <el-icon><Download /></el-icon> | ||
| 108 | + 导出 | ||
| 109 | + </el-button> | ||
| 110 | + <div class="table-info"> | ||
| 111 | + 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }} | ||
| 112 | + </div> | ||
| 107 | </div> | 113 | </div> |
| 108 | </div> | 114 | </div> |
| 109 | 115 | ||
| 110 | - <el-table | 116 | + <el-table |
| 111 | v-loading="loading" | 117 | v-loading="loading" |
| 112 | - :data="rebateList" | 118 | + :data="rebateList" |
| 113 | style="width: 100%" | 119 | style="width: 100%" |
| 114 | :header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 'normal' }" | 120 | :header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 'normal' }" |
| 115 | :empty-text="rebateList.length === 0 ? '暂无数据' : ''" | 121 | :empty-text="rebateList.length === 0 ? '暂无数据' : ''" |
| ... | @@ -153,7 +159,7 @@ | ... | @@ -153,7 +159,7 @@ |
| 153 | </template> | 159 | </template> |
| 154 | </el-table-column> | 160 | </el-table-column> |
| 155 | </el-table> | 161 | </el-table> |
| 156 | - | 162 | + |
| 157 | <!-- 分页 --> | 163 | <!-- 分页 --> |
| 158 | <div class="pagination-wrapper"> | 164 | <div class="pagination-wrapper"> |
| 159 | <div class="pagination-info"> | 165 | <div class="pagination-info"> |
| ... | @@ -178,7 +184,7 @@ | ... | @@ -178,7 +184,7 @@ |
| 178 | @keyup.enter="handleJumpPage" | 184 | @keyup.enter="handleJumpPage" |
| 179 | /> | 185 | /> |
| 180 | 页 | 186 | 页 |
| 181 | - </div> | 187 | + </div> |
| 182 | </div> | 188 | </div> |
| 183 | </div> | 189 | </div> |
| 184 | 190 | ||
| ... | @@ -384,12 +390,13 @@ import { ref, reactive, onMounted } from 'vue' | ... | @@ -384,12 +390,13 @@ import { ref, reactive, onMounted } from 'vue' |
| 384 | import { ElMessage, ElMessageBox } from 'element-plus' | 390 | import { ElMessage, ElMessageBox } from 'element-plus' |
| 385 | import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue' | 391 | import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue' |
| 386 | import * as echarts from 'echarts' | 392 | import * as echarts from 'echarts' |
| 387 | -import rebateApi, { type Rebate, type RebateSearchParams } from '@/api/rebate' | 393 | +import rebateApi, { type Rebate, type RebateSearchParams, exportRebates } from '@/api/rebate' |
| 388 | import { formatDate } from '@/utils/index' | 394 | import { formatDate } from '@/utils/index' |
| 389 | 395 | ||
| 390 | // 响应式数据 | 396 | // 响应式数据 |
| 391 | const loading = ref(false) | 397 | const loading = ref(false) |
| 392 | const submitLoading = ref(false) | 398 | const submitLoading = ref(false) |
| 399 | +const exportLoading = ref(false) | ||
| 393 | const auditLoading = ref(false) | 400 | const auditLoading = ref(false) |
| 394 | const rebateList = ref<Rebate[]>([]) | 401 | const rebateList = ref<Rebate[]>([]) |
| 395 | const selectedRebates = ref<Rebate[]>([]) | 402 | const selectedRebates = ref<Rebate[]>([]) |
| ... | @@ -780,7 +787,7 @@ const handleViewDetail = async (row: Rebate) => { | ... | @@ -780,7 +787,7 @@ const handleViewDetail = async (row: Rebate) => { |
| 780 | // 根据request.ts拦截器的处理,response.data已经是实际数据 | 787 | // 根据request.ts拦截器的处理,response.data已经是实际数据 |
| 781 | if (response && response.data) { | 788 | if (response && response.data) { |
| 782 | rebateDetail.value = response.data | 789 | rebateDetail.value = response.data |
| 783 | - detailDialogVisible.value = true | 790 | + detailDialogVisible.value = true |
| 784 | console.log('详情数据:', response.data) | 791 | console.log('详情数据:', response.data) |
| 785 | } else { | 792 | } else { |
| 786 | ElMessage.error('获取返利详情失败') | 793 | ElMessage.error('获取返利详情失败') |
| ... | @@ -884,7 +891,7 @@ const handleDelete = async (row: Rebate) => { | ... | @@ -884,7 +891,7 @@ const handleDelete = async (row: Rebate) => { |
| 884 | const { data } = await rebateApi.deleteRebate(row.rebateId!) | 891 | const { data } = await rebateApi.deleteRebate(row.rebateId!) |
| 885 | if (data.code === 200) { | 892 | if (data.code === 200) { |
| 886 | ElMessage.success('删除成功') | 893 | ElMessage.success('删除成功') |
| 887 | - getRebateList() | 894 | + getRebateList() |
| 888 | } else { | 895 | } else { |
| 889 | ElMessage.error(data.message || '删除失败') | 896 | ElMessage.error(data.message || '删除失败') |
| 890 | } | 897 | } |
| ... | @@ -942,7 +949,7 @@ const handleBatchDelete = async () => { | ... | @@ -942,7 +949,7 @@ const handleBatchDelete = async () => { |
| 942 | 949 | ||
| 943 | if (data.code === 200) { | 950 | if (data.code === 200) { |
| 944 | ElMessage.success('批量删除成功') | 951 | ElMessage.success('批量删除成功') |
| 945 | - getRebateList() | 952 | + getRebateList() |
| 946 | } else { | 953 | } else { |
| 947 | ElMessage.error(data.message || '批量删除失败') | 954 | ElMessage.error(data.message || '批量删除失败') |
| 948 | } | 955 | } |
| ... | @@ -1039,30 +1046,62 @@ const handleBatchRelease = async () => { | ... | @@ -1039,30 +1046,62 @@ const handleBatchRelease = async () => { |
| 1039 | // 导出 | 1046 | // 导出 |
| 1040 | const handleExport = async () => { | 1047 | const handleExport = async () => { |
| 1041 | try { | 1048 | try { |
| 1042 | - loading.value = true | 1049 | + // 显示确认弹窗 |
| 1043 | - const params = { ...searchForm } | 1050 | + await ElMessageBox.confirm( |
| 1044 | - | 1051 | + '确定要导出返利数据吗?导出将包含当前筛选条件下的所有数据。', |
| 1045 | - const response = await rebateApi.exportRebate(params) | 1052 | + '确认导出', |
| 1053 | + { | ||
| 1054 | + confirmButtonText: '确定导出', | ||
| 1055 | + cancelButtonText: '取消', | ||
| 1056 | + type: 'warning', | ||
| 1057 | + center: true | ||
| 1058 | + } | ||
| 1059 | + ) | ||
| 1046 | 1060 | ||
| 1047 | - // 创建下载链接 | 1061 | + // 用户确认后显示加载提示 |
| 1048 | - const blob = new Blob([response.data], { | 1062 | + const loadingMessage = ElMessage({ |
| 1049 | - type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | 1063 | + message: '正在导出数据,请稍候...', |
| 1064 | + type: 'warning', | ||
| 1065 | + duration: 0, // 不自动关闭 | ||
| 1066 | + showClose: false | ||
| 1050 | }) | 1067 | }) |
| 1051 | - const url = window.URL.createObjectURL(blob) | ||
| 1052 | - const link = document.createElement('a') | ||
| 1053 | - link.href = url | ||
| 1054 | - link.download = `返利数据_${new Date().toISOString().split('T')[0]}.xlsx` | ||
| 1055 | - document.body.appendChild(link) | ||
| 1056 | - link.click() | ||
| 1057 | - document.body.removeChild(link) | ||
| 1058 | - window.URL.revokeObjectURL(url) | ||
| 1059 | 1068 | ||
| 1060 | - ElMessage.success('导出成功') | 1069 | + try { |
| 1070 | + exportLoading.value = true | ||
| 1071 | + const params = { ...searchForm } | ||
| 1072 | + | ||
| 1073 | + const response = await exportRebates(params) | ||
| 1074 | + | ||
| 1075 | + // 创建下载链接 | ||
| 1076 | + const blob = new Blob([response as unknown as BlobPart], { | ||
| 1077 | + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
| 1078 | + }) | ||
| 1079 | + const url = window.URL.createObjectURL(blob) | ||
| 1080 | + const link = document.createElement('a') | ||
| 1081 | + link.href = url | ||
| 1082 | + link.download = `返利数据_${new Date().toISOString().split('T')[0]}.xlsx` | ||
| 1083 | + document.body.appendChild(link) | ||
| 1084 | + link.click() | ||
| 1085 | + document.body.removeChild(link) | ||
| 1086 | + window.URL.revokeObjectURL(url) | ||
| 1087 | + | ||
| 1088 | + // 关闭加载提示,显示成功消息 | ||
| 1089 | + loadingMessage.close() | ||
| 1090 | + ElMessage.success('导出成功!文件已开始下载') | ||
| 1091 | + | ||
| 1092 | + } catch (exportError) { | ||
| 1093 | + // 关闭加载提示 | ||
| 1094 | + loadingMessage.close() | ||
| 1095 | + throw exportError | ||
| 1096 | + } | ||
| 1097 | + | ||
| 1061 | } catch (error) { | 1098 | } catch (error) { |
| 1062 | - console.error('导出失败:', error) | 1099 | + if (error !== 'cancel') { |
| 1063 | - ElMessage.error('导出失败') | 1100 | + console.error('导出失败:', error) |
| 1101 | + ElMessage.error('导出失败,请重试') | ||
| 1102 | + } | ||
| 1064 | } finally { | 1103 | } finally { |
| 1065 | - loading.value = false | 1104 | + exportLoading.value = false |
| 1066 | } | 1105 | } |
| 1067 | } | 1106 | } |
| 1068 | 1107 | ||
| ... | @@ -1073,27 +1112,30 @@ const initTrendChart = async () => { | ... | @@ -1073,27 +1112,30 @@ const initTrendChart = async () => { |
| 1073 | const chart = echarts.init(trendChart.value) | 1112 | const chart = echarts.init(trendChart.value) |
| 1074 | 1113 | ||
| 1075 | try { | 1114 | try { |
| 1076 | - // 调用后端API获取月度统计数据 | 1115 | + // 调用后端API获取月度统计数据 - 查询2025年的数据 |
| 1077 | - const response = await rebateApi.getRebateMonthlyStats() | 1116 | + const response = await rebateApi.getRebateMonthlyStats(2025) |
| 1078 | - let monthlyData = [] | 1117 | + console.log('月度统计API响应:', response) |
| 1118 | + let monthlyData: any[] = [] | ||
| 1079 | 1119 | ||
| 1080 | - if (response && response.data) { | 1120 | + // request.ts拦截器已经返回了data字段,所以response就是数据数组 |
| 1081 | - monthlyData = response.data | 1121 | + if (response && Array.isArray(response)) { |
| 1122 | + monthlyData = response | ||
| 1082 | } | 1123 | } |
| 1124 | + console.log('处理后的月度数据:', monthlyData) | ||
| 1083 | 1125 | ||
| 1084 | const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] | 1126 | const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] |
| 1085 | 1127 | ||
| 1086 | // 处理后端返回的月度数据 | 1128 | // 处理后端返回的月度数据 |
| 1087 | - const totalTrendData = months.map((month, index) => { | 1129 | + const totalRebateData = months.map((month, index) => { |
| 1088 | const monthIndex = index + 1 | 1130 | const monthIndex = index + 1 |
| 1089 | const monthData = monthlyData.find((item: any) => item.month === monthIndex) | 1131 | const monthData = monthlyData.find((item: any) => item.month === monthIndex) |
| 1090 | return monthData ? monthData.totalAmount : 0 | 1132 | return monthData ? monthData.totalAmount : 0 |
| 1091 | }) | 1133 | }) |
| 1092 | 1134 | ||
| 1093 | - const calculatedTrendData = months.map((month, index) => { | 1135 | + const usedRebateData = months.map((month, index) => { |
| 1094 | const monthIndex = index + 1 | 1136 | const monthIndex = index + 1 |
| 1095 | const monthData = monthlyData.find((item: any) => item.month === monthIndex) | 1137 | const monthData = monthlyData.find((item: any) => item.month === monthIndex) |
| 1096 | - return monthData ? monthData.calculatedAmount : 0 | 1138 | + return monthData ? (monthData.calculatedAmount || 0) : 0 |
| 1097 | }) | 1139 | }) |
| 1098 | 1140 | ||
| 1099 | const option = { | 1141 | const option = { |
| ... | @@ -1143,10 +1185,10 @@ const initTrendChart = async () => { | ... | @@ -1143,10 +1185,10 @@ const initTrendChart = async () => { |
| 1143 | }, | 1185 | }, |
| 1144 | series: [ | 1186 | series: [ |
| 1145 | { | 1187 | { |
| 1146 | - name: '返利金额', | 1188 | + name: '返利合计', |
| 1147 | type: 'line', | 1189 | type: 'line', |
| 1148 | smooth: true, | 1190 | smooth: true, |
| 1149 | - data: totalTrendData, | 1191 | + data: totalRebateData, |
| 1150 | itemStyle: { color: '#4CAF50' }, | 1192 | itemStyle: { color: '#4CAF50' }, |
| 1151 | lineStyle: { | 1193 | lineStyle: { |
| 1152 | color: '#4CAF50', | 1194 | color: '#4CAF50', |
| ... | @@ -1157,10 +1199,10 @@ const initTrendChart = async () => { | ... | @@ -1157,10 +1199,10 @@ const initTrendChart = async () => { |
| 1157 | showSymbol: true | 1199 | showSymbol: true |
| 1158 | }, | 1200 | }, |
| 1159 | { | 1201 | { |
| 1160 | - name: '已审核返利', | 1202 | + name: '已使用返利', |
| 1161 | type: 'line', | 1203 | type: 'line', |
| 1162 | smooth: true, | 1204 | smooth: true, |
| 1163 | - data: calculatedTrendData, | 1205 | + data: usedRebateData, |
| 1164 | itemStyle: { color: '#2196F3' }, | 1206 | itemStyle: { color: '#2196F3' }, |
| 1165 | lineStyle: { | 1207 | lineStyle: { |
| 1166 | color: '#2196F3', | 1208 | color: '#2196F3', |
| ... | @@ -1185,8 +1227,8 @@ const initTrendChart = async () => { | ... | @@ -1185,8 +1227,8 @@ const initTrendChart = async () => { |
| 1185 | 1227 | ||
| 1186 | const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] | 1228 | const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] |
| 1187 | 1229 | ||
| 1188 | - const totalTrendData = months.map(() => Math.floor(totalAmount / 12)) | 1230 | + const totalRebateDataFallback = months.map(() => Math.floor(totalAmount / 12)) |
| 1189 | - const calculatedTrendData = months.map(() => Math.floor(calculatedAmount / 12)) | 1231 | + const usedRebateDataFallback = months.map(() => Math.floor(calculatedAmount / 12)) |
| 1190 | 1232 | ||
| 1191 | const fallbackOption = { | 1233 | const fallbackOption = { |
| 1192 | tooltip: { | 1234 | tooltip: { |
| ... | @@ -1235,10 +1277,10 @@ const initTrendChart = async () => { | ... | @@ -1235,10 +1277,10 @@ const initTrendChart = async () => { |
| 1235 | }, | 1277 | }, |
| 1236 | series: [ | 1278 | series: [ |
| 1237 | { | 1279 | { |
| 1238 | - name: '返利金额', | 1280 | + name: '返利合计', |
| 1239 | type: 'line', | 1281 | type: 'line', |
| 1240 | smooth: true, | 1282 | smooth: true, |
| 1241 | - data: totalTrendData, | 1283 | + data: totalRebateDataFallback, |
| 1242 | itemStyle: { color: '#4CAF50' }, | 1284 | itemStyle: { color: '#4CAF50' }, |
| 1243 | lineStyle: { | 1285 | lineStyle: { |
| 1244 | color: '#4CAF50', | 1286 | color: '#4CAF50', |
| ... | @@ -1249,10 +1291,10 @@ const initTrendChart = async () => { | ... | @@ -1249,10 +1291,10 @@ const initTrendChart = async () => { |
| 1249 | showSymbol: true | 1291 | showSymbol: true |
| 1250 | }, | 1292 | }, |
| 1251 | { | 1293 | { |
| 1252 | - name: '已审核返利', | 1294 | + name: '已使用返利', |
| 1253 | type: 'line', | 1295 | type: 'line', |
| 1254 | smooth: true, | 1296 | smooth: true, |
| 1255 | - data: calculatedTrendData, | 1297 | + data: usedRebateDataFallback, |
| 1256 | itemStyle: { color: '#2196F3' }, | 1298 | itemStyle: { color: '#2196F3' }, |
| 1257 | lineStyle: { | 1299 | lineStyle: { |
| 1258 | color: '#2196F3', | 1300 | color: '#2196F3', |
| ... | @@ -1280,33 +1322,42 @@ const initPieChart = async () => { | ... | @@ -1280,33 +1322,42 @@ const initPieChart = async () => { |
| 1280 | try { | 1322 | try { |
| 1281 | // 调用后端API获取状态统计数据 | 1323 | // 调用后端API获取状态统计数据 |
| 1282 | const response = await rebateApi.getRebateStatusStats() | 1324 | const response = await rebateApi.getRebateStatusStats() |
| 1283 | - let statusData = { calculatedCount: 0, unCalculatedCount: 0 } | 1325 | + console.log('状态统计API响应:', response) |
| 1326 | + let statusData: any = { calculatedAmount: 0, unCalculatedAmount: 0 } | ||
| 1284 | 1327 | ||
| 1285 | - if (response && response.data) { | 1328 | + // request.ts拦截器已经返回了data字段,所以response就是数据对象 |
| 1286 | - statusData = response.data | 1329 | + if (response && typeof response === 'object') { |
| 1330 | + statusData = response | ||
| 1287 | } | 1331 | } |
| 1332 | + console.log('处理后的状态数据:', statusData) | ||
| 1333 | + | ||
| 1334 | + // 计算剩余返利和已使用返利 | ||
| 1335 | + const usedRebate = statusData.calculatedAmount || 0 | ||
| 1336 | + const remainingRebate = statusData.unCalculatedAmount || 0 | ||
| 1288 | 1337 | ||
| 1289 | const option = { | 1338 | const option = { |
| 1290 | tooltip: { | 1339 | tooltip: { |
| 1291 | trigger: 'item', | 1340 | trigger: 'item', |
| 1292 | - formatter: '{b}: {c} ({d}%)' | 1341 | + formatter: (params: any) => { |
| 1342 | + return `${params.name}: ¥${params.value.toLocaleString()} (${params.percent}%)` | ||
| 1343 | + } | ||
| 1293 | }, | 1344 | }, |
| 1294 | series: [ | 1345 | series: [ |
| 1295 | { | 1346 | { |
| 1296 | - name: '审核状态', | 1347 | + name: '返利统计', |
| 1297 | type: 'pie', | 1348 | type: 'pie', |
| 1298 | radius: ['35%', '65%'], | 1349 | radius: ['35%', '65%'], |
| 1299 | center: ['50%', '50%'], | 1350 | center: ['50%', '50%'], |
| 1300 | data: [ | 1351 | data: [ |
| 1301 | { | 1352 | { |
| 1302 | - value: statusData.calculatedCount || 0, | 1353 | + value: remainingRebate, |
| 1303 | - name: '已计算', | 1354 | + name: '剩余返利', |
| 1304 | itemStyle: { color: '#2196F3' } | 1355 | itemStyle: { color: '#2196F3' } |
| 1305 | }, | 1356 | }, |
| 1306 | { | 1357 | { |
| 1307 | - value: statusData.unCalculatedCount || 0, | 1358 | + value: usedRebate, |
| 1308 | - name: '未计算', | 1359 | + name: '已使用返利', |
| 1309 | - itemStyle: { color: '#4CAF50' } | 1360 | + itemStyle: { color: '#9CCC65' } |
| 1310 | } | 1361 | } |
| 1311 | ], | 1362 | ], |
| 1312 | label: { | 1363 | label: { |
| ... | @@ -1339,30 +1390,36 @@ const initPieChart = async () => { | ... | @@ -1339,30 +1390,36 @@ const initPieChart = async () => { |
| 1339 | console.error('获取饼图数据失败:', error) | 1390 | console.error('获取饼图数据失败:', error) |
| 1340 | 1391 | ||
| 1341 | // 如果API调用失败,使用当前列表数据作为备用方案 | 1392 | // 如果API调用失败,使用当前列表数据作为备用方案 |
| 1342 | - const calculatedCount = rebateList.value.filter(item => item.calcFlag === 1).length | 1393 | + const usedAmount = rebateList.value |
| 1343 | - const unCalculatedCount = rebateList.value.filter(item => item.calcFlag === 0).length | 1394 | + .filter(item => item.calcFlag === 1) |
| 1395 | + .reduce((sum, item) => sum + (item.rebateAmount || 0), 0) | ||
| 1396 | + const remainingAmount = rebateList.value | ||
| 1397 | + .filter(item => item.calcFlag === 0) | ||
| 1398 | + .reduce((sum, item) => sum + (item.rebateAmount || 0), 0) | ||
| 1344 | 1399 | ||
| 1345 | const fallbackOption = { | 1400 | const fallbackOption = { |
| 1346 | tooltip: { | 1401 | tooltip: { |
| 1347 | trigger: 'item', | 1402 | trigger: 'item', |
| 1348 | - formatter: '{b}: {c} ({d}%)' | 1403 | + formatter: (params: any) => { |
| 1404 | + return `${params.name}: ¥${params.value.toLocaleString()} (${params.percent}%)` | ||
| 1405 | + } | ||
| 1349 | }, | 1406 | }, |
| 1350 | series: [ | 1407 | series: [ |
| 1351 | { | 1408 | { |
| 1352 | - name: '审核状态', | 1409 | + name: '返利统计', |
| 1353 | type: 'pie', | 1410 | type: 'pie', |
| 1354 | radius: ['35%', '65%'], | 1411 | radius: ['35%', '65%'], |
| 1355 | center: ['50%', '50%'], | 1412 | center: ['50%', '50%'], |
| 1356 | data: [ | 1413 | data: [ |
| 1357 | { | 1414 | { |
| 1358 | - value: calculatedCount, | 1415 | + value: remainingAmount, |
| 1359 | - name: '已审核', | 1416 | + name: '剩余返利', |
| 1360 | itemStyle: { color: '#2196F3' } | 1417 | itemStyle: { color: '#2196F3' } |
| 1361 | }, | 1418 | }, |
| 1362 | { | 1419 | { |
| 1363 | - value: unCalculatedCount, | 1420 | + value: usedAmount, |
| 1364 | - name: '未审核', | 1421 | + name: '已使用返利', |
| 1365 | - itemStyle: { color: '#4CAF50' } | 1422 | + itemStyle: { color: '#9CCC65' } |
| 1366 | } | 1423 | } |
| 1367 | ], | 1424 | ], |
| 1368 | label: { | 1425 | label: { |
| ... | @@ -1475,7 +1532,7 @@ onMounted(async () => { | ... | @@ -1475,7 +1532,7 @@ onMounted(async () => { |
| 1475 | font-size: 16px; | 1532 | font-size: 16px; |
| 1476 | font-weight: 500; | 1533 | font-weight: 500; |
| 1477 | color: #333; | 1534 | color: #333; |
| 1478 | - margin: 0; | 1535 | + margin: 0; |
| 1479 | } | 1536 | } |
| 1480 | 1537 | ||
| 1481 | .chart-legend { | 1538 | .chart-legend { |
| ... | @@ -1540,9 +1597,15 @@ onMounted(async () => { | ... | @@ -1540,9 +1597,15 @@ onMounted(async () => { |
| 1540 | color: #333; | 1597 | color: #333; |
| 1541 | } | 1598 | } |
| 1542 | 1599 | ||
| 1543 | - .table-info { | 1600 | + .table-actions { |
| 1544 | - font-size: 12px; | 1601 | + display: flex; |
| 1545 | - color: #999; | 1602 | + align-items: center; |
| 1603 | + gap: 12px; | ||
| 1604 | + | ||
| 1605 | + .table-info { | ||
| 1606 | + font-size: 12px; | ||
| 1607 | + color: #999; | ||
| 1608 | + } | ||
| 1546 | } | 1609 | } |
| 1547 | } | 1610 | } |
| 1548 | 1611 | ||
| ... | @@ -1571,7 +1634,7 @@ onMounted(async () => { | ... | @@ -1571,7 +1634,7 @@ onMounted(async () => { |
| 1571 | } | 1634 | } |
| 1572 | 1635 | ||
| 1573 | .pagination-wrapper { | 1636 | .pagination-wrapper { |
| 1574 | - display: flex; | 1637 | + display: flex; |
| 1575 | justify-content: space-between; | 1638 | justify-content: space-between; |
| 1576 | align-items: center; | 1639 | align-items: center; |
| 1577 | padding: 12px 20px; | 1640 | padding: 12px 20px; | ... | ... |
frontend/src/views/settings/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="settings-container"> | ||
| 3 | + <!-- 页面标题 --> | ||
| 4 | + <div class="page-header"> | ||
| 5 | + <h2>系统设置</h2> | ||
| 6 | + <p>管理系统配置和参数</p> | ||
| 7 | + </div> | ||
| 8 | + | ||
| 9 | + <!-- 设置内容 --> | ||
| 10 | + <div class="settings-content"> | ||
| 11 | + <div class="settings-card"> | ||
| 12 | + <h3>基本设置</h3> | ||
| 13 | + <div class="setting-item"> | ||
| 14 | + <label>系统名称:</label> | ||
| 15 | + <input v-model="settings.systemName" class="setting-input" placeholder="请输入系统名称" /> | ||
| 16 | + </div> | ||
| 17 | + <div class="setting-item"> | ||
| 18 | + <label>系统版本:</label> | ||
| 19 | + <input v-model="settings.systemVersion" class="setting-input" placeholder="请输入系统版本" /> | ||
| 20 | + </div> | ||
| 21 | + <div class="setting-item"> | ||
| 22 | + <label>系统描述:</label> | ||
| 23 | + <textarea v-model="settings.systemDescription" class="setting-textarea" placeholder="请输入系统描述"></textarea> | ||
| 24 | + </div> | ||
| 25 | + </div> | ||
| 26 | + | ||
| 27 | + <div class="settings-card"> | ||
| 28 | + <h3>业务设置</h3> | ||
| 29 | + <div class="setting-item"> | ||
| 30 | + <label>默认分页大小:</label> | ||
| 31 | + <select v-model="settings.defaultPageSize" class="setting-select"> | ||
| 32 | + <option value="10">10条/页</option> | ||
| 33 | + <option value="20">20条/页</option> | ||
| 34 | + <option value="50">50条/页</option> | ||
| 35 | + <option value="100">100条/页</option> | ||
| 36 | + </select> | ||
| 37 | + </div> | ||
| 38 | + <div class="setting-item"> | ||
| 39 | + <label>数据保留天数:</label> | ||
| 40 | + <input v-model="settings.dataRetentionDays" type="number" class="setting-input" placeholder="请输入数据保留天数" /> | ||
| 41 | + </div> | ||
| 42 | + <div class="setting-item"> | ||
| 43 | + <label>自动备份:</label> | ||
| 44 | + <label class="checkbox-label"> | ||
| 45 | + <input v-model="settings.autoBackup" type="checkbox" /> | ||
| 46 | + <span>启用自动备份</span> | ||
| 47 | + </label> | ||
| 48 | + </div> | ||
| 49 | + </div> | ||
| 50 | + | ||
| 51 | + <div class="settings-card"> | ||
| 52 | + <h3>安全设置</h3> | ||
| 53 | + <div class="setting-item"> | ||
| 54 | + <label>会话超时时间(分钟):</label> | ||
| 55 | + <input v-model="settings.sessionTimeout" type="number" class="setting-input" placeholder="请输入会话超时时间" /> | ||
| 56 | + </div> | ||
| 57 | + <div class="setting-item"> | ||
| 58 | + <label>密码复杂度:</label> | ||
| 59 | + <select v-model="settings.passwordComplexity" class="setting-select"> | ||
| 60 | + <option value="low">低</option> | ||
| 61 | + <option value="medium">中</option> | ||
| 62 | + <option value="high">高</option> | ||
| 63 | + </select> | ||
| 64 | + </div> | ||
| 65 | + <div class="setting-item"> | ||
| 66 | + <label>登录失败锁定:</label> | ||
| 67 | + <label class="checkbox-label"> | ||
| 68 | + <input v-model="settings.loginLock" type="checkbox" /> | ||
| 69 | + <span>启用登录失败锁定</span> | ||
| 70 | + </label> | ||
| 71 | + </div> | ||
| 72 | + </div> | ||
| 73 | + | ||
| 74 | + <div class="settings-card"> | ||
| 75 | + <h3>通知设置</h3> | ||
| 76 | + <div class="setting-item"> | ||
| 77 | + <label>邮件通知:</label> | ||
| 78 | + <label class="checkbox-label"> | ||
| 79 | + <input v-model="settings.emailNotification" type="checkbox" /> | ||
| 80 | + <span>启用邮件通知</span> | ||
| 81 | + </label> | ||
| 82 | + </div> | ||
| 83 | + <div class="setting-item"> | ||
| 84 | + <label>短信通知:</label> | ||
| 85 | + <label class="checkbox-label"> | ||
| 86 | + <input v-model="settings.smsNotification" type="checkbox" /> | ||
| 87 | + <span>启用短信通知</span> | ||
| 88 | + </label> | ||
| 89 | + </div> | ||
| 90 | + <div class="setting-item"> | ||
| 91 | + <label>系统消息:</label> | ||
| 92 | + <label class="checkbox-label"> | ||
| 93 | + <input v-model="settings.systemMessage" type="checkbox" /> | ||
| 94 | + <span>启用系统消息</span> | ||
| 95 | + </label> | ||
| 96 | + </div> | ||
| 97 | + </div> | ||
| 98 | + | ||
| 99 | + <!-- 操作按钮 --> | ||
| 100 | + <div class="settings-actions"> | ||
| 101 | + <button @click="handleSave" class="action-btn primary">💾 保存设置</button> | ||
| 102 | + <button @click="handleReset" class="action-btn secondary">🔄 重置</button> | ||
| 103 | + <button @click="handleTest" class="action-btn info">🧪 测试连接</button> | ||
| 104 | + </div> | ||
| 105 | + </div> | ||
| 106 | + </div> | ||
| 107 | +</template> | ||
| 108 | + | ||
| 109 | +<script setup lang="ts"> | ||
| 110 | +import { ref, reactive, onMounted } from 'vue' | ||
| 111 | + | ||
| 112 | +// 设置数据 | ||
| 113 | +const settings = reactive({ | ||
| 114 | + systemName: 'Apple ERP系统', | ||
| 115 | + systemVersion: '1.0.0', | ||
| 116 | + systemDescription: '企业资源规划管理系统', | ||
| 117 | + defaultPageSize: 20, | ||
| 118 | + dataRetentionDays: 365, | ||
| 119 | + autoBackup: true, | ||
| 120 | + sessionTimeout: 30, | ||
| 121 | + passwordComplexity: 'medium', | ||
| 122 | + loginLock: true, | ||
| 123 | + emailNotification: true, | ||
| 124 | + smsNotification: false, | ||
| 125 | + systemMessage: true | ||
| 126 | +}) | ||
| 127 | + | ||
| 128 | +// 原始设置(用于重置) | ||
| 129 | +const originalSettings = ref({}) | ||
| 130 | + | ||
| 131 | +// 保存设置 | ||
| 132 | +const handleSave = () => { | ||
| 133 | + // 这里可以调用后端API保存设置 | ||
| 134 | + console.log('保存设置:', settings) | ||
| 135 | + showMessage('设置保存成功', 'success') | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +// 重置设置 | ||
| 139 | +const handleReset = () => { | ||
| 140 | + Object.assign(settings, originalSettings.value) | ||
| 141 | + showMessage('设置已重置', 'warning') | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +// 测试连接 | ||
| 145 | +const handleTest = () => { | ||
| 146 | + showMessage('连接测试成功', 'success') | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +// 显示消息 | ||
| 150 | +const showMessage = (message: string, type: 'success' | 'warning' | 'error') => { | ||
| 151 | + // 这里可以集成Element Plus的消息组件 | ||
| 152 | + console.log(`${type}: ${message}`) | ||
| 153 | +} | ||
| 154 | + | ||
| 155 | +// 组件挂载时加载设置 | ||
| 156 | +onMounted(() => { | ||
| 157 | + // 这里可以调用后端API加载设置 | ||
| 158 | + originalSettings.value = { ...settings } | ||
| 159 | +}) | ||
| 160 | +</script> | ||
| 161 | + | ||
| 162 | +<style scoped> | ||
| 163 | +.settings-container { | ||
| 164 | + padding: 20px; | ||
| 165 | + background-color: #f5f5f5; | ||
| 166 | + min-height: 100vh; | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +.page-header { | ||
| 170 | + margin-bottom: 30px; | ||
| 171 | + text-align: center; | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +.page-header h2 { | ||
| 175 | + color: #333; | ||
| 176 | + margin-bottom: 10px; | ||
| 177 | + font-size: 28px; | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +.page-header p { | ||
| 181 | + color: #666; | ||
| 182 | + font-size: 16px; | ||
| 183 | +} | ||
| 184 | + | ||
| 185 | +.settings-content { | ||
| 186 | + max-width: 1200px; | ||
| 187 | + margin: 0 auto; | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +.settings-card { | ||
| 191 | + background: white; | ||
| 192 | + border-radius: 8px; | ||
| 193 | + padding: 24px; | ||
| 194 | + margin-bottom: 24px; | ||
| 195 | + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +.settings-card h3 { | ||
| 199 | + color: #333; | ||
| 200 | + margin-bottom: 20px; | ||
| 201 | + font-size: 18px; | ||
| 202 | + border-bottom: 2px solid #e9ecef; | ||
| 203 | + padding-bottom: 10px; | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +.setting-item { | ||
| 207 | + display: flex; | ||
| 208 | + align-items: center; | ||
| 209 | + margin-bottom: 20px; | ||
| 210 | + gap: 16px; | ||
| 211 | +} | ||
| 212 | + | ||
| 213 | +.setting-item label { | ||
| 214 | + min-width: 150px; | ||
| 215 | + color: #333; | ||
| 216 | + font-weight: 500; | ||
| 217 | +} | ||
| 218 | + | ||
| 219 | +.setting-input, | ||
| 220 | +.setting-select, | ||
| 221 | +.setting-textarea { | ||
| 222 | + flex: 1; | ||
| 223 | + padding: 8px 12px; | ||
| 224 | + border: 1px solid #ddd; | ||
| 225 | + border-radius: 4px; | ||
| 226 | + font-size: 14px; | ||
| 227 | + transition: border-color 0.3s; | ||
| 228 | +} | ||
| 229 | + | ||
| 230 | +.setting-input:focus, | ||
| 231 | +.setting-select:focus, | ||
| 232 | +.setting-textarea:focus { | ||
| 233 | + outline: none; | ||
| 234 | + border-color: #007bff; | ||
| 235 | + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +.setting-textarea { | ||
| 239 | + min-height: 80px; | ||
| 240 | + resize: vertical; | ||
| 241 | +} | ||
| 242 | + | ||
| 243 | +.checkbox-label { | ||
| 244 | + display: flex; | ||
| 245 | + align-items: center; | ||
| 246 | + gap: 8px; | ||
| 247 | + cursor: pointer; | ||
| 248 | +} | ||
| 249 | + | ||
| 250 | +.checkbox-label input[type="checkbox"] { | ||
| 251 | + width: 16px; | ||
| 252 | + height: 16px; | ||
| 253 | +} | ||
| 254 | + | ||
| 255 | +.settings-actions { | ||
| 256 | + display: flex; | ||
| 257 | + gap: 16px; | ||
| 258 | + justify-content: center; | ||
| 259 | + margin-top: 30px; | ||
| 260 | +} | ||
| 261 | + | ||
| 262 | +.action-btn { | ||
| 263 | + padding: 12px 24px; | ||
| 264 | + border: none; | ||
| 265 | + border-radius: 6px; | ||
| 266 | + font-size: 14px; | ||
| 267 | + font-weight: 500; | ||
| 268 | + cursor: pointer; | ||
| 269 | + transition: all 0.3s; | ||
| 270 | + min-width: 120px; | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +.action-btn.primary { | ||
| 274 | + background-color: #007bff; | ||
| 275 | + color: white; | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +.action-btn.primary:hover { | ||
| 279 | + background-color: #0056b3; | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +.action-btn.secondary { | ||
| 283 | + background-color: #6c757d; | ||
| 284 | + color: white; | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +.action-btn.secondary:hover { | ||
| 288 | + background-color: #545b62; | ||
| 289 | +} | ||
| 290 | + | ||
| 291 | +.action-btn.info { | ||
| 292 | + background-color: #17a2b8; | ||
| 293 | + color: white; | ||
| 294 | +} | ||
| 295 | + | ||
| 296 | +.action-btn.info:hover { | ||
| 297 | + background-color: #138496; | ||
| 298 | +} | ||
| 299 | + | ||
| 300 | +@media (max-width: 768px) { | ||
| 301 | + .setting-item { | ||
| 302 | + flex-direction: column; | ||
| 303 | + align-items: flex-start; | ||
| 304 | + } | ||
| 305 | + | ||
| 306 | + .setting-item label { | ||
| 307 | + min-width: auto; | ||
| 308 | + margin-bottom: 8px; | ||
| 309 | + } | ||
| 310 | + | ||
| 311 | + .settings-actions { | ||
| 312 | + flex-direction: column; | ||
| 313 | + align-items: center; | ||
| 314 | + } | ||
| 315 | +} | ||
| 316 | +</style> |
| ... | @@ -7,7 +7,7 @@ | ... | @@ -7,7 +7,7 @@ |
| 7 | "auto-imports.d.ts", | 7 | "auto-imports.d.ts", |
| 8 | "components.d.ts" | 8 | "components.d.ts" |
| 9 | ], | 9 | ], |
| 10 | - "exclude": ["src/**/__tests__/*"], | 10 | + "exclude": ["src/**/__tests__/*", "src/views/test-menu.vue"], |
| 11 | "compilerOptions": { | 11 | "compilerOptions": { |
| 12 | "composite": true, | 12 | "composite": true, |
| 13 | "baseUrl": ".", | 13 | "baseUrl": ".", | ... | ... |
-
Please register or login to post a comment