diff --git a/inventory-web/src/views/stock/stocktake/index.vue b/inventory-web/src/views/stock/stocktake/index.vue index 87626cd..502e05c 100644 --- a/inventory-web/src/views/stock/stocktake/index.vue +++ b/inventory-web/src/views/stock/stocktake/index.vue @@ -49,14 +49,33 @@ -
-
- -
扫描条码
+
+
+ + 点击开启全屏扫码
-
- -

操作中...
点击返回扫描

+
+ + 无扫码权限 +
+ +
+ + + +
@@ -210,6 +229,24 @@
+ +
+
+ + 扫码模式 +
+
+ +
+ +
+ + +
+
截止时间:{{ new Date().toLocaleString() }}
@@ -253,7 +290,7 @@ import { ref, computed, onMounted, nextTick } from 'vue' import { getAllStock } from '@/api/inbound/stock' import QrScanner from '@/components/QrScanner/index.vue' import { ElMessage, ElMessageBox } from 'element-plus' -import { Search, VideoPlay, VideoPause, List, Checked, Download, ArrowRight, Cloudy, Edit, EditPen } from '@element-plus/icons-vue' +import { Search, VideoPlay, VideoPause, List, Checked, Download, ArrowRight, Cloudy, Edit, EditPen, CameraFilled, Close } from '@element-plus/icons-vue' import request from '@/utils/request' import { useUserStore } from '@/stores/user' import * as XLSX from 'xlsx' @@ -290,6 +327,12 @@ const isSessionActive = ref(false) const serverDraftCount = ref(0) const syncStatus = ref<'success' | 'syncing' | 'failed'>('success') +// 新增:扫码相关状态 +const showCamera = ref(false) +const barcodeInput = ref('') +const barcodeRef = ref() +const hasPermission = userStore.hasPermission('inventory_stocktake:operation') + const showList = ref(false) const showFinishDialog = ref(false) const showQtyDialog = ref(false) @@ -431,7 +474,7 @@ const loadData = async () => { } const onScanSuccess = (code: string) => { - if (!code) return + if (!code || loading.value) return const trimCode = code.trim() if (!/^[A-Za-z0-9\-\.]+$/.test(trimCode)) { @@ -439,33 +482,65 @@ const onScanSuccess = (code: string) => { return } + if (trimCode.length < 3) { + ElMessage.warning('扫描结果过短,请对准重试') + return + } + const item = allData.value.find(i => i.uuid === trimCode || i.bar_code === trimCode) if (item) { if (navigator.vibrate) navigator.vibrate(100) - const isBatchMultiple = (item.batch_no && item.batch_no.length > 0) && (item.qty_stock > 1); - - if (isBatchMultiple) { - openQtyDialog(item) - } else { - if (item.scanned) { - openQtyDialog(item) - } else { - updateAndSync(item, 1) - ElMessage.success(`自动确认: ${item.name} +1`) - } - } + // ★★★ 核心修改:扫码成功后立即关闭全屏扫码,弹出填数对话框 + showCamera.value = false + // 无论是否多批次,都弹出对话框让用户确认数量 + openQtyDialog(item) } else { ElMessage.error(`不在库条码: ${trimCode}`) if (navigator.vibrate) navigator.vibrate([200, 50, 200]) } } +// 开启全屏扫码 +const openFullscreenScanner = () => { + if (!hasPermission) { + ElMessage.warning('无扫码权限') + return + } + showCamera.value = true +} + +// 关闭全屏扫码 +const closeScanner = () => { + showCamera.value = false +} + +// 手动输入条码 +const handleManualInput = async () => { + const code = barcodeInput.value.trim() + if (!code) return + + loading.value = true + try { + const item = allData.value.find(i => i.uuid === code || i.bar_code === code) + + if (item) { + if (navigator.vibrate) navigator.vibrate(100) + openQtyDialog(item) + } else { + ElMessage.error(`不在库条码: ${code}`) + } + } finally { + barcodeInput.value = '' + loading.value = false + } +} + const openQtyDialog = (item: StockItem) => { currentItem.value = item - inputQty.value = item.scanned ? item.qty_actual : undefined + inputQty.value = item.scanned ? item.qty_actual : 1 showQtyDialog.value = true nextTick(() => { @@ -481,6 +556,13 @@ const handleManualConfirm = () => { updateAndSync(currentItem.value, val) showQtyDialog.value = false ElMessage.success(`已记录实盘: ${val}`) + + // ★★★ 核心修改:确认数量后自动重新打开全屏扫码,实现无缝闭环 + nextTick(() => { + if (hasPermission) { + showCamera.value = true + } + }) } const updateAndSync = async (item: StockItem, quantity: number) => { @@ -761,4 +843,132 @@ const finishStocktake = async () => { font-size: 24px !important; height: 58px !important; } + +/* ★★★ 新增:扫码区域样式 ★★★ */ +.scan-section { + margin-bottom: 15px; +} + +.camera-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 180px; + background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%); + border: 2px dashed #409EFF; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s; +} + +.camera-placeholder:hover { + background: linear-gradient(135deg, #d9ecff 0%, #b3d8ff 100%); + transform: scale(1.01); +} + +.camera-placeholder .text { + margin-top: 10px; + color: #409EFF; + font-size: 14px; +} + +.input-box { + margin-top: 15px; +} + +.input-box :deep(.el-input__wrapper) { + box-shadow: 0 0 0 1px #dcdfe6 inset; +} + +.input-box :deep(.el-input__wrapper:hover) { + box-shadow: 0 0 0 1px #c0c4cc inset; +} + +.input-box :deep(.el-input__wrapper.is-focus) { + box-shadow: 0 0 0 1px #409EFF inset; +} + +/* ★★★ 全屏扫码 Overlay 样式 ★★★ */ +.fullscreen-scanner-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #000; + z-index: 2000; + display: flex; + flex-direction: column; +} + +.scanner-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 15px; + background: rgba(0, 0, 0, 0.8); + color: #fff; +} + +.scanner-title { + font-size: 18px; + font-weight: bold; +} + +.close-btn { + background: rgba(255, 255, 255, 0.2); + border: none; + color: #fff; +} + +.scanner-placeholder { + width: 40px; +} + +.scanner-body { + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.scanner-footer { + padding: 20px; + text-align: center; + background: rgba(0, 0, 0, 0.8); + color: #fff; +} + +.scanner-footer p { + margin: 5px 0; +} + +.current-count { + font-size: 16px; + font-weight: bold; + color: #409EFF; +} + +@media (max-width: 768px) { + .app-container { + padding: 5px; + } + + .title-box { + font-size: 16px; + } + + .camera-placeholder { + height: 120px; + } + + .bottom-actions { + flex-direction: column; + } + + .bottom-actions .el-button { + width: 100%; + } +} \ No newline at end of file