From 4e7131b29190a00bda8f5f3fabb83820b83a3782 Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Mon, 27 Oct 2025 17:16:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0download=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E7=94=A8=E4=BA=8E=E5=B1=95=E7=A4=BA=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加/download/documentID路由用于文档下载 - server端添加文档元数据获取与下载API - 将app中的types移至shared,与server共享 --- app/components/DocumentList.vue | 20 +++ app/models/mappers/documentMapper.ts | 2 + app/models/views/DocumentListView.ts | 3 + app/models/views/ProductDocumentView.ts | 3 + app/pages/download/[id].vue | 133 ++++++++++++++++++ i18n/locales/en.json | 1 + i18n/locales/zh.json | 1 + server/api/download/[id].ts | 22 +++ server/api/file/[id].ts | 24 ++++ server/utils/file.ts | 53 +++++++ {app => shared}/types/common.ts | 0 {app => shared}/types/directus/index.ts | 0 {app => shared}/types/directus/my-schema.ts | 0 shared/types/file.ts | 12 ++ shared/types/index.ts | 2 + {app => shared}/types/meilisearch/index.ts | 0 .../types/meilisearch/meili-index.ts | 0 .../types/meilisearch/search-result.ts | 0 18 files changed, 276 insertions(+) create mode 100644 app/pages/download/[id].vue create mode 100644 server/api/download/[id].ts create mode 100644 server/api/file/[id].ts create mode 100644 server/utils/file.ts rename {app => shared}/types/common.ts (100%) rename {app => shared}/types/directus/index.ts (100%) rename {app => shared}/types/directus/my-schema.ts (100%) create mode 100644 shared/types/file.ts create mode 100644 shared/types/index.ts rename {app => shared}/types/meilisearch/index.ts (100%) rename {app => shared}/types/meilisearch/meili-index.ts (100%) rename {app => shared}/types/meilisearch/search-result.ts (100%) diff --git a/app/components/DocumentList.vue b/app/components/DocumentList.vue index 0061070..22bb95a 100644 --- a/app/components/DocumentList.vue +++ b/app/components/DocumentList.vue @@ -4,6 +4,7 @@ v-for="(doc, index) in documents" :key="index" class="document-card" + @click="handleClick(doc.fileId)" >

{{ doc.title }}

