<h1 align="center">ImgPond</h1>

<p align="center">
  图片上传一站式解决方案。
</p>

<p align="center">
  <a href="https://bundlephobia.com/package/yj-imgpond"><img alt="minzipped size" src="https://img.shields.io/bundlephobia/minzip/yj-imgpond"></a>
</p>

<br>

## 特性

- 数据双向绑定 `v-model`，支持任意绑定值类型
- 数据源
  - 用户选择本地文件 (File)
  - 编程式提供数据源 (File/Blob/Base64/URL/[object URL](https://developer.mozilla.org/en-US/docs/Web/API/File_API/Using_files_from_web_applications#example_using_object_urls_to_display_images))
- 编辑图片
  - 自由裁剪
  - 锁定比例裁剪 (支持设置具体的值及其公差，也支持设置一个范围)
  - 翻转、缩放、无级角度旋转
  - 输出品质调节
- 限制图片
  - 数量上限、下限
  - 体积上限、下限
  - 格式筛选
  - 自定义校验
- 拖拉拽排序
- 预览图片
- 局部注册并传参，或全局注册并传参

<br>

## 安装

```shell
npm i yj-imgpond
```

### 外置依赖

- vue@2
- element-ui
- pic-viewer

### 局部注册

```vue
<template>
  <ImgPond
    v-model="value"
    v-bind="{
      /* 局部配置 */
    }"
  />
</template>

<script>
import PicViewer from 'pic-viewer'
import ImgPond from 'yj-imgpond'

export default {
  components: { PicViewer, ImgPond }
}
</script>
```

### 全局注册

```ts
import PicViewer from 'pic-viewer'
import ImgPond from 'yj-imgpond'

Vue.use(PicViewer, {
  // 全局配置
})
Vue.use(ImgPond, {
  // 全局配置
})
```

### CDN + ESM

> ⚠ 暂不支持 (ElementUI 未提供 ESM 导出)

### CDN + UMD

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" />
  </head>

  <body>
    <div id="app">
      <img-pond v-model="value"></img-pond>
    </div>
    <script src="https://unpkg.com/vue@2"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <script src="https://unpkg.com/pic-viewer@0.10"></script>
    <script src="https://unpkg.com/yj-imgpond@0.12"></script>
    <script>
      Vue.use(PicViewer)
      Vue.use(ImgPond)

      new Vue({
        data() {
          return {
            value: undefined
          }
        }
      }).$mount('#app')
    </script>
  </body>
</html>
```

<br>

## 属性

| 名称                 | 说明                                                                                        | 类型                                                                            | 默认值      |
| -------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------- |
| value / v-model      | 绑定值                                                                                      | any                                                                             |             |
| arrayed              | 绑定值是否为数组类型，默认自动                                                              | boolean                                                                         |             |
| srcAt                | 图片链接的位置                                                                              | string / symbol / (value: any) => any                                           |             |
| upload               | 调用接口上传图片，返回图片链接                                                              | (output: File \| Blob) => Promise<string \| object> \| string \| object \| void |             |
| count                | 数量限制                                                                                    | number / [number?, number?]                                                     |             |
| size                 | 体积限制（MB）                                                                              | number / [number?, number?]                                                     |             |
| accept               | [原生 input 的 accept](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept) | string                                                                          | `'image/*'` |
| validator            | 自定义数据源校验器                                                                          | (source: File \| Blob \| string) => boolean                                     |             |
| disabled             | 禁用状态                                                                                    | boolean                                                                         | `false`     |
| editable             | 是否开启编辑功能                                                                            | boolean                                                                         | `true`      |
| aspectRatio          | 锁定裁剪比例                                                                                | string / [string?, string?]                                                     |             |
| aspectRatioTolerance | 锁定裁剪比例的公差                                                                          | number                                                                          | `0`         |
| isCompress         | 超出宽度是否自动压缩 | boolean      | true        |
| compressMaxWidth         | 自动压缩宽度 | number      | 1280        |
| compressorParams     | 压缩图片参数（同compressorjs参数）                                                                                | number                                                                          | {convertTypes: [], convertSize: Infinity}          |
| cropperMode          | 图片裁剪模式                                                                                | number                                                                          | 0           |

### arrayed

如果数量上限和图片数量均不超过 1，则处于单选状态，否则为多选

默认情况下，在单选时输出的绑定值形如：item，多选时输出的绑定值形如：[item，item]

item 具体是什么格式？

未配置 srcAt 时，会提取图片链接作为 item，配置了则不会

如果将 arrayed 设置为 `true` 则强制输出数组类型，无论单选还是多选

如果将 arrayed 设置为 `false` 则强制输出非数组类型，如果此时图片数量为多个，则会执行 `JSON.stringify`

### srcAt

用于定位 value 和 upload 返回值中的图片链接，适用于绑定值非图片链接本身的情况

- 支持属性名，如 `'url'`
- 支持属性路径，如 `'data[0].url'`
- 支持 symbol 类型的属性名
- 支持 Function，如 `value => value.url`

### upload

开启编辑功能时，会在编辑完成后调用，未开启编辑功能时，会在选择图片后调用

未配置或函数返回值为空时，绑定值将输出二进制文件

参数为编辑产物：

用户选择本地文件、编程式提供 File 类型的数据源时，编辑产物的类型为 File

编程式提供非 File 类型的数据源且编辑了图片时，编辑产物的类型为 Blob

未开启编辑功能或未编辑时，编辑产物即输入值

编程式提供 string 类型的数据源且未编辑时，不需要上传，该方法不会被调用

返回值类型为 Promise\<object\> 或 object 时需要配置 srcAt

### count

- `10`：限制数量上限不高于 10 张
- `[1]`：限制数量下限不低于 1 张
- `[1, 10]`：限制数量下限不低于 1 张，且上限不高于 10 张

### size

- `10`：限制体积上限不高于 10 MB
- `[1]`：限制体积下限不低于 1 MB
- `[1, 10]`：限制体积下限不低于 1 MB，且上限不高于 10 MB

### aspectRatio

- `1/1`：限制宽高比为 1 比 1
- `['1/1']`：限制宽高比下限不低于 1 比 1
- `['1/1', '2/1']`：限制宽高比下限不低于 1 比 1，且上限不高于 2 比 1

### accept

通过文件对话框选择图片时，优先展示指定类型的文件

> ⚠ 用户仍可以选择其它类型，文件类型校验应使用 validator

语法：

- 文件扩展名，不区分大小写，如 `'.jpg.jpeg.png'`
- [MIME type](http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)，如 `'image/jpeg,image/png'`

### cropperMode

- 0: 没有限制
- 1: 限制裁剪框不超过图片容器的范围。
- 2: 限制最片容器尺寸以在裁剪容器中展示。 如果图片容器和裁剪容器的比例不同，则图片容器以 cover 模式填充（图片容器保持原有比例，最长边和裁剪容器大小一致，短边等比缩放，可能会有部分区域不可见)。
- 3: 限制图片容器尺寸以在裁剪器中展示。 如果图片容器和裁剪容器的比例不同，则图片容器以 contain 模式填充（图片容器保持原有比例，最短边和裁剪容器大小一直，长边等比缩放，可能会有留白）。
  <br>

## 插槽

同 `el-upload`

<br>

## 事件

| 名称       | 说明                                                    | 回调参数         |
| ---------- | ------------------------------------------------------- | ---------------- |
| size-error | 选中图片的体积不符合要求时触发                          | 图片体积（Byte） |
| ...        | `el-upload` 的事件（Function 类型的属性，去掉 on 前缀） |                  |

```html
<ImgPond @remove="onRemove" @beforeUpload="onBeforeUpload" />
```

<br>

## 方法

| 名称       | 说明               | 参数                                                                     |
| ---------- | ------------------ | ------------------------------------------------------------------------ |
| openEditor | 打开图片编辑对话框 | (source: File \| Blob \| string \| File[] \| Blob[] \| string[]) => void |

参数为输入的数据源，支持的数据类型有：

- File
- Blob
- Base64
- URL：需要跨域支持
- object URL：需要在当前 `document` 创建

如果没有编辑图片，则输出值类型不变 (与输入值一致)

如果编辑了图片，输入类型为 File 时，输出类型也为 File，其它情况均输出 Blob 类型

<br>

## 编程式提供数据源

```vue
<!-- 示例: 输入图片链接进行编辑 -->
<!-- 如果需要附加图片名称，可以先转换为 File 类型再输入 -->

<template>
  <ImgPond v-show="false" ref="imgPondRef" :upload="upload" />

  <el-button @click="openEditor"> 编辑图片 </el-button>
</template>

<script setup>
const imgPondRef = ref()

async function urlToFile(url, fileName) {
  const blob = await (await fetch(url)).blob()
  return new File([blob], fileName, { type: blob.type })
}

function openEditor() {
  const file = urlToFile('https://picsum.photos/100', '100x100.jpg')
  imgPondRef.value.openEditor(file)
}

function upload(file) {
  return POST.upload(import.meta.env.VITE_APP_UPLOAD_API, {
    file
  }).then((res) => res.data.data)
}
</script>
```

<br>

## 校验文件扩展名

```vue
<template>
  <ImgPond :accept="accept" :validator="validator" />
</template>

<script setup>
const accept = '.jpg,.jpeg,.png'
const extension = accept.split(',')

function validator(source) {
  let valid = true
  if (source instanceof File) {
    valid = extension.includes(source.name.replace(/.+\./, '.').toLowerCase())
    if (!valid) {
      alert(`"${source.name}" 的格式不在可支持范围: ${accept}`)
    }
  }
  return valid
}
</script>
```

<br>

## 上传状态

```vue
<template>
  <ImgPond ref="imgPondRef" />
</template>

<script setup>
const imgPondRef = ref()

console.log(imgPondRef.value.uploading)
</script>
```

<br>

## 自定义上传时机

1. 不配置 upload，绑定值得到二进制文件
2. 将 srcAt 配置为 `'url'`，使图片能够正常预览
3. 在适当时机自行上传

<br>

## 自定义 trigger

```vue
<template>
  <ImgPond class="custom-trigger" list-type="text" mb="8px">
    <el-button>自定义 trigger</el-button>
  </ImgPond>
</template>

<style lang="scss" scoped>
.custom-trigger {
  :deep(.pic-viewer),
  :deep(.el-upload-list),
  :deep(.el-upload__tip),
  :deep(.el-upload__text) {
    display: none;
  }
}
</style>
```

<br>

## 自定义 tip

```vue
<ImgPond aspectRatio="375/120" :size="10" :count="1">
  <template #tip="{ aspectRatio, size, count, accept }">
    <div>{{ count }}</div>
    <div>{{ size }}</div>
    <div>{{ aspectRatio }}</div>
    <div>{{ accept }}</div>
  </template>
</ImgPond>
```

<br>

## 嵌套在表格中

以宽高 `50px` 为例，修改为如下样式：

```scss
:deep(.pic-viewer li) {
  margin: 0 !important; // 如果允许多张，则去掉这行

  img {
    height: 50px !important;
  }
}

:deep(.el-upload-list__item) {
  width: 50px;
  height: 50px;
  margin: 0; // 如果允许多张，则去掉这行

  & > .el-upload-list__item-status-label {
    width: 34px;
    height: 18px;

    & > i {
      margin-top: 0;
    }
  }

  .el-upload-list__item-actions {
    line-height: 50px;
    font-size: 16px;

    & > span + span {
      margin-left: 4px;
    }
  }
}

:deep(.el-upload) {
  width: 50px;
  height: 50px;
  line-height: 50px;
  margin: 0;

  & > .el-icon-plus {
    font-size: initial;
  }

  & > .el-upload__text {
    display: none;
  }
}
```

<br>

## 升级日志

### v0.12.10
feat: 实现自动压缩功能，当图片宽度超过 compressMaxWidth 时进行压缩

### v0.12.10
feat: 增加文件建议大小和溢出提示配置，文件大小溢出时自动压缩，如果压缩后仍然大于预期大小，将提示用户自行处理

### v0.12.9
feat: 支持配置图片压缩功能，实时显示裁剪后的图片大小
