<template>
|
<div class="cos-simple-test">
|
<div class="page-card">
|
<h3 class="card-title">腾讯云COS上传简单测试</h3>
|
|
<!-- 配置信息 -->
|
<el-alert
|
title="配置说明"
|
type="warning"
|
:closable="false"
|
show-icon
|
style="margin-bottom: 20px;"
|
>
|
<p>请在 <code>web/src/utils/cos-config.ts</code> 中配置正确的COS信息:</p>
|
<ul>
|
<li>Region: 存储桶所在地域(如:ap-chengdu)</li>
|
<li>Bucket: 存储桶名称</li>
|
<li>SecretId 和 SecretKey: 访问密钥(生产环境应通过后端获取临时密钥)</li>
|
</ul>
|
</el-alert>
|
|
<!-- 当前配置显示 -->
|
<el-card shadow="never" style="margin-bottom: 20px;">
|
<template #header>
|
<span>当前配置</span>
|
</template>
|
<div class="config-display">
|
<p><strong>存储桶:</strong> {{ cosConfig.bucket }}</p>
|
<p><strong>地域:</strong> {{ cosConfig.region }}</p>
|
<p><strong>状态:</strong>
|
<el-tag :type="connectionStatus === 'success' ? 'success' : 'danger'">
|
{{ connectionStatus === 'success' ? '配置正常' : '请检查配置' }}
|
</el-tag>
|
</p>
|
</div>
|
</el-card>
|
|
<!-- 文件上传 -->
|
<el-card shadow="never">
|
<template #header>
|
<span>文件上传测试</span>
|
</template>
|
|
<div class="upload-section">
|
<!-- 选择文件 -->
|
<div class="file-input-section">
|
<el-upload
|
ref="uploadRef"
|
:auto-upload="false"
|
:show-file-list="false"
|
:on-change="handleFileSelect"
|
accept="image/*"
|
>
|
<el-button type="primary">
|
<el-icon><Plus /></el-icon>
|
选择图片文件
|
</el-button>
|
</el-upload>
|
|
<div v-if="selectedFile" class="selected-file">
|
<div class="file-info">
|
<el-icon><Picture /></el-icon>
|
<span>{{ selectedFile.name }}</span>
|
<span class="file-size">({{ formatFileSize(selectedFile.size) }})</span>
|
</div>
|
<el-button type="danger" size="small" @click="clearFile">
|
移除
|
</el-button>
|
</div>
|
</div>
|
|
<!-- 存储目录选择 -->
|
<div v-if="selectedFile" class="folder-section">
|
<label>存储目录:</label>
|
<el-select v-model="selectedFolder" placeholder="选择存储目录">
|
<el-option label="头像 (avatars/)" value="avatars/" />
|
<el-option label="图片 (images/)" value="images/" />
|
<el-option label="文档 (documents/)" value="documents/" />
|
<el-option label="其他 (others/)" value="others/" />
|
</el-select>
|
</div>
|
|
<!-- 上传按钮 -->
|
<div v-if="selectedFile" class="upload-actions">
|
<el-button
|
type="success"
|
:loading="uploading"
|
@click="uploadFile"
|
>
|
<el-icon><Upload /></el-icon>
|
{{ uploading ? '上传中...' : '开始上传' }}
|
</el-button>
|
</div>
|
|
<!-- 上传进度 -->
|
<div v-if="uploadProgress.show" class="progress-section">
|
<div class="progress-info">
|
<span>{{ uploadProgress.fileName }}</span>
|
<span>{{ uploadProgress.status }}</span>
|
</div>
|
<el-progress
|
:percentage="uploadProgress.percent"
|
:status="uploadProgress.type"
|
/>
|
</div>
|
|
<!-- 上传结果 -->
|
<div v-if="uploadResult.show" class="result-section">
|
<el-alert
|
:title="uploadResult.success ? '上传成功!' : '上传失败!'"
|
:type="uploadResult.success ? 'success' : 'error'"
|
:closable="false"
|
show-icon
|
>
|
<div v-if="uploadResult.success">
|
<p><strong>文件URL:</strong></p>
|
<el-input
|
v-model="uploadResult.url"
|
readonly
|
style="margin: 8px 0;"
|
>
|
<template #append>
|
<el-button @click="copyUrl">复制</el-button>
|
</template>
|
</el-input>
|
<div class="result-actions">
|
<el-button type="primary" @click="openFile">查看文件</el-button>
|
<el-button type="danger" @click="deleteFile">删除文件</el-button>
|
</div>
|
</div>
|
<div v-else>
|
<p>{{ uploadResult.error }}</p>
|
</div>
|
</el-alert>
|
</div>
|
</div>
|
</el-card>
|
|
<!-- 使用说明 -->
|
<el-card shadow="never" style="margin-top: 20px;">
|
<template #header>
|
<span>使用说明</span>
|
</template>
|
<div class="instructions">
|
<ol>
|
<li>确保已在 <code>cos-config.ts</code> 中配置正确的COS信息</li>
|
<li>选择要上传的图片文件(支持 jpg, png, gif, webp 格式)</li>
|
<li>选择存储目录</li>
|
<li>点击"开始上传"按钮</li>
|
<li>上传成功后可以查看或删除文件</li>
|
</ol>
|
<p><strong>注意:</strong> 这是测试页面,生产环境中应该通过后端API获取临时密钥,而不是在前端直接配置永久密钥。</p>
|
</div>
|
</el-card>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, reactive, onMounted } from 'vue'
|
import { ElMessage } from 'element-plus'
|
import { uploadToCOS, deleteFromCOS } from '@/utils/cos'
|
import { DEV_COS_CONFIG } from '@/utils/cos-config'
|
|
// 组件状态
|
const uploading = ref(false)
|
const connectionStatus = ref<'success' | 'error'>('error')
|
|
// COS配置
|
const cosConfig = reactive({
|
bucket: DEV_COS_CONFIG.Bucket,
|
region: DEV_COS_CONFIG.Region
|
})
|
|
// 选中的文件
|
const selectedFile = ref<File | null>(null)
|
const selectedFolder = ref('images/')
|
|
// 上传进度
|
const uploadProgress = reactive({
|
show: false,
|
fileName: '',
|
percent: 0,
|
status: '',
|
type: undefined as 'success' | 'exception' | undefined
|
})
|
|
// 上传结果
|
const uploadResult = reactive({
|
show: false,
|
success: false,
|
url: '',
|
error: '',
|
fileKey: ''
|
})
|
|
// 文件选择处理
|
const handleFileSelect = (file: any) => {
|
const newFile = file.raw as File
|
|
// 检查文件类型
|
if (!newFile.type.startsWith('image/')) {
|
ElMessage.error('请选择图片文件')
|
return
|
}
|
|
// 检查文件大小(限制10MB)
|
if (newFile.size > 10 * 1024 * 1024) {
|
ElMessage.error('文件大小不能超过10MB')
|
return
|
}
|
|
selectedFile.value = newFile
|
uploadResult.show = false
|
uploadProgress.show = false
|
}
|
|
// 清除文件
|
const clearFile = () => {
|
selectedFile.value = null
|
uploadResult.show = false
|
uploadProgress.show = false
|
}
|
|
// 上传文件
|
const uploadFile = async () => {
|
if (!selectedFile.value) {
|
ElMessage.error('请先选择文件')
|
return
|
}
|
|
uploading.value = true
|
uploadProgress.show = true
|
uploadProgress.fileName = selectedFile.value.name
|
uploadProgress.percent = 0
|
uploadProgress.status = '准备上传...'
|
uploadProgress.type = undefined
|
uploadResult.show = false
|
|
try {
|
// 模拟进度更新
|
const progressInterval = setInterval(() => {
|
if (uploadProgress.percent < 90) {
|
uploadProgress.percent += Math.random() * 20
|
uploadProgress.status = `上传中... ${Math.round(uploadProgress.percent)}%`
|
}
|
}, 200)
|
|
// 执行上传
|
const url = await uploadToCOS(selectedFile.value, selectedFolder.value)
|
|
// 清除进度定时器
|
clearInterval(progressInterval)
|
|
// 更新进度为完成
|
uploadProgress.percent = 100
|
uploadProgress.status = '上传完成'
|
uploadProgress.type = 'success'
|
|
// 显示结果
|
uploadResult.show = true
|
uploadResult.success = true
|
uploadResult.url = url
|
uploadResult.fileKey = `${selectedFolder.value}${selectedFile.value.name}`
|
|
ElMessage.success('文件上传成功!')
|
|
} catch (error: any) {
|
console.error('上传失败:', error)
|
|
uploadProgress.percent = 0
|
uploadProgress.status = '上传失败'
|
uploadProgress.type = 'exception'
|
|
uploadResult.show = true
|
uploadResult.success = false
|
uploadResult.error = error.message || '上传失败,请检查网络连接和COS配置'
|
|
ElMessage.error('文件上传失败')
|
} finally {
|
uploading.value = false
|
}
|
}
|
|
// 复制URL
|
const copyUrl = async () => {
|
try {
|
await navigator.clipboard.writeText(uploadResult.url)
|
ElMessage.success('URL已复制到剪贴板')
|
} catch (error) {
|
ElMessage.error('复制失败')
|
}
|
}
|
|
// 打开文件
|
const openFile = () => {
|
window.open(uploadResult.url, '_blank')
|
}
|
|
// 删除文件
|
const deleteFile = async () => {
|
try {
|
await deleteFromCOS(uploadResult.fileKey)
|
ElMessage.success('文件删除成功')
|
uploadResult.show = false
|
clearFile()
|
} catch (error) {
|
console.error('删除失败:', error)
|
ElMessage.error('文件删除失败')
|
}
|
}
|
|
// 格式化文件大小
|
const formatFileSize = (bytes: number): string => {
|
if (bytes === 0) return '0 B'
|
const k = 1024
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
}
|
|
// 检查配置
|
const checkConfig = () => {
|
if (cosConfig.bucket && cosConfig.region) {
|
connectionStatus.value = 'success'
|
} else {
|
connectionStatus.value = 'error'
|
}
|
}
|
|
onMounted(() => {
|
checkConfig()
|
})
|
</script>
|
|
<style lang="scss" scoped>
|
.cos-simple-test {
|
.card-title {
|
margin-bottom: 20px;
|
color: #303133;
|
font-size: 18px;
|
font-weight: 600;
|
}
|
|
.config-display {
|
p {
|
margin: 8px 0;
|
|
strong {
|
color: #606266;
|
margin-right: 8px;
|
}
|
}
|
}
|
|
.upload-section {
|
.file-input-section {
|
margin-bottom: 20px;
|
|
.selected-file {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-top: 12px;
|
padding: 12px;
|
border: 1px solid #e4e7ed;
|
border-radius: 4px;
|
background-color: #f5f7fa;
|
|
.file-info {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
|
.file-size {
|
color: #909399;
|
font-size: 12px;
|
}
|
}
|
}
|
}
|
|
.folder-section {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
margin-bottom: 20px;
|
|
label {
|
font-weight: 500;
|
color: #606266;
|
}
|
}
|
|
.upload-actions {
|
margin-bottom: 20px;
|
}
|
|
.progress-section {
|
margin-bottom: 20px;
|
|
.progress-info {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 8px;
|
font-size: 14px;
|
color: #606266;
|
}
|
}
|
|
.result-section {
|
.result-actions {
|
margin-top: 12px;
|
display: flex;
|
gap: 8px;
|
}
|
}
|
}
|
|
.instructions {
|
ol {
|
margin: 0;
|
padding-left: 20px;
|
|
li {
|
margin-bottom: 8px;
|
line-height: 1.5;
|
}
|
}
|
|
p {
|
margin-top: 16px;
|
padding: 8px;
|
background-color: #fff6f7;
|
border-left: 4px solid #f56c6c;
|
color: #606266;
|
}
|
|
code {
|
background-color: #f5f7fa;
|
padding: 2px 4px;
|
border-radius: 2px;
|
font-family: 'Courier New', monospace;
|
}
|
}
|
}
|
</style>
|