Browse Source

Merge branch 'zhaohuanhuan_asrs-v4' into testing

# Conflicts:
#	src/router/index.ts
#	src/views/haikang/putaway/putaway/index.vue
zhaohuanhuan 1 month ago
parent
commit
9f63833358

+ 74 - 0
src/api/returnTask/index.ts

@@ -0,0 +1,74 @@
+// @ts-ignore
+import request from '@/utils/request'
+// @ts-ignore
+import {createReturnFirstSeedType, createReturnTaskType, getReturnTaskBinListType, getReturnTaskPageType, returnTaskFirstStepCompleteType, updateReturnTaskUsedQtyType} from '@/types/returnTask'
+
+//分页查询还库任务列表
+export function getReturnTaskPage(params:getReturnTaskPageType) {
+  return request({
+    url: '/api/wms/return/task/page',
+    method: 'get',
+    params
+  })
+}
+
+//分页查询还库任务列表
+export function createReturnTask(data:createReturnTaskType) {
+  return request({
+    url: '/api/wms/return/task/create',
+    method: 'post',
+    data
+  })
+}
+
+
+//还库分配格口
+export function createReturnFirstSeed(data:createReturnFirstSeedType) {
+  return request({
+    url: '/api/wms/return/task/firstSeed',
+    method: 'post',
+    data
+  })
+}
+
+
+
+//查询还库分配格口数据
+export function getReturnTaskBinList(params:getReturnTaskBinListType) {
+  return request({
+    url: '/api/wms/return/task/item/list',
+    method: 'get',
+    params
+  })
+}
+
+
+
+//放弃任务
+export function returnTaskAbandon(data:getReturnTaskBinListType) {
+  return request({
+    url: '/api/wms/return/task/abandon',
+    method: 'post',
+    data
+  })
+}
+
+//完成分配格口和呼叫校车
+export function returnTaskFirstStepComplete(data:returnTaskFirstStepCompleteType) {
+  return request({
+    url: '/api/wms/return/task/firstStepComplete',
+    method: 'post',
+    data
+  })
+}
+
+//更新还库任务已使用数量
+export function updateReturnTaskUsedQty(data:updateReturnTaskUsedQtyType) {
+  return request({
+    url: '/api/wms/return/task/item/updateUsedQty',
+    method: 'post',
+    data
+  })
+}
+
+

+ 5 - 0
src/hooks/basic/menu.js

@@ -79,6 +79,11 @@ export default function() {
               icon: 'newspaper-o',
               path: 'inventory-transfer',
             },
+            {
+              title: '还库任务',
+              icon: 'newspaper-o',
+              path: 'return-list',
+            },
           ],
         },
         {

+ 18 - 0
src/router/index.ts

@@ -182,6 +182,24 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'测试'},
     component: () => import('@/views/test.vue')
   },
+  {
+    path: '/return-list',
+    name: 'ReturnList',
+    meta:{title:'还库列表'},
+    component: () => import('@/views/inventory/returnTask/list/index.vue')
+  },
+  {
+    path: '/return-bin',
+    name: 'ReturnBin',
+    meta:{title:'还库任务-分格口'},
+    component: () => import('@/views/inventory/returnTask/bin/index.vue')
+  },
+  {
+    path: '/return-task',
+    name: 'ReturnTask',
+    meta:{title:'还库任务'},
+    component: () => import('@/views/inventory/returnTask/task/index.vue')
+  },
 ];
 
 // 创建路由实例

+ 97 - 0
src/types/returnTask.ts

@@ -0,0 +1,97 @@
+
+/**
+ * 分页查询还库任务列表
+ */
+export interface getReturnTaskPageType {
+  page: number;
+  size: number;
+  warehouse: string;
+  /**
+   * 状态
+   */
+  status?: string;
+  /**
+   * 任务号
+   */
+  taskNo?: string;
+  [property: string]: any;
+}
+
+/**
+ * 创建还库任务
+ */
+export interface createReturnTaskType {
+  /**
+   * 容器号
+   */
+  containerCode: string;
+  /**
+   * 仓库编码
+   */
+  warehouse: string;
+  [property: string]: any;
+}
+
+/**
+ * 创建还库任务
+ */
+export interface createReturnFirstSeedType {
+  /**
+   * 商品条码
+   */
+  barcode: string;
+  /**
+   * 库位容器号
+   */
+  containerCode: string;
+  /**
+   * 商品批次号
+   */
+  lotNum: string;
+  /**
+   * 任务号
+   */
+  taskNo: string;
+  [property: string]: any;
+}
+
+//查询还库分配格口数据
+export interface getReturnTaskBinListType {
+  taskNo: string;
+  [property: string]: any;
+}
+
+//完成分配格口和呼叫校车
+export interface returnTaskFirstStepCompleteType {
+  /**
+   * 是否调用海康快上
+   */
+  callHikQuickIn: boolean;
+  /**
+   * 任务号
+   */
+  taskNo: string;
+  [property: string]: any;
+}
+
+/**
+ * 更新还库任务已使用数量
+ */
+export interface updateReturnTaskUsedQtyType {
+  /**
+   * 批次号
+   */
+  lotNum: string;
+  /**
+   * 本次移库数量
+   */
+  moveQty: number;
+  /**
+   * 任务号
+   */
+  taskNo: string;
+  [property: string]: any;
+}
+
+
+

+ 5 - 6
src/views/haikang/putaway/putaway/index.vue

@@ -51,14 +51,12 @@
               v-else
               ref="shelfRef"
               :shelfCode="workBinNo"
-              :highlightCells="barcodeActive.locationId"
-              :rows="4"
-              :cols="2"
+              :highlightCells="[barcodeActive.locationId]"
+              :rows="barcodeActive.containerRow"
+              :cols="barcodeActive.containerCol"
             ></shelf>
           </div>
           <div  style="font-size: 14px;" v-if="barcodeActive.gridNum" >料箱格口:{{ barcodeActive.gridNum }}</div>
-<!--          <div  style="font-size: 14px;"  v-else >料箱格口:请手动扫描</div>-->
-          <!-- 货架组件 -->
         </div>
       </div>
       <div class="putaway-bottom"  v-if="equipmentBarcodeList.length>0">
