Files
KCGL/inventory-web/src/components/SmartScannerDialog.vue

270 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog
v-model="visible"
title="智能扫码"
width="600px"
destroy-on-close
:close-on-click-modal="false"
@close="handleClose"
>
<!-- Step 1: 扫码中 -->
<div v-if="step === 1" class="scanner-container">
<div id="qr-reader" ref="qrReaderRef"></div>
<div class="scanner-tip">
<el-alert type="info" :closable="false" show-icon>
请将条码/二维码对准摄像头系统将自动识别
</el-alert>
</div>
</div>
<!-- Step 2: 提取中 -->
<div v-if="step === 2" class="extract-container">
<el-alert type="success" :closable="false" title="扫码成功" show-icon style="margin-bottom: 15px;">
已识别到内容请选择或输入需要的序列号
</el-alert>
<!-- 全量数据展示 -->
<div class="full-data">
<div class="label">原始数据</div>
<div class="raw-text">{{ rawText }}</div>
</div>
<!-- 智能切割标签 - 仅当有多个有效片段时显示 -->
<div v-if="splitSegments.length > 1" class="smart-tags">
<div class="label">智能切割点击标签提取</div>
<div class="tags-container">
<el-tag
v-for="(片段, index) in splitSegments"
:key="index"
class="mx-1 mt-2"
style="cursor: pointer"
:type="selectedSegmentIndex === index ? 'success' : 'info'"
@click="selectSegment(index)"
>
{{ segment }}
</el-tag>
</div>
</div>
<!-- 最终结果确认 -->
<div class="final-input">
<el-input
v-model="finalResult"
placeholder="确认或微调序列号"
size="large"
>
<template #prepend>序列号</template>
</el-input>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button v-if="step === 2" @click="backToScanner">重新扫码</el-button>
<el-button v-if="step === 2" type="primary" @click="confirmResult">确认提取</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch, nextTick, onUnmounted } from 'vue'
import { Html5Qrcode } from 'html5-qrcode'
interface Props {
modelValue: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update:modelValue', val: boolean): void
(e: 'confirm', val: string): void
}>()
const visible = ref(props.modelValue)
const step = ref(1) // 1: 扫码中, 2: 提取中
const rawText = ref('')
const finalResult = ref('')
const splitSegments = ref<string[]>([])
const selectedSegmentIndex = ref<number | null>(null)
const qrReaderRef = ref<HTMLElement | null>(null)
let html5QrCode: Html5Qrcode | null = null
watch(() => props.modelValue, (val) => {
visible.value = val
if (val) {
step.value = 1
rawText.value = ''
finalResult.value = ''
splitSegments.value = []
selectedSegmentIndex.value = null
nextTick(() => {
startScanner()
})
} else {
stopScanner()
}
})
watch(visible, (val) => {
emit('update:modelValue', val)
if (!val) {
stopScanner()
}
})
const startScanner = async () => {
if (!qrReaderRef.value) return
try {
html5QrCode = new Html5Qrcode('qr-reader')
await html5QrCode.start(
{ facingMode: 'environment' },
{
fps: 10,
qrbox: { width: 250, height: 250 },
aspectRatio: 1.0
},
(decodedText) => {
// 扫码成功
handleScanSuccess(decodedText)
},
() => {
// 扫码失败回调,忽略
}
)
} catch (err) {
console.error('启动摄像头失败:', err)
ElMessage.error('无法启动摄像头,请确保已授予权限')
}
}
const stopScanner = async () => {
if (html5QrCode) {
try {
await html5QrCode.stop()
html5QrCode.clear()
} catch (err) {
console.error('停止摄像头失败:', err)
}
html5QrCode = null
}
}
const handleScanSuccess = (text: string) => {
rawText.value = text
finalResult.value = text
splitText(text)
step.value = 2
stopScanner()
}
const splitText = (text: string) => {
// 使用正则表达式智能切割,过滤空字符串
const segments = text.split(/[;,\s_\|:]+/).filter(s => s && s.trim().length > 0)
splitSegments.value = segments
if (segments.length > 0) {
selectedSegmentIndex.value = 0
finalResult.value = segments[0]
}
}
const selectSegment = (index: number) => {
selectedSegmentIndex.value = index
finalResult.value = splitSegments.value[index]
}
const backToScanner = () => {
step.value = 1
rawText.value = ''
finalResult.value = ''
splitSegments.value = []
selectedSegmentIndex.value = null
nextTick(() => {
startScanner()
})
}
const confirmResult = () => {
if (!finalResult.value.trim()) {
ElMessage.warning('请输入或选择序列号')
return
}
emit('confirm', finalResult.value.trim())
handleClose()
}
const handleClose = () => {
visible.value = false
stopScanner()
}
onUnmounted(() => {
stopScanner()
})
</script>
<style scoped>
.scanner-container {
text-align: center;
}
#qr-reader {
width: 100%;
border-radius: 8px;
overflow: hidden;
}
.scanner-tip {
margin-top: 15px;
}
.extract-container {
padding: 10px 0;
}
.label {
font-size: 14px;
font-weight: 600;
color: #606266;
margin-bottom: 10px;
}
.full-data {
margin-bottom: 20px;
}
.raw-text {
background: #f5f7fa;
padding: 12px;
border-radius: 4px;
word-break: break-all;
font-family: monospace;
font-size: 13px;
color: #909399;
}
.smart-tags {
margin-bottom: 20px;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.final-input {
margin-top: 10px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>