mirror of
https://github.com/keven1024/015.git
synced 2026-05-26 07:08:02 +00:00
feat(front): implement file sharing and download functionality with improved type safety
This commit is contained in:
@@ -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 }) })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
53
front/pages/s/[id].vue
Normal 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>
|
||||
Reference in New Issue
Block a user