@@ -548,7 +546,8 @@ const _setBoxInboundInventory = async () => {
     const data={
       container,sku,boxCode,taskLineNoList,taskNo,warehouse,lotNum,
       location:searchLocation.value,
-      quantity:count.value
+      quantity:count.value,
+      robotType:mode.value?'ASRS':'HIK'
     }
     const res = await setBoxInboundInventory(data)
     return res

+ 546 - 0
src/views/inventory/returnTask/bin/index.vue

@@ -0,0 +1,546 @@
+<template>
+  <div class="container">
+    <van-nav-bar
+        title="还库任务-分格口"
+        left-arrow
+        fixed
+        placeholder
+        @click-left="onClickLeft"
+    >
+      <template #left>
+        <van-icon name="arrow-left" size="25"/>
+        <div style="color: #fff">返回</div>
+      </template>
+      <template #right>
+        <div style="color: #fff;line-height: 46px " @click="onComplete">完成</div>
+        <div style="padding:14px 0 12px 6px" @click="onClickRightIcon">
+          <van-icon name="list-switch" size="25"/>
+        </div>
+      </template>
+    </van-nav-bar>
+    <div class="move-stock">
+      <div class="code">
+        <div class="code-title">
+          <div>{{ containerNo || '' }}</div>
+          <div class="code-tips">
+            <van-notice-bar :background="'none'" :speed="50" :text="tips"/>
+          </div>
+        </div>
+        <div class="code-input">
+          <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>
+        </div>
+      </div>
+      <div class="move-stock-content">
+        <table class="compact-table">
+          <colgroup>
+            <col v-for="i in 10" style="width: 10%;">
+          </colgroup>
+          <tbody>
+          <tr>
+            <td colspan="3">格口</td>
+            <td colspan="7">{{ bin }}</td>
+          </tr>
+          </tbody>
+        </table>
+      </div>
+      <div class="move-stock-list">
+        <table class="task-table">
+          <thead>
+          <tr>
+            <th>商品条码</th>
+            <th>商品名称</th>
+            <th>格口号</th>
+            <th>数量</th>
+          </tr>
+          </thead>
+          <tbody>
+          <tr v-for="(item, index) in binList" :key="index" v-if="binList.length>0">
+            <td>{{ item.barcode }}</td>
+            <td>{{ item.skuName }}</td>
+            <td>{{ item.slot }}</td>
+            <td>{{ item.qty }}</td>
+          </tr>
+          <tr v-else>
+            <td colspan="4">
+              <van-empty :image="nodataUrl" image-size="120"/>
+            </td>
+          </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+    <!-- 弹出框 -->
+    <van-action-sheet
+        v-model:show="modeTrueFalseBy"
+        :actions="actions"
+        cancel-text="取消"
+        close-on-click-action
+        @select="onSelectMode"
+    />
+    <!--  单据选择-->
+    <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.lotNum,item.customerId);barcodeActive=item;lotBarcodeTrueFalseBy=false">
+          <template #title>
+            {{ item.sku }}({{ item.availableQty }}件)
+          </template>
+          <template #label>
+            生产日期:{{ item.productionDate || '--' }}-失效日期:{{ item.expirationDate || '--' }}
+          </template>
+        </van-cell>
+      </van-cell-group>
+    </van-action-sheet>
+  </div>
+</template>
+
+<script setup>
+import {onMounted, onUnmounted, ref} from 'vue'
+import {useRouter, useRoute} from 'vue-router'
+import {useStore} from '@/store/modules/user'
+import {androidFocus, getHeader, goBack, scanError, scanSuccess} from '@/utils/android'
+import {closeListener, openListener, scanInit} from '@/utils/keydownListener'
+import {getInventoryList} from '@/api/check/index'
+import {closeLoading, showLoading} from '@/utils/loading'
+import nodataUrl from '@/assets/nodata.png'
+import {showConfirmDialog, showDialog, showNotify, showToast} from 'vant'
+import {getRecommendedLocation} from "@/api/haikang/index";
+import {
+  createReturnFirstSeed,
+  getReturnTaskBinList,
+  returnTaskAbandon,
+  returnTaskFirstStepComplete
+} from "@/api/returnTask/index";
+
+const router = useRouter()
+const route = useRoute()
+const store = useStore()
+try {
+  getHeader()
+  androidFocus()
+} catch (error) {
+  router.push('/login')
+}
+const warehouse = store.warehouse
+
+const pattern = /^[1-9]\d*$/
+// 容器号和扫描类型的状态
+const taskNo = ref(route.query.code)
+const containerNo = ref(route.query.container)
+//扫描类型
+const scanType = ref(2)
+const searchRef = ref(null)
+//扫描条码
+const searchBarcode = ref('')
+//扫描库位
+const location = ref('')
+//批次数据
+const lotBarcodeList = ref([])
+const lotBarcodeTrueFalseBy = ref(false)
+const barcodeActive = ref({})
+//格口
+const bin = ref('-')
+const binList = ref([])
+const model = ref({})
+const countRef = ref(null)
+const back = ref(true)
+// 页面初始化
+onMounted(() => {
+  openListener()
+  scanInit(_handlerScan)
+  loadData()
+})
+//切换模式
+const actions = [
+  {name: '放弃', key: 'abandon'}
+]
+const modeTrueFalseBy = ref(false)
+
+// 扫描条码监听
+const _handlerScan = (code) => {
+  if (scanType.value === 2) {
+    setTimeout(() => {
+      _getInventoryList(code)
+    }, 200)
+  }
+}
+// 获取库存数据
+const _getInventoryList = async (barcode) => {
+  const data = {warehouse, location: containerNo.value, barcode}
+  try {
+    showLoading()
+    const res = await getInventoryList(data)
+    closeLoading()
+    if (res.data.length === 0) {
+      scanError()
+      searchBarcode.value = ''
+      showNotify({duration: 5000, message: `条码:${barcode},未找到可还库库存,请检查条码!`})
+      tips.value = `条码:${barcode},未找到可还库库存,请检查条码!`
+      return
+    }
+    barcodeActive.value = {}
+    lotBarcodeList.value = res.data
+    searchBarcode.value = res.data[0].sku
+    if (lotBarcodeList.value.length == 1) {
+      barcodeActive.value = lotBarcodeList.value[0]
+      await _getRecommendedLocation(lotBarcodeList.value[0].lotNum, lotBarcodeList.value[0].customerId)
+    } else if (lotBarcodeList.value.length > 1) {
+      lotBarcodeTrueFalseBy.value = true
+    }
+
+  } catch (err) {
+    closeLoading()
+    console.error(err)
+  }
+}
+// 获取库存数据
+const _getRecommendedLocation = async (lotNum, owner) => {
+  try {
+    const params = {warehouse, lotNum, owner, zoneGroup: 'WH01-01'}
+    const res = await getRecommendedLocation(params)
+    //  '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 setLocation = (item) => {
+  const data = {
+    taskNo: taskNo.value,
+    barcode: searchBarcode.value,
+    lotNum: barcodeActive.value.lotNum,
+    containerCode: item.location
+  }
+  createReturnFirstSeed(data).then(res => {
+    scanSuccess()
+    searchBarcode.value = ''
+    bin.value = res.data
+    tips.value = `请扫描商品条码`
+    getTaskBinList()
+  }).catch(err => {
+    scanError()
+    barcodeActive.value = {}
+    searchBarcode.value = ''
+    bin.value = '-'
+    tips.value = `${err.message}`
+  })
+}
+
+// 提示文本根据扫描类型返回
+const tips = ref('请扫描商品条码')
+const onSelectMode = async (value) => {
+  if (value.key == 'abandon') {
+    showDialog({
+      title: '温馨提示',
+      message: '您正在进行放弃任务操作,是否继续?',
+    }).then(() => {
+      showLoading()
+      returnTaskAbandon({taskNo: taskNo.value}).then(res => {
+        router.push({name: 'ReturnList'})
+        showToast({duration: 3000, message: '当前任务已放弃正在跳转到任务页面,请稍后~'})
+      }).catch((err) => {
+        tips.value = `${err.message}`
+      }).finally(_ => {
+        closeLoading()
+      })
+    });
+
+  }
+}
+
+const getTaskBinList = () => {
+  getReturnTaskBinList({taskNo: taskNo.value}).then(res => {
+    binList.value = res.data.sort((a, b) => {
+      if (a.slot < b.slot) {
+        return 1;
+      }
+      if (a.slot > b.slot) {
+        return -1;
+      }
+      return 0
+    });
+  }).catch(err => {
+    scanError()
+    tips.value = `${err.message}`
+    showNotify({type: 'danger', duration: 3000, message: `${err.message}`})
+  })
+}
+const onClickRightIcon = () => {
+  modeTrueFalseBy.value = true
+}
+// 完成任务
+const onComplete = () => {
+  showConfirmDialog({
+    title: '温馨提示',
+    message:
+        '您正在进行完成操作,是否立即呼叫小车?',
+    confirmButtonText: '立即呼叫',
+    cancelButtonText: '不呼叫',
+  })
+    .then(() => {
+      _returnTaskFirstStepComplete(true)
+    })
+    .catch(() => {
+      _returnTaskFirstStepComplete(false)
+    });
+}
+const _returnTaskFirstStepComplete=(callHikQuickIn)=>{
+  showLoading()
+  returnTaskFirstStepComplete({taskNo:taskNo.value,callHikQuickIn}).then(res => {
+    if(callHikQuickIn){
+      router.replace({ name: 'ReturnTask', query: { code:taskNo.value,container:containerNo.value } });
+    }else {
+      router.push({name: 'ReturnList'})
+    }
+  }).finally(_ => {
+    closeLoading()
+  })
+}
+// 数据刷新
+const loadData = () => {
+  scanType.value = 2
+  getTaskBinList()
+  setTimeout(() => {
+    if (binList.value.length > 0) {
+      bin.value = binList.value[0].slot
+    }
+  }, 400)
+}
+
+const onClickLeft = () => {
+  router.push({name:'ReturnList'})
+};
+
+onUnmounted(() => {
+  closeListener()
+})
+
+window.onRefresh = loadData
+</script>
+
+<style scoped lang="scss">
+.container {
+  .move-stock {
+    .code {
+      background: #e9f4ff;
+      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;
+        font-size: 16px;
+      }
+
+      ::v-deep(.van-search__field) {
+        border-bottom: 2px solid #ffffff;
+        height: 50px;
+        display: flex;
+        align-items: center;
+      }
+
+      ::v-deep(.van-search__content) {
+        background: #fff;
+        height: 50px;
+        display: flex;
+        align-items: center;
+      }
+
+      ::v-deep(.van-field__control) {
+        font-size: 16px;
+        height: 50px;
+        line-height: 50px;
+      }
+
+      .search-input-barcode {
+        ::v-deep(.van-search__field) {
+          border-bottom: 2px solid #0077ff;
+          z-index: 2;
+        }
+      }
+    }
+
+    .code-tips {
+      color: #ed6a0c;
+      flex: 1;
+    }
+
+    .code-count {
+      font-size: 16px;
+      font-weight: bold;
+
+      span {
+        color: #0077ff;
+      }
+    }
+
+    .nav-right {
+      padding: 14px 0 12px 5px;
+    }
+
+    .move-stock-list {
+      width: 100%;
+      overflow-y: auto;
+      max-height: 280px;
+      min-height: 100px;
+
+      .move-button {
+        background: #1989fa;
+        color: #fff;
+        width: 100%;
+        height: 30px;
+        font-size: 14px;
+        line-height: 30px;
+        font-weight: bold;
+      }
+
+      .task-table,
+      .task-table-bin,
+      .task-table-box {
+        width: 100%;
+        table-layout: fixed;
+        border-collapse: collapse;
+        font-size: 13px;
+      }
+
+      .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: 13px;
+      }
+
+      .task-table-bin thead {
+        background-color: #3f8dff;
+      }
+
+      .task-table-bin tbody {
+        background: #cde7ff;
+      }
+
+      .task-table tbody tr.pricking-active {
+        background-color: #d6f9e7;
+      }
+
+      .task-table tbody tr.stock-active {
+        background-color: #fffadd;
+      }
+
+      .task-table tbody tr.virtual-active {
+        background-color: #cacaca;
+      }
+    }
+
+    .move-stock-content {
+      margin-bottom: 10px;
+
+      .compact-table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 0;
+        font-size: 14px;
+        line-height: 1.2;
+        border: 1px solid #ccc;
+
+        td {
+          padding: 8px 12px;
+          border: 1px solid #ccc;
+          background: transparent;
+          text-align: center;
+        }
+
+        td:first-child {
+          color: #333;
+          font-size: 25px;
+        }
+
+        td:last-child {
+          color: #e63535;
+          font-size: 50px;
+
+        }
+      }
+    }
+  }
+}
+
+.count-input {
+  ::v-deep(.van-field__value) {
+    border-bottom: 2px solid #0077ff;
+    font-size: 16px;
+  }
+}
+
+.completion {
+  text-align: right;
+  font-size: 14px;
+  line-height: 35px;
+  color: #0077ff;
+  padding: 0 10px;
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+.tips {
+  font-size: 13px;
+  text-align: left;
+  padding: 5px 15px;
+}
+</style>

+ 243 - 0
src/views/inventory/returnTask/list/index.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="container">
+    <div class="task">
+      <div class="top">
+        <div class="nav-bar">
+          <van-nav-bar title="还库任务" left-arrow @click-left="goBack" @click-right="onClickRight">
+            <template #left>
+              <van-icon name="arrow-left" size="25"   />
+              <div style="color: #fff;height: 46px;padding-right:20px;line-height: 46px" >返回</div>
+            </template>
+            <template #right>
+              <div style="color: #fff;line-height: 46px " >创建任务</div>
+            </template>
+          </van-nav-bar>
+        </div>
+        <div class="content">
+          <van-pull-refresh v-model="refreshing" @refresh="onRefresh" :style="{ 'max-height': computedMaxHeight, 'min-height': computedMaxHeight, 'overflow': 'auto' }">
+            <van-list
+              v-model:loading="loading"
+              :finished="finished"
+              finished-text="没有更多了"
+              @load="onLoad"
+              :style="{ 'max-height': computedMaxHeight,'min-height':computedMaxHeight ,'overflow':'auto'}"
+            >
+              <table border="1" style="width: 100%; border-collapse: collapse; text-align: center; table-layout: fixed;">
+                <thead>
+                <tr>
+                  <th>任务号</th>
+                  <th style="width: 50px">状态</th>
+                  <th style="width: 75px">创建日期</th>
+                  <th style="width: 55px">剩余数量</th>
+                  <th style="width: 70px">操作</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr v-for="(row, rowIndex) in taskList" :key="rowIndex" >
+                  <td style="word-wrap: break-word" >{{ row.containerCode }}</td>
+                  <td style="word-wrap: break-word" >{{ row.statusDesc }}</td>
+                  <td style="word-wrap: break-word;font-size:11px">{{ time(row.createTime) }}</td>
+                  <td style="word-wrap: break-word;">{{ row.remainingQty }}</td>
+                  <td>
+                    <van-button type="primary" size="mini" plain @click="onComplete(row)" v-if="row.status=='CREATED'" >作业</van-button>
+                    <van-button type="primary" size="mini" plain @click="linkTask(row)">查看</van-button>
+                  </td>
+                </tr>
+                <tr v-if="taskList.length === 0 && !loading">
+                  <td colspan="5">
+                    <van-empty :image="nodataUrl" image-size="140" >
+                      <van-button round type="primary" class="bottom-button" size="small" @click="onRefresh">刷新</van-button>
+                    </van-empty>
+                  </td>
+                </tr>
+                </tbody>
+              </table>
+            </van-list>
+            <van-back-top right="80vw" bottom="10vh" />
+          </van-pull-refresh>
+        </div>
+      </div>
+    </div>
+    <input-barcode ref="inputBarcodeRef" @setBarcode="setBarcode" />
+  </div>
+</template>
+
+<script setup>
+import { computed, onMounted, ref, nextTick } from 'vue'
+import { getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
+import { useStore } from '@/store/modules/user'
+import {showConfirmDialog, showToast} from 'vant'
+import nodataUrl from '@/assets/nodata.png'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { useRouter } from 'vue-router'
+import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
+import {createReturnTask, getReturnTaskPage, returnTaskFirstStepComplete} from "@/api/returnTask/index.ts"
+const router = useRouter()
+try {
+  getHeader()
+}catch (error) {
+  router.push('/login')
+}
+const topHeight = ref(46)
+const computedMaxHeight = computed(() => {
+  return `calc(100vh - ${topHeight.value}px)`;
+});
+const storeUser = useStore()
+const warehouse = storeUser.warehouse
+const taskList = ref([])
+const statusMap = ref({
+  'CREATED': '创建',
+  'IN_PROGRESS': '进行中',
+  'FINISHED': '完成'
+})
+
+const page = ref(1)
+const size = ref(20)
+const total = ref(0)
+const loading = ref(false)
+const finished = ref(false)
+const refreshing = ref(false)
+const isLoading = ref(false)
+
+const onLoad = async () => {
+  if (finished.value) {
+    loading.value = false
+    return
+  }
+
+  if (isLoading.value) {
+    return
+  }
+
+  const isRefreshing = refreshing.value
+  if (isRefreshing) {
+    page.value = 1
+    taskList.value = []
+    refreshing.value = false
+  }
+
+  isLoading.value = true
+
+  try {
+    const params = {
+      warehouse: warehouse,
+      page: page.value,
+      size: size.value
+    }
+    const res = await getReturnTaskPage(params)
+    const records = res.data.records || []
+
+    if (page.value === 1) {
+      total.value = res.data.total || 0
+    }
+
+    taskList.value = [...taskList.value, ...records]
+
+    if (records.length === 0 || taskList.value.length >= total.value) {
+      finished.value = true
+    } else {
+      page.value++
+    }
+  } catch (error) {
+    console.error('加载数据失败:', error)
+    finished.value = true
+  } finally {
+    isLoading.value = false
+    loading.value = false
+  }
+}
+const time = (createTime) => {
+  const date = new Date(createTime)
+  const year = date.getFullYear().toString().slice(2)
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  return `${year}${month}${day}${hours}:${minutes}`
+}
+
+const onRefresh = () => {
+  finished.value = false
+  refreshing.value = true
+  page.value = 1
+  loading.value = false
+  isLoading.value = false
+  onLoad()
+}
+
+onMounted(() => {
+  nextTick(() => {
+    if (!loading.value && !finished.value && taskList.value.length === 0) {
+      onLoad()
+    }
+  })
+})
+
+const inputBarcodeRef = ref(null)
+const onClickRight = () => {
+  inputBarcodeRef.value?.show(undefined, '请扫描反拣容器', '')
+}
+
+const setBarcode = (code) => {
+  createReturnTask({ warehouse, containerCode: code }).then(res => {
+    console.log(res, "res")
+    scanSuccess()
+    router.push({ name: 'ReturnBin', query: { code: res.data, container: code, status: 'CREATING' } })
+  }).catch(err => {
+    scanError()
+    setTimeout(() => {
+      inputBarcodeRef.value?.show('', '请扫描反拣容器', err.message)
+    }, 300)
+  })
+}
+
+
+const linkTask = (item) => {
+  const name = item.status === 'WORKING' ? 'ReturnTask' : 'ReturnBin'
+  router.push({ name, query: { code: item.taskNo, container: item.containerCode, status: item.status } })
+}
+// 完成任务
+const onComplete = (item) => {
+  showConfirmDialog({
+    title: '温馨提示',
+    message:
+        '您正在进行作业操作,是否立即呼叫小车?',
+    confirmButtonText: '立即呼叫',
+  })
+      .then(() => {
+        _returnTaskFirstStepComplete(true,item)
+      })
+}
+const _returnTaskFirstStepComplete=(callHikQuickIn,item)=>{
+  showLoading()
+  returnTaskFirstStepComplete({taskNo:item.taskNo,callHikQuickIn}).then(res => {
+    router.replace({ name: 'ReturnTask', query: { code:item.taskNo,container:item.containerNo } });
+  }).finally(_ => {
+    closeLoading()
+  })
+}
+
+window.onRefresh = onRefresh
+</script>
+
+<style scoped lang="sass">
+.container
+  width: 100%
+
+  .task
+    display: flex
+    flex-direction: column
+    height: 100vh
+
+    .top
+      flex: 1
+      display: flex
+      flex-direction: column
+
+      .nav-bar
+        height: 46px
+
+      .content
+        flex: 1
+        font-size: 13px
+</style>

+ 856 - 0
src/views/inventory/returnTask/task/index.vue

@@ -0,0 +1,856 @@
+<template>
+  <div class="container">
+    <van-nav-bar
+        title="还库任务"
+        left-arrow
+        fixed
+        placeholder
+        @click-left="onClickLeft"
+    >
+      <template #left>
+        <van-icon name="arrow-left" size="25"/>
+        <div style="color: #fff">返回</div>
+      </template>
+      <template #right>
+        <div style="color: #fff;line-height: 46px " @click="onComplete">料箱回库</div>
+      </template>
+    </van-nav-bar>
+    <div class="move-stock">
+      <div class="code">
+        <div class="code-title">
+          <div>{{ containerNo || '' }}</div>
+          <div class="code-tips">
+            <van-notice-bar :background="'none'" :speed="50" :text="tips"/>
+          </div>
+        </div>
+        <div class="code-input">
+          <van-field v-model="workBinNo" placeholder="请扫描料箱号" label="料箱编号:" left-icon="" :class="[scanType===1?'search-input-barcode':'']"
+                     autocomplete="off" readonly @click="setBarcodeInput(workBinNo,1)"></van-field>
+          <van-field v-model="searchBarcode" placeholder="请扫描商品条码" label="商品条码:" left-icon="" readonly :class="[scanType===2?'search-input-barcode':'']"
+                     autocomplete="off" @click="setBarcodeInput(searchBarcode,2)"></van-field>
+          <van-field ref="countRef" @click="scanType=3" v-model="count" type="number" placeholder="还库数量" label="还库数量:" left-icon="" autocomplete="off"
+                     :class="{'search-input-barcode': scanType === 3,'input-count': barcodeActive.availableQty}"  @keydown.enter="setPutaway(2)"
+                     :max="barcodeActive.availableQty?barcodeActive.availableQty:10000"
+          >
+            <template #button>
+              <div v-if="barcodeActive.qty" style="color:#333;font-size: 14px;font-weight: bold">预计{{ barcodeActive.qty ||0 }}</div>
+            </template>
+          </van-field>
+          <van-field v-model="searchLocation" placeholder="请扫描库位" label="还库库位:" left-icon="" readonly :class="[scanType===4?'search-input-barcode':'']"
+                     autocomplete="off" @click="setBarcodeInput(searchLocation,4)">
+            <template #button>
+              <div v-if="recommendedLocation" style="color:#666;font-size: 12px"><span style="font-size: 10px">推荐:</span>{{ recommendedLocation }}</div>
+            </template>
+          </van-field>
+        </div>
+        <div class="code-button">
+          <van-button type="primary" size="small" block :disabled="searchLocation!=recommendedLocation || searchLocation=='' "   @click="setPutaway(2)" >提交还库</van-button>
+        </div>
+      </div>
+      <div class="move-stock-list">
+        <table class="task-table">
+          <thead>
+          <tr>
+            <th>商品条码</th>
+            <th>商品名称</th>
+            <th>格口号</th>
+            <th>数量</th>
+          </tr>
+          </thead>
+          <tbody>
+          <template v-if="binList.length>0">
+            <tr v-for="(item, index) in binList" :key="index"
+                :class="{
+                  'container-highlight': isContainerHighlighted(item.containerCode),
+                  'barcode-highlight': isBarcodeHighlighted(item.containerCode, item.lotNum)
+                }">
+              <td>{{ item.barcode }}</td>
+              <td>{{ item.skuName }}</td>
+              <td>{{ item.slot }}</td>
+              <td>{{item.availableQty}}</td>
+            </tr>
+          </template>
+          <tr v-else>
+            <td colspan="4">
+              <van-empty :image="nodataUrl" image-size="120"/>
+            </td>
+          </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+    <!--  单据选择-->
+    <van-action-sheet :show="lotBarcodeTrueFalseBy" cancel-text="取消" description="请选择商品批次"
+                      close-on-click-action
+                      @update:show="lotBarcodeTrueFalseBy = $event">
+      <van-cell-group>
+        <van-cell v-for="(item, idx) in lotBarcodeList" :key="idx"
+                  @click="onSelectLotBarcode(item)">
+          <template #title>
+            {{item.barcode }}({{item.availableQty}}件)
+          </template>
+          <template #label>
+            生产日期:{{ item.productionDate || '--' }}-失效日期:{{ item.expirationDate || '--' }}
+          </template>
+        </van-cell>
+      </van-cell-group>
+    </van-action-sheet>
+    <!-- 条码输入组件 -->
+    <input-barcode ref="inputBarcodeRef" @setBarcode="_handlerScan" />
+    <!-- 料箱回库组件 -->
+    <go-box-back ref="goBackRef" :scanType="scanType" @setGoBack="setGoBack" />
+  </div>
+</template>
+
+<script setup>
+import {onMounted, onUnmounted, ref} from 'vue'
+import {useRouter, useRoute} from 'vue-router'
+import {useStore} from '@/store/modules/user'
+import {androidFocus, getHeader, goBack, scanError, scanSuccess} from '@/utils/android'
+import {closeListener, openListener, scanInit} from '@/utils/keydownListener'
+import {closeLoading, showLoading} from '@/utils/loading'
+import nodataUrl from '@/assets/nodata.png'
+import {showConfirmDialog, showDialog, showNotify, showToast} from 'vant'
+import { barcodeToUpperCase } from '@/utils/dataType'
+import {
+  getReturnTaskBinList,
+  updateReturnTaskUsedQty
+} from "@/api/returnTask/index";
+import { movementReturn } from '@/api/check/index'
+import { boxReturn } from '@/api/haikang'
+import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
+import GoBoxBack from '@/views/haikang/putaway/components/GoBoxBack.vue'
+
+const router = useRouter()
+const route = useRoute()
+const store = useStore()
+try {
+  getHeader()
+  androidFocus()
+} catch (error) {
+  router.push('/login')
+}
+const warehouse = store.warehouse
+
+const pattern = /^[1-9]\d*$/
+// 容器号和扫描类型的状态
+const taskNo = ref(route.query.code)
+const containerNo = ref(route.query.container)
+//扫描类型
+const scanType = ref(2)
+const searchRef = ref(null)
+//料箱编号
+const workBinNo = ref('')
+//扫描条码
+const searchBarcode = ref('')
+//原始扫描的条码(用于匹配binList)
+const originalBarcode = ref('')
+//还库数量
+const count = ref('')
+//扫描库位
+const location = ref('')
+//还库库位
+const searchLocation = ref('')
+//推荐库位
+const recommendedLocation = ref('')
+//提示信息
+const tips = ref('请扫描料箱编号')
+//批次数据
+const lotBarcodeList = ref([])
+const lotBarcodeTrueFalseBy = ref(false)
+const barcodeActive = ref({})
+//格口
+const bin = ref('-')
+const binList = ref([])
+const model = ref({})
+const countRef = ref(null)
+const back = ref(true)
+const goBackRef = ref(null)
+// 高亮状态:橘黄色高亮(containerCode匹配)
+const orangeHighlightSet = ref(new Set())
+// 高亮状态:绿色高亮(条码匹配)
+const greenHighlightSet = ref(new Set())
+
+// 检查是否应该高亮
+const isContainerHighlighted = (containerCode) => orangeHighlightSet.value?.has(containerCode)
+const isBarcodeHighlighted = (containerCode, lotNum) => greenHighlightSet.value?.has(`${containerCode}-${lotNum}`)
+
+// 更新高亮状态:从橘黄色变为绿色
+const updateHighlight = (barcode, lotNum) => {
+  const barcodeUpper = barcodeToUpperCase(barcode)
+  binList.value
+      .filter(item => {
+        // 匹配条码
+        const barcodeMatch = barcodeToUpperCase(item.barcode) === barcodeUpper ||
+            barcodeToUpperCase(item.alternateSku1) === barcodeUpper ||
+            barcodeToUpperCase(item.alternateSku2) === barcodeUpper
+        return orangeHighlightSet.value.has(item.containerCode) &&
+            barcodeMatch &&
+            item.lotNum === lotNum
+      })
+      .forEach(item => {
+        orangeHighlightSet.value.delete(item.containerCode)
+        greenHighlightSet.value.add(`${item.containerCode}-${item.lotNum}`)
+      })
+}
+
+// 页面初始化
+onMounted(() => {
+  openListener()
+  scanInit(_handlerScan)
+  loadData()
+})
+
+//设置条码
+const inputBarcodeRef = ref(null)
+const setBarcodeInput = (code, type) => {
+  const typeMap = { 1: '请扫描料箱编号', 2: '请扫描商品条码', 3: '请输入还库数量', 4: '请扫描还库库位' }
+  scanType.value = type
+  inputBarcodeRef.value?.show(code, typeMap[type])
+}
+
+// 扫描条码监听
+const _handlerScan = (code) => {
+  if (scanType.value === 1) {
+    workBinNo.value = code
+    // 先清空推荐库位、预计数量和匹配的条码
+    recommendedLocation.value = ''
+    barcodeActive.value = {}
+    searchBarcode.value = ''
+    originalBarcode.value = ''
+    count.value = ''
+    searchLocation.value = ''
+    // 高亮所有匹配的containerCode
+    orangeHighlightSet.value.clear()
+    greenHighlightSet.value.clear()
+    // 在binList中查找匹配的containerCode并高亮
+    const matched = binList.value.filter(item =>
+        item.containerCode && (item.containerCode === code || item.containerCode.startsWith(`${code}-`))
+    )
+    if (matched.length === 0) {
+      scanError()
+      tips.value = `料箱编号:${code},未找到匹配的数据`
+      showNotify({ type: 'danger', duration: 3000, message: `料箱编号:${code},未找到匹配的数据` })
+      // 清空页面
+      workBinNo.value = ''
+      searchBarcode.value = ''
+      originalBarcode.value = ''
+      searchLocation.value = ''
+      recommendedLocation.value = ''
+      count.value = ''
+      barcodeActive.value = {}
+      orangeHighlightSet.value.clear()
+      greenHighlightSet.value.clear()
+      scanType.value = 1
+      return
+    }
+    matched.forEach(item => orangeHighlightSet.value.add(item.containerCode))
+    sortBinList()
+    scanType.value = 2
+    tips.value = '请扫描商品条码'
+    scanSuccess()
+  } else if (scanType.value === 2) {
+    // 商品条码扫描
+    setTimeout(() => {
+      _getInventoryList(code)
+    }, 200)
+  } else if (scanType.value === 4) {
+    const codeUpper = barcodeToUpperCase(code)
+    const recommendedUpper = barcodeToUpperCase(recommendedLocation.value)
+    if (codeUpper !== recommendedUpper) {
+      tips.value = `${code}-扫描库位与推荐库位不一致,请重新扫描`
+      showNotify({ type: 'danger', duration: 3000, message: `${code}-扫描库位与推荐库位不一致,请重新扫描` })
+      scanError()
+      return
+    }
+    searchLocation.value = barcodeToUpperCase(code)
+    scanType.value = 3
+    count.value = barcodeActive.value.availableQty || 1
+    tips.value = '请输入还库数量'
+    scanSuccess()
+  }
+}
+
+
+// 从binList中获取匹配到的推荐库位(containerCode)
+const getRecommendedLocationFromBin = (barcode, lotNum) => {
+  const barcodeUpper = barcodeToUpperCase(barcode)
+  // 获取匹配到的绿色高亮项(最终匹配到的批次)
+  const matchedItems = binList.value.filter(item => {
+    // 匹配 barcode、alternateSku1、alternateSku2 三个字段,使用 barcodeToUpperCase 进行大小写不敏感匹配
+    const barcodeMatch = barcodeToUpperCase(item.barcode) === barcodeUpper ||
+        barcodeToUpperCase(item.alternateSku1) === barcodeUpper ||
+        barcodeToUpperCase(item.alternateSku2) === barcodeUpper
+    const match = barcodeMatch && item.lotNum === lotNum
+    if (!match) return false
+    return greenHighlightSet.value?.has(`${item.containerCode}-${item.lotNum}`)
+  })
+
+  if (matchedItems.length > 0) {
+    // 返回第一个匹配项的containerCode作为推荐库位
+    return matchedItems[0].containerCode || ''
+  }
+  return ''
+}
+
+// 选择批次后的处理
+const onSelectLotBarcode = (item) => {
+  // 使用批次对象中保存的实际 barcode(支持 alternateSku1、alternateSku2 匹配)
+  const actualBarcode = item.barcode || originalBarcode.value
+  updateHighlight(actualBarcode, item.lotNum)
+  // 基于最终匹配到的批次计算数量(使用可用数量 availableQty)
+  barcodeActive.value = item
+  // 从binList中获取推荐库位(containerCode)
+  recommendedLocation.value = getRecommendedLocationFromBin(actualBarcode, item.lotNum)
+  lotBarcodeTrueFalseBy.value = false
+  scanType.value = 4
+  tips.value = '请扫描还库库位'
+  scanSuccess()
+}
+
+const _getInventoryList = (barcode) => {
+  try {
+    // 检查是否有当前料箱号
+    if (!workBinNo.value) {
+      scanError()
+      searchBarcode.value = ''
+      originalBarcode.value = ''
+      const msg = '请先扫描料箱编号'
+      tips.value = msg
+      showNotify({duration: 5000, message: msg})
+      return
+    }
+
+    // 保存原始条码用于匹配binList
+    originalBarcode.value = barcode
+
+    // 在当前料箱内查找匹配的条码数据
+    const scanBarcodeUpper = barcodeToUpperCase(barcode)
+    const matchedItems = binList.value.filter(binItem => {
+      const containerMatch = binItem.containerCode === workBinNo.value ||
+          binItem.containerCode?.startsWith(`${workBinNo.value}-`)
+      if (!containerMatch) return false
+      return barcodeToUpperCase(binItem.barcode) === scanBarcodeUpper ||
+          barcodeToUpperCase(binItem.alternateSku1) === scanBarcodeUpper ||
+          barcodeToUpperCase(binItem.alternateSku2) === scanBarcodeUpper
+    })
+
+    // 如果没有任何匹配的数据,提示错误
+    if (matchedItems.length === 0) {
+      scanError()
+      const msg = `${barcode}不在当前料箱内,当前料箱号:${workBinNo.value}`
+      searchBarcode.value = ''
+      originalBarcode.value = ''
+      tips.value = msg
+      showNotify({duration: 5000, message: msg})
+      return
+    }
+
+    // 根据批次(lotNum)分组,生成批次列表
+    const lotMap = new Map()
+    matchedItems.forEach(item => {
+      const lotNum = item.lotNum || ''
+      if (!lotMap.has(lotNum)) {
+        // 获取实际匹配的条码
+        const matchedBarcode = barcodeToUpperCase(item.barcode) === scanBarcodeUpper ? item.barcode :
+            barcodeToUpperCase(item.alternateSku1) === scanBarcodeUpper ? item.barcode :
+                barcodeToUpperCase(item.alternateSku2) === scanBarcodeUpper ? item.barcode : barcode
+
+        // 计算可用数量
+        lotMap.set(lotNum, {
+          customerId:item.customerId,
+          lotNum: lotNum,
+          usedQty:item.usedQty,
+          qty:item.qty,
+          availableQty: item.availableQty, // 添加可用数量
+          productionDate: item.productionDate ,
+          expirationDate: item.expirationDate,
+          barcode: item.barcode // 使用实际匹配的条码
+        })
+      }
+    })
+
+    // 转换为数组
+    const matchedInBin = Array.from(lotMap.values())
+
+    // 如果没有任何批次,提示错误
+    if (matchedInBin.length === 0) {
+      scanError()
+      const msg = `${barcode}不在当前料箱内,当前料箱号:${workBinNo.value}`
+      searchBarcode.value = ''
+      originalBarcode.value = ''
+      tips.value = msg
+      showNotify({duration: 5000, message: msg})
+      return
+    }
+
+    // 只保留在当前料箱内的批次
+    lotBarcodeList.value = matchedInBin
+    searchBarcode.value = matchedInBin[0].barcode
+
+    if (matchedInBin.length === 1) {
+      // 只有一个批次,直接选择
+      const item = matchedInBin[0]
+      // 更新高亮状态(从橘黄色变为绿色)
+      updateHighlight(item.barcode, item.lotNum)
+      barcodeActive.value = item
+      // 从binList中获取推荐库位(containerCode)
+      recommendedLocation.value = getRecommendedLocationFromBin(item.barcode, item.lotNum)
+      scanType.value = 4
+      tips.value = '请扫描还库库位'
+      scanSuccess()
+    } else {
+      // 多个批次,弹出批次选择弹窗
+      lotBarcodeTrueFalseBy.value = true
+    }
+  } catch (err) {
+    console.error(err)
+    scanError()
+    tips.value = err.message || '匹配失败'
+    showNotify({type: 'danger', duration: 3000, message: err.message || '匹配失败'})
+  }
+}
+
+// 提交还库
+const setPutaway = async (type) => {
+  if (type !== 2) return
+
+  const validations = [
+    { check: !searchBarcode.value, type: 2, msg: '请扫描商品条码' },
+    { check: !searchLocation.value, type: 4, msg: '请扫描还库库位' },
+    { check: barcodeToUpperCase(searchLocation.value) !== barcodeToUpperCase(recommendedLocation.value), type: 4, msg: '扫描库位与推荐库位不一致,请重新扫描' },
+    { check: !count.value || Number(count.value) <= 0, type: 3, msg: '请输入还库数量' },
+    { check: barcodeActive.value.availableQty && Number(count.value) > Number(barcodeActive.value.availableQty), type: 3, msg: '还库数量不能大于预计还库数量' }
+  ]
+
+  const error = validations.find(v => v.check)
+  if (error) {
+    scanType.value = error.type
+    tips.value = error.msg
+    showNotify({ type: 'danger', duration: 3000, message: error.msg })
+    scanError()
+    return
+  }
+  const {customerId,lotNum,barcode:sku}=barcodeActive.value
+  const data = {
+    warehouse: warehouse,
+    owner:customerId,
+    lotNum,
+    sku,
+    quantity: count.value,
+    fmLocation:containerNo.value ,
+    toLocation: searchLocation.value
+  }
+  try {
+    showLoading()
+    const res = await movementReturn(data)
+    if(res){
+      // 还库成功后,更新还库任务已使用数量
+      try {
+        await updateReturnTaskUsedQty({
+          taskNo: taskNo.value,
+          lotNum: barcodeActive.value.lotNum,
+          moveQty: Number(count.value)
+        })
+      } catch (updateErr) {
+        console.error('更新已使用数量失败:', updateErr)
+        // 不阻断主流程,只记录错误
+      }
+    }
+    closeLoading()
+    scanSuccess()
+    // 保存当前还库数量,用于料箱回库弹窗显示
+    const lastCount = count.value
+    const lastBarcodeActive = { ...barcodeActive.value }
+    // 重置(保留料箱号,以便继续扫描同一料箱的其他商品)
+    searchBarcode.value = ''
+    originalBarcode.value = ''
+    searchLocation.value = ''
+    recommendedLocation.value = ''
+    count.value = ''
+    barcodeActive.value = {}
+    // 刷新binList
+    getTaskBinList()
+
+    // 检查当前料箱是否已完成还库
+    setTimeout(() => {
+      if (checkBinComplete()) {
+        // 所有商品都已还库完成,弹出料箱回库提示
+        goBackRef.value?.show(workBinNo.value, '1', lastCount, lastBarcodeActive)
+      } else {
+        // 还有未还库的商品,继续扫描
+        scanType.value = 2
+        tips.value = '请扫描商品条码'
+        showNotify({ type: 'success', duration: 3000, message: '还库成功,请继续扫描商品条码' })
+      }
+    }, 300)
+  } catch (err) {
+    closeLoading()
+    scanError()
+    tips.value = err.message || '还库失败'
+    showNotify({ type: 'danger', duration: 3000, message: err.message || '还库失败' })
+  }
+}
+
+// 排序:匹配到的料箱排在最前面
+const sortBinList = () => {
+  binList.value.sort((a, b) => {
+    const aHighlighted = orangeHighlightSet.value.has(a.containerCode)
+    const bHighlighted = orangeHighlightSet.value.has(b.containerCode)
+    if (aHighlighted !== bHighlighted) return aHighlighted ? -1 : 1
+    return b.slot - a.slot
+  })
+}
+
+const getTaskBinList = () => {
+  getReturnTaskBinList({taskNo: taskNo.value}).then(res => {
+    // 过滤并计算可用数量
+    binList.value = res.data
+        .filter(v => v.qty !== v.usedQty)
+        .map(item => ({
+          ...item,
+          availableQty: (item.qty || 0) - (item.usedQty || 0) // 计算可用数量
+        }))
+    sortBinList()
+  }).catch(err => {
+    scanError()
+    tips.value = `${err.message}`
+    showNotify({type: 'danger', duration: 3000, message: `${err.message}`})
+  })
+}
+
+// 检查当前料箱是否已完成还库
+const checkBinComplete = () => {
+  if (!workBinNo.value) return false
+  // 检查binList中是否还有匹配当前料箱号的数据
+  const remainingItems = binList.value.filter(item =>
+      item.containerCode && (item.containerCode === workBinNo.value || item.containerCode.startsWith(`${workBinNo.value}-`))
+  )
+  return remainingItems.length === 0
+}
+// 料箱回库(使用上架页面的逻辑)
+const onComplete = () => {
+  if (!workBinNo.value) {
+    scanType.value = 1
+    tips.value = '请先扫描料箱编号'
+    showNotify({ type: 'danger', duration: 3000, message: '请先扫描料箱编号' })
+    scanError()
+    return
+  }
+
+  // 从binList中获取container和externalCode信息
+  const currentBinItem = binList.value.find(item =>
+      item.containerCode && (item.containerCode === workBinNo.value || item.containerCode.startsWith(`${workBinNo.value}-`))
+  )
+
+  const container = currentBinItem?.container || containerNo.value
+  const externalCode = currentBinItem?.externalCode || taskNo.value
+
+  showConfirmDialog({
+    title: '料箱回库',
+    message: `${workBinNo.value},是否执行料箱回库?`,
+  })
+      .then(() => {
+        showLoading()
+        boxReturn({ warehouse, container, boxCode: workBinNo.value, externalCode,taskType:'HIK' }).then(res => {
+          closeLoading()
+          scanSuccess()
+          tips.value = '料箱回库成功,请继续扫描料箱编号'
+          showNotify({ type: 'success', duration: 3000, message: '料箱回库成功,请继续扫描料箱编号' })
+          if(binList.value==0){
+            router.push({ name: 'ReturnList'})
+          }
+          resetAllFields()
+        }).catch(err => {
+          closeLoading()
+          scanError()
+          tips.value = err.message || '料箱回库失败'
+          showNotify({ type: 'danger', duration: 3000, message: err.message || '料箱回库失败' })
+        })
+      })
+      .catch(() => {})
+}
+
+// 料箱回库处理(使用上架页面的逻辑)
+const setGoBack = async (item) => {
+  if (item.active === '1') {
+    // 料箱回库
+    if (!workBinNo.value) {
+      scanType.value = 1
+      tips.value = '请先扫描料箱编号'
+      showNotify({ type: 'danger', duration: 3000, message: '请先扫描料箱编号' })
+      scanError()
+      return
+    }
+
+    const container =  containerNo.value
+    const externalCode =  taskNo.value
+    showLoading()
+    try {
+      await boxReturn({ warehouse, container, boxCode: workBinNo.value, externalCode,taskType:'HIK'  })
+      closeLoading()
+      scanSuccess()
+      if(binList.value==0){
+       await router.push({ name: 'ReturnList'})
+      }
+      // 重置所有状态
+      resetAllFields()
+      tips.value = '料箱回库成功,请继续扫描料箱编号'
+      showNotify({ type: 'success', duration: 3000, message: '料箱回库成功,请继续扫描料箱编号' })
+    } catch (err) {
+      closeLoading()
+      scanError()
+      tips.value = err.message || '料箱回库失败'
+      showNotify({ type: 'danger', duration: 3000, message: err.message || '料箱回库失败' })
+    }
+  }
+}
+
+// 重置所有字段
+const resetAllFields = () => {
+  workBinNo.value = ''
+  searchBarcode.value = ''
+  originalBarcode.value = ''
+  searchLocation.value = ''
+  recommendedLocation.value = ''
+  count.value = ''
+  barcodeActive.value = {}
+  orangeHighlightSet.value.clear()
+  greenHighlightSet.value.clear()
+  scanType.value = 1
+  getTaskBinList()
+}
+
+onUnmounted(() => {
+  closeListener()
+})
+
+const onClickLeft = () => {
+  router.push({name:'ReturnList'})
+};
+// 数据刷新
+const loadData = () => {
+  scanType.value = 1
+  tips.value = '请扫描料箱编号'
+  getTaskBinList()
+}
+
+onUnmounted(() => {
+  closeListener()
+})
+
+window.onRefresh = loadData
+</script>
+
+<style scoped lang="scss">
+.container {
+  .move-stock {
+    .code {
+      background: #e9f4ff;
+      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-field) {
+        min-height: 50px;
+        display: flex;
+        align-items: center;
+      }
+
+      ::v-deep(.van-field__body) {
+        min-height: 50px;
+        display: flex;
+        align-items: center;
+      }
+
+      ::v-deep(.van-cell) {
+        padding: 5px 20px 0 20px;
+      }
+
+      ::v-deep(.van-field__control) {
+        border-bottom: 2px solid #efefef;
+        height: 50px;
+        line-height: 50px;
+        font-size: 16px;
+        display: flex;
+        align-items: center;
+      }
+
+      ::v-deep(.van-field__button) {
+        display: flex;
+        align-items: center;
+        height: 50px;
+      }
+
+      ::v-deep(.van-field__label) {
+        width: unset;
+        font-size: 16px;
+      }
+
+      .search-input-barcode {
+        ::v-deep(.van-field__control) {
+          border-bottom: 2px solid #0077ff;
+          z-index: 2;
+        }
+      }
+    }
+    .code-button{
+      padding: 10px 15px;
+    }
+    .code-tips {
+      color: #ed6a0c;
+      flex: 1;
+    }
+
+    .code-count {
+      font-size: 16px;
+      font-weight: bold;
+
+      span {
+        color: #0077ff;
+      }
+    }
+
+    .nav-right {
+      padding: 14px 0 12px 5px;
+    }
+
+    .move-stock-list {
+      width: 100%;
+      overflow-y: auto;
+      max-height: 280px;
+      min-height: 100px;
+
+      .move-button {
+        background: #1989fa;
+        color: #fff;
+        width: 100%;
+        height: 30px;
+        font-size: 14px;
+        line-height: 30px;
+        font-weight: bold;
+      }
+
+      .task-table,
+      .task-table-bin,
+      .task-table-box {
+        width: 100%;
+        table-layout: fixed;
+        border-collapse: collapse;
+        font-size: 13px;
+      }
+
+      .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: 13px;
+      }
+
+      .task-table-bin thead {
+        background-color: #3f8dff;
+      }
+
+      .task-table-bin tbody {
+        background: #cde7ff;
+      }
+
+      .task-table tbody tr.pricking-active {
+        background-color: #d6f9e7;
+      }
+
+      .task-table tbody tr.stock-active {
+        background-color: #fffadd;
+      }
+
+      .task-table tbody tr.virtual-active {
+        background-color: #cacaca;
+      }
+
+      .task-table tbody tr.container-highlight {
+        background-color: #ffa500;
+      }
+
+      .task-table tbody tr.barcode-highlight {
+        background-color: #90ee90 !important;
+      }
+    }
+
+    .move-stock-content {
+      margin-bottom: 10px;
+
+      .compact-table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 0;
+        font-size: 14px;
+        line-height: 1.2;
+        border: 1px solid #ccc;
+
+        td {
+          padding: 8px 12px;
+          border: 1px solid #ccc;
+          background: transparent;
+          text-align: center;
+        }
+
+        td:first-child {
+          color: #333;
+          font-size: 20px;
+        }
+
+        td:last-child {
+          color: #e63535;
+          font-size: 30px;
+
+        }
+      }
+    }
+  }
+}
+
+.count-input {
+  ::v-deep(.van-field__value) {
+    border-bottom: 2px solid #0077ff;
+    font-size: 16px;
+  }
+}
+
+.completion {
+  text-align: right;
+  font-size: 14px;
+  line-height: 35px;
+  color: #0077ff;
+  padding: 0 10px;
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+.tips {
+  font-size: 13px;
+  text-align: left;
+  padding: 5px 15px;
+}
+</style>