Vue的移动端多图上传插件vue-easy-uploader的示例代码


Posted in Javascript onNovember 27, 2017

前言

这段时间赶项目,需要用到多文件上传,用Vue进行前端项目开发。在网上找了不少插件,都不是十分满意,有的使用起来繁琐,有的不能适应本项目。就打算自己折腾一下,写一个Vue的上传插件,一劳永逸,以后可以直接使用。

目前vue-easy-uploader已上传到GitHub和NPM,使用起来方便简单,不需要繁琐的配置即可投入生产,不过需要后端配合,实现上传接口。

本项目GitHub地址: https://github.com/quanzaiyu/vue-easy-uploader

本项目NPM地址: https://www.npmjs.com/package/vue-easy-uploader

详细的使用方法都在仓库Readme中,就不赘述,这里谈下本插件的设计开发思路。

插件介绍

vue-easy-uploader是一个多图上传插件。主要特性包括:

  1. 多文件上传
  2. 上传图片预览
  3. 上传状态监测
  4. 删除指定图片
  5. 清空图片
  6. 重新上传

后期版本迭代将不限于图片,往通用文件上传进行改进。

先看看上传插件使用时候的效果图:

Vue的移动端多图上传插件vue-easy-uploader的示例代码

目录结构

Vue的移动端多图上传插件vue-easy-uploader的示例代码

index.js # 主入口文件
store.js # 状态管理
uploader.vue # 上传组件

文件解析

index.js

import uploader from './uploader'
import store from './store'

let plugin = {}

plugin.install = function (_Vue, _store) {
 _Vue.component('uploader', uploader)
 _store.registerModule('imgstore', store)
}

export default plugin

这是插件的主入口文件,注册了全局的上传组件和状态管理,使用时只需要在项目入口文件(一般是main.js)中加入以下代码即可引入此插件:

import Vue from 'vue'
import Vuex from 'vuex'
import uploader from 'vue-easy-uploader'
 
let store = new Vuex.Store({})
Vue.use(uploader, store)

store.js

此文件为状态管理配置文件,主要包含三个state:

img_upload_cache # 上传文件缓存
img_paths # 上传状态,包括 ready selected uploading finished
img_status # 上传后的路径反馈数组(数据结构为Set集合)

针对每个state都有自己的mutation,用于改变state,规范上mutation都需要使用大写字母加下划线的形式,本人习惯使用小写字母,不过都不是原则上的问题。

最重要的一个state是img_status,用于监视图片上传的状态。包括以下几个状态:

ready # 上传开始前的准备状态 
selected # 已选择上传文件 
uploading # 开始上传 
finished # 上传完毕

在组件中可以通过改变上传状态实现文件的上传,同时也可以监听上传状态的变化而执行回调。如:

methods: {
 upload () {
  this.$store.commit('set_img_status', 'uploading')
 },
 submit () {
  // some code
 }
}
computed: {
 ...mapState({
  imgStatus: state => state.imgstore.img_status
 })
},
watch: {
 imgStatus () {
  if (this.imgStatus === 'finished') {
   this.submit()
  }
 }
}

上述代码中,使用upload方法更新了上传状态,让图片开始执行上传操作,使用watch进行上传状态的监视,当上传完成(img_status状态变为finished),执行回调函数submit。

源文件如下:

// Created by quanzaiyu on 2017/10/25 0025.
var state = {
 img_upload_cache: [],
 img_paths: [],
 img_status: 'ready' // 上传状态 ready selected uploading finished
}

const actions = {}

const getters = {}

const mutations = {
 set_img_upload_cache (state, arg) {
  state.img_upload_cache = arg
 },
 set_img_paths (state, arg) {
  state.img_paths = arg
 },
 set_img_status (state, arg) {
  state.img_status = arg
 }
}

export default {
 state,
 mutations,
 actions,
 getters
}

uploader.vue

先看源代码(为了节省空间,未贴出style部分的代码):

