vue3实现上传组件

vue3实现上传组件

技术杂谈小彩虹2021-07-19 15:00:16100A+A-

1.介绍

虽然前端UI框架大都提供文件上传的组件,以及很多插件可供选择,工作中可能不需要我们手写一个上传组件,但是从零封装组件对学习是很有助益的。下文为大家介绍使用Vue3+TypeScript实现的一个文件上传的功能,目前只实现上传等基本功能,后续会逐渐对功能进行扩展,持续更新。

效果如下图

load.gif

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:支持二进制数据(上传文件时必须指定)
  1. 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>

6 git地址

还没上传,后续补充

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们