|
|
@@ -0,0 +1,519 @@
|
|
|
+<template>
|
|
|
+ <div class="container">
|
|
|
+ <van-nav-bar
|
|
|
+ title="海柔快上" left-arrow fixed placeholder @click-left="goBack">
|
|
|
+ <template #left>
|
|
|
+ <van-icon name="arrow-left" size="25" />
|
|
|
+ <div style="color: #fff">返回</div>
|
|
|
+ </template>
|
|
|
+ </van-nav-bar>
|
|
|
+ <div class="allocation">
|
|
|
+ <div class="code">
|
|
|
+ <div class="code-title">
|
|
|
+ <div><span style="font-size: 12px">容器:</span>{{ containerNo || '--' }}</div>
|
|
|
+ <div class="code-tips">
|
|
|
+ <van-notice-bar :background="'none'" :speed="50" :text="tips" />
|
|
|
+ </div>
|
|
|
+ <van-button plain size="mini" type="primary" @click="_setContainerNo()">切换容器</van-button>
|
|
|
+ </div>
|
|
|
+ <div class="code-input">
|
|
|
+ <van-search
|
|
|
+ v-model="wallNo"
|
|
|
+ placeholder="请扫描二次分拨墙号"
|
|
|
+ label="二次分拨墙号:"
|
|
|
+ @search="scanType=2"
|
|
|
+ left-icon=""
|
|
|
+ :class="[scanType===3?'search-input-barcode':'','van-hairline--bottom']"
|
|
|
+ @focus="scanType=3"
|
|
|
+ autocomplete="off"
|
|
|
+ >
|
|
|
+ </van-search>
|
|
|
+ <van-search
|
|
|
+ ref="searchRef"
|
|
|
+ v-model="searchBarcode"
|
|
|
+ placeholder="请扫描商品条码"
|
|
|
+ @search="_handlerScan(searchBarcode)"
|
|
|
+ label="商品条码:"
|
|
|
+ left-icon=""
|
|
|
+ :class="[scanType===2?'search-input-barcode':'','van-hairline--bottom']"
|
|
|
+ @focus="scanType=2"
|
|
|
+ autocomplete="off"
|
|
|
+ >
|
|
|
+ </van-search>
|
|
|
+ <van-search
|
|
|
+ class="code-bin"
|
|
|
+ ref="searchRef"
|
|
|
+ v-model="bin"
|
|
|
+ placeholder="分配格口"
|
|
|
+ label="分配格口:"
|
|
|
+ readonly
|
|
|
+ left-icon=""
|
|
|
+ >
|
|
|
+ </van-search>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <van-divider :style="{ color: '#333', borderColor: '#1989fa', padding: '0 16px',margin:'5px 0' }">上架库位
|
|
|
+ </van-divider>
|
|
|
+ <div class="move-stock-list">
|
|
|
+ <table class="task-table">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>库位</th>
|
|
|
+ <th>类型</th>
|
|
|
+ <th>数量</th>
|
|
|
+ <th style="width: 80px"></th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr v-for="(item, index) in locationActive" :key="index" v-if="locationActive.length>0">
|
|
|
+ <td>{{ item.location }}</td>
|
|
|
+ <td>{{ locationType[item.type] || item.type }}</td>
|
|
|
+ <td>{{ item.quantity || 0 }}</td>
|
|
|
+ <td>
|
|
|
+ <!-- <van-button type="primary" plain size="mini" style="width:100%" @click="setLocation(item)" v-if="!locationActive.location">选择</van-button>-->
|
|
|
+ <div>
|
|
|
+ <van-icon size="24" color="#07c160" name="success" />
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ <tr v-else>
|
|
|
+ <td colspan="4">
|
|
|
+ <div>暂无数据</div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 条码输入组件 -->
|
|
|
+ <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
|
|
|
+ <!-- 单据选择-->
|
|
|
+ <van-action-sheet v-model:show="lotBarcodeTrueFalseBy" cancel-text="取消" description="请选择商品批次"
|
|
|
+ close-on-click-action>
|
|
|
+ <van-cell-group>
|
|
|
+ <van-cell v-for="item in lotBarcodeList"
|
|
|
+ @click="_getRecommendedLocation(item.lotNumber,item.owner);barcodeActive=item;lotBarcodeTrueFalseBy=false">
|
|
|
+ <template #title>
|
|
|
+ {{ item.barcode }}({{ item.quantity }}件)
|
|
|
+ </template>
|
|
|
+ <template #label>
|
|
|
+ 生产日期:{{ item.lotAtt01 || '--' }}-失效日期:{{ item.lotAtt02 || '--' }}
|
|
|
+ </template>
|
|
|
+ </van-cell>
|
|
|
+ </van-cell-group>
|
|
|
+ </van-action-sheet>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script setup lang="ts">
|
|
|
+import { onMounted, onUnmounted, ref } from 'vue'
|
|
|
+import { showConfirmDialog, showNotify, showToast } from 'vant'
|
|
|
+import { androidFocus, getHeader, goBack, playVoiceBin, scanError, scanSuccess } from '@/utils/android'
|
|
|
+import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
|
|
|
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
|
|
|
+import { useStore } from '@/store/modules/user'
|
|
|
+import { finishTask, getRecommendedLocation, getWaitPutawayList, setBindAllocateWall } from '@/api/haikang'
|
|
|
+import { barcodeToUpperCase } from '@/utils/dataType'
|
|
|
+import { closeLoading, showLoading } from '@/utils/loading'
|
|
|
+import { getWaitPutawayInfo, getWaitPutawayListNew } from '@/api/putaway'
|
|
|
+import { bindAllocateWall } from '@/api/robot'
|
|
|
+try {
|
|
|
+ getHeader()
|
|
|
+ androidFocus()
|
|
|
+} catch (error) {
|
|
|
+}
|
|
|
+// 页面初始化
|
|
|
+onMounted(() => {
|
|
|
+ openListener()
|
|
|
+ scanInit(_handlerScan)
|
|
|
+ loadData()
|
|
|
+})
|
|
|
+const store = useStore()
|
|
|
+const warehouse = store.warehouse
|
|
|
+//收货容器号
|
|
|
+const containerNo = ref('')
|
|
|
+//分拨墙号
|
|
|
+const wallNo = ref('')
|
|
|
+//扫描类型
|
|
|
+const scanType = ref(3)
|
|
|
+//条码
|
|
|
+const searchBarcode = ref('')
|
|
|
+// 错误提示
|
|
|
+const tips = ref('')
|
|
|
+//强制返回
|
|
|
+const back = ref(true)
|
|
|
+//输入框类型
|
|
|
+const inputBarcodeRef = ref(null)
|
|
|
+const inputBarcodeType = ref('barcode')
|
|
|
+const location=ref('')
|
|
|
+//库位类型
|
|
|
+const locationType = {
|
|
|
+ 'EA': '件拣货库位',
|
|
|
+ 'AP': '补充拣货位',
|
|
|
+ 'CS': '箱拣货库位',
|
|
|
+ 'HP': '快拣补货位',
|
|
|
+ 'PC': '箱/件合并拣货库位',
|
|
|
+ 'PT': '播种库位',
|
|
|
+ 'RS': '存储库位',
|
|
|
+ 'SS': '理货站',
|
|
|
+ 'ST': '过渡库位',
|
|
|
+ 'WB': '组装工作区',
|
|
|
+}
|
|
|
+// 格口数据
|
|
|
+const bin = ref('') //当前格口
|
|
|
+// lotNumber与格口号的映射关系
|
|
|
+const lotNumberToBinMap = new Map()
|
|
|
+//推荐库位
|
|
|
+const locationList = ref([])
|
|
|
+const dataMap = ref({})
|
|
|
+
|
|
|
+// 设置容器号
|
|
|
+const setBarcode = (code) => {
|
|
|
+ if (inputBarcodeType.value === 'barcode') {
|
|
|
+ _handlerScan(code)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const params = { warehouseId:warehouse, containerId: code }
|
|
|
+ getWaitPutawayListNew(params).then(res => {
|
|
|
+ if (res.data.asnToShelfList.length==0 && res.data.noAsnToShelfList.length==0 ) {
|
|
|
+ scanError()
|
|
|
+ inputBarcodeRef.value?.show('', '请扫描收货容器号', '暂未查询到待上架任务,请切换容器')
|
|
|
+ } else {
|
|
|
+ scanSuccess()
|
|
|
+ bin.value = ''
|
|
|
+ searchBarcode.value=''
|
|
|
+ wallNo.value=''
|
|
|
+ const noAsnToShelfList = res.data.noAsnToShelfList.map(item => {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ lotNumber: item.lotNum,
|
|
|
+ lotAtt01: item.productDate,
|
|
|
+ lotAtt02: item.expireDate,
|
|
|
+ lotAtt04: item.productNum,
|
|
|
+ lotAtt08: item.quality,
|
|
|
+ quantity: item.remainShelfQty,
|
|
|
+ docNo: item.businessNo,
|
|
|
+ taskLineNo: item.id,
|
|
|
+ owner: item.customer,
|
|
|
+ }
|
|
|
+ })
|
|
|
+ const list = [...res.data.asnToShelfList, ...noAsnToShelfList]
|
|
|
+ dataMap.value = groupedData(list)
|
|
|
+ scanType.value = 3
|
|
|
+ containerNo.value = code
|
|
|
+ }
|
|
|
+ }).catch(err => {
|
|
|
+ scanError()
|
|
|
+ inputBarcodeRef.value?.show('', '请扫描退货收货容器号', err.message)
|
|
|
+ })
|
|
|
+
|
|
|
+}
|
|
|
+//切换容器
|
|
|
+const _setContainerNo = () => {
|
|
|
+ inputBarcodeType.value = 'container'
|
|
|
+ back.value = false
|
|
|
+ inputBarcodeRef.value?.show('', '请扫描退货收货容器号', '')
|
|
|
+}
|
|
|
+//批次数据
|
|
|
+const lotBarcodeList = ref([])
|
|
|
+const lotBarcodeTrueFalseBy = ref(false)
|
|
|
+const barcodeActive = ref({})
|
|
|
+// 扫描条码监听
|
|
|
+const _handlerScan = (code) => {
|
|
|
+ if (code) {
|
|
|
+ if (scanType.value == 2) {
|
|
|
+ if (!wallNo.value) {
|
|
|
+ tips.value = `请先扫描二次分拨墙号`
|
|
|
+ showToast({ duration: 3000, message: '请先扫描二次分拨墙号' })
|
|
|
+ scanType.value = 3
|
|
|
+ searchBarcode.value = ''
|
|
|
+ bin.value = ''
|
|
|
+ locationActive.value = []
|
|
|
+ scanError()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ scanSuccess()
|
|
|
+ searchBarcode.value = code
|
|
|
+ barcodeActive.value = {}
|
|
|
+ lotBarcodeList.value = matchingBarcodeItem(dataMap.value, code)
|
|
|
+ locationActive.value = []
|
|
|
+ bin.value = ''
|
|
|
+ if (lotBarcodeList.value.length > 0) {
|
|
|
+ if (lotBarcodeList.value.length == 1) {
|
|
|
+ barcodeActive.value = lotBarcodeList.value[0]
|
|
|
+ _getRecommendedLocation(lotBarcodeList.value[0].lotNumber, lotBarcodeList.value[0].owner)
|
|
|
+ } else if (lotBarcodeList.value.length > 1) {
|
|
|
+ locationList.value = []
|
|
|
+ lotBarcodeTrueFalseBy.value = true
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ scanError()
|
|
|
+ tips.value = `${code}-商品条码不匹配,请重新扫描`
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
|
|
|
+ searchBarcode.value = ''
|
|
|
+ locationList.value = []
|
|
|
+ }
|
|
|
+ } else if (scanType.value == 3) {
|
|
|
+ wallNo.value = code
|
|
|
+ scanType.value = 2
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+//匹配待上架列表数据
|
|
|
+const matchingBarcodeItem = (data, barcode) => {
|
|
|
+ const matchingItems = []
|
|
|
+ for (const key in data) {
|
|
|
+ const barcodeList = key.match(/\((.*?)\)/)[1].split('、')
|
|
|
+ if (data.hasOwnProperty(key)) {
|
|
|
+ if (barcodeList.some(item => barcodeToUpperCase(item) === barcodeToUpperCase(barcode))) {
|
|
|
+ matchingItems.push(data[key])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return matchingItems.length > 0 ? matchingItems : []
|
|
|
+}
|
|
|
+// 获取库存数据
|
|
|
+const _getRecommendedLocation = async (lotNum, owner) => {
|
|
|
+ try {
|
|
|
+ const params = { warehouse, lotNum, owner,zoneGroup:'WH01-02-01' }
|
|
|
+ const res = await getRecommendedLocation(params)
|
|
|
+ locationList.value = res.data
|
|
|
+ // 'EA'数据
|
|
|
+ const eaItems = res.data.filter(item => item.type === 'EA')
|
|
|
+ // 获取 quantity < 300 的
|
|
|
+ let result = eaItems.find(item => item.quantity !== null && item.quantity < 1000)
|
|
|
+ // 获取 quantity 为 null 的
|
|
|
+ if (!result) {
|
|
|
+ result = eaItems.find(item => item.quantity === null)
|
|
|
+ }
|
|
|
+ if (result) {
|
|
|
+ if(result.quantity===null){
|
|
|
+ scanError()
|
|
|
+ tips.value = `${searchBarcode.value}:库区内无商品库存,请调空料箱进行入库`
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: `${searchBarcode.value}:库区内无商品库存,请调空料箱进行入库` })
|
|
|
+ searchBarcode.value = ''
|
|
|
+ scanType.value=2
|
|
|
+ return
|
|
|
+ }
|
|
|
+ setLocation([result])
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error(err)
|
|
|
+ }
|
|
|
+}
|
|
|
+// 设置库位
|
|
|
+const locationActive = ref([])
|
|
|
+const setLocation = (item) => {
|
|
|
+ locationActive.value = item
|
|
|
+
|
|
|
+ // 根据lotNumber分配固定格口
|
|
|
+ const lotNumber = barcodeActive.value.lotNumber
|
|
|
+ let isNewBin = false // 标记是否是新分配的格口
|
|
|
+
|
|
|
+ // 如果这个lotNumber已经有分配的格口,使用现有的
|
|
|
+ if (lotNumberToBinMap.has(lotNumber)) {
|
|
|
+ bin.value = lotNumberToBinMap.get(lotNumber).toString()
|
|
|
+ } else {
|
|
|
+ // 如果没有,分配下一个可用的格口
|
|
|
+ const nextBinNumber = lotNumberToBinMap.size + 1
|
|
|
+ if (nextBinNumber > 60) {
|
|
|
+ showToast({ duration: 3000, message: '格口号超过60,不进行后续操作' })
|
|
|
+ scanError()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ bin.value = nextBinNumber.toString()
|
|
|
+ lotNumberToBinMap.set(lotNumber, nextBinNumber)
|
|
|
+ isNewBin = true // 标记为新分配的格口
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = {
|
|
|
+ warehouse,
|
|
|
+ equipment: wallNo.value,
|
|
|
+ container: containerNo.value,
|
|
|
+ barcode: barcodeActive.value.barcode,
|
|
|
+ bin:bin.value,
|
|
|
+ location: locationActive.value[0].location,
|
|
|
+ lotNum: barcodeActive.value.lotNumber,
|
|
|
+ sysVersion:'V6'
|
|
|
+ }
|
|
|
+ showLoading()
|
|
|
+ setBindAllocateWall(data).then(res => {
|
|
|
+ tips.value = `请将${searchBarcode.value},放入分拨墙-${wallNo.value}-《${data.bin}》格口`
|
|
|
+ playVoiceBin(Number(bin.value))
|
|
|
+ closeLoading()
|
|
|
+ }).catch(err => {
|
|
|
+ // 如果是新分配的格口且接口请求失败,删除刚刚分配的格口信息
|
|
|
+ if (isNewBin) {
|
|
|
+ lotNumberToBinMap.delete(lotNumber)
|
|
|
+ }
|
|
|
+ searchBarcode.value = ''
|
|
|
+ locationActive.value = []
|
|
|
+ bin.value = ''
|
|
|
+ tips.value = err.message
|
|
|
+ scanError()
|
|
|
+ closeLoading()
|
|
|
+ })
|
|
|
+}
|
|
|
+// 数据刷新
|
|
|
+const loadData = () => {
|
|
|
+ if (!containerNo.value) {
|
|
|
+ inputBarcodeType.value = 'container'
|
|
|
+ inputBarcodeRef.value?.show('', '请扫描退货收货容器号', '')
|
|
|
+ return
|
|
|
+ } else {
|
|
|
+ setBarcode(containerNo.value)
|
|
|
+ }
|
|
|
+}
|
|
|
+//根据条码批次分组数据
|
|
|
+const groupedData = (data) => {
|
|
|
+ return data.reduce((acc, item) => {
|
|
|
+ const key = `(${item.barcode}、${item.barcodeAs}、${item.sku})-${item.lotNumber}`
|
|
|
+ if (acc[key]) {
|
|
|
+ acc[key].quantity += item.quantity
|
|
|
+ } else {
|
|
|
+ acc[key] = { ...item }
|
|
|
+ }
|
|
|
+ return acc
|
|
|
+ }, {})
|
|
|
+}
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ closeListener()
|
|
|
+})
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+//删除分拨
|
|
|
+const onClickRight = () => {
|
|
|
+ showConfirmDialog({
|
|
|
+ title: '温馨提示',
|
|
|
+ message:'您正在进行释放分拨墙是否继续?',
|
|
|
+ keyboardEnabled:false
|
|
|
+ }).then(() => {
|
|
|
+ showLoading()
|
|
|
+ const params={warehouse,container:containerNo.value}
|
|
|
+ finishTask(params).then(res=>{
|
|
|
+ showNotify({ type: 'success', duration: 3000, message: `解绑成功` })
|
|
|
+ scanSuccess()
|
|
|
+ reset()
|
|
|
+ loadData()
|
|
|
+ }).catch(err=>{
|
|
|
+ scanError()
|
|
|
+ }).finally(() => {
|
|
|
+ closeLoading()
|
|
|
+ })
|
|
|
+ }).catch(() => {})
|
|
|
+}
|
|
|
+const reset=()=>{
|
|
|
+ containerNo.value=''
|
|
|
+ wallNo.value=''
|
|
|
+ scanType.value=3
|
|
|
+ searchBarcode.value = ''
|
|
|
+ bin.value=''
|
|
|
+ locationActive.value = []
|
|
|
+ tips.value='请扫描容器号'
|
|
|
+ lotNumberToBinMap.clear() // 清空lotNumber与格口的映射关系
|
|
|
+}
|
|
|
+window.onRefresh = loadData
|
|
|
+</script>
|
|
|
+<style scoped lang="sass">
|
|
|
+.container
|
|
|
+ background: #e9f4ff
|
|
|
+
|
|
|
+ .allocation
|
|
|
+ .code
|
|
|
+ box-sizing: border-box
|
|
|
+ padding: 8px 0
|
|
|
+
|
|
|
+ .code-title
|
|
|
+ display: flex
|
|
|
+ justify-content: space-between
|
|
|
+ padding: 0 15px 8px 15px
|
|
|
+
|
|
|
+ .code-input
|
|
|
+ ::v-deep(.van-search)
|
|
|
+ padding: 0
|
|
|
+
|
|
|
+ ::v-deep(.van-search__label)
|
|
|
+ font-size: 16px
|
|
|
+
|
|
|
+ ::v-deep(.van-search__field)
|
|
|
+ border-bottom: 2px solid #efefef
|
|
|
+ font-size: 16px
|
|
|
+
|
|
|
+ ::v-deep(.van-search__content)
|
|
|
+ background: #fff
|
|
|
+
|
|
|
+ .search-input-barcode
|
|
|
+ ::v-deep(.van-search__field)
|
|
|
+ border-bottom: 2px solid #0077ff
|
|
|
+ z-index: 2
|
|
|
+ .code-bin
|
|
|
+ ::v-deep(.van-field__control)
|
|
|
+ font-size: 20px
|
|
|
+ color: #ff0d00
|
|
|
+
|
|
|
+
|
|
|
+ .code-tips
|
|
|
+ color: #ed6a0c
|
|
|
+ flex: 1
|
|
|
+
|
|
|
+ .code-count
|
|
|
+ font-size: 16px
|
|
|
+ font-weight: bold
|
|
|
+
|
|
|
+ span
|
|
|
+ color: #0077ff
|
|
|
+
|
|
|
+ .location
|
|
|
+ background: #fff
|
|
|
+ text-align: left
|
|
|
+ padding: 10px 15px
|
|
|
+ margin-bottom: 10px
|
|
|
+ font-size: 14px
|
|
|
+
|
|
|
+ .move-stock-list
|
|
|
+ width: 100%
|
|
|
+ overflow-y: auto
|
|
|
+ max-height: 60vh
|
|
|
+ min-height: 100px
|
|
|
+
|
|
|
+ .move-button
|
|
|
+ background: #1989fa
|
|
|
+ color: #fff
|
|
|
+ width: 100%
|
|
|
+ height: 30px
|
|
|
+ font-size: 15px
|
|
|
+ line-height: 30px
|
|
|
+ font-weight: bold
|
|
|
+
|
|
|
+ .task-table, .task-table-bin, .task-table-box
|
|
|
+ width: 100%
|
|
|
+ table-layout: fixed
|
|
|
+ border-collapse: collapse
|
|
|
+ font-size: 15px
|
|
|
+
|
|
|
+ .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
|
|
|
+ text-align: center
|
|
|
+ border: 1px solid #ccc
|
|
|
+ word-wrap: break-word
|
|
|
+ word-break: break-all
|
|
|
+
|
|
|
+ .task-table thead, .task-table-bin thead, .task-table-box thead
|
|
|
+ background-color: #3f8dff
|
|
|
+ position: sticky
|
|
|
+ top: 0
|
|
|
+ color: white
|
|
|
+ font-size: 15px
|
|
|
+
|
|
|
+ .task-table-bin thead
|
|
|
+ background-color: #3f8dff
|
|
|
+
|
|
|
+ .task-table-bin tbody
|
|
|
+ background: #cde7ff
|
|
|
+
|
|
|
+ .nav-right
|
|
|
+ padding: 14px 0 12px 5px
|
|
|
+ color: #fff
|
|
|
+
|
|
|
+
|
|
|
+</style>
|