vue图片上传组件

发布时间 2023-07-13 15:24:21作者: lix_uan
<template>
  <div
    @dragover.prevent
    @dragenter.prevent="drag = true"
    @dragleave.prevent="drag = false"
    @drop.prevent="onDrop"
    @click="input.click"
    v-show="status === 'upload'"
    :class="drag ? 'upload-active' : ''"
    class="box upload">
    <input @change="selectChange" ref="input" type="file" />
  </div>
  <div v-show="status === 'uploading'" class="box uploading">
    <img :src="imgUrl" />
    <p>{{ progress }}%</p>
    <progress id="file" max="100" :value="progress"></progress>
    <p>文件上传中...</p>
    <button @click="cancel">取消</button>
  </div>
  <div v-show="status === 'finished'" class="box finished">
    <div @click="cancel" class="cancel"><i-material-symbols-close /></div>
    <img :src="imgUrl" />
  </div>
</template>
<script setup>
  import axios from 'axios'

  const status = ref('upload') // upload、uploading、finished
  const input = ref()
  const progress = ref(0)
  const imgUrl = ref('')
  const drag = ref(false)
  let cancelUpload = null

  const selectChange = e => {
    if (!e.target.files.length) return
    const file = e.target.files[0]
    if (!validateFile(file)) return

    status.value = 'uploading'
    imgUrl.value = URL.createObjectURL(file) // blob

    cancelUpload = upload(
      file,
      val => (progress.value = val),
      resp => {
        status.value = 'finished'
        imgUrl.value = resp.data.url
      }
    )
  }

  const validateFile = file => {
    const suffixArr = ['jpg', 'jpeg', '.png']
    const maxSize = 1024 * 1024 * 5
    if (file.name && file.size) {
      const suffix = file.name.split('.').pop()
      if (suffixArr.includes(suffix) && file.size <= maxSize) return true
    }
    return false
  }

  const upload = (file, onProgress, onFinish) => {
    // 获取上传进度,取消请求:https://juejin.cn/post/7127547763679559693
    const controller = new AbortController()
    const data = new FormData()
    data.append('file', file)
    data.append('bar', 'foo')

    axios
      .post('/upload', data, {
        headers: { 'Content-Type': 'multipart/form-data;charset=utf-8' },
        onUploadProgress(e) {
          // 获取上传进度
          if (e.lengthComputable) {
            onProgress(Math.round((e.loaded * 100) / e.total))
          }
        }
      })
      .then(resp => onFinish(resp))

    // 取消上传
    return () => controller.abort()
  }

  const cancel = () => {
    cancelUpload && cancelUpload()
    status.value = 'upload'
    progress.value = 0
    drag.value = false
  }

  const onDrop = e => {
    selectChange({ target: { files: e.dataTransfer.files } })
  }
</script>
<style scoped lang="less">
  .box {
    position: relative;
    margin: 100px auto;
    width: 250px;
    height: 250px;
    box-sizing: border-box;
    border-radius: 5px;
  }

  img {
    position: absolute;
    left: 0;
    top: 0;
    z-index: -1;
    width: 100%;
    object-fit: cover;
  }
  .upload {
    border: 2px dashed #ccc;

    &:hover {
      border-color: #409eff;
      cursor: pointer;
    }

    input {
      display: none;
    }

    &::after {
      content: '+';
      position: absolute;
      font-size: 100px;
      color: #aaa;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
  }

  .upload-active {
    background-color: rgba(64, 158, 255, 0.3);
    border-color: #409eff;
  }

  .uploading {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    border: 2px solid #ccc;
    background-color: rgba(0, 0, 0, 0.5);

    p {
      color: #fff;
      font-size: 18px;
      margin: 10px;
    }

    progress {
      width: 200px;
      height: 8px;
      border-radius: 85px;
      background-color: #fff;

      &::-moz-progress-bar {
        background-color: #409eff;
      }
    }

    button {
      all: initial;
      color: #fff;
      cursor: pointer;
    }
  }

  .finished {
    border: 2px solid #ccc;
    cursor: pointer;

    .cancel {
      position: absolute;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 25px;
      height: 25px;
      background-color: rgba(0, 0, 0, 0.6);
      right: 0;
      top: 0;
      border-radius: 3px 3px 0 3px;
      font-size: 22px;
      color: #fff;
    }
  }
</style>