# CompressorJS 实战使用文档

基于项目中的 `src/pages/approval/submit.vue` 实例编写。

## 1. 简介

CompressorJS 是一个轻量级的 JavaScript 图像压缩库，基于 Canvas API，依赖少且易用。适用于文件上传前压缩，可显著减少文件大小和上传时间。

## 2. 安装

```bash
pnpm add compressorjs
# 或
npm install compressorjs
```

## 3. 基础使用（基于项目实例）

### 3.1 基本语法

```javascript
import Compressor from 'compressorjs'

new Compressor(file, {
  quality: 0.8,
  maxWidth: 1280,
  maxHeight: 1280,
  success(result) {
    // 压缩成功后的处理
  },
  error(err) {
    // 压缩失败处理
  }
})
```

### 3.2 实际项目代码解析

在 `submit.vue:54-115`，`handleFileUpload` 函数中：

```javascript
const handleFileUpload = async (items: UploaderFileListItem | UploaderFileListItem[]) => {
  const files = Array.isArray(items) ? items : [items]

  const uploadPromises = files.map(async (item) => {
    const file = item.file as File

    // 压缩文件
    let compressedFile = file
    try {
      compressedFile = await new Promise<File>((resolve, reject) => {
        new Compressor(file, {
          quality: 0.8,                    // 压缩质量 0-1 之间
          maxWidth: 1280,                  // 最大宽度
          maxHeight: 1280,                 // 最大高度
          success(result) {
            // 转换为 File 对象，保持原始文件名
            resolve(new File([result], file.name, {
              type: 'image/jpeg',          // 统一输出为 jpeg
              lastModified: Date.now()
            }))
          },
          error(err) {
            reject(err)
          }
        })
      })
    } catch (error) {
      console.error('压缩失败:', error)
      // 压缩失败时使用原文件
    }

    // 继续上传...
  })
}
```

**关键点：**
- 使用 Promise 包装 Compressor 的回调式 API
- 压缩失败时降级使用原文件
- 转换结果为标准 File 对象，便于后续处理

## 4. 配置参数详解

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `quality` | number | 0.8 | 压缩质量，0-1 之间，值越小文件越小但质量越差 |
| `maxWidth` | number | Infinity | 最大宽度（像素） |
| `maxHeight` | number | Infinity | 最大高度（像素） |
| `minWidth` | number | 0 | 最小宽度 |
| `minHeight` | number | 0 | 最小高度 |
| `width` | number | - | 固定宽度（优先级高于 maxWidth） |
| `height` | number | - | 固定高度（优先级高于 maxHeight） |
| `resize` | string | 'cover' | 调整大小模式：'none'\|'contain'\|'cover'\|'crop' |
| `crop` | boolean | false | 是否裁剪（需配合 width/height） |
| `rotate` | number | 0 | 旋转角度（度） |
| `blur` | number | 0 | 模糊半径 |
| `grayscale` | boolean | false | 转为灰度图 |
| `input` | string | 'file' | 输入类型 |
| `output` | string | 'blob' | 输出类型：'blob'\|'base64'\|'datauristring' |

## 5. 常见使用场景

### 5.1 批量图片压缩

```javascript
const compressFiles = async (files) => {
  const promises = files.map(file => {
    return new Promise((resolve, reject) => {
      new Compressor(file, {
        quality: 0.8,
        maxWidth: 1280,
        maxHeight: 1280,
        success: resolve,
        error: reject
      })
    })
  })

  return await Promise.all(promises)
}
```

### 5.2 限制文件大小

```javascript
const compressWithSizeLimit = (file, maxSizeMB = 2) => {
  return new Promise((resolve, reject) => {
    const maxSizeBytes = maxSizeMB * 1024 * 1024

    new Compressor(file, {
      quality: 0.6,              // 降低质量确保文件更小
      maxWidth: 1024,
      maxHeight: 1024,
      success: (result) => {
        if (result.size > maxSizeBytes) {
          // 仍超出限制，进一步压缩
          new Compressor(file, {
            quality: 0.4,
            maxWidth: 800,
            maxHeight: 800,
            success: resolve,
            error: reject
          })
        } else {
          resolve(result)
        }
      },
      error: reject
    })
  })
}
```

