|
|
|
@ -23,55 +23,151 @@ |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 出库单列表 --> |
|
|
|
<div class="content-area"> |
|
|
|
<div class="outbound-list"> |
|
|
|
<!-- 当没有选中出库单时,显示所有出库单 --> |
|
|
|
<template v-if="!selectedOutbound"> |
|
|
|
<div |
|
|
|
v-for="(item, index) in outboundList" |
|
|
|
:key="index" |
|
|
|
class="outbound-card" |
|
|
|
@click="selectOutbound(item)" |
|
|
|
> |
|
|
|
<div class="card-title"> |
|
|
|
<span class="title-label">出库单号:{{ item.outboundNo }}</span> |
|
|
|
<span class="title-value">{{ item.relatedNo }}</span> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="card-details"> |
|
|
|
<div class="detail-item"> |
|
|
|
<div class="detail-label">标签张数</div> |
|
|
|
<div class="detail-value"> |
|
|
|
<span class="qualified">{{ item.pickedLabels }}</span><span class="total">{{ item.totalLabels }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="detail-item"> |
|
|
|
<div class="detail-label">物料总数</div> |
|
|
|
<div class="detail-value"> |
|
|
|
<span class="qualified">{{ item.pickedQty }}</span><span class="total">{{ item.totalQty }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="detail-item"> |
|
|
|
<div class="detail-label">状态</div> |
|
|
|
<div class="detail-value">{{ item.status || "待出库" }}</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 空状态 --> |
|
|
|
<div v-if="outboundList.length === 0 && !loading" class="empty-state"> |
|
|
|
<i class="el-icon-box"></i> |
|
|
|
<p>暂无待领料出库单</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 加载状态 --> |
|
|
|
<div v-if="loading" class="loading-state"> |
|
|
|
<i class="el-icon-loading"></i> |
|
|
|
<p>加载中...</p> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<!-- 当选中出库单时,只显示选中的出库单 --> |
|
|
|
<div |
|
|
|
v-for="(item, index) in outboundList" |
|
|
|
:key="index" |
|
|
|
class="outbound-card" |
|
|
|
@click="goToPickingPage(item)" |
|
|
|
v-else |
|
|
|
class="outbound-card selected" |
|
|
|
@click="deselectOutbound" |
|
|
|
> |
|
|
|
<div class="card-title"> |
|
|
|
<span class="title-label">出库单号</span> |
|
|
|
<span class="title-value">{{ item.outboundNo }}</span> |
|
|
|
<span class="title-label">出库单号:{{ selectedOutbound.outboundNo }}</span> |
|
|
|
<span class="title-value">{{ selectedOutbound.relatedNo }}</span> |
|
|
|
<i class="el-icon-arrow-left back-icon" title="返回列表"></i> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="card-details"> |
|
|
|
<div class="detail-item"> |
|
|
|
<div class="detail-label">关联单号</div> |
|
|
|
<div class="detail-value">{{ item.relatedNo }}</div> |
|
|
|
</div> |
|
|
|
<div class="detail-item"> |
|
|
|
<div class="detail-label">标签张数</div> |
|
|
|
<div class="detail-value"> |
|
|
|
<span class="qualified">{{ item.pickedLabels }}</span><span class="total">{{ item.totalLabels }}</span> |
|
|
|
<span class="qualified">{{ selectedOutbound.pickedLabels }}</span><span class="total">{{ selectedOutbound.totalLabels }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="detail-item"> |
|
|
|
<div class="detail-label">物料总数</div> |
|
|
|
<div class="detail-value"> |
|
|
|
<span class="qualified">{{ item.pickedQty }}</span><span class="total">{{ item.totalQty }}</span> |
|
|
|
<span class="qualified">{{ selectedOutbound.pickedQty }}</span><span class="total">{{ selectedOutbound.totalQty }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="detail-item"> |
|
|
|
<div class="detail-label">状态</div> |
|
|
|
<div class="detail-value">{{ selectedOutbound.status || "待出库" }}</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 空状态 --> |
|
|
|
<div v-if="outboundList.length === 0 && !loading" class="empty-state"> |
|
|
|
<i class="el-icon-box"></i> |
|
|
|
<p>暂无待领料出库单</p> |
|
|
|
</div> |
|
|
|
<!-- 物料详情列表 --> |
|
|
|
<div |
|
|
|
class="content-area" |
|
|
|
v-if="selectedOutbound && materialList.length > 0" |
|
|
|
> |
|
|
|
<div |
|
|
|
v-for="(material, index) in materialList" |
|
|
|
:key="index" |
|
|
|
class="material-card" |
|
|
|
@click="selectMaterial(material)" |
|
|
|
> |
|
|
|
<!-- 第一行:工单号(左)| 需求数量(右) --> |
|
|
|
<div class="card-row"> |
|
|
|
<span class="field-item"> |
|
|
|
<span class="field-label">工单号:</span> |
|
|
|
<span class="field-value">{{ material.orderNo }}</span> |
|
|
|
</span> |
|
|
|
<span class="field-item"> |
|
|
|
<span class="field-label">需求数量:</span> |
|
|
|
<span class="field-value">{{ material.requiredQty || 0 }}</span> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 第二行:物料编码(左)| 单位(右) --> |
|
|
|
<div class="card-row"> |
|
|
|
<span class="field-item"> |
|
|
|
<span class="field-label">物料编码:</span> |
|
|
|
<span class="field-value">{{ material.partNo }}</span> |
|
|
|
</span> |
|
|
|
<span class="field-item"> |
|
|
|
<span class="field-label">单位:</span> |
|
|
|
<span class="field-value">{{ material.umId}}</span> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 第三行:物料名称(全宽) --> |
|
|
|
<div class="card-row-full"> |
|
|
|
<span class="field-label">物料名称:</span> |
|
|
|
<span class="field-value">{{ material.materialDesc || material.partDesc }}</span> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 加载状态 --> |
|
|
|
<div v-if="loading" class="loading-state"> |
|
|
|
<i class="el-icon-loading"></i> |
|
|
|
<p>加载中...</p> |
|
|
|
<!-- 第四行:物料总数(全宽) --> |
|
|
|
<div class="card-row-full"> |
|
|
|
<span class="field-label">物料总数:</span> |
|
|
|
<span class="field-value">{{ material.pickedQty || 0 }}/{{ material.totalQty || 0 }}</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 物料列表加载状态 --> |
|
|
|
<div v-if="selectedOutbound && materialListLoading" class="loading-state"> |
|
|
|
<i class="el-icon-loading"></i> |
|
|
|
<p>加载物料详情中...</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 物料列表空状态 --> |
|
|
|
<div v-if="selectedOutbound && !materialListLoading && materialList.length === 0" class="empty-state"> |
|
|
|
<i class="el-icon-document"></i> |
|
|
|
<p>暂无物料详情</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
import { getProductionOutboundList } from "@/api/production.js"; |
|
|
|
import { getProductionOutboundList, getOutboundMaterialDetails } from "@/api/production.js"; |
|
|
|
import { getCurrentWarehouse } from '@/utils' |
|
|
|
import moment from 'moment'; |
|
|
|
|
|
|
|
@ -80,7 +176,10 @@ export default { |
|
|
|
return { |
|
|
|
searchCode: '', |
|
|
|
outboundList: [], |
|
|
|
loading: false |
|
|
|
loading: false, |
|
|
|
selectedOutbound: null, |
|
|
|
materialList: [], |
|
|
|
materialListLoading: false |
|
|
|
}; |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
@ -154,13 +253,63 @@ export default { |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 跳转到领料页面 |
|
|
|
goToPickingPage(item) { |
|
|
|
// 选择出库单 |
|
|
|
selectOutbound(item) { |
|
|
|
this.selectedOutbound = item; |
|
|
|
this.loadMaterialList(item); |
|
|
|
}, |
|
|
|
|
|
|
|
// 取消选择出库单 |
|
|
|
deselectOutbound() { |
|
|
|
this.selectedOutbound = null; |
|
|
|
this.materialList = []; |
|
|
|
}, |
|
|
|
|
|
|
|
// 加载物料列表 |
|
|
|
loadMaterialList(item) { |
|
|
|
this.materialListLoading = true; |
|
|
|
|
|
|
|
const params = { |
|
|
|
site: localStorage.getItem('site'), |
|
|
|
outboundNo: item.outboundNo |
|
|
|
}; |
|
|
|
|
|
|
|
getOutboundMaterialDetails(params).then(({ data }) => { |
|
|
|
this.materialListLoading = false; |
|
|
|
if (data && data.code === 0) { |
|
|
|
this.materialList = (data.data || []).map((item, index) => ({ |
|
|
|
...item, |
|
|
|
// 映射后端字段到前端字段 |
|
|
|
materialCode: item.partNo, |
|
|
|
materialDesc: item.partDescription, |
|
|
|
requiredQty: item.requiredQty, |
|
|
|
pickedQty: 0, // 后端暂时没有已领数量,默认为0 |
|
|
|
partNo: item.partNo, |
|
|
|
partDesc: item.partDescription, |
|
|
|
unit: '个' // 默认单位 |
|
|
|
})); |
|
|
|
} else { |
|
|
|
this.$message.error(data.msg || '获取物料明细失败'); |
|
|
|
this.materialList = []; |
|
|
|
} |
|
|
|
}).catch(error => { |
|
|
|
this.materialListLoading = false; |
|
|
|
console.error('获取物料明细失败:', error); |
|
|
|
this.$message.error('获取物料明细失败'); |
|
|
|
this.materialList = []; |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 选择物料,跳转到领料页面 |
|
|
|
selectMaterial(material) { |
|
|
|
this.$router.push({ |
|
|
|
name: 'productionPickingDetail', |
|
|
|
params: { |
|
|
|
buNo: item.buNo, |
|
|
|
outboundNo: item.outboundNo |
|
|
|
buNo: this.selectedOutbound.buNo, |
|
|
|
outboundNo: this.selectedOutbound.outboundNo, |
|
|
|
relatedNo: material.orderNo, |
|
|
|
materialCode: material.materialCode || material.partNo, |
|
|
|
materialDesc: material.materialDesc || material.partDesc |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
@ -226,9 +375,8 @@ export default { |
|
|
|
background: white; |
|
|
|
} |
|
|
|
|
|
|
|
/* 内容区域 */ |
|
|
|
.content-area { |
|
|
|
flex: 1; |
|
|
|
/* 出库单列表 */ |
|
|
|
.outbound-list { |
|
|
|
overflow-y: auto; |
|
|
|
padding: 12px 16px; |
|
|
|
} |
|
|
|
@ -242,6 +390,7 @@ export default { |
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
|
|
|
cursor: pointer; |
|
|
|
transition: all 0.2s ease; |
|
|
|
border: 2px solid transparent; |
|
|
|
} |
|
|
|
|
|
|
|
.outbound-card:hover { |
|
|
|
@ -249,13 +398,66 @@ export default { |
|
|
|
transform: translateY(-1px); |
|
|
|
} |
|
|
|
|
|
|
|
.outbound-card.selected { |
|
|
|
border-color: #17B3A3; |
|
|
|
background: #f0fffe; |
|
|
|
} |
|
|
|
|
|
|
|
.outbound-card:active { |
|
|
|
transform: translateY(0); |
|
|
|
} |
|
|
|
|
|
|
|
/* 内容区域 */ |
|
|
|
.content-area { |
|
|
|
flex: 1; |
|
|
|
overflow-y: auto; |
|
|
|
padding: 8px 0 12px 0; |
|
|
|
background: #f8f9fa; |
|
|
|
margin: 0 16px 12px 16px; |
|
|
|
border-radius: 0 0 8px 8px; |
|
|
|
} |
|
|
|
|
|
|
|
/* 物料卡片 */ |
|
|
|
.material-card { |
|
|
|
background: white; |
|
|
|
border-radius: 6px; |
|
|
|
margin-bottom: 8px; |
|
|
|
margin-left: 20px; |
|
|
|
margin-right: 16px; |
|
|
|
padding: 12px; |
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); |
|
|
|
cursor: pointer; |
|
|
|
transition: all 0.2s ease; |
|
|
|
border: 1px solid #e8f4f3; |
|
|
|
position: relative; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card::before { |
|
|
|
content: ''; |
|
|
|
position: absolute; |
|
|
|
left: -12px; |
|
|
|
top: 50%; |
|
|
|
transform: translateY(-50%); |
|
|
|
width: 4px; |
|
|
|
height: 20px; |
|
|
|
background: #17B3A3; |
|
|
|
border-radius: 2px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card:hover { |
|
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12); |
|
|
|
transform: translateY(-1px); |
|
|
|
border-color: #17B3A3; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card:active { |
|
|
|
transform: translateY(0); |
|
|
|
} |
|
|
|
|
|
|
|
/* 卡片标题 */ |
|
|
|
.card-title { |
|
|
|
margin-bottom: 12px; |
|
|
|
position: relative; |
|
|
|
} |
|
|
|
|
|
|
|
.title-label { |
|
|
|
@ -269,7 +471,103 @@ export default { |
|
|
|
font-size: 16px; |
|
|
|
font-weight: bold; |
|
|
|
color: #333; |
|
|
|
margin-left: 20px; |
|
|
|
margin-left: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.back-icon { |
|
|
|
position: absolute; |
|
|
|
right: 16px; |
|
|
|
top: 50%; |
|
|
|
transform: translateY(-50%); |
|
|
|
font-size: 18px; |
|
|
|
color: #17B3A3; |
|
|
|
cursor: pointer; |
|
|
|
transition: color 0.2s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.back-icon:hover { |
|
|
|
color: #0d8f7f; |
|
|
|
} |
|
|
|
|
|
|
|
/* 物料描述行 */ |
|
|
|
.part-desc-row { |
|
|
|
margin-bottom: 8px; |
|
|
|
padding: 0 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.desc-text { |
|
|
|
font-size: 11px; |
|
|
|
color: #666; |
|
|
|
line-height: 1.3; |
|
|
|
word-break: break-all; |
|
|
|
} |
|
|
|
|
|
|
|
/* 物料卡片内的字段行样式 */ |
|
|
|
.material-card .card-row { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
margin-bottom: 6px; |
|
|
|
gap: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card .card-row-full { |
|
|
|
margin-bottom: 6px; |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card .field-item { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
flex: 1; |
|
|
|
min-width: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card .field-label { |
|
|
|
font-size: 11px; |
|
|
|
color: #666; |
|
|
|
white-space: nowrap; |
|
|
|
margin-right: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card .field-value { |
|
|
|
font-size: 12px; |
|
|
|
color: #333; |
|
|
|
font-weight: 500; |
|
|
|
word-break: break-all; |
|
|
|
flex: 1; |
|
|
|
} |
|
|
|
|
|
|
|
/* 物料卡片内的详情样式 */ |
|
|
|
.material-card .card-details { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: flex-start; |
|
|
|
gap: 3px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card .detail-item { |
|
|
|
flex: 1; |
|
|
|
text-align: center; |
|
|
|
min-width: 45px; |
|
|
|
max-width: 60px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card .detail-label { |
|
|
|
font-size: 10px; |
|
|
|
color: #666; |
|
|
|
margin-bottom: 3px; |
|
|
|
line-height: 1.2; |
|
|
|
margin-left: -8px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card .detail-value { |
|
|
|
font-size: 11px; |
|
|
|
color: #333; |
|
|
|
font-weight: 500; |
|
|
|
line-height: 1.2; |
|
|
|
margin-left: -8px; |
|
|
|
} |
|
|
|
|
|
|
|
/* 卡片详情 */ |
|
|
|
@ -363,6 +661,7 @@ export default { |
|
|
|
margin: 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* 响应式设计 */ |
|
|
|
@media (max-width: 360px) { |
|
|
|
.header-bar { |
|
|
|
@ -373,14 +672,27 @@ export default { |
|
|
|
padding: 8px 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.outbound-list { |
|
|
|
padding: 8px 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.content-area { |
|
|
|
padding: 8px 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.outbound-card { |
|
|
|
.outbound-card, .material-card { |
|
|
|
padding: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card { |
|
|
|
margin-left: 16px; |
|
|
|
margin-right: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.content-area { |
|
|
|
margin: 0 12px 8px 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.card-details { |
|
|
|
flex-wrap: wrap; |
|
|
|
gap: 6px; |
|
|
|
@ -391,5 +703,10 @@ export default { |
|
|
|
margin-bottom: 6px; |
|
|
|
min-width: 50px; |
|
|
|
} |
|
|
|
|
|
|
|
.material-card .detail-item { |
|
|
|
flex: 0 0 45%; |
|
|
|
min-width: 40px; |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |