Element Plus文件管理:上传下载与预览功能实现

Element Plus文件管理:上传下载与预览功能实现

【免费下载链接】element-plus element-plus/element-plus: Element Plus 是一个基于 Vue 3 的组件库,提供了丰富且易于使用的 UI 组件,用于快速搭建企业级桌面和移动端的前端应用。 【免费下载链接】element-plus 项目地址: https://gitcode.com/GitHub_Trending/el/element-plus

在企业级应用开发中,文件管理是必不可少的功能模块。Element Plus作为基于Vue 3的企业级UI组件库,提供了强大且易用的文件上传、下载和预览功能。本文将深入探讨如何利用Element Plus实现完整的文件管理解决方案。

文件上传功能实现

基础文件上传

Element Plus的el-upload组件提供了完整的文件上传解决方案,支持多种上传方式和丰富的配置选项。

<template>
  <el-upload
    v-model:file-list="fileList"
    class="upload-demo"
    action="/api/upload"
    :headers="headers"
    :data="uploadData"
    :multiple="true"
    :limit="10"
    :on-preview="handlePreview"
    :on-remove="handleRemove"
    :on-success="handleSuccess"
    :on-error="handleError"
    :before-upload="beforeUpload"
  >
    <el-button type="primary">点击上传</el-button>
    <template #tip>
      <div class="el-upload__tip">
        支持jpg/png/pdf/docx格式,单个文件不超过10MB
      </div>
    </template>
  </el-upload>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { UploadProps, UploadUserFile } from 'element-plus'

const fileList = ref<UploadUserFile[]>([])
const headers = ref({
  Authorization: `Bearer ${localStorage.getItem('token')}`
})
const uploadData = ref({
  category: 'document',
  userId: '12345'
})

const handlePreview: UploadProps['onPreview'] = (file) => {
  console.log('预览文件:', file)
  // 实现文件预览逻辑
}

const handleRemove: UploadProps['onRemove'] = (file, fileList) => {
  console.log('移除文件:', file, fileList)
}

const handleSuccess: UploadProps['onSuccess'] = (response, file) => {
  ElMessage.success(`${file.name} 上传成功`)
  console.log('上传成功:', response)
}

const handleError: UploadProps['onError'] = (error, file) => {
  ElMessage.error(`${file.name} 上传失败: ${error.message}`)
}

const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
  const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']
  const maxSize = 10 * 1024 * 1024 // 10MB
  
  if (!allowedTypes.includes(rawFile.type)) {
    ElMessage.error('不支持的文件格式')
    return false
  }
  
  if (rawFile.size > maxSize) {
    ElMessage.error('文件大小不能超过10MB')
    return false
  }
  
  return true
}
</script>

拖拽上传实现

Element Plus支持拖拽上传功能,提供更好的用户体验。

<template>
  <el-upload
    v-model:file-list="fileList"
    class="upload-demo"
    action="/api/upload"
    :drag="true"
    :multiple="true"
    :on-change="handleChange"
  >
    <el-icon class="el-icon--upload"><upload-filled /></el-icon>
    <div class="el-upload__text">
      拖拽文件到此处或<em>点击上传</em>
    </div>
    <template #tip>
      <div class="el-upload__tip">
        支持拖拽上传,一次可上传多个文件
      </div>
    </template>
  </el-upload>
</template>

<script lang="ts" setup>
import { UploadFilled } from '@element-plus/icons-vue'
import type { UploadProps } from 'element-plus'

const handleChange: UploadProps['onChange'] = (file, fileList) => {
  console.log('文件变化:', file, fileList)
}
</script>

文件列表管理与缩略图展示

图片文件缩略图

<template>
  <el-upload
    v-model:file-list="fileList"
    action="/api/upload"
    list-type="picture-card"
    :on-preview="handlePictureCardPreview"
    :on-remove="handleRemove"
  >
    <el-icon><plus /></el-icon>
  </el-upload>

  <el-dialog v-model="dialogVisible">
    <img w-full :src="dialogImageUrl" alt="Preview Image" />
  </el-dialog>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps } from 'element-plus'

