Skip to content

封装文件上传

更新: 2025/3/20 11:11:44 字数: 0 字

需求分析

  我们的前端使用的是 element-plus 组件库,他有自己的上传组件 el-upload,但是它有缺陷,也就是与我们的需求不符。

功能需求:

  1. 可以点击打开资源文件管理器,选择文件上传,也可以使用拖拽到指定位置进行文件上传。
  2. 当文件上传到指定个数后,隐藏上传框,显示已上传文件列表。
  3. 上传文件列表可以点击文件名或者点击下载按钮进行下载,可以点击删除按钮进行删除。

实现结果展示

功能实现效果:

  1. 可以点击打开资源文件管理器,选择文件上传。
  2. 可以将要上传的文件拖拽到对应的区域进行文件上传。
  3. 可以点击文件名或者点击下载按钮进行下载。
  4. 可以点击删除按钮进行删除。

封装文件上传组件

props 传值

vue
<script setup>
  import {useVModel} from "@vueuse/core";

  const $emit = defineEmits(['update:modelValue', 'update:disabled'])
  const props = defineProps({
    // fileList: [ {"name": "sql专题.docx", "diskName": "20250320103839_28.docx", "raw": "[object File]", "uid": 20473, "fileId": 20473 }],
    // url: 'xxxx'
    fileData: {
      type: Object,
      default: () => {
      }
    },
    disabled: {
      type: Boolean,
      default: false
    },
    limit: {
      type: Number,
      default: 1
    },
    // 允许上传的文件类型, 例如['png', 'jpg', 'jpeg']
    fileType: {
      type: Array || String,
      default: '*'
    },
    fileSize: {
      type: Number,
      default: 10,
    },
  })

  const modelValue = useVModel(props, "fileData", $emit)
</script>

展示结构

vue

<template>
  <div class="my-upload">
    <!--上传区域    -->
    <div
        ref="uploadRef"
        class="upload-input"
        :class="{ 'dragging': isDragging, 'maskLayer': disabled }"
        v-show="showUpload"
    >
      <input
          ref="fileInputRef"
          type="file"
          id="file-input"
          style="display: none"
          :accept="fileAccept"
          @change="onImageAdded"
          :disabled="disabled"
      />
      <div
          class="card upload-card"
          @click="openFileDialog"
      >
        <!--文件上传的 svg + 描述文字        -->
        <el-icon class="svg-icon">
          <upload-filled/>
        </el-icon>
        <div class="el-upload__text">
          拖拽文件到此处 或者 <em :class="{'strikethrough':disabled, 'link-type':!disabled}">点击上传</em>
        </div>
      </div>
    </div>
    <!--提示信息    -->
    <span v-show="showUpload" style="font-size: 12px; margin-left: 5px;">请上传 {{ fileTypeName }} 类型的文件,且大小不要超过 {{
        fileSize
      }} MB </span>
    <!--文件展示文件列表    -->
    <div>
      <div class="list-item" v-for="(item, index) in modelValue.fileList" :key="index">
        <div class="item-left link-type" :title="item.name" @click="handleOnlineView(item)">
          <el-icon style="margin-right: 3px">
            <Document/>
          </el-icon>
          {{ item.name }}
        </div>
        <el-icon class="link-type" @click="handlePreview(item)">
          <Download/>
        </el-icon>
        <el-button :disabled="disabled" type="text" size="small" @click="handleRemove(item)">删除</el-button>
      </div>
    </div>
    <!-- 图片预览 -->
    <el-image-viewer
        v-if="showImagePreview"
        :zoom-rate="1.2"
        @close="closePreview"
        :url-list="imgPreviewList"
    />
  </div>
</template>

点击进行文件上传

点击上传框会触发 openFileDialog 方法,然后调用 fileInputRef.click() 方法,打开资源文件管理器,选择文件上传。

js
// 打开文件选择框
function openFileDialog() {
  fileInputRef.value.click();
}

当我们选择完成文件后,会触发 input 上传的 change 事件,会执行 onImageAdded 方法,然后调用 uploadFile 方法,进行文件上传。

  1. 判断文件是否为空,并且文件数量是否超过限制,如果为 0 或者文件数量超过限制,直接返回.
  2. 判断文件后缀是否在允许的文件类型中.
  3. 判断文件大小是否符合要求.
