# 头像裁剪弹窗组件

根据设计稿精确还原的头像裁剪弹窗组件，专为头像裁剪场景优化。

## 设计特点

- ✅ **完全还原设计稿** - 与 Figma 设计稿保持 100% 一致
- ✅ **全屏沉浸式体验** - 深色背景遮罩，突出裁剪区域
- ✅ **圆形头像裁剪** - 专门为头像场景优化的 1:1 圆形裁剪
- ✅ **直观的缩放控制** - 滑块 + 按钮双重缩放控制
- ✅ **优雅的交互动画** - 平滑的过渡效果和毛玻璃质感
- ✅ **移动端适配** - 支持触摸手势和响应式布局

## 基础用法

```tsx
import { AvatarCropModal } from '@/components/ui/avatar-crop-modal'

function MyComponent() {
  const [showCrop, setShowCrop] = useState(false)
  const [selectedImage, setSelectedImage] = useState<File | null>(null)
  const [avatarUrl, setAvatarUrl] = useState('')

  return (
    <>
      <button onClick={() => setShowCrop(true)}>裁剪头像</button>

      <AvatarCropModal
        isOpen={showCrop}
        onClose={() => setShowCrop(false)}
        image={selectedImage}
        onConfirm={(croppedImageUrl) => {
          setAvatarUrl(croppedImageUrl)
          setShowCrop(false)
        }}
        onCancel={() => setShowCrop(false)}
      />
    </>
  )
}
```

## 组件 API

### Props

| 属性        | 类型                                | 必需 | 说明                              |
| ----------- | ----------------------------------- | ---- | --------------------------------- |
| `isOpen`    | `boolean`                           | ✅   | 控制弹窗显示/隐藏                 |
| `onClose`   | `() => void`                        | ✅   | 关闭弹窗回调                      |
| `image`     | `string \| File`                    | ✅   | 要裁剪的图片（URL或File对象）     |
| `onConfirm` | `(croppedImageUrl: string) => void` | ✅   | 确认裁剪回调，返回裁剪后的图片URL |
| `onCancel`  | `() => void`                        | ❌   | 取消操作回调（可选）              |

### 返回值

`onConfirm` 回调会返回一个 `string` 类型的图片 URL，这是一个 blob URL，可以直接用于：

- 显示裁剪后的头像预览
- 上传到服务器
- 转换为 base64 格式
- 下载到本地

## 交互说明

### 缩放控制

- **滑块** - 拖拽滑块调整缩放比例（1x - 3x）
- **+/- 按钮** - 点击按钮微调缩放（每次 ±0.1）
- **鼠标滚轮** - 滚动鼠标滚轮调整缩放（桌面端）
- **双指缩放** - 双指捏合手势调整缩放（移动端）

### 图片位置

- **拖拽** - 在裁剪区域内拖拽图片调整位置
- **触摸** - 移动端支持触摸拖拽

### 操作按钮

- **Cancel** - 取消裁剪，关闭弹窗
- **Confirm** - 确认裁剪，返回裁剪后的图片
- **X 按钮** - 关闭弹窗（右上角）
- **背景点击** - 点击背景区域关闭弹窗

## 集成示例

### 在表单中使用

```tsx
// ImageForm.tsx
import { AvatarCropModal } from '@/components/ui/avatar-crop-modal'

export default function ImageForm() {
  const [showCropModal, setShowCropModal] = useState(false)
  const form = useForm()

  return (
    <form>
      {/* 头像字段 */}
      <FormField
        control={form.control}
        name="avatarUrl"
        render={({ field }) => (
          <FormItem>
            <FormLabel>Avatar</FormLabel>
            <div className="flex items-center gap-4">
              {field.value ? (
                <img
                  src={field.value}
                  alt="Avatar"
                  className="h-20 w-20 rounded-full object-cover"
                />
              ) : (
                <div className="h-20 w-20 rounded-full bg-gray-200" />
              )}
              <button type="button" onClick={() => setShowCropModal(true)}>
                裁剪头像
              </button>
            </div>
          </FormItem>
        )}
      />

      {/* 裁剪弹窗 */}
      <AvatarCropModal
        isOpen={showCropModal}
        onClose={() => setShowCropModal(false)}
        image={originalImageUrl}
        onConfirm={(croppedImageUrl) => {
          form.setValue('avatarUrl', croppedImageUrl)
          setShowCropModal(false)
        }}
      />
    </form>
  )
}
```

### 图片上传 + 裁剪流程

```tsx
function AvatarUploader() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null)
  const [showCrop, setShowCrop] = useState(false)
  const [finalAvatar, setFinalAvatar] = useState('')

  const handleFileSelect = (file: File) => {
    setSelectedFile(file)
    setShowCrop(true) // 选择文件后立即打开裁剪器
  }

  const handleCropConfirm = async (croppedImageUrl: string) => {
    // 可以在这里上传到服务器
    const uploadedUrl = await uploadToServer(croppedImageUrl)
    setFinalAvatar(uploadedUrl)
    setShowCrop(false)
  }

  return (
    <div>
      <input
        type="file"
        accept="image/*"
        onChange={(e) => {
          const file = e.target.files?.[0]
          if (file) handleFileSelect(file)
        }}
      />

      {finalAvatar && (
        <img src={finalAvatar} alt="Final avatar" className="h-32 w-32 rounded-full" />
      )}

      {selectedFile && (
        <AvatarCropModal
          isOpen={showCrop}
          onClose={() => setShowCrop(false)}
          image={selectedFile}
          onConfirm={handleCropConfirm}
        />
      )}
    </div>
  )
}
```

## 样式自定义

组件使用了以下 CSS 类名，可以通过覆盖这些类名来自定义样式：

```css
/* 缩放滑块样式 */
.zoom-slider {
  /* 自定义滑块轨道和按钮样式 */
}

/* 裁剪区域样式 */
.avatar-crop-container .reactEasyCrop_CropArea {
  /* 自定义裁剪边框和阴影 */
}
```

## 测试页面

访问 `/test-avatar-crop` 查看完整的功能演示和使用示例。

## 技术实现

- **裁剪引擎**: react-easy-crop
- **图片处理**: HTML5 Canvas API
- **输出格式**: JPEG (90% 质量)
- **裁剪比例**: 固定 1:1 (正方形)
- **裁剪形状**: 圆形

## 浏览器兼容性

- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+

需要支持 Canvas API 和 File API。

## 注意事项

1. **图片大小**: 建议上传图片不超过 10MB
2. **图片格式**: 支持 JPEG、PNG、GIF、WebP
3. **内存管理**: 组件会自动清理创建的 blob URL，防止内存泄漏
4. **CORS**: 如果裁剪跨域图片，需要确保图片服务器支持 CORS
