zhouhui.jiang

update 订单、出库 、发票

...@@ -997,6 +997,47 @@ ...@@ -997,6 +997,47 @@
997 } 997 }
998 ``` 998 ```
999 999
1000 +### 7.9 获取所有产品
1001 +
1002 +**接口路径:** `GET /api/product/all`
1003 +
1004 +**功能描述:** 获取所有启用的产品信息,用于下拉选择
1005 +
1006 +**权限要求:**
1007 +
1008 +**请求参数:**
1009 +
1010 +**响应示例:**
1011 +```json
1012 +{
1013 + "code": 200,
1014 + "message": "操作成功",
1015 + "data": [
1016 + {
1017 + "productId": 1,
1018 + "productCode": "APL-IP15-128G-BK",
1019 + "productName": "iPhone 15|A2848|iPhone|黑色"
1020 + },
1021 + {
1022 + "productId": 2,
1023 + "productCode": "APL-IP15-256G-BK",
1024 + "productName": "iPhone 15|A2848|iPhone|黑色"
1025 + },
1026 + {
1027 + "productId": 3,
1028 + "productCode": "APL-IP15-128G-WH",
1029 + "productName": "iPhone 15|A2848|iPhone|白色"
1030 + }
1031 + ]
1032 +}
1033 +```
1034 +
1035 +**响应字段说明:**
1036 +- 只返回未删除且销售状态为在售的产品
1037 +- 按产品编码升序排列
1038 +- 产品名称格式:`产品名称|产品型号|产品类别|产品颜色`
1039 +- 主要用于订单管理等模块的下拉选择
1040 +
1000 ## 8. 经销商管理 (DealerInfoController) 1041 ## 8. 经销商管理 (DealerInfoController)
1001 1042
1002 ### 8.1 分页查询经销商列表 1043 ### 8.1 分页查询经销商列表
...@@ -1328,6 +1369,81 @@ ...@@ -1328,6 +1369,81 @@
1328 } 1369 }
1329 ``` 1370 ```
1330 1371
1372 +### 8.9 获取所有经销商
1373 +
1374 +**接口路径:** `GET /api/dealer/all`
1375 +
1376 +**功能描述:** 获取所有启用的经销商信息,用于下拉选择
1377 +
1378 +**权限要求:**
1379 +
1380 +**请求参数:**
1381 +
1382 +**响应示例:**
1383 +```json
1384 +{
1385 + "code": 200,
1386 + "message": "操作成功",
1387 + "data": [
1388 + {
1389 + "dealerId": 1,
1390 + "dealerCode": "DL001",
1391 + "dealerName": "北京经销商",
1392 + "creditCode": "91110000123456789X",
1393 + "dealerLevel": 1,
1394 + "region": "北京",
1395 + "contactPerson": "张三",
1396 + "contactPhone": "13800138000",
1397 + "cooperateStartDate": "2024-01-01",
1398 + "cooperateStatus": 1,
1399 + "businessLicenseUrl": "https://example.com/license.jpg",
1400 + "cooperationAgreementUrl": "https://example.com/agreement.pdf",
1401 + "qualificationAuditStatus": 2,
1402 + "auditOpinion": "审核通过",
1403 + "totalRebateAmount": 10000.00,
1404 + "usedRebateAmount": 5000.00,
1405 + "pendingRebateAmount": 5000.00,
1406 + "lastRebateUpdateTime": "2024-01-01 10:00:00",
1407 + "createBy": "admin",
1408 + "createTime": "2024-01-01 10:00:00",
1409 + "updateBy": "admin",
1410 + "updateTime": "2024-01-01 10:00:00",
1411 + "delFlag": "0"
1412 + },
1413 + {
1414 + "dealerId": 2,
1415 + "dealerCode": "DL002",
1416 + "dealerName": "上海经销商",
1417 + "creditCode": "91310000123456789Y",
1418 + "dealerLevel": 2,
1419 + "region": "上海",
1420 + "contactPerson": "李四",
1421 + "contactPhone": "13900139000",
1422 + "cooperateStartDate": "2024-02-01",
1423 + "cooperateStatus": 1,
1424 + "businessLicenseUrl": "https://example.com/license2.jpg",
1425 + "cooperationAgreementUrl": "https://example.com/agreement2.pdf",
1426 + "qualificationAuditStatus": 2,
1427 + "auditOpinion": "审核通过",
1428 + "totalRebateAmount": 15000.00,
1429 + "usedRebateAmount": 8000.00,
1430 + "pendingRebateAmount": 7000.00,
1431 + "lastRebateUpdateTime": "2024-02-01 10:00:00",
1432 + "createBy": "admin",
1433 + "createTime": "2024-02-01 10:00:00",
1434 + "updateBy": "admin",
1435 + "updateTime": "2024-02-01 10:00:00",
1436 + "delFlag": "0"
1437 + }
1438 + ]
1439 +}
1440 +```
1441 +
1442 +**响应字段说明:**
1443 +- 只返回未删除且合作状态为正常的经销商
1444 +- 按经销商编码升序排列
1445 +- 主要用于订单管理等模块的下拉选择
1446 +
1331 ## 数据字典 1447 ## 数据字典
1332 1448
1333 ### 销售状态(saleStatus) 1449 ### 销售状态(saleStatus)
......
...@@ -33,6 +33,13 @@ public class DealerInfoController { ...@@ -33,6 +33,13 @@ public class DealerInfoController {
33 return ApiRes.success(page); 33 return ApiRes.success(page);
34 } 34 }
35 35
36 + @Operation(summary = "获取所有经销商", description = "获取所有启用的经销商信息,用于下拉选择")
37 + @GetMapping("/all")
38 + public ApiRes<List<DealerInfo>> getAllDealers() {
39 + List<DealerInfo> dealers = dealerInfoService.getAllDealers();
40 + return ApiRes.success(dealers);
41 + }
42 +
36 @Operation(summary = "获取经销商详情", description = "根据经销商ID获取经销商详细信息") 43 @Operation(summary = "获取经销商详情", description = "根据经销商ID获取经销商详细信息")
37 @GetMapping("/{dealerId}") 44 @GetMapping("/{dealerId}")
38 @PreAuthorize("hasAuthority('dealer:detail')") 45 @PreAuthorize("hasAuthority('dealer:detail')")
......
...@@ -3,6 +3,7 @@ package com.apple.erp.controller; ...@@ -3,6 +3,7 @@ package com.apple.erp.controller;
3 import com.apple.erp.dto.ProductAddReq; 3 import com.apple.erp.dto.ProductAddReq;
4 import com.apple.erp.dto.ProductQueryReq; 4 import com.apple.erp.dto.ProductQueryReq;
5 import com.apple.erp.dto.ProductUpdateReq; 5 import com.apple.erp.dto.ProductUpdateReq;
6 +import com.apple.erp.dto.ProductSimpleRes;
6 import com.apple.erp.entity.ProductInfo; 7 import com.apple.erp.entity.ProductInfo;
7 import com.apple.erp.service.ProductInfoService; 8 import com.apple.erp.service.ProductInfoService;
8 import com.apple.erp.dto.response.ApiRes; 9 import com.apple.erp.dto.response.ApiRes;
...@@ -174,4 +175,15 @@ public class ProductInfoController { ...@@ -174,4 +175,15 @@ public class ProductInfoController {
174 return ApiRes.error("修改返利标识失败:" + e.getMessage()); 175 return ApiRes.error("修改返利标识失败:" + e.getMessage());
175 } 176 }
176 } 177 }
178 +
179 + @Operation(summary = "获取所有产品", description = "获取所有启用的产品信息,用于下拉选择")
180 + @GetMapping("/all")
181 + public ApiRes<List<ProductSimpleRes>> getAllProducts() {
182 + try {
183 + List<ProductSimpleRes> products = productInfoService.getAllProducts();
184 + return ApiRes.success(products);
185 + } catch (Exception e) {
186 + return ApiRes.error("获取产品列表失败:" + e.getMessage());
187 + }
188 + }
177 } 189 }
......
1 +package com.apple.erp.dto;
2 +
3 +import io.swagger.v3.oas.annotations.media.Schema;
4 +import lombok.Data;
5 +
6 +/**
7 + * 产品简单信息响应DTO
8 + * 用于下拉选择等场景
9 + *
10 + * @author Apple ERP Team
11 + * @since 2025-01-27
12 + */
13 +@Data
14 +@Schema(description = "产品简单信息")
15 +public class ProductSimpleRes {
16 +
17 + @Schema(description = "产品ID")
18 + private Long productId;
19 +
20 + @Schema(description = "产品编码")
21 + private String productCode;
22 +
23 + @Schema(description = "产品名称")
24 + private String productName;
25 +}
...@@ -69,4 +69,10 @@ public interface DealerInfoService extends IService<DealerInfo> { ...@@ -69,4 +69,10 @@ public interface DealerInfoService extends IService<DealerInfo> {
69 * @return 是否成功 69 * @return 是否成功
70 */ 70 */
71 boolean updateDealerAuditStatus(Long dealerId, Integer qualificationAuditStatus, String auditOpinion); 71 boolean updateDealerAuditStatus(Long dealerId, Integer qualificationAuditStatus, String auditOpinion);
72 +
73 + /**
74 + * 获取所有启用的经销商
75 + * @return 经销商列表
76 + */
77 + List<DealerInfo> getAllDealers();
72 } 78 }
......
...@@ -3,6 +3,7 @@ package com.apple.erp.service; ...@@ -3,6 +3,7 @@ package com.apple.erp.service;
3 import com.apple.erp.dto.ProductAddReq; 3 import com.apple.erp.dto.ProductAddReq;
4 import com.apple.erp.dto.ProductQueryReq; 4 import com.apple.erp.dto.ProductQueryReq;
5 import com.apple.erp.dto.ProductUpdateReq; 5 import com.apple.erp.dto.ProductUpdateReq;
6 +import com.apple.erp.dto.ProductSimpleRes;
6 import com.apple.erp.entity.ProductInfo; 7 import com.apple.erp.entity.ProductInfo;
7 import com.baomidou.mybatisplus.core.metadata.IPage; 8 import com.baomidou.mybatisplus.core.metadata.IPage;
8 import com.baomidou.mybatisplus.extension.service.IService; 9 import com.baomidou.mybatisplus.extension.service.IService;
...@@ -91,4 +92,11 @@ public interface ProductInfoService extends IService<ProductInfo> { ...@@ -91,4 +92,11 @@ public interface ProductInfoService extends IService<ProductInfo> {
91 * @return 是否存在 92 * @return 是否存在
92 */ 93 */
93 boolean checkProductCodeExists(String productCode, Long productId); 94 boolean checkProductCodeExists(String productCode, Long productId);
95 +
96 + /**
97 + * 获取所有启用的产品简单信息
98 + *
99 + * @return 产品简单信息列表
100 + */
101 + List<ProductSimpleRes> getAllProducts();
94 } 102 }
......
...@@ -234,4 +234,13 @@ public class DealerInfoServiceImpl extends ServiceImpl<DealerInfoMapper, DealerI ...@@ -234,4 +234,13 @@ public class DealerInfoServiceImpl extends ServiceImpl<DealerInfoMapper, DealerI
234 } 234 }
235 return count(queryWrapper) > 0; 235 return count(queryWrapper) > 0;
236 } 236 }
237 +
238 + @Override
239 + public List<DealerInfo> getAllDealers() {
240 + LambdaQueryWrapper<DealerInfo> queryWrapper = new LambdaQueryWrapper<>();
241 + queryWrapper.eq(DealerInfo::getDelFlag, "0") // 未删除
242 + .eq(DealerInfo::getCooperateStatus, 1) // 正常合作状态
243 + .orderByAsc(DealerInfo::getDealerCode); // 按经销商编码排序
244 + return list(queryWrapper);
245 + }
237 } 246 }
......
1 -package com.apple.erp.service.impl;
2 -
3 -import com.apple.erp.dto.*;
4 -import com.apple.erp.entity.DeliveryItem;
5 -import com.apple.erp.mapper.DeliveryItemMapper;
6 -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
7 -import com.baomidou.mybatisplus.core.metadata.IPage;
8 -import com.baomidou.mybatisplus.core.toolkit.Wrappers;
9 -import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
10 -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
11 -import com.apple.erp.entity.DeliveryMain;
12 -import com.apple.erp.mapper.DeliveryMainMapper;
13 -import com.apple.erp.service.DeliveryMainService;
14 -import lombok.extern.slf4j.Slf4j;
15 -import org.springframework.beans.BeanUtils;
16 -import org.springframework.beans.factory.annotation.Autowired;
17 -import org.springframework.stereotype.Service;
18 -import org.springframework.transaction.annotation.Transactional;
19 -import org.springframework.util.StringUtils;
20 -
21 -import java.time.LocalDateTime;
22 -import java.time.format.DateTimeFormatter;
23 -import java.util.List;
24 -import java.util.stream.Collectors;
25 -
26 -/**
27 - * 出库主表Service实现类
28 - *
29 - * @author Apple ERP System
30 - * @since 2025-01-01
31 - */
32 -@Slf4j
33 -@Service
34 -public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, DeliveryMain> implements DeliveryMainService {
35 -
36 - @Autowired
37 - private DeliveryItemMapper deliveryItemMapper;
38 -
39 - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
40 -
41 - @Override
42 - public Page<DeliveryRes> getDeliveryList(DeliveryQueryReq queryReq) {
43 - LambdaQueryWrapper<DeliveryMain> queryWrapper = Wrappers.lambdaQuery(DeliveryMain.class)
44 - .eq(StringUtils.hasText(queryReq.getDeliveryNo()), DeliveryMain::getDeliveryNo, queryReq.getDeliveryNo())
45 - .eq(StringUtils.hasText(queryReq.getDealerCode()), DeliveryMain::getDealerCode, queryReq.getDealerCode())
46 - .like(StringUtils.hasText(queryReq.getDealerName()), DeliveryMain::getDealerName, queryReq.getDealerName())
47 - .eq(StringUtils.hasText(queryReq.getOrderNo()), DeliveryMain::getOrderNo, queryReq.getOrderNo())
48 - .eq(queryReq.getDeliveryStatus() != null, DeliveryMain::getDeliveryStatus, queryReq.getDeliveryStatus())
49 - .eq(StringUtils.hasText(queryReq.getWarehouseCode()), DeliveryMain::getWarehouseCode, queryReq.getWarehouseCode())
50 - .eq(StringUtils.hasText(queryReq.getDataSource()), DeliveryMain::getDataSource, queryReq.getDataSource())
51 - .ge(StringUtils.hasText(queryReq.getDeliveryStartDate()), DeliveryMain::getDeliveryDate, parseDateTime(queryReq.getDeliveryStartDate()))
52 - .le(StringUtils.hasText(queryReq.getDeliveryEndDate()), DeliveryMain::getDeliveryDate, parseDateTime(queryReq.getDeliveryEndDate()))
53 - .eq(DeliveryMain::getDelFlag, "0")
54 - .orderByDesc(DeliveryMain::getCreateTime);
55 -
56 - Page<DeliveryMain> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize());
57 - IPage<DeliveryMain> deliveryMainPage = baseMapper.selectPage(page, queryWrapper);
58 -
59 - List<DeliveryRes> deliveryResList = deliveryMainPage.getRecords().stream().map(deliveryMain -> {
60 - DeliveryRes deliveryRes = new DeliveryRes();
61 - BeanUtils.copyProperties(deliveryMain, deliveryRes);
62 - // 查找出库明细
63 - LambdaQueryWrapper<DeliveryItem> itemQueryWrapper = Wrappers.lambdaQuery(DeliveryItem.class)
64 - .eq(DeliveryItem::getDeliveryId, deliveryMain.getDeliveryId());
65 - List<DeliveryItem> deliveryItems = deliveryItemMapper.selectList(itemQueryWrapper);
66 - List<DeliveryItemRes> itemResList = deliveryItems.stream().map(deliveryItem -> {
67 - DeliveryItemRes itemRes = new DeliveryItemRes();
68 - BeanUtils.copyProperties(deliveryItem, itemRes);
69 - return itemRes;
70 - }).collect(Collectors.toList());
71 - deliveryRes.setDeliveryItems(itemResList);
72 - return deliveryRes;
73 - }).collect(Collectors.toList());
74 -
75 - Page<DeliveryRes> resultPage = new Page<>(deliveryMainPage.getCurrent(), deliveryMainPage.getSize(), deliveryMainPage.getTotal());
76 - resultPage.setRecords(deliveryResList);
77 - return resultPage;
78 - }
79 -
80 - @Override
81 - public DeliveryRes getDeliveryDetail(Long deliveryId) {
82 - DeliveryMain deliveryMain = baseMapper.selectById(deliveryId);
83 - if (deliveryMain == null || "2".equals(deliveryMain.getDelFlag())) {
84 - return null;
85 - }
86 -
87 - DeliveryRes deliveryRes = new DeliveryRes();
88 - BeanUtils.copyProperties(deliveryMain, deliveryRes);
89 -
90 - LambdaQueryWrapper<DeliveryItem> itemQueryWrapper = Wrappers.lambdaQuery(DeliveryItem.class)
91 - .eq(DeliveryItem::getDeliveryId, deliveryId);
92 - List<DeliveryItem> deliveryItems = deliveryItemMapper.selectList(itemQueryWrapper);
93 - List<DeliveryItemRes> itemResList = deliveryItems.stream().map(deliveryItem -> {
94 - DeliveryItemRes itemRes = new DeliveryItemRes();
95 - BeanUtils.copyProperties(deliveryItem, itemRes);
96 - return itemRes;
97 - }).collect(Collectors.toList());
98 - deliveryRes.setDeliveryItems(itemResList);
99 - return deliveryRes;
100 - }
101 -
102 - @Override
103 - @Transactional
104 - public boolean addDelivery(DeliveryAddReq addReq) {
105 - // 检查出库单编号是否已存在
106 - if (isDeliveryNoExist(addReq.getDeliveryNo(), null)) {
107 - throw new IllegalArgumentException("出库单编号已存在");
108 - }
109 -
110 - DeliveryMain deliveryMain = new DeliveryMain();
111 - BeanUtils.copyProperties(addReq, deliveryMain);
112 - deliveryMain.setCreateTime(LocalDateTime.now());
113 - deliveryMain.setUpdateTime(LocalDateTime.now());
114 - if(StringUtils.hasText(addReq.getDeliveryDate())) {
115 - deliveryMain.setDeliveryDate(parseDateTime(addReq.getDeliveryDate()));
116 - }
117 - deliveryMain.setDelFlag("0");
118 - int insert = baseMapper.insert(deliveryMain);
119 - if (insert > 0) {
120 - for (DeliveryItemAddReq itemAddReq : addReq.getDeliveryItems()) {
121 - DeliveryItem deliveryItem = new DeliveryItem();
122 - BeanUtils.copyProperties(itemAddReq, deliveryItem);
123 - deliveryItem.setDeliveryId(deliveryMain.getDeliveryId());
124 - deliveryItem.setDeliveryNo(deliveryMain.getDeliveryNo());
125 - deliveryItem.setOrderNo(deliveryMain.getOrderNo());
126 - deliveryItem.setCreateTime(LocalDateTime.now());
127 - deliveryItem.setUpdateTime(LocalDateTime.now());
128 - deliveryItem.setDelFlag("0");
129 - deliveryItemMapper.insert(deliveryItem);
130 - }
131 - return true;
132 - }
133 - return false;
134 - }
135 -
136 - @Override
137 - @Transactional
138 - public boolean updateDelivery(DeliveryUpdateReq updateReq) {
139 - // 检查出库单编号是否已存在且不属于当前出库单
140 - if (isDeliveryNoExist(updateReq.getDeliveryNo(), updateReq.getDeliveryId())) {
141 - throw new IllegalArgumentException("出库单编号已存在");
142 - }
143 -
144 - DeliveryMain deliveryMain = new DeliveryMain();
145 - BeanUtils.copyProperties(updateReq, deliveryMain);
146 - deliveryMain.setUpdateTime(LocalDateTime.now());
147 - int update = baseMapper.updateById(deliveryMain);
148 - if (update > 0) {
149 - // 先删除旧的出库明细
150 - LambdaQueryWrapper<DeliveryItem> deleteWrapper = Wrappers.lambdaQuery(DeliveryItem.class)
151 - .eq(DeliveryItem::getDeliveryId, updateReq.getDeliveryId());
152 - deliveryItemMapper.delete(deleteWrapper);
153 -
154 - // 插入新的出库明细
155 - for (DeliveryItemUpdateReq itemUpdateReq : updateReq.getDeliveryItems()) {
156 - DeliveryItem deliveryItem = new DeliveryItem();
157 - BeanUtils.copyProperties(itemUpdateReq, deliveryItem);
158 - deliveryItem.setDeliveryId(updateReq.getDeliveryId());
159 - deliveryItem.setDeliveryNo(updateReq.getDeliveryNo());
160 - deliveryItem.setOrderNo(updateReq.getOrderNo());
161 - deliveryItem.setCreateTime(LocalDateTime.now()); // 新增明细时设置创建时间
162 - deliveryItem.setUpdateTime(LocalDateTime.now());
163 - deliveryItem.setDelFlag("0");
164 - deliveryItemMapper.insert(deliveryItem);
165 - }
166 - return true;
167 - }
168 - return false;
169 - }
170 -
171 - @Override
172 - @Transactional
173 - public boolean deleteDelivery(Long deliveryId) {
174 - DeliveryMain deliveryMain = new DeliveryMain();
175 - deliveryMain.setDeliveryId(deliveryId);
176 - deliveryMain.setDelFlag("2"); // 逻辑删除
177 - deliveryMain.setUpdateTime(LocalDateTime.now());
178 - return baseMapper.updateById(deliveryMain) > 0;
179 - }
180 -
181 - @Override
182 - @Transactional
183 - public boolean batchDeleteDeliveries(List<Long> deliveryIds) {
184 - List<DeliveryMain> deliveriesToUpdate = deliveryIds.stream().map(deliveryId -> {
185 - DeliveryMain deliveryMain = new DeliveryMain();
186 - deliveryMain.setDeliveryId(deliveryId);
187 - deliveryMain.setDelFlag("2");
188 - deliveryMain.setUpdateTime(LocalDateTime.now());
189 - return deliveryMain;
190 - }).collect(Collectors.toList());
191 - return updateBatchById(deliveriesToUpdate);
192 - }
193 -
194 - @Override
195 - public boolean updateDeliveryStatus(Long deliveryId, Integer deliveryStatus) {
196 - DeliveryMain deliveryMain = new DeliveryMain();
197 - deliveryMain.setDeliveryId(deliveryId);
198 - deliveryMain.setDeliveryStatus(deliveryStatus);
199 - deliveryMain.setUpdateTime(LocalDateTime.now());
200 - return baseMapper.updateById(deliveryMain) > 0;
201 - }
202 -
203 - /**
204 - * 检查出库单编号是否已存在
205 - * @param deliveryNo 出库单编号
206 - * @param excludeDeliveryId 排除的出库单ID(用于修改时)
207 - * @return 是否存在
208 - */
209 - private boolean isDeliveryNoExist(String deliveryNo, Long excludeDeliveryId) {
210 - LambdaQueryWrapper<DeliveryMain> queryWrapper = Wrappers.lambdaQuery(DeliveryMain.class)
211 - .eq(DeliveryMain::getDeliveryNo, deliveryNo)
212 - .eq(DeliveryMain::getDelFlag, "0");
213 - if (excludeDeliveryId != null) {
214 - queryWrapper.ne(DeliveryMain::getDeliveryId, excludeDeliveryId);
215 - }
216 - return baseMapper.selectCount(queryWrapper) > 0;
217 - }
218 -
219 - private LocalDateTime parseDateTime(String dateTimeStr) {
220 - if (!StringUtils.hasText(dateTimeStr)) {
221 - return null;
222 - }
223 - try {
224 - return LocalDateTime.parse(dateTimeStr, DATE_TIME_FORMATTER);
225 - } catch (Exception e) {
226 - log.warn("日期时间解析失败: {}", dateTimeStr, e);
227 - return null;
228 - }
229 - }
230 -}
231 -
232 1
......
...@@ -3,6 +3,7 @@ package com.apple.erp.service.impl; ...@@ -3,6 +3,7 @@ package com.apple.erp.service.impl;
3 import com.apple.erp.dto.ProductAddReq; 3 import com.apple.erp.dto.ProductAddReq;
4 import com.apple.erp.dto.ProductQueryReq; 4 import com.apple.erp.dto.ProductQueryReq;
5 import com.apple.erp.dto.ProductUpdateReq; 5 import com.apple.erp.dto.ProductUpdateReq;
6 +import com.apple.erp.dto.ProductSimpleRes;
6 import com.apple.erp.entity.ProductInfo; 7 import com.apple.erp.entity.ProductInfo;
7 import com.apple.erp.mapper.ProductInfoMapper; 8 import com.apple.erp.mapper.ProductInfoMapper;
8 import com.apple.erp.service.ProductInfoService; 9 import com.apple.erp.service.ProductInfoService;
...@@ -192,4 +193,40 @@ public class ProductInfoServiceImpl extends ServiceImpl<ProductInfoMapper, Produ ...@@ -192,4 +193,40 @@ public class ProductInfoServiceImpl extends ServiceImpl<ProductInfoMapper, Produ
192 193
193 return queryWrapper; 194 return queryWrapper;
194 } 195 }
196 +
197 + @Override
198 + public List<ProductSimpleRes> getAllProducts() {
199 + LambdaQueryWrapper<ProductInfo> queryWrapper = new LambdaQueryWrapper<>();
200 + queryWrapper.eq(ProductInfo::getDelFlag, "0") // 未删除
201 + .eq(ProductInfo::getSaleStatus, 1) // 在售状态
202 + .orderByAsc(ProductInfo::getProductCode); // 按产品编码排序
203 +
204 + List<ProductInfo> products = list(queryWrapper);
205 + return products.stream()
206 + .map(product -> {
207 + ProductSimpleRes res = new ProductSimpleRes();
208 + res.setProductId(product.getProductId());
209 + res.setProductCode(product.getProductCode());
210 +
211 + // 拼接产品名称:产品名称|产品型号|产品类别|产品颜色
212 + StringBuilder productNameBuilder = new StringBuilder();
213 + productNameBuilder.append(product.getProductName() != null ? product.getProductName() : "");
214 +
215 + if (product.getProductModel() != null && !product.getProductModel().trim().isEmpty()) {
216 + productNameBuilder.append("|").append(product.getProductModel());
217 + }
218 +
219 + if (product.getProductType() != null && !product.getProductType().trim().isEmpty()) {
220 + productNameBuilder.append("|").append(product.getProductType());
221 + }
222 +
223 + if (product.getColor() != null && !product.getColor().trim().isEmpty()) {
224 + productNameBuilder.append("|").append(product.getColor());
225 + }
226 +
227 + res.setProductName(productNameBuilder.toString());
228 + return res;
229 + })
230 + .collect(java.util.stream.Collectors.toList());
231 + }
195 } 232 }
......
...@@ -17,8 +17,8 @@ spring: ...@@ -17,8 +17,8 @@ spring:
17 type: com.alibaba.druid.pool.DruidDataSource 17 type: com.alibaba.druid.pool.DruidDataSource
18 driver-class-name: com.mysql.cj.jdbc.Driver 18 driver-class-name: com.mysql.cj.jdbc.Driver
19 url: jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true 19 url: jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
20 - username: apple_erp_app 20 + username: root
21 - password: StrongPassw0rd! 21 + password: MySQL@123
22 druid: 22 druid:
23 # 初始连接数 23 # 初始连接数
24 initial-size: 5 24 initial-size: 5
...@@ -70,7 +70,7 @@ spring: ...@@ -70,7 +70,7 @@ spring:
70 redis: 70 redis:
71 host: localhost 71 host: localhost
72 port: 6379 72 port: 6379
73 - password: 123456 73 + password:
74 database: 0 74 database: 0
75 timeout: 10000ms 75 timeout: 10000ms
76 lettuce: 76 lettuce:
......
...@@ -7,10 +7,30 @@ export {} ...@@ -7,10 +7,30 @@ export {}
7 7
8 declare module 'vue' { 8 declare module 'vue' {
9 export interface GlobalComponents { 9 export interface GlobalComponents {
10 + ElButton: typeof import('element-plus/es')['ElButton']
11 + ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
12 + ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
13 + ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
14 + ElDialog: typeof import('element-plus/es')['ElDialog']
15 + ElForm: typeof import('element-plus/es')['ElForm']
16 + ElFormItem: typeof import('element-plus/es')['ElFormItem']
17 + ElInput: typeof import('element-plus/es')['ElInput']
18 + ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
19 + ElOption: typeof import('element-plus/es')['ElOption']
20 + ElPagination: typeof import('element-plus/es')['ElPagination']
21 + ElRadio: typeof import('element-plus/es')['ElRadio']
22 + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
23 + ElSelect: typeof import('element-plus/es')['ElSelect']
24 + ElTable: typeof import('element-plus/es')['ElTable']
25 + ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
26 + ElTag: typeof import('element-plus/es')['ElTag']
10 Header: typeof import('./src/components/layout/Header.vue')['default'] 27 Header: typeof import('./src/components/layout/Header.vue')['default']
11 RouterLink: typeof import('vue-router')['RouterLink'] 28 RouterLink: typeof import('vue-router')['RouterLink']
12 RouterView: typeof import('vue-router')['RouterView'] 29 RouterView: typeof import('vue-router')['RouterView']
13 Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default'] 30 Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default']
14 SidebarItem: typeof import('./src/components/layout/SidebarItem.vue')['default'] 31 SidebarItem: typeof import('./src/components/layout/SidebarItem.vue')['default']
15 } 32 }
33 + export interface ComponentCustomProperties {
34 + vLoading: typeof import('element-plus/es')['ElLoadingDirective']
35 + }
16 } 36 }
......
...@@ -162,5 +162,10 @@ export const dealerApi = { ...@@ -162,5 +162,10 @@ export const dealerApi = {
162 return api.post<ApiResponse>(`/dealer/${dealerId}/auditStatus`, null, { 162 return api.post<ApiResponse>(`/dealer/${dealerId}/auditStatus`, null, {
163 params: { qualificationAuditStatus, auditOpinion } 163 params: { qualificationAuditStatus, auditOpinion }
164 }) 164 })
165 + },
166 +
167 + // 获取所有经销商(用于下拉选择)
168 + getAllDealers: () => {
169 + return api.get<ApiResponse<DealerInfo[]>>('/dealer/all')
165 } 170 }
166 } 171 }
......
...@@ -55,6 +55,13 @@ export interface ProductInfo { ...@@ -55,6 +55,13 @@ export interface ProductInfo {
55 updateTime: string 55 updateTime: string
56 } 56 }
57 57
58 +// 产品简单信息接口(用于下拉选择)
59 +export interface ProductSimpleRes {
60 + productId: number
61 + productCode: string
62 + productName: string
63 +}
64 +
58 // 产品查询请求参数 65 // 产品查询请求参数
59 export interface ProductQueryReq { 66 export interface ProductQueryReq {
60 productCode?: string 67 productCode?: string
...@@ -157,5 +164,10 @@ export const productApi = { ...@@ -157,5 +164,10 @@ export const productApi = {
157 // 修改返利标识 164 // 修改返利标识
158 updateRebateFlag: (productId: number, rebateFlag: number) => { 165 updateRebateFlag: (productId: number, rebateFlag: number) => {
159 return api.post(`/product/${productId}/rebate`, null, { params: { rebateFlag } }) 166 return api.post(`/product/${productId}/rebate`, null, { params: { rebateFlag } })
167 + },
168 +
169 + // 获取所有产品(用于下拉选择)
170 + getAllProducts: () => {
171 + return api.get<ApiResponse<ProductSimpleRes[]>>('/product/all')
160 } 172 }
161 } 173 }
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
4 <div class="search-section"> 4 <div class="search-section">
5 <div class="search-row"> 5 <div class="search-row">
6 <div class="search-item"> 6 <div class="search-item">
7 - <label>单编号:</label> 7 + <label>出库单编号:</label>
8 <input 8 <input
9 - v-model="searchParams.orderNo" 9 + v-model="searchParams.deliveryNo"
10 class="search-input" 10 class="search-input"
11 - placeholder="请输入单编号" 11 + placeholder="请输入出库单编号"
12 /> 12 />
13 </div> 13 </div>
14 <div class="search-item"> 14 <div class="search-item">
...@@ -38,17 +38,17 @@ ...@@ -38,17 +38,17 @@
38 </div> 38 </div>
39 <div class="search-row"> 39 <div class="search-row">
40 <div class="search-item"> 40 <div class="search-item">
41 - <label>开始日期:</label> 41 + <label>出库开始日期:</label>
42 <input 42 <input
43 - v-model="searchParams.startDate" 43 + v-model="searchParams.deliveryStartDate"
44 class="search-input" 44 class="search-input"
45 type="date" 45 type="date"
46 /> 46 />
47 </div> 47 </div>
48 <div class="search-item"> 48 <div class="search-item">
49 - <label>结束日期:</label> 49 + <label>出库结束日期:</label>
50 <input 50 <input
51 - v-model="searchParams.endDate" 51 + v-model="searchParams.deliveryEndDate"
52 class="search-input" 52 class="search-input"
53 type="date" 53 type="date"
54 /> 54 />
...@@ -113,6 +113,7 @@ ...@@ -113,6 +113,7 @@
113 <td>{{ formatTime(delivery.createTime || '') }}</td> 113 <td>{{ formatTime(delivery.createTime || '') }}</td>
114 <td class="action-col"> 114 <td class="action-col">
115 <button @click="handleView(delivery)" class="view-btn">👁️ 查看</button> 115 <button @click="handleView(delivery)" class="view-btn">👁️ 查看</button>
116 + <button @click="handleEdit(delivery)" class="edit-btn">✏️ 编辑</button>
116 <button @click="handleDelete(delivery)" class="delete-btn">🗑️ 删除</button> 117 <button @click="handleDelete(delivery)" class="delete-btn">🗑️ 删除</button>
117 </td> 118 </td>
118 </tr> 119 </tr>
...@@ -179,15 +180,11 @@ ...@@ -179,15 +180,11 @@
179 <div class="detail-grid"> 180 <div class="detail-grid">
180 <div class="detail-item"> 181 <div class="detail-item">
181 <label>出库单ID:</label> 182 <label>出库单ID:</label>
182 - <span>{{ orderDetail.deliveryId }}</span> 183 + <span>{{ deliveryDetail.deliveryId }}</span>
183 </div> 184 </div>
184 <div class="detail-item"> 185 <div class="detail-item">
185 <label>出库单编号:</label> 186 <label>出库单编号:</label>
186 - <span>{{ orderDetail.deliveryNo }}</span> 187 + <span>{{ deliveryDetail.deliveryNo }}</span>
187 - </div>
188 - <div class="detail-item">
189 - <label>关联订单编号:</label>
190 - <span>{{ orderDetail.orderNo }}</span>
191 </div> 188 </div>
192 <div class="detail-item"> 189 <div class="detail-item">
193 <label>关联订单编号:</label> 190 <label>关联订单编号:</label>
...@@ -203,7 +200,7 @@ ...@@ -203,7 +200,7 @@
203 </div> 200 </div>
204 <div class="detail-item"> 201 <div class="detail-item">
205 <label>出库日期:</label> 202 <label>出库日期:</label>
206 - <span>{{ formatDate(orderDetail.deliveryDate) }}</span> 203 + <span>{{ formatDate(deliveryDetail.deliveryDate) }}</span>
207 </div> 204 </div>
208 <div class="detail-item"> 205 <div class="detail-item">
209 <label>出库状态:</label> 206 <label>出库状态:</label>
...@@ -213,43 +210,43 @@ ...@@ -213,43 +210,43 @@
213 </div> 210 </div>
214 <div class="detail-item"> 211 <div class="detail-item">
215 <label>仓库编码:</label> 212 <label>仓库编码:</label>
216 - <span>{{ orderDetail.warehouseCode }}</span> 213 + <span>{{ deliveryDetail.warehouseCode }}</span>
217 </div> 214 </div>
218 <div class="detail-item"> 215 <div class="detail-item">
219 <label>数据来源:</label> 216 <label>数据来源:</label>
220 - <span>{{ orderDetail.dataSource || '-' }}</span> 217 + <span>{{ deliveryDetail.dataSource || '-' }}</span>
221 </div> 218 </div>
222 <div class="detail-item"> 219 <div class="detail-item">
223 <label>上传时间:</label> 220 <label>上传时间:</label>
224 - <span>{{ formatTime(orderDetail.uploadTime || '') }}</span> 221 + <span>{{ formatTime(deliveryDetail.uploadTime || '') }}</span>
225 </div> 222 </div>
226 <div class="detail-item"> 223 <div class="detail-item">
227 <label>创建时间:</label> 224 <label>创建时间:</label>
228 - <span>{{ formatTime(orderDetail.createTime || '') }}</span> 225 + <span>{{ formatTime(deliveryDetail.createTime || '') }}</span>
229 </div> 226 </div>
230 <div class="detail-item"> 227 <div class="detail-item">
231 <label>更新时间:</label> 228 <label>更新时间:</label>
232 - <span>{{ formatTime(orderDetail.updateTime || '') }}</span> 229 + <span>{{ formatTime(deliveryDetail.updateTime || '') }}</span>
233 </div> 230 </div>
234 </div> 231 </div>
235 </div> 232 </div>
236 233
237 - <div class="detail-section" v-if="orderDetail.deliveryItems && orderDetail.deliveryItems.length > 0"> 234 + <div class="detail-section" v-if="deliveryDetail.deliveryItems && deliveryDetail.deliveryItems.length > 0">
238 <h4>出库明细</h4> 235 <h4>出库明细</h4>
239 <table class="detail-table"> 236 <table class="detail-table">
240 <thead> 237 <thead>
241 <tr> 238 <tr>
242 - <th>产品编码</th>
243 <th>产品名称</th> 239 <th>产品名称</th>
240 + <th>产品编码</th>
244 <th>出库数量</th> 241 <th>出库数量</th>
245 <th>出库单价</th> 242 <th>出库单价</th>
246 <th>出库金额</th> 243 <th>出库金额</th>
247 </tr> 244 </tr>
248 </thead> 245 </thead>
249 <tbody> 246 <tbody>
250 - <tr v-for="item in orderDetail.deliveryItems" :key="item.deliveryItemId"> 247 + <tr v-for="item in deliveryDetail.deliveryItems" :key="item.deliveryItemId">
251 - <td>{{ item.productCode }}</td>
252 <td>{{ item.productName }}</td> 248 <td>{{ item.productName }}</td>
249 + <td>{{ item.productCode }}</td>
253 <td>{{ item.deliveryQty }}</td> 250 <td>{{ item.deliveryQty }}</td>
254 <td>{{ formatCurrency(item.deliveryPrice) }}</td> 251 <td>{{ formatCurrency(item.deliveryPrice) }}</td>
255 <td>{{ formatCurrency(item.deliveryAmount) }}</td> 252 <td>{{ formatCurrency(item.deliveryAmount) }}</td>
...@@ -262,15 +259,15 @@ ...@@ -262,15 +259,15 @@
262 </div> 259 </div>
263 </div> 260 </div>
264 261
265 - <!-- 新增出库对话框 --> 262 + <!-- 编辑出库对话框 -->
266 - <div v-if="showAddDialog" class="dialog-overlay" @click="closeAddDialog"> 263 + <div v-if="showEditDialog" class="dialog-overlay" @click="closeEditDialog">
267 <div class="dialog-content large-dialog" @click.stop> 264 <div class="dialog-content large-dialog" @click.stop>
268 <div class="dialog-header"> 265 <div class="dialog-header">
269 - <h3>新增出库</h3> 266 + <h3>编辑出库</h3>
270 - <button @click="closeAddDialog" class="close-btn">×</button> 267 + <button @click="closeEditDialog" class="close-btn">×</button>
271 </div> 268 </div>
272 <div class="dialog-body"> 269 <div class="dialog-body">
273 - <form @submit.prevent="handleSubmitAdd"> 270 + <form @submit.prevent="handleSubmitEdit">
274 <!-- 出库基本信息 --> 271 <!-- 出库基本信息 -->
275 <div class="form-section"> 272 <div class="form-section">
276 <h4>出库基本信息</h4> 273 <h4>出库基本信息</h4>
...@@ -278,33 +275,215 @@ ...@@ -278,33 +275,215 @@
278 <div class="form-item"> 275 <div class="form-item">
279 <label class="required">出库单编号:</label> 276 <label class="required">出库单编号:</label>
280 <input 277 <input
281 - v-model="addFormData.deliveryNo" 278 + v-model="editFormData.deliveryNo"
282 class="form-input" 279 class="form-input"
283 placeholder="请输入出库单编号" 280 placeholder="请输入出库单编号"
284 required 281 required
285 /> 282 />
286 </div> 283 </div>
287 <div class="form-item"> 284 <div class="form-item">
288 - <label class="required">经销商编码:</label> 285 + <label class="required">经销商名称:</label>
286 + <select
287 + v-model="editFormData.dealerName"
288 + class="form-input"
289 + @change="handleEditDealerChange"
290 + required
291 + >
292 + <option value="">请选择经销商</option>
293 + <option
294 + v-for="dealer in dealerList"
295 + :key="dealer.dealerId"
296 + :value="dealer.dealerName"
297 + >
298 + {{ dealer.dealerName }}
299 + </option>
300 + </select>
301 + </div>
302 + </div>
303 + <div class="form-row">
304 + <div class="form-item">
305 + <label class="required">关联订单编号:</label>
289 <input 306 <input
290 - v-model="addFormData.dealerCode" 307 + v-model="editFormData.orderNo"
291 class="form-input" 308 class="form-input"
292 - placeholder="请输入经销商编码" 309 + placeholder="请输入关联订单编号"
310 + required
311 + />
312 + </div>
313 + <div class="form-item">
314 + <label class="required">出库日期:</label>
315 + <input
316 + v-model="editFormData.deliveryDate"
317 + class="form-input"
318 + type="datetime-local"
319 + step="1"
293 required 320 required
294 /> 321 />
295 </div> 322 </div>
296 </div> 323 </div>
297 <div class="form-row"> 324 <div class="form-row">
298 <div class="form-item"> 325 <div class="form-item">
299 - <label class="required">经销商名称:</label> 326 + <label>仓库编码:</label>
300 <input 327 <input
301 - v-model="addFormData.dealerName" 328 + v-model="editFormData.warehouseCode"
302 class="form-input" 329 class="form-input"
303 - placeholder="请输入经销商名称" 330 + placeholder="请输入仓库编码"
331 + />
332 + </div>
333 + <div class="form-item">
334 + <label>仓库名称:</label>
335 + <input
336 + v-model="editFormData.warehouseName"
337 + class="form-input"
338 + placeholder="请输入仓库名称"
339 + />
340 + </div>
341 + </div>
342 + <div class="form-row">
343 + <div class="form-item">
344 + <label>数据来源:</label>
345 + <input
346 + v-model="editFormData.dataSource"
347 + class="form-input"
348 + placeholder="请输入数据来源"
349 + />
350 + </div>
351 + <!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
352 + <input
353 + v-model="editFormData.dealerCode"
354 + type="hidden"
355 + />
356 + </div>
357 + </div>
358 +
359 + <!-- 出库明细 -->
360 + <div class="form-section">
361 + <h4>出库明细</h4>
362 + <div class="order-items-header">
363 + <button type="button" @click="addEditDeliveryItem" class="add-item-btn">+ 添加明细</button>
364 + </div>
365 + <div class="order-items-container">
366 + <div v-for="(item, index) in editFormData.deliveryItems" :key="index" class="order-item">
367 + <div class="item-row-single">
368 + <div class="form-item">
369 + <label class="required">产品名称:</label>
370 + <select
371 + v-model="item.productName"
372 + class="form-input"
373 + @change="handleEditProductChange(index)"
374 + required
375 + >
376 + <option value="">请选择产品</option>
377 + <option
378 + v-for="product in productList"
379 + :key="product.productId"
380 + :value="product.productName"
381 + >
382 + {{ product.productName }}
383 + </option>
384 + </select>
385 + </div>
386 + <div class="form-item">
387 + <label class="required">产品编码:</label>
388 + <input
389 + v-model="item.productCode"
390 + class="form-input readonly"
391 + placeholder="自动填充"
392 + readonly
393 + required
394 + />
395 + </div>
396 + <div class="form-item">
397 + <label class="required">出库数量:</label>
398 + <input
399 + v-model.number="item.deliveryQty"
400 + class="form-input"
401 + type="number"
402 + placeholder="请输入出库数量"
403 + @input="calculateEditDeliveryItemTotal(index)"
404 + required
405 + />
406 + </div>
407 + <div class="form-item">
408 + <label class="required">出库单价:</label>
409 + <input
410 + v-model.number="item.deliveryPrice"
411 + class="form-input"
412 + type="number"
413 + step="0.01"
414 + placeholder="请输入出库单价"
415 + @input="calculateEditDeliveryItemTotal(index)"
416 + required
417 + />
418 + </div>
419 + <div class="form-item">
420 + <label>出库金额:</label>
421 + <input
422 + v-model.number="item.deliveryAmount"
423 + class="form-input"
424 + type="number"
425 + step="0.01"
426 + readonly
427 + />
428 + </div>
429 + <button type="button" @click="removeEditDeliveryItem(index)" class="remove-item-btn">删除</button>
430 + </div>
431 + </div>
432 + </div>
433 + </div>
434 +
435 + <div class="form-actions">
436 + <button type="button" @click="closeEditDialog" class="cancel-btn">取消</button>
437 + <button type="submit" class="submit-btn">确定</button>
438 + </div>
439 + </form>
440 + </div>
441 + </div>
442 + </div>
443 +
444 + <!-- 新增出库对话框 -->
445 + <div v-if="showAddDialog" class="dialog-overlay" @click="closeAddDialog">
446 + <div class="dialog-content large-dialog" @click.stop>
447 + <div class="dialog-header">
448 + <h3>新增出库</h3>
449 + <button @click="closeAddDialog" class="close-btn">×</button>
450 + </div>
451 + <div class="dialog-body">
452 + <form @submit.prevent="handleSubmitAdd">
453 + <!-- 出库基本信息 -->
454 + <div class="form-section">
455 + <h4>出库基本信息</h4>
456 + <div class="form-row">
457 + <div class="form-item">
458 + <label class="required">出库单编号:</label>
459 + <input
460 + v-model="addFormData.deliveryNo"
461 + class="form-input"
462 + placeholder="请输入出库单编号"
304 required 463 required
305 /> 464 />
306 </div> 465 </div>
307 <div class="form-item"> 466 <div class="form-item">
467 + <label class="required">经销商名称:</label>
468 + <select
469 + v-model="addFormData.dealerName"
470 + class="form-input"
471 + @change="handleAddDealerChange"
472 + required
473 + >
474 + <option value="">请选择经销商</option>
475 + <option
476 + v-for="dealer in dealerList"
477 + :key="dealer.dealerId"
478 + :value="dealer.dealerName"
479 + >
480 + {{ dealer.dealerName }}
481 + </option>
482 + </select>
483 + </div>
484 + </div>
485 + <div class="form-row">
486 + <div class="form-item">
308 <label class="required">关联订单编号:</label> 487 <label class="required">关联订单编号:</label>
309 <input 488 <input
310 v-model="addFormData.orderNo" 489 v-model="addFormData.orderNo"
...@@ -313,8 +492,6 @@ ...@@ -313,8 +492,6 @@
313 required 492 required
314 /> 493 />
315 </div> 494 </div>
316 - </div>
317 - <div class="form-row">
318 <div class="form-item"> 495 <div class="form-item">
319 <label class="required">出库日期:</label> 496 <label class="required">出库日期:</label>
320 <input 497 <input
...@@ -325,6 +502,8 @@ ...@@ -325,6 +502,8 @@
325 required 502 required
326 /> 503 />
327 </div> 504 </div>
505 + </div>
506 + <div class="form-row">
328 <div class="form-item"> 507 <div class="form-item">
329 <label>仓库编码:</label> 508 <label>仓库编码:</label>
330 <input 509 <input
...@@ -333,8 +512,6 @@ ...@@ -333,8 +512,6 @@
333 placeholder="请输入仓库编码" 512 placeholder="请输入仓库编码"
334 /> 513 />
335 </div> 514 </div>
336 - </div>
337 - <div class="form-row">
338 <div class="form-item"> 515 <div class="form-item">
339 <label>仓库名称:</label> 516 <label>仓库名称:</label>
340 <input 517 <input
...@@ -343,6 +520,8 @@ ...@@ -343,6 +520,8 @@
343 placeholder="请输入仓库名称" 520 placeholder="请输入仓库名称"
344 /> 521 />
345 </div> 522 </div>
523 + </div>
524 + <div class="form-row">
346 <div class="form-item"> 525 <div class="form-item">
347 <label>数据来源:</label> 526 <label>数据来源:</label>
348 <input 527 <input
...@@ -351,6 +530,11 @@ ...@@ -351,6 +530,11 @@
351 placeholder="请输入数据来源" 530 placeholder="请输入数据来源"
352 /> 531 />
353 </div> 532 </div>
533 + <!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
534 + <input
535 + v-model="addFormData.dealerCode"
536 + type="hidden"
537 + />
354 </div> 538 </div>
355 </div> 539 </div>
356 540
...@@ -364,20 +548,30 @@ ...@@ -364,20 +548,30 @@
364 <div v-for="(item, index) in addFormData.deliveryItems" :key="index" class="order-item"> 548 <div v-for="(item, index) in addFormData.deliveryItems" :key="index" class="order-item">
365 <div class="item-row-single"> 549 <div class="item-row-single">
366 <div class="form-item"> 550 <div class="form-item">
367 - <label class="required">产品编码:</label> 551 + <label class="required">产品名称:</label>
368 - <input 552 + <select
369 - v-model="item.productCode" 553 + v-model="item.productName"
370 class="form-input" 554 class="form-input"
371 - placeholder="请输入产品编码" 555 + @change="handleAddProductChange(index)"
372 required 556 required
373 - /> 557 + >
558 + <option value="">请选择产品</option>
559 + <option
560 + v-for="product in productList"
561 + :key="product.productId"
562 + :value="product.productName"
563 + >
564 + {{ product.productName }}
565 + </option>
566 + </select>
374 </div> 567 </div>
375 <div class="form-item"> 568 <div class="form-item">
376 - <label class="required">产品名称:</label> 569 + <label class="required">产品编码:</label>
377 <input 570 <input
378 - v-model="item.productName" 571 + v-model="item.productCode"
379 - class="form-input" 572 + class="form-input readonly"
380 - placeholder="请输入产品名称" 573 + placeholder="自动填充"
574 + readonly
381 required 575 required
382 /> 576 />
383 </div> 577 </div>
...@@ -434,12 +628,17 @@ ...@@ -434,12 +628,17 @@
434 <script setup lang="ts"> 628 <script setup lang="ts">
435 import { ref, reactive, computed, onMounted } from 'vue' 629 import { ref, reactive, computed, onMounted } from 'vue'
436 import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery' 630 import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery'
631 +import { dealerApi, type DealerInfo } from '../../api/dealer'
632 +import { productApi } from '../../api/product'
633 +import type { ProductSimpleRes } from '../../api/product'
437 634
438 // 响应式数据 635 // 响应式数据
439 const loading = ref(false) 636 const loading = ref(false)
440 const deliveryList = ref<DeliveryInfo[]>([]) 637 const deliveryList = ref<DeliveryInfo[]>([])
441 const showDetailDialog = ref(false) 638 const showDetailDialog = ref(false)
442 const deliveryDetail = ref<DeliveryInfo | null>(null) 639 const deliveryDetail = ref<DeliveryInfo | null>(null)
640 +const dealerList = ref<DealerInfo[]>([])
641 +const productList = ref<ProductSimpleRes[]>([])
443 642
444 // 新增出库相关数据 643 // 新增出库相关数据
445 const showAddDialog = ref(false) 644 const showAddDialog = ref(false)
...@@ -455,9 +654,23 @@ const addFormData = reactive({ ...@@ -455,9 +654,23 @@ const addFormData = reactive({
455 deliveryItems: [] as any[] 654 deliveryItems: [] as any[]
456 }) 655 })
457 656
657 +// 编辑出库相关数据
658 +const showEditDialog = ref(false)
659 +const editFormData = reactive({
660 + deliveryId: 0,
661 + deliveryNo: '',
662 + dealerCode: '',
663 + dealerName: '',
664 + orderNo: '',
665 + deliveryDate: '',
666 + warehouseCode: '',
667 + warehouseName: '',
668 + dataSource: '',
669 + deliveryItems: [] as any[]
670 +})
671 +
458 // 搜索参数 672 // 搜索参数
459 const searchParams = reactive({ 673 const searchParams = reactive({
460 - orderNo: '',
461 deliveryNo: '', 674 deliveryNo: '',
462 dealerCode: '', 675 dealerCode: '',
463 dealerName: '', 676 dealerName: '',
...@@ -466,8 +679,6 @@ const searchParams = reactive({ ...@@ -466,8 +679,6 @@ const searchParams = reactive({
466 dataSource: '', 679 dataSource: '',
467 deliveryStartDate: '', 680 deliveryStartDate: '',
468 deliveryEndDate: '', 681 deliveryEndDate: '',
469 - startDate: '',
470 - endDate: '',
471 pageNum: 1, 682 pageNum: 1,
472 pageSize: 10 683 pageSize: 10
473 }) 684 })
...@@ -539,7 +750,16 @@ const getPageNumbers = () => { ...@@ -539,7 +750,16 @@ const getPageNumbers = () => {
539 const fetchDeliveries = async () => { 750 const fetchDeliveries = async () => {
540 try { 751 try {
541 loading.value = true 752 loading.value = true
542 - const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize } 753 + // 处理日期格式,将日期转换为完整的日期时间格式
754 + const processedParams = { ...searchParams }
755 + if (processedParams.deliveryStartDate) {
756 + processedParams.deliveryStartDate = processedParams.deliveryStartDate + ' 00:00:00'
757 + }
758 + if (processedParams.deliveryEndDate) {
759 + processedParams.deliveryEndDate = processedParams.deliveryEndDate + ' 23:59:59'
760 + }
761 +
762 + const params = { ...processedParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
543 const response = await deliveryApi.getDeliveryList(params) as any 763 const response = await deliveryApi.getDeliveryList(params) as any
544 console.log('出库查询API响应:', response) 764 console.log('出库查询API响应:', response)
545 765
...@@ -569,12 +789,12 @@ const handleSearch = () => { ...@@ -569,12 +789,12 @@ const handleSearch = () => {
569 789
570 const handleReset = () => { 790 const handleReset = () => {
571 Object.assign(searchParams, { 791 Object.assign(searchParams, {
572 - orderNo: '', 792 + deliveryNo: '',
573 dealerCode: '', 793 dealerCode: '',
574 dealerName: '', 794 dealerName: '',
575 deliveryStatus: undefined, 795 deliveryStatus: undefined,
576 - startDate: '', 796 + deliveryStartDate: '',
577 - endDate: '' 797 + deliveryEndDate: ''
578 }) 798 })
579 pagination.pageNum = 1 799 pagination.pageNum = 1
580 fetchDeliveries() 800 fetchDeliveries()
...@@ -598,6 +818,42 @@ const handleView = async (delivery: DeliveryInfo) => { ...@@ -598,6 +818,42 @@ const handleView = async (delivery: DeliveryInfo) => {
598 } 818 }
599 } 819 }
600 820
821 +const handleEdit = async (delivery: DeliveryInfo) => {
822 + try {
823 + // 调用后端接口获取完整的出库详情(包括出库明细)
824 + const response = await deliveryApi.getDeliveryDetail(delivery.deliveryId) as any
825 + console.log('编辑出库API响应:', response)
826 +
827 + if (response && response.deliveryId) {
828 + // 处理日期时间格式,将后端返回的日期时间格式转换为前端datetime-local格式
829 + let formattedDeliveryDate = response.deliveryDate
830 + if (formattedDeliveryDate && formattedDeliveryDate.includes(' ')) {
831 + // 将 YYYY-MM-DD HH:MM:SS 格式转换为 YYYY-MM-DDTHH:MM:SS 格式
832 + formattedDeliveryDate = formattedDeliveryDate.replace(' ', 'T')
833 + }
834 +
835 + Object.assign(editFormData, {
836 + deliveryId: response.deliveryId,
837 + deliveryNo: response.deliveryNo,
838 + dealerCode: response.dealerCode,
839 + dealerName: response.dealerName,
840 + orderNo: response.orderNo,
841 + deliveryDate: formattedDeliveryDate,
842 + warehouseCode: response.warehouseCode || '',
843 + warehouseName: response.warehouseName || '',
844 + dataSource: response.dataSource || '',
845 + deliveryItems: response.deliveryItems || []
846 + })
847 + showEditDialog.value = true
848 + } else {
849 + showMessage('获取出库详情失败', 'error')
850 + }
851 + } catch (error) {
852 + console.error('获取出库详情失败:', error)
853 + showMessage('获取出库详情失败, 请重试', 'error')
854 + }
855 +}
856 +
601 const handleDelete = async (delivery: DeliveryInfo) => { 857 const handleDelete = async (delivery: DeliveryInfo) => {
602 if (confirm(`确定要删除出库单"${delivery.deliveryNo}"吗?`)) { 858 if (confirm(`确定要删除出库单"${delivery.deliveryNo}"吗?`)) {
603 try { 859 try {
...@@ -637,6 +893,40 @@ const closeAddDialog = () => { ...@@ -637,6 +893,40 @@ const closeAddDialog = () => {
637 showAddDialog.value = false 893 showAddDialog.value = false
638 } 894 }
639 895
896 +const closeEditDialog = () => {
897 + showEditDialog.value = false
898 +}
899 +
900 +// 经销商选择处理
901 +const handleAddDealerChange = () => {
902 + const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === addFormData.dealerName)
903 + if (selectedDealer) {
904 + addFormData.dealerCode = selectedDealer.dealerCode
905 + }
906 +}
907 +
908 +const handleEditDealerChange = () => {
909 + const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === editFormData.dealerName)
910 + if (selectedDealer) {
911 + editFormData.dealerCode = selectedDealer.dealerCode
912 + }
913 +}
914 +
915 +// 产品选择处理
916 +const handleAddProductChange = (index: number) => {
917 + const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === addFormData.deliveryItems[index].productName)
918 + if (selectedProduct) {
919 + addFormData.deliveryItems[index].productCode = selectedProduct.productCode
920 + }
921 +}
922 +
923 +const handleEditProductChange = (index: number) => {
924 + const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === editFormData.deliveryItems[index].productName)
925 + if (selectedProduct) {
926 + editFormData.deliveryItems[index].productCode = selectedProduct.productCode
927 + }
928 +}
929 +
640 const addDeliveryItem = () => { 930 const addDeliveryItem = () => {
641 addFormData.deliveryItems.push({ 931 addFormData.deliveryItems.push({
642 productCode: '', 932 productCode: '',
...@@ -651,6 +941,20 @@ const removeDeliveryItem = (index: number) => { ...@@ -651,6 +941,20 @@ const removeDeliveryItem = (index: number) => {
651 addFormData.deliveryItems.splice(index, 1) 941 addFormData.deliveryItems.splice(index, 1)
652 } 942 }
653 943
944 +const addEditDeliveryItem = () => {
945 + editFormData.deliveryItems.push({
946 + productCode: '',
947 + productName: '',
948 + deliveryQty: 1,
949 + deliveryPrice: 0,
950 + deliveryAmount: 0
951 + })
952 +}
953 +
954 +const removeEditDeliveryItem = (index: number) => {
955 + editFormData.deliveryItems.splice(index, 1)
956 +}
957 +
654 const calculateDeliveryItemTotal = (index: number) => { 958 const calculateDeliveryItemTotal = (index: number) => {
655 const item = addFormData.deliveryItems[index] 959 const item = addFormData.deliveryItems[index]
656 if (item.deliveryQty && item.deliveryPrice) { 960 if (item.deliveryQty && item.deliveryPrice) {
...@@ -658,6 +962,13 @@ const calculateDeliveryItemTotal = (index: number) => { ...@@ -658,6 +962,13 @@ const calculateDeliveryItemTotal = (index: number) => {
658 } 962 }
659 } 963 }
660 964
965 +const calculateEditDeliveryItemTotal = (index: number) => {
966 + const item = editFormData.deliveryItems[index]
967 + if (item.deliveryQty && item.deliveryPrice) {
968 + item.deliveryAmount = item.deliveryQty * item.deliveryPrice
969 + }
970 +}
971 +
661 const handleSubmitAdd = async () => { 972 const handleSubmitAdd = async () => {
662 try { 973 try {
663 // 处理日期时间格式,将datetime-local格式转换为后端需要的格式 974 // 处理日期时间格式,将datetime-local格式转换为后端需要的格式
...@@ -680,9 +991,9 @@ const handleSubmitAdd = async () => { ...@@ -680,9 +991,9 @@ const handleSubmitAdd = async () => {
680 deliveryItems: addFormData.deliveryItems.map(item => ({ 991 deliveryItems: addFormData.deliveryItems.map(item => ({
681 productCode: item.productCode, 992 productCode: item.productCode,
682 productName: item.productName, 993 productName: item.productName,
683 - productQty: item.deliveryQty, 994 + deliveryQty: item.deliveryQty,
684 - unitPrice: item.deliveryPrice, 995 + deliveryPrice: item.deliveryPrice,
685 - totalPrice: item.deliveryAmount 996 + deliveryAmount: item.deliveryAmount
686 })) 997 }))
687 } 998 }
688 999
...@@ -696,6 +1007,44 @@ const handleSubmitAdd = async () => { ...@@ -696,6 +1007,44 @@ const handleSubmitAdd = async () => {
696 } 1007 }
697 } 1008 }
698 1009
1010 +const handleSubmitEdit = async () => {
1011 + try {
1012 + // 处理日期时间格式,将datetime-local格式转换为后端需要的格式
1013 + let formattedDeliveryDate = editFormData.deliveryDate
1014 + if (formattedDeliveryDate && formattedDeliveryDate.includes('T')) {
1015 + // 将 YYYY-MM-DDTHH:MM:SS 格式转换为 YYYY-MM-DD HH:MM:SS 格式
1016 + formattedDeliveryDate = formattedDeliveryDate.replace('T', ' ')
1017 + }
1018 +
1019 + // 构造编辑出库的请求数据
1020 + const deliveryData = {
1021 + deliveryId: editFormData.deliveryId,
1022 + deliveryNo: editFormData.deliveryNo,
1023 + dealerCode: editFormData.dealerCode,
1024 + dealerName: editFormData.dealerName,
1025 + orderNo: editFormData.orderNo,
1026 + deliveryDate: formattedDeliveryDate,
1027 + warehouseCode: editFormData.warehouseCode,
1028 + dataSource: editFormData.dataSource || '系统录入',
1029 + deliveryItems: editFormData.deliveryItems.map(item => ({
1030 + productCode: item.productCode,
1031 + productName: item.productName,
1032 + deliveryQty: item.deliveryQty,
1033 + deliveryPrice: item.deliveryPrice,
1034 + deliveryAmount: item.deliveryAmount
1035 + }))
1036 + }
1037 +
1038 + await deliveryApi.updateDelivery(deliveryData)
1039 + showMessage('编辑出库成功', 'success')
1040 + closeEditDialog()
1041 + fetchDeliveries() // 刷新列表
1042 + } catch (error) {
1043 + console.error('编辑出库失败:', error)
1044 + showMessage('编辑出库失败, 请重试', 'error')
1045 + }
1046 +}
1047 +
699 1048
700 1049
701 const handlePageChange = (page: number) => { 1050 const handlePageChange = (page: number) => {
...@@ -731,9 +1080,37 @@ const closeDetailDialog = () => { ...@@ -731,9 +1080,37 @@ const closeDetailDialog = () => {
731 deliveryDetail.value = null 1080 deliveryDetail.value = null
732 } 1081 }
733 1082
1083 +// 加载经销商列表
1084 +const loadDealers = async () => {
1085 + try {
1086 + const response = await dealerApi.getAllDealers() as any
1087 + if (response && response.data && Array.isArray(response.data)) {
1088 + dealerList.value = response.data
1089 + }
1090 + } catch (error) {
1091 + console.error('加载经销商列表失败:', error)
1092 + showMessage('加载经销商列表失败', 'error')
1093 + }
1094 +}
1095 +
1096 +// 加载产品列表
1097 +const loadProducts = async () => {
1098 + try {
1099 + const response = await productApi.getAllProducts() as any
1100 + if (response && response.data && Array.isArray(response.data)) {
1101 + productList.value = response.data
1102 + }
1103 + } catch (error) {
1104 + console.error('加载产品列表失败:', error)
1105 + showMessage('加载产品列表失败', 'error')
1106 + }
1107 +}
1108 +
734 // 生命周期 1109 // 生命周期
735 onMounted(() => { 1110 onMounted(() => {
736 fetchDeliveries() 1111 fetchDeliveries()
1112 + loadDealers()
1113 + loadProducts()
737 }) 1114 })
738 </script> 1115 </script>
739 1116
...@@ -958,6 +1335,11 @@ onMounted(() => { ...@@ -958,6 +1335,11 @@ onMounted(() => {
958 color: white; 1335 color: white;
959 } 1336 }
960 1337
1338 +.edit-btn {
1339 + background: #ffc107;
1340 + color: #333;
1341 +}
1342 +
961 .delete-btn { 1343 .delete-btn {
962 background: #dc3545; 1344 background: #dc3545;
963 color: white; 1345 color: white;
...@@ -1256,6 +1638,23 @@ onMounted(() => { ...@@ -1256,6 +1638,23 @@ onMounted(() => {
1256 box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2); 1638 box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
1257 } 1639 }
1258 1640
1641 +.form-select {
1642 + padding: 6px 8px;
1643 + border: 1px solid #ddd;
1644 + border-radius: 4px;
1645 + font-size: 12px;
1646 + width: 100%;
1647 + box-sizing: border-box;
1648 + background-color: #fff;
1649 + cursor: pointer;
1650 +}
1651 +
1652 +.form-select:focus {
1653 + outline: none;
1654 + border-color: #007bff;
1655 + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
1656 +}
1657 +
1259 .form-actions { 1658 .form-actions {
1260 display: flex; 1659 display: flex;
1261 justify-content: flex-end; 1660 justify-content: flex-end;
...@@ -1337,13 +1736,16 @@ onMounted(() => { ...@@ -1337,13 +1736,16 @@ onMounted(() => {
1337 min-width: 0; 1736 min-width: 0;
1338 } 1737 }
1339 1738
1739 +/* 产品名称字段 - 占用最多空间(现在在第一位) */
1340 .order-item .form-item:nth-child(1) { 1740 .order-item .form-item:nth-child(1) {
1341 - flex: 0 0 120px; 1741 + flex: 1 1 180px !important;
1742 + min-width: 120px !important;
1743 + max-width: 200px;
1342 } 1744 }
1343 1745
1746 +/* 产品编码字段 - 固定宽度(现在在第二位) */
1344 .order-item .form-item:nth-child(2) { 1747 .order-item .form-item:nth-child(2) {
1345 - flex: 1 1 200px; 1748 + flex: 0 0 140px;
1346 - min-width: 150px;
1347 } 1749 }
1348 1750
1349 .order-item .form-item:nth-child(3) { 1751 .order-item .form-item:nth-child(3) {
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
5 <div class="search-row"> 5 <div class="search-row">
6 <div class="search-item"> 6 <div class="search-item">
7 <label>发票编号:</label> 7 <label>发票编号:</label>
8 - <input 8 + <input
9 - v-model="searchParams.invoiceNo" 9 + v-model="searchParams.invoiceNo"
10 - class="search-input" 10 + class="search-input"
11 placeholder="请输入发票编号" 11 placeholder="请输入发票编号"
12 /> 12 />
13 </div> 13 </div>
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
48 <div class="search-item"> 48 <div class="search-item">
49 <label>开票开始日期:</label> 49 <label>开票开始日期:</label>
50 <input 50 <input
51 - v-model="searchParams.invoiceStartDate" 51 + v-model="searchParams.invoiceStartDate"
52 class="search-input" 52 class="search-input"
53 type="date" 53 type="date"
54 /> 54 />
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
56 <div class="search-item"> 56 <div class="search-item">
57 <label>开票结束日期:</label> 57 <label>开票结束日期:</label>
58 <input 58 <input
59 - v-model="searchParams.invoiceEndDate" 59 + v-model="searchParams.invoiceEndDate"
60 class="search-input" 60 class="search-input"
61 type="date" 61 type="date"
62 /> 62 />
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
101 <th>开票日期</th> 101 <th>开票日期</th>
102 <th>发票金额</th> 102 <th>发票金额</th>
103 <th>开票状态</th> 103 <th>开票状态</th>
104 - <th>上传时间</th> 104 + <th>开票时间</th>
105 <th>关联订单编号</th> 105 <th>关联订单编号</th>
106 <th>操作</th> 106 <th>操作</th>
107 </tr> 107 </tr>
...@@ -187,20 +187,20 @@ ...@@ -187,20 +187,20 @@
187 <form @submit.prevent="handleSubmit"> 187 <form @submit.prevent="handleSubmit">
188 <div class="form-row"> 188 <div class="form-row">
189 <div class="form-item"> 189 <div class="form-item">
190 - <label class="required">订单编号:</label> 190 + <label class="required">发票号码:</label>
191 <input 191 <input
192 - v-model="formData.orderNo" 192 + v-model="formData.invoiceNo"
193 class="form-input" 193 class="form-input"
194 - placeholder="请输入订单编号" 194 + placeholder="请输入发票号码"
195 required 195 required
196 /> 196 />
197 </div> 197 </div>
198 <div class="form-item"> 198 <div class="form-item">
199 - <label class="required">发票号码:</label> 199 + <label class="required">订单编号:</label>
200 <input 200 <input
201 - v-model="formData.invoiceNo" 201 + v-model="formData.orderNo"
202 class="form-input" 202 class="form-input"
203 - placeholder="请输入发票号码" 203 + placeholder="请输入订单编号"
204 required 204 required
205 /> 205 />
206 </div> 206 </div>
...@@ -215,27 +215,28 @@ ...@@ -215,27 +215,28 @@
215 /> 215 />
216 </div> 216 </div>
217 <div class="form-item"> 217 <div class="form-item">
218 - <label class="required">经销商编码:</label>
219 - <input
220 - v-model="formData.dealerCode"
221 - class="form-input"
222 - placeholder="请输入经销商编码"
223 - required
224 - />
225 - </div>
226 - </div>
227 - <div class="form-row">
228 - <div class="form-item">
229 <label class="required">经销商名称:</label> 218 <label class="required">经销商名称:</label>
230 - <input 219 + <select
231 v-model="formData.dealerName" 220 v-model="formData.dealerName"
232 class="form-input" 221 class="form-input"
233 - placeholder="请输入经销商名称" 222 + @change="handleDealerChange"
234 required 223 required
235 - /> 224 + >
225 + <option value="">请选择经销商</option>
226 + <option
227 + v-for="dealer in dealerList"
228 + :key="dealer.dealerId"
229 + :value="dealer.dealerName"
230 + >
231 + {{ dealer.dealerName }}
232 + </option>
233 + </select>
236 </div> 234 </div>
237 </div> 235 </div>
238 <div class="form-row"> 236 <div class="form-row">
237 + <input v-model="formData.dealerCode" type="hidden"/>
238 + </div>
239 + <div class="form-row">
239 <div class="form-item"> 240 <div class="form-item">
240 <label class="required">开票日期:</label> 241 <label class="required">开票日期:</label>
241 <input 242 <input
...@@ -292,6 +293,23 @@ ...@@ -292,6 +293,23 @@
292 <div class="invoice-items-container"> 293 <div class="invoice-items-container">
293 <div v-for="(item, index) in formData.invoiceItems" :key="index" class="invoice-item"> 294 <div v-for="(item, index) in formData.invoiceItems" :key="index" class="invoice-item">
294 <div class="item-row-single"> 295 <div class="item-row-single">
296 + <div class="form-item" style="flex: 1 1 200px; min-width: 150px;">
297 + <label>产品名称:</label>
298 + <select
299 + v-model="item.productName"
300 + class="form-input"
301 + @change="handleProductChange(index)"
302 + >
303 + <option value="">请选择产品</option>
304 + <option
305 + v-for="product in productList"
306 + :key="product.productId"
307 + :value="product.productName"
308 + >
309 + {{ product.productName }}
310 + </option>
311 + </select>
312 + </div>
295 <div class="form-item" style="flex: 0 0 120px;"> 313 <div class="form-item" style="flex: 0 0 120px;">
296 <label>产品编码:</label> 314 <label>产品编码:</label>
297 <input 315 <input
...@@ -300,14 +318,6 @@ ...@@ -300,14 +318,6 @@
300 placeholder="产品编码" 318 placeholder="产品编码"
301 /> 319 />
302 </div> 320 </div>
303 - <div class="form-item" style="flex: 1 1 200px; min-width: 150px;">
304 - <label>产品名称:</label>
305 - <input
306 - v-model="item.productName"
307 - class="form-input"
308 - placeholder="产品名称"
309 - />
310 - </div>
311 <div class="form-item" style="flex: 0 0 80px;"> 321 <div class="form-item" style="flex: 0 0 80px;">
312 <label>数量:</label> 322 <label>数量:</label>
313 <input 323 <input
...@@ -460,8 +470,8 @@ ...@@ -460,8 +470,8 @@
460 <table class="detail-table"> 470 <table class="detail-table">
461 <thead> 471 <thead>
462 <tr> 472 <tr>
463 - <th>产品编码</th>
464 <th>产品名称</th> 473 <th>产品名称</th>
474 + <th>产品编码</th>
465 <th>数量</th> 475 <th>数量</th>
466 <th>单价</th> 476 <th>单价</th>
467 <th>金额</th> 477 <th>金额</th>
...@@ -471,8 +481,8 @@ ...@@ -471,8 +481,8 @@
471 </thead> 481 </thead>
472 <tbody> 482 <tbody>
473 <tr v-for="item in invoiceDetail.invoiceItems" :key="item.invoiceItemId"> 483 <tr v-for="item in invoiceDetail.invoiceItems" :key="item.invoiceItemId">
474 - <td>{{ item.productCode }}</td>
475 <td>{{ item.productName }}</td> 484 <td>{{ item.productName }}</td>
485 + <td>{{ item.productCode }}</td>
476 <td>{{ item.invoiceQty }}</td> 486 <td>{{ item.invoiceQty }}</td>
477 <td>{{ formatCurrency(item.unitPriceNoTax) }}</td> 487 <td>{{ formatCurrency(item.unitPriceNoTax) }}</td>
478 <td class="amount-text">{{ formatCurrency(item.amountNoTax) }}</td> 488 <td class="amount-text">{{ formatCurrency(item.amountNoTax) }}</td>
...@@ -508,6 +518,9 @@ ...@@ -508,6 +518,9 @@
508 <script setup lang="ts"> 518 <script setup lang="ts">
509 import { ref, reactive, computed, onMounted } from 'vue' 519 import { ref, reactive, computed, onMounted } from 'vue'
510 import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice' 520 import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice'
521 +import { dealerApi, type DealerInfo } from '../../api/dealer'
522 +import { productApi } from '../../api/product'
523 +import type { ProductSimpleRes } from '../../api/product'
511 524
512 // 响应式数据 525 // 响应式数据
513 const loading = ref(false) 526 const loading = ref(false)
...@@ -516,6 +529,8 @@ const showDialog = ref(false) ...@@ -516,6 +529,8 @@ const showDialog = ref(false)
516 const showDetailDialog = ref(false) 529 const showDetailDialog = ref(false)
517 const isEdit = ref(false) 530 const isEdit = ref(false)
518 const invoiceDetail = ref<InvoiceInfo | null>(null) 531 const invoiceDetail = ref<InvoiceInfo | null>(null)
532 +const dealerList = ref<DealerInfo[]>([])
533 +const productList = ref<ProductSimpleRes[]>([])
519 534
520 // 搜索参数 535 // 搜索参数
521 const searchParams = reactive({ 536 const searchParams = reactive({
...@@ -950,6 +965,48 @@ const handleTableViewToggle = () => { ...@@ -950,6 +965,48 @@ const handleTableViewToggle = () => {
950 showMessage('视图切换功能开发中...', 'warning') 965 showMessage('视图切换功能开发中...', 'warning')
951 } 966 }
952 967
968 +// 加载经销商列表
969 +const loadDealers = async () => {
970 + try {
971 + const response = await dealerApi.getAllDealers() as any
972 + if (response && response.data && Array.isArray(response.data)) {
973 + dealerList.value = response.data
974 + }
975 + } catch (error) {
976 + console.error('加载经销商列表失败:', error)
977 + showMessage('加载经销商列表失败', 'error')
978 + }
979 +}
980 +
981 +// 经销商选择变化处理
982 +const handleDealerChange = () => {
983 + const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
984 + if (selectedDealer) {
985 + formData.dealerCode = selectedDealer.dealerCode
986 + }
987 +}
988 +
989 +// 加载产品列表
990 +const loadProducts = async () => {
991 + try {
992 + const response = await productApi.getAllProducts() as any
993 + if (response && response.data && Array.isArray(response.data)) {
994 + productList.value = response.data
995 + }
996 + } catch (error) {
997 + console.error('加载产品列表失败:', error)
998 + showMessage('加载产品列表失败', 'error')
999 + }
1000 +}
1001 +
1002 +// 产品选择变化处理
1003 +const handleProductChange = (index: number) => {
1004 + const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === formData.invoiceItems[index].productName)
1005 + if (selectedProduct) {
1006 + formData.invoiceItems[index].productCode = selectedProduct.productCode
1007 + }
1008 +}
1009 +
953 const closeDialog = () => { 1010 const closeDialog = () => {
954 showDialog.value = false 1011 showDialog.value = false
955 } 1012 }
...@@ -962,6 +1019,8 @@ const closeDetailDialog = () => { ...@@ -962,6 +1019,8 @@ const closeDetailDialog = () => {
962 // 生命周期 1019 // 生命周期
963 onMounted(() => { 1020 onMounted(() => {
964 fetchOrders() 1021 fetchOrders()
1022 + loadDealers()
1023 + loadProducts()
965 }) 1024 })
966 </script> 1025 </script>
967 1026
......
...@@ -235,25 +235,25 @@ ...@@ -235,25 +235,25 @@
235 /> 235 />
236 </div> 236 </div>
237 <div class="form-item"> 237 <div class="form-item">
238 - <label class="required">经销商编码:</label>
239 - <input
240 - v-model="formData.dealerCode"
241 - class="form-input"
242 - placeholder="请输入经销商编码"
243 - required
244 - />
245 - </div>
246 - </div>
247 - <div class="form-row">
248 - <div class="form-item">
249 <label class="required">经销商名称:</label> 238 <label class="required">经销商名称:</label>
250 - <input 239 + <select
251 v-model="formData.dealerName" 240 v-model="formData.dealerName"
252 - class="form-input" 241 + class="form-select"
253 - placeholder="请输入经销商名称" 242 + @change="handleDealerChange"
254 required 243 required
255 - /> 244 + >
245 + <option value="">请选择经销商</option>
246 + <option
247 + v-for="dealer in dealerList"
248 + :key="dealer.dealerId"
249 + :value="dealer.dealerName"
250 + >
251 + {{ dealer.dealerName }}
252 + </option>
253 + </select>
256 </div> 254 </div>
255 + </div>
256 + <div class="form-row">
257 <div class="form-item"> 257 <div class="form-item">
258 <label class="required">订单日期:</label> 258 <label class="required">订单日期:</label>
259 <input 259 <input
...@@ -263,19 +263,19 @@ ...@@ -263,19 +263,19 @@
263 required 263 required
264 /> 264 />
265 </div> 265 </div>
266 - </div>
267 - <div class="form-row">
268 <div class="form-item"> 266 <div class="form-item">
269 - <label class="required">订单总金额:</label> 267 + <label>订单总金额:</label>
270 <input 268 <input
271 v-model.number="formData.totalAmount" 269 v-model.number="formData.totalAmount"
272 - class="form-input" 270 + class="form-input readonly"
273 type="number" 271 type="number"
274 step="0.01" 272 step="0.01"
275 - placeholder="请输入订单总金额" 273 + placeholder="自动计算"
276 - required 274 + readonly
277 /> 275 />
278 </div> 276 </div>
277 + </div>
278 + <div class="form-row">
279 <div class="form-item"> 279 <div class="form-item">
280 <label>返利金额:</label> 280 <label>返利金额:</label>
281 <input 281 <input
...@@ -286,6 +286,11 @@ ...@@ -286,6 +286,11 @@
286 placeholder="请输入返利金额" 286 placeholder="请输入返利金额"
287 /> 287 />
288 </div> 288 </div>
289 + <!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
290 + <input
291 + v-model="formData.dealerCode"
292 + type="hidden"
293 + />
289 </div> 294 </div>
290 </div> 295 </div>
291 296
...@@ -297,25 +302,34 @@ ...@@ -297,25 +302,34 @@
297 </div> 302 </div>
298 <div class="order-items-container"> 303 <div class="order-items-container">
299 <div v-for="(item, index) in formData.orderItems" :key="index" class="order-item"> 304 <div v-for="(item, index) in formData.orderItems" :key="index" class="order-item">
300 - <div class="item-row-single"> 305 + <div class="item-row-single">
301 - <div class="form-item"> 306 + <div class="form-item">
302 - <label class="required">产品编码:</label> 307 + <label class="required">产品名称:</label>
303 - <input 308 + <select
304 - v-model="item.productCode" 309 + v-model="item.productName"
305 - class="form-input" 310 + class="form-select"
306 - placeholder="请输入产品编码" 311 + @change="handleProductChange(index)"
307 - required 312 + required
308 - /> 313 + >
309 - </div> 314 + <option value="">请选择产品</option>
310 - <div class="form-item"> 315 + <option
311 - <label class="required">产品名称:</label> 316 + v-for="product in productList"
312 - <input 317 + :key="product.productId"
313 - v-model="item.productName" 318 + :value="product.productName"
314 - class="form-input" 319 + >
315 - placeholder="请输入产品名称" 320 + {{ product.productName }}
316 - required 321 + </option>
317 - /> 322 + </select>
318 - </div> 323 + </div>
324 + <div class="form-item">
325 + <label class="required">产品编码:</label>
326 + <input
327 + v-model="item.productCode"
328 + class="form-input readonly"
329 + placeholder="自动填充"
330 + readonly
331 + />
332 + </div>
319 <div class="form-item"> 333 <div class="form-item">
320 <label class="required">商品数量:</label> 334 <label class="required">商品数量:</label>
321 <input 335 <input
...@@ -343,9 +357,10 @@ ...@@ -343,9 +357,10 @@
343 <label>明细金额:</label> 357 <label>明细金额:</label>
344 <input 358 <input
345 v-model.number="item.itemAmount" 359 v-model.number="item.itemAmount"
346 - class="form-input" 360 + class="form-input readonly"
347 type="number" 361 type="number"
348 step="0.01" 362 step="0.01"
363 + placeholder="自动计算"
349 readonly 364 readonly
350 /> 365 />
351 </div> 366 </div>
...@@ -420,8 +435,8 @@ ...@@ -420,8 +435,8 @@
420 <table class="detail-table"> 435 <table class="detail-table">
421 <thead> 436 <thead>
422 <tr> 437 <tr>
423 - <th>产品编码</th>
424 <th>产品名称</th> 438 <th>产品名称</th>
439 + <th>产品编码</th>
425 <th>商品数量</th> 440 <th>商品数量</th>
426 <th>单价</th> 441 <th>单价</th>
427 <th>明细金额</th> 442 <th>明细金额</th>
...@@ -429,8 +444,8 @@ ...@@ -429,8 +444,8 @@
429 </thead> 444 </thead>
430 <tbody> 445 <tbody>
431 <tr v-for="item in orderDetail.orderItems" :key="item.itemId"> 446 <tr v-for="item in orderDetail.orderItems" :key="item.itemId">
432 - <td>{{ item.productCode }}</td>
433 <td>{{ item.productName }}</td> 447 <td>{{ item.productName }}</td>
448 + <td>{{ item.productCode }}</td>
434 <td>{{ item.productQty }}</td> 449 <td>{{ item.productQty }}</td>
435 <td>{{ formatCurrency(item.unitPrice) }}</td> 450 <td>{{ formatCurrency(item.unitPrice) }}</td>
436 <td>{{ formatCurrency(item.itemAmount) }}</td> 451 <td>{{ formatCurrency(item.itemAmount) }}</td>
...@@ -448,6 +463,9 @@ ...@@ -448,6 +463,9 @@
448 <script setup lang="ts"> 463 <script setup lang="ts">
449 import { ref, reactive, computed, onMounted } from 'vue' 464 import { ref, reactive, computed, onMounted } from 'vue'
450 import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order' 465 import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order'
466 +import { dealerApi, type DealerInfo } from '../../api/dealer'
467 +import { productApi } from '../../api/product'
468 +import type { ProductSimpleRes } from '../../api/product'
451 469
452 // 响应式数据 470 // 响应式数据
453 const loading = ref(false) 471 const loading = ref(false)
...@@ -457,6 +475,8 @@ const showDialog = ref(false) ...@@ -457,6 +475,8 @@ const showDialog = ref(false)
457 const showDetailDialog = ref(false) 475 const showDetailDialog = ref(false)
458 const isEdit = ref(false) 476 const isEdit = ref(false)
459 const orderDetail = ref<OrderInfo | null>(null) 477 const orderDetail = ref<OrderInfo | null>(null)
478 +const dealerList = ref<DealerInfo[]>([])
479 +const productList = ref<ProductSimpleRes[]>([])
460 480
461 // 搜索参数 481 // 搜索参数
462 const searchParams = reactive<OrderQueryReq>({ 482 const searchParams = reactive<OrderQueryReq>({
...@@ -697,6 +717,15 @@ const handleEdit = async (order: OrderInfo) => { ...@@ -697,6 +717,15 @@ const handleEdit = async (order: OrderInfo) => {
697 verifyStatus: response.verifyStatus, 717 verifyStatus: response.verifyStatus,
698 orderItems: response.orderItems || [] 718 orderItems: response.orderItems || []
699 }) 719 })
720 + // 编辑时重新计算总金额,确保数据一致性
721 + calculateTotalAmount()
722 + // 编辑时设置经销商编码(如果经销商名称已选择)
723 + if (formData.dealerName) {
724 + const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
725 + if (selectedDealer) {
726 + formData.dealerCode = selectedDealer.dealerCode
727 + }
728 + }
700 showDialog.value = true 729 showDialog.value = true
701 } else { 730 } else {
702 showMessage('获取订单详情失败', 'error') 731 showMessage('获取订单详情失败', 'error')
...@@ -802,10 +831,14 @@ const addOrderItem = () => { ...@@ -802,10 +831,14 @@ const addOrderItem = () => {
802 unitPrice: 0, 831 unitPrice: 0,
803 itemAmount: 0 832 itemAmount: 0
804 }) 833 })
834 + // 重新计算总金额
835 + calculateTotalAmount()
805 } 836 }
806 837
807 const removeOrderItem = (index: number) => { 838 const removeOrderItem = (index: number) => {
808 formData.orderItems.splice(index, 1) 839 formData.orderItems.splice(index, 1)
840 + // 重新计算总金额
841 + calculateTotalAmount()
809 } 842 }
810 843
811 const calculateItemTotal = (index: number) => { 844 const calculateItemTotal = (index: number) => {
...@@ -813,6 +846,16 @@ const calculateItemTotal = (index: number) => { ...@@ -813,6 +846,16 @@ const calculateItemTotal = (index: number) => {
813 if (item.productQty && item.unitPrice) { 846 if (item.productQty && item.unitPrice) {
814 item.itemAmount = item.productQty * item.unitPrice 847 item.itemAmount = item.productQty * item.unitPrice
815 } 848 }
849 + // 重新计算订单总金额
850 + calculateTotalAmount()
851 +}
852 +
853 +// 计算订单总金额
854 +const calculateTotalAmount = () => {
855 + const total = formData.orderItems.reduce((sum, item) => {
856 + return sum + (item.itemAmount || 0)
857 + }, 0)
858 + formData.totalAmount = total
816 } 859 }
817 860
818 const handleSubmit = async () => { 861 const handleSubmit = async () => {
...@@ -874,6 +917,48 @@ const handleSubmit = async () => { ...@@ -874,6 +917,48 @@ const handleSubmit = async () => {
874 } 917 }
875 } 918 }
876 919
920 +// 经销商选择处理
921 +const handleDealerChange = () => {
922 + const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
923 + if (selectedDealer) {
924 + formData.dealerCode = selectedDealer.dealerCode
925 + }
926 +}
927 +
928 +// 加载经销商列表
929 +const loadDealers = async () => {
930 + try {
931 + const response = await dealerApi.getAllDealers() as any
932 + if (response && response.data && Array.isArray(response.data)) {
933 + dealerList.value = response.data
934 + }
935 + } catch (error) {
936 + console.error('加载经销商列表失败:', error)
937 + showMessage('加载经销商列表失败', 'error')
938 + }
939 +}
940 +
941 +// 产品选择处理
942 +const handleProductChange = (index: number) => {
943 + const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === formData.orderItems[index].productName)
944 + if (selectedProduct) {
945 + formData.orderItems[index].productCode = selectedProduct.productCode
946 + }
947 +}
948 +
949 +// 加载产品列表
950 +const loadProducts = async () => {
951 + try {
952 + const response = await productApi.getAllProducts() as any
953 + if (response && response.data && Array.isArray(response.data)) {
954 + productList.value = response.data
955 + }
956 + } catch (error) {
957 + console.error('加载产品列表失败:', error)
958 + showMessage('加载产品列表失败', 'error')
959 + }
960 +}
961 +
877 const closeDialog = () => { 962 const closeDialog = () => {
878 showDialog.value = false 963 showDialog.value = false
879 formData.orderItems = [] 964 formData.orderItems = []
...@@ -887,6 +972,8 @@ const closeDetailDialog = () => { ...@@ -887,6 +972,8 @@ const closeDetailDialog = () => {
887 // 生命周期 972 // 生命周期
888 onMounted(() => { 973 onMounted(() => {
889 fetchOrders() 974 fetchOrders()
975 + loadDealers()
976 + loadProducts()
890 }) 977 })
891 </script> 978 </script>
892 979
...@@ -1360,7 +1447,26 @@ onMounted(() => { ...@@ -1360,7 +1447,26 @@ onMounted(() => {
1360 border: 1px solid #ddd; 1447 border: 1px solid #ddd;
1361 border-radius: 4px; 1448 border-radius: 4px;
1362 font-size: 12px; 1449 font-size: 12px;
1363 - width: 200px; 1450 + width: 250px;
1451 + height: 32px;
1452 + box-sizing: border-box;
1453 +}
1454 +
1455 +.form-select {
1456 + background-color: #fff;
1457 + cursor: pointer;
1458 +}
1459 +
1460 +.form-select:focus {
1461 + outline: none;
1462 + border-color: #007bff;
1463 + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
1464 +}
1465 +
1466 +.form-input.readonly {
1467 + background-color: #f5f5f5;
1468 + color: #666;
1469 + cursor: not-allowed;
1364 } 1470 }
1365 1471
1366 /* 订单明细中的输入框样式 - 优化空间利用 */ 1472 /* 订单明细中的输入框样式 - 优化空间利用 */
...@@ -1378,30 +1484,38 @@ onMounted(() => { ...@@ -1378,30 +1484,38 @@ onMounted(() => {
1378 min-width: 0; 1484 min-width: 0;
1379 } 1485 }
1380 1486
1381 -/* 产品编码字段 */ 1487 +/* 产品名称字段 - 占用最多空间,增加长度(现在在第一位) */
1382 .order-item .form-item:nth-child(1) { 1488 .order-item .form-item:nth-child(1) {
1383 - flex: 0 0 120px; 1489 + flex: 1.5 1 200px;
1490 + min-width: 150px;
1384 } 1491 }
1385 1492
1386 -/* 产品名称字段 - 占用更多空间 */ 1493 +/* 产品编码字段 - 固定宽度(现在在第二位),往左移动 */
1387 .order-item .form-item:nth-child(2) { 1494 .order-item .form-item:nth-child(2) {
1388 - flex: 1 1 200px; 1495 + flex: 0 0 180px;
1389 - min-width: 150px; 1496 + margin-left: -15px;
1390 } 1497 }
1391 1498
1392 -/* 数量字段 */ 1499 +/* 产品编码输入框宽度调整为90% */
1500 +.order-item .form-item:nth-child(2) .form-input {
1501 + width: 90%;
1502 +}
1503 +
1504 +/* 数量字段 - 较小宽度,往左移动 */
1393 .order-item .form-item:nth-child(3) { 1505 .order-item .form-item:nth-child(3) {
1394 flex: 0 0 80px; 1506 flex: 0 0 80px;
1507 + margin-left: -10px;
1395 } 1508 }
1396 1509
1397 -/* 单价字段 */ 1510 +/* 单价字段 - 中等宽度,往左移动 */
1398 .order-item .form-item:nth-child(4) { 1511 .order-item .form-item:nth-child(4) {
1399 - flex: 0 0 100px; 1512 + flex: 0 0 110px;
1513 + margin-left: -5px;
1400 } 1514 }
1401 1515
1402 -/* 明细金额字段 */ 1516 +/* 明细金额字段 - 中等宽度 */
1403 .order-item .form-item:nth-child(5) { 1517 .order-item .form-item:nth-child(5) {
1404 - flex: 0 0 100px; 1518 + flex: 0 0 110px;
1405 } 1519 }
1406 1520
1407 .order-item .form-item label { 1521 .order-item .form-item label {
...@@ -1431,16 +1545,19 @@ onMounted(() => { ...@@ -1431,16 +1545,19 @@ onMounted(() => {
1431 .order-items-container { 1545 .order-items-container {
1432 border: 1px solid #eee; 1546 border: 1px solid #eee;
1433 border-radius: 4px; 1547 border-radius: 4px;
1434 - padding: 6px; 1548 + padding: 8px;
1435 background: #f8f9fa; 1549 background: #f8f9fa;
1550 + width: 100%;
1436 } 1551 }
1437 1552
1438 .order-item { 1553 .order-item {
1439 background: white; 1554 background: white;
1440 border: 1px solid #ddd; 1555 border: 1px solid #ddd;
1441 border-radius: 4px; 1556 border-radius: 4px;
1442 - padding: 6px; 1557 + padding: 8px;
1443 - margin-bottom: 6px; 1558 + margin-bottom: 8px;
1559 + width: 100%;
1560 + box-sizing: border-box;
1444 } 1561 }
1445 1562
1446 .order-item:last-child { 1563 .order-item:last-child {
...@@ -1461,22 +1578,28 @@ onMounted(() => { ...@@ -1461,22 +1578,28 @@ onMounted(() => {
1461 /* 单行布局 - 所有字段在一行显示,优化空间利用 */ 1578 /* 单行布局 - 所有字段在一行显示,优化空间利用 */
1462 .item-row-single { 1579 .item-row-single {
1463 display: flex; 1580 display: flex;
1464 - gap: 4px; 1581 + gap: 8px;
1465 align-items: end; 1582 align-items: end;
1466 flex-wrap: nowrap; 1583 flex-wrap: nowrap;
1467 - justify-content: space-between; 1584 + width: 100%;
1468 } 1585 }
1469 1586
1470 .remove-item-btn { 1587 .remove-item-btn {
1471 - padding: 4px 8px; 1588 + padding: 6px 12px;
1472 background: #dc3545; 1589 background: #dc3545;
1473 color: white; 1590 color: white;
1474 border: none; 1591 border: none;
1475 border-radius: 4px; 1592 border-radius: 4px;
1476 cursor: pointer; 1593 cursor: pointer;
1477 font-size: 11px; 1594 font-size: 11px;
1478 - height: 28px; 1595 + height: 32px;
1479 align-self: end; 1596 align-self: end;
1597 + transition: background-color 0.2s;
1598 + flex-shrink: 0;
1599 +}
1600 +
1601 +.remove-item-btn:hover {
1602 + background: #c82333;
1480 } 1603 }
1481 1604
1482 .form-actions { 1605 .form-actions {
......