library-picturebook-activity/oss-direct-upload-demo/frontend/UploadDemo.vue

211 lines
4.4 KiB
Vue
Raw Permalink Normal View History

<!--
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>