<template>
 <div class="imgUploader">
  <div class="file-list">
   <section
    v-for="(file, index) in imgStore" :key="index"
    class="file-item draggable-item"
   >
    <img :src="file.src" alt="" ondragstart="return false;">
    <span class="file-remove" @click="remove(index)">+</span>
   </section>
   <section class="file-item" v-if="imgStatus !== 'finished'">
    <div class="add">
     <span>+</span>
     <input type="file" pictype='30010003' multiple
      data-role="none" accept="image/*"
      @change="selectImgs"
      ref="file"
     >
    </div>
   </section>
  </div>
  <div class="uploadBtn">
   <section>
    <span v-if="imgStore.length > 0" class="empty"
     @click="empty">
      {{imgStatus === 'finished' ? '重新上传' : '清空'}}
    </span>
   </section>
  </div>
 </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
 props: ['url'],
 data () {
  return {
   files: [], // 文件缓存
   index: 0 // 序列号
  }
 },
 computed: {
  ...mapState({
   imgStore: state => state.imgstore.img_upload_cache,
   imgPaths: state => state.imgstore.img_paths,
   imgStatus: state => state.imgstore.img_status
  })
 },
 methods: {
  // 选择图片
  selectImgs () { # ①
   let fileList = this.$refs.file.files
   for (let i = 0; i < fileList.length; i++) {
    // 文件过滤
    if (fileList[i].name.match(/.jpg|.gif|.png|.bmp/i)) { 
     let item = {
      key: this.index++,
      name: fileList[i].name,
      size: fileList[i].size,
      file: fileList[i]
     }
     // 将图片文件转成BASE64格式
     let reader = new FileReader() # ②
     reader.onload = (e) => {
      this.$set(item, 'src', e.target.result)
     }
     reader.readAsDataURL(fileList[i])
     this.files.push(item)
     this.$store.commit('set_img_upload_cache', this.files) // 存储文件缓存
     this.$store.commit('set_img_status', 'selected') // 更新文件上传状态
    }
   }
  },
  // 上传图片
  submit () {
   let formData = new FormData() # ③
   this.imgStore.forEach((item, index) => {
    item.name = 'imgFiles[' + index + ']' # ④
    formData.append(item.name, item.file)
   })
   formData.forEach((v, k) => console.log(k, ' => ', v))
   // 新建请求
   const xhr = new XMLHttpRequest() # ⑤
   xhr.open('POST', this.url, true)
   xhr.send(formData)
   xhr.onload = () => {
    if (xhr.status === 200 || xhr.status === 304) {
     let datas = JSON.parse(xhr.responseText)
     console.log('response: ', datas)
     // 存储返回的地址
     let imgUrlPaths = new Set()  # ⑥
     datas.forEach(e => { // error === 0为成功状态
      e.error === 0 && imgUrlPaths.add(e.url)
     })
     this.$store.commit('set_img_paths', imgUrlPaths) // 存储返回的地址
     this.files = [] // 清空文件缓存
     this.index = 0 // 初始化序列号
     this.$store.commit('set_img_status', 'finished') // 更新文件上传状态
    } else {
     alert(`${xhr.status} 请求错误!`)
    }
   }
  },
  // 移除图片
  remove (index) {
   this.files.splice(index, 1)
   this.$store.commit('set_img_upload_cache', this.files) // 更新存储文件缓存
  },
  // 清空图片
  empty () {
   this.files.splice(0, this.files.length)
   this.$store.commit('set_img_upload_cache', this.files) // 更新存储文件缓存
   this.$store.commit('set_img_paths', [])
  }
 },
 beforeCreate () {
  this.$store.commit('set_img_status', 'ready') // 更新文件上传状态
 },
 destroyed () {
  this.$store.commit('set_img_upload_cache', [])
  this.$store.commit('set_img_paths', [])
 },
 watch: {
  imgStatus () {
   if (this.imgStatus === 'uploading') {
    this.submit()  # ⑦
   }
  },
  imgStore () {
   if (this.imgStore.length <= 0) {
    this.$store.commit('set_img_status', 'ready') // 更新文件上传状态
   }
  }
 }
}
</script>

<style lang="less" scoped>
...
</style>

以上代码中有一些注释序号,是此插件设计的主要思路,其他代码都比较容易理解,分别说下

① 选择文件后执行,img_status状态变为selected。
② 将带上传的图片文件转化为Base64格式,用于缩略图显示。
③ 创建一个表单对象,用于存储待上传的文件。
④ 注意这里的name属性值,暂时写死,后面设计打算从组件中指定name属性,如果是多文件的话,name属性的数组序号从0开始递增。
⑤ 未依赖任何Ajax请求插件,使用原生的XMLHttpRequest对象创建请求。
⑥ 存储上传成功后服务器返回的上传路径。
⑦ 检测上传状态,当在使用此插件时将img_status的状态设置为uploading时执行上传操作。

使用

参考本项目的GItHub和NPM。

注意

使用此插件时,需要与后端约定返回的数据格式,如下:

[{"error":0,"url":"\/uploads\/api\/201711\/25\/fde412bd83d3ec5d6a49769bd0c143cd.jpg"},{"error":0,"url":"\/uploads\/api\/201711\/25\/c6fd51f0388c63a0b6d350331c945fb1.jpg"}]

预览如下:

Vue的移动端多图上传插件vue-easy-uploader的示例代码

