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 相关文章推荐
深入了解Vue3模板编译原理
Nov 19 Vue.js
用vue设计一个日历表
Dec 03 Vue.js
vue-router定义元信息meta操作
Dec 07 Vue.js
vue中利用three.js实现全景图的完整示例
Dec 07 Vue.js
vue 使用class创建和清除水印的示例代码
Dec 25 Vue.js
vue 实现图片懒加载功能
Dec 31 Vue.js
Vue如何实现变量表达式选择器
Feb 18 Vue.js
vue如何批量引入组件、注册和使用详解
May 12 Vue.js
Vue.js中v-for指令的用法介绍
Mar 13 Vue.js
vue cli4中mockjs在dev环境和build环境的配置详情
Apr 06 Vue.js
使用vue判断当前环境是安卓还是IOS
Apr 12 Vue.js
vue二维数组循环嵌套方式 循环数组、循环嵌套数组
Apr 24 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
判“新”函数:得到今天与明天的秒数
2006/10/09 PHP
利用discuz实现PHP大文件上传应用实例代码
2008/11/14 PHP
php数组函数序列之array_combine() - 数组合并函数使用说明
2011/10/29 PHP
深入php处理整数函数的详解
2013/06/09 PHP
php生成缩略图填充白边(等比缩略图方案)
2013/12/25 PHP
php数据库的增删改查 php与javascript之间的交互
2017/08/31 PHP
根据分辩率调用不同的CSS.
2007/01/08 Javascript
20个最新的jQuery插件
2012/01/13 Javascript
表头固定(利用jquery实现原理介绍)
2012/11/08 Javascript
JavaScript实现两个Table固定表头根据页面大小自行调整
2014/01/03 Javascript
js/jquery解析json和数组格式的方法详解
2014/01/09 Javascript
jQuery判断checkbox(复选框)是否被选中以及全选、反选实现代码
2014/02/21 Javascript
JavaScript判断textarea值是否为空并给出相应提示
2014/09/04 Javascript
javascript删除元素节点removeChild()用法实例
2015/05/26 Javascript
理解 JavaScript Scoping &amp; Hoisting(二)
2015/11/18 Javascript
Vue关于组件化开发知识点详解
2020/05/13 Javascript
简单了解Vue computed属性及watch区别
2020/07/10 Javascript
vue中后端做Excel导出功能返回数据流前端的处理操作
2020/09/08 Javascript
python strip() 函数和 split() 函数的详解及实例
2017/02/03 Python
Python标准库笔记struct模块的使用
2018/02/22 Python
CentOS 7下安装Python3.6 及遇到的问题小结
2018/11/08 Python
python3爬虫学习之数据存储txt的案例详解
2019/04/24 Python
浅谈Python大神都是这样处理XML文件的
2019/05/31 Python
深入了解Python装饰器的高级用法
2020/08/13 Python
解决CSS3 transition-delay 属性默认值0不带单位失效的问题
2020/10/29 HTML / CSS
在阿尔卑斯山或希腊度过快乐假期:Alpine Elements
2019/12/28 全球购物
教师年度考核评语
2014/04/28 职场文书
人力资源求职信
2014/05/25 职场文书
经济国贸专业求职信
2014/06/18 职场文书
学生吸烟检讨书
2014/09/14 职场文书
婚内分居协议书范文
2014/11/26 职场文书
大学生学年个人总结
2015/02/15 职场文书
幼师个人总结范文
2015/02/28 职场文书
2015年采购员工作总结
2015/04/27 职场文书
遗失证明范文
2015/06/19 职场文书
MySQL中rank() over、dense_rank() over、row_number() over用法介绍
2022/03/23 MySQL