@@ -36,6 +37,15 @@ }, }); + const localePath = useLocalePath(); + + const handleClick = (id: string) => { + // 获取路由参数 + if (id) { + navigateTo(localePath(`/download/${id}`)); + } + }; + const handleDownload = async (fileName: string, fileUrl: string) => { const response = await fetch(fileUrl); const blob = await response.blob(); @@ -59,6 +69,16 @@ width: 100%; } + .document-card { + cursor: pointer; + transition: all 0.3s ease; + } + + .document-card:hover { + transform: translateY(-1px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); + } + .document-meta { font-size: 0.8rem; color: var(--el-text-color-secondary); diff --git a/app/models/mappers/documentMapper.ts b/app/models/mappers/documentMapper.ts index cf9dec4..d5c855d 100644 --- a/app/models/mappers/documentMapper.ts +++ b/app/models/mappers/documentMapper.ts @@ -23,6 +23,7 @@ export function toProductDocumentView( return { id: raw.id, + fileId: fileId, filename: file.filename_download, title: trans.title, url: url, @@ -68,6 +69,7 @@ export function toDocumentListView(raw: ProductDocument): DocumentListView { return { id: raw.id, + fileId: fileId, filename: file.filename_download, title: trans.title, url: url, diff --git a/app/models/views/DocumentListView.ts b/app/models/views/DocumentListView.ts index 4fd3bae..5b66106 100644 --- a/app/models/views/DocumentListView.ts +++ b/app/models/views/DocumentListView.ts @@ -25,6 +25,9 @@ export interface DocumentListView { /** 唯一标识符 **/ id: number; + /** 文件UUID **/ + fileId: string; + /** 文件名 **/ filename: string; diff --git a/app/models/views/ProductDocumentView.ts b/app/models/views/ProductDocumentView.ts index dae8aa3..36625d2 100644 --- a/app/models/views/ProductDocumentView.ts +++ b/app/models/views/ProductDocumentView.ts @@ -6,6 +6,9 @@ export interface ProductDocumentView { /** 唯一标识符 **/ id: number; + /** 文件UUID **/ + fileId: string; + /** 文件名 **/ filename: string; diff --git a/app/pages/download/[id].vue b/app/pages/download/[id].vue new file mode 100644 index 0000000..4183861 --- /dev/null +++ b/app/pages/download/[id].vue @@ -0,0 +1,133 @@ + + + + + diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 336a26f..462e8e1 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -34,6 +34,7 @@ "support": "Support", "about-us": "About Us", "contact-info": "Contact Info", + "downloads": "Downloads", "faq": "FAQ", "documents": "Documents", "calculator": "Calculator" diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json index b7f1163..9fc3894 100644 --- a/i18n/locales/zh.json +++ b/i18n/locales/zh.json @@ -34,6 +34,7 @@ "support": "服务支持", "about-us": "关于我们", "contact-info": "联系信息", + "downloads": "文件下载", "faq": "常见问题", "documents": "文档资料", "calculator": "纸管计算工具" diff --git a/server/api/download/[id].ts b/server/api/download/[id].ts new file mode 100644 index 0000000..02e7a62 --- /dev/null +++ b/server/api/download/[id].ts @@ -0,0 +1,22 @@ +import { getFileMeta } from '../../utils/file'; + +export default defineEventHandler(async (event) => { + const id = getRouterParam(event, 'id'); + if (!id) { + throw createError({ statusCode: 400, message: '缺少文件ID' }); + } + const file = await getFileMeta(id); + if (!file) { + throw createError({ statusCode: 404, message: '文件不存在' }); + } + + const res = await $fetch(file.url, { + responseType: 'arrayBuffer', + }); + return new Response(res, { + headers: { + 'Content-Disposition': `attachment; filename="${encodeURIComponent(file.filename_download)}"`, + 'Content-Type': file.type, + }, + }); +}); diff --git a/server/api/file/[id].ts b/server/api/file/[id].ts new file mode 100644 index 0000000..c504a88 --- /dev/null +++ b/server/api/file/[id].ts @@ -0,0 +1,24 @@ +import { getFileMeta } from '../../utils/file'; + +/** + * 用于处理文件相关的API请求 + * 返回指定ID的文件信息 + */ +export default defineEventHandler(async (event) => { + const id = getRouterParam(event, 'id'); + if (!id) + throw createError({ + statusCode: 400, + statusMessage: 'File ID is required', + }); + + const file = await getFileMeta(id); + if (!file) { + throw createError({ + statusCode: 404, + statusMessage: 'File not found', + }); + } + + return file; +}); diff --git a/server/utils/file.ts b/server/utils/file.ts new file mode 100644 index 0000000..5d4ae83 --- /dev/null +++ b/server/utils/file.ts @@ -0,0 +1,53 @@ +/** + * 判断文件是否支持预览 + */ +export function isPreviewable(mime: string | null | undefined): boolean { + if (!mime || mime === undefined) return false; + return ( + mime.startsWith('image/') || + mime.startsWith('video/') || + mime === 'application/pdf' || + mime.startsWith('text/') + ); +} + +/** + * 从 Directus 获取文件元信息 + */ +export async function getFileMeta(id: string): Promise { + const runtimeConfig = useRuntimeConfig(); + const baseUrl = runtimeConfig.public.directus.url; + const access_token = runtimeConfig.public.directus.token; + + try { + const response = await $fetch<{ data: DirectusFile }>( + `${baseUrl}/files/${id}`, + { + headers: { + Authorization: access_token ? `Bearer ${access_token}` : '', + }, + } + ); + + const file = response.data; + if (!file) return null; + + return { + id: file.id, + title: file.filename_disk ?? '', + filename_download: file.filename_download ?? '', + type: file.type ?? '', + filesize: Number(file.filesize), + width: file.width ?? undefined, + height: file.height ?? undefined, + uploaded_on: file.uploaded_on ?? undefined, + url: `${baseUrl}/assets/${file.id}`, + previewable: isPreviewable(file.type), + }; + } catch (error) { + if (error instanceof Error) { + console.error('Error fetching file metadata:', error.message); + } + return null; + } +} diff --git a/app/types/common.ts b/shared/types/common.ts similarity index 100% rename from app/types/common.ts rename to shared/types/common.ts diff --git a/app/types/directus/index.ts b/shared/types/directus/index.ts similarity index 100% rename from app/types/directus/index.ts rename to shared/types/directus/index.ts diff --git a/app/types/directus/my-schema.ts b/shared/types/directus/my-schema.ts similarity index 100% rename from app/types/directus/my-schema.ts rename to shared/types/directus/my-schema.ts diff --git a/shared/types/file.ts b/shared/types/file.ts new file mode 100644 index 0000000..4ed289d --- /dev/null +++ b/shared/types/file.ts @@ -0,0 +1,12 @@ +export interface FileMeta { + id: string; + title: string; + filename_download: string; + type: string; + filesize: number; + width?: number; + height?: number; + uploaded_on?: string; + url: string; + previewable: boolean; +} diff --git a/shared/types/index.ts b/shared/types/index.ts new file mode 100644 index 0000000..f0ea818 --- /dev/null +++ b/shared/types/index.ts @@ -0,0 +1,2 @@ +export * from './directus'; +export * from './meilisearch'; diff --git a/app/types/meilisearch/index.ts b/shared/types/meilisearch/index.ts similarity index 100% rename from app/types/meilisearch/index.ts rename to shared/types/meilisearch/index.ts diff --git a/app/types/meilisearch/meili-index.ts b/shared/types/meilisearch/meili-index.ts similarity index 100% rename from app/types/meilisearch/meili-index.ts rename to shared/types/meilisearch/meili-index.ts diff --git a/app/types/meilisearch/search-result.ts b/shared/types/meilisearch/search-result.ts similarity index 100% rename from app/types/meilisearch/search-result.ts rename to shared/types/meilisearch/search-result.ts