useUploader()
useUploader() 把 framework 内置的 createUploader 工厂与当前项目的 SystemStore(接口地址)+
UserStore(当前用户 ID)自动接线,返回开箱即用的上传器实例。无需手动传 endpoint / token / userId,
每次调用都从 store 读最新值,避免 stale closure。
前置条件
- 项目已启用 framework(
modules: ['@swift-web/framework'])+ 处于已登录状态- 后端已下发
system/info,并包含upload_image_url、upload_file_url、upload_file_query_url、upload_file_max_length、upload_image_max_length等字段(uploader 实际接口地址完全由后端 systemInfo 控制)- 仅浏览器可用(依赖
File/FormData/Blob),SSR 阶段不要调用
基础用法
<script setup lang="ts">
const uploader = useUploader()
async function handleAvatar(file: File) {
// 第二个参数 thumbnail:true=生成缩略图,false=原图
const { id, value, width, height } = await uploader.uploadImage(file, false)
// value 是后端返回的 URL;id 用于业务侧引用
console.log({ id, value, width, height })
}
</script>
返回值
useUploader() 返回一个对象,含以下四个方法:
| 方法 | 类型签名 | 用途 |
|---|---|---|
uploadImage | (file: File, thumbnail: boolean) => Promise<ImageUploadResult> | 单次 POST 上传图片,本地预校验大小 |
uploadVideo | (files: File[], options?: VideoUploadOptions) => void | 并发分片上传多个视频文件(默认 2 MB / 片、重试 3 次) |
uploadFile | (file: File, options?: FileUploadOptions) => Promise<FileUploadResult> | 分片上传普通文件(PDF / 文档 / zip 等),无转码二段 |
queryVideo | (id: string, retries?: number) => Promise<VideoQueryResult> | 轮询后端转码完成(每 3s 一次,默认最多 20 次) |
uploadImage
POST 单次请求,本地先校验字节数(图片按 upload_image_max_length,未配置才回退 upload_file_max_length)(超出抛 Error('文件超出大小')),返回值如下:
type ImageUploadResult = {
id: string // 后端文件 ID(业务侧外键)
value: string // 文件访问 URL
width: number // 解析自后端 file_info "1280x720"
height: number
}
<script setup lang="ts">
const uploader = useUploader()
async function onPick(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0]
if (!file) return
try {
const cover = await uploader.uploadImage(file, false)
form.value.cover = { id: cover.id, url: cover.value, w: cover.width, h: cover.height }
} catch (err) {
// 可能错误:'文件超出大小' / 后端 status='n' / '图片尺寸解析失败:file_info="..."'
console.error(err)
}
}
</script>
uploadVideo
callback 风格,调用立即返回 void。对每个 File 独立实例化 UploadChunk,并发互不阻塞。
type VideoUploadOptions = {
retries?: number // 单分片最大重试次数,默认 3
chunkSize?: number // 分片字节数,默认 1024 * 1024 * 2(2 MB)
onBefore?: (file: File) => void
onProcess?: (r: ChunkProgress) => void
onSuccess?: (r: ChunkSuccess) => void
onError?: (r: ChunkError) => void
}
interface ChunkProgress { file: File; processed_num: number; chunk_total: number; chunk_index: number; file_md5: string }
interface ChunkSuccess { file: File; id: string; file_md5: string }
interface ChunkError { file: File; error: unknown; chunk_total: number; chunk_index: number; file_md5: string }
典型组合:上传视频 → 拿到 id → 调 queryVideo 等转码完成 → 写表单。
<script setup lang="ts">
const uploader = useUploader()
const progress = ref(0)
const videoMeta = ref<Awaited<ReturnType<typeof uploader.queryVideo>> | null>(null)
function pickVideo(file: File) {
uploader.uploadVideo([file], {
onProcess: ({ processed_num, chunk_total }) => {
progress.value = Math.round((processed_num / chunk_total) * 100)
},
onSuccess: async ({ id }) => {
videoMeta.value = await uploader.queryVideo(id)
},
onError: ({ error }) => {
console.error('视频上传失败', error)
},
})
}
</script>
uploadFile
上传普通文件(PDF / 文档 / zip 等非图非视频)。与 uploadVideo 复用同一分片端点(upload_file_url),
但无转码二段——后端分片成功响应即含可访问 URL,因此不需要 queryVideo。范式贴近 uploadImage:
返回 Promise,await 直接拿结果,失败 reject Error。
type FileUploadOptions = {
chunkSize?: number // 分片字节数,默认 1024 * 1024 * 2(2 MB)
retries?: number // 单分片最大重试次数,默认 3
concurrency?: number // 并发分片数,不传则用默认 6
onProcess?: (r: ChunkProgress) => void // 进度回调(复用 upload-chunk 的 ChunkProgress)
onBefore?: (file: File) => void // 开始上传前(md5 计算前)回调
}
type FileUploadResult = {
id: string // 后端文件 ID(业务侧外键)
value: string // 文件访问 URL(对齐 uploadImage 的 value 语义)
file_md5: string // 整文件 MD5
}
<script setup lang="ts">
const uploader = useUploader()
const progress = ref(0)
async function pickFile(file: File) {
try {
const { id, value } = await uploader.uploadFile(file, {
onProcess: ({ processed_num, chunk_total }) => {
progress.value = Math.round((processed_num / chunk_total) * 100)
},
})
form.value.attachment = { id, url: value }
} catch (err) {
// 失败终态:网络故障 / 后端 status='n' / 空文件等,统一 reject Error
console.error('文件上传失败', err)
}
}
</script>
uploadFile vs uploadVideo:两者同端点、同分片机制,差别在结果获取方式——
uploadFile无转码、await拿 URL;uploadVideo走回调 + 后续queryVideo等转码。 上传图片用uploadImage、视频用uploadVideo、其余文件用uploadFile。
queryVideo
视频上传成功后,后端转码需要时间。queryVideo 每隔 3 秒请求一次转码状态,直到 file_m3u8 可用或重试耗尽。
type VideoQueryResult = {
id: string
file_m3u8: string // 转码后的播放地址
file_duration: string
file_preview_m3u8: string
file_status: string
width: number // 解析自 file_quality "1920x1080"
height: number
}
// 重试默认 20 次(≈ 60s);耗尽抛 Error('查询重试次数已用尽')
const result = await uploader.queryVideo(id, 30)
注意事项
- 接口地址完全由后端
system/info驱动,前端无配置项可改。本地若想换上传后端,需调 mock 的system/info。 uploadVideo不是 Promise——它通过onSuccess/onError回调通知结果。需要 await 的写法把它包一层 Promise 即可。- 单次失败的分片由
UploadChunk内部按retries重试,业务侧拿到onError时已是终态。 queryVideo静默吞掉单次失败重试(只在重试耗尽时 reject),开发期可加全局request日志看真实 HTTP 失败。- 上传超时由
runtimeConfig.public.swiftWebFramework.request.uploadTimeout控制,默认600_000(10 分钟)。