vue 实现上传组件


Posted in Vue.js onMay 31, 2021

1.介绍

效果如下图

 

2.思路

文件上传的两种实现方式

1.From形式

<form 
  method="post" 
  enctype="multipart/from-data"
  action="api/upload"
>
  <input type="file name="file">
  <button type="submit">Submit</button>
</form>

form的method属性指定为 "post" 请求,通过HTML表单发送数据给服务器,并返回服务器的修改结果,在这种情况下Content-Type是通过在<form>元素中设置正确的enctype属性。

form的enctype属性规定在发送到服务器之前应该如何对表单数据进行编码。

  • application/x-www-form-urlencoded(默认值):表示在发送前编码所有字符,数据被编码成以"&"分隔的键值对,同时以"="分隔键和值,("name=seven&age=19")。不支持二进制数据。
  • multipart/form-data:支持二进制数据(上传文件时必须指定)

2.JavaScript异步请求形式

我们知道 FormData 接口提供了一种表示表单数据的键值对 key/value 的构造方式,并且可以轻松的将数据通过XMLHttpRequest.send()方法发送出去,本接口和此方法都相当简单直接。如果送出时的编码类型被设为 "multipart/form-data",它会使用和表单一样的格式。

var formdata = new FormData(); // 创建FormData对象
formdata.append("name","laotie"); // 通过append()方法添加新的属性值
... // 更多方法请点下面链接

FormData接口

3.生命周期

上传组件也有它的生命周期

beforeUpload --> uploading --> fileUploaded 或者 uploadedError

4.代码草稿

本例中采用js异步请求的方式开发上传组件

<input type="file" name="file" @change.prevent="handleFileChange">
// 创建一个file类型的input,用于触发文件上传,后面可以把input隐藏掉,自定义好看的样式
// 自定义样式的时候可以用slot区分不同上传状态的样式(loading,success,defult)
const handleFileChange = (e:Event)=>{
  const target = e.target as HTMLInputElement
  const files = Array.from(target.files)// 注意这里取得的是一个类数组
  if(files){
    // 取得文件
    const uploadedFile = files[0]
    
    if(!validateFormat) return
    // ...这里只是提供一种思路,具体校验不再讲述
    // 在这里做一些上传文件前的校验,比如文件格式,大小等,
    // 不符合要求的话就不在继续发送请求
    
    const formData = new FormData()
    formData.append(uploadedFile.name,uploadedFile)
    
    axios.post('/upload',formData,{
      headers:{
         // 注意设置编码类型
        'Content-Type': 'multipart/form-data'
      }
    }).then(res=>{
      console.log('上传成功')
    }).catch(error =>{
      // 文件上传失败
    }).finally(()=>{
      // 文件上传完成,无论成功还是失败
      // 这里可以清除一下input.value
    })
  }
}

5.具体实现

// Upload.vue
<template>
  <div class="upload-container">
    <div class="upload-box" @click.prevent="triggerUpload" v-bind="$attrs">
      <slot name="loading" v-if="fileStatus==='loading'">
        <button class="btn btn-primary">上传中</button>
      </slot>
      <slot name="uploaded" v-else-if="fileStatus==='success'" :uploadedData="fileData">
        <button class="btn btn-primary">上传成功</button>
      </slot>
      <slot v-else name="default">
        <button class="btn btn-primary">点击上传</button>
      </slot>
    </div>
    <input type="file" class="file-input d-none" name="file" ref="uploadInput" @change="hanldeInput"/>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, PropType, watch } from 'vue'
