您好,欢迎来到汇智养生。
搜索
您的当前位置:首页基于vue-upload-component封装一个图片上传组件的示例

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

来源:汇智养生


需求分析

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

  • 多图上传
  • 点击预览
  • 图片前端压缩
  • 支持初始化数据
  • 相关功能及资源分析

    基本功能

    先到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)
     })
    }
    // base 转 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})
    },
    // base 转 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 文档

    Copyright © 2019- huiziwo.com 版权所有

    违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

    本站由北京市万商天勤律师事务所王兴未律师提供法律服务