编辑 | blame | 历史 | 原始文档

本地文件上传 README

本文档基于以下实现整理:

  • src/components/AttachmentUpload/file/index.vue
  • src/components/AttachmentUpload/image/index.vue
  • src/components/AttachmentPreview/image/index.vue
  • src/components/Dialog/FileList.vue
  • src/api/basicData/common.js
  • src/api/publicApi/commonFile.js

相关组件已在 src/main.js 中注册为全局组件,可直接在页面中使用:

  • FileUpload
  • ImageUpload
  • ImagePreview
  • FileListDialog

1. 功能概览

当前这套上传能力主要分为 4 部分:

  1. FileUpload:普通文件上传,支持拖拽、批量上传、预览、删除
  2. ImageUpload:图片上传,支持图片墙展示、预览、删除
  3. ImagePreview:图片列表预览展示
  4. FileListDialog:业务附件弹窗,支持查询、上传、删除、下载

上传底层统一走接口:

  • POST /common/upload

对应方法在 src/api/basicData/common.js

uploadFile(data)

2. 上传接口说明

2.1 通用上传接口

文件上传组件和图片上传组件都调用了:

import { uploadFile } from '@/api/basicData/common'

接口特征:

  • 请求方式:POST
  • 地址:/common/upload
  • 请求类型:multipart/form-data
  • 支持 FormData 批量上传
  • 默认字段名:files

组件内部会这样组装参数:

const formData = new FormData()
validFiles.forEach((file) => {
  formData.append(props.uploadFieldName, file.raw)
})

2.2 上传返回值要求

上传成功后,组件会尝试从以下结构中提取数组:

  • response
  • response.data
  • response.data.data
  • response.payload
  • response.payload.data
  • response.rows
  • response.result

因此后端返回数组时,上面任意一种结构都可以被识别。

组件展示时常用到的字段有:

  • 文件名:name / originalFilename / fileName / uidFilename
  • 文件地址:url / downloadURL
  • 图片地址:url / previewURL / previewUrl
  • 主键:id

建议上传接口返回的单项对象尽量包含:

{
  id: 1,
  originalFilename: 'demo.pdf',
  downloadURL: 'https://xxx/demo.pdf',
  previewURL: 'https://xxx/demo.png'
}

3. FileUpload 文件上传组件

组件路径:

src/components/AttachmentUpload/file/index.vue

3.1 基础用法

<template>
  <FileUpload v-model:file-list="fileList" />
</template>

<script setup>
import { ref } from 'vue'

const fileList = ref([])
</script>

3.2 常用属性

属性 说明 类型 默认值
fileList 绑定文件列表 Array []
limit 最大上传数量 Number 10
fileSize 单个文件大小限制,单位 MB Number 50
fileType 允许上传的文件类型,如 ['pdf', 'docx'] Array []
buttonText 上传提示文案 String 单击选择文件
disabled 是否禁用 Boolean false
uploadFieldName FormData 字段名 String files
index 表格/列表行模式下的当前行索引 Number -1
childrenKey 行内挂载字段名 String files

3.3 事件

事件 说明
update:fileList 文件列表变化时触发
change 文件列表变化时触发,返回最新列表

3.4 限制规则

组件内已实现:

  • 文件数量限制
  • 文件大小限制
  • 文件类型校验
  • 上传中状态锁定
  • 失败后自动清空当前选择队列

例如限制 PDF/Word:

<FileUpload
  v-model:file-list="fileList"
  :limit="5"
  :file-size="20"
  :file-type="['pdf', 'doc', 'docx']"
/>

3.5 返回数据格式建议

FileUpload 更适合接收这样的列表:

[
  {
    id: 1,
    originalFilename: '合同.pdf',
    downloadURL: 'https://xxx/contract.pdf'
  }
]

因为组件打开文件时会优先读取:

url || downloadURL || previewURL || previewUrl

3.6 行内嵌套模式

如果上传组件放在表格某一行中,可配合 indexchildrenKey 使用:

<FileUpload
  v-model:file-list="tableData"
  :index="scope.$index"
  children-key="files"
/>

此时组件会自动读写:

tableData[scope.$index].files

4. ImageUpload 图片上传组件

组件路径:

src/components/AttachmentUpload/image/index.vue

4.1 基础用法

<template>
  <ImageUpload v-model:file-list="imageList" />
</template>

<script setup>
import { ref } from 'vue'

const imageList = ref([])
</script>

4.2 默认行为

图片上传组件默认:

  • 最多上传 10
  • 单张不超过 10MB
  • 默认支持格式:png / jpg / jpeg / webp
  • 使用 picture-card 风格展示
  • 点击缩略图可预览大图

4.3 常用示例

<ImageUpload
  v-model:file-list="imageList"
  :limit="9"
  :file-size="5"
  :file-type="['png', 'jpg', 'jpeg']"
  button-text="上传图片"
/>

4.4 返回数据格式建议

ImageUpload 展示图片时优先读取:

url || previewURL || previewUrl

建议后端返回:

[
  {
    id: 1,
    originalFilename: '现场图片.jpg',
    previewURL: 'https://xxx/image.jpg'
  }
]

