feat: complete upload progress bar

This commit is contained in:
yanghao05
2025-04-17 20:13:12 +08:00
parent 1ca75b6060
commit d961c1a4a5
4 changed files with 27 additions and 49 deletions

View File

@@ -35,7 +35,7 @@ type PreCheckResp struct {
func (up *uploads) PreUploadCheck(ctx fiber.Ctx, md5, ext string) (*PreCheckResp, error) { func (up *uploads) PreUploadCheck(ctx fiber.Ctx, md5, ext string) (*PreCheckResp, error) {
_, err := models.Medias.GetByHash(ctx.Context(), md5) _, err := models.Medias.GetByHash(ctx.Context(), md5)
if err != nil && errors.Is(err, qrm.ErrNoRows) { if err != nil && errors.Is(err, qrm.ErrNoRows) {
preSign, err := up.oss.PreSignUpload(ctx.Context(), fmt.Sprintf("%s%s", md5, ext)) preSign, err := up.oss.PreSignUpload(ctx.Context(), fmt.Sprintf("%s.%s", md5, ext))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -19,8 +19,9 @@ func (c *OSSClient) GetClient() *oss.Client {
func (c *OSSClient) PreSignUpload(ctx context.Context, path string) (*oss.PresignResult, error) { func (c *OSSClient) PreSignUpload(ctx context.Context, path string) (*oss.PresignResult, error) {
request := &oss.PutObjectRequest{ request := &oss.PutObjectRequest{
Bucket: oss.Ptr(c.config.Bucket), Bucket: oss.Ptr(c.config.Bucket),
Key: oss.Ptr("quyun/" + strings.Trim(path, "/")), Key: oss.Ptr("quyun/" + strings.Trim(path, "/")),
ContentType: oss.Ptr("multipart/form-data"),
} }
log.Printf("%+v", request) log.Printf("%+v", request)
return c.client.Presign(ctx, request) return c.client.Presign(ctx, request)

View File

@@ -11,11 +11,8 @@ export const mediaService = {
return httpClient.post('/admin/medias', mediaInfo); return httpClient.post('/admin/medias', mediaInfo);
}, },
getUploadToken() { preUploadedCheck(md5, ext) {
return httpClient.get('/admin/uploads/token'); return httpClient.get(`/admin/uploads/pre-uploaded-check/${md5}.${ext}`);
},
preUploadedCheck(md5) {
return httpClient.get(`/admin/uploads/pre-uploaded-check/${md5}`);
}, },
uploadedSuccess(data) { uploadedSuccess(data) {

View File

@@ -15,7 +15,6 @@ const dropZone = ref(null);
const uploadProgress = ref(0); const uploadProgress = ref(0);
const isUploading = ref(false); const isUploading = ref(false);
const currentFile = ref(null); const currentFile = ref(null);
const ossConfig = ref(null);
const uploadHistory = ref([]); const uploadHistory = ref([]);
const STORAGE_KEY = 'media_upload_history'; const STORAGE_KEY = 'media_upload_history';
@@ -56,19 +55,10 @@ const addToHistory = (file, status = 'uploading') => {
// Clear completed uploads // Clear completed uploads
const clearCompleted = () => { const clearCompleted = () => {
uploadHistory.value = uploadHistory.value.filter(item => item.status !== 'completed'); uploadHistory.value = [];
saveUploadHistory(); saveUploadHistory();
}; };
const getOssToken = async () => {
try {
const response = await mediaService.getUploadToken();
ossConfig.value = response.data;
} catch (error) {
toast.add({ severity: 'error', summary: '错误', detail: '获取上传凭证失败', life: 3000 });
}
};
// Add pending uploads state // Add pending uploads state
const uploadQueue = ref([]); const uploadQueue = ref([]);
const currentUploadIndex = ref(-1); const currentUploadIndex = ref(-1);
@@ -107,10 +97,12 @@ const processNextUpload = async () => {
try { try {
const md5Hash = await calculateMD5(nextFile.file); const md5Hash = await calculateMD5(nextFile.file);
// get file ext
const fileExt = nextFile.file.name.split('.').pop();
// Check if file exists before upload // Check if file exists before upload
const checkResult = await mediaService.preUploadedCheck(md5Hash); const checkResult = await mediaService.preUploadedCheck(md5Hash, fileExt);
console.log(checkResult)
if (checkResult.data === 'exists') { if (checkResult.data.exists) {
// Skip upload and mark as completed // Skip upload and mark as completed
uploadQueue.value.shift(); uploadQueue.value.shift();
addToHistory(nextFile.file, 'completed'); addToHistory(nextFile.file, 'completed');
@@ -121,8 +113,8 @@ const processNextUpload = async () => {
life: 3000 life: 3000
}); });
} else { } else {
// Proceed with upload // Proceed with upload using pre-signed URL
await uploadFile(nextFile.file, md5Hash); await uploadFile(nextFile.file, md5Hash, checkResult.data.pre_sign);
uploadQueue.value.shift(); uploadQueue.value.shift();
addToHistory(nextFile.file, 'completed'); addToHistory(nextFile.file, 'completed');
} }
@@ -175,34 +167,15 @@ const calculateMD5 = (file) => {
}); });
}; };
// Modify uploadFile function // Modify uploadFile function to use pre-signed URL
const uploadFile = async (file, md5Hash) => { const uploadFile = async (file, md5Hash, preSign) => {
try { try {
currentFile.value = file; currentFile.value = file;
isUploading.value = true; isUploading.value = true;
uploadProgress.value = 0; uploadProgress.value = 0;
if (!ossConfig.value) {
await getOssToken();
}
const fileExt = file.name.split('.').pop();
const newFileName = `${md5Hash}.${fileExt}`;
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('success_action_status', '200');
formData.append('policy', ossConfig.value.policy);
formData.append('x-oss-signature', ossConfig.value.signature);
formData.append('x-oss-signature-version', 'OSS4-HMAC-SHA256');
formData.append('x-oss-credential', ossConfig.value.x_oss_credential);
formData.append('x-oss-date', ossConfig.value.x_oss_date);
formData.append('key', ossConfig.value.dir + newFileName);
formData.append('x-oss-security-token', ossConfig.value.security_token);
formData.append('callback', ossConfig.value.callback);
formData.append('file', file);
xhr.upload.onprogress = (e) => { xhr.upload.onprogress = (e) => {
if (e.lengthComputable) { if (e.lengthComputable) {
@@ -230,8 +203,16 @@ const uploadFile = async (file, md5Hash) => {
} }
}; };
xhr.open('POST', ossConfig.value.host, true); xhr.open(preSign.Method, preSign.URL, true);
xhr.send(formData);
// Add signed headers if provided
if (preSign.SignedHeaders) {
Object.entries(preSign.SignedHeaders).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
}
xhr.send(file);
}); });
toast.add({ severity: 'success', summary: '成功', detail: '文件上传成功', life: 3000 }); toast.add({ severity: 'success', summary: '成功', detail: '文件上传成功', life: 3000 });
@@ -346,8 +327,7 @@ onMounted(() => {
<template #header> <template #header>
<div class="flex justify-between items-center bg-gray-50 p-4 rounded-t-lg"> <div class="flex justify-between items-center bg-gray-50 p-4 rounded-t-lg">
<h3 class="text-lg font-medium">上传历史</h3> <h3 class="text-lg font-medium">上传历史</h3>
<Button v-if="completedUploads.length" icon="pi pi-trash" text @click="clearCompleted" <Button icon="pi pi-trash" text @click="clearCompleted" label="清除历史记录" />
label="清除已完成" />
</div> </div>
</template> </template>
<template #content> <template #content>