最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue3实现上传组件

    正文概述 掘金(小蝉儿)   2021-05-30   1013

    1.介绍

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

    效果如下图

    vue3实现上传组件

    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地址

    还没上传,后续补充


    起源地下载网 » vue3实现上传组件

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元