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


Posted in Javascript onOctober 16, 2018

需求分析

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

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

相关功能及资源分析

基本功能

先到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 文档

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
基于jquery点击自以外任意处,关闭自身的代码
Feb 10 Javascript
jQuery控制的不同方向的滑动(向左、向右滑动等)
Jul 18 Javascript
浅谈JavaScript前端开发的MVC结构与MVVM结构
Jun 03 Javascript
vue.js入门教程之计算属性
Sep 01 Javascript
jQuery插件ajaxFileUpload异步上传文件
Oct 19 Javascript
jQuery插件zTree实现删除树节点的方法示例
Mar 08 Javascript
使用 vue-i18n 切换中英文效果
May 23 Javascript
vue 表单验证按钮事件交由父组件触发的方法
Dec 17 Javascript
vue搜索和vue模糊搜索代码实例
May 07 Javascript
Node.js API详解之 querystring用法实例分析
Apr 29 Javascript
vue项目接口管理,所有接口都在apis文件夹中统一管理操作
Aug 13 Javascript
解决vue项目本地启动时无法携带cookie的问题
Feb 06 Vue.js
Postman的下载及安装教程详解
Oct 16 #Javascript
Vue.js 时间转换代码及时间戳转时间字符串
Oct 16 #Javascript
详解angular2如何手动点击特定元素上的点击事件
Oct 16 #Javascript
iView框架问题整理小结
Oct 16 #Javascript
详解多页应用 Webpack4 配置优化与踩坑记录
Oct 16 #Javascript
js拖动滑块和点击水波纹效果实例代码
Oct 16 #Javascript
ajax与jsonp的区别及用法
Oct 16 #Javascript
You might like
PHP分页显示制作详细讲解
2006/12/05 PHP
php全排列递归算法代码
2012/10/09 PHP
PHP下SSL加密解密、验证、签名方法(很简单)
2020/06/28 PHP
PHP中for循环与foreach的区别
2017/03/06 PHP
CI(CodeIgniter)框架视图中加载视图的方法
2017/03/24 PHP
JS获取IUSR_机器名和IWAM_机器名帐号的密码
2006/12/06 Javascript
javascript 强制刷新页面的实现代码
2009/12/13 Javascript
jquery修改属性值实例代码(设置属性值)
2014/01/06 Javascript
js检测输入内容全为空格的方法
2014/05/03 Javascript
跟我学习javascript的闭包
2015/11/16 Javascript
JavaScript组合模式学习要点
2016/08/26 Javascript
JS实现探测网站链接的方法【测试可用】
2016/11/08 Javascript
Vue.JS入门教程之自定义指令
2016/12/08 Javascript
nodeJs链接Mysql做增删改查的简单操作
2017/02/04 NodeJs
JavaScript实现简单的四则运算计算器完整实例
2017/04/28 Javascript
使用vue-resource进行数据交互的实例
2017/09/02 Javascript
bootstrap table sum总数量统计实现方法
2017/10/29 Javascript
javascript设计模式 ? 单例模式原理与应用实例分析
2020/04/09 Javascript
vue接口请求加密实例
2020/08/11 Javascript
原生js实现贪吃蛇游戏
2020/10/26 Javascript
vue+iview分页组件的封装
2020/11/17 Vue.js
[32:36]完美世界DOTA2联赛PWL S3 LBZS vs CPG 第二场 12.12
2020/12/16 DOTA
通过python+selenium3实现浏览器刷简书文章阅读量
2017/12/26 Python
Python求两点之间的直线距离(2种实现方法)
2019/07/07 Python
python字典的遍历3种方法详解
2019/08/10 Python
python利用后缀表达式实现计算器功能
2021/02/22 Python
小学语文教研活动总结
2014/07/01 职场文书
优秀党员学习焦裕禄精神思想汇报范文
2014/09/10 职场文书
高校师德师风自我剖析材料
2014/09/29 职场文书
机关干部三严三实心得体会
2014/10/13 职场文书
民政工作个人总结
2015/02/28 职场文书
婚礼领导致辞大全
2015/07/28 职场文书
学习经验交流会总结
2015/11/02 职场文书
Redis缓存-序列化对象存储乱码问题的解决
2021/06/21 Redis
详解Java七大阻塞队列之SynchronousQueue
2021/09/04 Java/Android
Redis实战高并发之扣减库存项目
2022/04/14 Redis