基于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 相关文章推荐
用javascript控制iframe滚动的代码
Apr 10 Javascript
利用JS实现浏览器的title闪烁
Jul 08 Javascript
javascript调试之DOM断点调试法使用技巧分享
Apr 15 Javascript
jQuery 监控键盘一段时间没输入
Apr 22 Javascript
javascript-解决mongoose数据查询的异步操作
Dec 22 Javascript
Vue项目添加动态浏览器头部title的方法
Jul 11 Javascript
从零开始学习搭建React脚手架项目
Aug 23 Javascript
vue计算属性+vue中class与style绑定(推荐)
Mar 30 Javascript
jQuery实现鼠标放置名字上显示详细内容气泡提示框效果的方法分析
Apr 04 jQuery
vue-cli单页面预渲染seo-prerender-spa-plugin操作
Aug 10 Javascript
vue3自定义dialog、modal组件的方法
Jan 04 Vue.js
JavaScript执行机制详细介绍
Dec 06 Javascript
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实现定时生成HTML网站首页实例代码
2008/11/20 PHP
网页游戏开发入门教程二(游戏模式+系统)
2009/11/02 PHP
PHP中的Streams详细介绍
2014/11/12 PHP
CI框架中redis缓存相关操作文件示例代码
2016/05/17 PHP
PHP实现分布式memcache设置web集群session同步的方法
2018/04/10 PHP
thinkPHP框架实现类似java过滤器的简单方法示例
2018/09/05 PHP
Yii框架响应组件用法实例分析
2019/09/04 PHP
利用js对象弹出一个层
2008/03/26 Javascript
一些常用且实用的原生JavaScript函数
2010/09/08 Javascript
javascript中运用闭包和自执行函数解决大量的全局变量问题
2010/12/30 Javascript
jquery()函数的三种语法介绍
2013/10/09 Javascript
如何解决手机浏览器页面点击不跳转浏览器双击放大网页
2016/07/01 Javascript
AngularJS利用Controller完成URL跳转
2016/08/09 Javascript
js文件中直接alert()中文出来的是乱码的解决方法
2016/11/01 Javascript
删除table表格行的实例讲解
2017/09/21 Javascript
JS实现合并json对象的方法
2017/10/10 Javascript
angular2 ng2 @input和@output理解及示例
2017/10/10 Javascript
Vuejs 2.0 子组件访问/调用父组件的方法(示例代码)
2018/02/08 Javascript
vue 微信授权登录解决方案
2018/04/10 Javascript
JavaScript中Array方法你该知道的正确打开方法
2018/09/11 Javascript
[36:20]KG vs SECRET 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/19 DOTA
跟老齐学Python之玩转字符串(3)
2014/09/14 Python
Python实现简单截取中文字符串的方法
2015/06/15 Python
Python使用email模块对邮件进行编码和解码的实例教程
2016/07/01 Python
Python 制作糗事百科爬虫实例
2016/09/22 Python
python解析基于xml格式的日志文件
2017/02/25 Python
Python 模拟登陆的两种实现方法
2017/08/10 Python
详解Django-restframework 之频率源码分析
2019/02/27 Python
推荐技术人员一款Python开源库(造数据神器)
2020/07/08 Python
使用Python pip怎么升级pip
2020/08/11 Python
Python3.7安装PyQt5 运行配置Pycharm的详细教程
2020/10/15 Python
pip/anaconda修改镜像源,加快python模块安装速度的操作
2021/03/04 Python
HTML5新标签兼容——&gt; 的两种方法
2018/09/12 HTML / CSS
大学毕业生通用自荐信范文
2013/10/31 职场文书
天地会口号
2014/06/17 职场文书
css实现左上角飘带效果的完整代码
2022/03/18 HTML / CSS