基于vue-upload-component封装一个图片上传组件的示例

作者:拳头巴掌 时间:2024-05-10 14:14:42 

需求分析

业务要求,需要一个图片上传控件,需满足

  • 多图上传

  • 点击预览

  • 图片前端压缩

  • 支持初始化数据

相关功能及资源分析

基本功能

先到https://www.npmjs.com/search?q=vue+upload上搜索有关上传的控件,没有完全满足需求的组件,过滤后找到 vue-upload-component 组件,功能基本都有,自定义也比较灵活,就以以此进行二次开发。

预览

因为项目是基于 vant 做的,本身就提供了 ImagePreview 的预览组件,使用起来也简单,如果业务需求需要放大缩小,这个组件就不满足了。

压缩
可以通过 canvas 相关api来实现压缩功能,还可以用一些第三方库来实现, 例如image-compressor.js

数据

因为表单页面涉及编辑的情况,上传组件为了展示优雅点,需要做点处理。首先就先要对数据格式和服务端进行约定,然后在处理剩下的

开发

需求和实现思路基本确定,开始进入编码,先搭建可运行可测试的环境

第一步,创建相关目录


|- components
|- ImageUpload
|- ImageUpload.vue
|- index.js

第二步,安装依赖


$ npm i image-compressor.js -S
$ npm i vue-upload-component -S

第三步,编写核心主体代码


// index.js
import ImageUpload from './ImageUpload'
export default ImageUpload

// ImageUpload.vue
<template>
<div class="m-image-upload">
<!--
 这里分为两段遍历,理由是:在编辑情况下要默认为组件添加默认数据,虽然说组件提供了 `add` 方法,
 但在编辑状态下,需要把 url 形式的图片转换成 File 之后才可以添加进去,略微麻烦。
 所以分两次遍历,一次遍历表单对象里的图片(直接用img标签展示,新上传的图片可以通过 blob 来赋值 src),第二次遍历组件里的 files
-->
<div
 class="file-item"
 v-for="(file, index) in value">
 <img
 :src="file.thumb || file.url"
 @click="preview(index)"
 />
 <van-icon
 name="clear"
 class="close"
 @click="remove(index, true)"/> <!-- 把图片从数组中删除 -->
</div>
<div
 :class="{'file-item': true, 'active': file.active, 'error': !!file.error}"
 v-for="(file, index) in files"> <!-- 加几个样式来控制 `上传中` 和 `上传失败` 的样式-->
 <img
 v-if="file.blob"
 :src="file.blob"
 />
 <div class="uploading-shade">
 <p>{{ file.progress }} %</p>
 <p>正在上传</p>
 </div>
 <div class="error-shade">
 <p>上传失败!</p>
 </div>
 <van-icon
 name="clear"
 class="close"
 @click="remove(index)"
 />
</div>
<file-upload
 ref="uploader"
 v-model="files"
 multiple
 :thread="10"
 extensions="jpg,gif,png,webp"
 post-action="http://localhost:3000/file/upload"
 @input-file="inputFile"
 @input-filter="inputFilter"
>
 <van-icon name="photo"/>
</file-upload>
</div>
</template>

<script>
/**
* 图片上传控件
* 使用方法:
 import ImageUpload from '@/components/ImageUpload'
 ...
 components: {
 ImageUpload
 },
 ...
 <image-upload :value.sync="pics"/>
*/

import uploader from 'vue-upload-component'
import ImageCompressor from 'image-compressor.js';
import { ImagePreview } from 'vant';

