zh 2 månader sedan
förälder
incheckning
53b7baf48f

+ 74 - 22
src/api/location/merge.ts

@@ -3,25 +3,26 @@ import request from '@/utils/request'
 
 // 库位合并详情
 export interface LocationMergeDetails {
-  id: number
-  code: string
-  warehouse: string
-  sourceLocation: string
-  targetLocation: string
-  sourceBox: string
-  targetBox: string
-  status: string
-  quantity: number
-  moveQty: number
-  sku: string
-  barcode: string
-  productName: string
-  lotNumber: string
-  owner: string
-  qualityStatus: string
-  warehouseType: string
-  productionDate: string
-  expiryDate: string
+  id?: number
+  code?: string
+  warehouse?: string
+  sourceLocation?: string
+  targetLocation?: string
+  sourceBox?: string
+  targetBox?: string
+  status?: string
+  quantity?: number
+  moveQty?: number
+  sku?: string
+  barcode?: string
+  productName?: string
+  lotNumber?: string
+  owner?: string
+  qualityStatus?: string
+  warehouseType?: string
+  productionDate?: string
+  expiryDate?: string
+  involveBox?: string[]
 }
 
 // 料箱相关的并库详情VO
@@ -58,15 +59,42 @@ export function getBoxSplitCode(warehouse: string, locations: string[]) {
   })
 }
 
+/**
+ * 料箱回库结果
+ */
+export interface BoxInboundResult {
+  t1: string // 料箱编码
+  t2: string // 执行结果消息
+}
+
 /**
  * 执行料箱回库(呼唤机器人)
  * @param warehouse 仓库
+ * @param boxCodes 可选的料箱编码列表,如果不传则回库所有料箱
  */
-export function boxInbound(warehouse: string) {
-  return request<[string, string][]>({
+export function boxInbound(warehouse: string, boxCodes?: string[]): Promise<BoxInboundResult[]> {
+  const params: any = { warehouse }
+  if (boxCodes && boxCodes.length > 0) {
+    params.boxCodes = boxCodes
+  }
+
+  return request<BoxInboundResult[]>({
     url: '/api/wms/location/merge/boxInbound',
     method: 'post',
-    params: { warehouse }
+    params,
+    paramsSerializer: (params: any) => {
+      const searchParams = new URLSearchParams()
+      Object.keys(params).forEach(key => {
+        const value = params[key]
+        if (Array.isArray(value)) {
+          // Spring @RequestParam List 期望格式: key=v1&key=v2
+          value.forEach(item => searchParams.append(key, item))
+        } else {
+          searchParams.append(key, value)
+        }
+      })
+      return searchParams.toString()
+    }
   })
 }
 
@@ -109,3 +137,27 @@ export function getBoxStatus(warehouse: string, boxCodeList: string[]) {
     }
   })
 }
+
+/**
+ * 获取正在作业中的并库任务详情
+ * @param warehouse 仓库
+ */
+export function getWorkingMergeTasks(warehouse: string) {
+  return request<LocationMergeDetails[]>({
+    url: '/api/wms/location/merge/workingTasks',
+    method: 'get',
+    params: { warehouse }
+  })
+}
+
+/**
+ * 海康-站点解绑
+ * @param data
+ */
+export function boxAndStationUnbindTask(params:any) {
+  return request({
+    url: '/api/wms/location/merge/boxAndStationUnbindTask',
+    method: 'post',
+    params
+  })
+}

+ 278 - 0
src/views/robot/merge/components/BoxSelectionDialog.vue

