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 分鐘)。