Skip to content

单文件上传使用说明 ​

管理端普通文件 / 图片 / 小体积批量文件通过 admin/src/components/upload/ 下两个组件上传,都走后端 POST /api/admin/common/upload/file 落盘 + checkInstant 秒传预检。业务不要自己写 axios 上传。

场景组件
用户点击按钮触发(表单里的"上传头像"等)components/upload(默认插槽作为触发区)
拖拽区 + 选完自动开始上传(素材库)components/upload/dropzone

大文件 / 需要分片 / 需要中断,请改用 分片上传。 后端存储/接口详见 后端-存储引擎。业务表只保存 file_hash_id(统一文件哈希主键),访问地址通过 /api/uploads/<id> 动态拼装。

一、共同流程 ​

两个组件内部完全同链路,仅 UI 与触发时机不同:

选择文件 → SparkMD5(10MB 分块) → POST /common/upload/checkInstant 秒传预检
    ├─ 命中:直接返回 { file_hash_id, url, ... },不再上传
    └─ 未命中:FormData → POST /common/upload/file,onUploadProgress 上报进度
  • 鉴权统一走请求头 token: <JWT> + version: <config.version>,不是 Authorization。
  • 回传结构与 el-upload 一致:emit('change', [{ name, response }]),response.data 即后端 CommonUploadFileResp(file_hash_id / name / ext / size / url)。
  • 任一文件失败:feedback.msgError + emit('error') 中断后续。

二、components/upload ​

默认插槽作为触发区,点击后弹出文件选择;可选 showProgress 打开上传进度对话框(每个文件独立进度条 + 秒传/失败标签)。

Props ​

属性类型默认说明
urlstring''相对路径会拼 config.baseUrl + config.urlPrefix;空则用默认 /common/upload/file;以 http 开头视为绝对 URL 直接使用
extstring[][]允许扩展名(无点),如 ['png','jpg'],生成 <input accept>
multiplebooleantrue是否允许多选
limitnumber10多选时上限,超出会直接拒收整批并提示
dataobject{}随文件一起提交的业务字段(追加到 FormData)
showProgressbooleanfalse是否弹出上传进度对话框

Emits / Expose ​

  • change(fileLists: { name, response }[]) — 全部成功或秒传命中。
  • error() — 任一文件失败即触发。
  • defineExpose({ triggerSelect }) — 外部可编程触发文件选择。

用法 ​

vue
<template>
    <upload :ext="['png','jpg']" :multiple="false" @change="onUploaded">
        <el-button>选择头像</el-button>
    </upload>
</template>

<script setup lang="ts">
import Upload from '@/components/upload/index.vue'
const onUploaded = (list: any[]) => {
    const r = list[0]?.response?.data     // 统一后端结构
    form.avatar = r?.file_hash_id ?? ''   // 业务表存哈希 ID
}
</script>

三、components/upload/dropzone(拖拽版) ​

外层是虚线拖拽区,选完自动开始上传;无进度弹窗,改为内嵌进度条(多文件时累加计算总进度)。

Props / Emits ​

属性类型默认说明
extstring[][]允许扩展名,生成 <input accept>
multiplebooleantrue是否允许多选
limitnumber10上限;0 视作无上限(内部用 999);超出会截断到上限继续上传(与 upload 拒收整批不同)
  • change(fileLists) / error() 语义与 upload 完全一致。

用法(素材库案例) ​

vue
<!-- components/material/picker.vue 内 -->
<UploadDropzone :ext="['png','jpg','gif','webp']" @change="handleUpload" />
ts
import UploadDropzone from '@/components/upload/dropzone.vue'

四、后端接口 ​

用途方法路径请求
秒传预检POST/api/admin/common/upload/checkInstant{ file_md5, file_name }
上传落盘POST/api/admin/common/upload/filemultipart/form-data,字段 file + data 追加项
文件流访问GET/api/uploads/<file_hash_id>[/<fileName>]自动 MIME + 长缓存

鉴权:请求头 token: <JWT>;管理端接口路径带 /api/admin 前缀,由 config.urlPrefix 决定。

五、常见坑 ​

  1. 业务表存 file_hash_id,别存 URL:URL 由 ossDomain + /api/uploads/<id> 动态拼(见 stores/modules/app.ts),换域名/协议时不用回补数据。
  2. limit 语义差异:upload 超出直接拒收整批并 msgError 终止;dropzone 是截断到上限继续上传。批量场景按 UI 需要选组件。
  3. 重复选择同一文件:upload 在 handleFileChange 末尾会 input.value = '' 清空,允许再次选同名文件;自定义触发时若绕过它,可能选不中同一文件。
  4. MD5 计算占用主线程:SparkMD5 走 FileReader.readAsArrayBuffer,几百 MB 图片会明显卡顿;极限优化把两个组件的 calcMD5 迁到 Web Worker 即可。
  5. "取消"不是真中断:upload 关进度弹窗只是 uploadSeq++,让在途任务在下一个检查点静默退出(已 in-flight 的 axios 请求仍会跑完,结果不再 emit);dropzone 无取消入口。若需要真中断,改用 分片上传(Upload.abort())。
  6. 秒传前提是历史曾成功上传过同 MD5:命中后不写文件、只回哈希 ID;首次上传的 MD5 由后端落 x_common_file_hash 表,不需要业务侧显式登记。

六、相关代码位置 ​

  • 前端组件:
    • admin/src/components/upload/index.vue
    • admin/src/components/upload/dropzone.vue
    • admin/src/utils/auth.ts(getToken)、admin/src/config/index.ts(baseUrl / urlPrefix / version)
  • 后端接口:server/app/controller/admin_ctl/common_controller/upload.go;驱动与存储:server/plugin/storage/。
  • 现网调用点:admin/src/components/material/picker.vue(UploadDropzone)。