@@ -0,0 +1,278 @@
+<template>
+  <van-popup
+    v-model:show="show"
+    position="bottom"
+    round
+    :style="{ maxHeight: '70%' }"
+    @close="onClose"
+  >
+    <div class="dialog-container">
+      <div class="dialog-header">
+        <span class="dialog-title">请选择需要回库的料箱</span>
+        <van-icon name="cross" @click="onClose" />
+      </div>
+
+      <div class="dialog-content">
+        <!-- 表头 -->
+        <div class="table-header">
+          <div class="header-left">料箱</div>
+          <div class="header-right">
+            <span class="select-all-link" @click="toggleAll">全选</span>
+          </div>
+        </div>
+
+        <van-checkbox-group v-model="checkedBoxes">
+          <van-cell-group inset>
+            <!-- 料箱列表 -->
+            <van-cell
+              v-for="(boxCode, index) in boxList"
+              clickable
+              :key="boxCode"
+              @click="toggleBox(index)"
+            >
+              <template #title>
+                <div class="cell-content">
+                  <div class="cell-left">{{ boxCode }}</div>
+                  <div class="cell-right">
+                    <van-checkbox
+                      :name="boxCode"
+                      :ref="el => checkboxRefs[index] = el"
+                      @click.stop
+                    />
+                  </div>
+                </div>
+              </template>
+            </van-cell>
+          </van-cell-group>
+        </van-checkbox-group>
+      </div>
+
+      <div class="dialog-footer">
+        <van-button
+          size="large"
+          block
+          type="primary"
+          :disabled="checkedBoxes.length === 0"
+          @click="confirmSelection"
+        >
+          确认回库 ({{ checkedBoxes.length }}个料箱)
+        </van-button>
+      </div>
+    </div>
+  </van-popup>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { showToast, showLoadingToast, closeToast } from 'vant'
+import { boxInbound } from '@/api/location/merge'
+import { scanError, scanSuccess } from '@/utils/android'
+import { log } from 'console'
+import { BoxInboundResult } from '@/api/location/merge.ts'
+
+interface Props {
+  boxList: string[]
+  warehouse: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  boxList: () => [],
+  warehouse: ''
+})
+
+const emit = defineEmits<{
+  success: []
+  close: []
+}>()
+
+const show = ref(false)
+const checkedBoxes = ref<string[]>([])
+const checkboxRefs = ref<any[]>([])
+
+// 计算是否全选
+const isAllSelected = computed(() => {
+  return checkedBoxes.value.length === props.boxList.length && props.boxList.length > 0
+})
+
+// 显示弹窗
+const showDialog = () => {
+  checkedBoxes.value = []
+  show.value = true
+}
+
+// 关闭弹窗
+const onClose = () => {
+  show.value = false
+  emit('close')
+}
+
+// 切换单个料箱
+const toggleBox = (index: number) => {
+  const boxCode = props.boxList[index]
+  const checkbox = checkboxRefs.value[index]
+  if (checkbox) {
+    checkbox.toggle()
+  }
+}
+
+// 切换全选
+const toggleAll = () => {
+  if (isAllSelected.value) {
+    checkedBoxes.value = []
+  } else {
+    checkedBoxes.value = [...props.boxList]
+  }
+}
+
+// 确认选择
+const confirmSelection = async () => {
+  if (checkedBoxes.value.length === 0) return
+
+  try {
+    showLoadingToast({ message: '正在呼唤机器人...', forbidClick: true })
+    const { data } = await boxInbound(props.warehouse, checkedBoxes.value)
+    closeToast()
+    // 显示详细结果
+    let message = '呼唤机器人结果:\n'
+    data.forEach((item: BoxInboundResult) => {
+      message += `${item.t1}: ${item.t2}\n`
+    })
+
+    // 判断整体结果
+    const hasSuccess = data.some((item: BoxInboundResult) =>
+      item.t2.includes('回库成功')
+    )
+    const hasFailure = data.some((item: BoxInboundResult) =>
+      !item.t2.includes('回库成功')
+    )
+
+    if (hasSuccess && !hasFailure) {
+      // 全部成功
+      scanSuccess()
+      showToast(message.trim())
+    } else if (!hasSuccess && hasFailure) {
+      // 全部失败
+      scanError()
+      showToast(message.trim())
+    } else {
+      // 部分成功
+      scanSuccess()
+      showToast(message.trim())
+    }
+
+    show.value = false
+    emit('success')
+
+  } catch (error: any) {
+    closeToast()
+    scanError()
+    showToast(error.message || '呼唤机器人失败')
+  }
+}
+
+// 暴露方法给父组件使用
+defineExpose({
+  showDialog
+})
+</script>
+
+<style scoped lang="scss">
+.dialog-container {
+  padding: 16px;
+  max-height: 70vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #eee;
+
+  .dialog-title {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333;
+  }
+
+  .van-icon {
+    cursor: pointer;
+    color: #999;
+  }
+}
+
+.dialog-content {
+  flex: 1;
+  padding: 12px 0;
+  overflow-y: auto;
+
+  .hint-text {
+    font-size: 14px;
+    color: #666;
+    margin-bottom: 12px;
+    text-align: center;
+  }
+
+  .table-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 12px 16px;
+    background-color: #f8f8f8;
+    border-radius: 8px;
+    margin-bottom: 8px;
+    font-weight: 500;
+    color: #333;
+
+    .header-left {
+      flex: 1;
+      text-align: left;
+    }
+
+    .header-right {
+      text-align: right;
+
+      .select-all-link {
+        color: #1989fa;
+        cursor: pointer;
+        text-decoration: none;
+
+        &:hover {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+
+  .van-cell-group {
+    margin: 0;
+  }
+
+  .van-cell {
+    .cell-content {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      width: 100%;
+
+      .cell-left {
+        flex: 1;
+        font-size: 14px;
+        color: #333;
+        text-align: left;
+      }
+
+      .cell-right {
+        margin-left: 12px;
+      }
+    }
+  }
+}
+
+.dialog-footer {
+  padding-top: 12px;
+  border-top: 1px solid #eee;
+}
+</style>

+ 188 - 0
src/views/robot/merge/components/MergeTaskDetailsDialog.vue

@@ -0,0 +1,188 @@
+<template>
+  <van-popup
+    v-model:show="show"
+    position="bottom"
+    round
+    :style="{ maxHeight: '70%' }"
+    @close="onClose"
+  >
+    <div class="dialog-container">
+      <div class="dialog-header">
+        <span class="dialog-title">并库任务详情</span>
+        <van-icon name="cross" @click="onClose" />
+      </div>
+
+      <div class="dialog-content">
+        <div v-if="loading" class="loading-container">
+          <van-loading size="24px">加载中...</van-loading>
+        </div>
+        <div v-else-if="taskList.length === 0" class="empty-container">
+          <van-empty description="暂无并库任务" />
+        </div>
+        <div v-else class="task-list">
+          <div
+            v-for="(task, index) in taskList"
+            :key="task.id"
+            class="task-item"
+          >
+            <div class="task-header">
+              <span class="task-index">#{{ index + 1 }}</span>
+              <span class="task-sku">SKU: {{ task.sku }}</span>
+            </div>
+            <div class="task-details">
+              <div class="detail-row">
+                <span class="detail-label">推荐清空库位:</span>
+                <span class="detail-value">{{ task.sourceLocation }}</span>
+              </div>
+              <div class="detail-row">
+                <span class="detail-label">目标库位:</span>
+                <span class="detail-value">{{ task.targetLocation }}</span>
+              </div>
+              <div class="detail-row">
+                <span class="detail-label">推荐数量:</span>
+                <span class="detail-value">{{ task.moveQty }}</span>
+              </div>
+              <div class="detail-row">
+                <span class="detail-label">涉及料箱:</span>
+                <span class="detail-value">{{ task.involveBox ? task.involveBox.join(', ') : '-' }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </van-popup>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { LocationMergeDetails } from '@/api/location/merge'
+import { getWorkingMergeTasks } from '@/api/location/merge'
+import { showToast } from 'vant'
+
+const taskList = ref<LocationMergeDetails[]>([])
+const loading = ref(false)
+const show = ref(false)
+
+// 显示弹窗并加载数据
+const showDialog = async (warehouse: string) => {
+  show.value = true
+  loading.value = true
+
+  try {
+    const res = await getWorkingMergeTasks(warehouse)
+    taskList.value = res.data || []
+  } catch (error: any) {
+    showToast(error.message || '获取并库任务详情失败')
+    show.value = false
+  } finally {
+    loading.value = false
+  }
+}
+
+// 关闭弹窗
+const onClose = () => {
+  show.value = false
+}
+
+// 暴露方法给父组件使用
+defineExpose({
+  showDialog
+})
+</script>
+
+<style scoped lang="scss">
+.dialog-container {
+  padding: 16px;
+}
+
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #eee;
+
+  .dialog-title {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333;
+  }
+
+  .van-icon {
+    cursor: pointer;
+    color: #999;
+  }
+}
+
+.dialog-content {
+  padding: 12px 0;
+  max-height: 400px;
+  overflow-y: auto;
+}
+
+.loading-container,
+.empty-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 40px 0;
+}
+
+.task-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.task-item {
+  background: #f9f9f9;
+  border-radius: 8px;
+  padding: 12px;
+  border: 1px solid #e5e5e5;
+}
+
+.task-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+
+  .task-index {
+    font-size: 12px;
+    color: #1989fa;
+    font-weight: 500;
+  }
+
+  .task-sku {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+  }
+}
+
+.task-details {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.detail-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  .detail-label {
+    font-size: 12px;
+    color: #666;
+    min-width: 80px;
+  }
+
+  .detail-value {
+    font-size: 12px;
+    color: #333;
+    text-align: right;
+    flex: 1;
+  }
+}
+</style>

+ 219 - 86
src/views/robot/merge/index.vue

@@ -1,11 +1,14 @@
 <template>
   <div class="merge-container">
     <van-nav-bar
-      title="海康并库" left-arrow fixed placeholder @click-left="goBack">
+      title="海康并库" left-arrow fixed placeholder @click-left="goBack" @click-right="onDetailsClick">
       <template #left>
         <van-icon name="arrow-left" size="25" />
         <div style="color: #fff">返回</div>
       </template>
+      <template #right>
+        <div style="color: #fff">详情</div>
+      </template>
     </van-nav-bar>
     <!-- 扫描输入框区域 -->
     <div class="scan-section">
@@ -106,58 +109,109 @@
       </div>
     </div>
 
-    <!-- 料箱选择区域 -->
-    <div class="grid-section">
-      <div class="grid-container">
-        <div
-          v-for="station in stationList"
-          :key="station.id"
-          class="box-wrapper"
-        >
-          <!-- 站台序号在上方 -->
-          <div class="box-number">{{ station.displayNumber }}</div>
-          <div
-            class="box-item"
-            :class="{
-              'box-filled': station.status === 'filled' && !station.splitCount,
-              'box-empty-box': station.status === 'emptyBox',
-              'box-waiting': station.status === 'waiting',
-              'box-error': station.status === 'error',
-              'box-selected': selectedBox === station.stationCode,
-              'box-split': station.splitCount && station.status !== 'error' && station.status !== 'waiting'
-            }"
-                        @click="handleStationClick(station)"
-          >
-                      <!-- 分割的料箱(异常/等待调箱状态不渲染分割) -->
-            <template v-if="station.splitCount && station.subLocations && station.status !== 'error' && station.status !== 'waiting'">
-              <div class="sub-grid" :style="getSubGridStyle(station.splitCount)">
-                <div
-                  v-for="sub in station.subLocations"
-                  :key="sub.id"
-                  class="sub-location"
-                  :class="{
-                    'sub-filled': sub.status === 'filled',
-                    'sub-selected': selectedBox === sub.locationCode,
-                    'sub-disabled': !isLocationClickable(sub.locationCode)
-                  }"
-                  @click.stop="isLocationClickable(sub.locationCode) && selectSubLocation(station, sub)"
-                ></div>
+    <!-- 站点类型选择和料箱选择区域 -->
+    <van-tabs v-model:active="selectedStationType" @change="handleStationTypeChange" class="station-tabs">
+      <van-tab title="上架站点" name="shelf">
+        <div class="grid-section">
+          <div class="grid-container">
+            <div
+              v-for="station in stationList"
+              :key="station.id"
+              class="box-wrapper"
+            >
+              <!-- 站台序号在上方 -->
+              <div class="box-number">{{ station.displayNumber }}</div>
+              <div
+                class="box-item"
+                :class="{
+                  'box-filled': station.status === 'filled' && !station.splitCount,
+                  'box-empty-box': station.status === 'emptyBox',
+                  'box-waiting': station.status === 'waiting',
+                  'box-error': station.status === 'error',
+                  'box-selected': selectedBox === station.stationCode,
+                  'box-split': station.splitCount && ['offline', 'filled', 'emptyBox'].includes(station.status)
+                }"
+                @click="handleStationClick(station)"
+              >
+                <!-- 分割的料箱(异常/等待调箱状态不渲染分割) -->
+                <template v-if="station.splitCount && station.subLocations && ['offline', 'filled', 'emptyBox'].includes(station.status)">
+                  <div class="sub-grid" :style="getSubGridStyle(station.splitCount)">
+                    <div
+                      v-for="sub in station.subLocations"
+                      :key="sub.id"
+                      class="sub-location"
+                      :class="{
+                        'sub-filled': sub.status === 'filled',
+                        'sub-selected': selectedBox === sub.locationCode,
+                        'sub-disabled': !isLocationClickable(sub.locationCode)
+                      }"
+                      @click.stop="isLocationClickable(sub.locationCode) && selectSubLocation(station, sub)"
+                    ></div>
+                  </div>
+                </template>
+                <!-- 普通站台或异常状态 -->
+                <template v-else>
+                  <span v-if="station.label" class="box-label">{{ station.label }}</span>
+                </template>
               </div>
