SwUploadVideo is a chunked video upload component. Upload logic is fully injected by the caller
via the uploadFn and queryFn props — the component has no built-in endpoint, making it
compatible with any backend protocol.
After upload completes, the component can optionally poll for transcoding results via queryFn
(controlled by pollVideo). The default slot exposes current state, enabling fully custom trigger
UI including drag-and-drop zones.
Basic usage (button)
Simplest form: button picks a video → chunk progress → transcode polling → playable preview when done.
Basic (button + progress + playable preview)
<!-- accept restricts formats (default 'video/*'); multiple allows multi-select —
each file goes through "upload + transcode polling" independently, emitting @success one by one -->
<SwUploadVideo
v-slot="{ open, pending }"
:upload-fn="chunkUpload"
:query-fn="queryVideo"
accept="video/mp4"
multiple
@success="(r) => list.push(r)"
>
<SwButton :disabled="pending" @click="() => open()">Batch upload videos</SwButton>
</SwUploadVideo>
Custom trigger area (#default slot)
The "+ upload card": the default slot fully takes over the trigger UI, drawing upload / transcode progress inside the card.
+ upload card (slot customization)
Drag-and-drop upload
The slot's isOver highlights the drop zone; the progress bar and transcode-wait hint are also slot-drawn.
Drag + progress + transcode polling
Skip transcoding poll (pollVideo=false)
Use when you only need the raw chunk-upload result (ChunkSuccess) without waiting for transcoding.
No transcode wait (ChunkSuccess returned directly)
Error handling
The error event fires when a chunk fails (retries exhausted).
Chunk failure feedback (@error)
Controlled list (fileList + #file + maxCount + beforeUpload)
With v-model:file-list the component manages the uploaded list: successful results are appended automatically, and the #file slot renders each item with preview + removal; maxCount caps the count (an over-limit selection is rejected as a batch and emits exceed), and beforeUpload runs per-file validation (size / duplicate name / type — return false to drop).
<template>
<SwUploadVideo
v-model:file-list="videos"
:upload-fn="chunkUpload"
:query-fn="queryVideo"
:max-count="3"
:before-upload="(f) => f.size <= maxSize || (useToast().warning('视频过大'), false)"
multiple
@exceed="useToast().warning('最多上传 3 个视频')"
>
<template #default="{ open, pending, progress }">
<SwButton :loading="pending" @click="open">
{{ pending ? `上传中 ${progress}%` : '选择视频' }}
</SwButton>
</template>
<template #file="{ file, remove }">
<span>{{ file.id }} <SwButton aria-label="删除" @click="remove">✕</SwButton></span>
</template>
</SwUploadVideo>
</template>
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
uploadFn | Function | ✓ | — | Chunked upload function; see VideoUploadOptions |
queryFn | Function | ✓ | — | Transcoding query; returns Promise<VideoQueryResult> |
accept | string | 'video/*' | Accepted file types | |
chunkSize | number | 2097152 | Chunk size in bytes (default 2 MB) | |
retries | number | 3 | Retry attempts on chunk failure | |
multiple | boolean | false | Allow multiple file selection | |
pollVideo | boolean | true | Poll for transcoding result after upload | |
fileList | ChunkSuccess | VideoQueryResult[] | undefined | Controlled list of uploaded files (v-model:file-list). When provided, successful results are appended automatically; pair with the #file slot for preview + removal. Omit to keep the "single-shot shell" behavior | |
maxCount | number | undefined | Maximum file count (including the current fileList). A selection that would exceed it is rejected as a whole batch and emits exceed | |
beforeUpload | (file: File, batch: File[]) => boolean | Promise<boolean> | undefined | Per-file veto hook: return false to drop that file (others still upload; if all are dropped the batch is aborted). Size / duplicate-name / custom checks and user feedback belong in the hook |
Type Definitions
VideoUploadOptions
interface VideoUploadOptions {
chunkSize?: number
retries?: number
onProcess?: (result: ChunkProgress) => void
onSuccess?: (result: { id: string; file_md5: string; file: File }) => void
onError?: (result: { file: File; error: unknown }) => void
onBefore?: (file: File) => void
}
interface ChunkProgress {
file: File
processed_num: number
chunk_total: number
chunk_index: number
file_md5: string
}
VideoQueryResult
interface VideoQueryResult {
id: string
file_m3u8: string
file_duration: string
file_preview_m3u8: string
file_status: string
width: number
height: number
}
Events
| Name | Payload | Description |
|---|---|---|
progress | [pct: number] | Chunk upload progress, 0–100 |
success | [result: ChunkSuccess | VideoQueryResult] | ChunkSuccess when pollVideo=false; VideoQueryResult otherwise |
error | [err: Error] | Fired on upload or poll failure |
update:fileList | ChunkSuccess | VideoQueryResult[] | Controlled list changed (result appended / removed via remove()), carries the new list |
remove | (file: ChunkSuccess | VideoQueryResult, index: number) | The #file slot's remove() was called |
exceed | File[] | Selection exceeded maxCount, carries the selected files (the whole batch was rejected) |
Slots
default (scoped slot)
| Scope variable | Type | Description |
|---|---|---|
isOver | boolean | File is being dragged over the zone |
pending | boolean | Upload or poll is in progress |
progress | number | Current upload progress (0–100) |
open | () => void | Programmatically open file picker |
file | { file, index, remove } | Rendered once per item when fileList is provided (preview / delete UI is yours to draw); remove() removes the item and emits remove |
Notes
- Both
uploadFnandqueryFnare required and must be implemented by the consumer. chunkSizedefaults to 2 MB (2097152 bytes); must match the backend's expected chunk size.- When
pollVideo=true, thesuccessevent fires only after transcoding completes, with aVideoQueryResultpayload. - When
pollVideo=false,successfires immediately after chunk upload with{ id, file_md5, file }.