Showing
32 changed files
with
1557 additions
and
250 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 获取用户列表 |
| ... | @@ -2296,6 +2331,50 @@ | ... | @@ -2296,6 +2331,50 @@ |
| 2296 | } | 2331 | } |
| 2297 | ``` | 2332 | ``` |
| 2298 | 2333 | ||
| 2334 | +### 8. 导出发票数据 | ||
| 2335 | + | ||
| 2336 | +**接口路径:** `POST /api/invoice/export` | ||
| 2337 | +**请求方法:** POST | ||
| 2338 | +**权限要求:** `invoice:export` | ||
| 2339 | + | ||
| 2340 | +**请求参数:** 同发票查询接口参数 | ||
| 2341 | + | ||
| 2342 | +**响应:** 返回Excel文件流 | ||
| 2343 | + | ||
| 2344 | +--- | ||
| 2345 | + | ||
| 2346 | +## 八、数据导出接口 | ||
| 2347 | + | ||
| 2348 | +### 1. 订单数据导出 | ||
| 2349 | + | ||
| 2350 | +**接口路径:** `POST /order/export` | ||
| 2351 | +**请求方法:** POST | ||
| 2352 | +**权限要求:** `order:export` | ||
| 2353 | + | ||
| 2354 | +**请求参数:** 同订单查询接口参数 | ||
| 2355 | + | ||
| 2356 | +**响应:** 返回Excel文件流,文件名格式:`订单数据_yyyyMMdd_HHmmss.xlsx` | ||
| 2357 | + | ||
| 2358 | +### 2. 出库数据导出 | ||
| 2359 | + | ||
| 2360 | +**接口路径:** `POST /api/delivery/export` | ||
| 2361 | +**请求方法:** POST | ||
| 2362 | +**权限要求:** `delivery:export` | ||
| 2363 | + | ||
| 2364 | +**请求参数:** 同出库查询接口参数 | ||
| 2365 | + | ||
| 2366 | +**响应:** 返回Excel文件流,文件名格式:`出库数据_yyyyMMdd_HHmmss.xlsx` | ||
| 2367 | + | ||
| 2368 | +### 3. 发票数据导出 | ||
| 2369 | + | ||
| 2370 | +**接口路径:** `POST /api/invoice/export` | ||
| 2371 | +**请求方法:** POST | ||
| 2372 | +**权限要求:** `invoice:export` | ||
| 2373 | + | ||
| 2374 | +**请求参数:** 同发票查询接口参数 | ||
| 2375 | + | ||
| 2376 | +**响应:** 返回Excel文件流,文件名格式:`发票数据_yyyyMMdd_HHmmss.xlsx` | ||
| 2377 | + | ||
| 2299 | --- | 2378 | --- |
| 2300 | 2379 | ||
| 2301 | **文档版本:** 1.0.0 | 2380 | **文档版本:** 1.0.0 | ... | ... |
| 1 | +package com.apple.erp.config; | ||
| 2 | + | ||
| 3 | +import java.util.HashMap; | ||
| 4 | +import java.util.Map; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 字典值配置类 | ||
| 8 | + * 集中管理所有字典值转换配置 | ||
| 9 | + * | ||
| 10 | + * @author Apple ERP System | ||
| 11 | + * @since 2025-01-01 | ||
| 12 | + */ | ||
| 13 | +public class DictConfig { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 操作类型字典 | ||
| 17 | + */ | ||
| 18 | + public static final Map<Integer, String> OPERATE_TYPE = new HashMap<Integer, String>() {{ | ||
| 19 | + put(1, "新增"); | ||
| 20 | + put(2, "修改"); | ||
| 21 | + put(3, "删除"); | ||
| 22 | + }}; | ||
| 23 | + | ||
| 24 | + /** | ||
| 25 | + * 计算状态字典 | ||
| 26 | + */ | ||
| 27 | + public static final Map<Integer, String> CALC_FLAG = new HashMap<Integer, String>() {{ | ||
| 28 | + put(0, "未计算"); | ||
| 29 | + put(1, "已计算"); | ||
| 30 | + }}; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 审核状态字典 | ||
| 34 | + */ | ||
| 35 | + public static final Map<Integer, String> AUDIT_STATUS = new HashMap<Integer, String>() {{ | ||
| 36 | + put(0, "待审核"); | ||
| 37 | + put(1, "审核通过"); | ||
| 38 | + put(2, "审核中"); | ||
| 39 | + put(3, "审核拒绝"); | ||
| 40 | + }}; | ||
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * 出库状态字典 | ||
| 44 | + */ | ||
| 45 | + public static final Map<Integer, String> DELIVERY_STATUS = new HashMap<Integer, String>() {{ | ||
| 46 | + put(0, "未出库"); | ||
| 47 | + put(1, "已出库"); | ||
| 48 | + put(2, "部分出库"); | ||
| 49 | + }}; | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 发票状态字典 | ||
| 53 | + */ | ||
| 54 | + public static final Map<Integer, String> INVOICE_STATUS = new HashMap<Integer, String>() {{ | ||
| 55 | + put(0, "未开票"); | ||
| 56 | + put(1, "已开票"); | ||
| 57 | + put(2, "部分开票"); | ||
| 58 | + }}; | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * 返利计算状态字典 | ||
| 62 | + */ | ||
| 63 | + public static final Map<Integer, String> REBATE_CALC_FLAG = new HashMap<Integer, String>() {{ | ||
| 64 | + put(0, "未计算"); | ||
| 65 | + put(1, "已计算"); | ||
| 66 | + }}; | ||
| 67 | + | ||
| 68 | + /** | ||
| 69 | + * 审核状态字典 | ||
| 70 | + */ | ||
| 71 | + public static final Map<Integer, String> VERIFY_STATUS = new HashMap<Integer, String>() {{ | ||
| 72 | + put(0, "待审核"); | ||
| 73 | + put(1, "审核通过"); | ||
| 74 | + put(2, "审核中"); | ||
| 75 | + put(3, "审核拒绝"); | ||
| 76 | + }}; | ||
| 77 | + | ||
| 78 | + /** | ||
| 79 | + * 工单状态字典 | ||
| 80 | + */ | ||
| 81 | + public static final Map<Integer, String> WORKORDER_STATUS = new HashMap<Integer, String>() {{ | ||
| 82 | + put(0, "待处理"); | ||
| 83 | + put(1, "处理中"); | ||
| 84 | + put(2, "已完成"); | ||
| 85 | + put(3, "已关闭"); | ||
| 86 | + }}; | ||
| 87 | + | ||
| 88 | + /** | ||
| 89 | + * 严重程度字典 | ||
| 90 | + */ | ||
| 91 | + public static final Map<Integer, String> SEVERITY_LEVEL = new HashMap<Integer, String>() {{ | ||
| 92 | + put(1, "低"); | ||
| 93 | + put(2, "中"); | ||
| 94 | + put(3, "高"); | ||
| 95 | + put(4, "紧急"); | ||
| 96 | + }}; | ||
| 97 | +} |
| 1 | +package com.apple.erp.config; | ||
| 2 | + | ||
| 3 | +import java.util.HashMap; | ||
| 4 | +import java.util.Map; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 导出配置类 | ||
| 8 | + * 定义各模块的导出字段映射配置 | ||
| 9 | + * | ||
| 10 | + * @author Apple ERP System | ||
| 11 | + * @since 2025-01-01 | ||
| 12 | + */ | ||
| 13 | +public class ExportConfig { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 订单导出配置 | ||
| 17 | + */ | ||
| 18 | + public static final Map<String, String[]> ORDER_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 19 | + put("headers", new String[]{ | ||
| 20 | + "订单ID", "订单编号", "经销商编码", "经销商名称", "订单日期", | ||
| 21 | + "订单金额", "返利金额", "出库状态", "开票状态", "返利计算状态", | ||
| 22 | + "数据来源", "审核状态", "上传时间", "创建时间" | ||
| 23 | + }); | ||
| 24 | + put("fields", new String[]{ | ||
| 25 | + "orderId", "orderNo", "dealerCode", "dealerName", "orderDate", | ||
| 26 | + "totalAmount", "rebateAmount", "deliveryStatus", "invoiceStatus", "rebateCalcFlag", | ||
| 27 | + "dataSource", "verifyStatus", "uploadTime", "createTime" | ||
| 28 | + }); | ||
| 29 | + }}; | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 出库导出配置 | ||
| 33 | + */ | ||
| 34 | + public static final Map<String, String[]> DELIVERY_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 35 | + put("headers", new String[]{ | ||
| 36 | + "出库单ID", "出库单编号", "经销商编码", "经销商名称", "出库日期", | ||
| 37 | + "关联订单编号", "出库状态", "仓库编码", "数据来源", "创建时间" | ||
| 38 | + }); | ||
| 39 | + put("fields", new String[]{ | ||
| 40 | + "deliveryId", "deliveryNo", "dealerCode", "dealerName", "deliveryDate", | ||
| 41 | + "orderNo", "deliveryStatus", "warehouseCode", "dataSource", "createTime" | ||
| 42 | + }); | ||
| 43 | + }}; | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * 发票导出配置 | ||
| 47 | + */ | ||
| 48 | + public static final Map<String, String[]> INVOICE_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 49 | + put("headers", new String[]{ | ||
| 50 | + "发票ID", "发票编号", "订单编号", "出库单编号", "经销商编码", | ||
| 51 | + "经销商名称", "发票金额", "发票日期", "开票状态", "税率", | ||
| 52 | + "数据来源", "创建时间" | ||
| 53 | + }); | ||
| 54 | + put("fields", new String[]{ | ||
| 55 | + "invoiceId", "invoiceNo", "orderNo", "deliveryNo", "dealerCode", | ||
| 56 | + "dealerName", "totalAmount", "invoiceDate", "invoiceStatus", "taxRate", | ||
| 57 | + "dataSource", "createTime" | ||
| 58 | + }); | ||
| 59 | + }}; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * 返利导出配置 | ||
| 63 | + */ | ||
| 64 | + public static final Map<String, String[]> REBATE_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 65 | + put("headers", new String[]{ | ||
| 66 | + "返利ID", "返利编号", "订单编号", "经销商编码", "经销商名称", | ||
| 67 | + "返利金额", "返利类型", "计算状态", "审核状态", "数据来源", | ||
| 68 | + "创建时间", "更新时间" | ||
| 69 | + }); | ||
| 70 | + put("fields", new String[]{ | ||
| 71 | + "rebateId", "rebateNo", "orderNo", "dealerCode", "dealerName", | ||
| 72 | + "rebateAmount", "operateType", "calcFlag", "auditStatus", "dataSource", | ||
| 73 | + "createTime", "updateTime" | ||
| 74 | + }); | ||
| 75 | + }}; | ||
| 76 | + | ||
| 77 | + /** | ||
| 78 | + * 异常工单导出配置 | ||
| 79 | + */ | ||
| 80 | + public static final Map<String, String[]> EXCEPTION_WORKORDER_EXPORT_CONFIG = new HashMap<String, String[]>() {{ | ||
| 81 | + put("headers", new String[]{ | ||
| 82 | + "工单ID", "工单编号", "工单类型", "严重程度", "工单状态", | ||
| 83 | + "问题描述", "处理人", "创建人", "创建时间", "更新时间" | ||
| 84 | + }); | ||
| 85 | + put("fields", new String[]{ | ||
| 86 | + "workorderId", "workorderNo", "workorderType", "severityLevel", "workorderStatus", | ||
| 87 | + "problemDescription", "assignee", "creator", "createTime", "updateTime" | ||
| 88 | + }); | ||
| 89 | + }}; | ||
| 90 | +} |
| ... | @@ -4,6 +4,7 @@ import com.apple.erp.dto.request.LoginReq; | ... | @@ -4,6 +4,7 @@ import com.apple.erp.dto.request.LoginReq; |
| 4 | import com.apple.erp.dto.request.RefreshTokenReq; | 4 | import com.apple.erp.dto.request.RefreshTokenReq; |
| 5 | import com.apple.erp.dto.response.ApiRes; | 5 | import com.apple.erp.dto.response.ApiRes; |
| 6 | import com.apple.erp.dto.response.LoginRes; | 6 | import com.apple.erp.dto.response.LoginRes; |
| 7 | +import com.apple.erp.dto.response.RoleRes; | ||
| 7 | import com.apple.erp.dto.response.UserInfoRes; | 8 | import com.apple.erp.dto.response.UserInfoRes; |
| 8 | import com.apple.erp.entity.SysUser; | 9 | import com.apple.erp.entity.SysUser; |
| 9 | import com.apple.erp.service.SysUserService; | 10 | import com.apple.erp.service.SysUserService; |
| ... | @@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.*; | ... | @@ -23,6 +24,7 @@ import org.springframework.web.bind.annotation.*; |
| 23 | 24 | ||
| 24 | import javax.servlet.http.HttpServletRequest; | 25 | import javax.servlet.http.HttpServletRequest; |
| 25 | import javax.validation.Valid; | 26 | import javax.validation.Valid; |
| 27 | +import java.util.List; | ||
| 26 | 28 | ||
| 27 | /** | 29 | /** |
| 28 | * 认证控制器 | 30 | * 认证控制器 |
| ... | @@ -214,9 +216,34 @@ public class AuthController { | ... | @@ -214,9 +216,34 @@ public class AuthController { |
| 214 | if (authentication != null && authentication.isAuthenticated()) { | 216 | if (authentication != null && authentication.isAuthenticated()) { |
| 215 | String username = authentication.getName(); | 217 | String username = authentication.getName(); |
| 216 | 218 | ||
| 217 | - UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities()); | 219 | + try { |
| 218 | - ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); | 220 | + // 获取用户详细信息 |
| 219 | - return ResponseEntity.ok(response); | 221 | + SysUser user = sysUserService.findByUsername(username); |
| 222 | + if (user != null) { | ||
| 223 | + // 获取用户角色信息 | ||
| 224 | + List<RoleRes> roles = sysUserService.getUserRoles(user.getUserId()); | ||
| 225 | + | ||
| 226 | + UserInfoRes userInfo = new UserInfoRes( | ||
| 227 | + user.getUsername(), | ||
| 228 | + user.getRealName(), | ||
| 229 | + roles, | ||
| 230 | + authentication.getAuthorities() | ||
| 231 | + ); | ||
| 232 | + ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); | ||
| 233 | + return ResponseEntity.ok(response); | ||
| 234 | + } else { | ||
| 235 | + // 如果找不到用户信息,返回基本信息 | ||
| 236 | + UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities()); | ||
| 237 | + ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); | ||
| 238 | + return ResponseEntity.ok(response); | ||
| 239 | + } | ||
| 240 | + } catch (Exception e) { | ||
| 241 | + log.error("获取用户详细信息失败: " + e.getMessage(), e); | ||
| 242 | + // 如果获取详细信息失败,返回基本信息 | ||
| 243 | + UserInfoRes userInfo = new UserInfoRes(username, authentication.getAuthorities()); | ||
| 244 | + ApiRes<UserInfoRes> response = ApiRes.success("获取用户信息成功", userInfo); | ||
| 245 | + return ResponseEntity.ok(response); | ||
| 246 | + } | ||
| 220 | } else { | 247 | } else { |
| 221 | ApiRes<UserInfoRes> response = ApiRes.error("未认证"); | 248 | ApiRes<UserInfoRes> response = ApiRes.error("未认证"); |
| 222 | return ResponseEntity.status(401).body(response); | 249 | return ResponseEntity.status(401).body(response); | ... | ... |
| ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.DeliveryRes; | ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.DeliveryRes; |
| 6 | import com.apple.erp.dto.DeliveryUpdateReq; | 6 | import com.apple.erp.dto.DeliveryUpdateReq; |
| 7 | import com.apple.erp.service.DeliveryMainService; | 7 | import com.apple.erp.service.DeliveryMainService; |
| 8 | import com.apple.erp.dto.response.ApiRes; | 8 | import com.apple.erp.dto.response.ApiRes; |
| 9 | +import com.apple.erp.service.ExcelExportService; | ||
| 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 10 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 10 | import io.swagger.v3.oas.annotations.Operation; | 11 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | import io.swagger.v3.oas.annotations.Parameter; | 12 | import io.swagger.v3.oas.annotations.Parameter; |
| 12 | import io.swagger.v3.oas.annotations.tags.Tag; | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 13 | import org.springframework.beans.factory.annotation.Autowired; | 14 | import org.springframework.beans.factory.annotation.Autowired; |
| 15 | +import org.springframework.http.ResponseEntity; | ||
| 14 | import org.springframework.security.access.prepost.PreAuthorize; | 16 | import org.springframework.security.access.prepost.PreAuthorize; |
| 15 | import org.springframework.validation.annotation.Validated; | 17 | import org.springframework.validation.annotation.Validated; |
| 16 | import org.springframework.web.bind.annotation.*; | 18 | import org.springframework.web.bind.annotation.*; |
| 17 | 19 | ||
| 18 | import javax.validation.Valid; | 20 | import javax.validation.Valid; |
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.format.DateTimeFormatter; | ||
| 19 | import java.util.List; | 23 | import java.util.List; |
| 20 | 24 | ||
| 21 | /** | 25 | /** |
| ... | @@ -32,6 +36,9 @@ public class DeliveryMainController { | ... | @@ -32,6 +36,9 @@ public class DeliveryMainController { |
| 32 | 36 | ||
| 33 | @Autowired | 37 | @Autowired |
| 34 | private DeliveryMainService deliveryMainService; | 38 | private DeliveryMainService deliveryMainService; |
| 39 | + | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 35 | 42 | ||
| 36 | @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表") | 43 | @Operation(summary = "分页查询出库列表", description = "根据查询条件分页获取出库列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| ... | @@ -139,6 +146,40 @@ public class DeliveryMainController { | ... | @@ -139,6 +146,40 @@ public class DeliveryMainController { |
| 139 | return ApiRes.error("修改出库状态失败: " + e.getMessage()); | 146 | return ApiRes.error("修改出库状态失败: " + e.getMessage()); |
| 140 | } | 147 | } |
| 141 | } | 148 | } |
| 149 | + | ||
| 150 | + @Operation(summary = "导出出库数据", description = "根据查询条件导出出库数据到Excel") | ||
| 151 | + @PostMapping("/export") | ||
| 152 | + @PreAuthorize("hasAuthority('delivery:export')") | ||
| 153 | + public ResponseEntity<byte[]> exportDeliveries(@Valid @RequestBody DeliveryQueryReq queryReq) { | ||
| 154 | + try { | ||
| 155 | + // 获取所有符合条件的数据(不分页) | ||
| 156 | + DeliveryQueryReq exportQuery = new DeliveryQueryReq(); | ||
| 157 | + exportQuery.setDeliveryNo(queryReq.getDeliveryNo()); | ||
| 158 | + exportQuery.setDealerCode(queryReq.getDealerCode()); | ||
| 159 | + exportQuery.setDealerName(queryReq.getDealerName()); | ||
| 160 | + exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus()); | ||
| 161 | + exportQuery.setWarehouseCode(queryReq.getWarehouseCode()); | ||
| 162 | + exportQuery.setDataSource(queryReq.getDataSource()); | ||
| 163 | + exportQuery.setDeliveryStartDate(queryReq.getDeliveryStartDate()); | ||
| 164 | + exportQuery.setDeliveryEndDate(queryReq.getDeliveryEndDate()); | ||
| 165 | + // 设置大分页获取所有数据 | ||
| 166 | + exportQuery.setPageNum(1); | ||
| 167 | + exportQuery.setPageSize(10000); | ||
| 168 | + | ||
| 169 | + Page<DeliveryRes> result = deliveryMainService.getDeliveryList(exportQuery); | ||
| 170 | + List<DeliveryRes> deliveries = result.getRecords(); | ||
| 171 | + | ||
| 172 | + // 生成文件名 | ||
| 173 | + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); | ||
| 174 | + String fileName = "出库数据_" + timestamp; | ||
| 175 | + | ||
| 176 | + // 导出到Excel | ||
| 177 | + return excelExportService.exportDeliveries(deliveries); | ||
| 178 | + | ||
| 179 | + } catch (Exception e) { | ||
| 180 | + throw new RuntimeException("导出出库数据失败: " + e.getMessage(), e); | ||
| 181 | + } | ||
| 182 | + } | ||
| 142 | } | 183 | } |
| 143 | 184 | ||
| 144 | 185 | ... | ... |
| ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.InvoiceRes; | ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.InvoiceRes; |
| 6 | import com.apple.erp.dto.InvoiceUpdateReq; | 6 | import com.apple.erp.dto.InvoiceUpdateReq; |
| 7 | import com.apple.erp.service.InvoiceMainService; | 7 | import com.apple.erp.service.InvoiceMainService; |
| 8 | import com.apple.erp.dto.response.ApiRes; | 8 | import com.apple.erp.dto.response.ApiRes; |
| 9 | +import com.apple.erp.service.ExcelExportService; | ||
| 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 10 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 10 | import io.swagger.v3.oas.annotations.Operation; | 11 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | import io.swagger.v3.oas.annotations.Parameter; | 12 | import io.swagger.v3.oas.annotations.Parameter; |
| 12 | import io.swagger.v3.oas.annotations.tags.Tag; | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 13 | import org.springframework.beans.factory.annotation.Autowired; | 14 | import org.springframework.beans.factory.annotation.Autowired; |
| 15 | +import org.springframework.http.ResponseEntity; | ||
| 14 | import org.springframework.security.access.prepost.PreAuthorize; | 16 | import org.springframework.security.access.prepost.PreAuthorize; |
| 15 | import org.springframework.validation.annotation.Validated; | 17 | import org.springframework.validation.annotation.Validated; |
| 16 | import org.springframework.web.bind.annotation.*; | 18 | import org.springframework.web.bind.annotation.*; |
| 17 | 19 | ||
| 18 | import javax.validation.Valid; | 20 | import javax.validation.Valid; |
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.format.DateTimeFormatter; | ||
| 19 | import java.util.List; | 23 | import java.util.List; |
| 20 | 24 | ||
| 21 | /** | 25 | /** |
| ... | @@ -32,6 +36,9 @@ public class InvoiceMainController { | ... | @@ -32,6 +36,9 @@ public class InvoiceMainController { |
| 32 | 36 | ||
| 33 | @Autowired | 37 | @Autowired |
| 34 | private InvoiceMainService invoiceMainService; | 38 | private InvoiceMainService invoiceMainService; |
| 39 | + | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 35 | 42 | ||
| 36 | @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表") | 43 | @Operation(summary = "分页查询发票列表", description = "根据查询条件分页获取发票列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| ... | @@ -139,6 +146,43 @@ public class InvoiceMainController { | ... | @@ -139,6 +146,43 @@ public class InvoiceMainController { |
| 139 | return ApiRes.error("修改发票状态失败: " + e.getMessage()); | 146 | return ApiRes.error("修改发票状态失败: " + e.getMessage()); |
| 140 | } | 147 | } |
| 141 | } | 148 | } |
| 149 | + | ||
| 150 | + @Operation(summary = "导出发票数据", description = "根据查询条件导出发票数据到Excel") | ||
| 151 | + @PostMapping("/export") | ||
| 152 | + @PreAuthorize("hasAuthority('invoice:export')") | ||
| 153 | + public ResponseEntity<byte[]> exportInvoices(@Valid @RequestBody InvoiceQueryReq queryReq) { | ||
| 154 | + try { | ||
| 155 | + // 获取所有符合条件的数据(不分页) | ||
| 156 | + InvoiceQueryReq exportQuery = new InvoiceQueryReq(); | ||
| 157 | + exportQuery.setInvoiceNo(queryReq.getInvoiceNo()); | ||
| 158 | + exportQuery.setOrderNo(queryReq.getOrderNo()); | ||
| 159 | + exportQuery.setDeliveryNo(queryReq.getDeliveryNo()); | ||
| 160 | + exportQuery.setDealerCode(queryReq.getDealerCode()); | ||
| 161 | + exportQuery.setDealerName(queryReq.getDealerName()); | ||
| 162 | + exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus()); | ||
| 163 | + exportQuery.setDataSource(queryReq.getDataSource()); | ||
| 164 | + exportQuery.setInvoiceStartDate(queryReq.getInvoiceStartDate()); | ||
| 165 | + exportQuery.setInvoiceEndDate(queryReq.getInvoiceEndDate()); | ||
| 166 | + exportQuery.setMinAmount(queryReq.getMinAmount()); | ||
| 167 | + exportQuery.setMaxAmount(queryReq.getMaxAmount()); | ||
| 168 | + // 设置大分页获取所有数据 | ||
| 169 | + exportQuery.setPageNum(1); | ||
| 170 | + exportQuery.setPageSize(10000); | ||
| 171 | + | ||
| 172 | + Page<InvoiceRes> result = invoiceMainService.getInvoiceList(exportQuery); | ||
| 173 | + List<InvoiceRes> invoices = result.getRecords(); | ||
| 174 | + | ||
| 175 | + // 生成文件名 | ||
| 176 | + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); | ||
| 177 | + String fileName = "发票数据_" + timestamp; | ||
| 178 | + | ||
| 179 | + // 导出到Excel | ||
| 180 | + return excelExportService.exportInvoices(invoices); | ||
| 181 | + | ||
| 182 | + } catch (Exception e) { | ||
| 183 | + throw new RuntimeException("导出发票数据失败: " + e.getMessage(), e); | ||
| 184 | + } | ||
| 185 | + } | ||
| 142 | } | 186 | } |
| 143 | 187 | ||
| 144 | 188 | ... | ... |
| ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes; | ... | @@ -6,16 +6,20 @@ import com.apple.erp.dto.OrderRes; |
| 6 | import com.apple.erp.dto.OrderUpdateReq; | 6 | import com.apple.erp.dto.OrderUpdateReq; |
| 7 | import com.apple.erp.service.OrderMainService; | 7 | import com.apple.erp.service.OrderMainService; |
| 8 | import com.apple.erp.dto.response.ApiRes; | 8 | import com.apple.erp.dto.response.ApiRes; |
| 9 | +import com.apple.erp.service.ExcelExportService; | ||
| 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 10 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 10 | import io.swagger.v3.oas.annotations.Operation; | 11 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | import io.swagger.v3.oas.annotations.Parameter; | 12 | import io.swagger.v3.oas.annotations.Parameter; |
| 12 | import io.swagger.v3.oas.annotations.tags.Tag; | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 13 | import org.springframework.beans.factory.annotation.Autowired; | 14 | import org.springframework.beans.factory.annotation.Autowired; |
| 15 | +import org.springframework.http.ResponseEntity; | ||
| 14 | import org.springframework.security.access.prepost.PreAuthorize; | 16 | import org.springframework.security.access.prepost.PreAuthorize; |
| 15 | import org.springframework.validation.annotation.Validated; | 17 | import org.springframework.validation.annotation.Validated; |
| 16 | import org.springframework.web.bind.annotation.*; | 18 | import org.springframework.web.bind.annotation.*; |
| 17 | 19 | ||
| 18 | import javax.validation.Valid; | 20 | import javax.validation.Valid; |
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.format.DateTimeFormatter; | ||
| 19 | import java.util.List; | 23 | import java.util.List; |
| 20 | 24 | ||
| 21 | /** | 25 | /** |
| ... | @@ -32,6 +36,9 @@ public class OrderMainController { | ... | @@ -32,6 +36,9 @@ public class OrderMainController { |
| 32 | 36 | ||
| 33 | @Autowired | 37 | @Autowired |
| 34 | private OrderMainService orderMainService; | 38 | private OrderMainService orderMainService; |
| 39 | + | ||
| 40 | + @Autowired | ||
| 41 | + private ExcelExportService excelExportService; | ||
| 35 | 42 | ||
| 36 | @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表") | 43 | @Operation(summary = "分页查询订单列表", description = "根据条件分页查询订单列表") |
| 37 | @GetMapping("/list") | 44 | @GetMapping("/list") |
| ... | @@ -182,4 +189,42 @@ public class OrderMainController { | ... | @@ -182,4 +189,42 @@ public class OrderMainController { |
| 182 | return ApiRes.error("修改返利计算状态失败: " + e.getMessage()); | 189 | return ApiRes.error("修改返利计算状态失败: " + e.getMessage()); |
| 183 | } | 190 | } |
| 184 | } | 191 | } |
| 192 | + | ||
| 193 | + @Operation(summary = "导出订单数据", description = "根据查询条件导出订单数据到Excel") | ||
| 194 | + @PostMapping("/export") | ||
| 195 | + @PreAuthorize("hasAuthority('order:export')") | ||
| 196 | + public ResponseEntity<byte[]> exportOrders(@Valid @RequestBody OrderQueryReq queryReq) { | ||
| 197 | + try { | ||
| 198 | + // 获取所有符合条件的数据(不分页) | ||
| 199 | + OrderQueryReq exportQuery = new OrderQueryReq(); | ||
| 200 | + exportQuery.setOrderNo(queryReq.getOrderNo()); | ||
| 201 | + exportQuery.setDealerCode(queryReq.getDealerCode()); | ||
| 202 | + exportQuery.setDealerName(queryReq.getDealerName()); | ||
| 203 | + exportQuery.setDeliveryStatus(queryReq.getDeliveryStatus()); | ||
| 204 | + exportQuery.setInvoiceStatus(queryReq.getInvoiceStatus()); | ||
| 205 | + exportQuery.setRebateCalcFlag(queryReq.getRebateCalcFlag()); | ||
| 206 | + exportQuery.setDataSource(queryReq.getDataSource()); | ||
| 207 | + exportQuery.setVerifyStatus(queryReq.getVerifyStatus()); | ||
| 208 | + exportQuery.setOrderStartDate(queryReq.getOrderStartDate()); | ||
| 209 | + exportQuery.setOrderEndDate(queryReq.getOrderEndDate()); | ||
| 210 | + exportQuery.setMinAmount(queryReq.getMinAmount()); | ||
| 211 | + exportQuery.setMaxAmount(queryReq.getMaxAmount()); | ||
| 212 | + // 设置大分页获取所有数据 | ||
| 213 | + exportQuery.setPageNum(1); | ||
| 214 | + exportQuery.setPageSize(10000); | ||
| 215 | + | ||
| 216 | + Page<OrderRes> result = orderMainService.getOrderList(exportQuery); | ||
| 217 | + List<OrderRes> orders = result.getRecords(); | ||
| 218 | + | ||
| 219 | + // 生成文件名 | ||
| 220 | + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); | ||
| 221 | + String fileName = "订单数据_" + timestamp; | ||
| 222 | + | ||
| 223 | + // 导出到Excel | ||
| 224 | + return excelExportService.exportOrders(orders); | ||
| 225 | + | ||
| 226 | + } catch (Exception e) { | ||
| 227 | + throw new RuntimeException("导出订单数据失败: " + e.getMessage(), e); | ||
| 228 | + } | ||
| 229 | + } | ||
| 185 | } | 230 | } | ... | ... |
| ... | @@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes; | ... | @@ -7,6 +7,7 @@ import com.apple.erp.dto.response.ApiRes; |
| 7 | import com.apple.erp.dto.response.RebateRes; | 7 | import com.apple.erp.dto.response.RebateRes; |
| 8 | import com.apple.erp.entity.Rebate; | 8 | import com.apple.erp.entity.Rebate; |
| 9 | import com.apple.erp.service.RebateService; | 9 | import com.apple.erp.service.RebateService; |
| 10 | +import com.apple.erp.service.ExcelExportService; | ||
| 10 | import com.baomidou.mybatisplus.core.metadata.IPage; | 11 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 11 | import io.swagger.v3.oas.annotations.Operation; | 12 | import io.swagger.v3.oas.annotations.Operation; |
| 12 | import io.swagger.v3.oas.annotations.tags.Tag; | 13 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | @@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*; | ... | @@ -17,8 +18,12 @@ import org.springframework.web.bind.annotation.*; |
| 17 | 18 | ||
| 18 | import javax.validation.Valid; | 19 | import javax.validation.Valid; |
| 19 | import java.math.BigDecimal; | 20 | import java.math.BigDecimal; |
| 21 | +import java.time.LocalDateTime; | ||
| 22 | +import java.time.format.DateTimeFormatter; | ||
| 20 | import java.util.List; | 23 | import java.util.List; |
| 21 | import java.util.Map; | 24 | import java.util.Map; |
| 25 | +import org.springframework.http.ResponseEntity; | ||
| 26 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
| 22 | 27 | ||
| 23 | /** | 28 | /** |
| 24 | * 返利台账明细管理控制器 | 29 | * 返利台账明细管理控制器 |
| ... | @@ -36,6 +41,9 @@ public class RebateController { | ... | @@ -36,6 +41,9 @@ public class RebateController { |
| 36 | 41 | ||
| 37 | @Autowired | 42 | @Autowired |
| 38 | private RebateService rebateService; | 43 | private RebateService rebateService; |
| 44 | + | ||
| 45 | + @Autowired | ||
| 46 | + private ExcelExportService excelExportService; | ||
| 39 | 47 | ||
| 40 | @Operation(summary = "分页查询返利明细列表") | 48 | @Operation(summary = "分页查询返利明细列表") |
| 41 | @PostMapping("/page") | 49 | @PostMapping("/page") |
| ... | @@ -277,4 +285,40 @@ public class RebateController { | ... | @@ -277,4 +285,40 @@ public class RebateController { |
| 277 | return ApiRes.error(e.getMessage()); | 285 | return ApiRes.error(e.getMessage()); |
| 278 | } | 286 | } |
| 279 | } | 287 | } |
| 288 | + | ||
| 289 | + @Operation(summary = "导出返利数据", description = "根据查询条件导出返利数据到Excel") | ||
| 290 | + @PostMapping("/export") | ||
| 291 | + @PreAuthorize("hasAuthority('rebate:export')") | ||
| 292 | + public ResponseEntity<byte[]> exportRebates(@Valid @RequestBody RebateQueryReq queryReq) { | ||
| 293 | + log.info("导出返利数据,参数:{}", queryReq); | ||
| 294 | + try { | ||
| 295 | + // 获取所有符合条件的数据(不分页) | ||
| 296 | + RebateQueryReq exportQuery = new RebateQueryReq(); | ||
| 297 | + exportQuery.setRebateNo(queryReq.getRebateNo()); | ||
| 298 | + exportQuery.setOrderNo(queryReq.getOrderNo()); | ||
| 299 | + exportQuery.setDealerCode(queryReq.getDealerCode()); | ||
| 300 | + exportQuery.setDealerName(queryReq.getDealerName()); | ||
| 301 | + exportQuery.setOperateType(queryReq.getOperateType()); | ||
| 302 | + exportQuery.setCalcFlag(queryReq.getCalcFlag()); | ||
| 303 | + exportQuery.setAuditStatus(queryReq.getAuditStatus()); | ||
| 304 | + exportQuery.setDataSource(queryReq.getDataSource()); | ||
| 305 | + exportQuery.setStartDate(queryReq.getStartDate()); | ||
| 306 | + exportQuery.setEndDate(queryReq.getEndDate()); | ||
| 307 | + exportQuery.setMinAmount(queryReq.getMinAmount()); | ||
| 308 | + exportQuery.setMaxAmount(queryReq.getMaxAmount()); | ||
| 309 | + // 设置大分页获取所有数据 | ||
| 310 | + exportQuery.setPageNum(1); | ||
| 311 | + exportQuery.setPageSize(10000); | ||
| 312 | + | ||
| 313 | + IPage<RebateRes> result = rebateService.getRebatePage(exportQuery); | ||
| 314 | + List<RebateRes> rebates = result.getRecords(); | ||
| 315 | + | ||
| 316 | + // 导出到Excel | ||
| 317 | + return excelExportService.exportRebates(rebates); | ||
| 318 | + | ||
| 319 | + } catch (Exception e) { | ||
| 320 | + log.error("导出返利数据失败", e); | ||
| 321 | + throw new RuntimeException("导出返利数据失败: " + e.getMessage(), e); | ||
| 322 | + } | ||
| 323 | + } | ||
| 280 | } | 324 | } | ... | ... |
| ... | @@ -193,6 +193,25 @@ public class SysDictItemController { | ... | @@ -193,6 +193,25 @@ public class SysDictItemController { |
| 193 | } | 193 | } |
| 194 | 194 | ||
| 195 | /** | 195 | /** |
| 196 | + * 刷新字典缓存 | ||
| 197 | + * 当字典项发生变化时,刷新Redis缓存以保持数据一致性 | ||
| 198 | + * | ||
| 199 | + * @return 操作结果 | ||
| 200 | + */ | ||
| 201 | + @Operation(summary = "刷新字典缓存", description = "刷新Redis字典缓存以保持数据一致性") | ||
| 202 | + @PostMapping("/refreshCache") | ||
| 203 | + @PreAuthorize("hasAuthority('sys:dict:edit')") | ||
| 204 | + public ApiRes<Void> refreshCache() { | ||
| 205 | + try { | ||
| 206 | + // 调用字典值转换器的刷新方法 | ||
| 207 | + com.apple.erp.util.DictValueConverter.refreshCache(); | ||
| 208 | + return ApiRes.success("字典缓存刷新成功", null); | ||
| 209 | + } catch (Exception e) { | ||
| 210 | + return ApiRes.error("刷新字典缓存失败: " + e.getMessage()); | ||
| 211 | + } | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + /** | ||
| 196 | * 转换SysDictItem为DictItemRes | 215 | * 转换SysDictItem为DictItemRes |
| 197 | * | 216 | * |
| 198 | * @param dictItem 字典项实体 | 217 | * @param dictItem 字典项实体 | ... | ... |
| ... | @@ -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.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 | +} |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
| 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 | +} |
| ... | @@ -26,6 +26,7 @@ declare module 'vue' { | ... | @@ -26,6 +26,7 @@ declare module 'vue' { |
| 26 | ElTable: typeof import('element-plus/es')['ElTable'] | 26 | ElTable: typeof import('element-plus/es')['ElTable'] |
| 27 | ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] | 27 | ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] |
| 28 | ElTag: typeof import('element-plus/es')['ElTag'] | 28 | ElTag: typeof import('element-plus/es')['ElTag'] |
| 29 | + ElText: typeof import('element-plus/es')['ElText'] | ||
| 29 | Header: typeof import('./src/components/layout/Header.vue')['default'] | 30 | Header: typeof import('./src/components/layout/Header.vue')['default'] |
| 30 | RouterLink: typeof import('vue-router')['RouterLink'] | 31 | RouterLink: typeof import('vue-router')['RouterLink'] |
| 31 | RouterView: typeof import('vue-router')['RouterView'] | 32 | 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 | ... | ... |
| ... | @@ -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>() |
| ... | @@ -371,9 +393,17 @@ watch(() => route.path, (newPath) => { | ... | @@ -371,9 +393,17 @@ watch(() => route.path, (newPath) => { |
| 371 | 393 | ||
| 372 | // 组件挂载时获取用户信息 | 394 | // 组件挂载时获取用户信息 |
| 373 | onMounted(() => { | 395 | onMounted(() => { |
| 374 | - const storedUserInfo = localStorage.getItem('userInfo') | 396 | + // 检查是否已登录 |
| 375 | - if (storedUserInfo) { | 397 | + const token = localStorage.getItem('token') |
| 376 | - userInfo.value = JSON.parse(storedUserInfo) | 398 | + if (token) { |
| 399 | + // 调用API获取用户信息 | ||
| 400 | + fetchUserInfo() | ||
| 401 | + } else { | ||
| 402 | + // 如果没有token,尝试从本地存储获取 | ||
| 403 | + const storedUserInfo = localStorage.getItem('userInfo') | ||
| 404 | + if (storedUserInfo) { | ||
| 405 | + userInfo.value = JSON.parse(storedUserInfo) | ||
| 406 | + } | ||
| 377 | } | 407 | } |
| 378 | }) | 408 | }) |
| 379 | </script> | 409 | </script> | ... | ... |
| 1 | import { createApp } from 'vue' | 1 | import { createApp } from 'vue' |
| 2 | import { createPinia } from 'pinia' | 2 | import { createPinia } from 'pinia' |
| 3 | import App from './App.vue' | 3 | import App from './App.vue' |
| 4 | +import ElementPlus from 'element-plus' | ||
| 5 | +import 'element-plus/dist/index.css' | ||
| 4 | 6 | ||
| 5 | import router from './router' | 7 | import router from './router' |
| 6 | 8 | ||
| ... | @@ -10,5 +12,6 @@ const pinia = createPinia() | ... | @@ -10,5 +12,6 @@ const pinia = createPinia() |
| 10 | 12 | ||
| 11 | app.use(pinia) | 13 | app.use(pinia) |
| 12 | app.use(router) | 14 | app.use(router) |
| 15 | +app.use(ElementPlus) | ||
| 13 | // 挂载应用 | 16 | // 挂载应用 |
| 14 | app.mount('#app') | 17 | app.mount('#app') | ... | ... |
| ... | @@ -46,6 +46,11 @@ service.interceptors.request.use( | ... | @@ -46,6 +46,11 @@ service.interceptors.request.use( |
| 46 | // 响应拦截器 | 46 | // 响应拦截器 |
| 47 | service.interceptors.response.use( | 47 | service.interceptors.response.use( |
| 48 | (response: AxiosResponse) => { | 48 | (response: AxiosResponse) => { |
| 49 | + // 如果是blob响应(文件下载),直接返回 | ||
| 50 | + if (response.config.responseType === 'blob') { | ||
| 51 | + return response.data | ||
| 52 | + } | ||
| 53 | + | ||
| 49 | const { code, message, data } = response.data | 54 | const { code, message, data } = response.data |
| 50 | 55 | ||
| 51 | // 请求成功 | 56 | // 请求成功 |
| ... | @@ -121,8 +126,8 @@ export const request = { | ... | @@ -121,8 +126,8 @@ export const request = { |
| 121 | return service.get(url, { params }) | 126 | return service.get(url, { params }) |
| 122 | }, | 127 | }, |
| 123 | 128 | ||
| 124 | - post<T = any>(url: string, data?: any): Promise<T> { | 129 | + post<T = any>(url: string, data?: any, config?: any): Promise<T> { |
| 125 | - return service.post(url, data) | 130 | + return service.post(url, data, config) |
| 126 | }, | 131 | }, |
| 127 | 132 | ||
| 128 | put<T = any>(url: string, data?: any): Promise<T> { | 133 | put<T = any>(url: string, data?: any): Promise<T> { | ... | ... |
| ... | @@ -2,7 +2,8 @@ | ... | @@ -2,7 +2,8 @@ |
| 2 | <div class="dashboard-container"> | 2 | <div class="dashboard-container"> |
| 3 | <div class="dashboard-header"> | 3 | <div class="dashboard-header"> |
| 4 | <h2>系统概览</h2> | 4 | <h2>系统概览</h2> |
| 5 | - <p>欢迎回来,{{ userInfo?.username || '用户' }}!</p> | 5 | + <p v-if="loading">正在加载用户信息...</p> |
| 6 | + <p v-else>欢迎回来,{{ userInfo?.username || '未知' }}!</p> | ||
| 6 | </div> | 7 | </div> |
| 7 | 8 | ||
| 8 | <div class="dashboard-stats"> | 9 | <div class="dashboard-stats"> |
| ... | @@ -43,32 +44,34 @@ | ... | @@ -43,32 +44,34 @@ |
| 43 | <div class="dashboard-card"> | 44 | <div class="dashboard-card"> |
| 44 | <h3>系统信息</h3> | 45 | <h3>系统信息</h3> |
| 45 | <div class="info-grid"> | 46 | <div class="info-grid"> |
| 46 | - <div class="info-item"> | 47 | + <div class="info-item"> |
| 47 | <label>用户名:</label> | 48 | <label>用户名:</label> |
| 48 | - <span>{{ userInfo?.username || '未知' }}</span> | 49 | + <span v-if="loading">加载中...</span> |
| 49 | - </div> | 50 | + <span v-else>{{ userInfo?.username || '未知' }}</span> |
| 50 | - <div class="info-item"> | 51 | + </div> |
| 52 | + <div class="info-item"> | ||
| 51 | <label>角色:</label> | 53 | <label>角色:</label> |
| 52 | - <span>{{ userInfo?.roles?.[0]?.roleName || '普通用户' }}</span> | 54 | + <span v-if="loading">加载中...</span> |
| 53 | - </div> | 55 | + <span v-else>{{ getRoleName(userInfo) || '普通用户' }}</span> |
| 54 | - <div class="info-item"> | 56 | + </div> |
| 57 | + <div class="info-item"> | ||
| 55 | <label>登录时间:</label> | 58 | <label>登录时间:</label> |
| 56 | <span>{{ currentTime }}</span> | 59 | <span>{{ currentTime }}</span> |
| 57 | - </div> | 60 | + </div> |
| 58 | - <div class="info-item"> | 61 | + <div class="info-item"> |
| 59 | <label>系统版本:</label> | 62 | <label>系统版本:</label> |
| 60 | <span>v1.0.0</span> | 63 | <span>v1.0.0</span> |
| 61 | </div> | 64 | </div> |
| 62 | - </div> | 65 | + </div> |
| 63 | </div> | 66 | </div> |
| 64 | 67 | ||
| 65 | <div class="dashboard-card"> | 68 | <div class="dashboard-card"> |
| 66 | <h3>快速操作</h3> | 69 | <h3>快速操作</h3> |
| 67 | <div class="quick-actions"> | 70 | <div class="quick-actions"> |
| 68 | - <button class="action-btn">👤 用户管理</button> | 71 | + <button class="action-btn" @click="navigateTo('/main/users')">👤 用户管理</button> |
| 69 | - <button class="action-btn">🛡️ 角色管理</button> | 72 | + <button class="action-btn" @click="navigateTo('/main/sys/role')">🛡️ 角色管理</button> |
| 70 | - <button class="action-btn">⚙️ 系统设置</button> | 73 | + <button class="action-btn" @click="navigateTo('/main/settings')">⚙️ 系统设置</button> |
| 71 | - <button class="action-btn">📊 查看日志</button> | 74 | + <button class="action-btn" @click="navigateTo('/main/sys/log')">📊 查看日志</button> |
| 72 | </div> | 75 | </div> |
| 73 | </div> | 76 | </div> |
| 74 | </div> | 77 | </div> |
| ... | @@ -78,28 +81,85 @@ | ... | @@ -78,28 +81,85 @@ |
| 78 | <script setup lang="ts"> | 81 | <script setup lang="ts"> |
| 79 | import { ref, onMounted } from 'vue' | 82 | import { ref, onMounted } from 'vue' |
| 80 | import { useRouter } from 'vue-router' | 83 | import { useRouter } from 'vue-router' |
| 84 | +import { request } from '@/utils/request' | ||
| 81 | 85 | ||
| 82 | const router = useRouter() | 86 | const router = useRouter() |
| 83 | const currentTime = ref('') | 87 | const currentTime = ref('') |
| 84 | const userInfo = ref<any>(null) | 88 | const userInfo = ref<any>(null) |
| 89 | +const loading = ref(false) | ||
| 90 | + | ||
| 91 | +// 获取角色名称 | ||
| 92 | +const getRoleName = (userInfo: any) => { | ||
| 93 | + // 优先使用roles字段中的角色信息 | ||
| 94 | + if (userInfo?.roles && Array.isArray(userInfo.roles) && userInfo.roles.length > 0) { | ||
| 95 | + return userInfo.roles[0].roleName || '普通用户' | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + // 如果没有roles字段,回退到authorities | ||
| 99 | + const authorities = userInfo?.authorities | ||
| 100 | + if (authorities && Array.isArray(authorities)) { | ||
| 101 | + // 查找ROLE_开头的权限 | ||
| 102 | + const roleAuthority = authorities.find(auth => | ||
| 103 | + auth.authority && auth.authority.startsWith('ROLE_') | ||
| 104 | + ) | ||
| 105 | + | ||
| 106 | + if (roleAuthority) { | ||
| 107 | + // 移除ROLE_前缀并转换为中文 | ||
| 108 | + const roleName = roleAuthority.authority.replace('ROLE_', '') | ||
| 109 | + const roleMap: { [key: string]: string } = { | ||
| 110 | + 'ADMIN': '管理员', | ||
| 111 | + 'USER': '普通用户', | ||
| 112 | + 'MANAGER': '经理', | ||
| 113 | + 'OPERATOR': '操作员' | ||
| 114 | + } | ||
| 115 | + return roleMap[roleName] || roleName | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + return '普通用户' | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +// 获取用户信息 | ||
| 123 | +const fetchUserInfo = async () => { | ||
| 124 | + try { | ||
| 125 | + loading.value = true | ||
| 126 | + const response = await request.get('/api/auth/userinfo') | ||
| 127 | + userInfo.value = response | ||
| 128 | + console.log('用户信息:', response) | ||
| 129 | + } catch (error) { | ||
| 130 | + console.error('获取用户信息失败:', error) | ||
| 131 | + // 如果获取失败,尝试从本地存储获取 | ||
| 132 | + const storedUserInfo = localStorage.getItem('userInfo') | ||
| 133 | + if (storedUserInfo) { | ||
| 134 | + userInfo.value = JSON.parse(storedUserInfo) | ||
| 135 | + } | ||
| 136 | + } finally { | ||
| 137 | + loading.value = false | ||
| 138 | + } | ||
| 139 | +} | ||
| 85 | 140 | ||
| 86 | onMounted(() => { | 141 | onMounted(() => { |
| 87 | // 获取当前时间 | 142 | // 获取当前时间 |
| 88 | currentTime.value = new Date().toLocaleString() | 143 | currentTime.value = new Date().toLocaleString() |
| 89 | 144 | ||
| 90 | - // 获取用户信息 | ||
| 91 | - const storedUserInfo = localStorage.getItem('userInfo') | ||
| 92 | - if (storedUserInfo) { | ||
| 93 | - userInfo.value = JSON.parse(storedUserInfo) | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | // 检查是否已登录 | 145 | // 检查是否已登录 |
| 97 | const token = localStorage.getItem('token') | 146 | const token = localStorage.getItem('token') |
| 98 | if (!token) { | 147 | if (!token) { |
| 99 | router.push('/') | 148 | router.push('/') |
| 149 | + return | ||
| 100 | } | 150 | } |
| 151 | + | ||
| 152 | + // 获取用户信息 | ||
| 153 | + fetchUserInfo() | ||
| 101 | }) | 154 | }) |
| 102 | 155 | ||
| 156 | +// 页面跳转函数 | ||
| 157 | +const navigateTo = (path: string) => { | ||
| 158 | + router.push(path).catch(err => { | ||
| 159 | + console.error('页面跳转失败:', err) | ||
| 160 | + }) | ||
| 161 | +} | ||
| 162 | + | ||
| 103 | const logout = () => { | 163 | const logout = () => { |
| 104 | // 清除本地存储 | 164 | // 清除本地存储 |
| 105 | localStorage.removeItem('token') | 165 | localStorage.removeItem('token') | ... | ... |
| ... | @@ -179,15 +179,11 @@ | ... | @@ -179,15 +179,11 @@ |
| 179 | <div class="detail-grid"> | 179 | <div class="detail-grid"> |
| 180 | <div class="detail-item"> | 180 | <div class="detail-item"> |
| 181 | <label>出库单ID:</label> | 181 | <label>出库单ID:</label> |
| 182 | - <span>{{ orderDetail.deliveryId }}</span> | 182 | + <span>{{ deliveryDetail.deliveryId }}</span> |
| 183 | </div> | 183 | </div> |
| 184 | <div class="detail-item"> | 184 | <div class="detail-item"> |
| 185 | <label>出库单编号:</label> | 185 | <label>出库单编号:</label> |
| 186 | - <span>{{ orderDetail.deliveryNo }}</span> | 186 | + <span>{{ deliveryDetail.deliveryNo }}</span> |
| 187 | - </div> | ||
| 188 | - <div class="detail-item"> | ||
| 189 | - <label>关联订单编号:</label> | ||
| 190 | - <span>{{ orderDetail.orderNo }}</span> | ||
| 191 | </div> | 187 | </div> |
| 192 | <div class="detail-item"> | 188 | <div class="detail-item"> |
| 193 | <label>关联订单编号:</label> | 189 | <label>关联订单编号:</label> |
| ... | @@ -203,7 +199,7 @@ | ... | @@ -203,7 +199,7 @@ |
| 203 | </div> | 199 | </div> |
| 204 | <div class="detail-item"> | 200 | <div class="detail-item"> |
| 205 | <label>出库日期:</label> | 201 | <label>出库日期:</label> |
| 206 | - <span>{{ formatDate(orderDetail.deliveryDate) }}</span> | 202 | + <span>{{ formatDate(deliveryDetail.deliveryDate) }}</span> |
| 207 | </div> | 203 | </div> |
| 208 | <div class="detail-item"> | 204 | <div class="detail-item"> |
| 209 | <label>出库状态:</label> | 205 | <label>出库状态:</label> |
| ... | @@ -213,28 +209,28 @@ | ... | @@ -213,28 +209,28 @@ |
| 213 | </div> | 209 | </div> |
| 214 | <div class="detail-item"> | 210 | <div class="detail-item"> |
| 215 | <label>仓库编码:</label> | 211 | <label>仓库编码:</label> |
| 216 | - <span>{{ orderDetail.warehouseCode }}</span> | 212 | + <span>{{ deliveryDetail.warehouseCode }}</span> |
| 217 | </div> | 213 | </div> |
| 218 | <div class="detail-item"> | 214 | <div class="detail-item"> |
| 219 | <label>数据来源:</label> | 215 | <label>数据来源:</label> |
| 220 | - <span>{{ orderDetail.dataSource || '-' }}</span> | 216 | + <span>{{ deliveryDetail.dataSource || '-' }}</span> |
| 221 | </div> | 217 | </div> |
| 222 | <div class="detail-item"> | 218 | <div class="detail-item"> |
| 223 | <label>上传时间:</label> | 219 | <label>上传时间:</label> |
| 224 | - <span>{{ formatTime(orderDetail.uploadTime || '') }}</span> | 220 | + <span>{{ formatTime(deliveryDetail.uploadTime || '') }}</span> |
| 225 | </div> | 221 | </div> |
| 226 | <div class="detail-item"> | 222 | <div class="detail-item"> |
| 227 | <label>创建时间:</label> | 223 | <label>创建时间:</label> |
| 228 | - <span>{{ formatTime(orderDetail.createTime || '') }}</span> | 224 | + <span>{{ formatTime(deliveryDetail.createTime || '') }}</span> |
| 229 | </div> | 225 | </div> |
| 230 | <div class="detail-item"> | 226 | <div class="detail-item"> |
| 231 | <label>更新时间:</label> | 227 | <label>更新时间:</label> |
| 232 | - <span>{{ formatTime(orderDetail.updateTime || '') }}</span> | 228 | + <span>{{ formatTime(deliveryDetail.updateTime || '') }}</span> |
| 233 | </div> | 229 | </div> |
| 234 | </div> | 230 | </div> |
| 235 | </div> | 231 | </div> |
| 236 | 232 | ||
| 237 | - <div class="detail-section" v-if="orderDetail.deliveryItems && orderDetail.deliveryItems.length > 0"> | 233 | + <div class="detail-section" v-if="deliveryDetail?.deliveryItems && deliveryDetail.deliveryItems.length > 0"> |
| 238 | <h4>出库明细</h4> | 234 | <h4>出库明细</h4> |
| 239 | <table class="detail-table"> | 235 | <table class="detail-table"> |
| 240 | <thead> | 236 | <thead> |
| ... | @@ -247,7 +243,7 @@ | ... | @@ -247,7 +243,7 @@ |
| 247 | </tr> | 243 | </tr> |
| 248 | </thead> | 244 | </thead> |
| 249 | <tbody> | 245 | <tbody> |
| 250 | - <tr v-for="item in orderDetail.deliveryItems" :key="item.deliveryItemId"> | 246 | + <tr v-for="item in deliveryDetail.deliveryItems" :key="item.itemId || item.deliveryId"> |
| 251 | <td>{{ item.productCode }}</td> | 247 | <td>{{ item.productCode }}</td> |
| 252 | <td>{{ item.productName }}</td> | 248 | <td>{{ item.productName }}</td> |
| 253 | <td>{{ item.deliveryQty }}</td> | 249 | <td>{{ item.deliveryQty }}</td> |
| ... | @@ -433,6 +429,7 @@ | ... | @@ -433,6 +429,7 @@ |
| 433 | 429 | ||
| 434 | <script setup lang="ts"> | 430 | <script setup lang="ts"> |
| 435 | import { ref, reactive, computed, onMounted } from 'vue' | 431 | import { ref, reactive, computed, onMounted } from 'vue' |
| 432 | +import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 436 | import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery' | 433 | import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery' |
| 437 | 434 | ||
| 438 | // 响应式数据 | 435 | // 响应式数据 |
| ... | @@ -518,7 +515,12 @@ const formatCurrency = (amount: number) => { | ... | @@ -518,7 +515,12 @@ const formatCurrency = (amount: number) => { |
| 518 | // 消息提示 | 515 | // 消息提示 |
| 519 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { | 516 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { |
| 520 | console.log(`${type}: ${message}`) | 517 | console.log(`${type}: ${message}`) |
| 521 | - alert(message) | 518 | + ElMessage({ |
| 519 | + message, | ||
| 520 | + type, | ||
| 521 | + duration: 3000, | ||
| 522 | + showClose: true | ||
| 523 | + }) | ||
| 522 | } | 524 | } |
| 523 | 525 | ||
| 524 | // 获取页码数组 | 526 | // 获取页码数组 |
| ... | @@ -612,8 +614,78 @@ const handleDelete = async (delivery: DeliveryInfo) => { | ... | @@ -612,8 +614,78 @@ const handleDelete = async (delivery: DeliveryInfo) => { |
| 612 | } | 614 | } |
| 613 | 615 | ||
| 614 | 616 | ||
| 615 | -const handleExport = () => { | 617 | +const handleExport = async () => { |
| 616 | - showMessage('导出功能开发中...', 'warning') | 618 | + try { |
| 619 | + // 显示确认弹窗 | ||
| 620 | + await ElMessageBox.confirm( | ||
| 621 | + '确定要导出出库数据吗?导出将包含当前筛选条件下的所有数据。', | ||
| 622 | + '确认导出', | ||
| 623 | + { | ||
| 624 | + confirmButtonText: '确定导出', | ||
| 625 | + cancelButtonText: '取消', | ||
| 626 | + type: 'warning', | ||
| 627 | + center: true | ||
| 628 | + } | ||
| 629 | + ) | ||
| 630 | + | ||
| 631 | + // 用户确认后显示加载提示 | ||
| 632 | + const loadingMessage = ElMessage({ | ||
| 633 | + message: '正在导出数据,请稍候...', | ||
| 634 | + type: 'warning', | ||
| 635 | + duration: 0, // 不自动关闭 | ||
| 636 | + showClose: false | ||
| 637 | + }) | ||
| 638 | + | ||
| 639 | + try { | ||
| 640 | + // 准备导出参数 | ||
| 641 | + const exportParams = { | ||
| 642 | + deliveryNo: searchParams.deliveryNo, | ||
| 643 | + dealerCode: searchParams.dealerCode, | ||
| 644 | + dealerName: searchParams.dealerName, | ||
| 645 | + deliveryStatus: searchParams.deliveryStatus, | ||
| 646 | + warehouseCode: searchParams.warehouseCode, | ||
| 647 | + dataSource: searchParams.dataSource, | ||
| 648 | + deliveryStartDate: searchParams.deliveryStartDate, | ||
| 649 | + deliveryEndDate: searchParams.deliveryEndDate | ||
| 650 | + } | ||
| 651 | + | ||
| 652 | + // 调用导出接口 | ||
| 653 | + const response = await deliveryApi.exportDeliveries(exportParams) | ||
| 654 | + | ||
| 655 | + // 创建下载链接 | ||
| 656 | + const blob = new Blob([response], { | ||
| 657 | + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
| 658 | + }) | ||
| 659 | + const url = window.URL.createObjectURL(blob) | ||
| 660 | + const link = document.createElement('a') | ||
| 661 | + link.href = url | ||
| 662 | + | ||
| 663 | + // 生成文件名 | ||
| 664 | + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14) | ||
| 665 | + link.download = `出库数据_${timestamp}.xlsx` | ||
| 666 | + | ||
| 667 | + // 触发下载 | ||
| 668 | + document.body.appendChild(link) | ||
| 669 | + link.click() | ||
| 670 | + document.body.removeChild(link) | ||
| 671 | + window.URL.revokeObjectURL(url) | ||
| 672 | + | ||
| 673 | + // 关闭加载提示,显示成功消息 | ||
| 674 | + loadingMessage.close() | ||
| 675 | + ElMessage.success('导出成功!文件已开始下载') | ||
| 676 | + | ||
| 677 | + } catch (exportError) { | ||
| 678 | + // 关闭加载提示 | ||
| 679 | + loadingMessage.close() | ||
| 680 | + throw exportError | ||
| 681 | + } | ||
| 682 | + | ||
| 683 | + } catch (error) { | ||
| 684 | + if (error !== 'cancel') { | ||
| 685 | + console.error('导出失败:', error) | ||
| 686 | + ElMessage.error('导出失败,请重试') | ||
| 687 | + } | ||
| 688 | + } | ||
| 617 | } | 689 | } |
| 618 | 690 | ||
| 619 | // 新增出库相关函数 | 691 | // 新增出库相关函数 |
| ... | @@ -680,9 +752,9 @@ const handleSubmitAdd = async () => { | ... | @@ -680,9 +752,9 @@ const handleSubmitAdd = async () => { |
| 680 | deliveryItems: addFormData.deliveryItems.map(item => ({ | 752 | deliveryItems: addFormData.deliveryItems.map(item => ({ |
| 681 | productCode: item.productCode, | 753 | productCode: item.productCode, |
| 682 | productName: item.productName, | 754 | productName: item.productName, |
| 683 | - productQty: item.deliveryQty, | 755 | + deliveryQty: item.deliveryQty, |
| 684 | - unitPrice: item.deliveryPrice, | 756 | + deliveryPrice: item.deliveryPrice, |
| 685 | - totalPrice: item.deliveryAmount | 757 | + deliveryAmount: item.deliveryAmount |
| 686 | })) | 758 | })) |
| 687 | } | 759 | } |
| 688 | 760 | ... | ... |
| ... | @@ -507,6 +507,7 @@ | ... | @@ -507,6 +507,7 @@ |
| 507 | 507 | ||
| 508 | <script setup lang="ts"> | 508 | <script setup lang="ts"> |
| 509 | import { ref, reactive, computed, onMounted } from 'vue' | 509 | import { ref, reactive, computed, onMounted } from 'vue' |
| 510 | +import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 510 | import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice' | 511 | import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice' |
| 511 | 512 | ||
| 512 | // 响应式数据 | 513 | // 响应式数据 |
| ... | @@ -594,7 +595,12 @@ const formatCurrency = (amount: number) => { | ... | @@ -594,7 +595,12 @@ const formatCurrency = (amount: number) => { |
| 594 | // 消息提示 | 595 | // 消息提示 |
| 595 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { | 596 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { |
| 596 | console.log(`${type}: ${message}`) | 597 | console.log(`${type}: ${message}`) |
| 597 | - alert(message) | 598 | + ElMessage({ |
| 599 | + message, | ||
| 600 | + type, | ||
| 601 | + duration: 3000, | ||
| 602 | + showClose: true | ||
| 603 | + }) | ||
| 598 | } | 604 | } |
| 599 | 605 | ||
| 600 | // 获取页码数组 | 606 | // 获取页码数组 |
| ... | @@ -825,8 +831,81 @@ const handleView = async (order: InvoiceInfo) => { | ... | @@ -825,8 +831,81 @@ const handleView = async (order: InvoiceInfo) => { |
| 825 | } | 831 | } |
| 826 | 832 | ||
| 827 | 833 | ||
| 828 | -const handleExport = () => { | 834 | +const handleExport = async () => { |
| 829 | - showMessage('导出功能开发中...', 'warning') | 835 | + try { |
| 836 | + // 显示确认弹窗 | ||
| 837 | + await ElMessageBox.confirm( | ||
| 838 | + '确定要导出发票数据吗?导出将包含当前筛选条件下的所有数据。', | ||
| 839 | + '确认导出', | ||
| 840 | + { | ||
| 841 | + confirmButtonText: '确定导出', | ||
| 842 | + cancelButtonText: '取消', | ||
| 843 | + type: 'warning', | ||
| 844 | + center: true | ||
| 845 | + } | ||
| 846 | + ) | ||
| 847 | + | ||
| 848 | + // 用户确认后显示加载提示 | ||
| 849 | + const loadingMessage = ElMessage({ | ||
| 850 | + message: '正在导出数据,请稍候...', | ||
| 851 | + type: 'warning', | ||
| 852 | + duration: 0, // 不自动关闭 | ||
| 853 | + showClose: false | ||
| 854 | + }) | ||
| 855 | + | ||
| 856 | + try { | ||
| 857 | + // 准备导出参数 | ||
| 858 | + const exportParams = { | ||
| 859 | + invoiceNo: searchParams.invoiceNo, | ||
| 860 | + orderNo: searchParams.orderNo, | ||
| 861 | + deliveryNo: searchParams.deliveryNo, | ||
| 862 | + dealerCode: searchParams.dealerCode, | ||
| 863 | + dealerName: searchParams.dealerName, | ||
| 864 | + invoiceStatus: searchParams.invoiceStatus, | ||
| 865 | + dataSource: searchParams.dataSource, | ||
| 866 | + invoiceStartDate: searchParams.invoiceStartDate, | ||
| 867 | + invoiceEndDate: searchParams.invoiceEndDate, | ||
| 868 | + minAmount: searchParams.minAmount, | ||
| 869 | + maxAmount: searchParams.maxAmount | ||
| 870 | + } | ||
| 871 | + | ||
| 872 | + // 调用导出接口 | ||
| 873 | + const response = await invoiceApi.exportInvoices(exportParams) | ||
| 874 | + | ||
| 875 | + // 创建下载链接 | ||
| 876 | + const blob = new Blob([response], { | ||
| 877 | + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
| 878 | + }) | ||
| 879 | + const url = window.URL.createObjectURL(blob) | ||
| 880 | + const link = document.createElement('a') | ||
| 881 | + link.href = url | ||
| 882 | + | ||
| 883 | + // 生成文件名 | ||
| 884 | + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14) | ||
| 885 | + link.download = `发票数据_${timestamp}.xlsx` | ||
| 886 | + | ||
| 887 | + // 触发下载 | ||
| 888 | + document.body.appendChild(link) | ||
| 889 | + link.click() | ||
| 890 | + document.body.removeChild(link) | ||
| 891 | + window.URL.revokeObjectURL(url) | ||
| 892 | + | ||
| 893 | + // 关闭加载提示,显示成功消息 | ||
| 894 | + loadingMessage.close() | ||
| 895 | + ElMessage.success('导出成功!文件已开始下载') | ||
| 896 | + | ||
| 897 | + } catch (exportError) { | ||
| 898 | + // 关闭加载提示 | ||
| 899 | + loadingMessage.close() | ||
| 900 | + throw exportError | ||
| 901 | + } | ||
| 902 | + | ||
| 903 | + } catch (error) { | ||
| 904 | + if (error !== 'cancel') { | ||
| 905 | + console.error('导出失败:', error) | ||
| 906 | + ElMessage.error('导出失败,请重试') | ||
| 907 | + } | ||
| 908 | + } | ||
| 830 | } | 909 | } |
| 831 | 910 | ||
| 832 | const handlePrintInvoice = (invoice: InvoiceInfo) => { | 911 | const handlePrintInvoice = (invoice: InvoiceInfo) => { | ... | ... |
| ... | @@ -447,6 +447,7 @@ | ... | @@ -447,6 +447,7 @@ |
| 447 | 447 | ||
| 448 | <script setup lang="ts"> | 448 | <script setup lang="ts"> |
| 449 | import { ref, reactive, computed, onMounted } from 'vue' | 449 | import { ref, reactive, computed, onMounted } from 'vue' |
| 450 | +import { ElMessage, ElMessageBox } from 'element-plus' | ||
| 450 | import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order' | 451 | import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order' |
| 451 | 452 | ||
| 452 | // 响应式数据 | 453 | // 响应式数据 |
| ... | @@ -586,9 +587,12 @@ const formatCurrency = (amount: number) => { | ... | @@ -586,9 +587,12 @@ const formatCurrency = (amount: number) => { |
| 586 | 587 | ||
| 587 | // 消息提示 | 588 | // 消息提示 |
| 588 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { | 589 | const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { |
| 589 | - // 这里可以集成消息提示组件 | 590 | + ElMessage({ |
| 590 | - console.log(`${type}: ${message}`) | 591 | + message, |
| 591 | - alert(message) | 592 | + type, |
| 593 | + duration: 3000, | ||
| 594 | + showClose: true | ||
| 595 | + }) | ||
| 592 | } | 596 | } |
| 593 | 597 | ||
| 594 | // 方法 | 598 | // 方法 |
| ... | @@ -757,8 +761,82 @@ const handleBatchDelete = async () => { | ... | @@ -757,8 +761,82 @@ const handleBatchDelete = async () => { |
| 757 | } | 761 | } |
| 758 | 762 | ||
| 759 | 763 | ||
| 760 | -const handleExport = () => { | 764 | +const handleExport = async () => { |
| 761 | - showMessage('导出功能开发中...', 'warning') | 765 | + try { |
| 766 | + // 显示确认弹窗 | ||
| 767 | + await ElMessageBox.confirm( | ||
| 768 | + '确定要导出订单数据吗?导出将包含当前筛选条件下的所有数据。', | ||
| 769 | + '确认导出', | ||
| 770 | + { | ||
| 771 | + confirmButtonText: '确定导出', | ||
| 772 | + cancelButtonText: '取消', | ||
| 773 | + type: 'warning', | ||
| 774 | + center: true | ||
| 775 | + } | ||
| 776 | + ) | ||
| 777 | + | ||
| 778 | + // 用户确认后显示加载提示 | ||
| 779 | + const loadingMessage = ElMessage({ | ||
| 780 | + message: '正在导出数据,请稍候...', | ||
| 781 | + type: 'warning', | ||
| 782 | + duration: 0, // 不自动关闭 | ||
| 783 | + showClose: false | ||
| 784 | + }) | ||
| 785 | + | ||
| 786 | + try { | ||
| 787 | + // 准备导出参数 | ||
| 788 | + const exportParams = { | ||
| 789 | + orderNo: searchParams.orderNo, | ||
| 790 | + dealerCode: searchParams.dealerCode, | ||
| 791 | + dealerName: searchParams.dealerName, | ||
| 792 | + deliveryStatus: searchParams.deliveryStatus, | ||
| 793 | + invoiceStatus: searchParams.invoiceStatus, | ||
| 794 | + rebateCalcFlag: searchParams.rebateCalcFlag, | ||
| 795 | + dataSource: searchParams.dataSource, | ||
| 796 | + verifyStatus: searchParams.verifyStatus, | ||
| 797 | + orderStartDate: searchParams.orderStartDate, | ||
| 798 | + orderEndDate: searchParams.orderEndDate, | ||
| 799 | + minAmount: searchParams.minAmount, | ||
| 800 | + maxAmount: searchParams.maxAmount | ||
| 801 | + } | ||
| 802 | + | ||
| 803 | + // 调用导出接口 | ||
| 804 | + const response = await orderApi.exportOrders(exportParams) | ||
| 805 | + | ||
| 806 | + // 创建下载链接 | ||
| 807 | + const blob = new Blob([response], { | ||
| 808 | + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
| 809 | + }) | ||
| 810 | + const url = window.URL.createObjectURL(blob) | ||
| 811 | + const link = document.createElement('a') | ||
| 812 | + link.href = url | ||
| 813 | + | ||
| 814 | + // 生成文件名 | ||
| 815 | + const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, '').slice(0, 14) | ||
| 816 | + link.download = `订单数据_${timestamp}.xlsx` | ||
| 817 | + | ||
| 818 | + // 触发下载 | ||
| 819 | + document.body.appendChild(link) | ||
| 820 | + link.click() | ||
| 821 | + document.body.removeChild(link) | ||
| 822 | + window.URL.revokeObjectURL(url) | ||
| 823 | + | ||
| 824 | + // 关闭加载提示,显示成功消息 | ||
| 825 | + loadingMessage.close() | ||
| 826 | + ElMessage.success('导出成功!文件已开始下载') | ||
| 827 | + | ||
| 828 | + } catch (exportError) { | ||
| 829 | + // 关闭加载提示 | ||
| 830 | + loadingMessage.close() | ||
| 831 | + throw exportError | ||
| 832 | + } | ||
| 833 | + | ||
| 834 | + } catch (error) { | ||
| 835 | + if (error !== 'cancel') { | ||
| 836 | + console.error('导出失败:', error) | ||
| 837 | + ElMessage.error('导出失败,请重试') | ||
| 838 | + } | ||
| 839 | + } | ||
| 762 | } | 840 | } |
| 763 | 841 | ||
| 764 | const handleSelectAll = () => { | 842 | const handleSelectAll = () => { | ... | ... |
This diff is collapsed. Click to expand it.
frontend/src/views/settings/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="settings-container"> | ||
| 3 | + <!-- 页面标题 --> | ||
| 4 | + <div class="page-header"> | ||
| 5 | + <h2>系统设置</h2> | ||
| 6 | + <p>管理系统配置和参数</p> | ||
| 7 | + </div> | ||
| 8 | + | ||
| 9 | + <!-- 设置内容 --> | ||
| 10 | + <div class="settings-content"> | ||
| 11 | + <div class="settings-card"> | ||
| 12 | + <h3>基本设置</h3> | ||
| 13 | + <div class="setting-item"> | ||
| 14 | + <label>系统名称:</label> | ||
| 15 | + <input v-model="settings.systemName" class="setting-input" placeholder="请输入系统名称" /> | ||
| 16 | + </div> | ||
| 17 | + <div class="setting-item"> | ||
| 18 | + <label>系统版本:</label> | ||
| 19 | + <input v-model="settings.systemVersion" class="setting-input" placeholder="请输入系统版本" /> | ||
| 20 | + </div> | ||
| 21 | + <div class="setting-item"> | ||
| 22 | + <label>系统描述:</label> | ||
| 23 | + <textarea v-model="settings.systemDescription" class="setting-textarea" placeholder="请输入系统描述"></textarea> | ||
| 24 | + </div> | ||
| 25 | + </div> | ||
| 26 | + | ||
| 27 | + <div class="settings-card"> | ||
| 28 | + <h3>业务设置</h3> | ||
| 29 | + <div class="setting-item"> | ||
| 30 | + <label>默认分页大小:</label> | ||
| 31 | + <select v-model="settings.defaultPageSize" class="setting-select"> | ||
| 32 | + <option value="10">10条/页</option> | ||
| 33 | + <option value="20">20条/页</option> | ||
| 34 | + <option value="50">50条/页</option> | ||
| 35 | + <option value="100">100条/页</option> | ||
| 36 | + </select> | ||
| 37 | + </div> | ||
| 38 | + <div class="setting-item"> | ||
| 39 | + <label>数据保留天数:</label> | ||
| 40 | + <input v-model="settings.dataRetentionDays" type="number" class="setting-input" placeholder="请输入数据保留天数" /> | ||
| 41 | + </div> | ||
| 42 | + <div class="setting-item"> | ||
| 43 | + <label>自动备份:</label> | ||
| 44 | + <label class="checkbox-label"> | ||
| 45 | + <input v-model="settings.autoBackup" type="checkbox" /> | ||
| 46 | + <span>启用自动备份</span> | ||
| 47 | + </label> | ||
| 48 | + </div> | ||
| 49 | + </div> | ||
| 50 | + | ||
| 51 | + <div class="settings-card"> | ||
| 52 | + <h3>安全设置</h3> | ||
| 53 | + <div class="setting-item"> | ||
| 54 | + <label>会话超时时间(分钟):</label> | ||
| 55 | + <input v-model="settings.sessionTimeout" type="number" class="setting-input" placeholder="请输入会话超时时间" /> | ||
| 56 | + </div> | ||
| 57 | + <div class="setting-item"> | ||
| 58 | + <label>密码复杂度:</label> | ||
| 59 | + <select v-model="settings.passwordComplexity" class="setting-select"> | ||
| 60 | + <option value="low">低</option> | ||
| 61 | + <option value="medium">中</option> | ||
| 62 | + <option value="high">高</option> | ||
| 63 | + </select> | ||
| 64 | + </div> | ||
| 65 | + <div class="setting-item"> | ||
| 66 | + <label>登录失败锁定:</label> | ||
| 67 | + <label class="checkbox-label"> | ||
| 68 | + <input v-model="settings.loginLock" type="checkbox" /> | ||
| 69 | + <span>启用登录失败锁定</span> | ||
| 70 | + </label> | ||
| 71 | + </div> | ||
| 72 | + </div> | ||
| 73 | + | ||
| 74 | + <div class="settings-card"> | ||
| 75 | + <h3>通知设置</h3> | ||
| 76 | + <div class="setting-item"> | ||
| 77 | + <label>邮件通知:</label> | ||
| 78 | + <label class="checkbox-label"> | ||
| 79 | + <input v-model="settings.emailNotification" type="checkbox" /> | ||
| 80 | + <span>启用邮件通知</span> | ||
| 81 | + </label> | ||
| 82 | + </div> | ||
| 83 | + <div class="setting-item"> | ||
| 84 | + <label>短信通知:</label> | ||
| 85 | + <label class="checkbox-label"> | ||
| 86 | + <input v-model="settings.smsNotification" type="checkbox" /> | ||
| 87 | + <span>启用短信通知</span> | ||
| 88 | + </label> | ||
| 89 | + </div> | ||
| 90 | + <div class="setting-item"> | ||
| 91 | + <label>系统消息:</label> | ||
| 92 | + <label class="checkbox-label"> | ||
| 93 | + <input v-model="settings.systemMessage" type="checkbox" /> | ||
| 94 | + <span>启用系统消息</span> | ||
| 95 | + </label> | ||
| 96 | + </div> | ||
| 97 | + </div> | ||
| 98 | + | ||
| 99 | + <!-- 操作按钮 --> | ||
| 100 | + <div class="settings-actions"> | ||
| 101 | + <button @click="handleSave" class="action-btn primary">💾 保存设置</button> | ||
| 102 | + <button @click="handleReset" class="action-btn secondary">🔄 重置</button> | ||
| 103 | + <button @click="handleTest" class="action-btn info">🧪 测试连接</button> | ||
| 104 | + </div> | ||
| 105 | + </div> | ||
| 106 | + </div> | ||
| 107 | +</template> | ||
| 108 | + | ||
| 109 | +<script setup lang="ts"> | ||
| 110 | +import { ref, reactive, onMounted } from 'vue' | ||
| 111 | + | ||
| 112 | +// 设置数据 | ||
| 113 | +const settings = reactive({ | ||
| 114 | + systemName: 'Apple ERP系统', | ||
| 115 | + systemVersion: '1.0.0', | ||
| 116 | + systemDescription: '企业资源规划管理系统', | ||
| 117 | + defaultPageSize: 20, | ||
| 118 | + dataRetentionDays: 365, | ||
| 119 | + autoBackup: true, | ||
| 120 | + sessionTimeout: 30, | ||
| 121 | + passwordComplexity: 'medium', | ||
| 122 | + loginLock: true, | ||
| 123 | + emailNotification: true, | ||
| 124 | + smsNotification: false, | ||
| 125 | + systemMessage: true | ||
| 126 | +}) | ||
| 127 | + | ||
| 128 | +// 原始设置(用于重置) | ||
| 129 | +const originalSettings = ref({}) | ||
| 130 | + | ||
| 131 | +// 保存设置 | ||
| 132 | +const handleSave = () => { | ||
| 133 | + // 这里可以调用后端API保存设置 | ||
| 134 | + console.log('保存设置:', settings) | ||
| 135 | + showMessage('设置保存成功', 'success') | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +// 重置设置 | ||
| 139 | +const handleReset = () => { | ||
| 140 | + Object.assign(settings, originalSettings.value) | ||
| 141 | + showMessage('设置已重置', 'warning') | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +// 测试连接 | ||
| 145 | +const handleTest = () => { | ||
| 146 | + showMessage('连接测试成功', 'success') | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +// 显示消息 | ||
| 150 | +const showMessage = (message: string, type: 'success' | 'warning' | 'error') => { | ||
| 151 | + // 这里可以集成Element Plus的消息组件 | ||
| 152 | + console.log(`${type}: ${message}`) | ||
| 153 | +} | ||
| 154 | + | ||
| 155 | +// 组件挂载时加载设置 | ||
| 156 | +onMounted(() => { | ||
| 157 | + // 这里可以调用后端API加载设置 | ||
| 158 | + originalSettings.value = { ...settings } | ||
| 159 | +}) | ||
| 160 | +</script> | ||
| 161 | + | ||
| 162 | +<style scoped> | ||
| 163 | +.settings-container { | ||
| 164 | + padding: 20px; | ||
| 165 | + background-color: #f5f5f5; | ||
| 166 | + min-height: 100vh; | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +.page-header { | ||
| 170 | + margin-bottom: 30px; | ||
| 171 | + text-align: center; | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +.page-header h2 { | ||
| 175 | + color: #333; | ||
| 176 | + margin-bottom: 10px; | ||
| 177 | + font-size: 28px; | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +.page-header p { | ||
| 181 | + color: #666; | ||
| 182 | + font-size: 16px; | ||
| 183 | +} | ||
| 184 | + | ||
| 185 | +.settings-content { | ||
| 186 | + max-width: 1200px; | ||
| 187 | + margin: 0 auto; | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +.settings-card { | ||
| 191 | + background: white; | ||
| 192 | + border-radius: 8px; | ||
| 193 | + padding: 24px; | ||
| 194 | + margin-bottom: 24px; | ||
| 195 | + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +.settings-card h3 { | ||
| 199 | + color: #333; | ||
| 200 | + margin-bottom: 20px; | ||
| 201 | + font-size: 18px; | ||
| 202 | + border-bottom: 2px solid #e9ecef; | ||
| 203 | + padding-bottom: 10px; | ||
| 204 | +} | ||
| 205 | + | ||
| 206 | +.setting-item { | ||
| 207 | + display: flex; | ||
| 208 | + align-items: center; | ||
| 209 | + margin-bottom: 20px; | ||
| 210 | + gap: 16px; | ||
| 211 | +} | ||
| 212 | + | ||
| 213 | +.setting-item label { | ||
| 214 | + min-width: 150px; | ||
| 215 | + color: #333; | ||
| 216 | + font-weight: 500; | ||
| 217 | +} | ||
| 218 | + | ||
| 219 | +.setting-input, | ||
| 220 | +.setting-select, | ||
| 221 | +.setting-textarea { | ||
| 222 | + flex: 1; | ||
| 223 | + padding: 8px 12px; | ||
| 224 | + border: 1px solid #ddd; | ||
| 225 | + border-radius: 4px; | ||
| 226 | + font-size: 14px; | ||
| 227 | + transition: border-color 0.3s; | ||
| 228 | +} | ||
| 229 | + | ||
| 230 | +.setting-input:focus, | ||
| 231 | +.setting-select:focus, | ||
| 232 | +.setting-textarea:focus { | ||
| 233 | + outline: none; | ||
| 234 | + border-color: #007bff; | ||
| 235 | + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); | ||
| 236 | +} | ||
| 237 | + | ||
| 238 | +.setting-textarea { | ||
| 239 | + min-height: 80px; | ||
| 240 | + resize: vertical; | ||
| 241 | +} | ||
| 242 | + | ||
| 243 | +.checkbox-label { | ||
| 244 | + display: flex; | ||
| 245 | + align-items: center; | ||
| 246 | + gap: 8px; | ||
| 247 | + cursor: pointer; | ||
| 248 | +} | ||
| 249 | + | ||
| 250 | +.checkbox-label input[type="checkbox"] { | ||
| 251 | + width: 16px; | ||
| 252 | + height: 16px; | ||
| 253 | +} | ||
| 254 | + | ||
| 255 | +.settings-actions { | ||
| 256 | + display: flex; | ||
| 257 | + gap: 16px; | ||
| 258 | + justify-content: center; | ||
| 259 | + margin-top: 30px; | ||
| 260 | +} | ||
| 261 | + | ||
| 262 | +.action-btn { | ||
| 263 | + padding: 12px 24px; | ||
| 264 | + border: none; | ||
| 265 | + border-radius: 6px; | ||
| 266 | + font-size: 14px; | ||
| 267 | + font-weight: 500; | ||
| 268 | + cursor: pointer; | ||
| 269 | + transition: all 0.3s; | ||
| 270 | + min-width: 120px; | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +.action-btn.primary { | ||
| 274 | + background-color: #007bff; | ||
| 275 | + color: white; | ||
| 276 | +} | ||
| 277 | + | ||
| 278 | +.action-btn.primary:hover { | ||
| 279 | + background-color: #0056b3; | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +.action-btn.secondary { | ||
| 283 | + background-color: #6c757d; | ||
| 284 | + color: white; | ||
| 285 | +} | ||
| 286 | + | ||
| 287 | +.action-btn.secondary:hover { | ||
| 288 | + background-color: #545b62; | ||
| 289 | +} | ||
| 290 | + | ||
| 291 | +.action-btn.info { | ||
| 292 | + background-color: #17a2b8; | ||
| 293 | + color: white; | ||
| 294 | +} | ||
| 295 | + | ||
| 296 | +.action-btn.info:hover { | ||
| 297 | + background-color: #138496; | ||
| 298 | +} | ||
| 299 | + | ||
| 300 | +@media (max-width: 768px) { | ||
| 301 | + .setting-item { | ||
| 302 | + flex-direction: column; | ||
| 303 | + align-items: flex-start; | ||
| 304 | + } | ||
| 305 | + | ||
| 306 | + .setting-item label { | ||
| 307 | + min-width: auto; | ||
| 308 | + margin-bottom: 8px; | ||
| 309 | + } | ||
| 310 | + | ||
| 311 | + .settings-actions { | ||
| 312 | + flex-direction: column; | ||
| 313 | + align-items: center; | ||
| 314 | + } | ||
| 315 | +} | ||
| 316 | +</style> |
| ... | @@ -7,7 +7,7 @@ | ... | @@ -7,7 +7,7 @@ |
| 7 | "auto-imports.d.ts", | 7 | "auto-imports.d.ts", |
| 8 | "components.d.ts" | 8 | "components.d.ts" |
| 9 | ], | 9 | ], |
| 10 | - "exclude": ["src/**/__tests__/*"], | 10 | + "exclude": ["src/**/__tests__/*", "src/views/test-menu.vue"], |
| 11 | "compilerOptions": { | 11 | "compilerOptions": { |
| 12 | "composite": true, | 12 | "composite": true, |
| 13 | "baseUrl": ".", | 13 | "baseUrl": ".", | ... | ... |
-
Please register or login to post a comment