Procházet zdrojové kódy

Merge branch 'zhaohuanhuan_asrs-v4'

zhaohuanhuan před 1 měsícem
rodič
revize
817776a276

+ 37 - 3
src/api/haikang/index.ts

@@ -64,11 +64,11 @@ export function setBoxInboundInventory(data:any) {
  * 料箱回库
  * @param params
  */
-export function boxReturn(params:any) {
+export function boxReturn(data:any) {
   return request({
     url: '/api/wms/return/inventory/boxReturn',
     method: 'post',
-    params
+    data
   })
 }
 
@@ -110,10 +110,44 @@ export function boxAndStationUnbindTask(params:any) {
  * 结束上架任务
  * @param data
  */
-export function finishTask(params:any) {
+export function finishTask(data:any) {
   return request({
     url: '/api/wms/return/inventory/finishTask',
     method: 'post',
+    data
+  })
+}
+/**
+ * 立库-托盘入库
+ * @param data
+ */
+export function createAsrsPalletInbound(params:any) {
+  return request({
+    url: '/api/wms/asrs/picking/putAway-pallet',
+    method: 'post',
+    params
+  })
+}
+/**
+ * 立库-货架入库
+ * @param data
+ */
+export function createAsrsShelfInbound(params:any) {
+  return request({
+    url: '/api/wms/asrs/picking/putAway-shelf',
+    method: 'post',
+    params
+  })
+}
+
+/**
+ * 立库-站点解绑
+ * @param data
+ */
+export function asrsBoxAndStationUnbindTask(params:any) {
+  return request({
+    url: '/api/wms/asrs/picking/release',
+    method: 'post',
     params
   })
 }

+ 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
+  })
+}
+
+

+ 21 - 1
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',
+            },
           ],
         },
         {
@@ -113,7 +118,22 @@ export default function() {
               title: '库位合并',
               icon: 'newspaper-o',
               path: 'robot-merge',
-            }
+            },
+            {
+              title: '立库V4快上',
+              icon: 'newspaper-o',
+              path:   'hik-putaway-allocation?type=asrs',
+            },
+            {
+              title: '立库V4上架',
+              icon: 'newspaper-o',
+              path:   'hik-putaway?type=asrs',
+            },
+            {
+              title: '立库V4入库',
+              icon: 'newspaper-o',
+              path:   'hik-box-return?type=asrs',
+            },
           ],
         },
         {

+ 18 - 0
src/router/index.ts

@@ -176,6 +176,24 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'面单识别'},
     component: () => import('@/views/inbound/photoOCR/index.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;
+}
+
+
+

+ 78 - 25
src/views/haikang/boxReturn/boxReturn/index.vue

@@ -2,7 +2,7 @@
 <template>
   <div class="box-return">
     <van-nav-bar
-      title="海康-入库/解绑" left-arrow fixed placeholder @click-left="goBack" @click-right="onConfirm()">
+        :title="mode?'立库V4-入库/解绑':'海康-入库/解绑'" left-arrow fixed placeholder @click-left="goBack" @click-right="onConfirm()">
       <template #left>
         <van-icon name="arrow-left" size="25" />
         <div style="color: #fff">返回</div>
@@ -15,12 +15,12 @@
       <div class="content-tips">
         <div style="flex: 1"><van-notice-bar left-icon="volume-o">{{ tips }}</van-notice-bar></div>
       </div>
-      <van-tabs v-model:active="active"   type="card">
+      <van-tabs v-model:active="active" @change="onActive"   type="card">
         <van-tab title="料箱入库" name="1"></van-tab>
         <van-tab title="解绑站点" name="2"  ></van-tab>
       </van-tabs>
         <div class="content-code">
-          <div class="barcode-input">
+          <div class="barcode-input" >
             <van-search
               ref="boxRef"
               v-model.lazy="scanBox"