import axios from 'axios'
type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
type FunctionProps = (file:File) => boolean
export default defineComponent({
  name: 'Upload',
  inheritAttrs: false,
  props: {
    // 上传的url
    action: {
      type: String,
      required: true
    },
    // 上传之前的校验,是一个返回布尔值的函数
    beforeUpload: {
      type: Function as PropType<FunctionProps>
    },
    // 上传好的数据,用来判断状态或做初始化展示
    uploadedData: {
      type: Object
    }
  },
  emits: ['file-uploaded-success', 'file-uploaded-error'],
  setup(props, ctx) {
    const uploadInput = ref<null | HTMLInputElement>(null)
    const fileStatus = ref<UploadStatus>(props.uploadedData ? 'success' : 'ready')
    const fileData = ref(props.uploadedData)
    watch(() => props.uploadedData, (val) => {
      if (val) {
        fileStatus.value = 'success'
        fileData.value = val
      }
    })
    const triggerUpload = () => {
      if (uploadInput.value) {
        uploadInput.value.click()
      }
    }
    const hanldeInput = (e:Event) => {
      const target = e.target as HTMLInputElement
      const files = target.files
      console.log(target)
      if (files) {
        const uploadFile = Array.from(files)
        const validateFormat = props.beforeUpload ? props.beforeUpload(uploadFile[0]) : true
        if (!validateFormat) return
        fileStatus.value = 'loading'
        const formData = new FormData()
        formData.append('file', uploadFile[0])
        axios.post(props.action, formData, {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        }).then(res => {
          console.log('文件上传成功', res)
          fileStatus.value = 'success'
          fileData.value = res.data
          ctx.emit('file-uploaded-success', res.data)
        }).catch(error => {
          console.log('文件上传失败', error)
          fileStatus.value = 'error'
          ctx.emit('file-uploaded-error', error)
        }).finally(() => {
          console.log('文件上传完成')
          if (uploadInput.value) {
            uploadInput.value.value = ''
          }
        })
      }
    }

    return {
      uploadInput,
      triggerUpload,
      hanldeInput,
      fileStatus,
      fileData
    }
  }
})
</script>

使用示例:

<template>
  <div class="create-post-page">
    <upload
      action="/upload"
      :beforeUpload="beforeUpload"
      :uploadedData="uploadedData"
      @file-uploaded-success="hanldeUploadSuccess"
      class="d-flex align-items-center justify-content-center bg-light text-secondary w-100 my-4"
      >
      <template #uploaded="slotProps">
        <div class="uploaded-area">
          <img :src="slotProps.uploadedData.data.url"/>
          <h3>点击重新上传</h3>
        </div>
       </template>
       <template #default>
         <h2>点击上传头图</h2>
       </template>
       <template #loading>
         <div class="d-flex">
          <div class="spinner-border text-secondary" role="status">
            <span class="sr-only"></span>
          </div>
         </div>
       </template>
    </upload>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
import Upload from '../components/Upload.vue'
import createMessage from '../components/createMessage'

