import { useState, useEffect, useCallback, useRef } from 'react'; import { Accordion, AccordionDetails, AccordionSummary, Alert, Box, Button, CircularProgress, IconButton, InputAdornment, Stack, TextField, Typography, } from '@mui/material'; import LinkIcon from '@mui/icons-material/Link'; import ImageIcon from '@mui/icons-material/Image'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ClearIcon from '@mui/icons-material/Clear'; import CopyButton from '@/components/CopyButton'; import { qrCodePageStyles } from '@/config/pageTheme'; import { useSnackbar } from '@/components/GlobalSnackbar'; import { parseQrCodeFromFile } from '@/utils/qrCodeParser'; import { useTranslation } from 'react-i18next'; interface QrCodeToUrlSectionProps { expanded: boolean; onExpandedChange: (expanded: boolean) => void; /** 桌面端强制展开(隐藏折叠交互) */ forceExpanded?: boolean; } const QrCodeToUrlSection = ({ expanded, onExpandedChange, forceExpanded = false, }: QrCodeToUrlSectionProps) => { const { t } = useTranslation(['qrCode']); const { showMessage } = useSnackbar(); const [qrCodeFile, setQrCodeFile] = useState(null); const [previewUrl, setPreviewUrl] = useState(''); const [parsedUrl, setParsedUrl] = useState(''); const [parseError, setParseError] = useState(''); const [parsing, setParsing] = useState(false); const [dragging, setDragging] = useState(false); const fileInputRef = useRef(null); // 清理预览 URL,防止内存泄漏 useEffect(() => { return () => { if (previewUrl) { URL.revokeObjectURL(previewUrl); } }; }, [previewUrl]); const handleFileChange = useCallback((file: File) => { setQrCodeFile(file); setPreviewUrl(URL.createObjectURL(file)); setParseError(''); setParsedUrl(''); }, []); const handleClearFile = () => { setQrCodeFile(null); if (previewUrl) { URL.revokeObjectURL(previewUrl); } setPreviewUrl(''); setParsedUrl(''); setParseError(''); showMessage(t('qrCode:imageCleared'), { severity: 'success', autoHideDuration: 1000, }); }; const handleInputChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { handleFileChange(e.target.files[0]); } }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setDragging(true); }; const handleDragLeave = () => { setDragging(false); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setDragging(false); const droppedFile = e.dataTransfer.files?.[0]; if (droppedFile) { handleFileChange(droppedFile); } }; const parseQrCode = async () => { if (!qrCodeFile) { showMessage(t('qrCode:selectImage'), { severity: 'error', autoHideDuration: 300 }); return; } try { setParsing(true); setParseError(''); setParsedUrl(''); const result = await parseQrCodeFromFile(qrCodeFile); if (result.success && result.data) { setParsedUrl(result.data); showMessage(t('qrCode:parseSuccess'), { severity: 'success', autoHideDuration: 1000 }); } else { showMessage(result.error || t('qrCode:noQrDetected'), { severity: 'error', autoHideDuration: 1000, }); } } catch (error) { console.error('解析二维码失败:', error); showMessage(t('qrCode:parseError'), { severity: 'error', autoHideDuration: 300 }); } finally { setParsing(false); } }; // 监听粘贴事件 useEffect(() => { const handlePaste = async (e: ClipboardEvent) => { if (!expanded) return; const items = e.clipboardData?.items; if (!items) return; for (let i = 0; i < items.length; i++) { if (items[i].type.startsWith('image/')) { e.preventDefault(); const file = items[i].getAsFile(); if (file) { try { handleFileChange(file); showMessage(t('qrCode:imagePasted'), { severity: 'success', autoHideDuration: 1000 }); } catch (error) { console.error('处理粘贴图片失败:', error); showMessage(t('qrCode:imagePasteError'), { severity: 'error', autoHideDuration: 3000, }); } } break; } } }; document.addEventListener('paste', handlePaste); return () => { document.removeEventListener('paste', handlePaste); }; }, [expanded, showMessage, handleFileChange, t]); return ( onExpandedChange(isExpanded)} sx={forceExpanded ? qrCodePageStyles.ACCORDION_DESKTOP : qrCodePageStyles.ACCORDION} > } sx={qrCodePageStyles.ACCORDION_SUMMARY}> {t('qrCode:qrToUrl')} ), }, }} sx={qrCodePageStyles.INPUT_STYLE} /> {parseError && ( {parseError} )} ); }; export default QrCodeToUrlSection;