zhouhui.jiang

update 订单、出库 、发票

......@@ -997,6 +997,47 @@
}
```
### 7.9 获取所有产品
**接口路径:** `GET /api/product/all`
**功能描述:** 获取所有启用的产品信息,用于下拉选择
**权限要求:**
**请求参数:**
**响应示例:**
```json
{
"code": 200,
"message": "操作成功",
"data": [
{
"productId": 1,
"productCode": "APL-IP15-128G-BK",
"productName": "iPhone 15|A2848|iPhone|黑色"
},
{
"productId": 2,
"productCode": "APL-IP15-256G-BK",
"productName": "iPhone 15|A2848|iPhone|黑色"
},
{
"productId": 3,
"productCode": "APL-IP15-128G-WH",
"productName": "iPhone 15|A2848|iPhone|白色"
}
]
}
```
**响应字段说明:**
- 只返回未删除且销售状态为在售的产品
- 按产品编码升序排列
- 产品名称格式:`产品名称|产品型号|产品类别|产品颜色`
- 主要用于订单管理等模块的下拉选择
## 8. 经销商管理 (DealerInfoController)
### 8.1 分页查询经销商列表
......@@ -1328,6 +1369,81 @@
}
```
### 8.9 获取所有经销商
**接口路径:** `GET /api/dealer/all`
**功能描述:** 获取所有启用的经销商信息,用于下拉选择
**权限要求:**
**请求参数:**
**响应示例:**
```json
{
"code": 200,
"message": "操作成功",
"data": [
{
"dealerId": 1,
"dealerCode": "DL001",
"dealerName": "北京经销商",
"creditCode": "91110000123456789X",
"dealerLevel": 1,
"region": "北京",
"contactPerson": "张三",
"contactPhone": "13800138000",
"cooperateStartDate": "2024-01-01",
"cooperateStatus": 1,
"businessLicenseUrl": "https://example.com/license.jpg",
"cooperationAgreementUrl": "https://example.com/agreement.pdf",
"qualificationAuditStatus": 2,
"auditOpinion": "审核通过",
"totalRebateAmount": 10000.00,
"usedRebateAmount": 5000.00,
"pendingRebateAmount": 5000.00,
"lastRebateUpdateTime": "2024-01-01 10:00:00",
"createBy": "admin",
"createTime": "2024-01-01 10:00:00",
"updateBy": "admin",
"updateTime": "2024-01-01 10:00:00",
"delFlag": "0"
},
{
"dealerId": 2,
"dealerCode": "DL002",
"dealerName": "上海经销商",
"creditCode": "91310000123456789Y",
"dealerLevel": 2,
"region": "上海",
"contactPerson": "李四",
"contactPhone": "13900139000",
"cooperateStartDate": "2024-02-01",
"cooperateStatus": 1,
"businessLicenseUrl": "https://example.com/license2.jpg",
"cooperationAgreementUrl": "https://example.com/agreement2.pdf",
"qualificationAuditStatus": 2,
"auditOpinion": "审核通过",
"totalRebateAmount": 15000.00,
"usedRebateAmount": 8000.00,
"pendingRebateAmount": 7000.00,
"lastRebateUpdateTime": "2024-02-01 10:00:00",
"createBy": "admin",
"createTime": "2024-02-01 10:00:00",
"updateBy": "admin",
"updateTime": "2024-02-01 10:00:00",
"delFlag": "0"
}
]
}
```
**响应字段说明:**
- 只返回未删除且合作状态为正常的经销商
- 按经销商编码升序排列
- 主要用于订单管理等模块的下拉选择
## 数据字典
### 销售状态(saleStatus)
......
......@@ -33,6 +33,13 @@ public class DealerInfoController {
return ApiRes.success(page);
}
@Operation(summary = "获取所有经销商", description = "获取所有启用的经销商信息,用于下拉选择")
@GetMapping("/all")
public ApiRes<List<DealerInfo>> getAllDealers() {
List<DealerInfo> dealers = dealerInfoService.getAllDealers();
return ApiRes.success(dealers);
}
@Operation(summary = "获取经销商详情", description = "根据经销商ID获取经销商详细信息")
@GetMapping("/{dealerId}")
@PreAuthorize("hasAuthority('dealer:detail')")
......
......@@ -3,6 +3,7 @@ package com.apple.erp.controller;
import com.apple.erp.dto.ProductAddReq;
import com.apple.erp.dto.ProductQueryReq;
import com.apple.erp.dto.ProductUpdateReq;
import com.apple.erp.dto.ProductSimpleRes;
import com.apple.erp.entity.ProductInfo;
import com.apple.erp.service.ProductInfoService;
import com.apple.erp.dto.response.ApiRes;
......@@ -174,4 +175,15 @@ public class ProductInfoController {
return ApiRes.error("修改返利标识失败:" + e.getMessage());
}
}
@Operation(summary = "获取所有产品", description = "获取所有启用的产品信息,用于下拉选择")
@GetMapping("/all")
public ApiRes<List<ProductSimpleRes>> getAllProducts() {
try {
List<ProductSimpleRes> products = productInfoService.getAllProducts();
return ApiRes.success(products);
} catch (Exception e) {
return ApiRes.error("获取产品列表失败:" + e.getMessage());
}
}
}
......
package com.apple.erp.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 产品简单信息响应DTO
* 用于下拉选择等场景
*
* @author Apple ERP Team
* @since 2025-01-27
*/
@Data
@Schema(description = "产品简单信息")
public class ProductSimpleRes {
@Schema(description = "产品ID")
private Long productId;
@Schema(description = "产品编码")
private String productCode;
@Schema(description = "产品名称")
private String productName;
}
......@@ -69,4 +69,10 @@ public interface DealerInfoService extends IService<DealerInfo> {
* @return 是否成功
*/
boolean updateDealerAuditStatus(Long dealerId, Integer qualificationAuditStatus, String auditOpinion);
/**
* 获取所有启用的经销商
* @return 经销商列表
*/
List<DealerInfo> getAllDealers();
}
......
......@@ -3,6 +3,7 @@ package com.apple.erp.service;
import com.apple.erp.dto.ProductAddReq;
import com.apple.erp.dto.ProductQueryReq;
import com.apple.erp.dto.ProductUpdateReq;
import com.apple.erp.dto.ProductSimpleRes;
import com.apple.erp.entity.ProductInfo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
......@@ -91,4 +92,11 @@ public interface ProductInfoService extends IService<ProductInfo> {
* @return 是否存在
*/
boolean checkProductCodeExists(String productCode, Long productId);
/**
* 获取所有启用的产品简单信息
*
* @return 产品简单信息列表
*/
List<ProductSimpleRes> getAllProducts();
}
......
......@@ -234,4 +234,13 @@ public class DealerInfoServiceImpl extends ServiceImpl<DealerInfoMapper, DealerI
}
return count(queryWrapper) > 0;
}
@Override
public List<DealerInfo> getAllDealers() {
LambdaQueryWrapper<DealerInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DealerInfo::getDelFlag, "0") // 未删除
.eq(DealerInfo::getCooperateStatus, 1) // 正常合作状态
.orderByAsc(DealerInfo::getDealerCode); // 按经销商编码排序
return list(queryWrapper);
}
}
......
package com.apple.erp.service.impl;
import com.apple.erp.dto.*;
import com.apple.erp.entity.DeliveryItem;
import com.apple.erp.mapper.DeliveryItemMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.apple.erp.entity.DeliveryMain;
import com.apple.erp.mapper.DeliveryMainMapper;
import com.apple.erp.service.DeliveryMainService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
/**
* 出库主表Service实现类
*
* @author Apple ERP System
* @since 2025-01-01
*/
@Slf4j
@Service
public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, DeliveryMain> implements DeliveryMainService {
@Autowired
private DeliveryItemMapper deliveryItemMapper;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public Page<DeliveryRes> getDeliveryList(DeliveryQueryReq queryReq) {
LambdaQueryWrapper<DeliveryMain> queryWrapper = Wrappers.lambdaQuery(DeliveryMain.class)
.eq(StringUtils.hasText(queryReq.getDeliveryNo()), DeliveryMain::getDeliveryNo, queryReq.getDeliveryNo())
.eq(StringUtils.hasText(queryReq.getDealerCode()), DeliveryMain::getDealerCode, queryReq.getDealerCode())
.like(StringUtils.hasText(queryReq.getDealerName()), DeliveryMain::getDealerName, queryReq.getDealerName())
.eq(StringUtils.hasText(queryReq.getOrderNo()), DeliveryMain::getOrderNo, queryReq.getOrderNo())
.eq(queryReq.getDeliveryStatus() != null, DeliveryMain::getDeliveryStatus, queryReq.getDeliveryStatus())
.eq(StringUtils.hasText(queryReq.getWarehouseCode()), DeliveryMain::getWarehouseCode, queryReq.getWarehouseCode())
.eq(StringUtils.hasText(queryReq.getDataSource()), DeliveryMain::getDataSource, queryReq.getDataSource())
.ge(StringUtils.hasText(queryReq.getDeliveryStartDate()), DeliveryMain::getDeliveryDate, parseDateTime(queryReq.getDeliveryStartDate()))
.le(StringUtils.hasText(queryReq.getDeliveryEndDate()), DeliveryMain::getDeliveryDate, parseDateTime(queryReq.getDeliveryEndDate()))
.eq(DeliveryMain::getDelFlag, "0")
.orderByDesc(DeliveryMain::getCreateTime);
Page<DeliveryMain> page = new Page<>(queryReq.getPageNum(), queryReq.getPageSize());
IPage<DeliveryMain> deliveryMainPage = baseMapper.selectPage(page, queryWrapper);
List<DeliveryRes> deliveryResList = deliveryMainPage.getRecords().stream().map(deliveryMain -> {
DeliveryRes deliveryRes = new DeliveryRes();
BeanUtils.copyProperties(deliveryMain, deliveryRes);
// 查找出库明细
LambdaQueryWrapper<DeliveryItem> itemQueryWrapper = Wrappers.lambdaQuery(DeliveryItem.class)
.eq(DeliveryItem::getDeliveryId, deliveryMain.getDeliveryId());
List<DeliveryItem> deliveryItems = deliveryItemMapper.selectList(itemQueryWrapper);
List<DeliveryItemRes> itemResList = deliveryItems.stream().map(deliveryItem -> {
DeliveryItemRes itemRes = new DeliveryItemRes();
BeanUtils.copyProperties(deliveryItem, itemRes);
return itemRes;
}).collect(Collectors.toList());
deliveryRes.setDeliveryItems(itemResList);
return deliveryRes;
}).collect(Collectors.toList());
Page<DeliveryRes> resultPage = new Page<>(deliveryMainPage.getCurrent(), deliveryMainPage.getSize(), deliveryMainPage.getTotal());
resultPage.setRecords(deliveryResList);
return resultPage;
}
@Override
public DeliveryRes getDeliveryDetail(Long deliveryId) {
DeliveryMain deliveryMain = baseMapper.selectById(deliveryId);
if (deliveryMain == null || "2".equals(deliveryMain.getDelFlag())) {
return null;
}
DeliveryRes deliveryRes = new DeliveryRes();
BeanUtils.copyProperties(deliveryMain, deliveryRes);
LambdaQueryWrapper<DeliveryItem> itemQueryWrapper = Wrappers.lambdaQuery(DeliveryItem.class)
.eq(DeliveryItem::getDeliveryId, deliveryId);
List<DeliveryItem> deliveryItems = deliveryItemMapper.selectList(itemQueryWrapper);
List<DeliveryItemRes> itemResList = deliveryItems.stream().map(deliveryItem -> {
DeliveryItemRes itemRes = new DeliveryItemRes();
BeanUtils.copyProperties(deliveryItem, itemRes);
return itemRes;
}).collect(Collectors.toList());
deliveryRes.setDeliveryItems(itemResList);
return deliveryRes;
}
@Override
@Transactional
public boolean addDelivery(DeliveryAddReq addReq) {
// 检查出库单编号是否已存在
if (isDeliveryNoExist(addReq.getDeliveryNo(), null)) {
throw new IllegalArgumentException("出库单编号已存在");
}
DeliveryMain deliveryMain = new DeliveryMain();
BeanUtils.copyProperties(addReq, deliveryMain);
deliveryMain.setCreateTime(LocalDateTime.now());
deliveryMain.setUpdateTime(LocalDateTime.now());
if(StringUtils.hasText(addReq.getDeliveryDate())) {
deliveryMain.setDeliveryDate(parseDateTime(addReq.getDeliveryDate()));
}
deliveryMain.setDelFlag("0");
int insert = baseMapper.insert(deliveryMain);
if (insert > 0) {
for (DeliveryItemAddReq itemAddReq : addReq.getDeliveryItems()) {
DeliveryItem deliveryItem = new DeliveryItem();
BeanUtils.copyProperties(itemAddReq, deliveryItem);
deliveryItem.setDeliveryId(deliveryMain.getDeliveryId());
deliveryItem.setDeliveryNo(deliveryMain.getDeliveryNo());
deliveryItem.setOrderNo(deliveryMain.getOrderNo());
deliveryItem.setCreateTime(LocalDateTime.now());
deliveryItem.setUpdateTime(LocalDateTime.now());
deliveryItem.setDelFlag("0");
deliveryItemMapper.insert(deliveryItem);
}
return true;
}
return false;
}
@Override
@Transactional
public boolean updateDelivery(DeliveryUpdateReq updateReq) {
// 检查出库单编号是否已存在且不属于当前出库单
if (isDeliveryNoExist(updateReq.getDeliveryNo(), updateReq.getDeliveryId())) {
throw new IllegalArgumentException("出库单编号已存在");
}
DeliveryMain deliveryMain = new DeliveryMain();
BeanUtils.copyProperties(updateReq, deliveryMain);
deliveryMain.setUpdateTime(LocalDateTime.now());
int update = baseMapper.updateById(deliveryMain);
if (update > 0) {
// 先删除旧的出库明细
LambdaQueryWrapper<DeliveryItem> deleteWrapper = Wrappers.lambdaQuery(DeliveryItem.class)
.eq(DeliveryItem::getDeliveryId, updateReq.getDeliveryId());
deliveryItemMapper.delete(deleteWrapper);
// 插入新的出库明细
for (DeliveryItemUpdateReq itemUpdateReq : updateReq.getDeliveryItems()) {
DeliveryItem deliveryItem = new DeliveryItem();
BeanUtils.copyProperties(itemUpdateReq, deliveryItem);
deliveryItem.setDeliveryId(updateReq.getDeliveryId());
deliveryItem.setDeliveryNo(updateReq.getDeliveryNo());
deliveryItem.setOrderNo(updateReq.getOrderNo());
deliveryItem.setCreateTime(LocalDateTime.now()); // 新增明细时设置创建时间
deliveryItem.setUpdateTime(LocalDateTime.now());
deliveryItem.setDelFlag("0");
deliveryItemMapper.insert(deliveryItem);
}
return true;
}
return false;
}
@Override
@Transactional
public boolean deleteDelivery(Long deliveryId) {
DeliveryMain deliveryMain = new DeliveryMain();
deliveryMain.setDeliveryId(deliveryId);
deliveryMain.setDelFlag("2"); // 逻辑删除
deliveryMain.setUpdateTime(LocalDateTime.now());
return baseMapper.updateById(deliveryMain) > 0;
}
@Override
@Transactional
public boolean batchDeleteDeliveries(List<Long> deliveryIds) {
List<DeliveryMain> deliveriesToUpdate = deliveryIds.stream().map(deliveryId -> {
DeliveryMain deliveryMain = new DeliveryMain();
deliveryMain.setDeliveryId(deliveryId);
deliveryMain.setDelFlag("2");
deliveryMain.setUpdateTime(LocalDateTime.now());
return deliveryMain;
}).collect(Collectors.toList());
return updateBatchById(deliveriesToUpdate);
}
@Override
public boolean updateDeliveryStatus(Long deliveryId, Integer deliveryStatus) {
DeliveryMain deliveryMain = new DeliveryMain();
deliveryMain.setDeliveryId(deliveryId);
deliveryMain.setDeliveryStatus(deliveryStatus);
deliveryMain.setUpdateTime(LocalDateTime.now());
return baseMapper.updateById(deliveryMain) > 0;
}
/**
* 检查出库单编号是否已存在
* @param deliveryNo 出库单编号
* @param excludeDeliveryId 排除的出库单ID(用于修改时)
* @return 是否存在
*/
private boolean isDeliveryNoExist(String deliveryNo, Long excludeDeliveryId) {
LambdaQueryWrapper<DeliveryMain> queryWrapper = Wrappers.lambdaQuery(DeliveryMain.class)
.eq(DeliveryMain::getDeliveryNo, deliveryNo)
.eq(DeliveryMain::getDelFlag, "0");
if (excludeDeliveryId != null) {
queryWrapper.ne(DeliveryMain::getDeliveryId, excludeDeliveryId);
}
return baseMapper.selectCount(queryWrapper) > 0;
}
private LocalDateTime parseDateTime(String dateTimeStr) {
if (!StringUtils.hasText(dateTimeStr)) {
return null;
}
try {
return LocalDateTime.parse(dateTimeStr, DATE_TIME_FORMATTER);
} catch (Exception e) {
log.warn("日期时间解析失败: {}", dateTimeStr, e);
return null;
}
}
}
......
......@@ -3,6 +3,7 @@ package com.apple.erp.service.impl;
import com.apple.erp.dto.ProductAddReq;
import com.apple.erp.dto.ProductQueryReq;
import com.apple.erp.dto.ProductUpdateReq;
import com.apple.erp.dto.ProductSimpleRes;
import com.apple.erp.entity.ProductInfo;
import com.apple.erp.mapper.ProductInfoMapper;
import com.apple.erp.service.ProductInfoService;
......@@ -192,4 +193,40 @@ public class ProductInfoServiceImpl extends ServiceImpl<ProductInfoMapper, Produ
return queryWrapper;
}
@Override
public List<ProductSimpleRes> getAllProducts() {
LambdaQueryWrapper<ProductInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ProductInfo::getDelFlag, "0") // 未删除
.eq(ProductInfo::getSaleStatus, 1) // 在售状态
.orderByAsc(ProductInfo::getProductCode); // 按产品编码排序
List<ProductInfo> products = list(queryWrapper);
return products.stream()
.map(product -> {
ProductSimpleRes res = new ProductSimpleRes();
res.setProductId(product.getProductId());
res.setProductCode(product.getProductCode());
// 拼接产品名称:产品名称|产品型号|产品类别|产品颜色
StringBuilder productNameBuilder = new StringBuilder();
productNameBuilder.append(product.getProductName() != null ? product.getProductName() : "");
if (product.getProductModel() != null && !product.getProductModel().trim().isEmpty()) {
productNameBuilder.append("|").append(product.getProductModel());
}
if (product.getProductType() != null && !product.getProductType().trim().isEmpty()) {
productNameBuilder.append("|").append(product.getProductType());
}
if (product.getColor() != null && !product.getColor().trim().isEmpty()) {
productNameBuilder.append("|").append(product.getColor());
}
res.setProductName(productNameBuilder.toString());
return res;
})
.collect(java.util.stream.Collectors.toList());
}
}
......
......@@ -17,8 +17,8 @@ spring:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/apple_erp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: apple_erp_app
password: StrongPassw0rd!
username: root
password: MySQL@123
druid:
# 初始连接数
initial-size: 5
......@@ -70,7 +70,7 @@ spring:
redis:
host: localhost
port: 6379
password: 123456
password:
database: 0
timeout: 10000ms
lettuce:
......
......@@ -7,10 +7,30 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
Header: typeof import('./src/components/layout/Header.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default']
SidebarItem: typeof import('./src/components/layout/SidebarItem.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
......
......@@ -162,5 +162,10 @@ export const dealerApi = {
return api.post<ApiResponse>(`/dealer/${dealerId}/auditStatus`, null, {
params: { qualificationAuditStatus, auditOpinion }
})
},
// 获取所有经销商(用于下拉选择)
getAllDealers: () => {
return api.get<ApiResponse<DealerInfo[]>>('/dealer/all')
}
}
......
......@@ -55,6 +55,13 @@ export interface ProductInfo {
updateTime: string
}
// 产品简单信息接口(用于下拉选择)
export interface ProductSimpleRes {
productId: number
productCode: string
productName: string
}
// 产品查询请求参数
export interface ProductQueryReq {
productCode?: string
......@@ -157,5 +164,10 @@ export const productApi = {
// 修改返利标识
updateRebateFlag: (productId: number, rebateFlag: number) => {
return api.post(`/product/${productId}/rebate`, null, { params: { rebateFlag } })
},
// 获取所有产品(用于下拉选择)
getAllProducts: () => {
return api.get<ApiResponse<ProductSimpleRes[]>>('/product/all')
}
}
......
......@@ -4,11 +4,11 @@
<div class="search-section">
<div class="search-row">
<div class="search-item">
<label>单编号:</label>
<label>出库单编号:</label>
<input
v-model="searchParams.orderNo"
v-model="searchParams.deliveryNo"
class="search-input"
placeholder="请输入单编号"
placeholder="请输入出库单编号"
/>
</div>
<div class="search-item">
......@@ -38,17 +38,17 @@
</div>
<div class="search-row">
<div class="search-item">
<label>开始日期:</label>
<label>出库开始日期:</label>
<input
v-model="searchParams.startDate"
v-model="searchParams.deliveryStartDate"
class="search-input"
type="date"
/>
</div>
<div class="search-item">
<label>结束日期:</label>
<label>出库结束日期:</label>
<input
v-model="searchParams.endDate"
v-model="searchParams.deliveryEndDate"
class="search-input"
type="date"
/>
......@@ -113,6 +113,7 @@
<td>{{ formatTime(delivery.createTime || '') }}</td>
<td class="action-col">
<button @click="handleView(delivery)" class="view-btn">👁️ 查看</button>
<button @click="handleEdit(delivery)" class="edit-btn">✏️ 编辑</button>
<button @click="handleDelete(delivery)" class="delete-btn">🗑️ 删除</button>
</td>
</tr>
......@@ -179,15 +180,11 @@
<div class="detail-grid">
<div class="detail-item">
<label>出库单ID:</label>
<span>{{ orderDetail.deliveryId }}</span>
<span>{{ deliveryDetail.deliveryId }}</span>
</div>
<div class="detail-item">
<label>出库单编号:</label>
<span>{{ orderDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
<span>{{ orderDetail.orderNo }}</span>
<span>{{ deliveryDetail.deliveryNo }}</span>
</div>
<div class="detail-item">
<label>关联订单编号:</label>
......@@ -203,7 +200,7 @@
</div>
<div class="detail-item">
<label>出库日期:</label>
<span>{{ formatDate(orderDetail.deliveryDate) }}</span>
<span>{{ formatDate(deliveryDetail.deliveryDate) }}</span>
</div>
<div class="detail-item">
<label>出库状态:</label>
......@@ -213,43 +210,43 @@
</div>
<div class="detail-item">
<label>仓库编码:</label>
<span>{{ orderDetail.warehouseCode }}</span>
<span>{{ deliveryDetail.warehouseCode }}</span>
</div>
<div class="detail-item">
<label>数据来源:</label>
<span>{{ orderDetail.dataSource || '-' }}</span>
<span>{{ deliveryDetail.dataSource || '-' }}</span>
</div>
<div class="detail-item">
<label>上传时间:</label>
<span>{{ formatTime(orderDetail.uploadTime || '') }}</span>
<span>{{ formatTime(deliveryDetail.uploadTime || '') }}</span>
</div>
<div class="detail-item">
<label>创建时间:</label>
<span>{{ formatTime(orderDetail.createTime || '') }}</span>
<span>{{ formatTime(deliveryDetail.createTime || '') }}</span>
</div>
<div class="detail-item">
<label>更新时间:</label>
<span>{{ formatTime(orderDetail.updateTime || '') }}</span>
<span>{{ formatTime(deliveryDetail.updateTime || '') }}</span>
</div>
</div>
</div>
<div class="detail-section" v-if="orderDetail.deliveryItems && orderDetail.deliveryItems.length > 0">
<div class="detail-section" v-if="deliveryDetail.deliveryItems && deliveryDetail.deliveryItems.length > 0">
<h4>出库明细</h4>
<table class="detail-table">
<thead>
<tr>
<th>产品编码</th>
<th>产品名称</th>
<th>产品编码</th>
<th>出库数量</th>
<th>出库单价</th>
<th>出库金额</th>
</tr>
</thead>
<tbody>
<tr v-for="item in orderDetail.deliveryItems" :key="item.deliveryItemId">
<td>{{ item.productCode }}</td>
<tr v-for="item in deliveryDetail.deliveryItems" :key="item.deliveryItemId">
<td>{{ item.productName }}</td>
<td>{{ item.productCode }}</td>
<td>{{ item.deliveryQty }}</td>
<td>{{ formatCurrency(item.deliveryPrice) }}</td>
<td>{{ formatCurrency(item.deliveryAmount) }}</td>
......@@ -262,15 +259,15 @@
</div>
</div>
<!-- 新增出库对话框 -->
<div v-if="showAddDialog" class="dialog-overlay" @click="closeAddDialog">
<!-- 编辑出库对话框 -->
<div v-if="showEditDialog" class="dialog-overlay" @click="closeEditDialog">
<div class="dialog-content large-dialog" @click.stop>
<div class="dialog-header">
<h3>新增出库</h3>
<button @click="closeAddDialog" class="close-btn">×</button>
<h3>编辑出库</h3>
<button @click="closeEditDialog" class="close-btn">×</button>
</div>
<div class="dialog-body">
<form @submit.prevent="handleSubmitAdd">
<form @submit.prevent="handleSubmitEdit">
<!-- 出库基本信息 -->
<div class="form-section">
<h4>出库基本信息</h4>
......@@ -278,33 +275,215 @@
<div class="form-item">
<label class="required">出库单编号:</label>
<input
v-model="addFormData.deliveryNo"
v-model="editFormData.deliveryNo"
class="form-input"
placeholder="请输入出库单编号"
required
/>
</div>
<div class="form-item">
<label class="required">经销商编码:</label>
<label class="required">经销商名称:</label>
<select
v-model="editFormData.dealerName"
class="form-input"
@change="handleEditDealerChange"
required
>
<option value="">请选择经销商</option>
<option
v-for="dealer in dealerList"
:key="dealer.dealerId"
:value="dealer.dealerName"
>
{{ dealer.dealerName }}
</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">关联订单编号:</label>
<input
v-model="addFormData.dealerCode"
v-model="editFormData.orderNo"
class="form-input"
placeholder="请输入经销商编码"
placeholder="请输入关联订单编号"
required
/>
</div>
<div class="form-item">
<label class="required">出库日期:</label>
<input
v-model="editFormData.deliveryDate"
class="form-input"
type="datetime-local"
step="1"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">经销商名称:</label>
<label>仓库编码:</label>
<input
v-model="addFormData.dealerName"
v-model="editFormData.warehouseCode"
class="form-input"
placeholder="请输入经销商名称"
placeholder="请输入仓库编码"
/>
</div>
<div class="form-item">
<label>仓库名称:</label>
<input
v-model="editFormData.warehouseName"
class="form-input"
placeholder="请输入仓库名称"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>数据来源:</label>
<input
v-model="editFormData.dataSource"
class="form-input"
placeholder="请输入数据来源"
/>
</div>
<!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
<input
v-model="editFormData.dealerCode"
type="hidden"
/>
</div>
</div>
<!-- 出库明细 -->
<div class="form-section">
<h4>出库明细</h4>
<div class="order-items-header">
<button type="button" @click="addEditDeliveryItem" class="add-item-btn">+ 添加明细</button>
</div>
<div class="order-items-container">
<div v-for="(item, index) in editFormData.deliveryItems" :key="index" class="order-item">
<div class="item-row-single">
<div class="form-item">
<label class="required">产品名称:</label>
<select
v-model="item.productName"
class="form-input"
@change="handleEditProductChange(index)"
required
>
<option value="">请选择产品</option>
<option
v-for="product in productList"
:key="product.productId"
:value="product.productName"
>
{{ product.productName }}
</option>
</select>
</div>
<div class="form-item">
<label class="required">产品编码:</label>
<input
v-model="item.productCode"
class="form-input readonly"
placeholder="自动填充"
readonly
required
/>
</div>
<div class="form-item">
<label class="required">出库数量:</label>
<input
v-model.number="item.deliveryQty"
class="form-input"
type="number"
placeholder="请输入出库数量"
@input="calculateEditDeliveryItemTotal(index)"
required
/>
</div>
<div class="form-item">
<label class="required">出库单价:</label>
<input
v-model.number="item.deliveryPrice"
class="form-input"
type="number"
step="0.01"
placeholder="请输入出库单价"
@input="calculateEditDeliveryItemTotal(index)"
required
/>
</div>
<div class="form-item">
<label>出库金额:</label>
<input
v-model.number="item.deliveryAmount"
class="form-input"
type="number"
step="0.01"
readonly
/>
</div>
<button type="button" @click="removeEditDeliveryItem(index)" class="remove-item-btn">删除</button>
</div>
</div>
</div>
</div>
<div class="form-actions">
<button type="button" @click="closeEditDialog" class="cancel-btn">取消</button>
<button type="submit" class="submit-btn">确定</button>
</div>
</form>
</div>
</div>
</div>
<!-- 新增出库对话框 -->
<div v-if="showAddDialog" class="dialog-overlay" @click="closeAddDialog">
<div class="dialog-content large-dialog" @click.stop>
<div class="dialog-header">
<h3>新增出库</h3>
<button @click="closeAddDialog" class="close-btn">×</button>
</div>
<div class="dialog-body">
<form @submit.prevent="handleSubmitAdd">
<!-- 出库基本信息 -->
<div class="form-section">
<h4>出库基本信息</h4>
<div class="form-row">
<div class="form-item">
<label class="required">出库单编号:</label>
<input
v-model="addFormData.deliveryNo"
class="form-input"
placeholder="请输入出库单编号"
required
/>
</div>
<div class="form-item">
<label class="required">经销商名称:</label>
<select
v-model="addFormData.dealerName"
class="form-input"
@change="handleAddDealerChange"
required
>
<option value="">请选择经销商</option>
<option
v-for="dealer in dealerList"
:key="dealer.dealerId"
:value="dealer.dealerName"
>
{{ dealer.dealerName }}
</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">关联订单编号:</label>
<input
v-model="addFormData.orderNo"
......@@ -313,8 +492,6 @@
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">出库日期:</label>
<input
......@@ -325,6 +502,8 @@
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>仓库编码:</label>
<input
......@@ -333,8 +512,6 @@
placeholder="请输入仓库编码"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>仓库名称:</label>
<input
......@@ -343,6 +520,8 @@
placeholder="请输入仓库名称"
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>数据来源:</label>
<input
......@@ -351,6 +530,11 @@
placeholder="请输入数据来源"
/>
</div>
<!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
<input
v-model="addFormData.dealerCode"
type="hidden"
/>
</div>
</div>
......@@ -364,20 +548,30 @@
<div v-for="(item, index) in addFormData.deliveryItems" :key="index" class="order-item">
<div class="item-row-single">
<div class="form-item">
<label class="required">产品编码:</label>
<input
v-model="item.productCode"
<label class="required">产品名称:</label>
<select
v-model="item.productName"
class="form-input"
placeholder="请输入产品编码"
@change="handleAddProductChange(index)"
required
/>
>
<option value="">请选择产品</option>
<option
v-for="product in productList"
:key="product.productId"
:value="product.productName"
>
{{ product.productName }}
</option>
</select>
</div>
<div class="form-item">
<label class="required">产品名称:</label>
<label class="required">产品编码:</label>
<input
v-model="item.productName"
class="form-input"
placeholder="请输入产品名称"
v-model="item.productCode"
class="form-input readonly"
placeholder="自动填充"
readonly
required
/>
</div>
......@@ -434,12 +628,17 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { deliveryApi, type DeliveryAddReq, type DeliveryInfo, type DeliveryQueryReq } from '../../api/delivery'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
import type { ProductSimpleRes } from '../../api/product'
// 响应式数据
const loading = ref(false)
const deliveryList = ref<DeliveryInfo[]>([])
const showDetailDialog = ref(false)
const deliveryDetail = ref<DeliveryInfo | null>(null)
const dealerList = ref<DealerInfo[]>([])
const productList = ref<ProductSimpleRes[]>([])
// 新增出库相关数据
const showAddDialog = ref(false)
......@@ -455,9 +654,23 @@ const addFormData = reactive({
deliveryItems: [] as any[]
})
// 编辑出库相关数据
const showEditDialog = ref(false)
const editFormData = reactive({
deliveryId: 0,
deliveryNo: '',
dealerCode: '',
dealerName: '',
orderNo: '',
deliveryDate: '',
warehouseCode: '',
warehouseName: '',
dataSource: '',
deliveryItems: [] as any[]
})
// 搜索参数
const searchParams = reactive({
orderNo: '',
deliveryNo: '',
dealerCode: '',
dealerName: '',
......@@ -466,8 +679,6 @@ const searchParams = reactive({
dataSource: '',
deliveryStartDate: '',
deliveryEndDate: '',
startDate: '',
endDate: '',
pageNum: 1,
pageSize: 10
})
......@@ -539,7 +750,16 @@ const getPageNumbers = () => {
const fetchDeliveries = async () => {
try {
loading.value = true
const params = { ...searchParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
// 处理日期格式,将日期转换为完整的日期时间格式
const processedParams = { ...searchParams }
if (processedParams.deliveryStartDate) {
processedParams.deliveryStartDate = processedParams.deliveryStartDate + ' 00:00:00'
}
if (processedParams.deliveryEndDate) {
processedParams.deliveryEndDate = processedParams.deliveryEndDate + ' 23:59:59'
}
const params = { ...processedParams, pageNum: pagination.pageNum, pageSize: pagination.pageSize }
const response = await deliveryApi.getDeliveryList(params) as any
console.log('出库查询API响应:', response)
......@@ -569,12 +789,12 @@ const handleSearch = () => {
const handleReset = () => {
Object.assign(searchParams, {
orderNo: '',
deliveryNo: '',
dealerCode: '',
dealerName: '',
deliveryStatus: undefined,
startDate: '',
endDate: ''
deliveryStartDate: '',
deliveryEndDate: ''
})
pagination.pageNum = 1
fetchDeliveries()
......@@ -598,6 +818,42 @@ const handleView = async (delivery: DeliveryInfo) => {
}
}
const handleEdit = async (delivery: DeliveryInfo) => {
try {
// 调用后端接口获取完整的出库详情(包括出库明细)
const response = await deliveryApi.getDeliveryDetail(delivery.deliveryId) as any
console.log('编辑出库API响应:', response)
if (response && response.deliveryId) {
// 处理日期时间格式,将后端返回的日期时间格式转换为前端datetime-local格式
let formattedDeliveryDate = response.deliveryDate
if (formattedDeliveryDate && formattedDeliveryDate.includes(' ')) {
// 将 YYYY-MM-DD HH:MM:SS 格式转换为 YYYY-MM-DDTHH:MM:SS 格式
formattedDeliveryDate = formattedDeliveryDate.replace(' ', 'T')
}
Object.assign(editFormData, {
deliveryId: response.deliveryId,
deliveryNo: response.deliveryNo,
dealerCode: response.dealerCode,
dealerName: response.dealerName,
orderNo: response.orderNo,
deliveryDate: formattedDeliveryDate,
warehouseCode: response.warehouseCode || '',
warehouseName: response.warehouseName || '',
dataSource: response.dataSource || '',
deliveryItems: response.deliveryItems || []
})
showEditDialog.value = true
} else {
showMessage('获取出库详情失败', 'error')
}
} catch (error) {
console.error('获取出库详情失败:', error)
showMessage('获取出库详情失败, 请重试', 'error')
}
}
const handleDelete = async (delivery: DeliveryInfo) => {
if (confirm(`确定要删除出库单"${delivery.deliveryNo}"吗?`)) {
try {
......@@ -637,6 +893,40 @@ const closeAddDialog = () => {
showAddDialog.value = false
}
const closeEditDialog = () => {
showEditDialog.value = false
}
// 经销商选择处理
const handleAddDealerChange = () => {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === addFormData.dealerName)
if (selectedDealer) {
addFormData.dealerCode = selectedDealer.dealerCode
}
}
const handleEditDealerChange = () => {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === editFormData.dealerName)
if (selectedDealer) {
editFormData.dealerCode = selectedDealer.dealerCode
}
}
// 产品选择处理
const handleAddProductChange = (index: number) => {
const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === addFormData.deliveryItems[index].productName)
if (selectedProduct) {
addFormData.deliveryItems[index].productCode = selectedProduct.productCode
}
}
const handleEditProductChange = (index: number) => {
const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === editFormData.deliveryItems[index].productName)
if (selectedProduct) {
editFormData.deliveryItems[index].productCode = selectedProduct.productCode
}
}
const addDeliveryItem = () => {
addFormData.deliveryItems.push({
productCode: '',
......@@ -651,6 +941,20 @@ const removeDeliveryItem = (index: number) => {
addFormData.deliveryItems.splice(index, 1)
}
const addEditDeliveryItem = () => {
editFormData.deliveryItems.push({
productCode: '',
productName: '',
deliveryQty: 1,
deliveryPrice: 0,
deliveryAmount: 0
})
}
const removeEditDeliveryItem = (index: number) => {
editFormData.deliveryItems.splice(index, 1)
}
const calculateDeliveryItemTotal = (index: number) => {
const item = addFormData.deliveryItems[index]
if (item.deliveryQty && item.deliveryPrice) {
......@@ -658,6 +962,13 @@ const calculateDeliveryItemTotal = (index: number) => {
}
}
const calculateEditDeliveryItemTotal = (index: number) => {
const item = editFormData.deliveryItems[index]
if (item.deliveryQty && item.deliveryPrice) {
item.deliveryAmount = item.deliveryQty * item.deliveryPrice
}
}
const handleSubmitAdd = async () => {
try {
// 处理日期时间格式,将datetime-local格式转换为后端需要的格式
......@@ -680,9 +991,9 @@ const handleSubmitAdd = async () => {
deliveryItems: addFormData.deliveryItems.map(item => ({
productCode: item.productCode,
productName: item.productName,
productQty: item.deliveryQty,
unitPrice: item.deliveryPrice,
totalPrice: item.deliveryAmount
deliveryQty: item.deliveryQty,
deliveryPrice: item.deliveryPrice,
deliveryAmount: item.deliveryAmount
}))
}
......@@ -696,6 +1007,44 @@ const handleSubmitAdd = async () => {
}
}
const handleSubmitEdit = async () => {
try {
// 处理日期时间格式,将datetime-local格式转换为后端需要的格式
let formattedDeliveryDate = editFormData.deliveryDate
if (formattedDeliveryDate && formattedDeliveryDate.includes('T')) {
// 将 YYYY-MM-DDTHH:MM:SS 格式转换为 YYYY-MM-DD HH:MM:SS 格式
formattedDeliveryDate = formattedDeliveryDate.replace('T', ' ')
}
// 构造编辑出库的请求数据
const deliveryData = {
deliveryId: editFormData.deliveryId,
deliveryNo: editFormData.deliveryNo,
dealerCode: editFormData.dealerCode,
dealerName: editFormData.dealerName,
orderNo: editFormData.orderNo,
deliveryDate: formattedDeliveryDate,
warehouseCode: editFormData.warehouseCode,
dataSource: editFormData.dataSource || '系统录入',
deliveryItems: editFormData.deliveryItems.map(item => ({
productCode: item.productCode,
productName: item.productName,
deliveryQty: item.deliveryQty,
deliveryPrice: item.deliveryPrice,
deliveryAmount: item.deliveryAmount
}))
}
await deliveryApi.updateDelivery(deliveryData)
showMessage('编辑出库成功', 'success')
closeEditDialog()
fetchDeliveries() // 刷新列表
} catch (error) {
console.error('编辑出库失败:', error)
showMessage('编辑出库失败, 请重试', 'error')
}
}
const handlePageChange = (page: number) => {
......@@ -731,9 +1080,37 @@ const closeDetailDialog = () => {
deliveryDetail.value = null
}
// 加载经销商列表
const loadDealers = async () => {
try {
const response = await dealerApi.getAllDealers() as any
if (response && response.data && Array.isArray(response.data)) {
dealerList.value = response.data
}
} catch (error) {
console.error('加载经销商列表失败:', error)
showMessage('加载经销商列表失败', 'error')
}
}
// 加载产品列表
const loadProducts = async () => {
try {
const response = await productApi.getAllProducts() as any
if (response && response.data && Array.isArray(response.data)) {
productList.value = response.data
}
} catch (error) {
console.error('加载产品列表失败:', error)
showMessage('加载产品列表失败', 'error')
}
}
// 生命周期
onMounted(() => {
fetchDeliveries()
loadDealers()
loadProducts()
})
</script>
......@@ -958,6 +1335,11 @@ onMounted(() => {
color: white;
}
.edit-btn {
background: #ffc107;
color: #333;
}
.delete-btn {
background: #dc3545;
color: white;
......@@ -1256,6 +1638,23 @@ onMounted(() => {
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
}
.form-select {
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
width: 100%;
box-sizing: border-box;
background-color: #fff;
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
}
.form-actions {
display: flex;
justify-content: flex-end;
......@@ -1337,13 +1736,16 @@ onMounted(() => {
min-width: 0;
}
/* 产品名称字段 - 占用最多空间(现在在第一位) */
.order-item .form-item:nth-child(1) {
flex: 0 0 120px;
flex: 1 1 180px !important;
min-width: 120px !important;
max-width: 200px;
}
/* 产品编码字段 - 固定宽度(现在在第二位) */
.order-item .form-item:nth-child(2) {
flex: 1 1 200px;
min-width: 150px;
flex: 0 0 140px;
}
.order-item .form-item:nth-child(3) {
......
......@@ -5,9 +5,9 @@
<div class="search-row">
<div class="search-item">
<label>发票编号:</label>
<input
v-model="searchParams.invoiceNo"
class="search-input"
<input
v-model="searchParams.invoiceNo"
class="search-input"
placeholder="请输入发票编号"
/>
</div>
......@@ -48,7 +48,7 @@
<div class="search-item">
<label>开票开始日期:</label>
<input
v-model="searchParams.invoiceStartDate"
v-model="searchParams.invoiceStartDate"
class="search-input"
type="date"
/>
......@@ -56,7 +56,7 @@
<div class="search-item">
<label>开票结束日期:</label>
<input
v-model="searchParams.invoiceEndDate"
v-model="searchParams.invoiceEndDate"
class="search-input"
type="date"
/>
......@@ -101,7 +101,7 @@
<th>开票日期</th>
<th>发票金额</th>
<th>开票状态</th>
<th>上传时间</th>
<th>开票时间</th>
<th>关联订单编号</th>
<th>操作</th>
</tr>
......@@ -187,20 +187,20 @@
<form @submit.prevent="handleSubmit">
<div class="form-row">
<div class="form-item">
<label class="required">订单编号:</label>
<label class="required">发票号码:</label>
<input
v-model="formData.orderNo"
v-model="formData.invoiceNo"
class="form-input"
placeholder="请输入订单编号"
placeholder="请输入发票号码"
required
/>
</div>
<div class="form-item">
<label class="required">发票号码:</label>
<label class="required">订单编号:</label>
<input
v-model="formData.invoiceNo"
v-model="formData.orderNo"
class="form-input"
placeholder="请输入发票号码"
placeholder="请输入订单编号"
required
/>
</div>
......@@ -215,27 +215,28 @@
/>
</div>
<div class="form-item">
<label class="required">经销商编码:</label>
<input
v-model="formData.dealerCode"
class="form-input"
placeholder="请输入经销商编码"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">经销商名称:</label>
<input
<select
v-model="formData.dealerName"
class="form-input"
placeholder="请输入经销商名称"
@change="handleDealerChange"
required
/>
>
<option value="">请选择经销商</option>
<option
v-for="dealer in dealerList"
:key="dealer.dealerId"
:value="dealer.dealerName"
>
{{ dealer.dealerName }}
</option>
</select>
</div>
</div>
<div class="form-row">
<input v-model="formData.dealerCode" type="hidden"/>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">开票日期:</label>
<input
......@@ -292,6 +293,23 @@
<div class="invoice-items-container">
<div v-for="(item, index) in formData.invoiceItems" :key="index" class="invoice-item">
<div class="item-row-single">
<div class="form-item" style="flex: 1 1 200px; min-width: 150px;">
<label>产品名称:</label>
<select
v-model="item.productName"
class="form-input"
@change="handleProductChange(index)"
>
<option value="">请选择产品</option>
<option
v-for="product in productList"
:key="product.productId"
:value="product.productName"
>
{{ product.productName }}
</option>
</select>
</div>
<div class="form-item" style="flex: 0 0 120px;">
<label>产品编码:</label>
<input
......@@ -300,14 +318,6 @@
placeholder="产品编码"
/>
</div>
<div class="form-item" style="flex: 1 1 200px; min-width: 150px;">
<label>产品名称:</label>
<input
v-model="item.productName"
class="form-input"
placeholder="产品名称"
/>
</div>
<div class="form-item" style="flex: 0 0 80px;">
<label>数量:</label>
<input
......@@ -460,8 +470,8 @@
<table class="detail-table">
<thead>
<tr>
<th>产品编码</th>
<th>产品名称</th>
<th>产品编码</th>
<th>数量</th>
<th>单价</th>
<th>金额</th>
......@@ -471,8 +481,8 @@
</thead>
<tbody>
<tr v-for="item in invoiceDetail.invoiceItems" :key="item.invoiceItemId">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.productCode }}</td>
<td>{{ item.invoiceQty }}</td>
<td>{{ formatCurrency(item.unitPriceNoTax) }}</td>
<td class="amount-text">{{ formatCurrency(item.amountNoTax) }}</td>
......@@ -508,6 +518,9 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { invoiceApi, type InvoiceInfo, type InvoiceQueryReq } from '../../api/invoice'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
import type { ProductSimpleRes } from '../../api/product'
// 响应式数据
const loading = ref(false)
......@@ -516,6 +529,8 @@ const showDialog = ref(false)
const showDetailDialog = ref(false)
const isEdit = ref(false)
const invoiceDetail = ref<InvoiceInfo | null>(null)
const dealerList = ref<DealerInfo[]>([])
const productList = ref<ProductSimpleRes[]>([])
// 搜索参数
const searchParams = reactive({
......@@ -950,6 +965,48 @@ const handleTableViewToggle = () => {
showMessage('视图切换功能开发中...', 'warning')
}
// 加载经销商列表
const loadDealers = async () => {
try {
const response = await dealerApi.getAllDealers() as any
if (response && response.data && Array.isArray(response.data)) {
dealerList.value = response.data
}
} catch (error) {
console.error('加载经销商列表失败:', error)
showMessage('加载经销商列表失败', 'error')
}
}
// 经销商选择变化处理
const handleDealerChange = () => {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
if (selectedDealer) {
formData.dealerCode = selectedDealer.dealerCode
}
}
// 加载产品列表
const loadProducts = async () => {
try {
const response = await productApi.getAllProducts() as any
if (response && response.data && Array.isArray(response.data)) {
productList.value = response.data
}
} catch (error) {
console.error('加载产品列表失败:', error)
showMessage('加载产品列表失败', 'error')
}
}
// 产品选择变化处理
const handleProductChange = (index: number) => {
const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === formData.invoiceItems[index].productName)
if (selectedProduct) {
formData.invoiceItems[index].productCode = selectedProduct.productCode
}
}
const closeDialog = () => {
showDialog.value = false
}
......@@ -962,6 +1019,8 @@ const closeDetailDialog = () => {
// 生命周期
onMounted(() => {
fetchOrders()
loadDealers()
loadProducts()
})
</script>
......
......@@ -235,25 +235,25 @@
/>
</div>
<div class="form-item">
<label class="required">经销商编码:</label>
<input
v-model="formData.dealerCode"
class="form-input"
placeholder="请输入经销商编码"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">经销商名称:</label>
<input
<select
v-model="formData.dealerName"
class="form-input"
placeholder="请输入经销商名称"
class="form-select"
@change="handleDealerChange"
required
/>
>
<option value="">请选择经销商</option>
<option
v-for="dealer in dealerList"
:key="dealer.dealerId"
:value="dealer.dealerName"
>
{{ dealer.dealerName }}
</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">订单日期:</label>
<input
......@@ -263,19 +263,19 @@
required
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label class="required">订单总金额:</label>
<label>订单总金额:</label>
<input
v-model.number="formData.totalAmount"
class="form-input"
class="form-input readonly"
type="number"
step="0.01"
placeholder="请输入订单总金额"
required
placeholder="自动计算"
readonly
/>
</div>
</div>
<div class="form-row">
<div class="form-item">
<label>返利金额:</label>
<input
......@@ -286,6 +286,11 @@
placeholder="请输入返利金额"
/>
</div>
<!-- 经销商编码字段隐藏,通过选择经销商名称自动填充 -->
<input
v-model="formData.dealerCode"
type="hidden"
/>
</div>
</div>
......@@ -297,25 +302,34 @@
</div>
<div class="order-items-container">
<div v-for="(item, index) in formData.orderItems" :key="index" class="order-item">
<div class="item-row-single">
<div class="form-item">
<label class="required">产品编码:</label>
<input
v-model="item.productCode"
class="form-input"
placeholder="请输入产品编码"
required
/>
</div>
<div class="form-item">
<label class="required">产品名称:</label>
<input
v-model="item.productName"
class="form-input"
placeholder="请输入产品名称"
required
/>
</div>
<div class="item-row-single">
<div class="form-item">
<label class="required">产品名称:</label>
<select
v-model="item.productName"
class="form-select"
@change="handleProductChange(index)"
required
>
<option value="">请选择产品</option>
<option
v-for="product in productList"
:key="product.productId"
:value="product.productName"
>
{{ product.productName }}
</option>
</select>
</div>
<div class="form-item">
<label class="required">产品编码:</label>
<input
v-model="item.productCode"
class="form-input readonly"
placeholder="自动填充"
readonly
/>
</div>
<div class="form-item">
<label class="required">商品数量:</label>
<input
......@@ -343,9 +357,10 @@
<label>明细金额:</label>
<input
v-model.number="item.itemAmount"
class="form-input"
class="form-input readonly"
type="number"
step="0.01"
placeholder="自动计算"
readonly
/>
</div>
......@@ -420,8 +435,8 @@
<table class="detail-table">
<thead>
<tr>
<th>产品编码</th>
<th>产品名称</th>
<th>产品编码</th>
<th>商品数量</th>
<th>单价</th>
<th>明细金额</th>
......@@ -429,8 +444,8 @@
</thead>
<tbody>
<tr v-for="item in orderDetail.orderItems" :key="item.itemId">
<td>{{ item.productCode }}</td>
<td>{{ item.productName }}</td>
<td>{{ item.productCode }}</td>
<td>{{ item.productQty }}</td>
<td>{{ formatCurrency(item.unitPrice) }}</td>
<td>{{ formatCurrency(item.itemAmount) }}</td>
......@@ -448,6 +463,9 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { orderApi, type OrderInfo, type OrderQueryReq, type OrderAddReq, type OrderUpdateReq, type OrderItemAddReq } from '../../api/order'
import { dealerApi, type DealerInfo } from '../../api/dealer'
import { productApi } from '../../api/product'
import type { ProductSimpleRes } from '../../api/product'
// 响应式数据
const loading = ref(false)
......@@ -457,6 +475,8 @@ const showDialog = ref(false)
const showDetailDialog = ref(false)
const isEdit = ref(false)
const orderDetail = ref<OrderInfo | null>(null)
const dealerList = ref<DealerInfo[]>([])
const productList = ref<ProductSimpleRes[]>([])
// 搜索参数
const searchParams = reactive<OrderQueryReq>({
......@@ -697,6 +717,15 @@ const handleEdit = async (order: OrderInfo) => {
verifyStatus: response.verifyStatus,
orderItems: response.orderItems || []
})
// 编辑时重新计算总金额,确保数据一致性
calculateTotalAmount()
// 编辑时设置经销商编码(如果经销商名称已选择)
if (formData.dealerName) {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
if (selectedDealer) {
formData.dealerCode = selectedDealer.dealerCode
}
}
showDialog.value = true
} else {
showMessage('获取订单详情失败', 'error')
......@@ -802,10 +831,14 @@ const addOrderItem = () => {
unitPrice: 0,
itemAmount: 0
})
// 重新计算总金额
calculateTotalAmount()
}
const removeOrderItem = (index: number) => {
formData.orderItems.splice(index, 1)
// 重新计算总金额
calculateTotalAmount()
}
const calculateItemTotal = (index: number) => {
......@@ -813,6 +846,16 @@ const calculateItemTotal = (index: number) => {
if (item.productQty && item.unitPrice) {
item.itemAmount = item.productQty * item.unitPrice
}
// 重新计算订单总金额
calculateTotalAmount()
}
// 计算订单总金额
const calculateTotalAmount = () => {
const total = formData.orderItems.reduce((sum, item) => {
return sum + (item.itemAmount || 0)
}, 0)
formData.totalAmount = total
}
const handleSubmit = async () => {
......@@ -874,6 +917,48 @@ const handleSubmit = async () => {
}
}
// 经销商选择处理
const handleDealerChange = () => {
const selectedDealer = dealerList.value.find(dealer => dealer.dealerName === formData.dealerName)
if (selectedDealer) {
formData.dealerCode = selectedDealer.dealerCode
}
}
// 加载经销商列表
const loadDealers = async () => {
try {
const response = await dealerApi.getAllDealers() as any
if (response && response.data && Array.isArray(response.data)) {
dealerList.value = response.data
}
} catch (error) {
console.error('加载经销商列表失败:', error)
showMessage('加载经销商列表失败', 'error')
}
}
// 产品选择处理
const handleProductChange = (index: number) => {
const selectedProduct = productList.value.find((product: ProductSimpleRes) => product.productName === formData.orderItems[index].productName)
if (selectedProduct) {
formData.orderItems[index].productCode = selectedProduct.productCode
}
}
// 加载产品列表
const loadProducts = async () => {
try {
const response = await productApi.getAllProducts() as any
if (response && response.data && Array.isArray(response.data)) {
productList.value = response.data
}
} catch (error) {
console.error('加载产品列表失败:', error)
showMessage('加载产品列表失败', 'error')
}
}
const closeDialog = () => {
showDialog.value = false
formData.orderItems = []
......@@ -887,6 +972,8 @@ const closeDetailDialog = () => {
// 生命周期
onMounted(() => {
fetchOrders()
loadDealers()
loadProducts()
})
</script>
......@@ -1360,7 +1447,26 @@ onMounted(() => {
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
width: 200px;
width: 250px;
height: 32px;
box-sizing: border-box;
}
.form-select {
background-color: #fff;
cursor: pointer;
}
.form-select:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.form-input.readonly {
background-color: #f5f5f5;
color: #666;
cursor: not-allowed;
}
/* 订单明细中的输入框样式 - 优化空间利用 */
......@@ -1378,30 +1484,38 @@ onMounted(() => {
min-width: 0;
}
/* 产品编码字段 */
/* 产品名称字段 - 占用最多空间,增加长度(现在在第一位) */
.order-item .form-item:nth-child(1) {
flex: 0 0 120px;
flex: 1.5 1 200px;
min-width: 150px;
}
/* 产品名称字段 - 占用更多空间 */
/* 产品编码字段 - 固定宽度(现在在第二位),往左移动 */
.order-item .form-item:nth-child(2) {
flex: 1 1 200px;
min-width: 150px;
flex: 0 0 180px;
margin-left: -15px;
}
/* 数量字段 */
/* 产品编码输入框宽度调整为90% */
.order-item .form-item:nth-child(2) .form-input {
width: 90%;
}
/* 数量字段 - 较小宽度,往左移动 */
.order-item .form-item:nth-child(3) {
flex: 0 0 80px;
margin-left: -10px;
}
/* 单价字段 */
/* 单价字段 - 中等宽度,往左移动 */
.order-item .form-item:nth-child(4) {
flex: 0 0 100px;
flex: 0 0 110px;
margin-left: -5px;
}
/* 明细金额字段 */
/* 明细金额字段 - 中等宽度 */
.order-item .form-item:nth-child(5) {
flex: 0 0 100px;
flex: 0 0 110px;
}
.order-item .form-item label {
......@@ -1431,16 +1545,19 @@ onMounted(() => {
.order-items-container {
border: 1px solid #eee;
border-radius: 4px;
padding: 6px;
padding: 8px;
background: #f8f9fa;
width: 100%;
}
.order-item {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
padding: 6px;
margin-bottom: 6px;
padding: 8px;
margin-bottom: 8px;
width: 100%;
box-sizing: border-box;
}
.order-item:last-child {
......@@ -1461,22 +1578,28 @@ onMounted(() => {
/* 单行布局 - 所有字段在一行显示,优化空间利用 */
.item-row-single {
display: flex;
gap: 4px;
gap: 8px;
align-items: end;
flex-wrap: nowrap;
justify-content: space-between;
width: 100%;
}
.remove-item-btn {
padding: 4px 8px;
padding: 6px 12px;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 11px;
height: 28px;
height: 32px;
align-self: end;
transition: background-color 0.2s;
flex-shrink: 0;
}
.remove-item-btn:hover {
background: #c82333;
}
.form-actions {
......