Files
riwa-ionic/src/components/qr-scanner/index.vue
2025-12-17 23:57:04 +07:00

163 lines
4.1 KiB
Vue

<!-- filepath: /src/components/ui/qr-scanner/QRScanner.vue -->
<script setup lang="ts">
import type { QRScanResult } from "@/composables/useQRScanner";
interface Props {
modelValue?: boolean;
title?: string;
subtitle?: string;
}
interface Emits {
"update:modelValue": [value: boolean];
"scan-success": [result: QRScanResult];
"scan-error": [error: Error];
"scan-cancelled": [];
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
title: "扫描二维码",
subtitle: "将二维码对准扫描框进行扫描",
});
const emit = defineEmits<Emits>();
const { isSupported, startScan, stopScan } = useQRScanner();
const isScanning = ref(false);
// 开始扫描
async function handleStartScan() {
try {
if (!isSupported) {
throw new Error("当前平台不支持二维码扫描");
}
isScanning.value = true;
const result = await startScan();
if (result) {
emit("scan-success", result);
await handleClose();
}
else {
emit("scan-cancelled");
await handleClose();
}
}
catch (error) {
console.error("Scan failed:", error);
emit("scan-error", error as Error);
await handleClose();
}
finally {
isScanning.value = false;
}
}
// 关闭扫描
async function handleClose() {
if (isScanning.value) {
await stopScan();
isScanning.value = false;
}
emit("update:modelValue", false);
}
// 监听模态框打开
watch(() => props.modelValue, (isOpen) => {
if (isOpen && isSupported) {
nextTick(() => {
handleStartScan();
});
}
});
// 组件卸载时确保停止扫描
onUnmounted(() => {
if (isScanning.value) {
stopScan();
}
});
</script>
<template>
<IonModal :is-open="modelValue" @did-dismiss="handleClose">
<IonHeader>
<IonToolbar>
<IonTitle>{{ title }}</IonTitle>
<IonButtons slot="end">
<IonButton @click="handleClose">
<IonIcon :icon="close" />
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent class="qr-scanner-content">
<div v-if="!isSupported" class="flex flex-col items-center justify-center h-full p-6 text-center">
<IonIcon :icon="qrCodeOutline" size="large" class="text-gray-400 mb-4" />
<h3 class="text-lg font-semibold mb-2">
不支持的平台
</h3>
<p class="text-gray-600">
二维码扫描仅在移动设备上支持
</p>
</div>
<div v-else-if="isScanning" class="flex flex-col h-full">
<!-- 扫描指示 -->
<div class="flex flex-col items-center justify-center flex-1 p-6 text-center text-white">
<div class="relative mb-8">
<div class="w-64 h-64 border-2 border-white border-dashed rounded-lg animate-pulse" />
<div class="absolute inset-0 flex items-center justify-center">
<IonIcon :icon="qrCodeOutline" size="large" />
</div>
</div>
<h3 class="text-lg font-semibold mb-2">
{{ title }}
</h3>
<p class="text-sm opacity-80">
{{ subtitle }}
</p>
</div>
<!-- 控制按钮 -->
<div class="p-6 bg-white">
<IonButton
expand="block"
fill="outline"
@click="handleClose"
>
取消扫描
</IonButton>
</div>
</div>
<div v-else class="flex flex-col items-center justify-center h-full p-6 text-center">
<IonIcon :icon="qrCodeOutline" size="large" class="text-primary mb-4" />
<h3 class="text-lg font-semibold mb-2">
准备扫描
</h3>
<p class="text-gray-600 mb-6">
点击开始按钮开始扫描二维码
</p>
<IonButton expand="block" @click="handleStartScan">
开始扫描
</IonButton>
</div>
</IonContent>
</IonModal>
</template>
<style scoped>
.qr-scanner-content {
--background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
/* 当扫描时,隐藏背景以显示相机预览 */
.qr-scanner-content.scanning {
--background: transparent;
}
</style>