截止时间:{{ 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