首页 > 编程 > JavaScript > 正文

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

2019-11-19 12:41:29
字体:
来源:转载
供稿:网友

需求分析

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

  • 多图上传
  • 点击预览
  • 图片前端压缩
  • 支持初始化数据

相关功能及资源分析

基本功能

先到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.jsimport 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 转 BlobdataURLtoBlob(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 转 FiledataURLtoFile(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 文档

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表