CosUpload 腾讯云上传组件
基于 TDesign Vue Next 和腾讯云 COS SDK 的增强上传组件,提供图片、视频、文件等多种上传模式。
功能特性
- 📤 腾讯云 COS 集成 - 直接上传文件到腾讯云对象存储
- 🖼️ 多种展示模式 - 支持图片、视频、文件列表等多种展示形式
- 🔄 拖拽排序 - 图片模式支持拖拽调整顺序
- 🎬 视频处理 - 支持视频上传、预览和封面选择
- 📁 文件管理 - 文件列表模式支持多文件管理
- 🎨 主题定制 - 多种预设主题,满足不同场景需求
- 📱 响应式设计 - 自适应不同屏幕尺寸
- 🔒 安全上传 - 支持临时密钥和签名认证
- ⏳ 进度显示 - 实时显示上传进度
- 🚀 性能优化 - 支持大文件上传和断点续传
基础使用
基础的文件上传功能,支持多种常见的上传主题。
文件上传(默认主题)
已上传文件:
[]
图片上传
点击上传图片
输入框模式
查看代码
vue
<template>
<div class="demo-container">
<t-space style="width: 100%" direction="vertical" :size="40">
<div class="demo-section">
<h4>文件上传(默认主题)</h4>
<CosUpload v-model="fileValue" path="/demo/files/" :cos-options="cosOptions" />
<div class="demo-result">
<p>已上传文件:</p>
<pre>{{ JSON.stringify(fileValue, null, 2) }}</pre>
</div>
</div>
<div class="demo-section">
<h4>图片上传</h4>
<CosUpload
v-model="imageValue"
theme="image"
path="/demo/images/"
:cos-options="cosOptions"
accept="image/*"
:max="3"
/>
</div>
<div class="demo-section">
<h4>输入框模式</h4>
<CosUpload
v-model="inputValue"
theme="file-input"
path="/demo/input/"
:cos-options="cosOptions"
/>
</div>
</t-space>
</div>
</template>
<script setup lang="ts">
import { CosUpload } from 't-design-pro'
import { Space as TSpace } from 'tdesign-vue-next'
import { useData } from 'vitepress'
import { ref, watch } from 'vue'
import getAuthorization from './getAuthorization'
const { isDark } = useData()
watch(
isDark,
(newVal) => {
document.documentElement.setAttribute('theme-mode', newVal ? 'dark' : 'light')
},
{
immediate: true
}
)
const Bucket = import.meta.env.VITE_COS_BUCKET || 'demo-bucket'
const Region = import.meta.env.VITE_COS_REGION || 'ap-shanghai'
const cosOptions = {
Bucket,
Region,
getAuthorization
}
const fileValue = ref([])
const imageValue = ref([])
const inputValue = ref([])
</script>
<style scoped>
.demo-container {
padding: 16px;
}
.demo-section {
padding: 20px;
background: var(--td-bg-color-container);
border-radius: var(--td-radius-medium);
}
.demo-section h4 {
margin-bottom: 16px;
color: var(--td-text-color-primary);
}
.demo-result {
margin-top: 16px;
padding: 12px;
background: var(--td-bg-color-component);
border-radius: var(--td-radius-default);
}
.demo-result p {
margin-bottom: 8px;
color: var(--td-text-color-secondary);
font-size: 12px;
}
.demo-result pre {
font-size: 12px;
color: var(--td-text-color-primary);
white-space: pre-wrap;
word-break: break-all;
}
</style>
主题模式
展示不同主题模式的效果,包括图片排序、视频上传、文件列表等。
图片排序模式
支持拖拽调整图片顺序,最多上传9张
视频上传
支持视频预览和封面选择,限制60秒以内
文件列表模式
详细的文件列表展示,支持多文件管理
音频上传(本地预览)
音频文件本地预览,不上传到 COS
自定义上传按钮
通过 custom 主题和默认插槽自定义
小尺寸图片上传
使用 size="small" 显示小尺寸上传框
点击上传图片
禁用状态
设置 disabled 禁用上传功能
查看代码
vue
<template>
<div class="demo-container">
<t-space direction="vertical" style="width: 100%" :size="40">
<div class="demo-section">
<h4>图片排序模式</h4>
<p class="demo-desc">支持拖拽调整图片顺序,最多上传9张</p>
<CosUpload
v-model="sortableImages"
theme="image-sortable"
path="/demo/sortable/"
:cos-options="cosOptions"
accept="image/*"
:max="9"
:abridge-name="[8, 6]"
/>
</div>
<div class="demo-section">
<h4>视频上传</h4>
<p class="demo-desc">支持视频预览和封面选择,限制60秒以内</p>
<CosUpload
v-model="videoValue"
v-model:loading="videoLoading"
theme="video"
path="/demo/videos/"
:cos-options="cosOptions"
accept="video/*"
:video-maxduration="60"
:max-file-size="{ size: 50, unit: 'MB' }"
:poster="videoPoster"
@update-poster="updateVideoPoster"
/>
<div v-if="videoLoading" class="loading-tip"><t-loading size="small" /> 视频上传中...</div>
</div>
<div class="demo-section">
<h4>文件列表模式</h4>
<p class="demo-desc">详细的文件列表展示,支持多文件管理</p>
<CosUpload
v-model="filesList"
theme="files-list"
path="/demo/files-list/"
:cos-options="cosOptions"
:max="5"
:max-file-size="{ size: 10, unit: 'MB' }"
:oss-extend-options="{
ContentDisposition: 'attachment'
}"
>
<template #filesListButtonTitle> 选择文件(最多5个) </template>
</CosUpload>
</div>
<div class="demo-section">
<h4>音频上传(本地预览)</h4>
<p class="demo-desc">音频文件本地预览,不上传到 COS</p>
<CosUpload
v-model="audioValue"
theme="audio"
path="/demo/audio/"
:cos-options="cosOptions"
accept="audio/*"
/>
</div>
<div class="demo-section">
<h4>自定义上传按钮</h4>
<p class="demo-desc">通过 custom 主题和默认插槽自定义</p>
<CosUpload
v-model="customValue"
theme="custom"
path="/demo/custom/"
:cos-options="cosOptions"
:disabled="customDisabled"
>
<t-button
:theme="customValue.length ? 'default' : 'primary'"
:variant="customValue.length ? 'outline' : 'base'"
:loading="customDisabled"
>
<template #icon>
<cloud-upload-icon v-if="!customValue.length" />
<refresh-icon v-else />
</template>
{{ customValue.length ? '重新上传' : '选择文件上传' }}
</t-button>
</CosUpload>
<div v-if="customValue.length" class="custom-result">
已上传:{{ customValue[0]?.name || customValue[0]?.url }}
</div>
</div>
<div class="demo-section">
<h4>小尺寸图片上传</h4>
<p class="demo-desc">使用 size="small" 显示小尺寸上传框</p>
<CosUpload
v-model="smallImages"
theme="image"
size="small"
path="/demo/small/"
:cos-options="cosOptions"
accept="image/*"
:max="4"
/>
</div>
<div class="demo-section">
<h4>禁用状态</h4>
<p class="demo-desc">设置 disabled 禁用上传功能</p>
<CosUpload
v-model="disabledValue"
theme="image-sortable"
path="/demo/disabled/"
:cos-options="cosOptions"
:disabled="true"
:max="3"
/>
</div>
</t-space>
</div>
</template>
<script setup lang="ts">
import { CosUpload } from 't-design-pro'
import { CloudUploadIcon, RefreshIcon } from 'tdesign-icons-vue-next'
import { Button as TButton, Loading as TLoading, Space as TSpace } from 'tdesign-vue-next'
import { useData } from 'vitepress'
import { ref, watch } from 'vue'
import getAuthorization from './getAuthorization'
const { isDark } = useData()
watch(
isDark,
(newVal) => {
document.documentElement.setAttribute('theme-mode', newVal ? 'dark' : 'light')
},
{
immediate: true
}
)
const Bucket = import.meta.env.VITE_COS_BUCKET || 'demo-bucket'
const Region = import.meta.env.VITE_COS_REGION || 'ap-shanghai'
const cosOptions = {
Bucket,
Region,
getAuthorization
}
// 不同主题的数据
const sortableImages = ref([])
const videoValue = ref([])
const videoPoster = ref([])
const videoLoading = ref(false)
const filesList = ref([])
const audioValue = ref([])
const customValue = ref([])
const customDisabled = ref(false)
const smallImages = ref([])
const disabledValue = ref([
{ url: 'https://tdesign.gtimg.com/demo/demo-image-1.png', name: 'demo1.png' },
{ url: 'https://tdesign.gtimg.com/demo/demo-image-2.png', name: 'demo2.png' }
])
// 视频封面更新
const updateVideoPoster = (poster) => {
videoPoster.value = poster
console.log('视频封面已更新:', poster)
}
// 监听自定义上传
watch(customValue, (val) => {
if (val.length) {
customDisabled.value = false
}
})
</script>
<style scoped>
.demo-container {
padding: 16px;
}
.demo-section {
padding: 20px;
background: var(--td-bg-color-container);
border-radius: var(--td-radius-medium);
}
.demo-section h4 {
margin-bottom: 8px;
color: var(--td-text-color-primary);
}
.demo-desc {
margin-bottom: 16px;
color: var(--td-text-color-secondary);
font-size: 12px;
}
.loading-tip {
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
color: var(--td-text-color-brand);
font-size: 14px;
}
.custom-result {
margin-top: 12px;
padding: 8px 12px;
background: var(--td-success-color-1);
border-radius: var(--td-radius-default);
color: var(--td-success-color-7);
font-size: 12px;
}
</style>
工具函数上传
使用 uploadFileToCos
工具函数实现自定义上传逻辑。
使用工具函数上传
通过 uploadFileToCos 函数实现自定义上传逻辑
批量上传(使用工具函数)
支持选择多个文件同时上传
带图片处理的上传
上传后自动应用图片处理规则(缩略图、水印等)
查看代码
vue
<template>
<div class="demo-container">
<t-space direction="vertical" style="width: 100%" :size="40">
<div class="demo-section">
<h4>使用工具函数上传</h4>
<p class="demo-desc">通过 uploadFileToCos 函数实现自定义上传逻辑</p>
<div class="custom-upload">
<input
ref="inputRef"
type="file"
accept="image/*"
class="file-input"
@change="handleFileUpload"
/>
<t-button :loading="inputLoading" @click="triggerFileSelect">
<template #icon><upload-icon /></template>
{{ imageSrc ? '重新选择图片' : '选择图片上传' }}
</t-button>
</div>
<div v-if="uploadProgress > 0 && uploadProgress < 100" class="progress-bar">
<t-progress :percentage="uploadProgress" />
<span class="progress-text">上传中 {{ uploadProgress }}%</span>
</div>
<div v-if="imageSrc" class="upload-result">
<div class="image-preview">
<img :src="imageSrc" alt="上传的图片" />
</div>
<div class="image-info">
<p><strong>文件URL:</strong></p>
<t-input v-model="imageSrc" readonly :tips="imageSrc" />
<t-button size="small" variant="text" @click="copyUrl"> 复制链接 </t-button>
</div>
</div>
<div v-if="errorMessage" class="error-message">
<t-alert theme="error" :message="errorMessage" />
</div>
</div>
<div class="demo-section">
<h4>批量上传(使用工具函数)</h4>
<p class="demo-desc">支持选择多个文件同时上传</p>
<div class="custom-upload">
<input
ref="multiInputRef"
type="file"
accept="image/*"
multiple
class="file-input"
@change="handleMultiFileUpload"
/>
<t-button
:loading="multiLoading"
:disabled="multiLoading"
@click="triggerMultiFileSelect"
>
<template #icon><folder-add-icon /></template>
选择多个文件
</t-button>
<span v-if="multiFiles.length > 0" class="file-count">
已上传 {{ multiFiles.length }} 个文件
</span>
</div>
<div v-if="multiLoading" class="uploading-list">
<h5>上传队列:</h5>
<div v-for="(file, index) in uploadingFiles" :key="index" class="uploading-item">
<span class="file-name">{{ file.name }}</span>
<t-progress :percentage="file.progress || 0" size="small" style="flex: 1" />
</div>
</div>
<div v-if="multiFiles.length > 0" class="files-grid">
<div v-for="(file, index) in multiFiles" :key="index" class="file-card">
<img :src="file.url" alt="" />
<t-button
size="small"
shape="circle"
variant="outline"
class="remove-btn"
@click="removeFile(index)"
>
<template #icon><close-icon /></template>
</t-button>
</div>
</div>
</div>
<div class="demo-section">
<h4>带图片处理的上传</h4>
<p class="demo-desc">上传后自动应用图片处理规则(缩略图、水印等)</p>
<t-radio-group v-model="processType" variant="default-filled">
<t-radio-button value="thumbnail">生成缩略图</t-radio-button>
<t-radio-button value="watermark">添加水印</t-radio-button>
<t-radio-button value="compress">压缩图片</t-radio-button>
</t-radio-group>
<div class="custom-upload" style="margin-top: 16px">
<input
ref="processInputRef"
type="file"
accept="image/*"
class="file-input"
@change="handleProcessFileUpload"
/>
<t-button :loading="processLoading" @click="triggerProcessFileSelect">
<template #icon><image-icon /></template>
选择图片(带处理)
</t-button>
</div>
<div v-if="processedImage" class="compare-images">
<div class="image-box">
<h5>原图</h5>
<img :src="originalImage" alt="原图" />
</div>
<div class="image-box">
<h5>处理后</h5>
<img :src="processedImage" alt="处理后" />
</div>
</div>
</div>
</t-space>
</div>
</template>
<script setup lang="ts">
import { uploadFileToCos } from 't-design-pro'
import { CloseIcon, FolderAddIcon, ImageIcon, UploadIcon } from 'tdesign-icons-vue-next'
import {
Alert as TAlert,
Button as TButton,
Input as TInput,
MessagePlugin,
Progress as TProgress,
RadioButton as TRadioButton,
RadioGroup as TRadioGroup,
Space as TSpace
} from 'tdesign-vue-next'
import { useData } from 'vitepress'
import { ref, watch } from 'vue'
import getAuthorization from './getAuthorization'
const { isDark } = useData()
watch(
isDark,
(newVal) => {
document.documentElement.setAttribute('theme-mode', newVal ? 'dark' : 'light')
},
{
immediate: true
}
)
const Bucket = import.meta.env.VITE_COS_BUCKET || 'demo-bucket'
const Region = import.meta.env.VITE_COS_REGION || 'ap-shanghai'
// 单文件上传
const inputRef = ref<HTMLInputElement | null>(null)
const inputLoading = ref(false)
const imageSrc = ref('')
const uploadProgress = ref(0)
const errorMessage = ref('')
// 多文件上传
const multiInputRef = ref<HTMLInputElement | null>(null)
const multiLoading = ref(false)
const multiFiles = ref<Array<{ url: string; name: string }>>([])
const uploadingFiles = ref<Array<{ name: string; progress: number }>>([])
// 图片处理
const processInputRef = ref<HTMLInputElement | null>(null)
const processLoading = ref(false)
const processType = ref('thumbnail')
const originalImage = ref('')
const processedImage = ref('')
// 触发文件选择
const triggerFileSelect = () => {
inputRef.value?.click()
}
const triggerMultiFileSelect = () => {
multiInputRef.value?.click()
}
const triggerProcessFileSelect = () => {
processInputRef.value?.click()
}
// 单文件上传处理
async function handleFileUpload() {
const file = inputRef.value?.files?.[0]
if (!file) return
inputLoading.value = true
uploadProgress.value = 0
errorMessage.value = ''
const cosOptions = {
Bucket,
Region,
path: '/demo/utils/',
getAuthorization: getAuthorization,
onProgress: (res) => {
uploadProgress.value = Math.round(res.percent * 100)
}
}
try {
const result = await uploadFileToCos(cosOptions, file)
if (result.data) {
imageSrc.value = result.data.url
MessagePlugin.success('上传成功!')
} else {
throw new Error(result.err?.message || '上传失败')
}
} catch (error: any) {
errorMessage.value = error.message
MessagePlugin.error(`上传失败:${error.message}`)
} finally {
inputLoading.value = false
uploadProgress.value = 0
}
}
// 多文件上传处理
async function handleMultiFileUpload() {
const files = Array.from(multiInputRef.value?.files || [])
if (files.length === 0) return
multiLoading.value = true
uploadingFiles.value = files.map((file) => ({
name: file.name,
progress: 0
}))
const uploadPromises = files.map(async (file, index) => {
const cosOptions = {
Bucket,
Region,
path: '/demo/batch/',
getAuthorization: getAuthorization,
onProgress: (res) => {
uploadingFiles.value[index].progress = Math.round(res.percent * 100)
}
}
try {
const result = await uploadFileToCos(cosOptions, file)
if (result.data) {
return { url: result.data.url, name: file.name }
}
return null
} catch (error) {
console.error(`文件 ${file.name} 上传失败:`, error)
return null
}
})
const results = await Promise.all(uploadPromises)
const successFiles = results.filter(Boolean) as Array<{ url: string; name: string }>
multiFiles.value.push(...successFiles)
MessagePlugin.success(`成功上传 ${successFiles.length} 个文件`)
multiLoading.value = false
uploadingFiles.value = []
// 清空文件选择
if (multiInputRef.value) {
multiInputRef.value.value = ''
}
}
// 带处理的图片上传
async function handleProcessFileUpload() {
const file = processInputRef.value?.files?.[0]
if (!file) return
processLoading.value = true
// 先上传原图
const originalOptions = {
Bucket,
Region,
path: '/demo/process/original/',
getAuthorization: getAuthorization
}
try {
const originalResult = await uploadFileToCos(originalOptions, file)
if (!originalResult.data) throw new Error('原图上传失败')
originalImage.value = originalResult.data.url
// 根据选择的处理类型生成处理规则
let processRule = ''
switch (processType.value) {
case 'thumbnail':
processRule = '?imageMogr2/thumbnail/500x500'
break
case 'watermark':
processRule = '?watermark/2/text/VCBEZXNpZ24gUHJv/fill/I0ZGRkZGRg/fontsize/20/dissolve/50'
break
case 'compress':
processRule = '?imageMogr2/quality/60'
break
}
// 处理后的图片URL
processedImage.value = originalImage.value + processRule
MessagePlugin.success('图片处理完成!')
} catch (error: any) {
MessagePlugin.error(`处理失败:${error.message}`)
} finally {
processLoading.value = false
}
}
// 移除文件
const removeFile = (index: number) => {
multiFiles.value.splice(index, 1)
}
// 复制URL
const copyUrl = async () => {
try {
await navigator.clipboard.writeText(imageSrc.value)
MessagePlugin.success('链接已复制到剪贴板')
} catch {
MessagePlugin.error('复制失败,请手动复制')
}
}
</script>
<style scoped>
.demo-container {
padding: 16px;
}
.demo-section {
padding: 20px;
background: var(--td-bg-color-container);
border-radius: var(--td-radius-medium);
}
.demo-section h4 {
margin-bottom: 8px;
color: var(--td-text-color-primary);
}
.demo-desc {
margin-bottom: 16px;
color: var(--td-text-color-secondary);
font-size: 12px;
}
.custom-upload {
display: flex;
align-items: center;
gap: 12px;
}
.file-input {
display: none;
}
.file-count {
color: var(--td-text-color-secondary);
font-size: 14px;
}
.progress-bar {
margin-top: 16px;
}
.progress-text {
display: inline-block;
margin-left: 12px;
color: var(--td-text-color-brand);
font-size: 12px;
}
.upload-result {
margin-top: 20px;
padding: 16px;
background: var(--td-bg-color-component);
border-radius: var(--td-radius-default);
}
.image-preview {
margin-bottom: 12px;
text-align: center;
}
.image-preview img {
max-width: 100%;
max-height: 200px;
border-radius: var(--td-radius-default);
}
.image-info {
display: flex;
align-items: center;
gap: 8px;
}
.image-info p {
margin: 0;
color: var(--td-text-color-secondary);
font-size: 12px;
white-space: nowrap;
}
.error-message {
margin-top: 16px;
}
.uploading-list {
margin-top: 16px;
padding: 12px;
background: var(--td-bg-color-component);
border-radius: var(--td-radius-default);
}
.uploading-list h5 {
margin-bottom: 12px;
color: var(--td-text-color-secondary);
font-size: 12px;
}
.uploading-item {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.file-name {
min-width: 120px;
color: var(--td-text-color-primary);
font-size: 12px;
}
.files-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 12px;
margin-top: 16px;
}
.file-card {
position: relative;
padding-top: 100%;
background: var(--td-bg-color-component);
border-radius: var(--td-radius-default);
overflow: hidden;
}
.file-card img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.remove-btn {
position: absolute;
top: 4px;
right: 4px;
background: rgba(255, 255, 255, 0.9);
}
.compare-images {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 20px;
}
.image-box {
text-align: center;
}
.image-box h5 {
margin-bottom: 8px;
color: var(--td-text-color-secondary);
font-size: 14px;
}
.image-box img {
width: 100%;
max-height: 200px;
object-fit: contain;
border: 1px solid var(--td-border-color);
border-radius: var(--td-radius-default);
}
</style>
API
Props
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
modelValue | 已上传文件列表,支持 v-model | Array<any> | [] |
theme | 上传组件主题 | string | 'file' |
max | 最大上传数量 | number | 1 |
abridgeName | 文件名省略配置 [前缀保留, 后缀保留] | Array<number> | - |
path | 上传路径(必填) | string | - |
poster | 视频封面(theme 为 video 时有效) | Array<any> | - |
disabled | 是否禁用 | boolean | false |
uploadExpandOptions | t-upload 其他支持的属性 | object | {} |
maxFileSize | 文件大小限制 | { size: number; unit: 'B' | 'KB' | 'MB' | 'GB' } | - |
ossExtendOptions | COS 扩展配置 | Omit<COS.UploadFileParams, omitOssOptions> | - |
accept | 接受的文件类型 | string | - |
pictureHandleRule | 图片处理规则 | string | - |
loading | 上传加载状态,支持 v-model:loading | boolean | false |
loadingAttach | 加载遮罩挂载位置 | boolean | string | false |
size | 组件尺寸 | 'small' | 'default' | 'default' |
videoMaxduration | 视频最大时长(秒) | number | - |
cosOptions | COS 配置(必填) | CreateCosInstanceOptions | - |
CreateCosInstanceOptions COS 配置
参数 | 说明 | 类型 | 必填 |
---|---|---|---|
Bucket | COS 存储桶名称 | string | 是 |
Region | COS 地域 | string | 是 |
getAuthorization | 获取临时密钥的方法 | COS.COSOptions['getAuthorization'] | 是 |
主题类型说明
主题值 | 说明 | 特性 |
---|---|---|
'file' | 文件上传 | 基础文件上传,显示文件信息 |
'image' | 图片上传 | 图片预览模式 |
'file-input' | 输入框模式 | 简洁的输入框上传 |
'file-flow' | 文件流式 | 文件列表流式布局 |
'image-flow' | 图片流式 | 图片列表流式布局 |
'image-sortable' | 图片排序 | 支持拖拽排序的图片列表 |
'video' | 视频上传 | 视频上传和预览,支持封面选择 |
'files-list' | 文件列表 | 详细的文件列表展示 |
'audio' | 音频上传 | 音频文件上传(本地预览) |
'custom' | 自定义 | 通过默认插槽自定义上传按钮 |
方法
方法名 | 说明 | 参数 | 返回值 |
---|---|---|---|
uploadFiles | 手动触发文件上传 | (files: File[]) | - |
事件
事件名 | 说明 | 参数 |
---|---|---|
update:modelValue | 文件列表变化时触发 | (value: Array<any>) |
update:loading | 上传状态变化时触发 | (loading: boolean) |
updatePoster | 视频封面更新时触发 | (poster: Array<any>) |
uploadSuccess | 单个文件上传成功时触发 | (file: any) |
插槽
插槽名 | 说明 | 参数 |
---|---|---|
default | 自定义上传按钮(theme 为 custom 时) | - |
filesListButtonTitle | 文件列表上传按钮文字 | - |
imageCoverIcon | 图片覆盖层图标 | { item: any } |
工具函数
uploadFileToCos
独立的上传函数,可用于自定义上传逻辑。
typescript
import { uploadFileToCos } from 't-design-pro'
const result = await uploadFileToCos(options, file)
参数
参数 | 说明 | 类型 |
---|---|---|
options | 上传配置 | uploadFileToCosOptions |
file | 要上传的文件 | File |
uploadFileToCosOptions
参数 | 说明 | 类型 | 必填 |
---|---|---|---|
Bucket | COS 存储桶名称 | string | 是 |
Region | COS 地域 | string | 是 |
path | 上传路径 | string | 是 |
getAuthorization | 获取临时密钥的方法 | Function | 是 |
onProgress | 上传进度回调 | (progressData: COS.ProgressInfo) => void | 否 |
高级用法
配置临时密钥
javascript
// getAuthorization.js
export default async function getAuthorization(options, callback) {
// 从后端获取临时密钥
const response = await fetch('/api/cos/sts', {
method: 'POST',
body: JSON.stringify({
bucket: options.Bucket,
region: options.Region,
})
})
const data = await response.json()
callback({
TmpSecretId: data.credentials.tmpSecretId,
TmpSecretKey: data.credentials.tmpSecretKey,
SecurityToken: data.credentials.sessionToken,
ExpiredTime: data.expiredTime,
})
}
图片处理规则
vue
<template>
<CosUpload
v-model="images"
theme="image-sortable"
:cos-options="cosOptions"
path="/uploads/images/"
picture-handle-rule="imageMogr2/thumbnail/500x500"
:max="9"
/>
</template>
视频上传限制
vue
<template>
<CosUpload
v-model="video"
theme="video"
:cos-options="cosOptions"
path="/uploads/videos/"
:video-maxduration="60"
:max-file-size="{ size: 100, unit: 'MB' }"
accept="video/*"
/>
</template>
文件下载配置
vue
<template>
<CosUpload
v-model="files"
theme="files-list"
:cos-options="cosOptions"
path="/uploads/files/"
:oss-extend-options="{
ContentDisposition: 'attachment'
}"
/>
</template>
自定义上传按钮
vue
<template>
<CosUpload
v-model="files"
theme="custom"
:cos-options="cosOptions"
path="/uploads/"
>
<t-button theme="primary" variant="outline">
<template #icon><cloud-upload-icon /></template>
点击上传
</t-button>
</CosUpload>
</template>
监听上传进度
vue
<template>
<CosUpload
v-model="files"
v-model:loading="uploading"
:cos-options="cosOptions"
path="/uploads/"
@upload-success="handleSuccess"
/>
</template>
<script setup>
const uploading = ref(false)
const handleSuccess = (file) => {
console.log('上传成功:', file)
MessagePlugin.success('文件上传成功!')
}
watch(uploading, (val) => {
console.log('上传状态:', val ? '上传中' : '上传完成')
})
</script>
注意事项
- COS 配置:使用前需要正确配置腾讯云 COS 的 Bucket、Region 和临时密钥获取方法
- 安全性:不要在前端直接配置永久密钥,应使用临时密钥方案
- 文件大小:大文件上传时建议配置合理的超时时间和重试策略
- 跨域配置:需要在 COS 控制台配置正确的 CORS 规则
- 图片处理:使用图片处理规则需要开通 COS 的数据处理功能
- 视频限制:视频文件较大,建议设置合理的大小和时长限制
- 并发控制:多文件上传时,组件会自动控制并发数量
- 错误处理:上传失败时会自动显示错误信息,可通过 MessagePlugin 自定义提示
常见问题
1. 如何获取上传后的文件 URL?
上传成功后,文件信息会包含 url
字段:
javascript
const handleSuccess = (file) => {
console.log('文件URL:', file.url)
}
2. 如何实现文件覆盖上传?
在 path 中使用固定的文件名:
vue
<CosUpload
:cos-options="cosOptions"
:path="`/uploads/avatar_${userId}.jpg`"
/>
3. 如何限制文件类型?
使用 accept
属性:
vue
<CosUpload
accept="image/png,image/jpeg"
:cos-options="cosOptions"
/>
4. 如何自定义文件名?
在 getAuthorization
中处理或使用 path
属性动态生成:
javascript
const customPath = computed(() => {
const timestamp = Date.now()
return `/uploads/${timestamp}/`
})