Files
blog-press/docs/Web/Vue/ElUpload.md
2026-05-27 11:26:16 +08:00

8.2 KiB

title, date
title date
Element Plus 上传器 2026-01-29

一、上传图片

<template>
  <el-upload
      v-model:file-list="imageFileList"
      list-type="picture-card"
      :on-preview="handlePreview"
      :limit="3"
      :before-upload="beforeUpload"
      :http-request="httpRequest"
      :on-exceed="handleExceed"
      :before-remove="beforeRemove"
      :on-remove="handleRemove"
  >
    <el-icon><Plus /></el-icon>
    <template #tip>
      <div class="el-upload__tip">
        请上传小于10M的图片
      </div>
    </template>

    <el-dialog v-model="dialogVisible">
      <el-image :src="dialogImageUrl" alt="Preview Image" />
    </el-dialog>
  </el-upload>
</template>

<script lang="ts" setup>
import { Plus } from '@element-plus/icons-vue'
import { onMounted, ref } from 'vue'
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import type { UploadRequestOptions } from 'element-plus'
import type { UploadProps, UploadUserFile, UploadRawFile } from 'element-plus'
import { getFileExtension, getFileHashName, validateImage } from '@/utils/file.ts'
import { uploadFileApi } from '@/apis/file.ts'

const imageFileList = ref<UploadUserFile[]>([])
const dialogImageUrl = ref('')
const dialogVisible = ref(false)

const baseUrl = ''
const apiImageList = [] as string[]

onMounted(async () => {
  imageFileList.value = []

  // 根据后端传来的List数据将信息添加文件列表中
  apiImageList.forEach((item, index) => {
    imageFileList.value.push({
      name: index.toString(),
      url: `${baseUrl}${item}`
    })
  })
})

const beforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
  const maxFileMax = 1024 * 1024 * 10
  const fileType = rawFile.type
  const fileName = rawFile.name
  const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase()

  if (!validateImage(fileType, fileExtension)) {
    ElMessage.error('请上传图片')
    return false
  }

  if (rawFile.size > maxFileMax) {
    ElMessage.error('请上传10M以内的文件!')
    return false
  }

  return true
}

const httpRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
  const loading = ElLoading.service({
    lock: true,
    text: '文件上传中 请等待',
    background: 'rgba(0, 0, 0, 0.7)'
  })

  try {
    // 重命名文件
    const filePath = `${baseUrl}/${getFileHashName(options.file.name)}.${getFileExtension(options.file.name)}`

    const data = new FormData()
    data.append('file', options.file)
    data.append('filePath', filePath)

    // 调用后端接口上传文件
    await uploadFileApi(data)
    apiImageList.push(filePath)
    ElMessage.success('上传成功')
  } catch (error) {
    console.log(error)
    ElMessage.error('上传失败 请联系管理员')
  } finally {
    loading.close()
  }
}

const handleExceed: UploadProps['onExceed'] = () => {
  ElMessage.warning('最多上传3张图片')
}

const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
  const index = imageFileList.value.findIndex((item) => item.uid === uploadFile.uid)
  apiImageList.splice(index, 1)
}

const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
  dialogImageUrl.value = uploadFile.url!
  dialogVisible.value = true
}

const beforeRemove: UploadProps['beforeRemove'] = () => {
  return ElMessageBox.confirm('确认删除该文件?').then(() => true, () => false)
}
</script>

二、上传视频

<template>
  <el-upload
      v-model:file-list="videoFileList"
      :limit="1"
      :before-upload="beforeUpload"
      :http-request="httpRequest"
      :on-exceed="handleExceed"
      :before-remove="beforeRemove"
      :on-remove="handleRemove"
  >
    <el-button type="primary">点击上传</el-button>
    <template #tip>
      <div class="el-upload__tip">
        请上传小于200M的视频
      </div>
    </template>
  </el-upload>
</template>

<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import type { UploadRequestOptions } from 'element-plus'
import type { UploadProps, UploadUserFile, UploadRawFile } from 'element-plus'
import { getFileExtension, getFileHashName, validateVideo } from '@/utils/file.ts'
import { uploadFileApi } from '@/apis/file.ts'

const videoFileList = ref<UploadUserFile[]>([])

let baseUrl = ''
const apiVideoUrl = ''