-            </template>
-            <!-- 普通站台或异常状态 -->
-            <template v-else>
-              <span v-if="station.label" class="box-label">{{ station.label }}</span>
-            </template>
+            </div>
           </div>
         </div>
-      </div>
-    </div>
+      </van-tab>
+      <van-tab title="退货缓存站点" name="return">
+        <div class="grid-section">
+          <div class="grid-container">
+            <div
+              v-for="station in stationList"
+              :key="station.id"
+              class="box-wrapper"
+            >
+              <!-- 站台序号在上方 -->
+              <div class="box-number">{{ station.displayNumber }}</div>
+              <div
+                class="box-item"
+                :class="{
+                  'box-filled': station.status === 'filled' && !station.splitCount,
+                  'box-empty-box': station.status === 'emptyBox',
+                  'box-waiting': station.status === 'waiting',
+                  'box-error': station.status === 'error',
+                  'box-selected': selectedBox === station.stationCode,
+                  'box-split': station.splitCount && ['offline', 'filled', 'emptyBox'].includes(station.status)
+                }"
+                @click="handleStationClick(station)"
+              >
+                <!-- 分割的料箱(异常/等待调箱状态不渲染分割) -->
+                <template v-if="station.splitCount && station.subLocations && ['offline', 'filled', 'emptyBox'].includes(station.status)">
+                  <div class="sub-grid" :style="getSubGridStyle(station.splitCount)">
+                    <div
+                      v-for="sub in station.subLocations"
+                      :key="sub.id"
+                      class="sub-location"
+                      :class="{
+                        'sub-filled': sub.status === 'filled',
+                        'sub-selected': selectedBox === sub.locationCode,
+                        'sub-disabled': !isLocationClickable(sub.locationCode)
+                      }"
+                      @click.stop="isLocationClickable(sub.locationCode) && selectSubLocation(station, sub)"
+                    ></div>
+                  </div>
+                </template>
+                <!-- 普通站台或异常状态 -->
+                <template v-else>
+                  <span v-if="station.label" class="box-label">{{ station.label }}</span>
+                </template>
+              </div>
+            </div>
+          </div>
+        </div>
+      </van-tab>
+    </van-tabs>
 
     <!-- 底部按钮 -->
     <div class="footer-buttons">
       <van-button class="btn-robot" size="small" @click="callRobot">呼唤机器人</van-button>
       <div class="btn-right">
