feat: 新增 S3 文件上传组件,支持文件大小和数量限制,优化上传流程

This commit is contained in:
2026-01-08 21:46:59 +07:00
parent a3c86ea0f7
commit 8d964957e5
4 changed files with 232 additions and 8 deletions

78
src/utils/aws/s3.ts Normal file
View File

@@ -0,0 +1,78 @@
import { toRefs } from 'vue';
import { client, safeClient } from '@/service/api';
interface UploadOptions {
fetchOptions: UploadFetchOptions;
onProgress?: (progress: number) => void;
signal?: AbortSignal;
}
export type UploadFetchOptions = CommonType.TreatyBody<typeof client.api.file_storage.upload_url.post>;
export async function uploadToS3(file: File, options: UploadOptions) {
const { onProgress, signal, fetchOptions } = options;
// 1. 获取预签名 URL
const { data, error } = await safeClient(
client.api.file_storage.upload_url.post({
...fetchOptions
})
);
if (error.value || !data.value) {
throw new Error('获取上传 URL 失败');
}
const { fileId, uploadUrl, method, headers } = toRefs(data.value);
// 2. 上传文件到 S3
return new Promise<string>((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 监听上传进度
xhr.upload.addEventListener('progress', (e: ProgressEvent) => {
if (e.lengthComputable && onProgress) {
const progress = Math.round((e.loaded / e.total) * 100);
onProgress(progress);
}
});
// 上传成功
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(fileId.value);
} else {
reject(new Error(`上传失败: ${xhr.status} ${xhr.statusText}`));
}
});
// 网络错误
xhr.addEventListener('error', () => {
reject(new Error('网络错误,上传失败'));
});
// 上传取消
xhr.addEventListener('abort', () => {
reject(new Error('上传已取消'));
});
// 超时
xhr.addEventListener('timeout', () => {
reject(new Error('上传超时'));
});
// 支持外部取消
if (signal) {
signal.addEventListener('abort', () => {
xhr.abort();
});
}
// 开始上传
xhr.open(method.value, uploadUrl.value, true);
for (const [key, value] of Object.entries(headers.value)) {
xhr.setRequestHeader(key, value);
}
xhr.timeout = 300000; // 5分钟超时
xhr.send(file);
});
}