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 相关文章推荐
Jquery 1.42 checkbox 全选和反选代码
Mar 27 Javascript
使用纯JS代码判断字符串中有多少汉字的实现方法(超简单实用)
Nov 12 Javascript
JS实现“隐藏与显示”功能(多种方法)
Nov 24 Javascript
JS作用域闭包、预解释和this关键字综合实例解析
Dec 16 Javascript
关于JavaScript中forEach和each用法浅析
Jul 27 Javascript
详解Node中导入模块require和import的区别
Aug 11 Javascript
在vue中使用css modules替代scroped的方法
Mar 10 Javascript
你可能不知道的CORS跨域资源共享
Mar 13 Javascript
Vue匿名插槽与作用域插槽的合并和覆盖行为
Apr 22 Javascript
Vue中axios的封装(报错、鉴权、跳转、拦截、提示)
Aug 20 Javascript
JavaScript实现简易计算器小功能
Oct 22 Javascript
js 数组 fill() 填充方法
Nov 02 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进程之间实现共享内存的方法
2014/06/13 PHP
PHP中使用jQuery+Ajax实现分页查询多功能操作(示例讲解)
2017/09/17 PHP
dojo 之基础篇(二)之从服务器读取数据
2007/03/24 Javascript
JavaScript 字符串处理函数使用小结
2010/12/02 Javascript
基于jquery实现状态限定编辑的代码
2012/02/11 Javascript
Javascript实现颜色rgb与16进制转换的方法
2015/04/18 Javascript
js实现卡片式项目管理界面UI设计效果
2015/12/08 Javascript
JavaScript iframe数据共享接口实现方法
2016/01/06 Javascript
jquery.multiselect多选下拉框实现代码
2016/11/11 Javascript
ionic cordova一次上传多张图片(类似input file提交表单)的实现方法
2016/12/16 Javascript
Bootstrap表单控件学习使用
2017/03/07 Javascript
js实现悬浮窗效果(支持拖动)
2017/03/09 Javascript
node.js中实现kindEditor图片上传功能的方法教程
2017/04/26 Javascript
Vue基于NUXT的SSR详解
2017/10/24 Javascript
全面介绍vue 全家桶和项目实例
2017/12/27 Javascript
vue 微信授权登录解决方案
2018/04/10 Javascript
JQuery通过后台获取数据遍历到前台的方法
2018/08/13 jQuery
jquery实现进度条状态展示
2020/03/26 jQuery
javascript运行机制之执行顺序理解
2020/08/03 Javascript
Python图像处理之图像的缩放、旋转与翻转实现方法示例
2019/01/04 Python
Python离线安装PIL 模块的方法
2019/01/08 Python
PyQt5 closeEvent关闭事件退出提示框原理解析
2020/01/08 Python
python3.8与pyinstaller冲突问题的快速解决方法
2020/01/16 Python
keras 获取某层输出 获取复用层的多次输出实例
2020/05/23 Python
如何用tempfile库创建python进程中的临时文件
2021/01/28 Python
HTML5 CSS3实现一个精美VCD包装盒个性幻灯片案例
2014/06/16 HTML / CSS
Black Halo官方网站:购买连衣裙、礼服和连体裤
2018/06/13 全球购物
欧洲最大的拼图游戏商店:JigsawPuzzle.co.uk
2018/07/04 全球购物
文明餐桌活动方案
2014/02/11 职场文书
企业仓管员岗位职责
2014/06/15 职场文书
2016中秋节晚会开场白
2015/11/26 职场文书
2016教师暑期培训学习心得体会
2016/01/09 职场文书
2017年大学生寒假社会实践活动总结
2016/04/06 职场文书
创业计划书之养殖业
2019/10/11 职场文书
苹果可能正在打击不进行更新的 App
2022/04/24 数码科技
Windows Server 2016服务器用户管理及远程授权图文教程
2022/08/14 Servers