-        <van-button type="primary" class="btn-reset" size="small" @click="resetInput">重新输入</van-button>
+        <van-button type="primary" class="btn-reset" size="small" @click="resetAllData">重新输入</van-button>
         <van-button class="btn-submit" size="small" @click="submitMove">提交移库</van-button>
       </div>
     </div>
@@ -170,11 +224,14 @@
       :style="{ maxHeight: '70%' }"
     >
       <div class="location-popup">
-        <div class="popup-header">
-          <span class="popup-title">库位信息</span>
-          <van-icon name="cross" @click="showLocationPopup = false" />
-        </div>
         <div class="popup-content">
+          <!-- 推荐类型信息 -->
+          <div class="recommend-type-section">
+            <div class="recommend-type">
+              {{ currentLocation.recommendType === 'clear' ? '此库位推荐清空' : '此库位推荐保留' }}
+            </div>
+            <van-icon name="cross" @click="showLocationPopup = false" />
+          </div>
           <div class="info-row">
             <span class="info-label">库位编号</span>
             <span class="info-value">{{ currentLocation.id }}</span>
@@ -186,7 +243,7 @@
           <!-- 推荐库位列表 -->
           <div class="recommend-section">
             <div class="recommend-title">
-              {{ currentLocation.recommendType === 'clear' ? '推荐清空' : '推荐保留' }}
+              {{ currentLocation.recommendType === 'clear' ? '推荐目标库位列表' : '推荐来源库位列表' }}
             </div>
             <div v-if="currentLocation.relatedLocations.length > 0" class="recommend-list">
               <div
