<template>
|
<div class="picking-confirm">
|
<div class="page-header">
|
<el-page-header @back="goBack">
|
<template #content>
|
<span class="title">出库拣选确认 - {{ orderInfo.orderNo }}</span>
|
</template>
|
</el-page-header>
|
</div>
|
|
<el-row :gutter="20" class="main-content">
|
<el-col :span="8">
|
<div class="scan-section">
|
<el-card header="扫码区域">
|
<el-form label-width="100px" size="small">
|
<el-form-item label="托盘条码">
|
<el-input
|
v-model="scanForm.palletCode"
|
placeholder="扫描或输入托盘条码"
|
@keyup.enter="handlePalletScan"
|
clearable
|
>
|
<template #append>
|
<el-button @click="handlePalletScan">确认</el-button>
|
</template>
|
</el-input>
|
</el-form-item>
|
|
<el-form-item label="物料条码">
|
<el-input
|
v-model="scanForm.barcode"
|
placeholder="扫描或输入物料条码"
|
@keyup.enter="handleBarcodeScan"
|
:disabled="!currentPallet"
|
clearable
|
>
|
<template #append>
|
<el-button @click="handleBarcodeScan" :disabled="!currentPallet">确认</el-button>
|
</template>
|
</el-input>
|
</el-form-item>
|
|
<el-form-item label="拣选数量">
|
<el-input-number
|
v-model="scanForm.quantity"
|
:min="1"
|
:max="maxPickQuantity"
|
:disabled="!currentLockInfo"
|
/>
|
</el-form-item>
|
|
<!-- 物料信息显示 -->
|
<el-form-item label="物料编码" v-if="currentMaterialInfo">
|
<el-input v-model="currentMaterialInfo.materielCode" readonly />
|
</el-form-item>
|
<el-form-item label="物料名称" v-if="currentMaterialInfo">
|
<el-input v-model="currentMaterialInfo.materielName" readonly />
|
</el-form-item>
|
</el-form>
|
|
<div class="current-info" v-if="currentPallet">
|
<p>当前托盘: {{ currentPallet.palletCode }}</p>
|
<p>货位: {{ currentPallet.locationCode }}</p>
|
<p>状态: {{ currentPallet.statusText }}</p>
|
</div>
|
</el-card>
|
|
<div class="action-buttons">
|
<el-button
|
type="warning"
|
@click="handleBackToStock"
|
:disabled="!currentPallet"
|
style="margin-bottom: 10px;"
|
>
|
回库
|
</el-button>
|
<el-button
|
type="success"
|
@click="handleDirectOutbound"
|
:disabled="!currentPallet"
|
style="margin-bottom: 10px;"
|
>
|
直接出库
|
</el-button>
|
<el-button
|
type="primary"
|
@click="handleOpenSplit"
|
:disabled="!currentLockInfo"
|
>
|
拆包
|
</el-button>
|
</div>
|
</div>
|
</el-col>
|
|
<el-col :span="16">
|
<el-card header="拣选结果">
|
<div class="summary-info">
|
<el-alert
|
:title="`未拣货: ${unpickedCount} 条, ${unpickedQuantity} 个`"
|
type="warning"
|
:closable="false"
|
/>
|
</div>
|
|
<vol-table
|
:data="pickedList"
|
:columns="pickedColumns"
|
:pagination="false"
|
:height="400"
|
>
|
<template #action="{ row }">
|
<el-button type="text" @click="handleCancelPick(row)">撤销</el-button>
|
</template>
|
</vol-table>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 拆包弹窗 -->
|
<vol-box
|
v-model="splitVisible"
|
title="拆包操作"
|
:width="600"
|
:height="500"
|
>
|
<SplitPackageModal
|
v-if="splitVisible"
|
:lockInfo="currentLockInfo"
|
@success="handleSplitSuccess"
|
@close="splitVisible = false"
|
/>
|
</vol-box>
|
</div>
|
</template>
|
|
<script>
|
import SplitPackageModal from './SplitPackageModal.vue'
|
|
export default {
|
components: { SplitPackageModal },
|
data() {
|
return {
|
orderInfo: {},
|
scanForm: {
|
palletCode: '',
|
barcode: '',
|
quantity: 1
|
},
|
currentPallet: null,
|
currentLockInfo: null,
|
currentMaterialInfo: null, // 新增:当前物料信息
|
pickedList: [],
|
pickedColumns: [
|
{ field: 'barcode', title: '物料条码', width: 150 },
|
{ field: 'materielCode', title: '物料编码', width: 120 },
|
{ field: 'materielName', title: '物料名称', width: 150 },
|
{ field: 'pickQuantity', title: '拣选数量', width: 100 },
|
{ field: 'palletCode', title: '托盘编号', width: 120 },
|
{ field: 'pickTime', title: '拣选时间', width: 160 },
|
{ field: 'operator', title: '操作人', width: 100 },
|
{ field: 'action', title: '操作', width: 80, slot: true }
|
],
|
splitVisible: false,
|
maxPickQuantity: 0,
|
allLockInfos: [] // 新增:保存所有锁定信息,用于条码匹配
|
}
|
},
|
computed: {
|
unpickedCount() {
|
return this.orderInfo.unpickedCount || 0
|
},
|
unpickedQuantity() {
|
return this.orderInfo.unpickedQuantity || 0
|
}
|
},
|
methods: {
|
goBack() {
|
this.$router.back()
|
},
|
|
async loadOrderInfo() {
|
const orderId = this.$route.query.orderId
|
if (!orderId) return
|
|
try {
|
const result = await this.http.get(`api/OutboundOrder/GetById?id=${orderId}`)
|
if (result.status) {
|
this.orderInfo = result.data
|
}
|
} catch (error) {
|
this.$message.error('加载出库单信息失败')
|
}
|
},
|
|
async handlePalletScan() {
|
if (!this.scanForm.palletCode) {
|
this.$message.warning('请输入托盘条码')
|
return
|
}
|
|
try {
|
const result = await this.http.get(
|
`api/OutboundPicking/GetPalletOutboundStatus?palletCode=${this.scanForm.palletCode}`
|
)
|
if (result.status) {
|
this.currentPallet = result.data
|
await this.loadPalletLockInfo()
|
this.$message.success(`托盘 ${this.scanForm.palletCode} 识别成功`)
|
} else {
|
this.$message.error(result.message)
|
}
|
} catch (error) {
|
this.$message.error('托盘识别失败')
|
}
|
},
|
|
async loadPalletLockInfo() {
|
if (!this.currentPallet) return
|
|
try {
|
const result = await this.http.get(
|
`api/OutboundPicking/GetPalletLockInfos?palletCode=${this.currentPallet.palletCode}`
|
)
|
if (result.status) {
|
this.allLockInfos = result.data
|
// 默认选择第一个锁定信息
|
if (this.allLockInfos.length > 0) {
|
this.currentLockInfo = this.allLockInfos[0]
|
this.currentMaterialInfo = {
|
materielCode: this.currentLockInfo.materielCode,
|
materielName: this.currentLockInfo.materielName
|
}
|
this.maxPickQuantity = this.currentLockInfo.assignQuantity - this.currentLockInfo.pickedQty
|
}
|
}
|
} catch (error) {
|
console.error('加载锁定信息失败:', error)
|
}
|
},
|
|
// 根据条码查找对应的锁定信息和物料信息
|
findLockInfoByBarcode(barcode) {
|
if (!this.allLockInfos || this.allLockInfos.length === 0) {
|
return null
|
}
|
|
// 首先精确匹配当前条码
|
let lockInfo = this.allLockInfos.find(x => x.currentBarcode === barcode)
|
if (lockInfo) {
|
return lockInfo
|
}
|
|
// 如果没有精确匹配,查找该条码对应的物料是否在锁定信息中
|
// 这里需要调用后端接口验证条码对应的物料
|
return null
|
},
|
|
async handleBarcodeScan() {
|
if (!this.scanForm.barcode) {
|
this.$message.warning('请输入物料条码')
|
return
|
}
|
|
if (!this.currentPallet) {
|
this.$message.warning('请先扫描托盘条码')
|
return
|
}
|
|
if (this.scanForm.quantity <= 0) {
|
this.$message.warning('请输入有效的拣选数量')
|
return
|
}
|
|
try {
|
// 验证条码并获取物料信息
|
const materialInfo = await this.validateBarcode(this.scanForm.barcode)
|
if (!materialInfo) {
|
this.$message.error('无效的物料条码')
|
return
|
}
|
|
// 查找对应的锁定信息
|
const targetLockInfo = this.findLockInfoByBarcodeAndMaterial(this.scanForm.barcode, materialInfo.materielCode)
|
if (!targetLockInfo) {
|
this.$message.error('该物料条码不在当前托盘的锁定信息中')
|
return
|
}
|
|
// 检查拣选数量
|
const availableQuantity = targetLockInfo.assignQuantity - targetLockInfo.pickedQty
|
if (this.scanForm.quantity > availableQuantity) {
|
this.$message.error(`拣选数量超过可用数量,剩余可拣选:${availableQuantity}`)
|
return
|
}
|
|
// 准备请求数据
|
const request = {
|
orderDetailId: targetLockInfo.orderDetailId,
|
barcode: this.scanForm.barcode,
|
materielCode: materialInfo.materielCode, // 传递物料编码
|
pickQuantity: this.scanForm.quantity,
|
locationCode: this.currentPallet.locationCode,
|
palletCode: this.currentPallet.palletCode,
|
stockId: targetLockInfo.stockId,
|
outStockLockInfoId: targetLockInfo.id // 传递锁定信息ID
|
}
|
|
const result = await this.http.post('api/OutboundPicking/ConfirmPicking', request)
|
if (result.status) {
|
this.$message.success('拣选确认成功')
|
|
// 重置表单
|
this.scanForm.barcode = ''
|
this.scanForm.quantity = 1
|
this.currentMaterialInfo = null
|
|
// 刷新数据
|
this.loadOrderInfo()
|
this.loadPickedHistory()
|
this.loadPalletLockInfo()
|
} else {
|
this.$message.error(result.message)
|
}
|
} catch (error) {
|
this.$message.error('拣选确认失败: ' + (error.message || '未知错误'))
|
}
|
},
|
|
// 根据条码和物料编码查找锁定信息
|
findLockInfoByBarcodeAndMaterial(barcode, materielCode) {
|
if (!this.allLockInfos || this.allLockInfos.length === 0) {
|
return null
|
}
|
|
// 首先尝试精确匹配条码
|
let lockInfo = this.allLockInfos.find(x =>
|
x.currentBarcode === barcode && x.materielCode === materielCode
|
)
|
|
if (lockInfo) {
|
return lockInfo
|
}
|
|
// 如果精确匹配失败,只匹配物料编码(允许从同一物料的不同条码拣选)
|
lockInfo = this.allLockInfos.find(x =>
|
x.materielCode === materielCode &&
|
(x.assignQuantity - x.pickedQty) > 0
|
)
|
|
return lockInfo
|
},
|
|
// 验证条码并获取物料信息
|
async validateBarcode(barcode) {
|
try {
|
const result = await this.http.get(`api/OutboundPicking/ValidateBarcode?barcode=${barcode}`)
|
if (result.status) {
|
return result.data
|
} else {
|
this.$message.error(result.message)
|
return null
|
}
|
} catch (error) {
|
this.$message.error('条码验证失败')
|
return null
|
}
|
},
|
|
async handleBackToStock() {
|
if (!this.currentPallet) return
|
|
try {
|
await this.$confirm(`确定将托盘 ${this.currentPallet.palletCode} 回库吗?`, '提示', {
|
type: 'warning'
|
})
|
|
const result = await this.http.post('api/BackToStock/GenerateBackToStockTask', {
|
palletCode: this.currentPallet.palletCode,
|
currentLocation: '拣选位',
|
operator: '当前用户'
|
})
|
|
if (result.status) {
|
this.$message.success('回库任务已生成')
|
this.resetCurrentPallet()
|
}
|
} catch (error) {
|
// 用户取消
|
}
|
},
|
|
async handleDirectOutbound() {
|
if (!this.currentPallet) return
|
|
try {
|
await this.$confirm(`确定将托盘 ${this.currentPallet.palletCode} 直接出库吗?`, '提示', {
|
type: 'warning'
|
})
|
|
const result = await this.http.post('api/OutboundPicking/DirectOutbound', {
|
palletCode: this.currentPallet.palletCode
|
})
|
|
if (result.status) {
|
this.$message.success('直接出库成功')
|
this.resetCurrentPallet()
|
this.loadOrderInfo()
|
}
|
} catch (error) {
|
// 用户取消
|
}
|
},
|
|
handleOpenSplit() {
|
if (!this.currentLockInfo) {
|
this.$message.warning('请先选择锁定信息')
|
return
|
}
|
this.splitVisible = true
|
},
|
|
handleSplitSuccess() {
|
this.$message.success('拆包成功')
|
this.loadPalletLockInfo()
|
},
|
|
resetCurrentPallet() {
|
this.currentPallet = null
|
this.currentLockInfo = null
|
this.currentMaterialInfo = null
|
this.allLockInfos = []
|
this.scanForm.palletCode = ''
|
},
|
|
async loadPickedHistory() {
|
const orderId = this.$route.query.orderId
|
if (!orderId) return
|
|
try {
|
const result = await this.http.get(`api/OutboundPicking/GetPickingHistory?orderId=${orderId}`)
|
if (result.status) {
|
this.pickedList = result.data
|
}
|
} catch (error) {
|
console.error('加载拣选历史失败:', error)
|
}
|
},
|
|
async handleCancelPick(row) {
|
try {
|
await this.$confirm('确定撤销这条拣选记录吗?', '提示', { type: 'warning' })
|
|
const result = await this.http.post('api/OutboundPicking/CancelPicking', {
|
pickingHistoryId: row.id
|
})
|
|
if (result.status) {
|
this.$message.success('撤销成功')
|
this.loadPickedHistory()
|
this.loadOrderInfo()
|
}
|
} catch (error) {
|
// 用户取消
|
}
|
}
|
},
|
mounted() {
|
this.loadOrderInfo()
|
this.loadPickedHistory()
|
}
|
}
|
</script>
|
|
<style scoped>
|
.picking-confirm {
|
padding: 20px;
|
}
|
|
.page-header {
|
margin-bottom: 20px;
|
}
|
|
.title {
|
font-size: 18px;
|
font-weight: bold;
|
}
|
|
.scan-section {
|
margin-bottom: 20px;
|
}
|
|
.action-buttons {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
}
|
|
.action-buttons .el-button {
|
width: 100%;
|
}
|
|
.current-info {
|
margin-top: 15px;
|
padding: 10px;
|
background-color: #f5f7fa;
|
border-radius: 4px;
|
}
|
|
.current-info p {
|
margin: 5px 0;
|
font-size: 14px;
|
}
|
|
.summary-info {
|
margin-bottom: 15px;
|
}
|
</style>
|