返回的是一个上传后的路径数组,包括error和url字段,每个文件有自己的上传状态,当error为0的时候为上传成功,并返回上传后的路径url

改进

后续版本打算进行如下改进

  1. 把表单的name属性名称通过组件传递。
  2. 自定义上传成功后服务器响应的数据格式,比如自定义error的名称和其值所表示的状态。
  3. 支持其他类型文件的上传,可以在组件中自行制定上传的文件类型,及其预览方式。

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

Javascript 相关文章推荐
Js+XML 操作
Sep 20 Javascript
网页禁用右键实现代码(JavaScript代码)
Oct 29 Javascript
单元选择合并变色示例代码
May 26 Javascript
js实现图片在未加载完成前显示加载中字样
Sep 03 Javascript
15个jquery常用方法、小技巧分享
Jan 13 Javascript
在Node.js中使用HTTP上传文件的方法
Jun 23 Javascript
Js制作点击输入框时默认文字消失的效果
Sep 05 Javascript
JavaScript文本框脚本编写的注意事项
Jan 25 Javascript
JavaScript位移运算符(无符号) &gt;&gt;&gt; 三个大于号 的使用方法详解
Mar 31 Javascript
JavaScript实现阿拉伯数字和中文数字互相转换
Jun 12 Javascript
iview在vue-cli3如何按需加载的方法
Oct 31 Javascript
微信小程序五子棋游戏的悔棋实现方法【附demo源码下载】
Feb 20 Javascript
vue实现商城购物车功能
Nov 27 #Javascript
Vim快速合并行及vim 将文件所有行合并到一行
Nov 27 #Javascript
详解利用Angular实现多团队模块化SPA开发框架
Nov 27 #Javascript
JavaScript实现修改伪类样式
Nov 27 #Javascript
Vue.js搭建移动端购物车界面
Jun 28 #Javascript
Vue实现购物车场景下的应用
Nov 27 #Javascript
javascript字体颜色控件的开发 JS实现字体控制
Nov 27 #Javascript
You might like
PHP 解决utf-8和gb2312编码转换问题
2010/03/18 PHP
PHP中使用CURL模拟登录并获取数据实例
2014/07/01 PHP
laravel安装和配置教程
2014/10/29 PHP
php自定义加密与解密程序实例
2014/12/31 PHP
php模拟服务器实现autoindex效果的方法
2015/03/10 PHP
Yii2框架dropDownList下拉菜单用法实例分析
2016/07/18 PHP
Zend Framework实现自定义过滤器的方法
2016/12/09 PHP
老生常谈PHP位运算的用途
2017/03/12 PHP
360搜索引擎自动收录php改写方案
2018/04/28 PHP
让textarea控件的滚动条怎是位与最下方
2007/04/20 Javascript
面向对象的Javascript之三(封装和信息隐藏)
2012/01/27 Javascript
JS随机漂浮广告代码具体实例
2013/11/19 Javascript
js时间戳转为日期格式的方法
2015/12/28 Javascript
JavaScript实现设计模式中的单例模式的一些技巧总结
2016/05/17 Javascript
jQuery 判断是否包含在数组中Array[]的方法
2016/08/03 Javascript
Bootstrap导航条可点击和鼠标悬停显示下拉菜单
2016/11/25 Javascript
解决Angular.Js与Django标签冲突的方案
2016/12/20 Javascript
浅谈vue2 单页面如何设置网页title
2017/11/08 Javascript
简单理解Vue中的nextTick方法
2018/01/30 Javascript
使用Angular Cli如何创建Angular私有库详解
2019/01/30 Javascript
让python的Cookie.py模块支持冒号做key的方法
2010/12/28 Python
PyQt5每天必学之事件与信号
2018/04/20 Python
Pytorch卷积层手动初始化权值的实例
2019/08/17 Python
python计算二维矩形IOU实例
2020/01/18 Python
基于Python测试程序是否有错误
2020/05/16 Python
Python flask框架端口失效解决方案
2020/06/04 Python
PyQt5的QWebEngineView使用示例
2020/10/20 Python
Converse匡威法国官网:美国著名帆布鞋品牌
2018/12/05 全球购物
电气自动化自荐信
2013/10/10 职场文书
销售顾问的岗位职责
2013/11/13 职场文书
汽车维修专业毕业生的求职信分享
2013/12/04 职场文书
求职简历中的自我评价分享
2013/12/08 职场文书
优良学风班申请材料
2014/02/13 职场文书
班级课外活动总结
2014/07/09 职场文书
医生个人年终总结
2015/02/28 职场文书
2016年公共机构节能宣传周活动总结
2016/04/05 职场文书