|
|
@@ -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>
|