|
@@ -1,11 +1,30 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="merge-container">
|
|
<div class="merge-container">
|
|
|
- <!-- 扫描输入框 -->
|
|
|
|
|
|
|
+ <!-- 扫描输入框区域 -->
|
|
|
<div class="scan-section">
|
|
<div class="scan-section">
|
|
|
<van-field
|
|
<van-field
|
|
|
|
|
+ ref="boxCodeInputRef"
|
|
|
v-model="boxCode"
|
|
v-model="boxCode"
|
|
|
placeholder="请扫描料箱号"
|
|
placeholder="请扫描料箱号"
|
|
|
clearable
|
|
clearable
|
|
|
|
|
+ @click="onBoxCodeClick"
|
|
|
|
|
+ @keyup.enter="onBoxCodeEnter"
|
|
|
|
|
+ />
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ ref="sourceLocationInputRef"
|
|
|
|
|
+ v-model="sourceLocation"
|
|
|
|
|
+ placeholder="请扫描源库位"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ @click="onSourceLocationClick"
|
|
|
|
|
+ @keyup.enter="onSourceLocationEnter"
|
|
|
|
|
+ />
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ ref="barcodeInputRef"
|
|
|
|
|
+ v-model="scanBarcode"
|
|
|
|
|
+ placeholder="请扫描商品条码"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ @click="onBarcodeClick"
|
|
|
|
|
+ @keyup.enter="onBarcodeEnter"
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -174,14 +193,30 @@
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
|
|
import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
|
|
|
-import { onMounted, onUnmounted, ref, reactive, watch, nextTick } from 'vue'
|
|
|
|
|
|
|
+import { onMounted, onUnmounted, ref, reactive, nextTick } from 'vue'
|
|
|
import { showToast, showLoadingToast, closeToast } from 'vant'
|
|
import { showToast, showLoadingToast, closeToast } from 'vant'
|
|
|
import { useStore } from '@/store/modules/user'
|
|
import { useStore } from '@/store/modules/user'
|
|
|
-import { getWorkingDetailsByBox, getBoxSplitCode, type BoxRelatedMergeDetailsVO, type LocationMergeDetails } from '@/api/location/merge'
|
|
|
|
|
|
|
+import { getWorkingDetailsByBox, getBoxSplitCode, boxInbound, type BoxRelatedMergeDetailsVO, type LocationMergeDetails } from '@/api/location/merge'
|
|
|
|
|
+import { getInventory, inventoryMovement } from '@/api/inventory'
|
|
|
|
|
+import { showConfirmDialog } from 'vant'
|
|
|
|
|
|
|
|
const store = useStore()
|
|
const store = useStore()
|
|
|
const warehouse = store.warehouse
|
|
const warehouse = store.warehouse
|
|
|
|
|
|
|
|
|
|
+// 扫描类型: 1=料箱号, 2=源库位, 3=商品条码
|
|
|
|
|
+const scanType = ref(1)
|
|
|
|
|
+
|
|
|
|
|
+// 输入框引用
|
|
|
|
|
+const boxCodeInputRef = ref<any>(null)
|
|
|
|
|
+const sourceLocationInputRef = ref<any>(null)
|
|
|
|
|
+const barcodeInputRef = ref<any>(null)
|
|
|
|
|
+
|
|
|
|
|
+// 扫描料箱号
|
|
|
|
|
+const boxCode = ref('')
|
|
|
|
|
+// 源库位
|
|
|
|
|
+const sourceLocation = ref('')
|
|
|
|
|
+// 商品条码
|
|
|
|
|
+const scanBarcode = ref('')
|
|
|
|
|
|
|
|
// 页面初始化
|
|
// 页面初始化
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
@@ -189,7 +224,7 @@ onMounted(() => {
|
|
|
scanInit(_handlerScan)
|
|
scanInit(_handlerScan)
|
|
|
// 获取焦点
|
|
// 获取焦点
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
- focusScanInput()
|
|
|
|
|
|
|
+ focusBoxCodeInput()
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -197,32 +232,171 @@ onUnmounted(() => {
|
|
|
closeListener()
|
|
closeListener()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-// 设置扫描输入框焦点
|
|
|
|
|
-const focusScanInput = () => {
|
|
|
|
|
- const input = document.querySelector('.scan-section input') as HTMLInputElement
|
|
|
|
|
- if (input) {
|
|
|
|
|
- input.focus()
|
|
|
|
|
- }
|
|
|
|
|
|
|
+// 设置料箱号输入框焦点
|
|
|
|
|
+const focusBoxCodeInput = () => {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ boxCodeInputRef.value?.focus()
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 设置源库位输入框焦点
|
|
|
|
|
+const focusSourceLocationInput = () => {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ sourceLocationInputRef.value?.focus()
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 扫描条码监听
|
|
|
|
|
|
|
+// 设置商品条码输入框焦点
|
|
|
|
|
+const focusBarcodeInput = () => {
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ barcodeInputRef.value?.focus()
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 扫描监听
|
|
|
const _handlerScan = (code: string) => {
|
|
const _handlerScan = (code: string) => {
|
|
|
- if (code) {
|
|
|
|
|
|
|
+ if (!code) return
|
|
|
|
|
+
|
|
|
|
|
+ if (scanType.value === 1) {
|
|
|
|
|
+ // 扫描料箱号
|
|
|
boxCode.value = code
|
|
boxCode.value = code
|
|
|
loadBoxData(code)
|
|
loadBoxData(code)
|
|
|
|
|
+ } else if (scanType.value === 2) {
|
|
|
|
|
+ // 扫描源库位
|
|
|
|
|
+ sourceLocation.value = code
|
|
|
|
|
+ onSourceLocationEnter()
|
|
|
|
|
+ } else if (scanType.value === 3) {
|
|
|
|
|
+ // 扫描商品条码
|
|
|
|
|
+ scanBarcode.value = code
|
|
|
|
|
+ onBarcodeEnter()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 扫描料箱号
|
|
|
|
|
-const boxCode = ref('')
|
|
|
|
|
|
|
+// 料箱号输入框点击 - 重置所有数据
|
|
|
|
|
+const onBoxCodeClick = () => {
|
|
|
|
|
+ resetAllData()
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-// 监听 boxCode 变化,当有值时调用接口
|
|
|
|
|
-watch(boxCode, (newVal) => {
|
|
|
|
|
- if (newVal && newVal.length > 5) {
|
|
|
|
|
- // 防抖处理,避免输入过程中频繁调用
|
|
|
|
|
- loadBoxData(newVal)
|
|
|
|
|
|
|
+// 料箱号回车
|
|
|
|
|
+const onBoxCodeEnter = () => {
|
|
|
|
|
+ if (boxCode.value && boxCode.value.length > 5) {
|
|
|
|
|
+ loadBoxData(boxCode.value)
|
|
|
}
|
|
}
|
|
|
-})
|
|
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 源库位输入框点击 - 重置除料箱号外的数据
|
|
|
|
|
+const onSourceLocationClick = () => {
|
|
|
|
|
+ resetExceptBoxCode()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 源库位回车
|
|
|
|
|
+const onSourceLocationEnter = () => {
|
|
|
|
|
+ if (!sourceLocation.value) return
|
|
|
|
|
+
|
|
|
|
|
+ // 清空商品信息
|
|
|
|
|
+ resetProductInfo()
|
|
|
|
|
+
|
|
|
|
|
+ // 切换到扫描商品条码
|
|
|
|
|
+ scanType.value = 3
|
|
|
|
|
+ focusBarcodeInput()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 商品条码输入框点击 - 只重置商品信息
|
|
|
|
|
+const onBarcodeClick = () => {
|
|
|
|
|
+ resetProductInfo()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 当前选中的库存数据(用于提交移库)
|
|
|
|
|
+const currentInventoryData = ref<any>(null)
|
|
|
|
|
+
|
|
|
|
|
+// 商品条码回车 - 调用getInventory获取商品信息
|
|
|
|
|
+const onBarcodeEnter = async () => {
|
|
|
|
|
+ if (!scanBarcode.value) return
|
|
|
|
|
+ if (!sourceLocation.value) {
|
|
|
|
|
+ showToast('请先扫描源库位')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ showLoadingToast({ message: '查询中...', forbidClick: true })
|
|
|
|
|
+
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ warehouse,
|
|
|
|
|
+ barcode: scanBarcode.value,
|
|
|
|
|
+ location: sourceLocation.value,
|
|
|
|
|
+ locationRegexp: '^(?!STAGE_|SORTATION_).*$'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const res = await getInventory(params)
|
|
|
|
|
+ closeToast()
|
|
|
|
|
+
|
|
|
|
|
+ if (res.data && res.data.length > 0) {
|
|
|
|
|
+ const inventoryData = res.data[0]
|
|
|
|
|
+ // 保存完整的库存数据
|
|
|
|
|
+ currentInventoryData.value = inventoryData
|
|
|
|
|
+ // 填充商品信息
|
|
|
|
|
+ productInfo.targetLocation = sourceLocation.value
|
|
|
|
|
+ productInfo.stockQty = inventoryData.quantityAvailable || inventoryData.quantity || ''
|
|
|
|
|
+ productInfo.productName = inventoryData.productName || inventoryData.skuName || ''
|
|
|
|
|
+ productInfo.barcode = inventoryData.barcode || scanBarcode.value
|
|
|
|
|
+ productInfo.qualityStatus = inventoryData.qualityStatus || ''
|
|
|
|
|
+ productInfo.warehouseType = inventoryData.warehouseType || ''
|
|
|
|
|
+ productInfo.batchNo = inventoryData.lotNum || ''
|
|
|
|
|
+ productInfo.productionDate = inventoryData.productionDate || ''
|
|
|
|
|
+ productInfo.expiryDate = inventoryData.expiryDate || ''
|
|
|
|
|
+ productInfo.moveQty = inventoryData.quantityAvailable || inventoryData.quantity || ''
|
|
|
|
|
+
|
|
|
|
|
+ showToast('商品信息获取成功')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showToast('未找到库存信息')
|
|
|
|
|
+ currentInventoryData.value = null
|
|
|
|
|
+ resetProductInfo()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ closeToast()
|
|
|
|
|
+ showToast(error.message || '查询失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 重置所有数据(回到最开始状态)
|
|
|
|
|
+const resetAllData = () => {
|
|
|
|
|
+ boxCode.value = ''
|
|
|
|
|
+ sourceLocation.value = ''
|
|
|
|
|
+ scanBarcode.value = ''
|
|
|
|
|
+ selectedBox.value = null
|
|
|
|
|
+ mergeDataList.value = []
|
|
|
|
|
+ clickableLocationsMap.value = new Map()
|
|
|
|
|
+ resetProductInfo()
|
|
|
|
|
+ initStations()
|
|
|
|
|
+ scanType.value = 1
|
|
|
|
|
+ focusBoxCodeInput()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 重置除料箱号外的数据
|
|
|
|
|
+const resetExceptBoxCode = () => {
|
|
|
|
|
+ sourceLocation.value = ''
|
|
|
|
|
+ scanBarcode.value = ''
|
|
|
|
|
+ selectedBox.value = null
|
|
|
|
|
+ resetProductInfo()
|
|
|
|
|
+ scanType.value = 2
|
|
|
|
|
+ focusSourceLocationInput()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 重置商品信息
|
|
|
|
|
+const resetProductInfo = () => {
|
|
|
|
|
+ scanBarcode.value = ''
|
|
|
|
|
+ currentInventoryData.value = null
|
|
|
|
|
+ productInfo.targetLocation = ''
|
|
|
|
|
+ productInfo.stockQty = ''
|
|
|
|
|
+ productInfo.productName = ''
|
|
|
|
|
+ productInfo.barcode = ''
|
|
|
|
|
+ productInfo.qualityStatus = ''
|
|
|
|
|
+ productInfo.warehouseType = ''
|
|
|
|
|
+ productInfo.batchNo = ''
|
|
|
|
|
+ productInfo.productionDate = ''
|
|
|
|
|
+ productInfo.expiryDate = ''
|
|
|
|
|
+ productInfo.moveQty = ''
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
// 加载料箱数据
|
|
// 加载料箱数据
|
|
|
const loadBoxData = async (code: string) => {
|
|
const loadBoxData = async (code: string) => {
|
|
@@ -268,6 +442,10 @@ const loadBoxData = async (code: string) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
closeToast()
|
|
closeToast()
|
|
|
|
|
+
|
|
|
|
|
+ // 加载完成后focus到源库位输入框
|
|
|
|
|
+ scanType.value = 2
|
|
|
|
|
+ focusSourceLocationInput()
|
|
|
} catch (error: any) {
|
|
} catch (error: any) {
|
|
|
closeToast()
|
|
closeToast()
|
|
|
showToast(error.message || '加载失败')
|
|
showToast(error.message || '加载失败')
|
|
@@ -285,7 +463,7 @@ const buildClickableLocationsMap = (boxDetailsList: BoxRelatedMergeDetailsVO[])
|
|
|
boxDetail.mergeDetails?.forEach((detail: LocationMergeDetails) => {
|
|
boxDetail.mergeDetails?.forEach((detail: LocationMergeDetails) => {
|
|
|
const sourceLocation = detail.sourceLocation
|
|
const sourceLocation = detail.sourceLocation
|
|
|
const targetLocation = detail.targetLocation
|
|
const targetLocation = detail.targetLocation
|
|
|
- const quantity = detail.moveQty || 0
|
|
|
|
|
|
|
+ const quantity = detail.quantity || 0
|
|
|
|
|
|
|
|
// 处理源库位(推荐清空库位)
|
|
// 处理源库位(推荐清空库位)
|
|
|
if (sourceLocation) {
|
|
if (sourceLocation) {
|
|
@@ -594,20 +772,21 @@ const confirmSelectLocation = () => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 呼唤机器人
|
|
// 呼唤机器人
|
|
|
-const callRobot = () => {
|
|
|
|
|
- showToast('正在呼唤机器人...')
|
|
|
|
|
|
|
+const callRobot = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ showLoadingToast({ message: '正在呼唤机器人...', forbidClick: true })
|
|
|
|
|
+ await boxInbound(warehouse)
|
|
|
|
|
+ closeToast()
|
|
|
|
|
+ showToast('呼唤机器人成功')
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ closeToast()
|
|
|
|
|
+ showToast(error.message || '呼唤机器人失败')
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 重新输入
|
|
|
|
|
|
|
+// 重新输入(不重置料箱号)
|
|
|
const resetInput = () => {
|
|
const resetInput = () => {
|
|
|
- boxCode.value = ''
|
|
|
|
|
- selectedBox.value = null
|
|
|
|
|
- mergeDataList.value = []
|
|
|
|
|
- clickableLocationsMap.value = new Map()
|
|
|
|
|
- initStations()
|
|
|
|
|
- nextTick(() => {
|
|
|
|
|
- focusScanInput()
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ resetExceptBoxCode()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 提交移库
|
|
// 提交移库
|
|
@@ -616,7 +795,59 @@ const submitMove = () => {
|
|
|
showToast('请先扫描料箱号')
|
|
showToast('请先扫描料箱号')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- showToast('提交移库成功')
|
|
|
|
|
|
|
+ if (!sourceLocation.value) {
|
|
|
|
|
+ showToast('请先扫描源库位')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!productInfo.barcode) {
|
|
|
|
|
+ showToast('请先扫描商品条码')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!productInfo.targetLocationNew) {
|
|
|
|
|
+ showToast('请先选择目标库位')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!productInfo.moveQty || Number(productInfo.moveQty) <= 0) {
|
|
|
|
|
+ showToast('请输入有效的移库数量')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (Number(productInfo.moveQty) > Number(productInfo.stockQty)) {
|
|
|
|
|
+ showToast('移库数量不能大于库存数量')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ showConfirmDialog({
|
|
|
|
|
+ title: '移库确认',
|
|
|
|
|
+ message: `${productInfo.barcode}从"${sourceLocation.value}"移动至"${productInfo.targetLocationNew}"共:${productInfo.moveQty}件`
|
|
|
|
|
+ })
|
|
|
|
|
+ .then(() => {
|
|
|
|
|
+ const { traceId, lotNum, lotNumber, ownerCode, owner, sku } = currentInventoryData.value || {}
|
|
|
|
|
+ const data = {
|
|
|
|
|
+ fmLocation: sourceLocation.value,
|
|
|
|
|
+ fmContainer: traceId || boxCode.value,
|
|
|
|
|
+ owner: ownerCode || owner || '',
|
|
|
|
|
+ sku: sku || productInfo.barcode,
|
|
|
|
|
+ lotNum: lotNum || lotNumber || productInfo.batchNo || '',
|
|
|
|
|
+ warehouse,
|
|
|
|
|
+ quantity: Number(productInfo.moveQty),
|
|
|
|
|
+ toLocation: productInfo.targetLocationNew
|
|
|
|
|
+ }
|
|
|
|
|
+ showLoadingToast({ message: '提交中...', forbidClick: true })
|
|
|
|
|
+ inventoryMovement(data)
|
|
|
|
|
+ .then(() => {
|
|
|
|
|
+ closeToast()
|
|
|
|
|
+ showToast('提交移库成功')
|
|
|
|
|
+ // 重置除料箱号外的数据,继续下一个移库
|
|
|
|
|
+ resetExceptBoxCode()
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((err: any) => {
|
|
|
|
|
+ closeToast()
|
|
|
|
|
+ showToast(err.message || '提交移库失败')
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(() => {
|
|
|
|
|
+ // 用户取消
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|