import { useState, useRef, useEffect } from 'react'; import { Box, Typography, TextField, Button, Stack, Alert, InputAdornment, CircularProgress, Accordion, AccordionSummary, AccordionDetails, } from '@mui/material'; import { Container, alpha } from '@mui/system'; import QrCodeIcon from '@mui/icons-material/QrCode'; import ImageIcon from '@mui/icons-material/Image'; import LinkIcon from '@mui/icons-material/Link'; import DownloadIcon from '@mui/icons-material/Download'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import qrcode from 'qrcode'; import jsQR from 'jsqr'; import GlobalSnackbar, { useSnackbar } from '@/components/GlobalSnackbar'; import CopyButton from '@/components/CopyButton'; import { storageUtil } from '@/utils/chromeStorage'; import { qrCodePageStyles, dashboardPageStyles } from '@/config/pageTheme'; const QrCodePage = () => { const { snackbarProps, showMessage } = useSnackbar({ autoHideDuration: 1500 }); const [isInitialized, setIsInitialized] = useState(false); // URL 转二维码状态 const [urlInput, setUrlInput] = useState(''); const [urlError, setUrlError] = useState(''); const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [generating, setGenerating] = useState(false); // 二维码转 URL 状态 const [qrCodeFile, setQrCodeFile] = useState(null); const [parsedUrl, setParsedUrl] = useState(''); const [parseError, setParseError] = useState(''); const [parsing, setParsing] = useState(false); // 卡片展开状态 const [urlExpanded, setUrlExpanded] = useState(true); const [qrExpanded, setQrExpanded] = useState(false); // 引用 const qrCodeRef = useRef(null); // 从存储加载状态 useEffect(() => { const loadState = async () => { try { const savedUrlExpanded = await storageUtil.get('qrCode/urlExpanded', true); const savedQrExpanded = await storageUtil.get('qrCode/qrExpanded', false); setUrlExpanded(savedUrlExpanded ?? true); setQrExpanded(savedQrExpanded ?? false); } catch (error) { console.error('加载状态失败:', error); } finally { setIsInitialized(true); } }; loadState(); }, []); // 保存状态到存储(仅在初始化完成后保存) useEffect(() => { if (!isInitialized) return; const saveState = async () => { try { await storageUtil.set('qrCode/urlExpanded', urlExpanded); await storageUtil.set('qrCode/qrExpanded', qrExpanded); } catch (error) { console.error('保存状态失败:', error); } }; saveState(); }, [urlExpanded, qrExpanded, isInitialized]); // 初始化未完成时显示加载状态 if (!isInitialized) { return ( ); } // 处理 URL 输入变化 const handleUrlInputChange = (e: React.ChangeEvent) => { setUrlInput(e.target.value); setUrlError(''); }; // 处理文件选择 const handleFileChange = (e: React.ChangeEvent) => { // 只有当用户实际选择了文件时才更新状态 // 如果用户取消选择,保持原有状态不变 if (e.target.files && e.target.files.length > 0) { const file = e.target.files[0]; setQrCodeFile(file); setParseError(''); setParsedUrl(''); } }; // 生成二维码 const generateQrCode = async () => { if (!urlInput) { setUrlError('请输入 URL'); return; } try { setGenerating(true); setUrlError(''); // 验证 URL 格式 let url = urlInput; if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; } // 生成二维码 const dataUrl = await qrcode.toDataURL(url, { width: 200, margin: 2, color: { dark: qrCodePageStyles.black, light: qrCodePageStyles.white, }, }); setQrCodeDataUrl(dataUrl); showMessage('二维码生成成功', { severity: 'success', autoHideDuration: 1000 }); } catch (error) { console.error('生成二维码失败:', error); showMessage('生成二维码失败,请重试', { severity: 'error', autoHideDuration: 300 }); } finally { setGenerating(false); } }; // 解析二维码 const parseQrCode = async () => { if (!qrCodeFile) { showMessage('请选择二维码图片', { severity: 'error', autoHideDuration: 300 }); return; } try { setParsing(true); setParseError(''); setParsedUrl(''); // 读取文件并解析 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('无法创建 canvas 上下文'); } const image = new Image(); image.src = URL.createObjectURL(qrCodeFile); await new Promise((resolve, reject) => { image.onload = () => { canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); resolve(); }; image.onerror = () => reject(new Error('图片加载失败')); }); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const code = jsQR(imageData.data, imageData.width, imageData.height); if (code) { setParsedUrl(code.data); showMessage('二维码解析成功', { severity: 'success', autoHideDuration: 1000 }); } else { showMessage('未检测到二维码', { severity: 'error', autoHideDuration: 1000 }); } } catch (error) { console.error('解析二维码失败:', error); showMessage('解析二维码失败,请重试', { severity: 'error', autoHideDuration: 300 }); } finally { setParsing(false); } }; // 下载二维码 const downloadQrCode = () => { if (!qrCodeDataUrl) return; const link = document.createElement('a'); link.href = qrCodeDataUrl; link.download = 'qrcode.png'; link.click(); showMessage('二维码下载成功', { severity: 'success', autoHideDuration: 300 }); }; // 复制二维码到剪贴板 const copyQrCode = async () => { if (!qrCodeDataUrl) return; try { // 将 data URL 转换为 Blob const response = await fetch(qrCodeDataUrl); const blob = await response.blob(); // 使用 Clipboard API 写入图像 await navigator.clipboard.write([ new ClipboardItem({ 'image/png': blob, }), ]); showMessage('二维码已复制到剪贴板', { severity: 'success', autoHideDuration: 1000 }); } catch (error) { console.error('复制二维码失败:', error); showMessage('复制二维码失败,请重试', { severity: 'error', autoHideDuration: 300 }); } }; return ( {/* Header */} 二维码工具 生成和解析二维码 {/* URL 转二维码 */} setUrlExpanded(isExpanded)} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)', '&:before': { display: 'none' }, }} > } sx={{ borderBottom: 'none' }}> URL 转二维码 {/* 二维码显示区域 */} {qrCodeDataUrl ? ( QR Code ) : ( 二维码将显示在这里 )} {/* 二维码转 URL */} setQrExpanded(isExpanded)} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)', '&:before': { display: 'none' }, }} > } sx={{ borderBottom: 'none' }}> 二维码转 URL {/* 解析结果显示 */} ), }, }} sx={{ '& .MuiOutlinedInput-root': { borderRadius: 3, }, }} /> {parseError && ( {parseError} )} ); }; export default QrCodePage;