feat: 添加文件上传进度和速度显示

- 新增 api.postFormDataWithProgress 方法支持上传进度回调
- 版本创建和编辑页面显示上传进度条和实时速度
- 按钮显示上传百分比
- 添加国际化翻译

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 22:01:16 +08:00
parent c755587a4d
commit b86fc60741
5 changed files with 138 additions and 4 deletions
+28 -2
View File
@@ -37,6 +37,8 @@ const versionId = computed(() => route.params.id as string)
const loading = ref(true)
const saving = ref(false)
const uploading = ref(false)
const uploadProgress = ref(0)
const uploadSpeed = ref('')
const applications = ref<Application[]>([])
const formData = ref({
@@ -168,11 +170,20 @@ async function uploadZipFile() {
}
uploading.value = true
uploadProgress.value = 0
uploadSpeed.value = ''
try {
const formDataObj = new FormData()
formDataObj.append('file', formData.value.file)
const response = await api.postFormData<UploadResponse>('/dev/versions/upload-zip', formDataObj)
const response = await api.postFormDataWithProgress<UploadResponse>(
'/dev/versions/upload-zip',
formDataObj,
(progress, speed) => {
uploadProgress.value = progress
uploadSpeed.value = speed
}
)
uploadedData.value = response
toast.success(t('admin.versions.createForm.uploadSuccess'))
}
@@ -182,6 +193,8 @@ async function uploadZipFile() {
}
finally {
uploading.value = false
uploadProgress.value = 0
uploadSpeed.value = ''
}
}
@@ -380,13 +393,26 @@ onMounted(() => {
>
<Loader2 v-if="uploading" class="h-4 w-4 mr-2 animate-spin" />
<Upload v-else class="h-4 w-4 mr-2" />
{{ t('admin.versions.createForm.uploadAndParse') }}
{{ uploading ? `${uploadProgress}%` : t('admin.versions.createForm.uploadAndParse') }}
</UiButton>
<UiButton variant="ghost" size="icon" :disabled="saving" @click="removeFile">
<X class="size-4" />
</UiButton>
</div>
</div>
<!-- 上传进度条 -->
<div v-if="uploading" class="space-y-1">
<div class="w-full bg-muted rounded-full h-2 overflow-hidden">
<div
class="bg-primary h-full transition-all duration-300 ease-out"
:style="{ width: `${uploadProgress}%` }"
/>
</div>
<div class="flex justify-between text-xs text-muted-foreground">
<span>{{ t('admin.versions.createForm.uploading') }}</span>
<span class="font-medium">{{ uploadSpeed }}</span>
</div>
</div>
<div v-if="uploadedData && uploadedData.file_hash !== formData.file?.name" class="rounded-lg bg-muted/50 p-3">
<div class="flex items-center gap-2 text-sm text-green-600 mb-2">
<CheckCircle class="size-4" />
+28 -2
View File
@@ -36,6 +36,8 @@ const router = useRouter()
const loading = ref(false)
const saving = ref(false)
const uploading = ref(false)
const uploadProgress = ref(0)
const uploadSpeed = ref('')
const applications = ref<Application[]>([])
const formData = ref({
@@ -149,11 +151,20 @@ async function uploadZipFile() {
}
uploading.value = true
uploadProgress.value = 0
uploadSpeed.value = ''
try {
const formDataObj = new FormData()
formDataObj.append('file', formData.value.file)
const response = await api.postFormData<UploadResponse>('/dev/versions/upload-zip', formDataObj)
const response = await api.postFormDataWithProgress<UploadResponse>(
'/dev/versions/upload-zip',
formDataObj,
(progress, speed) => {
uploadProgress.value = progress
uploadSpeed.value = speed
}
)
uploadedData.value = response
toast.success(t('admin.versions.createForm.uploadSuccess'))
}
@@ -163,6 +174,8 @@ async function uploadZipFile() {
}
finally {
uploading.value = false
uploadProgress.value = 0
uploadSpeed.value = ''
}
}
@@ -319,13 +332,26 @@ onMounted(() => {
>
<Loader2 v-if="uploading" class="h-4 w-4 mr-2 animate-spin" />
<Upload v-else class="h-4 w-4 mr-2" />
{{ t('admin.versions.createForm.uploadAndParse') }}
{{ uploading ? `${uploadProgress}%` : t('admin.versions.createForm.uploadAndParse') }}
</UiButton>
<UiButton variant="ghost" size="icon" :disabled="saving" @click="removeFile">
<X class="size-4" />
</UiButton>
</div>
</div>
<!-- 上传进度条 -->
<div v-if="uploading" class="space-y-1">
<div class="w-full bg-muted rounded-full h-2 overflow-hidden">
<div
class="bg-primary h-full transition-all duration-300 ease-out"
:style="{ width: `${uploadProgress}%` }"
/>
</div>
<div class="flex justify-between text-xs text-muted-foreground">
<span>{{ t('admin.versions.createForm.uploading') }}</span>
<span class="font-medium">{{ uploadSpeed }}</span>
</div>
</div>
<div v-if="uploadedData" class="rounded-lg bg-muted/50 p-3">
<div class="flex items-center gap-2 text-sm text-green-600 mb-2">
<CheckCircle class="size-4" />