@@ -206,6 +263,12 @@
         </div>
       </div>
     </van-popup>
+
+    <!-- 并库任务详情弹框 -->
+    <MergeTaskDetailsDialog ref="taskDetailsDialogRef"/>
+
+    <!-- 料箱选择弹框 -->
+    <BoxSelectionDialog ref="boxSelectionDialogRef" :box-list="currentBoxList" :warehouse="warehouse" @success="onBoxSelectionSuccess" />
   </div>
 </template>
 
@@ -214,11 +277,12 @@ import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
 import { onMounted, onUnmounted, ref, reactive, nextTick } from 'vue'
 import { showToast, showLoadingToast, closeToast } from 'vant'
 import { useStore } from '@/store/modules/user'
-import { getWorkingDetailsByBox, getBoxSplitCode, boxInbound, reissueTask, getBoxStatus, type BoxRelatedMergeDetailsVO, type LocationMergeDetails } from '@/api/location/merge'
+import { getWorkingDetailsByBox, getBoxSplitCode, boxAndStationUnbindTask, reissueTask, getBoxStatus, type BoxRelatedMergeDetailsVO, type LocationMergeDetails } from '@/api/location/merge'
 import { getInventory, inventoryMovement } from '@/api/inventory'
-import { boxAndStationUnbindTask } from '@/api/haikang'
 import { showConfirmDialog } from 'vant'
 import { getHeader, androidFocus, goBack, scanError, scanSuccess } from '@/utils/android'