const fileList = ref([])
const dialogImageUrl = ref('')
const dialogVisible = ref(false)

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

const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
  console.log(uploadFile, uploadFiles)
}
</script>

自定义文件列表模板

<template>
  <el-upload
    v-model:file-list="fileList"
    action="/api/upload"
    :auto-upload="false"
  >
    <template #trigger>
      <el-button type="primary">选择文件</el-button>
    </template>
    
    <template #file="{ file }">
      <div class="custom-file-item">
        <el-icon><document /></el-icon>
        <span class="file-name">{{ file.name }}</span>
        <span class="file-size">{{ formatFileSize(file.size) }}</span>
        <el-button
          size="small"
          type="danger"
          @click="handleRemove(file)"
        >
          删除
        </el-button>
      </div>
    </template>
    
    <el-button class="ml-3" type="success" @click="submitUpload">
      开始上传
    </el-button>
  </el-upload>
</template>

<script lang="ts" setup>
import { Document } from '@element-plus/icons-vue'
import type { UploadInstance } from 'element-plus'

const uploadRef = ref<UploadInstance>()
const fileList = ref([])

const formatFileSize = (size: number) => {
  if (size === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(size) / Math.log(k))
  return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

const submitUpload = () => {
  uploadRef.value!.submit()
}

const handleRemove = (file) => {
  const index = fileList.value.indexOf(file)
  if (index !== -1) {
    fileList.value.splice(index, 1)
  }
}
</script>

<style scoped>
.custom-file-item {
  display: flex;
  align-items: center;
  padding: 8px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  margin-bottom: 8px;
}

.file-name {
  flex: 1;
  margin: 0 12px;
}

.file-size {
  color: #909399;
  margin-right: 12px;
}
</style>

文件预览功能实现

图片预览功能

Element Plus的el-image组件提供了强大的图片预览功能。

<template>
  <div class="image-preview-demo">
    <el-image
      v-for="(src, index) in imageList"
      :key="index"
      style="width: 100px; height: 100px; margin-right: 10px"
      :src="src"
      :preview-src-list="imageList"
      :initial-index="index"
      fit="cover"
    />
  </div>
</template>

<script lang="ts" setup>
const imageList = [
  'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
  'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
  'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg'
]
</script>

自定义文件预览器

对于非图片文件,我们可以实现自定义的预览功能。

<template>
  <div class="file-manager">
    <el-table :data="fileList" style="width: 100%">
      <el-table-column prop="name" label="文件名" />
      <el-table-column prop="size" label="大小" :formatter="formatSize" />
      <el-table-column prop="type" label="类型" />
      <el-table-column label="操作" width="200">
        <template #default="{ row }">
          <el-button size="small" @click="handlePreview(row)">
            预览
          </el-button>
          <el-button size="small" type="primary" @click="handleDownload(row)">
            下载
          </el-button>
          <el-button size="small" type="danger" @click="handleDelete(row)">
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <el-dialog v-model="previewVisible" :title="currentFile.name" width="80%">
      <div v-if="isImage(currentFile)" class="image-preview">
        <el-image :src="currentFile.url" fit="contain" style="max-height: 60vh" />
      </div>
      <div v-else-if="isPdf(currentFile)" class="pdf-preview">
        <iframe :src="currentFile.url" width="100%" height="600px" />
      </div>
      <div v-else class="unsupported-preview">
        <el-alert
          title="不支持在线预览"
          :description="`请下载后使用本地应用程序查看: ${currentFile.name}`"
          type="info"
          show-icon
        />
      </div>
    </el-dialog>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'

interface FileItem {
  id: string
  name: string
  url: string
  size: number
  type: string
}

const fileList = ref<FileItem[]>([
  {
    id: '1',
    name: 'example.jpg',
    url: 'https://example.com/files/example.jpg',
    size: 1024000,
    type: 'image/jpeg'
  },
  {
    id: '2',
    name: 'document.pdf',
    url: 'https://example.com/files/document.pdf',
    size: 2048000,
    type: 'application/pdf'
  },
  {
    id: '3',
    name: 'data.xlsx',
    url: 'https://example.com/files/data.xlsx',
    size: 512000,
    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  }
])

const previewVisible = ref(false)
const currentFile = ref<FileItem>({} as FileItem)

const formatSize = (row: any, column: any, cellValue: any) => {
  if (cellValue === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(cellValue) / Math.log(k))
  return parseFloat((cellValue / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

const isImage = (file: FileItem) => {
  return file.type.startsWith('image/')
}

const isPdf = (file: FileItem) => {
  return file.type === 'application/pdf'
}

const handlePreview = (file: FileItem) => {
  currentFile.value = file
  previewVisible.value = true
}

const handleDownload = (file: FileItem) => {
  // 实现文件下载逻辑
  const link = document.createElement('a')
  link.href = file.url
  link.download = file.name
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  ElMessage.success(`开始下载: ${file.name}`)
}

const handleDelete = (file: FileItem) => {
  ElMessage.info(`删除文件: ${file.name}`)
  // 实现文件删除逻辑
}
</script>

文件下载功能实现

前端文件下载方案

<template>
  <div class="download-manager">
    <el-button type="primary" @click="downloadFile(downloadData)">
      下载数据文件
    </el-button>
    
    <el-button @click="downloadAsBlob">
      下载为Blob
    </el-button>
    
    <el-button @click="downloadWithProgress">
      带进度条的下载
    </el-button>
  </div>
</template>

<script lang="ts" setup>
import { ElMessage, ElNotification } from 'element-plus'

// 方法1: 直接下载
const downloadFile = (data: any, filename = 'data.json') => {
  const blob = new Blob([JSON.stringify(data, null, 2)], {
    type: 'application/json'
  })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = filename
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  URL.revokeObjectURL(url)
}

// 方法2: 使用Blob下载大文件
const downloadAsBlob = async () => {
  try {
    const response = await fetch('/api/large-file')
    if (!response.ok) throw new Error('下载失败')
    
    const blob = await response.blob()
    const url = URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.download = 'large-file.zip'
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    URL.revokeObjectURL(url)
    
    ElMessage.success('文件下载完成')
  } catch (error) {
    ElMessage.error('下载失败')
  }
}

// 方法3: 带进度条的下载
const downloadWithProgress = async () => {
  try {
    const response = await fetch('/api/file-with-progress')
    if (!response.ok) throw new Error('下载失败')
    
    const contentLength = response.headers.get('content-length')
    const total = contentLength ? parseInt(contentLength) : 0
    let loaded = 0
    
    const reader = response.body!.getReader()
    const chunks: Uint8Array[] = []
    
    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      
      chunks.push(value)
      loaded += value.length
      
      // 更新进度
      const progress = total > 0 ? (loaded / total) * 100 : 0
      ElNotification({
        title: '下载进度',
        message: `已下载: ${progress.toFixed(1)}%`,
        duration: 0,
        showClose: false
      })
    }
    
    const blob = new Blob(chunks)
    const url = URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.download = 'progress-file.zip'
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    URL.revokeObjectURL(url)
    
    ElNotification.closeAll()
    ElMessage.success('文件下载完成')
  } catch (error) {
    ElNotification.closeAll()
    ElMessage.error('下载失败')
  }
}

const downloadData = {
  items: [
    { id: 1, name: 'Item 1', value: 100 },
    { id: 2, name: 'Item 2', value: 200 },
    { id: 3, name: 'Item 3', value: 300 }
  ],
  timestamp: new Date().toISOString()
}
</script>

完整文件管理系统实现

文件管理状态管理

<template>
  <div class="file-management-system">
    <!-- 上传区域 -->
    <el-upload
      v-model:file-list="state.uploadFiles"
      action="/api/upload"
      :multiple="true"
      :limit="20"
      :on-success="handleUploadSuccess"
      :on-error="handleUploadError"
      :before-upload="beforeUpload"
      :file-list="state.fileList"
    >
      <el-button type="primary">上传文件</el-button>
    </el-upload>

    <!-- 文件列表 -->
    <el-table :data="state.fileList" style="width: 100%" v-loading="state.loading">
      <el-table-column prop="name" label="文件名" min-width="200">
        <template #default="{ row }">
          <div class="file-name-cell">
            <el-icon :size="20">
              <component :is="getFileIcon(row.type)" />
            </el-icon>
            <span class="file-name">{{ row.name }}</span>
          </div>
        </template>
      </el-table-column>
      
      <el-table-column prop="size" label="大小" width="100" :formatter="formatSize" />
      <el-table-column prop="type" label="类型" width="120" />
      <el-table-column prop="uploadTime" label="上传时间" width="180">
        <template #default="{ row }">
          {{ formatTime(row.uploadTime) }}
        </template>
      </el-table-column>
      
      <el-table-column label="操作" width="250" fixed="right">
        <template #default="{ row }">
          <el-button size="small" @click="handlePreview(row)" v-if="canPreview(row)">
            预览
          </el-button>
          <el-button size="small" type="primary" @click="handleDownload(row)">
            下载
          </el-button>
          <el-button size="small" type="danger" @click="handleDelete(row)">
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 预览对话框 -->
    <el-dialog v-model="state.previewVisible" :title="state.currentFile.name" width="80%">
      <file-preview :file="state.currentFile" />
    </el-dialog>
  </div>
</template>

<script lang="ts" setup>
import { reactive, onMounted } from 'vue'
import { 
  Document, 
  Picture, 
  VideoPlay, 
  Headset, 
  Notebook,
  Zip
} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'

interface FileItem {
  id: string
  name: string
  url: string
  size: number
  type: string
  uploadTime: string
  thumbnail?: string
}

interface State {
  fileList: FileItem[]
  uploadFiles: any[]
  loading: boolean
  previewVisible: boolean
  currentFile: FileItem
}

const state = reactive<State>({
  fileList: [],
  uploadFiles: [],
  loading: false,
  previewVisible: false,
  currentFile: {} as FileItem
})

// 文件图标映射
const fileIcons = {
  'image/': Picture,
  'video/': VideoPlay,
  'audio/': Headset,
  'text/': Notebook,
  'application/pdf': Document,
  'application/zip': Zip,
  'application/x-zip-compressed': Zip,
  'default': Document
}

const getFileIcon = (fileType: string) => {
  for (const [key, icon] of Object.entries(fileIcons)) {
    if (fileType.startsWith(key)) {
      return icon
    }
  }
  return fileIcons.default
}

const canPreview = (file: FileItem) => {
  const previewableTypes = [
    'image/',
    'application/pdf',
    'text/'
  ]
  return previewableTypes.some(type => file.type.startsWith(type))
}

const formatSize = (row: any, column: any, cellValue: any) => {
  if (cellValue === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(cellValue) / Math.log(k))
  return parseFloat((cellValue / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

const formatTime = (timestamp: string) => {
  return new Date(timestamp).toLocaleString()
}

const handlePreview = (file: FileItem) => {
  state.currentFile = file
  state.previewVisible = true
}

const handleDownload = async (file: FileItem) => {
  try {
    const response = await fetch(file.url)
    const blob = await response.blob()
    const url = URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.download = file.name
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    URL.revokeObjectURL(url)
    
    ElMessage.success(`下载成功: ${file.name}`)
  } catch (error) {
    ElMessage.error('下载失败')
  }
}

const handleDelete = async (file: FileItem) => {
  try {
    await ElMessageBox.confirm(
      `确定要删除文件 "${file.name}" 吗?`,
      '删除确认',
      { type: 'warning' }
    )
    
    // 调用删除API
    await fetch(`/api/files/${file.id}`, { method: 'DELETE' })
    
    // 从列表中移除
    state.fileList = state.fileList.filter(f => f.id !== file.id)
    ElMessage.success('文件删除成功')
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}

const handleUploadSuccess = (response: any, file: any) => {
  ElMessage.success(`${file.name} 上传成功`)
  // 刷新文件列表
  loadFileList()
}

const handleUploadError = (error: Error, file: any) => {
  ElMessage.error(`${file.name} 上传失败: ${error.message}`)
}

const beforeUpload = (file: File) => {
  const maxSize = 50 * 1024 * 1024 // 50MB
  if (file.size > maxSize) {
    ElMessage.error('文件大小不能超过50MB')
    return false
  }
  return true
}

const loadFileList = async () => {
  state.loading = true
  try {
    const response = await fetch('/api/files')
    state.fileList = await response.json()
  } catch (error) {
    ElMessage.error('加载文件列表失败')
  } finally {
    state.loading = false
  }
}

onMounted(() => {
  loadFileList()
})
</script>

<style scoped>
.file-management-system {
  padding: 20px;
}

.file-name-cell {
  display: flex;
  align-items: center;
  gap: 8px;
}

.file-name {
  font-weight: 500;
}

.upload-demo {
  margin-bottom: 20px;
}
</style>

最佳实践与性能优化

1. 分片上传大文件

// 大文件分片上传实现
const chunkedUpload = async (file: File, onProgress?: (progress: number) => void) => {
  const CHUNK_SIZE = 2 * 1024 * 1024 // 2MB
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE)
  const fileId = generateFileId()
  
  for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
    const start = chunkIndex * CHUNK_SIZE
    const end = Math.min(start + CHUNK_SIZE, file.size)
    const chunk = file.slice(start, end)
    
    const formData = new FormData()
    formData.append('file', chunk)
    formData.append('chunkIndex', chunkIndex.toString())
    formData.append('totalChunks', totalChunks.toString())
    formData.append('fileId', fileId)
    formData.append('fileName', file.name)
    
    try {
      await fetch('/api/upload-chunk', {
        method: 'POST',
        body: formData
      })
      
      const progress = ((chunkIndex + 1) / totalChunks) * 100
      onProgress?.(progress)
    } catch (error) {
      throw new Error(`上传分片 ${chunkIndex + 1}/${totalChunks} 失败`)
    }
  }
  
  // 合并分片
  await fetch('/api/merge-chunks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ fileId, fileName: file.name })
  })
}

const generateFileId = () => {
  return Date.now().toString(36) + Math.random().toString(36).substr(2)
}

2. 文件类型验证与安全处理

// 安全的文件类型验证
const validateFileType = (file: File, allowedTypes: string[]) => {
  const fileType = file.type.toLowerCase()
  
  // 检查MIME类型
  if (!allowedTypes.includes(fileType)) {
    throw new Error('不支持的文件类型')
  }
  
  // 检查文件扩展名
  const extension = file.name.split('.').pop()?.toLowerCase()
  const allowedExtensions = allowedTypes.map(type => 
    type.split('/')[1] || type.split('/')[0]
  )
  
  if (extension && !allowedExtensions.includes(extension)) {
    throw new Error('文件扩展名与类型不匹配')
  }
  
  return true
}

// 病毒扫描集成
const scanForViruses = async (file: File) => {
  const formData = new FormData()
  formData.append('file', file)
  
  const response = await fetch('/api/virus-scan', {
    method: 'POST',
    body: formData
  })
  
  const result = await response.json()
  if (!result.clean) {
    throw new Error('文件可能包含恶意内容')
  }
}

总结

Element Plus提供了完整的文件管理解决方案,通过el-uploadel-image组件可以轻松实现文件上传、下载和预览功能。本文介绍了:

  1. 基础文件上传:支持多文件、拖拽上传、文件验证
  2. 文件列表管理:缩略图展示、自定义文件模板
  3. 文件预览功能:图片预览、PDF预览、自定义预览器
  4. 文件下载方案:直接下载、Blob下载、带进度下载
  5. 完整文件管理系统:状态管理、操作集成
  6. 性能优化:分片上传、安全验证、病毒扫描

通过合理使用Element Plus的组件和API,可以构建出功能丰富、用户体验优秀的文件管理系统,满足企业级应用的需求。记得在实际项目中根据具体需求进行调整和优化,确保文件管理的安全性和性能。

【免费下载链接】element-plus element-plus/element-plus: Element Plus 是一个基于 Vue 3 的组件库,提供了丰富且易于使用的 UI 组件,用于快速搭建企业级桌面和移动端的前端应用。 【免费下载链接】element-plus 项目地址: https://gitcode.com/GitHub_Trending/el/element-plus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值