feat(front): implement file sharing and download functionality with improved type safety

This commit is contained in:
keven1024
2025-05-15 17:26:40 +08:00
parent 9f429626d7
commit 0bab15d519
6 changed files with 75 additions and 9 deletions

View File

@@ -24,7 +24,6 @@ const isDocument = computed(() => isPDF.value || isDOC.value || isXLS.value || i
const actions = [
{
label: '分享文件', icon: LucideShare, className: 'bg-green-300', onClick: () => {
console.log('复制链接', props.file)
showDrawer({ render: ({ hide }) => h(FileShareHandle, { ...props, hide }) })
}
},

View File

@@ -40,7 +40,7 @@ const props = defineProps<{
setFieldValue('has_password', false)
}
return true
}) as any" />
})" />
</div>
<div class="flex flex-row gap-3 min-h-9">
<SwitchField name="has_password" label="密码保护" :rules="((value: boolean) => {
@@ -48,7 +48,7 @@ const props = defineProps<{
setFieldValue('has_pickup_code', false)
}
return true
}) as any" />
})" />
<InputField v-if="!!values.has_password" name="password" placeholder="请输入密码" rules="required" />
</div>
<div class="flex flex-row gap-3 min-h-9">

View File

@@ -11,7 +11,12 @@ const props = defineProps<{
const { state } = useAsyncState(async () => {
const { file_id, config, file } = props?.data || {}
const { name } = file || {}
const data = await $fetch<any>(`/api/share`, {
const data = await $fetch<{
code: number
data: {
id?: string
}
}>(`/api/share`, {
method: 'POST',
body: {
type: 'file',

View File

@@ -6,7 +6,12 @@ const props = defineProps<{
const handleDownload = async () => {
const { id } = props?.data || {}
const data = await $fetch<any>(`/api/download`, {
const data = await $fetch<{
code: number
data: {
token?: string
}
}>(`/api/download`, {
method: 'POST',
body: {
share_id: id

View File

@@ -31,7 +31,7 @@ useAsyncState(async () => {
if (!file) return
const { size, type } = file || {}
const now = Date.now()
const hash = await calcFileHash({ file } as any)
const hash = await calcFileHash({ file })
if (hash) {
step.value = 'upload'
calcHashTime.value = Date.now() - now
@@ -87,11 +87,13 @@ useAsyncState(async () => {
formData.append('index', index + 1)
formData.append('id', id)
fileSliceUploadStatusList.value[index].status = 'uploading'
const res = await $fetch('/api/file/slice', {
const res = await $fetch<{
code: number
}>('/api/file/slice', {
method: 'POST',
body: formData
})
const { code } = (res as any) || {}
const { code } = res || {}
if (code !== 200) {
throw new Error('上传失败')
}
@@ -102,7 +104,9 @@ useAsyncState(async () => {
}
}))
}
const r = await $fetch<any>('/api/file/finish', {
const r = await $fetch<{
code: number
}>('/api/file/finish', {
method: 'POST',
body: {
id

53
front/pages/s/[id].vue Normal file
View File

@@ -0,0 +1,53 @@
<script setup lang="ts">
import { LucideAlertCircle } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
import { cx } from 'class-variance-authority'
import { times } from 'lodash-es'
import dayjs from 'dayjs'
import FileShareView from '@/components/Share/FileShareView.vue'
import TextShareView from '@/components/Share/TextShareView.vue'
const route = useRoute()
const router = useRouter()
const id = computed(() => route.params.id)
const { state, isLoading } = useAsyncState(async () => {
const data = await $fetch<{
code: number
data: {
id?: string
expire_at?: number
}
}>(`/api/share/${id.value}`)
return data?.data
}, null)
const isExpired = computed(() => {
const { expire_at } = state.value || {}
return !state || !expire_at || dayjs(expire_at * 10e2).isBefore(dayjs())
})
</script>
<template>
<div class="rounded-xl p-5 bg-white/50 backdrop-blur-xl w-full lg:w-200 my-5">
<div v-if="isLoading" class="flex flex-col gap-5 items-center">
<Skeleton :class="cx('w-40 h-5 rounded-full', i > 0 && '!w-20')" v-for="i in times(3)" :key="i" />
</div>
<template v-else>
<div v-if="isExpired" class="flex flex-col gap-5 items-center">
<LucideAlertCircle :size="48" class="text-orange-500 rounded-full bg-orange-500/30 p-2" />
<div class="text-xl ">此链接已过期</div>
<Button @click="() => {
router.push('/')
}">返回首页</Button>
</div>
<template v-else>
<FileShareView :data="state" />
<TextShareView :data="state" />
</template>
</template>
</div>
</template>