Refactor BarcodeInput and CameraUpload Components for Enhanced UI and Functionality
- Replace SVG icon in BarcodeInput with a new Icon component for improved visual consistency. - Update CameraUpload component to enhance styling and functionality, including: - Add a barcode scanning overlay for better user experience during scanning. - Refactor video and canvas elements to improve layout and responsiveness. - Adjust button sizes and styles for better accessibility and usability. - Implement more robust camera handling logic, including improved facing mode management and error handling for mobile devices. These changes aim to enhance the user interface and overall functionality of the barcode scanning feature, ensuring a smoother experience for users.
This commit is contained in:
parent
e6239f99cd
commit
d836d531a7
|
|
@ -61,33 +61,15 @@
|
|||
<div class="text-center">
|
||||
<template v-if="!scannedValue && !isScanning">
|
||||
<div class="text-gray-500 w-full flex justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
|
||||
/>
|
||||
</svg>
|
||||
<Icon
|
||||
name="i-material-symbols-barcode-scanner-rounded"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="mt-2 text-sm text-gray-500 font-medium select-none">
|
||||
{{ $t('forms.barcodeInput.clickToOpenCamera') }}
|
||||
</p>
|
||||
<div class="w-full items-center justify-center mt-2 hidden sm:flex">
|
||||
<UButton
|
||||
icon="i-heroicons-camera"
|
||||
color="white"
|
||||
class="px-2"
|
||||
@click.stop="startScanning"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="relative border">
|
||||
<div class="relative overflow-hidden" :class="[theme.fileInput.borderRadius]">
|
||||
<video
|
||||
id="webcam"
|
||||
autoplay
|
||||
|
|
@ -9,59 +9,89 @@
|
|||
{ hidden: !isCapturing },
|
||||
theme.fileInput.minHeight,
|
||||
theme.fileInput.borderRadius,
|
||||
'w-full h-full object-cover'
|
||||
'w-full h-full object-cover border border-gray-400/30'
|
||||
]"
|
||||
webkit-playsinline
|
||||
/>
|
||||
<canvas
|
||||
id="canvas"
|
||||
:class="{ hidden: !capturedImage }"
|
||||
:class="[
|
||||
{ hidden: !capturedImage },
|
||||
theme.fileInput.borderRadius,
|
||||
theme.fileInput.minHeight,
|
||||
'w-full h-full object-cover border border-gray-400/30'
|
||||
]"
|
||||
/>
|
||||
|
||||
<!-- Barcode scanning overlay -->
|
||||
<div
|
||||
v-if="isCapturing && isBarcodeMode"
|
||||
class="absolute inset-0 pointer-events-none"
|
||||
>
|
||||
<!-- Semi-transparent overlay -->
|
||||
<div class="absolute inset-0 bg-black/30"></div>
|
||||
|
||||
<!-- Scanning area (transparent window) -->
|
||||
<div class="absolute inset-0 flex items-center justify-center" style="padding-bottom: 60px;">
|
||||
<div class="relative w-4/5 h-3/5">
|
||||
<!-- Transparent window -->
|
||||
<div class="absolute inset-0 bg-transparent border-0"></div>
|
||||
|
||||
<!-- Corner indicators -->
|
||||
<div class="absolute top-0 left-0 w-8 h-8 border-t-2 border-l-2 border-white"></div>
|
||||
<div class="absolute top-0 right-0 w-8 h-8 border-t-2 border-r-2 border-white"></div>
|
||||
<div class="absolute bottom-0 left-0 w-8 h-8 border-b-2 border-l-2 border-white"></div>
|
||||
<div class="absolute bottom-0 right-0 w-8 h-8 border-b-2 border-r-2 border-white"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="cameraPermissionStatus === 'allowed'"
|
||||
class="absolute inset-x-0 grid place-content-center bottom-2"
|
||||
>
|
||||
<div
|
||||
v-if="isCapturing"
|
||||
class="p-2 px-4 flex items-center justify-center text-xs space-x-2"
|
||||
class="p-2 px-4 flex items-center justify-center text-xs space-x-3"
|
||||
>
|
||||
<span
|
||||
v-if="!isBarcodeMode"
|
||||
class="cursor-pointer rounded-full w-14 h-14 border-2 grid place-content-center"
|
||||
class="cursor-pointer rounded-full w-12 h-12 border-2 grid place-content-center bg-white/20 backdrop-blur-sm shadow-md"
|
||||
@click="processCapturedImage"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer bg-gray-100 rounded-full w-10 h-10 grid place-content-center"
|
||||
class="cursor-pointer bg-white rounded-full w-8 h-8 grid place-content-center"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="text-white cursor-pointer"
|
||||
class="text-white cursor-pointer bg-black/40 rounded-full backdrop-blur-sm shadow-md w-12 h-12 grid place-content-center"
|
||||
@click="cancelCamera"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:x-mark"
|
||||
class="w-8 h-8"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="isMobileDevice"
|
||||
class="text-white cursor-pointer"
|
||||
class="text-white cursor-pointer bg-black/40 rounded-full backdrop-blur-sm shadow-md w-12 h-12 grid place-content-center"
|
||||
@click="switchCamera"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:arrow-path"
|
||||
class="w-8 h-8"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="cameraPermissionStatus === 'blocked'"
|
||||
class="absolute p-5 top-0 inset-x-0 flex flex-col items-center justify-center space-y-4 text-center rounded border border-gray-400/30 h-full"
|
||||
class="absolute p-5 top-0 inset-x-0 flex flex-col items-center justify-center space-y-4 text-center border border-gray-400/30 h-full"
|
||||
:class="[theme.fileInput.borderRadius]"
|
||||
@click="openCameraUpload"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:camera"
|
||||
:name="isBarcodeMode ? 'i-material-symbols-barcode-scanner-rounded' : 'i-heroicons-camera'"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<p class="text-center font-bold">
|
||||
|
|
@ -80,7 +110,8 @@
|
|||
|
||||
<div
|
||||
v-else-if="cameraPermissionStatus === 'loading'"
|
||||
class="absolute p-5 top-0 inset-x-0 flex flex-col items-center justify-center space-y-4 text-center rounded border border-gray-400/30 h-full"
|
||||
class="absolute p-5 top-0 inset-x-0 flex flex-col items-center justify-center space-y-4 text-center border border-gray-400/30 h-full"
|
||||
:class="[theme.fileInput.borderRadius]"
|
||||
>
|
||||
<div class="w-6 h-6">
|
||||
<Loader />
|
||||
|
|
@ -88,11 +119,12 @@
|
|||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="absolute p-5 top-0 inset-x-0 flex flex-col items-center justify-center space-y-4 text-center rounded border border-gray-400/30 h-full"
|
||||
class="absolute p-5 top-0 inset-x-0 flex flex-col items-center justify-center space-y-4 text-center border border-gray-400/30 h-full"
|
||||
:class="[theme.fileInput.borderRadius]"
|
||||
@click="openCameraUpload"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:video-camera-slash"
|
||||
:name="isBarcodeMode ? 'i-material-symbols-barcode-scanner-rounded' : 'heroicons:video-camera-slash'"
|
||||
class="w-6 h-6"
|
||||
/>
|
||||
<p class="text-center font-bold">
|
||||
|
|
@ -195,16 +227,11 @@ export default {
|
|||
|
||||
async switchCamera() {
|
||||
try {
|
||||
// Stop current camera
|
||||
if (this.quaggaInitialized) {
|
||||
Quagga.stop()
|
||||
this.quaggaInitialized = false
|
||||
}
|
||||
this.webcam.stop()
|
||||
// Stop current camera and clean up resources
|
||||
this.cleanupCurrentStream()
|
||||
|
||||
// Toggle facing mode considering barcode mode
|
||||
this.currentFacingMode = this.isBarcodeMode ? 'environment' :
|
||||
(this.currentFacingMode === 'user' ? 'environment' : 'user')
|
||||
// Toggle facing mode
|
||||
this.currentFacingMode = this.currentFacingMode === 'user' ? 'environment' : 'user'
|
||||
|
||||
// Restart camera
|
||||
await this.openCameraUpload()
|
||||
|
|
@ -222,22 +249,49 @@ export default {
|
|||
const webcamElement = document.getElementById("webcam")
|
||||
const canvasElement = document.getElementById("canvas")
|
||||
|
||||
const constraints = {
|
||||
// Determine the facing mode to use
|
||||
let facingMode = this.currentFacingMode
|
||||
if (this.isBarcodeMode && this.currentFacingMode === 'user') {
|
||||
// Force environment mode for barcode scanning
|
||||
facingMode = 'environment'
|
||||
}
|
||||
|
||||
// Create constraints based on device capabilities
|
||||
let constraints = {
|
||||
audio: false,
|
||||
video: {
|
||||
facingMode: this.isBarcodeMode ? 'environment' : this.currentFacingMode,
|
||||
width: { ideal: 1280 },
|
||||
height: { ideal: 720 }
|
||||
}
|
||||
}
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia(constraints)
|
||||
// Use exact constraints for mobile devices to ensure proper camera selection
|
||||
if (this.isMobileDevice) {
|
||||
constraints.video.facingMode = { exact: facingMode }
|
||||
} else {
|
||||
constraints.video.facingMode = facingMode
|
||||
}
|
||||
|
||||
// Try to get the stream with the specified constraints
|
||||
let stream
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia(constraints)
|
||||
} catch (err) {
|
||||
// If exact constraint fails, fall back to preference
|
||||
if (this.isMobileDevice && err.name === 'OverconstrainedError') {
|
||||
constraints.video.facingMode = facingMode
|
||||
stream = await navigator.mediaDevices.getUserMedia(constraints)
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
this.mediaStream = stream // Store the stream reference
|
||||
webcamElement.srcObject = stream
|
||||
|
||||
this.webcam = new Webcam(
|
||||
webcamElement,
|
||||
this.isBarcodeMode ? 'environment' : this.currentFacingMode,
|
||||
facingMode,
|
||||
canvasElement
|
||||
)
|
||||
|
||||
|
|
@ -269,9 +323,18 @@ export default {
|
|||
type: "LiveStream",
|
||||
target: document.getElementById("webcam"),
|
||||
constraints: {
|
||||
facingMode: "environment"
|
||||
facingMode: this.currentFacingMode,
|
||||
width: { min: 640 },
|
||||
height: { min: 480 },
|
||||
aspectRatio: { min: 1, max: 2 }
|
||||
},
|
||||
},
|
||||
locator: {
|
||||
patchSize: "medium",
|
||||
halfSample: true
|
||||
},
|
||||
numOfWorkers: navigator.hardwareConcurrency || 4,
|
||||
frequency: 10,
|
||||
decoder: {
|
||||
readers: this.decoders || []
|
||||
},
|
||||
|
|
@ -326,3 +389,4 @@ export default {
|
|||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue