Skip to content

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-modelArray<any>[]
theme上传组件主题string'file'
max最大上传数量number1
abridgeName文件名省略配置 [前缀保留, 后缀保留]Array<number>-
path上传路径(必填)string-
poster视频封面(theme 为 video 时有效)Array<any>-
disabled是否禁用booleanfalse
uploadExpandOptionst-upload 其他支持的属性object{}
maxFileSize文件大小限制{ size: number; unit: 'B' | 'KB' | 'MB' | 'GB' }-
ossExtendOptionsCOS 扩展配置Omit<COS.UploadFileParams, omitOssOptions>-
accept接受的文件类型string-
pictureHandleRule图片处理规则string-
loading上传加载状态,支持 v-model:loadingbooleanfalse
loadingAttach加载遮罩挂载位置boolean | stringfalse
size组件尺寸'small' | 'default''default'
videoMaxduration视频最大时长(秒)number-
cosOptionsCOS 配置(必填)CreateCosInstanceOptions-

CreateCosInstanceOptions COS 配置

参数说明类型必填
BucketCOS 存储桶名称string
RegionCOS 地域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

参数说明类型必填
BucketCOS 存储桶名称string
RegionCOS 地域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>

注意事项

  1. COS 配置:使用前需要正确配置腾讯云 COS 的 Bucket、Region 和临时密钥获取方法
  2. 安全性:不要在前端直接配置永久密钥,应使用临时密钥方案
  3. 文件大小:大文件上传时建议配置合理的超时时间和重试策略
  4. 跨域配置:需要在 COS 控制台配置正确的 CORS 规则
  5. 图片处理:使用图片处理规则需要开通 COS 的数据处理功能
  6. 视频限制:视频文件较大,建议设置合理的大小和时长限制
  7. 并发控制:多文件上传时,组件会自动控制并发数量
  8. 错误处理:上传失败时会自动显示错误信息,可通过 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}/`
})