import { useEffect, useRef } from 'react'; import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformProfileModalShell } from './PlatformProfileModalShell'; const PROFILE_QR_SCAN_INTERVAL_MS = 360; type BarcodeDetectorLike = { detect: (source: CanvasImageSource) => Promise>; }; type BarcodeDetectorConstructorLike = new (options?: { formats?: string[]; }) => BarcodeDetectorLike; export type PlatformProfileQrScannerModalProps = { error: string | null; result: string | null; onClose: () => void; onError: (message: string) => void; onResult: (value: string) => void; }; function getBarcodeDetectorConstructor(): BarcodeDetectorConstructorLike | null { const maybeDetector = ( globalThis as unknown as { BarcodeDetector?: BarcodeDetectorConstructorLike; } ).BarcodeDetector; return typeof maybeDetector === 'function' ? maybeDetector : null; } /** * 个人中心共享扫码弹层。 * 保持首页现有扫码语义:申请摄像头、轮询识别、关闭时释放视频流。 */ export function PlatformProfileQrScannerModal({ error, result, onClose, onError, onResult, }: PlatformProfileQrScannerModalProps) { const videoRef = useRef(null); const streamRef = useRef(null); useEffect(() => { const videoElement = videoRef.current; if (!videoElement) { return; } let isMounted = true; let scanTimer: number | null = null; const detectorCtor = getBarcodeDetectorConstructor(); const detector = detectorCtor ? new detectorCtor({ formats: ['qr_code'] }) : null; const clearScanTimer = () => { if (scanTimer !== null) { window.clearTimeout(scanTimer); scanTimer = null; } }; const stopCamera = () => { const stream = streamRef.current; streamRef.current = null; if (stream) { stream.getTracks().forEach((track) => track.stop()); } videoElement.srcObject = null; }; const scanVideo = async () => { if (!isMounted || !detector || videoElement.readyState < 2) { if (isMounted && detector) { scanTimer = window.setTimeout(scanVideo, PROFILE_QR_SCAN_INTERVAL_MS); } return; } try { const codes = await detector.detect(videoElement); const rawValue = codes[0]?.rawValue?.trim(); if (rawValue) { clearScanTimer(); stopCamera(); onResult(rawValue); return; } } catch { onError('扫码识别失败,请调整二维码位置'); } if (isMounted) { scanTimer = window.setTimeout(scanVideo, PROFILE_QR_SCAN_INTERVAL_MS); } }; const startCamera = async () => { if ( typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia ) { onError('当前浏览器不支持摄像头扫码'); return; } try { const stream = await navigator.mediaDevices.getUserMedia({ audio: false, video: { facingMode: { ideal: 'environment' } }, }); if (!isMounted) { stream.getTracks().forEach((track) => track.stop()); return; } streamRef.current?.getTracks().forEach((track) => track.stop()); streamRef.current = stream; videoElement.srcObject = stream; await videoElement.play(); if (!detector) { onError('当前浏览器暂不支持二维码识别'); return; } scanTimer = window.setTimeout(scanVideo, PROFILE_QR_SCAN_INTERVAL_MS); } catch { onError('无法打开摄像头,请检查权限'); } }; void startCamera(); return () => { isMounted = false; clearScanTimer(); stopCamera(); }; }, [onError, onResult]); return (
扫码
{result ? ( 已识别:{result} ) : error ? ( {error} ) : null}
); }