js
// 上传文件
function onImageAdded() {
  let fileInput = fileInputRef.value;
  // 1. 判断文件是否为空,并且文件数量是否超过限制,如果为 0 或者文件数量超过限制,直接返回
  if (fileInput.files.length == 0 || (modelValue.value.fileList && modelValue.value.fileList.length >= props.limit)) {
    return;
  }
  let file = fileInput.files[0];
  
  // 2. 判断文件后缀是否在允许的文件类型中
  let fileExtension = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase();
  console.log(fileExtension)
  // 判断文件后缀是不是在允许的文件类型
  if (props.fileType !== '*' && props.fileType.indexOf(fileExtension) == -1) {
    ElMessage.error("文件格式不正确!");
    // 清除 input 中的文件
    fileInput.value = '';
    return;
  }

  // 3. 判断文件大小
  if (file.size / 1024 / 1024 > props.fileSize) {
    ElMessage.error("文件大小不能超过 " + props.fileSize + "MB!");
    // 清除 input 中的文件
    fileInput.value = '';
    return;
  }
  upload(file);
}

符合要求后,调用 upload 方法,进行文件上传。

下面是文件上传的逻辑,主要解决两个问题:

  1. 文件上传成功后,会返回一个文件 id,然后保存到 modelValue.value.fileId 中,用于后续的删除操作。
  2. 上传成功后如果是
js
// 文件上传
function upload(file) {
    // 上传图片
    let param = new FormData(); // 创建form对象
    param.append("file", file, file.name); // 通过append向form对象添加数据
    console.log("fileLenth", file.name.length); //
    if (file.name.length > 50) {
        ElMessage.error("文件名称长度不能超过 50 个字符!")
        return;
    }
    let config = {
        headers: {"Content-Type": "multipart/form-data"}
    };
    proxy.$modal.loading("文件上传中...")
    //  添加请求头
    service.post(modelValue.value.url, param, config)
        .then(req => {
            modelValue.value.fileList.push({
                name: req.filename,
                diskName: req.target,
                raw: file,
                uid: req.id,
                fileId: req.id,
                onlineUrl: req.onlineUrl
            })
            setTimeout(() => {
                isProgressVisible.value = false;
                isSuccessLabelVisible.value = true;
            }, 200);
        }).catch((err) => {
        isProgressVisible.value = false;
        // 清除 input 中的文件
        fileInputRef.value.value = '';
    }).finally(() => {
        proxy.$modal.closeLoading()
    })
}

删除文件

js
/**
 * 删除文件
 * @param file
 */
function handleRemove(item) {
  if (item.fileId) {
    deleteFile(item.fileId, item.diskName).then(res => {
      modelValue.value.fileList = modelValue.value.fileList.filter(v => {
        return v.fileId !== file.fileId
      })
      ElMessage.success(res.msg)
    })
  }
}

文件预览 (仅限于图片)

js
const handleOnlineView = (file) => {
    // 取出文件后缀
    const fileType = file.name.split('.').pop();
    console.log("fileType", fileType)
    // 判断文件类型是不是允许的图片类型
    if (['jpg', 'jpeg', 'png', 'gif'].includes(fileType) && file.name.url) {
        const url = import.meta.env.VITE_APP_ADDR + file.name.url
        showImage(url, file.name)
    } else {
        handlePreview(file)
    }
}

拖拽上传文件

js

const handleDragenter = (e) => {
    e.preventDefault()
    if(!isDragging.value) isDragging.value = true
}

const handleDragleave = (e) => {
    e.preventDefault()
    if(isDragging.value) isDragging.value = false
}

const handleDrop = (event) => {
    event.preventDefault();
    isDragging.value = false;
    const file = event.dataTransfer.files[0];
    // 触发文件上传 ———— 记得要校验哦
    upload(file);
}


onMounted(() => {
    // 当用户将拖拽的文件或元素释放(松开鼠标)到目标区域时触发。
    uploadRef.value.addEventListener('drop', handleDrop)
    // 当用户拖拽的文件或元素离开目标区域时触发。
    uploadRef.value.addEventListener('dragleave', handleDragleave)
    // 当用户拖拽的文件或元素首次进入目标区域时触发。
    uploadRef.value.addEventListener('dragenter', handleDragenter)
    // 当用户拖拽的文件或元素在目标区域内移动时触发。
    uploadRef.value.addEventListener('dragover', handleDragenter)
})

onUnmounted(() => {
    if (uploadRef.value) {
        uploadRef.value.removeEventListener('drop', handleDrop)
        uploadRef.value.removeEventListener('dragleave', handleDragleave)
        uploadRef.value.removeEventListener('dragenter', handleDragenter)
        uploadRef.value.removeEventListener('dragover', handleDragenter)
    }
})

道友再会.