zh před 2 měsíci
rodič
revize
89d37fa39f

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

@@ -0,0 +1,249 @@
+<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'
+
+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 })
+    await boxInbound(props.warehouse, checkedBoxes.value)
+    closeToast()
+    scanSuccess()
+    showToast('呼唤机器人成功')
+    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>