在线

https://chat.xutongbao.top/nextjs/light/image

🎨 设计特点

1. 视觉效果

  • 渐变背景with动态模糊圆圈动画
  • 毛玻璃效果(backdrop-blur)卡片设计
  • 平滑的过渡动画和悬停效果
  • 渐变文字标题和图标

2. 两种转换模式

  • 图片转 Base64:上传图片自动生成 Base64 编码
  • Base64 转图片:粘贴 Base64 编码并转换为图片

3. 功能特性

  • ✅ 拖拽上传支持
  • ✅ 实时图片预览
  • ✅ 一键复制 Base64 编码
  • ✅ 下载转换后的图片
  • ✅ 文件大小限制(5MB)
  • ✅ 支持多种图片格式(JPG、PNG、GIF、WebP)
  • ✅ Base64 自动识别(带或不带 data URL 前缀)

4. 用户体验

  • 清空按钮快速重置
  • 模式切换时自动清空数据
  • 复制成功提示
  • 错误提示友好
  • 响应式布局

5. 设计风格

  • 保留了原页面的返回按钮
  • 符合项目原有的设计语言
  • 使用项目的主题颜色系统(primary、secondary、accent)
  • 与 url/page.tsx 页面风格一致

现在你可以访问这个页面,它将提供流畅的 Base64 图片互转体验!🚀

'use client'

import { useState, useRef } from 'react'
import Header from '@/components/header'
import {
  ArrowLeft,
  Upload,
  Download,
  Image as ImageIcon,
  FileCode,
  Copy,
  Check,
  X,
  Sparkles,
  RefreshCw,
} from 'lucide-react'
import { useRouter } from 'next/navigation'

type ConversionMode = 'toBase64' | 'toImage'