4.5 行内嵌套模式

图片组件同样支持行内字段写回:

<ImageUpload
  v-model:file-list="tableData"
  :index="scope.$index"
  children-key="images"
/>

默认写回字段为 images

5. ImagePreview 图片预览组件

组件路径:

src/components/AttachmentPreview/image/index.vue

5.1 基础用法

<ImagePreview :file-list="imageList" />

5.2 可配置项

属性 说明 类型 默认值
fileList 图片列表 Array []
thumbSize 缩略图大小 Number 72
gap 缩略图间距 Number 10

5.3 数据要求

组件会过滤没有 previewURL 的项,因此如果要正常显示,建议至少包含:

[
  {
    previewURL: 'https://xxx/image.jpg',
    originalFilename: '图片1.jpg'
  }
]

如果列表为空,组件显示“暂无图片”。

6. FileListDialog 附件弹窗组件

组件路径:

src/components/Dialog/FileList.vue

这个组件适合业务表单或详情页里的“附件管理”场景,能力包括:

  • 根据业务主键查询附件列表
  • 打开弹窗查看附件
  • 在弹窗中继续上传附件
  • 删除附件
  • 下载附件

6.1 组件属性

属性 说明 类型 默认值
visible 是否显示弹窗 Boolean 必传
recordType 业务类型 String ''
recordId 业务主键 Number 0
title 弹窗标题 String 附件
width 弹窗宽度 String 50%
showActions 是否显示下载/删除操作列 Boolean true

6.2 基础用法

<template>
  <el-button @click="visible = true">查看附件</el-button>

  <FileListDialog
    v-model:visible="visible"
    record-type="salesLedger"
    :record-id="rowId"
    title="附件列表"
  />
</template>

<script setup>
import { ref } from 'vue'

const visible = ref(false)
const rowId = ref(1001)
</script>

6.3 组件内部依赖的接口

FileListDialog 本身不直接调用 commonFile.js,而是依赖:

  • attachmentList
  • createAttachment
  • deleteAttachment

处理逻辑为:

  1. 打开弹窗后根据 recordType + recordId 查询附件
  2. 点击“上传附件”后,内部使用 AttachmentUpload/file 先上传到 /common/upload
  3. 上传成功后,将返回的文件对象和已有列表一起提交给 createAttachment
  4. 删除时调用 deleteAttachment
  5. 下载时直接 window.open(downloadURL, '_blank')

因此这里要特别注意:

  • recordTyperecordId 必须是有效业务标识
  • 上传成功返回的数据,需要能被 createAttachment 直接接收
  • 列表中的下载地址字段应为 downloadURL

7. commonFile.js 说明

文件路径:

src/api/publicApi/commonFile.js

当前文件提供的是公共文件删除接口:

delCommonFile(ids)
delCommonFileInvoiceLedger(ids)

对应接口:

  • /commonFile/delCommonFile
  • /invoiceLedger/delFile

这两个方法更适合已经和具体业务绑定后的“删除已保存附件”场景,不负责上传文件本身。

示例:

import { delCommonFile } from '@/api/publicApi/commonFile'

await delCommonFile([1, 2, 3])

8. 推荐使用方式

8.1 普通业务表单上传附件

<FileUpload v-model:file-list="form.storageBlobDTOs" />

提交表单时直接带上:

{
  ...form,
  storageBlobDTOs: form.storageBlobDTOs
}

8.2 图片类业务

<ImageUpload v-model:file-list="form.images" />
<ImagePreview :file-list="form.images" />

8.3 已落库附件管理

<FileListDialog
  v-model:visible="dialogVisible"
  :record-type="recordType"
  :record-id="recordId"
/>

适合详情页、审批页、台账页这类“查看并维护当前业务附件”的场景。

9. 注意事项

  1. FileUploadImageUpload 只是负责把文件先传到 /common/upload,不等于已经和业务数据绑定。
  2. 如果业务需要持久化附件关系,仍需要在保存表单时把返回的文件对象提交给业务接口。
  3. ImagePreview 当前只识别 previewURL,如果后端只返回 url,预览组件将不会展示,最好统一补齐 previewURL
  4. FileListDialog 依赖 recordTyperecordId 查询和保存附件关系,新增业务时要先确认后端关联接口可用。
  5. 删除本地列表项和删除已保存附件是两件事:
  • 上传组件里的删除:只会从当前前端绑定数组中移除
  • commonFile.js / deleteAttachment:才是真正调用后端删除

10. 一套最常见的页面写法

<template>
  <el-form :model="form">
    <el-form-item label="附件">
      <FileUpload v-model:file-list="form.storageBlobDTOs" :limit="5" />
    </el-form-item>

    <el-form-item label="现场图片">
      <ImageUpload v-model:file-list="form.images" :limit="9" />
    </el-form-item>

    <el-form-item label="图片预览">
      <ImagePreview :file-list="form.images" />
    </el-form-item>
  </el-form>
</template>

<script setup>
import { ref } from 'vue'

const form = ref({
  storageBlobDTOs: [],
  images: [],
})
</script>

如果你的目标是“先上传,再跟业务一起保存”,这套写法可以直接作为基础模板使用。