feat: 更新媒体上传状态显示,添加上传进度和状态文本,优化用户体验
This commit is contained in:
@@ -100,11 +100,11 @@
|
||||
<draggable v-model="form.covers" item-key="tempId" class="flex flex-wrap gap-4" group="covers">
|
||||
<template #item="{ element: img, index }">
|
||||
<div class="relative group w-48 aspect-video rounded-lg overflow-hidden bg-slate-100 border border-slate-200 cursor-move">
|
||||
<div v-if="img.status === 'uploading'" class="absolute inset-0 bg-black/60 flex flex-col items-center justify-center z-20 text-white">
|
||||
<i class="pi pi-spin pi-spinner text-2xl mb-2"></i>
|
||||
<span class="text-xs font-bold">{{ img.progress }}%</span>
|
||||
<div v-if="img.status !== 'done'" class="absolute inset-0 bg-black/60 flex flex-col items-center justify-center z-20 text-white transition-opacity">
|
||||
<i class="pi pi-spin pi-spinner text-2xl mb-2" v-if="img.status === 'uploading'"></i>
|
||||
<span class="text-xs font-bold text-center px-2">{{ img.statusText }}</span>
|
||||
</div>
|
||||
<Image :src="img.url" preview imageClass="w-full h-full object-cover" />
|
||||
<Image :src="img.url" preview imageClass="w-full h-full object-cover" :class="{'opacity-50 blur-sm': img.status !== 'done'}" />
|
||||
<button @click="removeMediaItem('cover', index, img)"
|
||||
class="absolute top-1 right-1 w-6 h-6 bg-black/50 hover:bg-red-500 text-white rounded-full flex items-center justify-center transition-colors cursor-pointer z-10"><i
|
||||
class="pi pi-times text-xs"></i></button>
|
||||
@@ -123,18 +123,22 @@
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-3">视频列表 (拖拽排序)</label>
|
||||
<div class="space-y-3">
|
||||
<draggable v-model="form.videos" item-key="id" class="space-y-3" group="videos">
|
||||
<draggable v-model="form.videos" item-key="tempId" class="space-y-3" group="videos">
|
||||
<template #item="{ element: file, index }">
|
||||
<div class="flex items-center gap-4 p-3 border border-slate-200 rounded-lg bg-slate-50 cursor-move hover:border-primary-300 transition-colors">
|
||||
<div class="w-10 h-10 bg-blue-100 text-blue-600 rounded flex items-center justify-center"><i
|
||||
class="pi pi-video"></i></div>
|
||||
<div class="flex items-center gap-4 p-3 border border-slate-200 rounded-lg bg-slate-50 cursor-move hover:border-primary-300 transition-colors relative overflow-hidden" :class="{'opacity-60': file.status !== 'done'}">
|
||||
<!-- Progress -->
|
||||
<div v-if="file.status === 'uploading'" class="absolute bottom-0 left-0 h-1 bg-blue-500 transition-all duration-300" :style="{ width: file.progress + '%' }"></div>
|
||||
|
||||
<div class="w-10 h-10 bg-blue-100 text-blue-600 rounded flex items-center justify-center shrink-0">
|
||||
<i class="pi" :class="file.status === 'uploading' ? 'pi-spin pi-spinner' : 'pi-video'"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-bold text-sm text-slate-900 truncate">{{ file.name }}</div>
|
||||
<div class="text-xs text-slate-500">{{ file.size }}</div>
|
||||
<div class="text-xs text-slate-500">{{ file.status !== 'done' ? file.statusText : file.size }}</div>
|
||||
</div>
|
||||
<button @click="removeMediaItem('videos', index, file)"
|
||||
class="text-slate-400 hover:text-red-500 px-2 cursor-pointer"><i
|
||||
class="pi pi-trash"></i></button>
|
||||
class="pi" :class="file.status === 'uploading' ? 'pi-times-circle' : 'pi-trash'"></i></button>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
@@ -149,18 +153,22 @@
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-3">音频列表 (拖拽排序)</label>
|
||||
<div class="space-y-3">
|
||||
<draggable v-model="form.audios" item-key="id" class="space-y-3" group="audios">
|
||||
<draggable v-model="form.audios" item-key="tempId" class="space-y-3" group="audios">
|
||||
<template #item="{ element: file, index }">
|
||||
<div class="flex items-center gap-4 p-3 border border-slate-200 rounded-lg bg-slate-50 cursor-move hover:border-purple-300 transition-colors">
|
||||
<div class="w-10 h-10 bg-purple-100 text-purple-600 rounded flex items-center justify-center"><i
|
||||
class="pi pi-microphone"></i></div>
|
||||
<div class="flex items-center gap-4 p-3 border border-slate-200 rounded-lg bg-slate-50 cursor-move hover:border-purple-300 transition-colors relative overflow-hidden" :class="{'opacity-60': file.status !== 'done'}">
|
||||
<!-- Progress -->
|
||||
<div v-if="file.status === 'uploading'" class="absolute bottom-0 left-0 h-1 bg-purple-500 transition-all duration-300" :style="{ width: file.progress + '%' }"></div>
|
||||
|
||||
<div class="w-10 h-10 bg-purple-100 text-purple-600 rounded flex items-center justify-center shrink-0">
|
||||
<i class="pi" :class="file.status === 'uploading' ? 'pi-spin pi-spinner' : 'pi-microphone'"></i>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-bold text-sm text-slate-900 truncate">{{ file.name }}</div>
|
||||
<div class="text-xs text-slate-500">{{ file.size }}</div>
|
||||
<div class="text-xs text-slate-500">{{ file.status !== 'done' ? file.statusText : file.size }}</div>
|
||||
</div>
|
||||
<button @click="removeMediaItem('audios', index, file)"
|
||||
class="text-slate-400 hover:text-red-500 px-2 cursor-pointer"><i
|
||||
class="pi pi-trash"></i></button>
|
||||
class="pi" :class="file.status === 'uploading' ? 'pi-times-circle' : 'pi-trash'"></i></button>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
@@ -175,15 +183,19 @@
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-3">图片列表 (拖拽排序)</label>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<draggable v-model="form.images" item-key="id" class="flex flex-wrap gap-4" group="images">
|
||||
<draggable v-model="form.images" item-key="tempId" class="flex flex-wrap gap-4" group="images">
|
||||
<template #item="{ element: file, index }">
|
||||
<div class="relative group w-32 aspect-square rounded-lg overflow-hidden border border-slate-200 cursor-move">
|
||||
<Image :src="file.url" preview imageClass="w-full h-full object-cover" />
|
||||
<div
|
||||
<div v-if="file.status !== 'done'" class="absolute inset-0 bg-black/60 flex flex-col items-center justify-center z-20 text-white transition-opacity">
|
||||
<i class="pi pi-spin pi-spinner text-2xl mb-2" v-if="file.status === 'uploading'"></i>
|
||||
<span class="text-xs font-bold text-center px-2">{{ file.statusText }}</span>
|
||||
</div>
|
||||
<Image :src="file.url" preview imageClass="w-full h-full object-cover" :class="{'opacity-50 blur-sm': file.status !== 'done'}" />
|
||||
<div v-if="file.status === 'done'"
|
||||
class="absolute bottom-0 left-0 w-full bg-black/60 text-white text-xs p-1 truncate text-center pointer-events-none">
|
||||
{{ file.name }}</div>
|
||||
<button @click="removeMediaItem('images', index, file)"
|
||||
class="absolute top-1 right-1 w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer z-10"><i
|
||||
class="absolute top-1 right-1 w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center transition-colors cursor-pointer z-10"><i
|
||||
class="pi pi-times text-xs"></i></button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -340,6 +352,7 @@ const handleFileChange = async (event) => {
|
||||
url: URL.createObjectURL(file), // Preview local file immediately
|
||||
progress: 0,
|
||||
status: 'uploading',
|
||||
statusText: '等待中...',
|
||||
abort: null
|
||||
});
|
||||
|
||||
@@ -357,22 +370,35 @@ const handleFileChange = async (event) => {
|
||||
// Start Upload
|
||||
(async () => {
|
||||
try {
|
||||
item.statusText = '计算中...';
|
||||
const hash = await calculateHash(file);
|
||||
|
||||
let task;
|
||||
|
||||
if (type === 'video' || type === 'audio') {
|
||||
task = commonApi.uploadMultipart(file, hash, type, (p) => item.progress = p);
|
||||
item.statusText = '准备上传...';
|
||||
task = commonApi.uploadMultipart(file, hash, type, (p) => {
|
||||
item.progress = Math.round(p);
|
||||
item.statusText = `上传中 ${item.progress}%`;
|
||||
});
|
||||
} else {
|
||||
// Check hash for images
|
||||
// Simple upload for images
|
||||
item.statusText = '校验中...';
|
||||
let existing = null;
|
||||
try {
|
||||
existing = await commonApi.checkHash(hash);
|
||||
} catch(e) {}
|
||||
|
||||
if (existing) {
|
||||
item.statusText = '秒传成功';
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
task = { promise: Promise.resolve(existing), abort: () => {} };
|
||||
} else {
|
||||
task = commonApi.uploadWithProgress(file, type, (p) => item.progress = p);
|
||||
item.statusText = '上传中...';
|
||||
task = commonApi.uploadWithProgress(file, type, (p) => {
|
||||
item.progress = Math.round(p);
|
||||
item.statusText = `上传中 ${item.progress}%`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,6 +409,7 @@ const handleFileChange = async (event) => {
|
||||
item.id = res.id;
|
||||
item.url = res.url; // Update to remote URL
|
||||
item.status = 'done';
|
||||
item.statusText = '上传完成';
|
||||
item.progress = 100;
|
||||
} catch (e) {
|
||||
if (e.message === 'Aborted') {
|
||||
|
||||
Reference in New Issue
Block a user