onMounted(async () => {
  videoFileList.value = []
  if (apiVideoUrl) {
    videoFileList.value.push({
      name: 'video',
      url: `${baseUrl}p/${apiVideoUrl}`
    })
  }
})

const beforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
  const maxFileMax = 1024 * 1024 * 200
  const fileType = rawFile.type
  const fileName = rawFile.name
  const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase()

  if (!validateVideo(fileType, fileExtension)) {
    ElMessage.error('请上传视频')
    return false
  }

  if (rawFile.size > maxFileMax) {
    ElMessage.error('请上传200M以内的文件!')
    return false
  }

  return true
}

const httpRequest: UploadProps['httpRequest'] = async (options: UploadRequestOptions): Promise<any> => {
  const loading = ElLoading.service({
    lock: true,
    text: '文件上传中 请等待',
    background: 'rgba(0, 0, 0, 0.7)'
  })

  try {
    const filePath = `${baseUrl}/${getFileHashName(options.file.name)}.${getFileExtension(options.file.name)}`

    const data = new FormData()
    data.append('file', options.file)
    data.append('filePath', filePath)

    await uploadFileApi(data)
    baseUrl = filePath
    ElMessage.success('上传成功')
  } catch (error) {
    console.log(error)
    ElMessage.error('上传失败 请联系管理员')
  } finally {
    loading.close()
  }
}

const handleExceed: UploadProps['onExceed'] = () => {
  ElMessage.warning('最多上传1个视频')
}

const beforeRemove: UploadProps['beforeRemove'] = () => {
  return ElMessageBox.confirm('确认删除该文件?').then(() => true, () => false)
}

const handleRemove: UploadProps['onRemove'] = () => {
  baseUrl = ''
}
</script>

三、工具类

import { hashSHA256 } from 'vue3-common/utils/cryptoUtil'

export const getFileHashName = (name: string) => {
  return hashSHA256(name)
}

export const getFileExtension = (name: string) => {
  return name.substring(name.lastIndexOf('.') + 1).toLowerCase()
}

export const validateExcel = (fileType: string, fileExtension: string): boolean => {
  // 常见的 Excel MIME 类型
  const excelMimeTypes = [
    'application/vnd.ms-excel', // .xls
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
    'application/vnd.ms-excel.sheet.macroEnabled.12', // .xlsm
    'text/csv' // CSV
  ]

  // 常见的 Excel 文件扩展名
  const excelExtensions = ['xls', 'xlsx', 'xlsm', 'csv']

  return excelMimeTypes.includes(fileType) || excelExtensions.includes(fileExtension.toLowerCase())
}

export const validateImage = (fileType: string, fileExtension: string): boolean => {
  // 常见的图片 MIME 类型
  const imageMimeTypes = [
    'image/jpeg', // JPEG
    'image/jpg', // JPG
    'image/png', // PNG
    'image/gif', // GIF
    'image/bmp', // BMP
    'image/webp', // WebP
    'image/svg+xml' // SVG
  ]

  // 常见的图片文件扩展名
  const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']

  return imageMimeTypes.includes(fileType) || imageExtensions.includes(fileExtension.toLowerCase())
}

export const validateVideo = (fileType: string, fileExtension: string): boolean => {
  // 常见的视频 MIME 类型
  const videoMimeTypes = [
    'video/mp4', // MP4
    'video/mpeg', // MPEG
    'video/quicktime', // MOV
    'video/x-msvideo', // AVI
    'video/x-matroska', // MKV
    'video/webm', // WebM
    'video/3gpp', // 3GP
    'video/3gpp2', // 3G2
    'video/x-flv', // FLV
    'video/mp2t', // TS (MPEG Transport Stream)
    'application/x-mpegURL', // M3U8 (HLS)
    'video/H264', // H264
    'video/H265' // H265/HEVC
  ]

  // 常见的视频文件扩展名
  const videoExtensions = [
    'mp4',
    'mpeg',
    'mpg',
    'mov',
    'avi',
    'mkv',
    'webm',
    '3gp',
    '3g2',
    'flv',
    'ts',
    'm3u8',
    'h264',
    'h265',
    'hevc'
  ]

  return videoMimeTypes.includes(fileType) || videoExtensions.includes(fileExtension.toLowerCase())
}