211 lines
4.4 KiB
Vue
211 lines
4.4 KiB
Vue
|
|
<!--
|
|||
|
|
OSS 直传上传组件 Demo
|
|||
|
|
|
|||
|
|
最小可运行示例,展示如何使用 file.ts 和 env.ts 实现文件直传阿里云 OSS。
|
|||
|
|
|
|||
|
|
使用方式:
|
|||
|
|
1. 将 file.ts 和 env.ts 复制到你的项目中
|
|||
|
|
2. 安装 axios:npm 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>
|