export default {
name: 'ImageUpload',
props: {
 value: Array // 通过`.sync`来添加更新值的语法题,通过 this.$emit('update:value', this.value) 来更新
},
data() {
 return {
 files: [] // 存放在组件的file对象
 }
},
components: {
 'file-upload': uploader
},
methods: {
 // 当 add, update, remove File 这些事件的时候会触发
 inputFile(newFile, oldFile) {
 // 上传完成
 if (newFile && oldFile && !newFile.active && oldFile.active) {
  // 获得相应数据
  if (newFile.xhr && newFile.xhr.status === 200) {
  newFile.response.data.thumb = newFile.thumb // 把缩略图转移
  this.value.push(newFile.response.data) // 把 uploader 里的文件赋值给 value
  this.$refs.uploader.remove(newFile) // 删除当前文件对象
  this.$emit('update:value', this.value) // 更新值
  }
 }

// 自动上传
 if (Boolean(newFile) !== Boolean(oldFile) || oldFile.error !== newFile.error) {
  if (!this.$refs.uploader.active) {
  this.$refs.uploader.active = true
  }
 }
 },
 // 文件过滤,可以通过 prevent 来阻止上传
 inputFilter(newFile, oldFile, prevent) {
 if (newFile && (!oldFile || newFile.file !== oldFile.file)) {
  // 自动压缩
  if (newFile.file && newFile.type.substr(0, 6) === 'image/') { // && this.autoCompress > 0 && this.autoCompress < newFile.size(小于一定尺寸就不压缩)
  newFile.error = 'compressing'
  // 压缩图片
  const imageCompressor = new ImageCompressor(null, {
   quality: .5,
   convertSize: Infinity,
   maxWidth: 1000,
  })
  imageCompressor.compress(newFile.file).then((file) => {
   // 创建 blob 字段 用于图片预览
   newFile.blob = ''
   let URL = window.URL || window.webkitURL
   if (URL && URL.createObjectURL) {
   newFile.blob = URL.createObjectURL(file)
   }
   // 缩略图
   newFile.thumb = ''
   if (newFile.blob && newFile.type.substr(0, 6) === 'image/') {
   newFile.thumb = newFile.blob
   }
   // 更新 file
   this.$refs.uploader.update(newFile, {error: '', file, size: file.size, type: file.type})
  }).catch((err) => {
   this.$refs.uploader.update(newFile, {error: err.message || 'compress'})
  })
  }
 }
 },
 remove(index, isValue) {
 if (isValue) {
  this.value.splice(index, 1)
  this.$emit('update:value', this.value)
 } else {
  this.$refs.uploader.remove(this.files[index])
 }
 },
 preview(index) {
 ImagePreview({
  images: this.value.map(item => (item.thumb || item.url)),
  startPosition: index
 });
 }
}
}
</script>

图片压缩也可以自己来实现,主要是理清各种文件格式的转换


compress(imgFile) {
let _this = this
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.onload = e => {
 let img = new Image()
 img.src = e.target.result
 img.onload = () => {
 let canvas = document.createElement('canvas')
 let ctx = canvas.getContext('2d')
 canvas.width = img.width
 canvas.height = img.height
 // 铺底色
 ctx.fillStyle = '#fff'
 ctx.fillRect(0, 0, canvas.width, canvas.height)
 ctx.drawImage(img, 0, 0, img.width, img.height)

// 进行压缩
 let ndata = canvas.toDataURL('image/jpeg', 0.3)
 resolve(_this.dataURLtoFile(ndata, imgFile.name))
 }
}
reader.onerror = e => reject(e)
reader.readAsDataURL(imgFile)
})
}
// base64 转 Blob
dataURLtoBlob(dataurl) {
let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], {type: mime})
},
// base64 转 File
dataURLtoFile(dataurl, filename) {
let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], filename, {type: mime})
}

最终效果

基于vue-upload-component封装一个图片上传组件的示例

基于vue-upload-component封装一个图片上传组件的示例

参考资料

vue-upload-component 文档

来源:https://segmentfault.com/a/1190000016698171

标签:vue,upload,component,上传
0
投稿

猜你喜欢

  • python遍历文件夹,指定遍历深度与忽略目录的方法

    2022-12-28 23:57:27
  • openCV入门学习基础教程第三篇

    2022-05-20 00:00:59
  • golang语言http协议get拼接参数操作

    2024-05-08 10:45:10
  • go语言实现一个最简单的http文件服务器实例

    2024-02-21 21:52:41
  • 对pandas的dataframe绘图并保存的实现方法

    2021-12-21 14:54:50
  • Python 之 Json序列化嵌套类方式

    2021-05-25 18:11:44
  • 条件注释使用指南[译]

    2009-03-23 17:41:00
  • ASP.NET中的几种弹出框提示基本实现方法

    2023-07-13 00:23:50
  • 用什么库写 Python 命令行程序(示例代码详解)

    2023-01-01 09:35:10
  • Go语言struct要使用 tags的原因解析

    2023-08-31 09:25:19
  • 3个 Python 编程技巧

    2023-11-30 08:05:19
  • asp中isNull(str), isEmpty(str)和str=""的区别

    2008-02-15 13:10:00
  • 浅析Golang切片截取功能与C++的vector区别

    2024-04-23 09:34:51
  • JS比较两个数值的大小实例

    2024-04-17 10:41:03
  • 基于JS实现的随机数字抽签实例

    2024-04-16 09:27:23
  • 解决python3运行selenium下HTMLTestRunner报错的问题

    2022-04-14 16:44:19
  • 在ASP中使用Oracle数据库技巧

    2008-05-17 11:47:00
  • goland使用go mod模式的步骤详解

    2024-05-25 15:16:35
  • SQL Server查找表名或列名中包含空格的表和列实例代码

    2024-01-17 03:15:33
  • python3.3使用tkinter开发猜数字游戏示例

    2023-09-05 06:53:02
  • asp之家 网络编程 m.aspxhome.com