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项目实现主题切换的多种方法
Nov 26 Vue.js
详解vue-cli项目在IE浏览器打开报错解决方法
Dec 10 Vue.js
Vue实现摇一摇功能(兼容ios13.3以上)
Jan 26 Vue.js
Vue-router编程式导航的两种实现代码
Mar 04 Vue.js
vue-router中hash模式与history模式的区别
Jun 23 Vue.js
一起来看看Vue的核心原理剖析
Mar 24 Vue.js
vue route新窗口跳转页面并且携带与接收参数
Apr 10 Vue.js
vue代码分块和懒加载非必要资源文件
Apr 11 Vue.js
vue elementUI表格控制对应列
Apr 13 Vue.js
vue使用localStorage持久性存储实现评论列表
Apr 14 Vue.js
vue动态绑定style样式
Apr 20 Vue.js
vue elementUI批量上传文件
Apr 26 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
php,ajax实现分页
2008/03/27 PHP
PHP 得到根目录的 __FILE__ 常量
2008/07/23 PHP
php strcmp使用说明
2010/04/22 PHP
PHP实现的博客欢迎提示功能(很特别哦)
2014/06/05 PHP
php使用百度ping服务代码实例
2014/06/19 PHP
php根据某字段对多维数组进行排序的方法
2015/03/07 PHP
Laravel 5框架学习之表单
2015/04/08 PHP
PHP实现根据银行卡号判断银行
2015/04/29 PHP
js数据验证集合、js email验证、js url验证、js长度验证、js数字验证等简单封装
2010/05/15 Javascript
js 中 document.createEvent的用法
2010/08/29 Javascript
js动态设置鼠标事件示例代码
2013/10/30 Javascript
Javascript中封装window.open解决不兼容问题
2014/09/28 Javascript
JS控制表单提交的方法
2015/07/09 Javascript
利用JavaScript判断浏览器类型及版本
2016/08/23 Javascript
JS触摸与手势事件详解
2017/05/09 Javascript
AngularJS实现表单验证功能详解
2017/10/12 Javascript
Nodejs异步流程框架async的方法
2019/06/07 NodeJs
ES6基础之 Promise 对象用法实例详解
2019/08/22 Javascript
Vue.js仿Select下拉框效果
2020/02/18 Javascript
原生JS实现拖拽效果
2020/12/04 Javascript
在Django中创建URLconf相关的通用视图的方法
2015/07/20 Python
python爬虫之爬取百度音乐的实现方法
2019/08/24 Python
关于Python-faker的函数效果一览
2019/11/28 Python
Python ADF 单位根检验 如何查看结果的实现
2020/06/03 Python
python实现PolynomialFeatures多项式的方法
2021/01/06 Python
用HTML5.0制作网页的教程
2010/05/30 HTML / CSS
印尼在线精品店:Berrybenka.com
2016/10/22 全球购物
倩碧香港官方网站:Clinique香港
2017/11/13 全球购物
Sephora丝芙兰马来西亚官方网站:国际化妆品购物
2018/03/15 全球购物
viagogo波兰票务平台:演唱会、体育比赛、戏剧门票
2018/04/23 全球购物
Ruby如何创建一个线程
2013/03/10 面试题
初二政治教学反思
2014/01/12 职场文书
网站客服岗位职责
2014/04/05 职场文书
员工生日会策划方案
2014/06/14 职场文书
建筑节能汇报材料
2014/08/22 职场文书
Node-Red实现MySQL数据库连接的方法
2021/08/07 MySQL