update for Warranty plugin & barcode scanner

This commit is contained in:
Gloomy 2025-08-21 23:00:28 +00:00
parent 35add500ca
commit 6cbd431edb
2 changed files with 168 additions and 144 deletions

View file

@ -6,8 +6,17 @@
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn icon @click="close"><v-icon>mdi-close</v-icon></v-btn> <v-btn icon @click="close"><v-icon>mdi-close</v-icon></v-btn>
</v-toolbar> </v-toolbar>
<v-card-text class="pa-0"> <v-card-text class="pa-0">
<div class="scanner-container"> <div v-if="errorMessage" class="error-box">
<v-icon color="red" class="mr-2">mdi-alert-circle</v-icon>
{{ errorMessage }}
<div v-if="canRetry" class="mt-2">
<v-btn size="small" color="primary" @click="requestPermission">تلاش دوباره</v-btn>
</div>
</div>
<div v-else class="scanner-container">
<div v-if="overlay" class="scanner-overlay"> <div v-if="overlay" class="scanner-overlay">
<div class="scanner-line"></div> <div class="scanner-line"></div>
<div class="scanner-corner top-left"></div> <div class="scanner-corner top-left"></div>
@ -15,11 +24,18 @@
<div class="scanner-corner bottom-left"></div> <div class="scanner-corner bottom-left"></div>
<div class="scanner-corner bottom-right"></div> <div class="scanner-corner bottom-right"></div>
</div> </div>
<qrcode-stream :camera="camera" :torch="isFlashOn" :formats="formats" <qrcode-stream
:track="overlay ? paintOutline : undefined" @detect="onDetect" @init="onInit" /> :camera="camera"
:torch="isFlashOn"
:formats="formats"
:track="overlay ? paintOutline : undefined"
@detect="onDetect"
@init="onInit"
/>
</div> </div>
</v-card-text> </v-card-text>
<v-card-actions class="pa-2">
<v-card-actions class="pa-2" v-if="!errorMessage">
<v-btn @click="toggleFlash" :color="isFlashOn ? 'primary' : 'grey'"> <v-btn @click="toggleFlash" :color="isFlashOn ? 'primary' : 'grey'">
<v-icon start>mdi-flash</v-icon> فلش <v-icon start>mdi-flash</v-icon> فلش
</v-btn> </v-btn>
@ -58,6 +74,8 @@ const camera = ref<'auto' | 'off' | 'user' | 'environment'>('environment')
const isFlashOn = ref(false) const isFlashOn = ref(false)
const availableCameras = ref<MediaDeviceInfo[]>([]) const availableCameras = ref<MediaDeviceInfo[]>([])
const isBackCamera = ref(true) const isBackCamera = ref(true)
const errorMessage = ref('')
const canRetry = ref(false)
const paintOutline = (location: any, ctx: CanvasRenderingContext2D) => { const paintOutline = (location: any, ctx: CanvasRenderingContext2D) => {
if (!location) return if (!location) return
@ -73,11 +91,28 @@ const paintOutline = (location: any, ctx: CanvasRenderingContext2D) => {
ctx.stroke() ctx.stroke()
} }
const onInit = (promise: Promise<void>) => { const onInit = async (promise: Promise<void>) => {
promise.catch((err: Error) => { try {
emit('error', err) await promise
close() errorMessage.value = ''
}) canRetry.value = false
} catch (err: any) {
if (err.name === 'NotAllowedError' || err.message?.includes('permission')) {
errorMessage.value = 'برای استفاده از اسکنر لطفاً اجازه دسترسی به دوربین را بدهید.'
canRetry.value = true
emit('error', new Error(errorMessage.value))
return
}
if (err.name === 'NotFoundError' || err.message?.includes('camera') || err.message?.includes('device')) {
errorMessage.value = 'هیچ دوربینی در دستگاه شما یافت نشد.'
canRetry.value = false
emit('error', new Error(errorMessage.value))
return
}
errorMessage.value = 'خطا در راه‌اندازی دوربین: ' + (err.message || 'خطای نامشخص')
canRetry.value = false
emit('error', new Error(errorMessage.value))
}
} }
const onDetect = (promise: Promise<any>) => { const onDetect = (promise: Promise<any>) => {
@ -86,24 +121,45 @@ const onDetect = (promise: Promise<any>) => {
emit('detected', result.content) emit('detected', result.content)
close() close()
} }
}).catch(err => console.error('Error decoding QR:', err)) }).catch(err => {
console.error('Error decoding QR:', err)
})
} }
const toggleFlash = () => isFlashOn.value = !isFlashOn.value const toggleFlash = () => isFlashOn.value = !isFlashOn.value
const toggleCamera = () => { const toggleCamera = () => {
camera.value = camera.value === 'environment' ? 'user' : 'environment' camera.value = camera.value === 'environment' ? 'user' : 'environment'
isBackCamera.value = camera.value === 'environment' isBackCamera.value = camera.value === 'environment'
} }
const requestPermission = async () => {
try {
await navigator.mediaDevices.getUserMedia({ video: true })
errorMessage.value = ''
canRetry.value = false
} catch {
errorMessage.value = 'دسترسی به دوربین رد شد.'
canRetry.value = true
}
}
const close = () => { const close = () => {
internalShow.value = false internalShow.value = false
emit('close') emit('close')
} }
onMounted(async () => { onMounted(async () => {
const devices = await navigator.mediaDevices.enumerateDevices(); try {
const devices = await navigator.mediaDevices.enumerateDevices()
availableCameras.value = devices.filter(device => device.kind === 'videoinput') availableCameras.value = devices.filter(device => device.kind === 'videoinput')
if (availableCameras.value.length === 0) {
errorMessage.value = 'هیچ دوربینی در دستگاه شما یافت نشد.'
canRetry.value = false
emit('error', new Error(errorMessage.value))
}
} catch {
errorMessage.value = 'خطا در بررسی دوربین‌های موجود.'
canRetry.value = false
emit('error', new Error(errorMessage.value))
}
}) })
</script> </script>
@ -119,13 +175,11 @@ onMounted(async () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.scanner-container video { .scanner-container video {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
.scanner-overlay { .scanner-overlay {
position: absolute; position: absolute;
top: 0; top: 0;
@ -135,7 +189,6 @@ onMounted(async () => {
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
} }
.scanner-line { .scanner-line {
position: absolute; position: absolute;
top: 50%; top: 50%;
@ -145,7 +198,6 @@ onMounted(async () => {
background: #00ff00; background: #00ff00;
animation: scan 2s linear infinite; animation: scan 2s linear infinite;
} }
.scanner-corner { .scanner-corner {
position: absolute; position: absolute;
width: 20px; width: 20px;
@ -154,46 +206,18 @@ onMounted(async () => {
border-style: solid; border-style: solid;
border-width: 0; border-width: 0;
} }
.scanner-corner.top-left { top: 20%; left: 20%; border-top-width: 4px; border-left-width: 4px; }
.scanner-corner.top-left { .scanner-corner.top-right { top: 20%; right: 20%; border-top-width: 4px; border-right-width: 4px; }
top: 20%; .scanner-corner.bottom-left { bottom: 20%; left: 20%; border-bottom-width: 4px; border-left-width: 4px; }
left: 20%; .scanner-corner.bottom-right { bottom: 20%; right: 20%; border-bottom-width: 4px; border-right-width: 4px; }
border-top-width: 4px;
border-left-width: 4px;
}
.scanner-corner.top-right {
top: 20%;
right: 20%;
border-top-width: 4px;
border-right-width: 4px;
}
.scanner-corner.bottom-left {
bottom: 20%;
left: 20%;
border-bottom-width: 4px;
border-left-width: 4px;
}
.scanner-corner.bottom-right {
bottom: 20%;
right: 20%;
border-bottom-width: 4px;
border-right-width: 4px;
}
@keyframes scan { @keyframes scan {
0% { 0% { transform: translateY(-50px); }
transform: translateY(-50px); 50% { transform: translateY(50px); }
} 100% { transform: translateY(-50px); }
50% {
transform: translateY(50px);
}
100% {
transform: translateY(-50px);
} }
.error-box {
padding: 16px;
color: red;
text-align: center;
} }
</style> </style>

View file

@ -109,10 +109,10 @@
</div> </div>
<div class="col-actions"> <div class="col-actions">
<v-btn icon size="small" color="primary" variant="text" <!-- <v-btn icon size="small" color="primary" variant="text"
@click="scanner.open = true, scanner.mode = 'serial', scanner.itemIndex = index, scanner.lineIndex = lidx"> @click="scanner.open = true, scanner.mode = 'serial', scanner.itemIndex = index, scanner.lineIndex = lidx">
<v-icon>mdi-barcode-scan</v-icon> <v-icon>mdi-barcode-scan</v-icon>
</v-btn> </v-btn> -->
</div> </div>
<div class="col-warranty"> <div class="col-warranty">
@ -129,7 +129,7 @@
<div class="col-actions"> <div class="col-actions">
<v-btn icon size="small" color="secondary" variant="text" <v-btn icon size="small" color="secondary" variant="text"
@click="scanner.open = true, scanner.mode = 'warranty', scanner.itemIndex = index, scanner.lineIndex = lidx"> @click="scanner.open = true, scanner.mode = 'serial', scanner.itemIndex = index, scanner.lineIndex = lidx">
<v-icon>mdi-barcode-scan</v-icon> <v-icon>mdi-barcode-scan</v-icon>
</v-btn> </v-btn>
</div> </div>