@@ -57,9 +57,15 @@ import { getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
 import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
 import { barcodeToUpperCase } from '@/utils/dataType'
 import { showConfirmDialog, showNotify } from 'vant'
-import { boxAndStationUnbindTask, createHikBoxInboundTask } from '@/api/haikang'
+import {
+  asrsBoxAndStationUnbindTask,
+  boxAndStationUnbindTask, createAsrsPalletInbound,
+  createAsrsShelfInbound,
+  createHikBoxInboundTask
+} from '@/api/haikang'
 import { useStore } from '@/store/modules/user'
 import { closeLoading, showLoading } from '@/utils/loading'
+import {useRoute} from "vue-router";
 const tips=ref('请扫描料箱')
 const scanType = ref(1)
 const scanBox=ref('')
@@ -76,19 +82,26 @@ onMounted(() => {
 const store = useStore()
 const warehouse = store.warehouse
 const active=ref('1')
+const route = useRoute()
+const mode=ref(route.query.type)
 // 扫描条码监听
 const _handlerScan = (code) => {
   if(!code) return
   if (scanType.value == 1) {
-    const regex = /^HK\d{6}$/;
-    if(regex.test(barcodeToUpperCase(code))){
-      scanBox.value = barcodeToUpperCase(code)
+    let searchKey = barcodeToUpperCase(code);
+    const gridMatch = searchKey.match(/^([A-Z]+\d+)(?:-[AB]\d*)?$/);
+    if (gridMatch) {
+      searchKey = gridMatch[1]
+    }
+    const regex = /^(HK\d{6}|(HJ|TP)\d{5})$/;
+    if(regex.test(searchKey)){
+      scanBox.value = searchKey
       scanType.value=2
       tips.value='请扫描工作站台'
     }else {
-      tips.value=`${code}:料箱条码不匹配`
+      tips.value=`${searchKey}:料箱条码不匹配`
       scanError()
-      showNotify({ type: 'danger', duration: 3000, message:`${code}:料箱条码不匹配` })
+      showNotify({ type: 'danger', duration: 3000, message:`${searchKey}:料箱条码不匹配` })
       scanBox.value=''
     }
   }else if(scanType.value == 2){
@@ -98,8 +111,15 @@ const _handlerScan = (code) => {
     }
   }
 }
+const onActive=(type)=>{
+  // if(type==2 && mode.value=='asrs'){
+  //   scanType.value=2
+  // }else{
+  //   scanType.value=1
+  // }
+}
 const onConfirm=()=>{
-  if(!scanBox.value && active.value!=2){
+  if(!scanBox.value ){
     tips.value='请先扫描料箱编号'
     scanType.value=1
     showNotify({ type: 'danger', duration: 3000, message:'请先扫描料箱编号' })
@@ -116,25 +136,39 @@ const onConfirm=()=>{
   const message=active.value==1?'您正在进行入库操作是否继续':'您正在进行解绑操作是否继续'
   showConfirmDialog({
     title: '温馨提示',
-    message,
-    keyboardEnabled:false
+    message
   }).then(() => {
     if(active.value==1){
       _createHikBoxInboundTask()
     }else {
+      if(mode.value=='asrs'){
+        _asrsBoxAndStationUnbindTask()
+        return
+      }
       _boxAndStationUnbindTask()
     }
   }).catch(() => {})
 }
+
 const _createHikBoxInboundTask=()=>{
-  const data={
-    warehouse,
-    boxCode:scanBox.value,
-    stationCode:scanStation.value,
-    deviceType:'HIK_INBOUND_STATION'
+  // 提取公共数据
+  let baseData = {warehouse, stationCode: scanStation.value}
+  let url, data;
+  if (scanBox.value.startsWith('HJ')) {
+    data = {
+      ...baseData,
+      shelfCode: scanBox.value
+    };
+    url = createAsrsShelfInbound;
+  } else if (scanBox.value.startsWith('TP')) {
+    data = {
+      ...baseData,
+      palletCode: scanBox.value
+    };
+    url = createAsrsPalletInbound;
   }
   showLoading()
-  createHikBoxInboundTask(data).then(res=>{
+  url(data).then(res=>{
     tips.value='请继续扫描料箱编号'
     scanType.value=1
     scanBox.value=''
@@ -149,20 +183,39 @@ const _createHikBoxInboundTask=()=>{
     closeLoading()
   })
 }
+
+//海康解绑
 const _boxAndStationUnbindTask=()=>{
-  const data={
-    warehouse,
-    boxCode:scanBox.value || undefined,
-    stationCode:scanStation.value,
-  }
+  const data={warehouse, boxCode:scanBox.value || undefined, stationCode:scanStation.value}
   showLoading()
   boxAndStationUnbindTask(data).then(res=>{
     if(res.data){
-      tips.value='请继续扫描料箱编号'
+      tips.value='解绑成功,请继续扫描料箱编号'
       scanType.value=1
       scanBox.value=''
       scanStation.value=''
-      showNotify({ type: 'success', duration: 3000, message:'料箱解绑成功,请继续扫描需要入库料箱' })
+      showNotify({ type: 'success', duration: 3000, message:'解绑成功,请继续扫描料箱编号' })
+      scanSuccess()
+    }
+  }).catch(err=>{
+    tips.value=err.message
+    scanStation.value=''
+    scanError()
+  }).finally(()=>{
+    closeLoading()
+  })
+}
+//立库解绑
+const _asrsBoxAndStationUnbindTask=()=>{
+  const data={warehouse, stationCode:scanStation.value,containerCode:scanBox.value}
+  showLoading()
+  asrsBoxAndStationUnbindTask(data).then(res=>{
+    if(res.data){
+      tips.value='解绑成功,请继续扫描站点'
+      scanType.value=2
+      scanBox.value=''
+      scanStation.value=''
+      showNotify({ type: 'success', duration: 3000, message:'解绑成功,请继续扫描站点' })
       scanSuccess()
     }
   }).catch(err=>{

+ 461 - 0
src/views/haikang/putaway/components/Shelf.vue

@@ -0,0 +1,461 @@
+<template>
+  <div class="shelf-container" :style="{ '--cols': cols, '--rows': rows }">
+
+    <!-- 当前显示的面 -->
+    <div class="shelf-rack">
+      <!-- 货架主体 -->
+      <div class="rack-body">
+
+        <!-- 货架支柱 -->
+        <div class="rack-pillars">
+          <div class="rack-main">
+            <!-- 货架层板 -->
+            <div class="rack-shelves">
+              <div
+                v-for="row in reversedRows"
+                :key="`shelf-${row}`"
+                class="rack-shelf"
+              >
+                <!-- 货架板 -->
+                <div class="shelf-board">
+                  <div
+                    v-for="col in cols"
+                    :key="`cell-${shelfName}-${currentFace}${row}${col}`"
+                    class="shelf-cell"
+                    :class="{ 'highlight': isHighlighted(`${shelfName}-${currentFace}${row}${col}`) }"
+                  >
+                    <!-- 完整编号显示 -->
+                    <div class="cell-number">
+                      {{ shelfName }}-{{ currentFace }}{{ row }}{{ col }}
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed } from 'vue';
+
+// 接收父组件传递的数据
+const props = defineProps({
+  shelfCode: {
+    type: String,
+    default: ''
+  },
+  highlightCells: {
+    type: Array,
+    default: () => []
+  },
+  rows: {
+    type: Number,
+    default: 4
+  },
+  cols: {
+    type: Number,
+    default: 2
+  }
+});
+
+// 计算属性:从大到小排列,让第一层在底部
+const reversedRows = computed(() => {
+  return Array.from({ length: props.rows }, (_, i) => props.rows - i);
+});
+
+// 当前显示的面
+const currentFace = computed(() => {
+  // 优先根据货架编码判断显示的面
+  if (props.shelfCode) {
+    const match = props.shelfCode.match(/^([A-Z]+\d+)-([AB])$/);
+    if (match) {
+      return match[2]; // 返回A或B
+    }
+  }
+
+  // 如果货架编码没有指定面,则根据highlightCells中的编号自动判断显示的面
+  if (props.highlightCells && props.highlightCells.length > 0) {
+    // 检查是否有A面或B面的编号
+    const hasAFace = props.highlightCells.some(cell => cell.includes('-A'));
+    const hasBFace = props.highlightCells.some(cell => cell.includes('-B'));
+
+    // 如果同时有AB面,默认显示A面;如果只有一面,显示那一面
+    if (hasAFace) return 'A';
+    if (hasBFace) return 'B';
+  }
+  return 'A'; // 默认显示A面
+});
+
+// 货架名称
+const shelfName = computed(() => {
+  // 从货架编码中提取货架名称,如 HJ001-A -> HJ001
+  if (props.shelfCode) {
+    const match = props.shelfCode.match(/^([A-Z]+\d+)-[AB]$/);
+    if (match) {
+      return match[1];
+    }
+  }
+  return 'HJ001'; // 默认值
+});
+
+// 判断格口是否需要高亮
+const isHighlighted = (cellId) => {
+  if (!cellId || !props.highlightCells || props.highlightCells.length === 0) return false;
+
+  return props.highlightCells.includes(cellId);
+};
+
+// 暴露高亮方法
+const highlightCell = (cellId) => {
+  // 注意:由于 props 是只读的,这里只是为了保持接口兼容性
+  // 实际的高亮控制应该通过父组件更新 highlightCells prop
+  console.log('Highlight cell:', cellId);
+};
+
+const clearHighlight = () => {
+  // 注意:由于 props 是只读的,这里只是为了保持接口兼容性
+  // 实际的高亮控制应该通过父组件更新 highlightCells prop
+  console.log('Clear highlight');
+};
+
+defineExpose({ highlightCell, clearHighlight });
+</script>
+
+<style scoped>
+.shelf-container {
+  width: 75%;
+  margin: 0;
+  padding: 0;
+  background:
+    linear-gradient(135deg, rgba(248, 249, 250, 0.95) 0%, rgba(233, 236, 239, 0.95) 100%),
+    radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.1) 0%, transparent 50%),
+    radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
+  border-radius: 16px;
+  box-shadow:
+    0 8px 32px rgba(0, 0, 0, 0.12),
+    0 2px 8px rgba(0, 0, 0, 0.06),
+    inset 0 1px 0 rgba(255, 255, 255, 0.3),
+    inset 0 -1px 0 rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+  position: relative;
+  border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.shelf-container::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 2px;
+  background: linear-gradient(90deg,
+    transparent 0%,
+    rgba(255, 255, 255, 0.9) 20%,
+    rgba(255, 255, 255, 0.6) 50%,
+    rgba(255, 255, 255, 0.9) 80%,
+    transparent 100%);
+  border-radius: 16px 16px 0 0;
+}
+
+.shelf-container::after {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 1px;
+  background: linear-gradient(90deg,
+    transparent 0%,
+    rgba(0, 0, 0, 0.1) 50%,
+    transparent 100%);
+}
+
+.shelf-rack {
+  background:
+    linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 250, 251, 0.9) 100%),
+    radial-gradient(circle at 30% 40%, rgba(59, 130, 246, 0.05) 0%, transparent 40%),
+    radial-gradient(circle at 70% 60%, rgba(16, 185, 129, 0.05) 0%, transparent 40%);
+  overflow: hidden;
+  position: relative;
+  backdrop-filter: blur(12px) saturate(1.2);
+  border-radius: 14px;
+  margin: 2px;
+}
+
+.rack-title {
+  margin: 0;
+  font-size: 14px;
+  font-weight: 500;
+  letter-spacing: 0.5px;
+}
+
+.rack-footer {
+  background: #f5f5f5;
+  padding: 8px 15px;
+  text-align: center;
+  border-radius: 3px;
+}
+
+.rack-footer .rack-title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: #409eff;
+  letter-spacing: 0.5px;
+}
+
+.rack-body {
+  position: relative;
+  display: block;
+  min-height: calc(28px * var(--rows) + 4px);
+}
+
+/* 货架支柱 */
+.rack-pillars {
+  display: flex;
+  align-items: stretch;
+  position: relative;
+}
+
+.rack-pillar {
+  width: 5px;
+  background:
+    linear-gradient(180deg,
+      #9ca3af 0%,
+      #6b7280 20%,
+      #4b5563 50%,
+      #374151 80%,
+      #1f2937 100%);
+  position: relative;
+  border-radius: 2.5px;
+  box-shadow:
+    0 3px 8px rgba(0, 0, 0, 0.2),
+    inset 0 1px 0 rgba(255, 255, 255, 0.15),
+    inset 0 -1px 0 rgba(0, 0, 0, 0.2),
+    inset 1px 0 0 rgba(255, 255, 255, 0.05);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.left-pillar {
+  margin-right: 10px;
+  transform: skewY(-1deg);
+}
+
+.right-pillar {
+  margin-left: 10px;
+  transform: skewY(1deg);
+}
+
+.rack-main {
+  flex: 1;
+  position: relative;
+}
+
+/* 货架层板 */
+.rack-shelves {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 0px;
+}
+
+.rack-shelf {
+  position: relative;
+  flex: 1;
+}
+
+.shelf-board {
+  background: #fafafa;
+  padding: 2px 1px;
+  margin: 0;
+  display: flex;
+  gap: 0px;
+  align-items: center;
+  position: relative;
+}
+
+/* 货架格子 */
+.shelf-cell {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background:
+    linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 249, 250, 0.95) 100%),
+    radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.8) 0%, transparent 50%),
+    radial-gradient(circle at 75% 75%, rgba(0, 0, 0, 0.02) 0%, transparent 50%);
+  border: 1px solid rgba(222, 226, 230, 0.8);
+  border-bottom: 2px solid rgba(226, 232, 240, 0.9);
+  border-radius: 6px;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  position: relative;
+  min-width: 50px;
+  min-height: 24px;
+  margin: 0.5px;
+  box-shadow:
+    0 1px 3px rgba(0, 0, 0, 0.06),
+    inset 0 1px 0 rgba(255, 255, 255, 0.4),
+    inset 0 -1px 0 rgba(0, 0, 0, 0.05);
+  cursor: pointer;
+}
+
+.shelf-cell:hover {
+  transform: translateY(-0.5px) scale(1.01);
+  box-shadow:
+    0 3px 8px rgba(0, 0, 0, 0.1),
+    0 1px 3px rgba(0, 0, 0, 0.06),
+    inset 0 1px 0 rgba(255, 255, 255, 0.5),
+    inset 0 -1px 0 rgba(0, 0, 0, 0.03);
+  border-color: #a0aec0;
+  background:
+    linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(241, 243, 244, 0.98) 100%),
+    radial-gradient(circle at 25% 25%, rgba(255, 255, 255, 0.9) 0%, transparent 50%),
+    radial-gradient(circle at 75% 75%, rgba(0, 0, 0, 0.01) 0%, transparent 50%);
+}
+
+/* 单元格编号 */
+.cell-number {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1a202c;
+  text-align: center;
+  line-height: 1;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
+  letter-spacing: 0.01em;
+  text-shadow: 0 0.5px 1px rgba(0, 0, 0, 0.03);
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.shelf-cell.highlight {
+  background:
+    linear-gradient(135deg, rgba(240, 253, 244, 0.95) 0%, rgba(220, 252, 231, 0.95) 100%),
+    radial-gradient(circle at 30% 30%, rgba(34, 197, 94, 0.1) 0%, transparent 60%),
+    radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.3) 0%, transparent 60%);
+  border-color: #22c55e;
+  border-width: 1.5px;
+  box-shadow:
+    0 4px 12px rgba(34, 197, 94, 0.25),
+    0 2px 6px rgba(34, 197, 94, 0.15),
+    0 0 0 1px rgba(34, 197, 94, 0.1),
+    inset 0 1px 0 rgba(255, 255, 255, 0.2);
+  transform: translateY(-0.5px) scale(1.02);
+  animation: highlight-glow 2s ease-in-out infinite alternate;
+}
+
+.shelf-cell.highlight .cell-number {
+  color: #166534;
+  font-weight: 800;
+  text-shadow:
+    0 1px 2px rgba(22, 101, 52, 0.2),
+    0 0 0 1px rgba(34, 197, 94, 0.1);
+  background: linear-gradient(135deg, #166534 0%, #15803d 100%);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+@keyframes highlight-glow {
+  0% {
+    box-shadow:
+      0 4px 12px rgba(34, 197, 94, 0.25),
+      0 2px 6px rgba(34, 197, 94, 0.15),
+      0 0 0 1px rgba(34, 197, 94, 0.1),
+      inset 0 1px 0 rgba(255, 255, 255, 0.2);
+  }
+  100% {
+    box-shadow:
+      0 6px 16px rgba(34, 197, 94, 0.35),
+      0 3px 8px rgba(34, 197, 94, 0.2),
+      0 0 0 1px rgba(34, 197, 94, 0.15),
+      inset 0 1px 0 rgba(255, 255, 255, 0.3);
+  }
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .shelf-container {
+    width: 260px !important;
+    height: 100px !important;
+    padding: 0;
+    margin: 0 auto;
+    border-radius: 12px;
+    box-shadow:
+      0 6px 24px rgba(0, 0, 0, 0.15),
+      0 2px 8px rgba(0, 0, 0, 0.08),
+      inset 0 1px 0 rgba(255, 255, 255, 0.2),
+      inset 0 -1px 0 rgba(0, 0, 0, 0.03);
+    border: 1px solid rgba(255, 255, 255, 0.15);
+  }
+
+  .shelf-rack {
+    background:
+      linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 250, 251, 0.9) 100%),
+      radial-gradient(circle at 30% 40%, rgba(59, 130, 246, 0.03) 0%, transparent 40%),
+      radial-gradient(circle at 70% 60%, rgba(16, 185, 129, 0.03) 0%, transparent 40%);
+    backdrop-filter: blur(8px) saturate(1.1);
+    border-radius: 10px;
+    margin: 1px;
+  }
+
+  .rack-body {
+    min-height: calc(18px * var(--rows) + 4px);
+    height: calc(100% - 8px);
+  }
+
+  .rack-pillars {
+    position: relative;
+    margin-bottom: 3px;
+  }
+
+  .rack-pillar {
+    width: 3px;
+  }
+
+  .left-pillar {
+    margin-right: 3px;
+  }
+
+  .right-pillar {
+    margin-left: 3px;
+  }
+
+  .rack-shelves {
+    gap: 2px;
+  }
+
+  .shelf-board {
+    padding: 1px 0px;
+    gap: 1px;
+  }
+
+  .shelf-cell {
+    min-height: 16px;
+    min-width: 32px;
+    padding: 1px;
+    border-width: 1px;
+    margin: 0.5px;
+    border-radius: 3px;
+  }
+
+  .cell-number {
+    font-size: 11px;
+    font-weight: 600;
+  }
+
+  .shelf-cell:hover {
+    transform: none;
+  }
+
+  .shelf-cell.highlight {
+    border-color: #22c55e;
+    border-width: 1.5px;
+    box-shadow: 0 1px 4px rgba(34, 197, 94, 0.15), 0 0 0 1px rgba(34, 197, 94, 0.08);
+  }
+}
+</style>

+ 14 - 7
src/views/haikang/putaway/dispatch/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="container">
     <van-nav-bar
-      title="海康上架-调度" left-arrow fixed placeholder @click-left="goBack">
+      :title="mode?'立库V4快上':'海康快上'" left-arrow fixed placeholder @click-left="goBack">
       <template #left>
         <van-icon name="arrow-left" size="25" />
         <div style="color: #fff">返回</div>
@@ -25,7 +25,7 @@
             v-model="wallNo"
             placeholder="请扫描分拨墙号"
             label="分拨墙号:"
-            @search="scanType=2"
+            @search="_handlerScan(wallNo)"
             left-icon=""
             :class="[scanType===3?'search-input-barcode':'','van-hairline--bottom']"
             @focus="scanType=3"
@@ -116,10 +116,12 @@ import { androidFocus, getHeader, goBack, playVoiceBin, scanError, scanSuccess }
 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 { finishTask, getRecommendedLocation, setBindAllocateWall } from '@/api/haikang'
 import { barcodeToUpperCase } from '@/utils/dataType'
 import { closeLoading, showLoading } from '@/utils/loading'
 import { getWaitPutawayListNew } from '@/api/putaway'
+import {useRoute} from "vue-router";
+
 try {
   getHeader()
   androidFocus()
@@ -133,6 +135,8 @@ onMounted(() => {
 })
 const store = useStore()
 const warehouse = store.warehouse
+const route = useRoute()
+const mode=ref(route.query.type)
 //收货容器号
 const containerNo = ref('')
 //分拨墙号
@@ -257,6 +261,10 @@ const _handlerScan = (code) => {
     } else if (scanType.value == 3) {
       wallNo.value = code
       scanType.value = 2
+      searchBarcode.value = ''
+      bin.value=''
+      locationActive.value = []
+      tips.value='请扫描商品条码'
     }
   }
 }
@@ -276,7 +284,7 @@ const matchingBarcodeItem = (data, barcode) => {
 // 获取库存数据
 const _getRecommendedLocation = async (lotNum, owner) => {
   try {
-    const params = { warehouse, lotNum, owner,zoneGroup:'WH01-01' }
+    const params = { warehouse, lotNum, owner,zoneGroup:mode.value?'WH01-3':'WH01-01' }
     const res = await getRecommendedLocation(params)
     locationList.value = res.data
     //  'EA'数据
@@ -304,7 +312,6 @@ const _getRecommendedLocation = async (lotNum, owner) => {
 }
 // 设置库位
 const locationActive = ref([])
-const wallBinCounts = ref(new Map())
 const setLocation = (item) => {
     locationActive.value = item
     const data = {
@@ -317,6 +324,7 @@ const setLocation = (item) => {
       sku: barcodeActive.value.sku,
       location: locationActive.value[0].location,
       lotNum: barcodeActive.value.lotNumber,
+      type:mode.value?'ASRS':'HIK'
     }
     showLoading()
     setBindAllocateWall(data).then(res => {
@@ -366,10 +374,9 @@ const onClickRight = () => {
   showConfirmDialog({
     title: '温馨提示',
     message:'您正在进行释放分拨墙是否继续?',
-    keyboardEnabled:false
   }).then(() => {
     showLoading()
-    const params={warehouse,container:containerNo.value}
+    const params={warehouse,container:containerNo.value,taskType:mode.value?'ASRS':'HIK'}
     finishTask(params).then(res=>{
       showNotify({ type: 'success', duration: 3000, message: `解绑成功` })
       scanSuccess()

+ 139 - 29
src/views/haikang/putaway/putaway/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="container">
-    <van-nav-bar title="海康上架" left-arrow fixed placeholder @click-left="goBack">
+    <van-nav-bar :title="mode?'立库V4上架':'海康上架'" left-arrow fixed placeholder @click-left="goBack">
       <template #left>
         <van-icon name="arrow-left" size="25" /><div style="color: #fff">返回</div>
       </template>
@@ -40,9 +40,23 @@
           <van-button type="primary" size="small" block :disabled="searchLocation!=recommendedLocation || searchLocation=='' "   @click="setPutaway(2)" >提交上架</van-button>
         </div>
         <div class="putaway-box" v-if="barcodeActive.bin">
-          <div><box ref="boxRef" :box="{name:workBinNo,category:barcodeActive.gridCount}"></box></div>
+          <div class="shelf-wrapper">
+            <!-- 根据 boxCode 判断使用哪个组件 -->
+            <box
+              v-if="!barcodeActive.boxCode || !barcodeActive.boxCode.startsWith('HJ')"
+              ref="boxRef"
+              :box="{name:workBinNo,category:barcodeActive.gridCount}"
+            ></box>
+            <shelf
+              v-else
+              ref="shelfRef"
+              :shelfCode="workBinNo"
+              :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">
@@ -72,6 +86,21 @@
         </van-cell>
       </van-cell-group>
     </van-action-sheet>
+    <!--  单据选择-->
+    <van-action-sheet v-model:show="lotBarcodeTrueFalseBy" cancel-text="取消" description="请选择商品批次"
+                      close-on-click-action>
+      <van-cell-group>
+        <van-cell v-for="item in matchedBarcodeItem"
+                  @click="setBarcodeLot(item)">
+          <template #title>
+            {{ item.barcode }}({{ item.qty }}件)
+          </template>
+          <template #label>
+            生产日期:{{ item.lotAtt01 || '--' }}-失效日期:{{ item.lotAtt02 || '--' }}
+          </template>
+        </van-cell>
+      </van-cell-group>
+    </van-action-sheet>
   </div>
 </template>
 <script setup lang="ts">
@@ -83,9 +112,11 @@ import { showConfirmDialog, showNotify } from 'vant'
 import { barcodeToUpperCase, toMap } from '@/utils/dataType'
 import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
 import Box from '@/views/haikang/putaway/components/Box.vue'
+import Shelf from '@/views/haikang/putaway/components/Shelf.vue'
 import GoBoxBack from '@/views/haikang/putaway/components/GoBoxBack.vue'
 import { boxReturn, getShelveItemInfo, outboundEmptyBoxAndBindTask, setBoxInboundInventory } from '@/api/haikang'
 import { closeLoading, showLoading } from '@/utils/loading'
+import {useRoute} from "vue-router";
 try {
   getHeader()
   androidFocus()
@@ -100,7 +131,8 @@ onMounted(() => {
 const store = useStore()
 const warehouse = store.warehouse
 const dataList=ref([])
-
+const route = useRoute()
+const mode=ref(route.query.type)
 //料箱编号
 const workBinNo = ref('')
 //扫描类型
@@ -120,7 +152,19 @@ const searchLocation=ref('')
 const recommendedLocation=ref('')
 const locationList = (data) => {
   return data.reduce((acc, item) => {
-    const key = item.boxCode
+    // 优先使用 locationId(如果存在且符合格口格式),否则使用 boxCode
+    let key = item.locationId || item.boxCode
+
+    // 提取库位key,支持多种格式
+    if (key.match(/^([A-Z]+\d+-[AB])\d+$/)) {
+      // HJ0001-A11 -> HJ0001-A (货架A/B面格式)
+      key = key.replace(/^([A-Z]+\d+-[AB])\d+$/, '$1')
+    } else if (key.match(/^([A-Z]+\d+)-\d+$/)) {
+      // TP000006-1 -> TP000006 (其他库位格式)
+      key = key.replace(/^([A-Z]+\d+)-\d+$/, '$1')
+    }
+    // 其他格式如 TP000007 保持不变
+
     acc[key] = acc[key] || []
     acc[key].push(item)
     return acc
@@ -128,12 +172,25 @@ const locationList = (data) => {
 }
 //料箱库位
 const locationMap = ref({})
-const wallBinList = ref([
-  ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20'],
-  ['21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40'],
-  ['41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60'],
-  ['61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80'],
-])
+// 生成分拨格口列表的工具函数
+const generateWallBinList = (rows, cols = 20) => {
+  const result = []
+  for (let i = 0; i < rows; i++) {
+    const row = []
+    for (let j = 0; j < cols; j++) {
+      row.push(String(i * cols + j + 1))
+    }
+    result.push(row)
+  }
+  return result
+}
+
+// 根据模式生成分拨格口列表
+const wallBinList = ref(
+  mode.value === 'asrs'
+    ? generateWallBinList(3)  // ASRS模式:3行
+    : generateWallBinList(4)  // 默认模式:4行
+)
 const getRows = (wall) => {
   let rows = []
   for (let i = 0; i < wall.length; i += 4) {
@@ -145,6 +202,9 @@ const getRows = (wall) => {
 const locationDetailList = ref([])
 // //当前扫描商品
 const barcodeActive = ref({})
+//批次数据
+const lotBarcodeList = ref([])
+const lotBarcodeTrueFalseBy = ref(false)
 //分拨墙是否展示
 const equipmentTrueFalseBy=ref(false)
 // 分拨墙map
@@ -153,19 +213,38 @@ const equipmentMap=ref({})
 const equipmentBarcodeList=ref([])
 //料箱内已扫描条码
 const locationScanBarcodeBinList = ref([])
+// 料箱内已扫描格口
+const binScanList=ref([])
 //料箱内商品所有格口
 const locationBarcodeBinList = ref([])
 const boxRef = ref(null)
+const shelfRef = ref(null)
 const _handlerScan = (code) => {
   if (scanType.value == 1) {
-    workBinNo.value =barcodeToUpperCase(code)
+    // 料箱编号调整时,清除相关状态信息
+    searchBarcode.value = ''
+    equipmentBarcodeList.value = []
+    barcodeActive.value = {}
+    locationBarcodeBinList.value = []
+    locationScanBarcodeBinList.value = []
+    searchLocation.value = ''
+    recommendedLocation.value = ''
+    count.value = ''
+    binScanList.value=[]
+    // 处理扫描的格口编号,支持完整格式(如 HJ0001-A22)和库位格式(如 HJ0001-A)
+    let searchKey = barcodeToUpperCase(code)
+    const gridMatch = searchKey.match(/^([A-Z]+\d+-[AB])\d+$/)
+    if (gridMatch) {
+      searchKey = gridMatch[1] // 转换为库位格式,如 HJ0001-A
+    }
+    workBinNo.value =searchKey
     showLoading()
-    getShelveItemInfo({warehouse, boxCode: code, }).then(res => {
+    getShelveItemInfo({warehouse, boxCode: searchKey,type:mode.value?'ASRS':'HIK' }).then(res => {
       if(res.data.length>0){
         reset()
         dataList.value=res.data
         locationMap.value = locationList(res.data)
-        locationDetailList.value = locationMap.value[barcodeToUpperCase(code)]
+        locationDetailList.value = locationMap.value[searchKey]
         equipmentMap.value=locationDetailList.value.reduce((acc, item) => {
           if (!acc[item.equipment]) {
             acc[item.equipment] = [];
@@ -207,6 +286,7 @@ const _handlerScan = (code) => {
       recommendedLocation.value=''
       tips.value = err.message
       barcodeActive.value={}
+      binScanList.value=[]
       showNotify({ type: 'danger', duration: 3000, message:  err.message })
       scanError()
       closeLoading()
@@ -218,22 +298,26 @@ const _handlerScan = (code) => {
     )
     if (matchedBarcodeItem.value.length>0) {
       searchBarcode.value = code
-      matchedBarcodeItem.value.forEach((item) => {
-          locationScanBarcodeBinList.value.push(item.bin)
-        })
-        locationScanBarcodeBinList.value = [...new Set(locationScanBarcodeBinList.value)]
+      // matchedBarcodeItem.value.forEach((item) => {
+      //   locationScanBarcodeBinList.value.push(item.bin)
+      // })
+      // locationScanBarcodeBinList.value = [...new Set(locationScanBarcodeBinList.value)]
+      if (matchedBarcodeItem.value.length == 1) {
         barcodeActive.value =matchedBarcodeItem.value[0]
         recommendedLocation.value=barcodeActive.value.locationId
-
+        binScanList.value.push(barcodeActive.value.bin)
+        locationScanBarcodeBinList.value = [...new Set(binScanList.value.concat(barcodeActive.value.bin))];
         setTimeout(() => {
           boxRef.value?.clearHigh()
           boxRef.value?.high(barcodeActive.value.gridNum || 1)
         }, 300)
         scanType.value = 4
-        // count.value=1
-        // countRef.value?.focus()
         tips.value = `请扫描上架库位`
-      scanSuccess()
+        scanSuccess()
+      } else if (matchedBarcodeItem.value.length > 1) {
+        scanError()
+        lotBarcodeTrueFalseBy.value = true
+      }
     } else {
       count.value=''
       searchBarcode.value = ''
@@ -258,6 +342,20 @@ const _handlerScan = (code) => {
     tips.value = `请输入上架数量`
   }
 }
+const setBarcodeLot = (item)=>{
+  lotBarcodeTrueFalseBy.value = false
+  barcodeActive.value =item
+  recommendedLocation.value=barcodeActive.value.locationId
+  binScanList.value.push(barcodeActive.value.bin)
+  locationScanBarcodeBinList.value = [...new Set(binScanList.value.concat(barcodeActive.value.bin))];
+  setTimeout(() => {
+    boxRef.value?.clearHigh()
+    boxRef.value?.high(barcodeActive.value.gridNum || 1)
+  }, 300)
+  scanType.value = 4
+  tips.value = `请扫描上架库位`
+  scanSuccess()
+}
 
 // 选择分拨墙
 const selectEquipment=(key)=>{
@@ -275,6 +373,7 @@ const reset=()=>{
   locationBarcodeBinList.value=[]
   locationScanBarcodeBinList.value=[]
   locationDetailList.value=[]
+  binScanList.value=[]
   equipmentMap.value={}
   dataList.value=[]
 
@@ -360,7 +459,7 @@ const setPutaway=async (type)=>{
       .then(() => {
         showLoading()
         const {container,asnCode}=locationDetailList.value[0]
-        boxReturn({warehouse,container,boxCode:workBinNo.value,externalCode:asnCode}).then(res=>{
+        boxReturn({warehouse,container,boxCode:workBinNo.value,externalCode:asnCode,taskType:mode.value?'ASRS':'HIK'}).then(res=>{
           closeLoading()
           scanSuccess()
           tips.value = `料箱回库成功,请继续扫描料箱编号`
@@ -372,6 +471,7 @@ const setPutaway=async (type)=>{
           searchLocation.value=''
           recommendedLocation.value=''
           scanType.value = 1
+          binScanList.value=[]
         }).catch(err=>{
           closeLoading()
           scanError()
@@ -388,7 +488,7 @@ const setGoBack=async (item)=>{
     if(item.active==1){
       const {warehouse,container,boxCode,asnCode}=barcodeActive.value
       showLoading()
-      const boxRes= await boxReturn({warehouse,container,boxCode,externalCode:asnCode}).catch(err=>{
+      const boxRes= await boxReturn({warehouse,container,boxCode,externalCode:asnCode,taskType:mode.value?'ASRS':'HIK'}).catch(err=>{
         closeLoading()
       })
       closeLoading()
@@ -401,6 +501,7 @@ const setGoBack=async (item)=>{
         recommendedLocation.value=''
         scanType.value = 1
         tips.value = `请扫描料箱编号`
+        binScanList.value=[]
         showNotify({ type: 'success', duration: 3000, message: `请扫描下一个料箱编号` })
         scanSuccess()
       }
@@ -416,6 +517,7 @@ const setGoBack=async (item)=>{
         searchLocation.value=''
         recommendedLocation.value=''
         scanType.value = 1
+        binScanList.value=[]
         tips.value = `请扫描料箱编号`
         scanError()
       }))
@@ -428,6 +530,7 @@ const setGoBack=async (item)=>{
         searchLocation.value=''
         recommendedLocation.value=''
         scanType.value = 1
+        binScanList.value=[]
         tips.value = `料箱调取成功,请继续扫描料箱编号`
         showNotify({ type: 'success', duration: 3000, message: `料箱调取成功,请继续扫描料箱编号` })
         scanSuccess()
@@ -443,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
@@ -467,18 +571,18 @@ const loadData = () => {
 }
 // 刷新待上架数据
 const refreshPutawayList = (item) => {
-  getShelveItemInfo({warehouse:item.warehouse, boxCode:item.boxCode,}).then(res => {
+  getShelveItemInfo({warehouse:item.warehouse, boxCode:workBinNo.value,type:mode.value?'ASRS':'HIK'}).then(res => {
     if(res.data.length>0) {
       dataList.value = res.data
       locationMap.value = locationList(res.data)
-      locationDetailList.value = locationMap.value[barcodeToUpperCase(item.boxCode)]
+      locationDetailList.value = locationMap.value[workBinNo]
       equipmentMap.value=locationDetailList.value.reduce((acc, item) => {
         if (!acc[item.equipment]) {
           acc[item.equipment] = [];
         }
         acc[item.equipment].push(item);
         return acc;
-      }, {})
+      }, {});
       equipmentBarcodeList.value = equipmentMap.value[item.equipment]
     }
   })
@@ -546,7 +650,13 @@ window.onRefresh = loadData
 
 
     .putaway-button
-      padding: 10px 20px
+      padding: 10px 20px 5px 20px
+
+    .shelf-wrapper
+      display: flex
+      justify-content: center
+      align-items: center
+      margin:  0
 
     .wall-list-box
       background: #646566

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

@@ -0,0 +1,557 @@
+<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 => {
+        scanSuccess()
+        router.push({name: 'ReturnList'})
+        showToast({duration: 3000, message: '当前任务已放弃正在跳转到任务页面,请稍后~'})
+      }).catch((err) => {
+        scanError()
+        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 = () => {
+  if(binList.value.length==0){
+    scanError()
+    tips.value = '当前未扫描商品,请先扫描商品条码'
+    showToast({duration: 3000, message: tips.value})
+    return
+  }
+  showConfirmDialog({
+    title: '温馨提示',
+    message:
+        '您正在进行完成操作,是否立即呼叫小车?',
+    confirmButtonText: '立即呼叫',
+    cancelButtonText: '不呼叫',
+  })
+    .then(() => {
+      _returnTaskFirstStepComplete(true)
+    })
+    .catch(() => {
+      _returnTaskFirstStepComplete(false)
+    });
+}
+const _returnTaskFirstStepComplete=(callHikQuickIn)=>{
+  showLoading()
+  returnTaskFirstStepComplete({taskNo:taskNo.value,callHikQuickIn}).then(res => {
+    scanSuccess()
+    if(callHikQuickIn){
+      router.replace({ name: 'ReturnTask', query: { code:taskNo.value,container:containerNo.value } });
+    }else {
+      router.push({name: 'ReturnList'})
+    }
+  }).catch(err=>{
+    scanError()
+  }).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>

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

@@ -0,0 +1,246 @@
+<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 => {
+    scanSuccess()
+    router.replace({ name: 'ReturnTask', query: { code:item.taskNo,container:item.containerNo } });
+  }).catch(err=>{
+    scanError()
+  }).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 = barcodeToUpperCase(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 === barcodeToUpperCase(code) || item.containerCode.startsWith(`${barcodeToUpperCase(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>