export default defineComponent({
  name: 'CreatePost',
  components: { Upload },
  setup() {
    const uploadedData = ref() //创建一个响应式数据
    let imageId = ''
    onMounted(() => {
      ....
      // 这里有逻辑省略了,取到初始化数据image
      if (image) {
        uploadedData.value = { data: image }
      }
    })
    // 上传前校验,返回布尔值
    const beforeUpload = (file:File) => {
      const res = beforeUploadCheck(file, {
        format: ['image/jpeg', 'image/png'],
        size: 1
      })
      const { error, passed } = res
      if (error === 'format') {
        createMessage('上传图片只能是JPG/PNG格式!', 'error')
      }
      if (error === 'size') {
        createMessage('上传图片大小不能超过1MB', 'error')
      }
      return passed
    }
    // 上传成功后拿到imageId就可以进行后续处理,创建表单啥的
    const hanldeUploadSuccess = (res:ResponeseProps<ImageProps>) => {
      createMessage(`上传图片ID ${res.data._id}`, 'success')
      if (res.data._id) {
        imageId = res.data._id
      }
    }
    return {
      beforeUpload,
      hanldeUploadSuccess,
      uploadedData
    }
  }
})
</script>
<style>
.create-post-page{
  padding:0 20px 20px;
}
.create-post-page .upload-box{
  height:200px;
  cursor: pointer;
  overflow: hidden;
}
.create-post-page .upload-box img{
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.uploaded-area{
  position: relative;
}
.uploaded-area:hover h3{
  display: block;
}
.uploaded-area h3{
  display: none;
  position: absolute;
  color: #999;
  text-align: center;
  width: 100%;
  top:50%
}
</style>

以上就是vue 实现上传组件的详细内容,更多关于vue 上传组件的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
详解Vue中的自定义指令
Dec 07 Vue.js
vue3.0实现插件封装
Dec 14 Vue.js
Vue.extend 登录注册模态框的实现
Dec 29 Vue.js
Vue中强制组件重新渲染的正确方法
Jan 03 Vue.js
vue编写简单的购物车功能
Jan 08 Vue.js
Vue实现图书管理案例
Jan 20 Vue.js
Vue 3自定义指令开发的相关总结
Jan 29 Vue.js
vue首次渲染全过程
Apr 21 Vue.js
详解gantt甘特图可拖拽、编辑(vue、react都可用 highcharts)
Nov 27 Vue.js
Vue全局事件总线你了解吗
Feb 24 Vue.js
vue-cil之axios的二次封装与proxy反向代理使用说明
Apr 07 Vue.js
vue2的 router在使用过程中遇到的一些问题
Apr 13 Vue.js
vue基于Teleport实现Modal组件
Vue+Element UI实现概要小弹窗的全过程
vue-cli4.5.x快速搭建项目
Vue CLI中模式与环境变量的深入详解
springboot+VUE实现登录注册
May 27 #Vue.js
vue+springboot实现登录验证码
vue+spring boot实现校验码功能
May 27 #Vue.js
You might like
Thinkphp和onethink实现微信支付插件
2016/04/13 PHP
Node.js实战 建立简单的Web服务器
2012/03/08 Javascript
用jquery实现输入框获取焦点消失文字
2013/04/27 Javascript
js获取字符串最后一位方法汇总
2014/11/13 Javascript
JavaScript统计网站访问次数的实现代码
2015/11/18 Javascript
jQuery 获取遍历获取table中每一个tr中的第一个td的方法
2016/10/05 Javascript
浅析Jquery操作select
2016/12/13 Javascript
jquery 正整数数字校验正则表达式
2017/01/10 Javascript
jQuery命名空间与闭包用法示例
2017/01/12 Javascript
原生js实现无限循环轮播图效果
2017/01/20 Javascript
Vue自定义图片懒加载指令v-lazyload详解
2020/12/31 Javascript
JavaScript实现隐藏省略文字效果的方法
2017/04/27 Javascript
基于node.js的fs核心模块读写文件操作(实例讲解)
2017/09/10 Javascript
在小程序/mpvue中使用flyio发起网络请求的方法
2018/09/13 Javascript
IE8中jQuery.load()加载页面不显示的原因
2018/11/15 jQuery
Angular4.0动画操作实例详解
2019/05/10 Javascript
微信小程序如何修改radio和checkbox的默认样式和图标
2019/07/24 Javascript
小谈angular ng deploy的实现
2020/04/07 Javascript
用vue设计一个日历表
2020/12/03 Vue.js
python字典序问题实例
2014/09/26 Python
python matplotlib画图实例代码分享
2017/12/27 Python
Python 通过打码平台实现验证码的实现
2019/05/13 Python
pyqt5实现按钮添加背景图片以及背景图片的切换方法
2019/06/13 Python
pandas取出重复数据的方法
2019/07/04 Python
使用IPython或Spyder将省略号表示的内容完整输出
2020/04/20 Python
python中adb有什么功能
2020/06/07 Python
细说CSS3中的选择符
2008/10/17 HTML / CSS
医学专业大学生求职的自我评价
2013/11/27 职场文书
长安大学毕业生自我鉴定
2014/01/17 职场文书
新学期校长寄语
2014/01/18 职场文书
小学语文业务学习材料
2014/06/02 职场文书
2014年少先队工作总结
2014/12/03 职场文书
小学教师师德师风自我评价
2015/03/04 职场文书
详解jQuery的核心函数和事件处理
2022/02/18 jQuery
如何利用python创作字符画
2022/06/25 Python
MySQL中TIMESTAMP类型返回日期时间数据中带有T的解决
2022/12/24 MySQL