### 5.3 自定义输出格式

```javascript
// 输出为 base64
new Compressor(file, {
  quality: 0.8,
  maxWidth: 1280,
  maxHeight: 1280,
  output: 'datauristring',
  success: (result) => {
    const base64String = result // 已经是 base64 字符串
  }
})

// 转换为 PNG
new Compressor(file, {
  quality: 0.9,
  maxWidth: 1280,
  maxHeight: 1280,
  success: (result) => {
    new File([result], 'image.png', { type: 'image/png' })
  }
})
```

## 6. 最佳实践

### 6.1 错误处理

```javascript
let compressedFile = originalFile
try {
  compressedFile = await new Promise((resolve, reject) => {
    new Compressor(file, options)
  })
} catch (error) {
  console.error('压缩失败:', error)
  // 压缩失败时使用原文件，避免阻塞上传流程
}
```

### 6.2 文件类型检查

```javascript
const isImage = (file) => {
  return /^image\//.test(file.type)
}

if (!isImage(file)) {
  throw new Error('仅支持图片文件')
}
```

### 6.3 压缩级别建议

- **高质量**（适合产品图、头像）：
  ```javascript
  {
    quality: 0.9,
    maxWidth: 1920,
    maxHeight: 1920
  }
  ```

- **中等质量**（适合一般场景）：
  ```javascript
  {
    quality: 0.8,
    maxWidth: 1280,
    maxHeight: 1280
  }
  ```

- **高质量压缩**（适合缩略图、证明材料）：
  ```javascript
  {
    quality: 0.6,
    maxWidth: 800,
    maxHeight: 800
  }
  ```

### 6.4 进度提示（适用于 Vue/Vant）

```javascript
item.status = 'uploading'
item.message = '压缩中...'

try {
  // 压缩
  item.message = '上传中...'

  // 上传
  item.status = 'done'
  item.message = '上传成功'
} catch (error) {
  item.status = 'failed'
  item.message = '上传失败'
}
```

## 7. 性能优化

1. **避免重复压缩**：上传前检查文件是否已压缩
2. **合理设置尺寸**：根据业务需求设置最大宽高
3. **并发控制**：避免同时压缩过多文件
4. **使用 Web Workers**：大量文件时考虑使用 Web Worker

```javascript
// 并发控制示例
const processFilesWithLimit = async (files, limit = 3) => {
  const results = []
  const executing = []

  for (const file of files) {
    const promise = Promise.resolve().then(() => compressFile(file))
    results.push(promise)

    if (limit <= files.length) {
      const executing_promise = promise.then(() =>
        executing.splice(executing.indexOf(executing_promise), 1)
      )
      executing.push(executing_promise)

      if (executing.length >= limit) {
        await Promise.race(executing)
      }
    }
  }

  return Promise.all(results)
}
```

## 8. 注意事项

1. **只支持图片格式**：CompressorJS 仅处理图片文件（jpeg, png, gif 等）
2. **Canvas 依赖**：依赖浏览器 Canvas API，部分旧浏览器可能不支持
3. **内存使用**：大文件压缩会消耗较多内存，建议控制文件大小
4. **EXIF 方向**：压缩可能会丢失图片 EXIF 方向信息
5. **文件类型转换**：默认输出 blob，转换为 File 时需显式指定类型

## 9. 完整示例

参考项目中的完整实现：
- 文件：`src/pages/approval/submit.vue:54-115`
- 结合 Vant Uploader 组件的批量图片上传和压缩
- 集成错误处理和状态管理
- 支持压缩失败时的降级策略

## 10. 常见问题

**Q: 压缩后文件变大？**
A: 原文件已非常小，或开启了失真优化。尝试降低 quality 或设置更小的 maxWidth/maxHeight。

**Q: 支持 GIF 动态图？**
A: 不支持，CompressorJS 仅处理静态图像。

**Q: 如何保持文件类型？**
A: 压缩时输出为 blob，然后在 File 构造函数中指定目标类型：
```javascript
const type = file.type.includes('png') ? 'image/png' : 'image/jpeg'
new File([result], file.name, { type })
```
