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 相关文章推荐
javascript xml为数据源的下拉框控件
Jul 07 Javascript
再说AutoComplete自动补全之实现原理
Nov 05 Javascript
js中数组(Array)的排序(sort)注意事项说明
Jan 24 Javascript
深入了解JavaScript中的Symbol的使用方法
Jul 28 Javascript
window.open不被拦截的简单实现代码(推荐)
Aug 04 Javascript
Bootstrap时间选择器datetimepicker和daterangepicker使用实例解析
Sep 17 Javascript
JavaScript中return用法示例
Nov 29 Javascript
easyui combotree加载静态数据问题(选不上)解决方法
Dec 26 Javascript
在 Typescript 中使用可被复用的 Vue Mixin功能
Apr 17 Javascript
vue中的自定义分页插件组件的示例
Aug 18 Javascript
vue首次赋值不触发watch的解决方法
Sep 11 Javascript
js获取本日、本周、本月的时间代码
Feb 01 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
收音机发烧友应当熟知的100条知识
2021/03/02 无线电
php 5.3.5安装memcache注意事项小结
2011/04/12 PHP
一段实用的php验证码函数
2016/05/19 PHP
thinkphp3.x中变量的获取和过滤方法详解
2016/05/20 PHP
谈谈从phpinfo中能获取哪些值得注意的信息
2017/03/28 PHP
通过PHP的Wrapper无缝迁移原有项目到新服务的实现方法
2020/04/02 PHP
JavaScript实现页面跳转的方式汇总
2016/05/16 Javascript
JS实现n秒后自动跳转的两种方法
2020/11/30 Javascript
JS限定手机版中图片大小随分辨率自动调整的方法
2016/12/05 Javascript
微信小程序 自定义对话框实例详解
2017/01/20 Javascript
jQuery插件HighCharts绘制2D半圆环图效果示例【附demo源码下载】
2017/03/09 Javascript
JavaScript实现审核流程状态的动态显示进度条
2017/03/15 Javascript
解决BootStrap Fileinput手机图片上传显示旋转问题
2017/06/01 Javascript
JavaScript实现预览本地上传图片功能完整示例
2019/03/08 Javascript
[26:50]2018完美盛典DOTA2表演赛
2018/12/17 DOTA
python 域名分析工具实现代码
2009/07/15 Python
Python urllib模块urlopen()与urlretrieve()详解
2013/11/01 Python
使用Python的Twisted框架编写简单的网络客户端
2015/04/16 Python
深入理解Python对Json的解析
2017/02/14 Python
Python爬虫实现获取动态gif格式搞笑图片的方法示例
2018/12/24 Python
我用Python抓取了7000 多本电子书案例详解
2019/03/25 Python
Python常用模块logging——日志输出功能(示例代码)
2019/11/20 Python
python实现一次性封装多条sql语句(begin end)
2020/06/06 Python
python中使用asyncio实现异步IO实例分析
2021/02/26 Python
C语言中break与continue的区别
2012/07/12 面试题
银行个人求职自荐信范文
2013/12/16 职场文书
幼儿园保育员岗位职责
2014/04/13 职场文书
法律系毕业生求职信
2014/05/28 职场文书
记账会计岗位职责
2014/06/16 职场文书
幼儿园八一建军节活动方案
2014/08/27 职场文书
标准版离职证明书
2014/09/12 职场文书
出纳工作检讨书
2014/10/18 职场文书
悬空寺导游词
2015/02/05 职场文书
学校趣味运动会开幕词
2016/03/04 职场文书
导游词之舟山普陀山
2019/11/06 职场文书
DBCA命令行搭建Oracle ADG的流程
2021/06/11 Oracle