|
|
@@ -0,0 +1,474 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, computed } from 'vue'
|
|
|
+import { showToast } from 'vant'
|
|
|
+
|
|
|
+// 扫描料箱号
|
|
|
+const boxCode = ref('')
|
|
|
+
|
|
|
+// 商品信息
|
|
|
+const productInfo = reactive({
|
|
|
+ targetLocation: 'HK000001',
|
|
|
+ stockQty: '',
|
|
|
+ productName: '',
|
|
|
+ barcode: '',
|
|
|
+ qualityStatus: 'ZP',
|
|
|
+ warehouseType: 'CHZP',
|
|
|
+ batchNo: '',
|
|
|
+ productionDate: '',
|
|
|
+ expiryDate: '',
|
|
|
+ moveQty: '',
|
|
|
+ targetLocationNew: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 移库数量编辑
|
|
|
+const isEditingMoveQty = ref(false)
|
|
|
+const editMoveQty = () => {
|
|
|
+ isEditingMoveQty.value = true
|
|
|
+}
|
|
|
+const confirmMoveQty = () => {
|
|
|
+ isEditingMoveQty.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 料箱数据 - 支持分割成多个库位
|
|
|
+interface SubLocation {
|
|
|
+ id: string
|
|
|
+ status: 'empty' | 'filled' | 'selected'
|
|
|
+}
|
|
|
+
|
|
|
+interface BoxItem {
|
|
|
+ id: number
|
|
|
+ status: 'empty' | 'filled' | 'emptyBox' | 'waiting'
|
|
|
+ label?: string
|
|
|
+ splitCount?: number // 分割数量: 2, 4, 6, 8
|
|
|
+ subLocations?: SubLocation[] // 子库位
|
|
|
+}
|
|
|
+
|
|
|
+// 生成子库位
|
|
|
+const generateSubLocations = (boxId: number, count: number, filledIndexes: number[] = []): SubLocation[] => {
|
|
|
+ return Array.from({ length: count }, (_, i) => ({
|
|
|
+ id: `${boxId}-${i + 1}`,
|
|
|
+ status: filledIndexes.includes(i) ? 'filled' : 'empty'
|
|
|
+ }))
|
|
|
+}
|
|
|
+
|
|
|
+const boxList = ref<BoxItem[]>([
|
|
|
+ { id: 1, status: 'filled' },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ status: 'empty',
|
|
|
+ splitCount: 2,
|
|
|
+ subLocations: generateSubLocations(2, 2, [0])
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ status: 'empty',
|
|
|
+ splitCount: 4,
|
|
|
+ subLocations: generateSubLocations(3, 4, [0, 1])
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ status: 'empty',
|
|
|
+ splitCount: 6,
|
|
|
+ subLocations: generateSubLocations(4, 6, [0, 2])
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 5,
|
|
|
+ status: 'empty',
|
|
|
+ splitCount: 8,
|
|
|
+ subLocations: generateSubLocations(5, 8, [0, 3, 5])
|
|
|
+ },
|
|
|
+ { id: 6, status: 'empty' },
|
|
|
+ { id: 7, status: 'filled' },
|
|
|
+ { id: 8, status: 'empty' },
|
|
|
+ { id: 9, status: 'emptyBox', label: '空箱' },
|
|
|
+ { id: 10, status: 'waiting', label: '等待调箱' },
|
|
|
+ { id: 11, status: 'empty' },
|
|
|
+ { id: 12, status: 'empty' }
|
|
|
+])
|
|
|
+
|
|
|
+// 获取子库位的grid样式
|
|
|
+const getSubGridStyle = (splitCount: number) => {
|
|
|
+ switch (splitCount) {
|
|
|
+ case 2:
|
|
|
+ return { gridTemplateColumns: 'repeat(2, 1fr)', gridTemplateRows: '1fr' }
|
|
|
+ case 4:
|
|
|
+ return { gridTemplateColumns: 'repeat(2, 1fr)', gridTemplateRows: 'repeat(2, 1fr)' }
|
|
|
+ case 6:
|
|
|
+ return { gridTemplateColumns: 'repeat(3, 1fr)', gridTemplateRows: 'repeat(2, 1fr)' }
|
|
|
+ case 8:
|
|
|
+ return { gridTemplateColumns: 'repeat(4, 1fr)', gridTemplateRows: 'repeat(2, 1fr)' }
|
|
|
+ default:
|
|
|
+ return { gridTemplateColumns: 'repeat(2, 1fr)', gridTemplateRows: 'repeat(2, 1fr)' }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 选中的料箱/库位
|
|
|
+const selectedBox = ref<string | number | null>(null)
|
|
|
+
|
|
|
+// 选择料箱或子库位
|
|
|
+const selectBox = (box: BoxItem, subId?: string) => {
|
|
|
+ if (box.status === 'waiting' || box.status === 'emptyBox') return
|
|
|
+ selectedBox.value = subId || box.id
|
|
|
+}
|
|
|
+
|
|
|
+// 选择子库位
|
|
|
+const selectSubLocation = (box: BoxItem, sub: SubLocation) => {
|
|
|
+ selectedBox.value = sub.id
|
|
|
+}
|
|
|
+
|
|
|
+// 呼唤机器人
|
|
|
+const callRobot = () => {
|
|
|
+ showToast('正在呼唤机器人...')
|
|
|
+}
|
|
|
+
|
|
|
+// 重新输入
|
|
|
+const resetInput = () => {
|
|
|
+ boxCode.value = ''
|
|
|
+ selectedBox.value = null
|
|
|
+}
|
|
|
+
|
|
|
+// 提交移库
|
|
|
+const submitMove = () => {
|
|
|
+ if (!boxCode.value) {
|
|
|
+ showToast('请先扫描料箱号')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ showToast('提交移库成功')
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="merge-container">
|
|
|
+ <!-- 扫描输入框 -->
|
|
|
+ <div class="scan-section">
|
|
|
+ <van-field
|
|
|
+ v-model="boxCode"
|
|
|
+ placeholder="请扫描料箱号"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 信息展示表格 -->
|
|
|
+ <div class="info-table">
|
|
|
+ <div class="table-row">
|
|
|
+ <div class="cell label">目标库位</div>
|
|
|
+ <div class="cell value">{{ productInfo.targetLocation }}</div>
|
|
|
+ <div class="cell label">库存数量</div>
|
|
|
+ <div class="cell value">{{ productInfo.stockQty }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="table-row">
|
|
|
+ <div class="cell label">商品名称</div>
|
|
|
+ <div class="cell value span-2">{{ productInfo.productName }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="table-row">
|
|
|
+ <div class="cell label">商品条码</div>
|
|
|
+ <div class="cell value span-2">{{ productInfo.barcode }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="table-row">
|
|
|
+ <div class="cell label">质量状态</div>
|
|
|
+ <div class="cell value">{{ productInfo.qualityStatus }}</div>
|
|
|
+ <div class="cell label">属性仓</div>
|
|
|
+ <div class="cell value">{{ productInfo.warehouseType }}</div>
|
|
|
+ <div class="cell label">批号</div>
|
|
|
+ <div class="cell value"></div>
|
|
|
+ </div>
|
|
|
+ <div class="table-row">
|
|
|
+ <div class="cell label">生产日期</div>
|
|
|
+ <div class="cell value">{{ productInfo.productionDate }}</div>
|
|
|
+ <div class="cell label">失效日期</div>
|
|
|
+ <div class="cell value">{{ productInfo.expiryDate }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="table-row">
|
|
|
+ <div class="cell label">目标库位</div>
|
|
|
+ <div class="cell value">{{ productInfo.targetLocationNew }}</div>
|
|
|
+ <div class="cell label">移库数量</div>
|
|
|
+ <div class="cell value editable" @dblclick="editMoveQty">
|
|
|
+ <template v-if="isEditingMoveQty">
|
|
|
+ <van-field
|
|
|
+ v-model="productInfo.moveQty"
|
|
|
+ type="number"
|
|
|
+ autofocus
|
|
|
+ @blur="confirmMoveQty"
|
|
|
+ @keyup.enter="confirmMoveQty"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <span>{{ productInfo.moveQty }}</span>
|
|
|
+ <span v-if="!productInfo.moveQty" class="placeholder">双击编辑</span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 料箱选择区域 -->
|
|
|
+ <div class="grid-section">
|
|
|
+ <div class="grid-container">
|
|
|
+ <div
|
|
|
+ v-for="box in boxList"
|
|
|
+ :key="box.id"
|
|
|
+ class="box-wrapper"
|
|
|
+ >
|
|
|
+ <!-- 序号在料箱上方 -->
|
|
|
+ <div class="box-number">{{ box.id }}</div>
|
|
|
+ <div
|
|
|
+ class="box-item"
|
|
|
+ :class="{
|
|
|
+ 'box-filled': box.status === 'filled' && !box.splitCount,
|
|
|
+ 'box-empty-box': box.status === 'emptyBox',
|
|
|
+ 'box-waiting': box.status === 'waiting',
|
|
|
+ 'box-selected': selectedBox === box.id,
|
|
|
+ 'box-split': box.splitCount
|
|
|
+ }"
|
|
|
+ @click="!box.splitCount && selectBox(box)"
|
|
|
+ >
|
|
|
+ <!-- 分割的料箱 -->
|
|
|
+ <template v-if="box.splitCount && box.subLocations">
|
|
|
+ <div class="sub-grid" :style="getSubGridStyle(box.splitCount)">
|
|
|
+ <div
|
|
|
+ v-for="sub in box.subLocations"
|
|
|
+ :key="sub.id"
|
|
|
+ class="sub-location"
|
|
|
+ :class="{
|
|
|
+ 'sub-filled': sub.status === 'filled',
|
|
|
+ 'sub-selected': selectedBox === sub.id
|
|
|
+ }"
|
|
|
+ @click.stop="selectSubLocation(box, sub)"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <!-- 普通料箱 -->
|
|
|
+ <template v-else>
|
|
|
+ <span v-if="box.label" class="box-label">{{ box.label }}</span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部按钮 -->
|
|
|
+ <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 class="btn-submit" size="small" @click="submitMove">提交移库</van-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.merge-container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: #f5f5f5;
|
|
|
+ padding: 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.scan-section {
|
|
|
+ margin-bottom: 12px;
|
|
|
+
|
|
|
+ :deep(.van-field) {
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.info-table {
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ margin-bottom: 12px;
|
|
|
+
|
|
|
+ .table-row {
|
|
|
+ display: flex;
|
|
|
+ border-bottom: 1px solid #ddd;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .cell {
|
|
|
+ padding: 8px 6px;
|
|
|
+ font-size: 13px;
|
|
|
+ border-right: 1px solid #ddd;
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-right: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.label {
|
|
|
+ background: #f9f9f9;
|
|
|
+ color: #666;
|
|
|
+ flex: 0 0 70px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.value {
|
|
|
+ color: #333;
|
|
|
+
|
|
|
+ &.editable {
|
|
|
+ cursor: pointer;
|
|
|
+ min-height: 20px;
|
|
|
+
|
|
|
+ .placeholder {
|
|
|
+ color: #ccc;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.van-field) {
|
|
|
+ padding: 0;
|
|
|
+
|
|
|
+ .van-field__body {
|
|
|
+ height: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .van-field__control {
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.span-2 {
|
|
|
+ flex: 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.grid-section {
|
|
|
+ background: #fff;
|
|
|
+ padding: 12px;
|
|
|
+ margin-bottom: 80px;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ .grid-container {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(6, 1fr);
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .box-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .box-number {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 2px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .box-item {
|
|
|
+ width: 100%;
|
|
|
+ aspect-ratio: 1;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ background: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 4px;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .box-label {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #666;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 1.2;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.box-filled {
|
|
|
+ background: #d4e5f7;
|
|
|
+ border-color: #4a90d9;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.box-empty-box {
|
|
|
+ background: #f5f5f5;
|
|
|
+ border-color: #ddd;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.box-waiting {
|
|
|
+ background: #f5d4d4;
|
|
|
+ border-color: #e0b0b0;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.box-selected {
|
|
|
+ border: 2px solid #1989fa;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.box-split {
|
|
|
+ padding: 3px;
|
|
|
+ cursor: default;
|
|
|
+
|
|
|
+ .sub-grid {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: grid;
|
|
|
+ gap: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sub-location {
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ background: #fff;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 2px;
|
|
|
+
|
|
|
+ &.sub-filled {
|
|
|
+ background: #d4e5f7;
|
|
|
+ border-color: #4a90d9;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.sub-selected {
|
|
|
+ border: 2px solid #1989fa;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.footer-buttons {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 10px 12px;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+
|
|
|
+ .btn-right {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .van-button {
|
|
|
+ height: 32px;
|
|
|
+ font-size: 13px;
|
|
|
+ padding: 0 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-robot {
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #ff9800;
|
|
|
+ color: #ff9800;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-reset {
|
|
|
+ background: #5b9bd5;
|
|
|
+ border-color: #5b9bd5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-submit {
|
|
|
+ background: #ff9800;
|
|
|
+ border-color: #ff9800;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|