library-picturebook-activity/oss-direct-upload-demo/frontend/UploadDemo.vue
En b9ed5e17c6 feat: OSS 客户端直传改造(STS Token 签发 + 前端直传 + CORS 自动配置)
后端新增 OssUtils/OssTokenVo/OssCorsInitRunner,通过 STS 临时凭证实现客户端直传 OSS;
前端 upload API 适配直传流程,赛事创建/作品提交/作业/富文本编辑器均已切换;
多环境(dev/test/prod) OSS 配置补全;新增 oss-direct-upload-demo 示例项目及 E2E 测试。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 15:19:43 +08:00

211 lines
4.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
OSS 直传上传组件 Demo
最小可运行示例展示如何使用 file.ts env.ts 实现文件直传阿里云 OSS
使用方式
1. file.ts env.ts 复制到你的项目中
2. 安装 axiosnpm install axios
3. 在页面中引入此组件即可使用
-->
<template>
<div class="upload-demo">
<h2>阿里云 OSS 直传上传 Demo</h2>
<!-- 文件选择 -->
<div class="upload-area">
<input
type="file"
ref="fileInput"
@change="handleFileChange"
accept="image/*,.pdf,.doc,.docx,.mp4,.mp3"
/>
<button @click="handleUpload" :disabled="!selectedFile || uploading">
{{ uploading ? "上传中..." : "上传文件" }}
</button>
<button
v-if="uploading"
@click="handleCancel"
style="margin-left: 8px; color: red"
>
取消上传
</button>
</div>
<!-- 进度条 -->
<div v-if="uploading" class="progress-area">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
</div>
<span>{{ progress }}%</span>
</div>
<!-- 上传结果 -->
<div v-if="result" class="result-area">
<p>上传成功!</p>
<p>文件路径:{{ result.filePath }}</p>
<p>文件大小:{{ (result.fileSize / 1024).toFixed(1) }} KB</p>
<img
v-if="result.filePath && isImage(result.fileName)"
:src="result.filePath"
alt="预览"
style="max-width: 300px; margin-top: 8px"
/>
</div>
<!-- 错误信息 -->
<div v-if="error" class="error-area">
<p style="color: red">上传失败:{{ error }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { uploadFile } from "./file";
const fileInput = ref<HTMLInputElement>();
const selectedFile = ref<File | null>(null);
const uploading = ref(false);
const progress = ref(0);
const result = ref<{
filePath: string;
fileName: string;
fileSize: number;
} | null>(null);
const error = ref<string>("");
// 取消控制器
let abortController: AbortController | null = null;
/** 判断是否为图片 */
function isImage(fileName: string): boolean {
return /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(fileName);
}
/** 文件选择事件 */
function handleFileChange(event: Event) {
const target = event.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
selectedFile.value = target.files[0];
error.value = "";
result.value = null;
}
}
/** 开始上传 */
async function handleUpload() {
if (!selectedFile.value) return;
uploading.value = true;
progress.value = 0;
error.value = "";
result.value = null;
// 创建取消控制器
abortController = new AbortController();
try {
const uploadResult = await uploadFile(selectedFile.value, "demo", {
onProgress: (percent) => {
progress.value = percent;
},
signal: abortController.signal,
});
result.value = {
filePath: uploadResult.filePath,
fileName: uploadResult.fileName,
fileSize: uploadResult.fileSize,
};
} catch (err: any) {
if (err.name === "CanceledError" || err.name === "AbortError") {
error.value = "上传已取消";
} else {
error.value = err.message || "未知错误";
}
} finally {
uploading.value = false;
abortController = null;
}
}
/** 取消上传 */
function handleCancel() {
if (abortController) {
abortController.abort();
abortController = null;
}
}
</script>
<style scoped>
.upload-demo {
max-width: 500px;
margin: 20px auto;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.upload-area {
margin: 16px 0;
}
.progress-area {
display: flex;
align-items: center;
gap: 8px;
margin: 8px 0;
}
.progress-bar {
flex: 1;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #1890ff;
transition: width 0.3s;
}
.result-area,
.error-area {
margin-top: 16px;
padding: 12px;
border-radius: 4px;
}
.result-area {
background: #f6ffed;
border: 1px solid #b7eb8f;
}
.error-area {
background: #fff2f0;
border: 1px solid #ffccc7;
}
button {
padding: 6px 16px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: pointer;
background: #fff;
}
button:hover:not(:disabled) {
border-color: #1890ff;
color: #1890ff;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>