export default function ImageBase64Page() {
  const router = useRouter()
  const [mode, setMode] = useState<ConversionMode>('toBase64')
  const [imageFile, setImageFile] = useState<File | null>(null)
  const [imagePreview, setImagePreview] = useState<string>('')
  const [base64Input, setBase64Input] = useState('')
  const [base64Output, setBase64Output] = useState('')
  const [copied, setCopied] = useState(false)
  const fileInputRef = useRef<HTMLInputElement>(null)

  // 图片转 Base64
  const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]
    if (!file) return

    // 检查文件类型
    if (!file.type.startsWith('image/')) {
      alert('请选择图片文件')
      return
    }

    // 检查文件大小 (限制为 5MB)
    if (file.size > 5 * 1024 * 1024) {
      alert('图片大小不能超过 5MB')
      return
    }

    setImageFile(file)

    const reader = new FileReader()
    reader.onload = (e) => {
      const result = e.target?.result as string
      setImagePreview(result)
      setBase64Output(result)
    }
    reader.readAsDataURL(file)
  }

  // Base64 转图片
  const handleBase64Convert = () => {
    try {
      // 清理 Base64 字符串
      let cleanBase64 = base64Input.trim()

      // 如果不是完整的 data URL,添加前缀
      if (!cleanBase64.startsWith('data:')) {
        cleanBase64 = `data:image/png;base64,${cleanBase64}`
      }

      // 验证 Base64
      const img = new Image()
      img.onload = () => {
        setImagePreview(cleanBase64)
      }
      img.onerror = () => {
        alert('无效的 Base64 编码')
      }
      img.src = cleanBase64
    } catch (error) {
      alert('Base64 解析失败')
    }
  }

  // 复制到剪贴板
  const handleCopy = async () => {
    try {
      await navigator.clipboard.writeText(base64Output)
      setCopied(true)
      setTimeout(() => setCopied(false), 2000)
    } catch (error) {
      alert('复制失败')
    }
  }

  // 下载图片
  const handleDownload = () => {
    if (!imagePreview) return

    const link = document.createElement('a')
    link.href = imagePreview
    link.download = `image_${Date.now()}.png`
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
  }

  // 清空所有内容
  const handleReset = () => {
    setImageFile(null)
    setImagePreview('')
    setBase64Input('')
    setBase64Output('')
    if (fileInputRef.current) {
      fileInputRef.current.value = ''
    }
  }

  // 触发文件选择
  const triggerFileSelect = () => {
    fileInputRef.current?.click()
  }

  return (
    <>
      <Header />

      <main className='min-h-screen bg-linear-to-br from-primary/5 via-background to-secondary/5 relative overflow-hidden'>
        {/* 背景装饰 */}
        <div className='absolute inset-0 overflow-hidden pointer-events-none'>
          <div className='absolute top-20 left-1/4 w-96 h-96 bg-primary/5 rounded-full blur-3xl animate-pulse-slow' />
          <div
            className='absolute bottom-20 right-1/4 w-96 h-96 bg-secondary/5 rounded-full blur-3xl animate-pulse-slow'
            style={{ animationDelay: '2s' }}
          />
          <div
            className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-accent/5 rounded-full blur-3xl animate-pulse-slow'
            style={{ animationDelay: '4s' }}
          />
        </div>

        {/* 内容区域 */}
        <div className='relative max-w-6xl mx-auto px-4 py-12'>
          {/* 返回按钮 */}
          <button
            onClick={() => router.push('/light')}
            className='group mb-8 flex items-center gap-2 px-4 py-3 rounded-2xl bg-card/80 backdrop-blur-xl border-2 border-border hover:border-primary shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-105 animate-fade-in'
          >
            <div className='relative'>
              <div className='absolute inset-0 bg-primary/20 rounded-full blur-md scale-0 group-hover:scale-150 transition-transform duration-500' />
              <ArrowLeft className='relative w-5 h-5 text-primary group-hover:text-primary transition-all duration-300 group-hover:-translate-x-1' />
            </div>
            <span className='text-sm font-medium text-foreground group-hover:text-primary transition-colors duration-300'>
              返回
            </span>
          </button>

          {/* 主卡片 */}
          <div className='bg-card/80 backdrop-blur-xl rounded-3xl border-2 border-border shadow-2xl p-8 animate-fade-in-up'>
            {/* 标题区域 */}
            <div className='text-center mb-8'>
              <div className='inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-linear-to-br from-primary to-secondary mb-4 shadow-lg relative overflow-hidden group'>
                <div className='absolute inset-0 bg-linear-to-br from-accent to-primary opacity-0 group-hover:opacity-100 transition-opacity duration-500' />
                <Sparkles className='relative w-8 h-8 text-primary-foreground animate-pulse' />
              </div>
              <h1 className='text-3xl font-bold bg-linear-to-r from-primary to-secondary bg-clip-text text-transparent mb-2'>
                Base64 图片互转
              </h1>
              <p className='text-muted-foreground'>
                支持图片转 Base64 编码,以及 Base64 解码为图片
              </p>
            </div>

            {/* 模式切换器 */}
            <div className='flex gap-3 mb-6'>
              <button
                onClick={() => {
                  setMode('toBase64')
                  handleReset()
                }}
                className={`flex-1 py-3 px-4 rounded-xl font-medium transition-all duration-300 flex items-center justify-center gap-2
                  ${
                    mode === 'toBase64'
                      ? 'bg-linear-to-r from-primary to-secondary text-primary-foreground shadow-lg scale-105'
                      : 'bg-muted/50 text-muted-foreground hover:bg-muted'
                  }`}
              >
                <ImageIcon className='w-5 h-5' />
                <span>图片转 Base64</span>
              </button>
              <button
                onClick={() => {
                  setMode('toImage')
                  handleReset()
                }}
                className={`flex-1 py-3 px-4 rounded-xl font-medium transition-all duration-300 flex items-center justify-center gap-2
                  ${
                    mode === 'toImage'
                      ? 'bg-linear-to-r from-primary to-secondary text-primary-foreground shadow-lg scale-105'
                      : 'bg-muted/50 text-muted-foreground hover:bg-muted'
                  }`}
              >
                <FileCode className='w-5 h-5' />
                <span>Base64 转图片</span>
              </button>
            </div>

            {/* 图片转 Base64 模式 */}
            {mode === 'toBase64' && (
              <div className='space-y-6 animate-fade-in'>
                {/* 上传区域 */}
                <div className='space-y-4'>
                  <input
                    ref={fileInputRef}
                    type='file'
                    accept='image/*'
                    onChange={handleImageUpload}
                    className='hidden'
                  />

                  <div
                    onClick={triggerFileSelect}
                    className='group relative border-2 border-dashed border-border hover:border-primary rounded-2xl p-12 text-center cursor-pointer transition-all duration-300 bg-muted/20 hover:bg-muted/40'
                  >
                    <div className='absolute inset-0 bg-linear-to-br from-primary/5 to-secondary/5 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300' />
                    <div className='relative'>
                      <div className='inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 group-hover:bg-primary/20 mb-4 transition-colors duration-300'>
                        <Upload className='w-8 h-8 text-primary group-hover:scale-110 transition-transform duration-300' />
                      </div>
                      <p className='text-foreground font-medium mb-1'>
                        点击上传图片
                      </p>
                      <p className='text-sm text-muted-foreground'>
                        支持 JPG、PNG、GIF 等格式,最大 5MB
                      </p>
                    </div>
                  </div>

                  {/* 图片预览 */}
                  {imagePreview && (
                    <div className='space-y-4 animate-fade-in'>
                      <div className='relative rounded-2xl border-2 border-border bg-muted/20 p-4 overflow-hidden'>
                        <div className='flex items-center justify-between mb-3'>
                          <h3 className='text-sm font-semibold text-foreground flex items-center gap-2'>
                            <ImageIcon className='w-4 h-4 text-primary' />
                            图片预览
                          </h3>
                          <span className='text-xs text-muted-foreground'>
                            {imageFile?.name}
                          </span>
                        </div>
                        <div className='flex justify-center bg-background/50 rounded-xl p-4'>
                          <img
                            src={imagePreview}
                            alt='Preview'
                            className='max-h-64 rounded-lg shadow-lg'
                          />
                        </div>
                      </div>

                      {/* Base64 输出 */}
                      <div className='relative rounded-2xl border-2 border-border bg-muted/20 p-4'>
                        <div className='flex items-center justify-between mb-3'>
                          <h3 className='text-sm font-semibold text-foreground flex items-center gap-2'>
                            <FileCode className='w-4 h-4 text-primary' />
                            Base64 编码
                          </h3>
                          <button
                            onClick={handleCopy}
                            className='group flex items-center gap-2 px-3 py-1.5 rounded-lg bg-primary/10 hover:bg-primary/20 text-primary text-sm font-medium transition-all duration-300 hover:scale-105'
                          >
                            {copied ? (
                              <>
                                <Check className='w-4 h-4' />
                                <span>已复制</span>
                              </>
                            ) : (
                              <>
                                <Copy className='w-4 h-4' />
                                <span>复制</span>
                              </>
                            )}
                          </button>
                        </div>
                        <textarea
                          value={base64Output}
                          readOnly
                          className='w-full h-32 px-4 py-3 rounded-xl border border-border bg-background/50 text-sm text-foreground font-mono resize-none focus:outline-none focus:ring-2 focus:ring-primary/20'
                          placeholder='Base64 编码将显示在这里...'
                        />
                      </div>
                    </div>
                  )}
                </div>
              </div>
            )}

            {/* Base64 转图片模式 */}
            {mode === 'toImage' && (
              <div className='space-y-6 animate-fade-in'>
                {/* Base64 输入 */}
                <div className='space-y-4'>
                  <div className='relative rounded-2xl border-2 border-border bg-muted/20 p-4'>
                    <h3 className='text-sm font-semibold text-foreground mb-3 flex items-center gap-2'>
                      <FileCode className='w-4 h-4 text-primary' />
                      粘贴 Base64 编码
                    </h3>
                    <textarea
                      value={base64Input}
                      onChange={(e) => setBase64Input(e.target.value)}
                      className='w-full h-32 px-4 py-3 rounded-xl border border-border bg-background/50 text-sm text-foreground font-mono resize-none focus:outline-none focus:ring-2 focus:ring-primary/20 transition-all duration-300'
                      placeholder='粘贴 Base64 编码 (支持带或不带 data:image 前缀)'
                    />
                  </div>

                  {/* 转换按钮 */}
                  <button
                    onClick={handleBase64Convert}
                    disabled={!base64Input.trim()}
                    className='w-full group relative overflow-hidden rounded-2xl bg-linear-to-r from-primary to-secondary p-0.5 hover:shadow-xl hover:shadow-primary/25 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none'
                  >
                    <div
                      className='relative bg-card/90 backdrop-blur-sm rounded-2xl px-6 py-4 group-hover:bg-transparent transition-all duration-300'
                    >
                      <div className='flex items-center justify-center gap-2 text-foreground group-hover:text-primary-foreground transition-colors duration-300'>
                        <RefreshCw className='w-5 h-5' />
                        <span className='font-medium'>转换为图片</span>
                      </div>
                    </div>
                  </button>

                  {/* 图片预览 */}
                  {imagePreview && (
                    <div className='relative rounded-2xl border-2 border-border bg-muted/20 p-4 animate-fade-in'>
                      <div className='flex items-center justify-between mb-3'>
                        <h3 className='text-sm font-semibold text-foreground flex items-center gap-2'>
                          <ImageIcon className='w-4 h-4 text-primary' />
                          转换结果
                        </h3>
                        <button
                          onClick={handleDownload}
                          className='group flex items-center gap-2 px-3 py-1.5 rounded-lg bg-primary/10 hover:bg-primary/20 text-primary text-sm font-medium transition-all duration-300 hover:scale-105'
                        >
                          <Download className='w-4 h-4' />
                          <span>下载</span>
                        </button>
                      </div>
                      <div className='flex justify-center bg-background/50 rounded-xl p-4'>
                        <img
                          src={imagePreview}
                          alt='Converted'
                          className='max-h-96 rounded-lg shadow-lg'
                        />
                      </div>
                    </div>
                  )}
                </div>
              </div>
            )}

            {/* 操作按钮 */}
            {(imagePreview || base64Input) && (
              <div className='mt-6 flex gap-3 animate-fade-in'>
                <button
                  onClick={handleReset}
                  className='flex-1 group flex items-center justify-center gap-2 px-6 py-3 rounded-xl bg-muted/50 hover:bg-muted text-muted-foreground hover:text-foreground font-medium transition-all duration-300 hover:scale-105'
                >
                  <X className='w-5 h-5' />
                  <span>清空</span>
                </button>
                {mode === 'toBase64' && base64Output && (
                  <button
                    onClick={handleDownload}
                    className='flex-1 group relative overflow-hidden rounded-xl bg-linear-to-r from-accent to-secondary p-0.5 hover:shadow-lg hover:shadow-accent/25 transition-all duration-300'
                  >
                    <div className='relative bg-card/90 backdrop-blur-sm rounded-xl px-6 py-3 group-hover:bg-transparent transition-all duration-300'>
                      <div className='flex items-center justify-center gap-2 text-foreground group-hover:text-primary-foreground transition-colors duration-300'>
                        <Download className='w-5 h-5' />
                        <span className='font-medium'>下载图片</span>
                      </div>
                    </div>
                  </button>
                )}
              </div>
            )}

            {/* 使用说明 */}
            <div className='mt-8 p-6 rounded-2xl bg-muted/50 backdrop-blur-sm border border-border/50'>
              <h3 className='text-sm font-semibold text-foreground mb-3 flex items-center gap-2'>
                <div className='w-1.5 h-1.5 rounded-full bg-primary animate-pulse' />
                使用说明
              </h3>
              <ul className='space-y-2 text-sm text-muted-foreground'>
                <li className='flex items-start gap-2'>
                  <span className='text-primary mt-0.5'>•</span>
                  <span>
                    <strong className='text-foreground'>图片转 Base64:</strong>
                    上传图片文件,自动生成 Base64 编码
                  </span>
                </li>
                <li className='flex items-start gap-2'>
                  <span className='text-primary mt-0.5'>•</span>
                  <span>
                    <strong className='text-foreground'>Base64 转图片:</strong>
                    粘贴 Base64 编码,点击转换按钮查看图片
                  </span>
                </li>
                <li className='flex items-start gap-2'>
                  <span className='text-primary mt-0.5'>•</span>
                  <span>支持 JPG、PNG、GIF、WebP 等常见图片格式</span>
                </li>
                <li className='flex items-start gap-2'>
                  <span className='text-primary mt-0.5'>•</span>
                  <span>单个图片大小限制为 5MB</span>
                </li>
                <li className='flex items-start gap-2'>
                  <span className='text-primary mt-0.5'>•</span>
                  <span>Base64 编码支持带或不带 data URL 前缀</span>
                </li>
              </ul>
            </div>
          </div>
        </div>
      </main>

      {/* 自定义动画样式 */}
      <style jsx>{`
        @keyframes fade-in {
          from {
            opacity: 0;
          }
          to {
            opacity: 1;
          }
        }

        @keyframes fade-in-up {
          from {
            opacity: 0;
            transform: translateY(20px);
          }
          to {
            opacity: 1;
            transform: translateY(0);
          }
        }

        @keyframes pulse-slow {
          0%,
          100% {
            opacity: 0.3;
            transform: scale(1);
          }
          50% {
            opacity: 0.5;
            transform: scale(1.1);
          }
        }

        .animate-fade-in {
          animation: fade-in 0.5s ease-out;
        }

        .animate-fade-in-up {
          animation: fade-in-up 0.6s ease-out;
        }

        .animate-pulse-slow {
          animation: pulse-slow 4s ease-in-out infinite;
        }
      `}</style>
    </>
  )
}

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