+import MergeTaskDetailsDialog from './components/MergeTaskDetailsDialog.vue'
+import BoxSelectionDialog from './components/BoxSelectionDialog.vue'
 
 try {
   getHeader()
@@ -244,6 +308,8 @@ const boxCode = ref('')
 const sourceLocation = ref('')
 // 商品条码
 const scanBarcode = ref('')
+// 选中的站点类型:'return'(退货缓存站点)或 'shelf'(上架站点)
+const selectedStationType = ref('return')
 
 // 轮询定时器
 let pollingTimer: ReturnType<typeof setInterval> | null = null
@@ -382,6 +448,16 @@ const onTargetLocationEnter = () => {
   showToast(`已输入目标库位: ${productInfo.targetLocationNew}`)
 }
 
+// 处理站点类型切换
+const handleStationTypeChange = () => {
+  // 重新初始化站点(显示不同类型的站点)
+  initStations()
+  // 重新加载站点数据
+  if (boxCode.value) {
+    refreshBoxData()
+  }
+}
+
 // 当前选中的库存数据(用于提交移库)
 const currentInventoryData = ref<any>(null)
 
@@ -454,6 +530,7 @@ const resetAllData = () => {
   mergeDataList.value = []
   clickableLocationsMap.value = new Map()
   resetProductInfo()
+  productInfo.targetLocationNew = '' // 清空目标库位
   initStations()
   scanType.value = 1
   focusBoxCodeInput()
@@ -665,12 +742,19 @@ interface StationItem {
 // 站台列表
 const stationList = ref<StationItem[]>([])
 
-// 初始化站台列表(12个站台,编号13-24
+// 初始化站台列表(根据站点类型显示不同范围的站台
 const initStations = () => {
-  stationList.value = Array.from({ length: 12 }, (_, i) => {
-    const num = String(i + 13).padStart(2, '0')
+  const stationRange = selectedStationType.value === 'return'
+    ? { start: 13, end: 24 }  // 退货缓存站点
+    : { start: 1, end: 12 }   // 上架站点
+
+  const { start, end } = stationRange
+  const length = end - start + 1
+
+  stationList.value = Array.from({ length }, (_, i) => {
+    const num = String(start + i).padStart(2, '0')
     return {
-      id: i + 1,
+      id: start + i,
       stationCode: `RLOCHK${num}A01011`,
       displayNumber: num,
       status: 'offline' as const
@@ -733,7 +817,7 @@ const updateStationList = (boxDetailsList: BoxRelatedMergeDetailsVO[], splitMap:
       status = 'offline'
     } else if (boxStatus === 40) {
       status = 'error'
-      label = '异'
+      label = '调库'
     }
 
     // 获取分割数量
@@ -820,7 +904,7 @@ const handleStationClick = (station: StationItem) => {
     return
   }
   // 空料箱状态弹出解绑确认框(仅当料箱无分割,即料箱即库位时才能触发)
-  if (station.status === 'emptyBox' && (!station.splitCount || station.splitCount === 1)) {
+  if (station.status === 'emptyBox') {
     showUnbindConfirm(station)
     return
   }
@@ -834,7 +918,7 @@ const handleStationClick = (station: StationItem) => {
 const showReissueConfirm = (station: StationItem) => {
   showConfirmDialog({
     title: '重新下发任务',
-    message: `站台${station.displayNumber}的料箱出现异常,是否重新下发任务?`
+    message: `料箱${station.boxCode}调用海康异常,是否重新下发任务?`
   })
     .then(() => {
       doReissueTask(station)
@@ -865,8 +949,7 @@ const doUnbindTask = async (station: StationItem) => {
     const data = {
       warehouse,
       boxCode: station.boxCode,
-      stationCode: station.stationCode,
-      releaseStation: false
+      stationCode: station.stationCode
     }
     await boxAndStationUnbindTask(data)
     closeToast()
@@ -930,7 +1013,7 @@ const refreshBoxStatus = async () => {
         status = 'offline'
       } else if (newBoxStatus === 40) {
         status = 'error'
-        label = '异'
+        label = '调库'
       }
 
       return { ...station, status, label }
@@ -1072,6 +1155,11 @@ const selectSubLocation = (station: StationItem, sub: SubLocation) => {
     return
   }
 
+  if (station.status === 'emptyBox') {
+    showUnbindConfirm(station)
+    return
+  }
+
   selectedBox.value = sub.locationCode
 
   // 获取库位推荐信息
@@ -1112,6 +1200,24 @@ const confirmSelectLocation = async () => {
   }
 }
 
+// 并库任务详情弹框
+const taskDetailsDialogRef = ref<any>(null)
+// 点击详情按钮
+const onDetailsClick = () => {
+  taskDetailsDialogRef.value?.showDialog(warehouse)
+}
+
+// 料箱选择弹框
+const boxSelectionDialogRef = ref<any>(null)
+// 当前可选择的料箱列表
+const currentBoxList = ref<string[]>([])
+// 料箱选择成功
+const onBoxSelectionSuccess = () => {
+  // 重置页面数据并聚焦到料箱输入框
+  refreshBoxData()
+}
+
+
 // 根据SKU查询库存信息
 const queryInventoryBySku = async (sku: string, location: string) => {
   try {
@@ -1186,25 +1292,17 @@ const getTaskRecommendQty = (location: string): number => {
 }
 
 // 呼唤机器人
-const callRobot = async () => {
-  try {
-    showLoadingToast({ message: '正在呼唤机器人...', forbidClick: true })
-    await boxInbound(warehouse)
-    closeToast()
-    scanSuccess()
-    showToast('呼唤机器人成功')
-    // 重置页面数据并聚焦到料箱输入框
-    resetAllData()
-  } catch (error: any) {
-    closeToast()
-    scanError()
-    showToast(error.message || '呼唤机器人失败')
+const callRobot = () => {
+  // 获取当前所有料箱列表
+  currentBoxList.value = getBoxCodeList()
+
+  if (currentBoxList.value.length === 0) {
+    showToast('暂无需要回库的料箱')
+    return
   }
-}
 
-// 重新输入(不重置料箱号)
-const resetInput = () => {
-  resetExceptBoxCode()
+  // 显示选择Dialog
+  boxSelectionDialogRef.value?.showDialog()
 }
 
 // 提交移库
@@ -1321,6 +1419,22 @@ const submitMove = () => {
   }
 }
 
+.station-tabs {
+  margin-bottom: 50px;
+  background: #fff;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
+  :deep(.van-tabs__nav) {
+    background: transparent;
+  }
+
+  :deep(.van-tab) {
+    font-size: 14px;
+    font-weight: 500;
+  }
+}
+
 .info-table {
   background: #fff;
   border: 1px solid #ddd;
@@ -1431,7 +1545,7 @@ const submitMove = () => {
 .grid-section {
   background: #fff;
   padding: 12px;
-  margin-bottom: 80px;
+  margin-bottom: 12px;
   border-radius: 4px;
 
   .grid-container {
@@ -1498,12 +1612,11 @@ const submitMove = () => {
     }
 
         &.box-error {
-      background: #ffcccc;
       border-color: #ff6666;
       cursor: pointer;
 
       .box-label {
-        color: #cc0000;
+        color: #ff6666;
       }
     }
 
@@ -1620,7 +1733,27 @@ const submitMove = () => {
   }
 
   .popup-content {
-    padding: 12px 0;
+    padding: 6px 0;
+
+    .recommend-type-section {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 10px;
+
+      .recommend-type {
+        font-size: 16px;
+        font-weight: 500;
+        color: #1989fa;
+        text-align: left;
+      }
+
+      .van-icon {
+        font-size: 18px;
+        color: #666;
+        cursor: pointer;
+      }
+    }
 
     .info-row {
       display: flex;
@@ -1650,15 +1783,15 @@ const submitMove = () => {
   }
 
   .recommend-section {
-    margin-top: 12px;
-    padding-top: 12px;
-    border-top: 1px solid #eee;
+    margin-top: 6px;
+    padding-top: 6px;
 
     .recommend-title {
       font-size: 14px;
       font-weight: 500;
       color: #333;
       margin-bottom: 8px;
+      text-align: left;
     }
 
     .recommend-list {