From 8decc5bf1de335c43c04fd96208ae58297a46d62 Mon Sep 17 00:00:00 2001 From: star7th Date: Wed, 16 Apr 2025 22:47:19 +0800 Subject: [PATCH] update --- src/app/tools/base64_to_image/page.tsx | 577 ++++++++++++++++++ src/config/i18n/en.ts | 3 +- src/config/i18n/tools/base64_to_image/en.ts | 55 ++ .../i18n/tools/base64_to_image/index.ts | 9 + src/config/i18n/tools/base64_to_image/zh.ts | 55 ++ src/config/i18n/tools/index.ts | 4 +- src/config/i18n/zh.ts | 3 +- src/config/tools.ts | 6 + 8 files changed, 709 insertions(+), 3 deletions(-) create mode 100644 src/app/tools/base64_to_image/page.tsx create mode 100644 src/config/i18n/tools/base64_to_image/en.ts create mode 100644 src/config/i18n/tools/base64_to_image/index.ts create mode 100644 src/config/i18n/tools/base64_to_image/zh.ts diff --git a/src/app/tools/base64_to_image/page.tsx b/src/app/tools/base64_to_image/page.tsx new file mode 100644 index 0000000..c7fab45 --- /dev/null +++ b/src/app/tools/base64_to_image/page.tsx @@ -0,0 +1,577 @@ +'use client'; + +import React, { useState, useEffect, useRef, ChangeEvent } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faDownload, faTrash, faImage, faCopy, faCheck, faExchangeAlt, faUpload } from '@fortawesome/free-solid-svg-icons'; +import ToolHeader from '@/components/ToolHeader'; +import { useLanguage } from '@/context/LanguageContext'; +import tools from '@/config/tools'; +import BackToTop from '@/components/BackToTop'; + +// 添加CSS变量样式 +const styles = { + container: "min-h-screen flex flex-col max-w-[1440px] mx-auto p-4 md:p-6", + card: "card p-6 mb-6", + inputLabel: "block text-secondary text-sm font-bold mb-2", + heading: "text-lg font-semibold mb-2 text-primary", + textArea: "search-input w-full h-56 font-mono text-sm", + switchBtn: "btn-secondary flex items-center justify-center my-4 mx-auto", + imageContainer: "bg-block rounded-lg p-4 min-h-64 flex items-center justify-center border border-purple-glow/15 mt-4", + image: "max-w-full max-h-96 object-contain", + placeholder: "text-tertiary", + actionBtn: "w-full", + actionBtnDisabled: "btn-secondary opacity-50 cursor-not-allowed w-full", + errorMsg: "mt-2 text-red-500 text-sm", + successMsg: "mt-2 text-green-500 text-sm", + copyBtn: "btn-secondary text-sm py-1 px-3 flex items-center", + fileUploadBtn: "btn-primary flex items-center justify-center cursor-pointer", + fileName: "ml-3 text-secondary text-sm", + tabs: "flex mb-6 border-b border-purple-glow/20", + tab: "py-2 px-4 cursor-pointer text-secondary border-b-2 border-transparent", + activeTab: "py-2 px-4 text-primary border-b-2 border-primary font-medium", +}; + +// 转换模式类型 +type ConversionMode = 'base64_to_image' | 'image_to_base64'; + +export default function Base64ImageConverter() { + const { t } = useLanguage(); + + // 从工具配置中获取当前工具图标 + const toolIcon = tools.find(tool => tool.code === 'base64_to_image')?.icon || faImage; + + // 状态管理 + const [conversionMode, setConversionMode] = useState('base64_to_image'); + + // Base64转图片状态 + const [base64Input, setBase64Input] = useState(''); + const [imageOutput, setImageOutput] = useState(null); + const [error, setError] = useState(null); + const [mimeType, setMimeType] = useState(''); + const [fileName, setFileName] = useState('image'); + const [copied, setCopied] = useState(false); + + // 图片转Base64状态 + const [uploadedFile, setUploadedFile] = useState(null); + const [uploadedImage, setUploadedImage] = useState(null); + const [base64Output, setBase64Output] = useState(''); + const [rawBase64Output, setRawBase64Output] = useState(''); + const [outputWithPrefix, setOutputWithPrefix] = useState(true); + const [uploadError, setUploadError] = useState(null); + const [uploadSuccess, setUploadSuccess] = useState(null); + const [base64Copied, setBase64Copied] = useState(false); + + const fileInputRef = useRef(null); + + // 当Base64输入变化时处理转换(Base64转图片) + useEffect(() => { + if (conversionMode !== 'base64_to_image' || !base64Input.trim()) { + setImageOutput(null); + setError(null); + setMimeType(''); + return; + } + + try { + // 清除可能的数据URL前缀 + const processedInput = base64Input.trim(); + + // 检查是否已经是完整的数据URL + if (processedInput.startsWith('data:')) { + // 从数据URL中提取MIME类型 + const mimeMatch = processedInput.match(/^data:([^;]+);base64,/); + if (mimeMatch) { + setMimeType(mimeMatch[1]); + // 使用完整的数据URL + setImageOutput(processedInput); + setError(null); + + // 根据MIME类型设置合适的文件扩展名 + updateFileNameFromMimeType(mimeMatch[1]); + } else { + throw new Error(t('tools.base64_to_image.invalid_data_url')); + } + } else { + // 假设这是原始Base64,需要添加数据URL前缀 + // 尝试推断MIME类型或使用默认值 + const inferredMimeType = inferMimeTypeFromBase64(processedInput); + setMimeType(inferredMimeType); + + // 创建完整的数据URL + const dataUrl = `data:${inferredMimeType};base64,${processedInput}`; + setImageOutput(dataUrl); + setError(null); + + // 根据MIME类型设置合适的文件扩展名 + updateFileNameFromMimeType(inferredMimeType); + } + } catch (err) { + console.error('Error processing base64:', err); + setError((err as Error).message || t('tools.base64_to_image.processing_error')); + setImageOutput(null); + } + }, [base64Input, conversionMode, t]); + + // 从Base64数据推断MIME类型的简单逻辑 + const inferMimeTypeFromBase64 = (base64String: string): string => { + // 默认为PNG + let mimeType = 'image/png'; + + // 检查前几个字符以尝试推断类型 + // 注意:这是一个非常简化的版本,实际生产中可能需要更复杂的逻辑 + const firstChars = base64String.substring(0, 4); + + if (firstChars.startsWith('/9j/')) { + mimeType = 'image/jpeg'; + } else if (firstChars.startsWith('iVBO') || firstChars.startsWith('IVBO')) { + mimeType = 'image/png'; + } else if (firstChars.startsWith('R0lG')) { + mimeType = 'image/gif'; + } else if (firstChars.startsWith('UE5H')) { + mimeType = 'image/png'; + } else if (firstChars.startsWith('Qk0=')) { + mimeType = 'image/bmp'; + } else if (firstChars.startsWith('PHN2')) { + mimeType = 'image/svg+xml'; + } else if (firstChars.startsWith('AAAA')) { + mimeType = 'image/webp'; + } + + return mimeType; + }; + + // 根据MIME类型更新文件名的扩展名 + const updateFileNameFromMimeType = (mime: string) => { + let extension = 'png'; // 默认扩展名 + + // 根据MIME类型设置扩展名 + if (mime === 'image/jpeg' || mime === 'image/jpg') { + extension = 'jpg'; + } else if (mime === 'image/png') { + extension = 'png'; + } else if (mime === 'image/gif') { + extension = 'gif'; + } else if (mime === 'image/bmp') { + extension = 'bmp'; + } else if (mime === 'image/svg+xml') { + extension = 'svg'; + } else if (mime === 'image/webp') { + extension = 'webp'; + } + + setFileName(`image.${extension}`); + }; + + // 清空输入和输出(Base64转图片) + const clearBase64ToImage = () => { + setBase64Input(''); + setImageOutput(null); + setError(null); + setMimeType(''); + setFileName('image'); + }; + + // 清空输入和输出(图片转Base64) + const clearImageToBase64 = () => { + setUploadedFile(null); + setUploadedImage(null); + setBase64Output(''); + setRawBase64Output(''); + setUploadError(null); + setUploadSuccess(null); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + // 下载图片 + const downloadImage = () => { + if (!imageOutput) return; + + const link = document.createElement('a'); + link.href = imageOutput; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + // 加载示例Base64 + const loadExample = () => { + // 一个简单的彩色渐变PNG图像的Base64 + const exampleBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TtSIVByuIOGSoThZERRylikWwUNoKrTqYXPohNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APE1cVJ0UVK/F9SaBHjwXE/3t173L0DhGaVqWbPOKBqlpFOxMVcflUMvCKIEMKIISgxU5+TZRm4jq97+Ph6F+NZ3uf+HANKwWSATySeY7phEW8QT29aOud94ggrSQrxOfGYQRckfuS67PIb55LDAs8MGZnUPHGEWCx1sdzFrGKoxFPEUUXVKN+fc1nhvMVZrdZZ+578hcGCtpLhOs1RJLCEJFIQIaOOCqqwEKNVI8VEmvbjHv4Rx58il0yuChg5FlCDCsnxg//B727N0uSEmxROAH0vtv0xAoR2gVbDtr+Pbbt1AgSegSut499oAjOfpDc6WuwIGNwGLq47mrIHXO4AQ0+GbMqO5KcpFIvA+xl9Uw4YuAUCa25vzX2cPgBpamX5Nxg4AiJlyl73eHdPd2//nmn39wNZjHKYJHtTZAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+cMEwwTIg9nvK0AAACbSURBVHja7dAxAQAgDMAwwL/n8SSBJ2uGLdite3YEhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEIYQhhCGEH8tC+O6BRrHuUAeAAAAAElFTkSuQmCC'; + + setBase64Input(exampleBase64); + }; + + // 复制现有Base64到剪贴板(Base64转图片) + const copyBase64 = () => { + if (!base64Input) return; + + navigator.clipboard.writeText(base64Input) + .then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }) + .catch(err => { + console.error('Failed to copy:', err); + setError(t('tools.base64_to_image.copy_failed')); + }); + }; + + // 复制生成的Base64到剪贴板(图片转Base64) + const copyGeneratedBase64 = () => { + if (!base64Output) return; + + navigator.clipboard.writeText(base64Output) + .then(() => { + setBase64Copied(true); + setTimeout(() => setBase64Copied(false), 2000); + }) + .catch(err => { + console.error('Failed to copy:', err); + setUploadError(t('tools.base64_to_image.copy_failed')); + }); + }; + + // 切换转换模式 + const toggleConversionMode = () => { + setConversionMode(prevMode => + prevMode === 'base64_to_image' ? 'image_to_base64' : 'base64_to_image' + ); + + // 清空各自的状态 + clearBase64ToImage(); + clearImageToBase64(); + }; + + // 处理文件上传(图片转Base64) + const handleFileChange = (event: ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + // 检查文件类型 + if (!file.type.startsWith('image/')) { + setUploadError(t('tools.base64_to_image.invalid_image_file')); + return; + } + + // 检查文件大小(限制为10MB) + if (file.size > 10 * 1024 * 1024) { + setUploadError(t('tools.base64_to_image.file_too_large')); + return; + } + + setUploadedFile(file); + setUploadError(null); + + // 读取并显示图片预览 + const reader = new FileReader(); + reader.onload = (e) => { + const result = e.target?.result as string; + setUploadedImage(result); + + // 提取Base64部分 + const base64WithPrefix = result; + const base64Raw = result.split(',')[1]; + + setRawBase64Output(base64Raw); + setBase64Output(outputWithPrefix ? base64WithPrefix : base64Raw); + + setUploadSuccess(t('tools.base64_to_image.conversion_success')); + }; + reader.onerror = () => { + setUploadError(t('tools.base64_to_image.file_reading_error')); + }; + reader.readAsDataURL(file); + }; + + // 切换输出格式(是否包含前缀) + const toggleOutputFormat = () => { + setOutputWithPrefix(!outputWithPrefix); + setBase64Output(outputWithPrefix ? rawBase64Output : (uploadedImage || '')); + }; + + // 保存Base64内容为文本文件 + const saveBase64AsTextFile = () => { + if (!base64Output) return; + + const blob = new Blob([base64Output], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = uploadedFile ? `${uploadedFile.name}.txt` : 'base64_data.txt'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }; + + return ( +
+ {/* 工具标题 */} + + + {/* 切换按钮 */} + + + {/* 选项卡 */} +
+
setConversionMode('base64_to_image')} + > + {t('tools.base64_to_image.base64_to_image_tab')} +
+
setConversionMode('image_to_base64')} + > + {t('tools.base64_to_image.image_to_base64_tab')} +
+
+ + {/* Base64转图片 */} + {conversionMode === 'base64_to_image' && ( +
+ {/* 输入区域 */} +
+
+

{t('tools.base64_to_image.input_title')}

+ +