<template>
|
<div class="picking-confirm">
|
<div class="scan-section">
|
<el-alert
|
title="请使用扫码枪扫描托盘码和物料条码,扫码枪带回车功能,扫完物料条码自动确认"
|
type="info"
|
:closable="false"
|
class="scan-alert"
|
/>
|
|
<el-form :model="scanForm" label-width="100px" class="scan-form">
|
<el-form-item label="托盘码" required>
|
<el-input
|
ref="palletInput"
|
v-model="scanForm.palletCode"
|
placeholder="请扫描托盘码"
|
@keyup.enter="handlePalletScan"
|
@blur="loadPalletSummary"
|
clearable
|
/>
|
</el-form-item>
|
|
<el-form-item label="物料条码" required>
|
<el-input
|
ref="materialInput"
|
v-model="scanForm.materialBarcode"
|
placeholder="请扫描物料条码"
|
:disabled="!scanForm.palletCode"
|
@keyup.enter="handleMaterialScan"
|
clearable
|
/>
|
</el-form-item>
|
</el-form>
|
|
<!-- 托盘拣货统计 -->
|
<div v-if="palletSummary" class="pallet-summary">
|
<el-card header="托盘拣货统计">
|
<el-descriptions :column="3" border>
|
<el-descriptions-item label="托盘号">
|
{{ scanForm.palletCode }}
|
</el-descriptions-item>
|
<el-descriptions-item label="未拣货条数">
|
<el-text type="warning">{{ palletSummary.unpickedCount }}</el-text>
|
</el-descriptions-item>
|
<el-descriptions-item label="未拣货总数">
|
<el-text type="danger">{{ palletSummary.unpickedTotal }}</el-text>
|
</el-descriptions-item>
|
</el-descriptions>
|
</el-card>
|
</div>
|
|
<div class="action-buttons">
|
<el-button type="primary" @click="handleConfirm" :loading="confirmLoading">
|
手动确认
|
</el-button>
|
<el-button @click="handleReset">重置</el-button>
|
<el-button @click="$emit('close')">取消</el-button>
|
</div>
|
</div>
|
|
<!-- 已分拣记录列表 -->
|
<div class="picked-records">
|
<el-card header="已分拣记录">
|
<base-table
|
ref="pickedTable"
|
:table-config="pickedTableConfig"
|
:height="300"
|
/>
|
</el-card>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, nextTick } from 'vue'
|
import { ElMessage } from 'element-plus'
|
import BaseTable from '@/components/base/BaseTable.vue'
|
|
const props = defineProps({
|
orderNo: {
|
type: String,
|
required: true
|
}
|
})
|
|
const emit = defineEmits(['confirm', 'close'])
|
|
const palletInput = ref()
|
const materialInput = ref()
|
const pickedTable = ref()
|
|
const scanForm = reactive({
|
palletCode: '',
|
materialBarcode: ''
|
})
|
|
const palletSummary = ref(null)
|
const confirmLoading = ref(false)
|
|
const pickedTableConfig = reactive({
|
url: '/api/outbound/getPickingRecords',
|
query: { orderNo: props.orderNo },
|
columns: [
|
{ prop: 'TaskNo', label: '任务号', width: 150 },
|
{ prop: 'Barcode', label: '物料条码', width: 150 },
|
{ prop: 'MaterielName', label: '物料名称', width: 150 },
|
{ prop: 'PickQuantity', label: '拣货数量', width: 100 },
|
{ prop: 'LocationCode', label: '货位', width: 120 },
|
{ prop: 'CreateTime', label: '拣货时间', width: 180 }
|
]
|
})
|
|
onMounted(() => {
|
nextTick(() => {
|
if (palletInput.value) {
|
palletInput.value.focus()
|
}
|
})
|
})
|
const loadOrderInfo = async () => {
|
const orderId = route.query.orderId
|
if (!orderId) return
|
|
try {
|
const response = await http.get(`/api/OutboundOrder/GetById?id=${orderId}`)
|
if (response.status) {
|
orderInfo.value = response.data
|
}
|
} catch (error) {
|
ElMessage.error('加载出库单信息失败')
|
}
|
}
|
const handlePalletScan = async () => {
|
if (scanForm.palletCode) {
|
ElMessage.success(`已扫描托盘: ${scanForm.palletCode}`)
|
await loadPalletSummary()
|
|
nextTick(() => {
|
if (materialInput.value) {
|
materialInput.value.focus()
|
}
|
})
|
}
|
}
|
|
const handleMaterialScan = async () => {
|
if (!scanForm.palletCode) {
|
ElMessage.warning('请先扫描托盘码')
|
palletInput.value.focus()
|
return
|
}
|
|
if (!scanForm.materialBarcode) {
|
ElMessage.warning('请扫描物料条码')
|
return
|
}
|
|
await executePickingConfirm()
|
}
|
|
const loadPalletSummary = async () => {
|
if (!scanForm.palletCode) {
|
palletSummary.value = null
|
return
|
}
|
|
try {
|
const result = await $http.get('/api/outbound/getPalletPickingSummary', {
|
params: {
|
orderNo: props.orderNo,
|
palletCode: scanForm.palletCode
|
}
|
})
|
|
if (result.success) {
|
// 处理统计信息
|
const summary = result.data
|
const assigned = summary.find(x => x.Status === '已分配') || { TotalAssignQty: 0, TotalPickedQty: 0 }
|
const picked = summary.find(x => x.Status === '已拣选') || { TotalPickedQty: 0 }
|
|
palletSummary.value = {
|
unpickedCount: assigned.TotalAssignQty > 0 ? 1 : 0, // 简化计算
|
unpickedTotal: assigned.TotalAssignQty - assigned.TotalPickedQty
|
}
|
}
|
} catch (error) {
|
console.error('加载托盘统计失败:', error)
|
}
|
}
|
|
const handleConfirm = async () => {
|
if (!scanForm.palletCode || !scanForm.materialBarcode) {
|
ElMessage.warning('请填写完整的扫码信息')
|
return
|
}
|
|
await executePickingConfirm()
|
}
|
|
const executePickingConfirm = async () => {
|
confirmLoading.value = true
|
|
try {
|
// 先找到对应的出库锁定信息
|
const lockInfoResult = await $http.get('/api/outbound/getOutStockLockInfo', {
|
params: {
|
orderNo: props.orderNo,
|
palletCode: scanForm.palletCode,
|
materialBarcode: scanForm.materialBarcode
|
}
|
})
|
|
if (!lockInfoResult.success || !lockInfoResult.data || lockInfoResult.data.length === 0) {
|
ElMessage.error('未找到对应的出库锁定信息')
|
return
|
}
|
|
const lockInfo = lockInfoResult.data[0]
|
|
const request = {
|
outStockLockId: lockInfo.Id,
|
taskNo: `TASK_${Date.now()}`,
|
palletCode: scanForm.palletCode,
|
materialBarcode: scanForm.materialBarcode,
|
locationCode: lockInfo.LocationCode
|
}
|
|
const result = await $http.post('/api/outbound/pickingConfirm', request)
|
|
if (result.success) {
|
ElMessage.success('分拣确认成功')
|
handleReset()
|
emit('confirm')
|
|
// 刷新已分拣记录
|
if (pickedTable.value) {
|
pickedTable.value.refresh()
|
}
|
|
// 重新加载托盘统计
|
await loadPalletSummary()
|
} else {
|
ElMessage.error(result.message)
|
}
|
} catch (error) {
|
ElMessage.error('分拣确认失败')
|
} finally {
|
confirmLoading.value = false
|
}
|
}
|
|
const handleReset = () => {
|
scanForm.materialBarcode = ''
|
nextTick(() => {
|
if (materialInput.value) {
|
materialInput.value.focus()
|
}
|
})
|
}
|
</script>
|
|
<style scoped>
|
.picking-confirm {
|
display: flex;
|
flex-direction: column;
|
height: 70vh;
|
}
|
|
.scan-section {
|
flex-shrink: 0;
|
margin-bottom: 16px;
|
}
|
|
.scan-alert {
|
margin-bottom: 16px;
|
}
|
|
.scan-form {
|
max-width: 500px;
|
}
|
|
.pallet-summary {
|
margin: 16px 0;
|
}
|
|
.action-buttons {
|
margin-top: 16px;
|
}
|
|
.picked-records {
|
flex: 1;
|
min-height: 300px;
|
}
|
</style>
|