update for Warranty plugin

This commit is contained in:
Gloomy 2025-08-22 09:08:47 +00:00
parent 6cbd431edb
commit 681262c33e
2 changed files with 184 additions and 148 deletions

View file

@ -24,14 +24,8 @@
<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 <qrcode-stream :camera="camera" :torch="isFlashOn" :formats="formats"
:camera="camera" :track="overlay ? paintOutline : undefined" @detect="onDetect" @init="onInit" />
:torch="isFlashOn"
:formats="formats"
:track="overlay ? paintOutline : undefined"
@detect="onDetect"
@init="onInit"
/>
</div> </div>
</v-card-text> </v-card-text>
@ -48,36 +42,36 @@
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted } from 'vue' import { ref, watch, onMounted } from 'vue'
import { QrcodeStream } from 'vue3-qrcode-reader' import { QrcodeStream } from 'vue3-qrcode-reader'
const props = defineProps({ const props = defineProps({
modelValue: Boolean, modelValue: Boolean,
formats: { type: Array as () => string[], default: () => ['QR_CODE', 'EAN_13', 'CODE_128'] }, formats: { type: Array as () => string[], default: () => ['QR_CODE', 'EAN_13', 'CODE_128'] },
overlay: { type: Boolean, default: true } overlay: { type: Boolean, default: true }
}) })
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void (e: 'update:modelValue', value: boolean): void
(e: 'detected', barcode: string): void (e: 'detected', barcode: string): void
(e: 'error', error: Error): void (e: 'error', error: Error): void
(e: 'close'): void (e: 'close'): void
}>() }>()
const internalShow = ref(props.modelValue) const internalShow = ref(props.modelValue)
watch(() => props.modelValue, val => internalShow.value = val) watch(() => props.modelValue, val => internalShow.value = val)
watch(internalShow, val => emit('update:modelValue', val)) watch(internalShow, val => emit('update:modelValue', val))
const camera = ref<'auto' | 'off' | 'user' | 'environment'>('environment') 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 errorMessage = ref('')
const canRetry = ref(false) const canRetry = ref(false)
const paintOutline = (location: any, ctx: CanvasRenderingContext2D) => { const paintOutline = (location: any, ctx: CanvasRenderingContext2D) => {
if (!location) return if (!location) return
const { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner } = location const { topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner } = location
ctx.strokeStyle = '#00ff00' ctx.strokeStyle = '#00ff00'
@ -89,9 +83,9 @@
ctx.lineTo(bottomLeftCorner.x, bottomLeftCorner.y) ctx.lineTo(bottomLeftCorner.x, bottomLeftCorner.y)
ctx.closePath() ctx.closePath()
ctx.stroke() ctx.stroke()
} }
const onInit = async (promise: Promise<void>) => { const onInit = async (promise: Promise<void>) => {
try { try {
await promise await promise
errorMessage.value = '' errorMessage.value = ''
@ -113,9 +107,9 @@
canRetry.value = false canRetry.value = false
emit('error', new Error(errorMessage.value)) emit('error', new Error(errorMessage.value))
} }
} }
const onDetect = (promise: Promise<any>) => { const onDetect = (promise: Promise<any>) => {
promise.then(result => { promise.then(result => {
if (result?.content) { if (result?.content) {
emit('detected', result.content) emit('detected', result.content)
@ -124,14 +118,14 @@
}).catch(err => { }).catch(err => {
console.error('Error decoding QR:', 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 () => { const requestPermission = async () => {
try { try {
await navigator.mediaDevices.getUserMedia({ video: true }) await navigator.mediaDevices.getUserMedia({ video: true })
errorMessage.value = '' errorMessage.value = ''
@ -140,13 +134,13 @@
errorMessage.value = 'دسترسی به دوربین رد شد.' errorMessage.value = 'دسترسی به دوربین رد شد.'
canRetry.value = true canRetry.value = true
} }
} }
const close = () => { const close = () => {
internalShow.value = false internalShow.value = false
emit('close') emit('close')
} }
onMounted(async () => { onMounted(async () => {
try { try {
const devices = await navigator.mediaDevices.enumerateDevices() const devices = await navigator.mediaDevices.enumerateDevices()
availableCameras.value = devices.filter(device => device.kind === 'videoinput') availableCameras.value = devices.filter(device => device.kind === 'videoinput')
@ -160,11 +154,11 @@
canRetry.value = false canRetry.value = false
emit('error', new Error(errorMessage.value)) emit('error', new Error(errorMessage.value))
} }
}) })
</script> </script>
<style scoped> <style scoped>
.scanner-container { .scanner-container {
width: 100%; width: 100%;
height: 300px; height: 300px;
background: #000; background: #000;
@ -174,13 +168,15 @@
display: flex; display: flex;
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;
left: 0; left: 0;
@ -188,8 +184,9 @@
bottom: 0; bottom: 0;
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
} }
.scanner-line {
.scanner-line {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 0; left: 0;
@ -197,27 +194,62 @@
height: 2px; height: 2px;
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;
height: 20px; height: 20px;
border-color: #00ff00; border-color: #00ff00;
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-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 {
0% {
transform: translateY(-50px);
} }
.scanner-corner.top-left { top: 20%; left: 20%; border-top-width: 4px; border-left-width: 4px; }
.scanner-corner.top-right { top: 20%; right: 20%; border-top-width: 4px; border-right-width: 4px; } 50% {
.scanner-corner.bottom-left { bottom: 20%; left: 20%; border-bottom-width: 4px; border-left-width: 4px; } transform: translateY(50px);
.scanner-corner.bottom-right { bottom: 20%; right: 20%; border-bottom-width: 4px; border-right-width: 4px; }
@keyframes scan {
0% { transform: translateY(-50px); }
50% { transform: translateY(50px); }
100% { transform: translateY(-50px); }
} }
.error-box {
100% {
transform: translateY(-50px);
}
}
.error-box {
padding: 16px; padding: 16px;
color: red; color: red;
text-align: center; text-align: center;
} }
</style> </style>

View file

@ -343,6 +343,10 @@ const submit = async () => {
const errors: string[] = [] const errors: string[] = []
let totalCount = 0 let totalCount = 0
items.value.forEach((row) => {
totalCount += Number(row.ticketCount || 0)
})
if (totalCount === 0) errors.push('هیچ کالایی برای خروج انتخاب نشده است') if (totalCount === 0) errors.push('هیچ کالایی برای خروج انتخاب نشده است')
if (errors.length) { if (errors.length) {