|
|
@ -141,6 +141,7 @@ |
|
|
<div class="col-required-qty">需求数量</div> |
|
|
<div class="col-required-qty">需求数量</div> |
|
|
<div class="col-picked-qty">已领数量</div> |
|
|
<div class="col-picked-qty">已领数量</div> |
|
|
<div class="col-picked-qty">扫描数量</div> |
|
|
<div class="col-picked-qty">扫描数量</div> |
|
|
|
|
|
<div class="col-action">操作</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="table-body"> |
|
|
<div class="table-body"> |
|
|
@ -157,6 +158,9 @@ |
|
|
<div class="col-required-qty">{{ item.requiredQty || 0 }}</div> |
|
|
<div class="col-required-qty">{{ item.requiredQty || 0 }}</div> |
|
|
<div class="col-picked-qty">{{ item.pickedQty || 0 }}</div> |
|
|
<div class="col-picked-qty">{{ item.pickedQty || 0 }}</div> |
|
|
<div class="col-picked-qty">{{ item.scansQty || 0 }}</div> |
|
|
<div class="col-picked-qty">{{ item.scansQty || 0 }}</div> |
|
|
|
|
|
<div class="col-action"> |
|
|
|
|
|
<span class="row-action-link" @click.stop="openAlternativeDialog(item)">替代</span> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
@ -173,13 +177,67 @@ |
|
|
<button class="btn-close" @click="closeMaterialDialog">关闭</button> |
|
|
<button class="btn-close" @click="closeMaterialDialog">关闭</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 替代料弹窗(布局与物料清单一致) --> |
|
|
|
|
|
<div v-if="showAlternativeDialog" class="alternative-material-overlay"> |
|
|
|
|
|
<div class="material-modal"> |
|
|
|
|
|
<div class="modal-header"> |
|
|
|
|
|
<span class="modal-title">替代料</span> |
|
|
|
|
|
<i class="el-icon-close close-btn" @click="closeAlternativeDialog"></i> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="modal-body"> |
|
|
|
|
|
<div v-if="alternativeListLoading" class="loading-container"> |
|
|
|
|
|
<i class="el-icon-loading"></i> |
|
|
|
|
|
<span>加载中...</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="alternativeMaterialList.length > 0" class="material-table"> |
|
|
|
|
|
<div class="table-header"> |
|
|
|
|
|
<div class="col-no">NO.</div> |
|
|
|
|
|
<div class="col-material-code">物料编码</div> |
|
|
|
|
|
<div class="col-part-name">物料名称</div> |
|
|
|
|
|
<div class="col-required-qty">需求数量</div> |
|
|
|
|
|
<div class="col-picked-qty">已领数量</div> |
|
|
|
|
|
<div class="col-picked-qty">扫描数量</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="table-body"> |
|
|
|
|
|
<div |
|
|
|
|
|
v-for="(item, index) in alternativeMaterialList" |
|
|
|
|
|
:key="index" |
|
|
|
|
|
class="table-row" |
|
|
|
|
|
> |
|
|
|
|
|
<div class="col-no">{{ index + 1 }}</div> |
|
|
|
|
|
<div class="col-material-code clickable-part" @click="showStockDialogFn(item)">{{ item.materialCode || item.partNo }}</div> |
|
|
|
|
|
<div class="col-part-name"> |
|
|
|
|
|
<span class="part-name-text" @click.stop="showPartNameTip(item.materialName, $event)">{{ item.materialName || '-' }}</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="col-required-qty">{{ item.requiredQty || 0 }}</div> |
|
|
|
|
|
<div class="col-picked-qty">{{ item.pickedQty || 0 }}</div> |
|
|
|
|
|
<div class="col-picked-qty">{{ item.scansQty || 0 }}</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<!-- 物料名称提示框 --> |
|
|
|
|
|
<div v-if="showPartNameTooltip" class="part-name-tooltip" :style="tooltipStyle" @click.stop> |
|
|
|
|
|
<div class="tooltip-content">{{ currentPartName }}</div> |
|
|
|
|
|
|
|
|
<div v-else class="empty-material"> |
|
|
|
|
|
<i class="el-icon-document"></i> |
|
|
|
|
|
<p>暂无物料数据</p> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="modal-footer"> |
|
|
|
|
|
<button class="btn-close" @click="closeAlternativeDialog">关闭</button> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 物料名称提示框(主清单 / 替代料共用) --> |
|
|
|
|
|
<div v-if="showPartNameTooltip" class="part-name-tooltip" :style="tooltipStyle" @click.stop> |
|
|
|
|
|
<div class="tooltip-content">{{ currentPartName }}</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<!-- 可用库存弹窗 --> |
|
|
<!-- 可用库存弹窗 --> |
|
|
<div v-if="showStockDialog" class="stock-overlay"> |
|
|
<div v-if="showStockDialog" class="stock-overlay"> |
|
|
<div class="stock-modal"> |
|
|
<div class="stock-modal"> |
|
|
@ -216,7 +274,7 @@ |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
<script> |
|
|
<script> |
|
|
import { getOutboundDetails, validateLabelWithOutbound, confirmProductionPicking, getOutboundMaterialList, getScannedLabelList } from "@/api/production.js"; |
|
|
|
|
|
|
|
|
import { getOutboundDetails, validateLabelWithOutbound, confirmProductionPicking, getOutboundMaterialList, getBomAlternativePartDetails, getScannedLabelList } from "@/api/production.js"; |
|
|
import { getInventoryStock } from "@/api/inbound.js"; |
|
|
import { getInventoryStock } from "@/api/inbound.js"; |
|
|
import { getCurrentWarehouse } from '@/utils' |
|
|
import { getCurrentWarehouse } from '@/utils' |
|
|
import moment from 'moment'; |
|
|
import moment from 'moment'; |
|
|
@ -232,6 +290,10 @@ export default { |
|
|
showMaterialDialog: false, |
|
|
showMaterialDialog: false, |
|
|
materialList: [], |
|
|
materialList: [], |
|
|
materialListLoading: false, |
|
|
materialListLoading: false, |
|
|
|
|
|
showAlternativeDialog: false, |
|
|
|
|
|
alternativeMaterialList: [], |
|
|
|
|
|
alternativeListLoading: false, |
|
|
|
|
|
substituteSourcePartNo: '', |
|
|
isRemoveMode: false, // 默认为添加模式 |
|
|
isRemoveMode: false, // 默认为添加模式 |
|
|
relatedNo: '', |
|
|
relatedNo: '', |
|
|
showStockDialog: false, |
|
|
showStockDialog: false, |
|
|
@ -413,6 +475,53 @@ export default { |
|
|
this.hidePartNameTip(); |
|
|
this.hidePartNameTip(); |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
openAlternativeDialog(rowItem) { |
|
|
|
|
|
this.substituteSourcePartNo = rowItem.materialCode || rowItem.partNo; |
|
|
|
|
|
if (!this.substituteSourcePartNo) { |
|
|
|
|
|
this.$message.error('无法识别物料编码'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
this.showAlternativeDialog = true; |
|
|
|
|
|
this.loadAlternativeMaterialList(); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
loadAlternativeMaterialList() { |
|
|
|
|
|
if (!this.outboundInfo.site || !this.buNo || !this.outboundNo || !this.substituteSourcePartNo) { |
|
|
|
|
|
this.$message.error('缺少必要参数,无法获取替代料'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
this.alternativeListLoading = true; |
|
|
|
|
|
const params = { |
|
|
|
|
|
site: this.outboundInfo.site, |
|
|
|
|
|
buNo: this.buNo, |
|
|
|
|
|
outboundNo: this.outboundNo, |
|
|
|
|
|
warehouseId: getCurrentWarehouse(), |
|
|
|
|
|
relatedNo: this.relatedNo, |
|
|
|
|
|
partNo: this.substituteSourcePartNo |
|
|
|
|
|
}; |
|
|
|
|
|
getBomAlternativePartDetails(params).then(({ data }) => { |
|
|
|
|
|
this.alternativeListLoading = false; |
|
|
|
|
|
if (data && data.code === 0) { |
|
|
|
|
|
this.alternativeMaterialList = data.data || []; |
|
|
|
|
|
} else { |
|
|
|
|
|
this.$message.error(data.msg || '获取替代料失败'); |
|
|
|
|
|
this.alternativeMaterialList = []; |
|
|
|
|
|
} |
|
|
|
|
|
}).catch(error => { |
|
|
|
|
|
this.alternativeListLoading = false; |
|
|
|
|
|
console.error('获取替代料失败:', error); |
|
|
|
|
|
this.$message.error('获取替代料失败'); |
|
|
|
|
|
this.alternativeMaterialList = []; |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
closeAlternativeDialog() { |
|
|
|
|
|
this.showAlternativeDialog = false; |
|
|
|
|
|
this.hidePartNameTip(); |
|
|
|
|
|
this.alternativeMaterialList = []; |
|
|
|
|
|
this.substituteSourcePartNo = ''; |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
// 显示物料名称提示框 |
|
|
// 显示物料名称提示框 |
|
|
showPartNameTip(partName, event) { |
|
|
showPartNameTip(partName, event) { |
|
|
if (!partName || partName === '-') return; |
|
|
if (!partName || partName === '-') return; |
|
|
@ -457,7 +566,7 @@ export default { |
|
|
orderNo: this.outboundInfo.relatedNo, |
|
|
orderNo: this.outboundInfo.relatedNo, |
|
|
orderLineNo: '', |
|
|
orderLineNo: '', |
|
|
partNo: partNo, |
|
|
partNo: partNo, |
|
|
warehouseId: localStorage.getItem('warehouseId') || '' |
|
|
|
|
|
|
|
|
warehouseId: getCurrentWarehouse() || '' |
|
|
}; |
|
|
}; |
|
|
getInventoryStock(params).then(({ data }) => { |
|
|
getInventoryStock(params).then(({ data }) => { |
|
|
this.stockLoading = false; |
|
|
this.stockLoading = false; |
|
|
@ -1053,7 +1162,36 @@ export default { |
|
|
|
|
|
|
|
|
.clickable-part { color: #17B3A3; font-weight: 500; cursor: pointer; text-decoration: underline; } |
|
|
.clickable-part { color: #17B3A3; font-weight: 500; cursor: pointer; text-decoration: underline; } |
|
|
.clickable-part:hover { color: #0d8f7f; } |
|
|
.clickable-part:hover { color: #0d8f7f; } |
|
|
.stock-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 20px; } |
|
|
|
|
|
|
|
|
.alternative-material-overlay { |
|
|
|
|
|
position: fixed; |
|
|
|
|
|
top: 0; |
|
|
|
|
|
left: 0; |
|
|
|
|
|
right: 0; |
|
|
|
|
|
bottom: 0; |
|
|
|
|
|
background: rgba(0, 0, 0, 0.5); |
|
|
|
|
|
z-index: 10040; |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
padding: 20px; |
|
|
|
|
|
} |
|
|
|
|
|
.material-table .col-action { |
|
|
|
|
|
flex: 0.55; |
|
|
|
|
|
text-align: center; |
|
|
|
|
|
min-width: 44px; |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
flex-shrink: 0; |
|
|
|
|
|
} |
|
|
|
|
|
.row-action-link { |
|
|
|
|
|
color: #17B3A3; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
text-decoration: underline; |
|
|
|
|
|
} |
|
|
|
|
|
.row-action-link:active { |
|
|
|
|
|
color: #0d8f7f; |
|
|
|
|
|
} |
|
|
|
|
|
.stock-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10050; display: flex; align-items: center; justify-content: center; padding: 20px; } |
|
|
.stock-modal { background: white; border-radius: 12px; width: 100%; max-width: 800px; max-height: 80vh; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); overflow: hidden; display: flex; flex-direction: column; } |
|
|
.stock-modal { background: white; border-radius: 12px; width: 100%; max-width: 800px; max-height: 80vh; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); overflow: hidden; display: flex; flex-direction: column; } |
|
|
.stock-modal .modal-header { background: #17B3A3; color: white; padding: 5px 16px; display: flex; justify-content: space-between; align-items: center; min-height: 28px; } |
|
|
.stock-modal .modal-header { background: #17B3A3; color: white; padding: 5px 16px; display: flex; justify-content: space-between; align-items: center; min-height: 28px; } |
|
|
.stock-modal .modal-body { flex: 1; overflow: auto; padding: 0; } |
|
|
.stock-modal .modal-body { flex: 1; overflow: auto; padding: 0; } |
|
|
@ -1113,7 +1251,7 @@ export default { |
|
|
/* 物料名称提示框样式 */ |
|
|
/* 物料名称提示框样式 */ |
|
|
.part-name-tooltip { |
|
|
.part-name-tooltip { |
|
|
position: fixed; |
|
|
position: fixed; |
|
|
z-index: 10001; |
|
|
|
|
|
|
|
|
z-index: 10060; |
|
|
transform: translateY(-100%); |
|
|
transform: translateY(-100%); |
|
|
max-width: 200px; |
|
|
max-width: 200px; |
|
|
animation: tooltipFadeIn 0.2s ease; |
|
|
animation: tooltipFadeIn 0.2s ease; |
|
|
|