feat(front): add axios for API requests and enhance FileUploadProgressView with speed tracking and UI improvements

This commit is contained in:
keven1024
2026-01-10 14:34:01 +08:00
parent 9679a42be2
commit 8c4c7b0471
3 changed files with 46 additions and 76 deletions

View File

@@ -2,33 +2,52 @@
import dayjs from 'dayjs'
import { motion } from 'motion-v'
import getFileSize from '~/lib/getFileSize'
import showDrawer from '~/lib/showDrawer'
import FileUploadSpeedInfoView from './FileUploadSpeedInfoView.vue'
import { clamp } from 'lodash-es'
const { t } = useI18n()
const props = defineProps<{
speedChartData: Record<string, { fileId: string; index: number; value: number }[]>
}>()
const speedChartList = ref<{ timestamp: number; value: number }[]>([])
useIntervalFn(() => {
speedChartList.value.push({
timestamp: dayjs().unix() - 1,
value: props.speedChartData[dayjs().unix() - 1]?.reduce((acc, curr) => acc + curr.value, 0) ?? 0,
})
if (speedChartList.value.length > 60) {
speedChartList.value.shift()
}
}, 1000)
const handleShowSpeedInfo = () => {
showDrawer({
render: ({ hide }) => h(FileUploadSpeedInfoView, { hide }),
})
}
</script>
<template>
<div class="rounded-xl p-3 bg-white/80 flex flex-col gap-2 col-span-4 md:col-span-3 h-32 md:h-auto">
<div class="flex flex-col gap-1">
<div @click="() => {}" class="flex flex-row gap-1 items-center text-xs opacity-70">
<div @click="handleShowSpeedInfo" class="flex flex-row gap-1 items-center text-xs opacity-70">
{{ t('page.progress.file.totalUploadProgress') }}
<LucideInfo class="size-3" />
</div>
<div class="text-2xl font-bold">
{{ `${getFileSize(speedChartData[dayjs().unix()]?.reduce((acc, curr) => acc + curr.value, 0) ?? 0) ?? 0}/s` }}
{{ `${getFileSize(speedChartData[dayjs().unix() - 1]?.reduce((acc, curr) => acc + curr.value, 0) ?? 0) ?? 0}/s` }}
</div>
</div>
<div class="flex-1 relative overflow-hidden flex flex-row gap-0.5 justify-end items-end">
<motion.div
class="w-2 shrink-0 bg-primary relative"
:style="{
height: `${(i[1]?.reduce((acc, curr) => acc + curr.value, 0) / Math.max(...(Object.entries(speedChartData)?.map((r) => r[1]?.reduce((acc, curr) => acc + curr.value, 0)) || [1]))) * 100}%`,
height: `${clamp((i.value / Math.max(...(speedChartList?.map((r) => r.value) || [1]))) * 100, 1, 100)}%`,
}"
:layoutId="i[0]"
v-for="i in Object.entries(speedChartData)"
:key="i[0]"
:layoutId="i.timestamp"
v-for="i in speedChartList"
:key="i.timestamp"
:initial="{ x: 10, opacity: 0 }"
:animate="{ x: 0, opacity: 1 }"
:transition="{ duration: 1 }"

View File

