zhouhui.jiang

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

# Conflicts:
#	frontend/components.d.ts
#	frontend/src/views/delivery/index.vue
Showing 51 changed files with 5279 additions and 267 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
219 + try {
220 + // 获取用户详细信息
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 + // 如果找不到用户信息,返回基本信息
217 UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities()); 236 UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities());
218 ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); 237 ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo);
219 return ResponseEntity.ok(response); 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 /**
...@@ -33,6 +37,9 @@ public class DeliveryMainController { ...@@ -33,6 +37,9 @@ public class DeliveryMainController {
33 @Autowired 37 @Autowired
34 private DeliveryMainService deliveryMainService; 38 private DeliveryMainService deliveryMainService;
35 39
40 + @Autowired
41 + private ExcelExportService excelExportService;
42 +
36 @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表") 43 @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表")
37 @GetMapping("/list") 44 @GetMapping("/list")
38 @PreAuthorize("hasAuthority('delivery:list')") 45 @PreAuthorize("hasAuthority('delivery:list')")
...@@ -139,6 +146,40 @@ public class DeliveryMainController { ...@@ -139,6 +146,40 @@ public class DeliveryMainController {
139 return ApiRes.error("修改出库状态失败: " + e.getMessage()); 146 return ApiRes.error("修改出库状态失败: " + e.getMessage());
140 } 147 }
141 } 148 }
149 +
150 + @Operation(summary = "导出出库数据", description = "根据查询条件导出出库数据到Excel")
151 + @PostMapping("/export")
152 + @PreAuthorize("hasAuthority('delivery:export')")
153 + public ResponseEntity<byte[]> exportDeliveries(@Valid @RequestBody DeliveryQueryReq queryReq) {
154 + try {
155 + // 获取所有符合条件的数据(不分页)
156 + DeliveryQueryReq exportQuery = new DeliveryQueryReq();
157 + exportQuery.setDeliveryNo(queryReq.getDeliveryNo());
158 + exportQuery.setDealerCode(queryReq.getDealerCode());
159 + exportQuery.setDealerName(queryReq.getDealerName());
160 + exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus());
161 + exportQuery.setWarehouseCode(queryReq.getWarehouseCode());
162 + exportQuery.setDataSource(queryReq.getDataSource());
163 + exportQuery.setDeliveryStartDate(queryReq.getDeliveryStartDate());
164 + exportQuery.setDeliveryEndDate(queryReq.getDeliveryEndDate());
165 + // 设置大分页获取所有数据
166 + exportQuery.setPageNum(1);
167 + exportQuery.setPageSize(10000);
168 +
169 + Page<DeliveryRes> result = deliveryMainService.getDeliveryList(exportQuery);
170 + List<DeliveryRes> deliveries = result.getRecords();
171 +
172 + // 生成文件名
173 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
174 + String fileName = "出库数据_" + timestamp;
175 +
176 + // 导出到Excel
177 + return excelExportService.exportDeliveries(deliveries);
178 +
179 + } catch (Exception e) {
180 + throw new RuntimeException("导出出库数据失败: " + e.getMessage(), e);
181 + }
182 + }
142 } 183 }
143 184
144 185
......
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 /**
...@@ -33,6 +37,9 @@ public class InvoiceMainController { ...@@ -33,6 +37,9 @@ public class InvoiceMainController {
33 @Autowired 37 @Autowired
34 private InvoiceMainService invoiceMainService; 38 private InvoiceMainService invoiceMainService;
35 39
40 + @Autowired
41 + private ExcelExportService excelExportService;
42 +
36 @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表") 43 @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表")
37 @GetMapping("/list") 44 @GetMapping("/list")
38 @PreAuthorize("hasAuthority('invoice:list')") 45 @PreAuthorize("hasAuthority('invoice:list')")
...@@ -139,6 +146,43 @@ public class InvoiceMainController { ...@@ -139,6 +146,43 @@ public class InvoiceMainController {
139 return ApiRes.error("修改发票状态失败: " + e.getMessage()); 146 return ApiRes.error("修改发票状态失败: " + e.getMessage());
140 } 147 }
141 } 148 }
149 +
150 + @Operation(summary = "导出发票数据", description = "根据查询条件导出发票数据到Excel")
151 + @PostMapping("/export")
152 + @PreAuthorize("hasAuthority('invoice:export')")
153 + public ResponseEntity<byte[]> exportInvoices(@Valid @RequestBody InvoiceQueryReq queryReq) {
154 + try {
155 + // 获取所有符合条件的数据(不分页)
156 + InvoiceQueryReq exportQuery = new InvoiceQueryReq();
157 + exportQuery.setInvoiceNo(queryReq.getInvoiceNo());
158 + exportQuery.setOrderNo(queryReq.getOrderNo());
159 + exportQuery.setDeliveryNo(queryReq.getDeliveryNo());
160 + exportQuery.setDealerCode(queryReq.getDealerCode());
161 + exportQuery.setDealerName(queryReq.getDealerName());
162 + exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus());
163 + exportQuery.setDataSource(queryReq.getDataSource());
164 + exportQuery.setInvoiceStartDate(queryReq.getInvoiceStartDate());
165 + exportQuery.setInvoiceEndDate(queryReq.getInvoiceEndDate());
166 + exportQuery.setMinAmount(queryReq.getMinAmount());
167 + exportQuery.setMaxAmount(queryReq.getMaxAmount());
168 + // 设置大分页获取所有数据
169 + exportQuery.setPageNum(1);
170 + exportQuery.setPageSize(10000);
171 +
172 + Page<InvoiceRes> result = invoiceMainService.getInvoiceList(exportQuery);
173 + List<InvoiceRes> invoices = result.getRecords();
174 +
175 + // 生成文件名
176 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
177 + String fileName = "发票数据_" + timestamp;
178 +
179 + // 导出到Excel
180 + return excelExportService.exportInvoices(invoices);
181 +
182 + } catch (Exception e) {
183 + throw new RuntimeException("导出发票数据失败: " + e.getMessage(), e);
184 + }
185 + }
142 } 186 }
143 187
144 188
......
...@@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes; ...@@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes;
6 import com.apple.erp.dto.OrderUpdateReq; 6 import com.apple.erp.dto.OrderUpdateReq;
7 import com.apple.erp.service.OrderMainService; 7 import com.apple.erp.service.OrderMainService;
8 import com.apple.erp.dto.response.ApiRes; 8 import com.apple.erp.dto.response.ApiRes;
9 +import com.apple.erp.service.ExcelExportService;
9 import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 10 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
10 import io.swagger.v3.oas.annotations.Operation; 11 import io.swagger.v3.oas.annotations.Operation;
11 import io.swagger.v3.oas.annotations.Parameter; 12 import io.swagger.v3.oas.annotations.Parameter;
12 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
13 import org.springframework.beans.factory.annotation.Autowired; 14 import org.springframework.beans.factory.annotation.Autowired;
15 +import org.springframework.http.ResponseEntity;
14 import org.springframework.security.access.prepost.PreAuthorize; 16 import org.springframework.security.access.prepost.PreAuthorize;
15 import org.springframework.validation.annotation.Validated; 17 import org.springframework.validation.annotation.Validated;
16 import org.springframework.web.bind.annotation.*; 18 import org.springframework.web.bind.annotation.*;
17 19
18 import javax.validation.Valid; 20 import javax.validation.Valid;
21 +import java.time.LocalDateTime;
22 +import java.time.format.DateTimeFormatter;
19 import java.util.List; 23 import java.util.List;
20 24
21 /** 25 /**
...@@ -33,6 +37,9 @@ public class OrderMainController { ...@@ -33,6 +37,9 @@ public class OrderMainController {
33 @Autowired 37 @Autowired
34 private OrderMainService orderMainService; 38 private OrderMainService orderMainService;
35 39
40 + @Autowired
41 + private ExcelExportService excelExportService;
42 +
36 @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表") 43 @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表")
37 @GetMapping("/list") 44 @GetMapping("/list")
38 @PreAuthorize("hasAuthority('order:list')") 45 @PreAuthorize("hasAuthority('order:list')")
...@@ -182,4 +189,42 @@ public class OrderMainController { ...@@ -182,4 +189,42 @@ public class OrderMainController {
182 return ApiRes.error("修改返利计算状态失败: " + e.getMessage()); 189 return ApiRes.error("修改返利计算状态失败: " + e.getMessage());
183 } 190 }
184 } 191 }
192 +
193 + @Operation(summary = "导出订单数据", description = "根据查询条件导出订单数据到Excel")
194 + @PostMapping("/export")
195 + @PreAuthorize("hasAuthority('order:export')")
196 + public ResponseEntity<byte[]> exportOrders(@Valid @RequestBody OrderQueryReq queryReq) {
197 + try {
198 + // 获取所有符合条件的数据(不分页)
199 + OrderQueryReq exportQuery = new OrderQueryReq();
200 + exportQuery.setOrderNo(queryReq.getOrderNo());
201 + exportQuery.setDealerCode(queryReq.getDealerCode());
202 + exportQuery.setDealerName(queryReq.getDealerName());
203 + exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus());
204 + exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus());
205 + exportQuery.setRebateCalcFlag(queryReq.getRebateCalcFlag());
206 + exportQuery.setDataSource(queryReq.getDataSource());
207 + exportQuery.setVerifyStatus(queryReq.getVerifyStatus());
208 + exportQuery.setOrderStartDate(queryReq.getOrderStartDate());
209 + exportQuery.setOrderEndDate(queryReq.getOrderEndDate());
210 + exportQuery.setMinAmount(queryReq.getMinAmount());
211 + exportQuery.setMaxAmount(queryReq.getMaxAmount());
212 + // 设置大分页获取所有数据
213 + exportQuery.setPageNum(1);
214 + exportQuery.setPageSize(10000);
215 +
216 + Page<OrderRes> result = orderMainService.getOrderList(exportQuery);
217 + List<OrderRes> orders = result.getRecords();
218 +
219 + // 生成文件名
220 + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
221 + String fileName = "订单数据_" + timestamp;
222 +
223 + // 导出到Excel
224 + return excelExportService.exportOrders(orders);
225 +
226 + } catch (Exception e) {
227 + throw new RuntimeException("导出订单数据失败: " + e.getMessage(), e);
228 + }
229 + }
185 } 230 }
......
...@@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes; ...@@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes;
7 import com.apple.erp.dto.response.RebateRes; 7 import com.apple.erp.dto.response.RebateRes;
8 import com.apple.erp.entity.Rebate; 8 import com.apple.erp.entity.Rebate;
9 import com.apple.erp.service.RebateService; 9 import com.apple.erp.service.RebateService;
10 +import com.apple.erp.service.ExcelExportService;
10 import com.baomidou.mybatisplus.core.metadata.IPage; 11 import com.baomidou.mybatisplus.core.metadata.IPage;
11 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
12 import io.swagger.v3.oas.annotations.tags.Tag; 13 import io.swagger.v3.oas.annotations.tags.Tag;
...@@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*; ...@@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*;
17 18
18 import javax.validation.Valid; 19 import javax.validation.Valid;
19 import java.math.BigDecimal; 20 import java.math.BigDecimal;
21 +import java.time.LocalDateTime;
22 +import java.time.format.DateTimeFormatter;
20 import java.util.List; 23 import java.util.List;
21 import java.util.Map; 24 import java.util.Map;
25 +import org.springframework.http.ResponseEntity;
26 +import org.springframework.security.access.prepost.PreAuthorize;
22 27
23 /** 28 /**
24 * 返利台账明细管理控制器 29 * 返利台账明细管理控制器
...@@ -37,6 +42,9 @@ public class RebateController { ...@@ -37,6 +42,9 @@ public class RebateController {
37 @Autowired 42 @Autowired
38 private RebateService rebateService; 43 private RebateService rebateService;
39 44
45 + @Autowired
46 + private ExcelExportService excelExportService;
47 +
40 @Operation(summary = "分页查询返利明细列表") 48 @Operation(summary = "分页查询返利明细列表")
41 @PostMapping("/page") 49 @PostMapping("/page")
42 public ApiRes<IPage<RebateRes>> getRebatePage(@Valid @RequestBody RebateQueryReq queryReq) { 50 public ApiRes<IPage<RebateRes>> getRebatePage(@Valid @RequestBody RebateQueryReq queryReq) {
...@@ -277,4 +285,40 @@ public class RebateController { ...@@ -277,4 +285,40 @@ public class RebateController {
277 return ApiRes.error(e.getMessage()); 285 return ApiRes.error(e.getMessage());
278 } 286 }
279 } 287 }
288 +
289 + @Operation(summary = "导出返利数据", description = "根据查询条件导出返利数据到Excel")
290 + @PostMapping("/export")
291 + @PreAuthorize("hasAuthority('rebate:export')")
292 + public ResponseEntity<byte[]> exportRebates(@Valid @RequestBody RebateQueryReq queryReq) {
293 + log.info("导出返利数据,参数:{}", queryReq);
294 + try {
295 + // 获取所有符合条件的数据(不分页)
296 + RebateQueryReq exportQuery = new RebateQueryReq();
297 + exportQuery.setRebateNo(queryReq.getRebateNo());
298 + exportQuery.setOrderNo(queryReq.getOrderNo());
299 + exportQuery.setDealerCode(queryReq.getDealerCode());
300 + exportQuery.setDealerName(queryReq.getDealerName());
301 + exportQuery.setOperateType(queryReq.getOperateType());
302 + exportQuery.setCalcFlag(queryReq.getCalcFlag());
303 + exportQuery.setAuditStatus(queryReq.getAuditStatus());
304 + exportQuery.setDataSource(queryReq.getDataSource());
305 + exportQuery.setStartDate(queryReq.getStartDate());
306 + exportQuery.setEndDate(queryReq.getEndDate());
307 + exportQuery.setMinAmount(queryReq.getMinAmount());
308 + exportQuery.setMaxAmount(queryReq.getMaxAmount());
309 + // 设置大分页获取所有数据
310 + exportQuery.setPageNum(1);
311 + exportQuery.setPageSize(10000);
312 +
313 + IPage<RebateRes> result = rebateService.getRebatePage(exportQuery);
314 + List<RebateRes> rebates = result.getRecords();
315 +
316 + // 导出到Excel
317 + return excelExportService.exportRebates(rebates);
318 +
319 + } catch (Exception e) {
320 + log.error("导出返利数据失败", e);
321 + throw new RuntimeException("导出返利数据失败: " + e.getMessage(), e);
322 + }
323 + }
280 } 324 }
......
...@@ -193,6 +193,25 @@ public class SysDictItemController { ...@@ -193,6 +193,25 @@ public class SysDictItemController {
193 } 193 }
194 194
195 /** 195 /**
196 + * 刷新字典缓存
197 + * 当字典项发生变化时,刷新Redis缓存以保持数据一致性
198 + *
199 + * @return 操作结果
200 + */
201 + @Operation(summary = "刷新字典缓存", description = "刷新Redis字典缓存以保持数据一致性")
202 + @PostMapping("/refreshCache")
203 + @PreAuthorize("hasAuthority('sys:dict:edit')")
204 + public ApiRes<Void> refreshCache() {
205 + try {
206 + // 调用字典值转换器的刷新方法
207 + com.apple.erp.util.DictValueConverter.refreshCache();
208 + return ApiRes.success("字典缓存刷新成功", null);
209 + } catch (Exception e) {
210 + return ApiRes.error("刷新字典缓存失败: " + e.getMessage());
211 + }
212 + }
213 +
214 + /**
196 * 转换SysDictItem为DictItemRes 215 * 转换SysDictItem为DictItemRes
197 * 216 *
198 * @param dictItem 字典项实体 217 * @param dictItem 字典项实体
......
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 &lt;= #{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;
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
5 // Generated by unplugin-auto-import 5 // Generated by unplugin-auto-import
6 export {} 6 export {}
7 declare global { 7 declare global {
8 - 8 + const ElMessage: typeof import('element-plus/es')['ElMessage']
9 } 9 }
......
...@@ -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
......
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,10 +393,18 @@ watch(() => route.path, (newPath) => { ...@@ -370,10 +393,18 @@ watch(() => route.path, (newPath) => {
370 393
371 // 组件挂载时获取用户信息 394 // 组件挂载时获取用户信息
372 onMounted(() => { 395 onMounted(() => {
396 + // 检查是否已登录
397 + const token = localStorage.getItem('token')
398 + if (token) {
399 + // 调用API获取用户信息
400 + fetchUserInfo()
401 + } else {
402 + // 如果没有token,尝试从本地存储获取
373 const storedUserInfo = localStorage.getItem('userInfo') 403 const storedUserInfo = localStorage.getItem('userInfo')
374 if (storedUserInfo) { 404 if (storedUserInfo) {
375 userInfo.value = JSON.parse(storedUserInfo) 405 userInfo.value = JSON.parse(storedUserInfo)
376 } 406 }
407 + }
377 }) 408 })
378 </script> 409 </script>
379 410
......
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">
...@@ -45,11 +46,13 @@ ...@@ -45,11 +46,13 @@
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>
50 + <span v-else>{{ userInfo?.username || '未知' }}</span>
49 </div> 51 </div>
50 <div class="info-item"> 52 <div class="info-item">
51 <label>角色:</label> 53 <label>角色:</label>
52 - <span>{{ userInfo?.roles?.[0]?.roleName || '普通用户' }}</span> 54 + <span v-if="loading">加载中...</span>
55 + <span v-else>{{ getRoleName(userInfo) || '普通用户' }}</span>
53 </div> 56 </div>
54 <div class="info-item"> 57 <div class="info-item">
55 <label>登录时间:</label> 58 <label>登录时间:</label>
...@@ -65,10 +68,10 @@ ...@@ -65,10 +68,10 @@
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)
85 90
86 -onMounted(() => { 91 +// 获取角色名称
87 - // 获取当前时间 92 +const getRoleName = (userInfo: any) => {
88 - currentTime.value = new Date().toLocaleString() 93 + // 优先使用roles字段中的角色信息
94 + if (userInfo?.roles && Array.isArray(userInfo.roles) && userInfo.roles.length > 0) {
95 + return userInfo.roles[0].roleName || '普通用户'
96 + }
89 97
90 - // 获取用户信息 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 + // 如果获取失败,尝试从本地存储获取
91 const storedUserInfo = localStorage.getItem('userInfo') 132 const storedUserInfo = localStorage.getItem('userInfo')
92 if (storedUserInfo) { 133 if (storedUserInfo) {
93 userInfo.value = JSON.parse(storedUserInfo) 134 userInfo.value = JSON.parse(storedUserInfo)
94 } 135 }
136 + } finally {
137 + loading.value = false
138 + }
139 +}
140 +
141 +onMounted(() => {
142 + // 获取当前时间
143 + currentTime.value = new Date().toLocaleString()
95 144
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 = () => {
......
...@@ -61,11 +61,11 @@ ...@@ -61,11 +61,11 @@
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>
...@@ -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,10 +102,16 @@ ...@@ -102,10 +102,16 @@
102 <div class="table-title"> 102 <div class="table-title">
103 返利记录 103 返利记录
104 </div> 104 </div>
105 + <div class="table-actions">
106 + <el-button type="primary" size="small" @click="handleExport" :loading="exportLoading">
107 + <el-icon><Download /></el-icon>
108 + 导出
109 + </el-button>
105 <div class="table-info"> 110 <div class="table-info">
106 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }} 111 数据状态: {{ loading ? '加载中...' : `共${rebateList.length}条记录` }}
107 </div> 112 </div>
108 </div> 113 </div>
114 + </div>
109 115
110 <el-table 116 <el-table
111 v-loading="loading" 117 v-loading="loading"
...@@ -384,12 +390,13 @@ import { ref, reactive, onMounted } from 'vue' ...@@ -384,12 +390,13 @@ import { ref, reactive, onMounted } from 'vue'
384 import { ElMessage, ElMessageBox } from 'element-plus' 390 import { ElMessage, ElMessageBox } from 'element-plus'
385 import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue' 391 import { Search, Refresh, Plus, Check, Close, Money, Delete, Download } from '@element-plus/icons-vue'
386 import * as echarts from 'echarts' 392 import * as echarts from 'echarts'
387 -import rebateApi, { type Rebate, type RebateSearchParams } from '@/api/rebate' 393 +import rebateApi, { type Rebate, type RebateSearchParams, exportRebates } from '@/api/rebate'
388 import { formatDate } from '@/utils/index' 394 import { formatDate } from '@/utils/index'
389 395
390 // 响应式数据 396 // 响应式数据
391 const loading = ref(false) 397 const loading = ref(false)
392 const submitLoading = ref(false) 398 const submitLoading = ref(false)
399 +const exportLoading = ref(false)
393 const auditLoading = ref(false) 400 const auditLoading = ref(false)
394 const rebateList = ref<Rebate[]>([]) 401 const rebateList = ref<Rebate[]>([])
395 const selectedRebates = ref<Rebate[]>([]) 402 const selectedRebates = ref<Rebate[]>([])
...@@ -1039,13 +1046,34 @@ const handleBatchRelease = async () => { ...@@ -1039,13 +1046,34 @@ const handleBatchRelease = async () => {
1039 // 导出 1046 // 导出
1040 const handleExport = async () => { 1047 const handleExport = async () => {
1041 try { 1048 try {
1042 - loading.value = true 1049 + // 显示确认弹窗
1050 + await ElMessageBox.confirm(
1051 + '确定要导出返利数据吗?导出将包含当前筛选条件下的所有数据。',
1052 + '确认导出',
1053 + {
1054 + confirmButtonText: '确定导出',
1055 + cancelButtonText: '取消',
1056 + type: 'warning',
1057 + center: true
1058 + }
1059 + )
1060 +
1061 + // 用户确认后显示加载提示
1062 + const loadingMessage = ElMessage({
1063 + message: '正在导出数据,请稍候...',
1064 + type: 'warning',
1065 + duration: 0, // 不自动关闭
1066 + showClose: false
1067 + })
1068 +
1069 + try {
1070 + exportLoading.value = true
1043 const params = { ...searchForm } 1071 const params = { ...searchForm }
1044 1072
1045 - const response = await rebateApi.exportRebate(params) 1073 + const response = await exportRebates(params)
1046 1074
1047 // 创建下载链接 1075 // 创建下载链接
1048 - const blob = new Blob([response.data], { 1076 + const blob = new Blob([response as unknown as BlobPart], {
1049 type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 1077 type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
1050 }) 1078 })
1051 const url = window.URL.createObjectURL(blob) 1079 const url = window.URL.createObjectURL(blob)
...@@ -1057,12 +1085,23 @@ const handleExport = async () => { ...@@ -1057,12 +1085,23 @@ const handleExport = async () => {
1057 document.body.removeChild(link) 1085 document.body.removeChild(link)
1058 window.URL.revokeObjectURL(url) 1086 window.URL.revokeObjectURL(url)
1059 1087
1060 - ElMessage.success('导出成功') 1088 + // 关闭加载提示,显示成功消息
1089 + loadingMessage.close()
1090 + ElMessage.success('导出成功!文件已开始下载')
1091 +
1092 + } catch (exportError) {
1093 + // 关闭加载提示
1094 + loadingMessage.close()
1095 + throw exportError
1096 + }
1097 +
1061 } catch (error) { 1098 } catch (error) {
1099 + if (error !== 'cancel') {
1062 console.error('导出失败:', error) 1100 console.error('导出失败:', error)
1063 - ElMessage.error('导出失败') 1101 + ElMessage.error('导出失败,请重试')
1102 + }
1064 } finally { 1103 } finally {
1065 - loading.value = false 1104 + exportLoading.value = false
1066 } 1105 }
1067 } 1106 }
1068 1107
...@@ -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: {
...@@ -1540,11 +1597,17 @@ onMounted(async () => { ...@@ -1540,11 +1597,17 @@ onMounted(async () => {
1540 color: #333; 1597 color: #333;
1541 } 1598 }
1542 1599
1600 + .table-actions {
1601 + display: flex;
1602 + align-items: center;
1603 + gap: 12px;
1604 +
1543 .table-info { 1605 .table-info {
1544 font-size: 12px; 1606 font-size: 12px;
1545 color: #999; 1607 color: #999;
1546 } 1608 }
1547 } 1609 }
1610 + }
1548 1611
1549 .el-table { 1612 .el-table {
1550 border: none; 1613 border: none;
......
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": ".",
......