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 相关文章推荐
springboot+vue实现文件上传下载
Nov 17 Vue.js
ESLint 是如何检查 .vue 文件的
Nov 30 Vue.js
对vue生命周期的深入理解
Dec 03 Vue.js
vuex Module将 store 分割成模块的操作
Dec 07 Vue.js
Vue中inheritAttrs的使用实例详解
Dec 31 Vue.js
通过vue.extend实现消息提示弹框的方法记录
Jan 07 Vue.js
vue-quill-editor插入图片路径太长问题解决方法
Jan 08 Vue.js
如何在vue 中使用柱状图 并自修改配置
Jan 21 Vue.js
vue实现轮播图帧率播放
Jan 26 Vue.js
Vue实现下拉加载更多
May 09 Vue.js
vue2实现provide inject传递响应式
May 21 Vue.js
vue postcss-px2rem 自适应布局
May 15 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 和 XML: 使用expat函数(三)
2006/10/09 PHP
浅析is_writable的php实现
2013/06/18 PHP
php时区转换转换函数
2014/01/07 PHP
PHP中怎样防止SQL注入分析
2014/10/23 PHP
Javascript的一种模块模式
2008/03/22 Javascript
jQuery 选择器理解
2010/03/16 Javascript
Safari5中alert的无限循环BUG
2011/04/07 Javascript
jquery文字上下滚动的实现方法
2013/03/22 Javascript
jQuery如何取id有.的值一般的方法是取不到的
2014/04/18 Javascript
JavaScript获取表单内所有元素值的方法
2015/04/02 Javascript
JavaScript每天必学之数组和对象部分
2016/09/17 Javascript
Javascript 判断两个IP是否在同一网段实例代码
2016/11/28 Javascript
微信小程序扫描二维码获取信息实例详解
2019/05/07 Javascript
详解ES6 export default 和 import语句中的解构赋值
2019/05/28 Javascript
vue中进行微博分享的实例讲解
2019/10/14 Javascript
[01:02:25]2014 DOTA2华西杯精英邀请赛5 24 NewBee VS VG
2014/05/25 DOTA
使用python搭建Django应用程序步骤及版本冲突问题解决
2013/11/19 Python
python生成日历实例解析
2014/08/21 Python
总结Python编程中三条常用的技巧
2015/05/11 Python
在Django框架中运行Python应用全攻略
2015/07/17 Python
python 读取摄像头数据并保存的实例
2018/08/03 Python
Python实现深度遍历和广度遍历的方法
2019/01/22 Python
python实现发送form-data数据的方法详解
2019/09/27 Python
Python 实现OpenCV格式和PIL.Image格式互转
2020/01/09 Python
python3读取autocad图形文件.py实例
2020/06/05 Python
使用python对excel表格处理的一些小功能
2021/01/25 Python
英国领先的电子、技术和办公用品购物网站:Ebuyer
2018/04/04 全球购物
Yahoo的PHP面试题
2014/05/26 面试题
财务部副经理岗位职责范本
2014/06/17 职场文书
实习护士自荐信
2014/06/21 职场文书
四风个人对照检查材料思想汇报
2014/09/25 职场文书
党的群众路线教育实践活动心得体会范文
2014/11/05 职场文书
单位政审意见范文
2015/06/04 职场文书
JAVA SpringMVC实现自定义拦截器
2022/03/16 Python
草系十大最强宝可梦,纸片人上榜,榜首大家最熟悉
2022/03/18 日漫
Redis基本数据类型哈希Hash常用操作命令
2022/06/01 Redis