@@ -1,23 +1,12 @@
<script setup lang="ts">
import {
LucidePlay,
LucideSettings,
LucideSquare,
LucideInfo,
LucideFolders,
LucideArrowUpFromLine,
LucideCircleX,
LucideCheckCircle,
LucideLoaderCircle,
LucideArrowDownUp,
} from 'lucide-vue-next'
import { LucideSquare, LucideInfo, LucideFolders, LucideArrowUpFromLine, LucideCircleX, LucideCheckCircle, LucideLoaderCircle } from 'lucide-vue-next'
import Button from '@/components/ui/button/Button.vue'
import getFileSize from '~/lib/getFileSize'
import { cx } from 'class-variance-authority'
import asyncWorker from '@/lib/asyncWorker'
import calcFileHashWorker from '@/lib/calcFileHashWorker?worker'
import { clamp, debounce, get, groupBy, isEmpty, isNumber, isString, sample, shuffle, throttle, times } from 'lodash-es'
import { clamp, get, isEmpty, isNumber, sample, shuffle, throttle, times } from 'lodash-es'
import { nanoid } from 'nanoid'
import { toast } from 'vue-sonner'
import dayjs from 'dayjs'
@@ -28,6 +17,7 @@ import FileUploadSpeedInfoView from './FileUploadSpeedInfoView.vue'
import getFileChunk from '~/lib/getFileChunk'
import type { FileHandleKey } from '~/components/Preprocessing/types'
import asyncWait from '@/lib/asyncWait'
import axios from 'axios'
import type { SelectedFile, Uploadfile } from './types'
import FileUploadDetailView from './FileUploadDetailView.vue'
@@ -48,48 +38,7 @@ const activeTaskList = computed(() => uploadfiles.value.filter((r) => r.queue.le
const activeTaskAllQueue = computed(() => activeTaskList.value.flatMap((r) => r.queue))
const batchNum = ref(3)
const totalTaskStatus = computed(() => {
if (uploadfiles.value.some((r) => r.status === 'start')) {
return 'start'
}
if (uploadfiles.value.some((r) => r.status === 'pause')) {
return 'pause'
}
return 'disabled'
})
const totalUploadProgress = computed(() => {
const successCount = uploadfiles.value.reduce((acc, curr) => {
const { status, uploadInfo } = curr || {}
if (status === 'finish') return acc
const { chunks } = uploadInfo || {}
return acc + Object.entries(chunks || {}).filter(([index, chunk]) => chunk.status === 'success').length
}, 0)
const totalCount = uploadfiles.value.reduce((acc, curr) => {
const { status, uploadInfo } = curr || {}
if (status === 'finish') return acc
const { chunkLength } = uploadInfo || {}
return acc + (chunkLength || 0)
}, 0)
return ((successCount || 0) / (totalCount || 0)) * 100
})
const counter = useInterval(1000)
const speedChartData = ref<Record<number, { createdAt: number; value: number }[]>>({})
watch(counter, () => {
const speed = uploadfiles.value?.flatMap((item) => {
const { chunks, ChunkSize } = item?.uploadInfo || {}
return Object.entries(chunks || {})
.filter(([index, chunk]) => chunk.status === 'success' && dayjs().unix() - 60 < chunk.createdAt)
?.map(([index, chunk]) => {
const { createdAt } = chunk || {}
return {
createdAt,
value: ChunkSize || 0,
}
})
})
speedChartData.value = groupBy(speed, 'createdAt')
})
const speedChartData = ref<Record<number, { fileId: string; index: number; value: number }[]>>({})
onMounted(() => {
props.data.file.forEach((file) => {
@@ -269,11 +218,12 @@ const handleUpload = async (fileId: string, index: number) => {
formData.append('file', new Blob([chunk]))
formData.append('index', (index + 1).toString())
formData.append('id', id)
const res = await $fetch<{
code: number
}>('/api/file/slice', {
method: 'POST',
body: formData,
const { data: res } = await axios.post('/api/file/slice', formData, {
onUploadProgress: throttle((progressEvent) => {
const { rate } = progressEvent || {}
const timestamp = dayjs().unix()
speedChartData.value[timestamp] = [...(speedChartData.value[timestamp] || []), { fileId, index, value: rate || 0 }]
}, 1000),
})
const { code } = res || {}
if (code !== 200) {
@@ -334,7 +284,7 @@ const handleShowSpeedInfo = () => {
<template>
<div class="grid grid-cols-4 gap-5">
<FileUploadTotalSpeedView :speedChartData="throttledSpeedChartData" />
<FileUploadTotalSpeedView :speedChartData="speedChartData" />
<FileUploadTotalProgressControlView :uploadfiles="uploadfiles" />
<div class="col-span-4 flex flex-col bg-white/80 rounded-xl p-3 text-md gap-5">
<div class="flex flex-row gap-2 items-center">
@@ -394,15 +344,15 @@ const handleShowSpeedInfo = () => {
</div>
<div>{{ getFileSize(item?.file?.size) }}</div>
<div>
{{
`${getFileSize(
(Object.entries(item?.uploadInfo?.chunks || {})?.filter(
([, chunk]) => chunk.status === 'success' && dayjs().unix() - 60 < chunk.createdAt
)?.length /
60) *
(item?.uploadInfo?.ChunkSize || 0)
)} /s`
}}
<template v-if="item?.status === 'start' && item?.procressType === 'upload'">
{{
`${getFileSize(
Object.entries(speedChartData)
?.filter(([key, value]) => value.some((r) => r.fileId === item?.fileId) && dayjs().unix() - 1 === Number(key))
?.reduce((acc, curr) => acc + curr[1]?.reduce((acc, curr) => acc + curr.value, 0), 0) ?? 0
)} /s`
}}
</template>
</div>
<div
class="flex flex-row gap-2 items-center col-span-3 md:col-span-1"

View File

@@ -32,6 +32,7 @@
"@unovis/vue": "^1.6.2",
"@vee-validate/nuxt": "^4.15.1",
"@vee-validate/rules": "^4.15.1",
"axios": "^1.13.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.19",