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动态调整iframe高度的代码
Apr 10 Javascript
初学Javascript的一些总结
Nov 03 Javascript
浅谈javascript回调函数
Dec 07 Javascript
JS+CSS实现可拖动的弹出提示框
Feb 16 Javascript
简单实现Vue的observer和watcher
Dec 21 Javascript
详解vue-cli + webpack 多页面实例配置优化方法
Jul 13 Javascript
基于jQuery使用Ajax动态执行模糊查询功能
Jul 05 jQuery
浅析vue给不同环境配置不同打包命令
Aug 17 Javascript
eslint 的三大通用规则详解
May 16 Javascript
Vue分页器实现原理详解
Jun 28 Javascript
JS面试题中深拷贝的实现讲解
May 07 Javascript
小程序实现侧滑删除功能
Jun 25 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写的serv-u的web申请账号的程序
2006/10/09 PHP
php 字符串函数收集
2010/03/29 PHP
慎用preg_replace危险的/e修饰符(一句话后门常用)
2013/06/19 PHP
php实现查询百度google收录情况(示例代码)
2013/08/02 PHP
使用PHP连接数据库_实现用户数据的增删改查的整体操作示例
2017/09/01 PHP
Yii框架引入coreseek分页功能示例
2019/02/08 PHP
Javascript 代码也可以变得优美的实现方法
2009/06/22 Javascript
JQuery 网站换肤功能实现代码
2009/11/02 Javascript
基于jquery自己写tab滑动门(通用版)
2012/10/30 Javascript
js实现的点击数量加一可操作数据库
2014/05/09 Javascript
FF(火狐)浏览器无法执行window.close()解决方案
2014/11/13 Javascript
最全的Javascript编码规范(推荐)
2016/06/22 Javascript
Bootstrap登陆注册页面开发教程
2016/07/12 Javascript
ros::spin() 和 ros::spinOnce()函数的区别及详解
2016/10/01 Javascript
EasyUI学习之Combobox下拉列表(1)
2016/12/29 Javascript
BootStrap CSS全局样式和表格样式源码解析
2017/01/20 Javascript
深入理解Vue父子组件生命周期执行顺序及钩子函数
2018/08/12 Javascript
Vue.js组件间通信方式总结【推荐】
2018/11/23 Javascript
JS+JQuery实现无缝连接轮播图
2020/12/30 jQuery
[50:12]EG vs Fnatic 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
Python的randrange()方法使用教程
2015/05/15 Python
python3设计模式之简单工厂模式
2017/10/17 Python
Python实现的摇骰子猜大小功能小游戏示例
2017/12/18 Python
python3.6+django2.0开发一套学员管理系统
2018/03/03 Python
python logging日志模块以及多进程日志详解
2018/04/18 Python
详解python常用命令行选项与环境变量
2020/02/20 Python
python用什么编辑器进行项目开发
2020/06/17 Python
使用CSS3代码绘制可爱的Hello Kitty猫
2016/08/03 HTML / CSS
德国家具折扣店:POCO
2020/02/28 全球购物
2014最新党员批评与自我批评材料
2014/09/24 职场文书
校园学雷锋广播稿
2014/10/08 职场文书
2015年学校关工委工作总结
2015/04/03 职场文书
结婚通知短信大全
2015/04/17 职场文书
保险公司2016开门红口号集锦
2015/12/24 职场文书
2016年感恩教师节活动总结
2016/04/01 职场文书
MySQL锁机制
2021/04/05 MySQL