diff --git a/src/components/FilePreviewDialog.vue b/src/components/FilePreviewDialog.vue new file mode 100644 index 0000000..8eff5b8 --- /dev/null +++ b/src/components/FilePreviewDialog.vue @@ -0,0 +1,203 @@ + + !v && handleClose()" + > + + + {{ fileName || $t('btn.preview') }} + + + + + + {{ Math.round(pageScale * 100) }}% + + + + + + + {{ currentPage }} / {{ totalPages }} + + + + + + + {{ $t('btn.download') || '下载' }} + + + + + + + + + + {{ $t('msg.noPreview') || '暂不支持预览该类型' }} + + + + + + + + diff --git a/src/views/admin/hospitals/list.vue b/src/views/admin/hospitals/list.vue index 198b039..a98d294 100644 --- a/src/views/admin/hospitals/list.vue +++ b/src/views/admin/hospitals/list.vue @@ -84,20 +84,16 @@ - - - + (previewVisible = v)" + @close="cleanupPreview" + @download="onPreviewDownload" + /> @@ -108,6 +104,7 @@ import { ElMessage } from "element-plus"; import dayjs from "dayjs"; import { useI18n } from "vue-i18n"; import { getAdminHospitals, getUploadFile } from "@/service/modular/admin"; +import FilePreviewDialog from "@/components/FilePreviewDialog.vue"; const { t: $t } = useI18n(); const router = useRouter(); @@ -171,27 +168,77 @@ const goAdd = () => router.push("/admin/hospitals/edit"); const previewVisible = ref(false); const previewUrl = ref(""); +const previewBlob = ref(null); +const previewName = ref(""); +const previewType = ref(""); // 'pdf' | 'image' | '' -const cleanupPreviewUrl = () => { +const cleanupPreview = () => { if (previewUrl.value) { URL.revokeObjectURL(previewUrl.value); previewUrl.value = ""; } + previewBlob.value = null; + previewName.value = ""; + previewType.value = ""; }; -const closePreview = () => { - previewVisible.value = false; - cleanupPreviewUrl(); +// 从路径中提取最后的文件名(含扩展名)作为下载名 +const getFileNameFromPath = (path) => { + if (!path) return ""; + return String(path).replace(/\\/g, "/").split("/").filter(Boolean).pop() || ""; +}; + +// 根据文件名/扩展名+blob 的 mime 判断文件类型 +const getFileType = (fileName, blob) => { + const name = String(fileName || ""); + const ext = name.split(".").pop()?.toLowerCase() || ""; + const mime = blob?.type || ""; + if (ext === "pdf" || mime === "application/pdf") return "pdf"; + if ( + ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"].includes(ext) || + (mime && mime.startsWith("image/")) + ) { + return "image"; + } + return "other"; +}; + +// 浏览器直接下载 blob(指定文件名) +const downloadBlob = (blob, fileName) => { + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = fileName || "download"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + setTimeout(() => URL.revokeObjectURL(url), 0); +}; + +// FilePreviewDialog 下载按钮回调 +const onPreviewDownload = ({ blob, fileName }) => { + if (!blob) return; + downloadBlob(blob, fileName); }; const viewReport = async (attachmentPath) => { if (!attachmentPath) return; - const fileName = String(attachmentPath); + const path = String(attachmentPath); + const downloadName = getFileNameFromPath(path) || path; try { - const blob = await getUploadFile(fileName); - cleanupPreviewUrl(); - previewUrl.value = URL.createObjectURL(blob); - previewVisible.value = true; + const blob = await getUploadFile(path); + const type = getFileType(downloadName, blob); + // PDF / 图片走预览弹窗(PDF 走 vue-pdf-embed),其他格式直接下载 + if (type === "pdf" || type === "image") { + cleanupPreview(); + previewBlob.value = blob; + previewUrl.value = URL.createObjectURL(blob); + previewName.value = downloadName; + previewType.value = type; + previewVisible.value = true; + } else { + downloadBlob(blob, downloadName); + } } catch (e) { ElMessage.error(e?.message || $t('msg.failed')); } diff --git a/src/views/admin/orders/detail.vue b/src/views/admin/orders/detail.vue index 3601445..90cde3e 100644 --- a/src/views/admin/orders/detail.vue +++ b/src/views/admin/orders/detail.vue @@ -244,25 +244,16 @@ - - - - + (previewVisible = v)" + @close="cleanupPreview" + @download="onPreviewDownload" + /> @@ -288,6 +279,7 @@ import { getUploadFile, updateAdminOrder, } from "@/service/modular/admin"; +import FilePreviewDialog from "@/components/FilePreviewDialog.vue"; const { t: $t } = useI18n(); const route = useRoute(); @@ -312,21 +304,20 @@ const goBack = () => router.back(); const previewVisible = ref(false); const previewUrl = ref(""); +const previewBlob = ref(null); +const previewName = ref(""); const previewType = ref(""); // 'pdf' | 'image' | '' -const cleanupPreviewUrl = () => { +const cleanupPreview = () => { if (previewUrl.value) { URL.revokeObjectURL(previewUrl.value); previewUrl.value = ""; } + previewBlob.value = null; + previewName.value = ""; previewType.value = ""; }; -const closePreview = () => { - previewVisible.value = false; - cleanupPreviewUrl(); -}; - // 根据文件名/扩展名+blob 的 mime 判断文件类型 const getFileType = (fileName, blob) => { const name = String(fileName || ""); @@ -342,7 +333,7 @@ const getFileType = (fileName, blob) => { return "other"; }; -// 浏览器直接下载 blob +// 浏览器直接下载 blob(指定文件名) const downloadBlob = (blob, fileName) => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); @@ -354,6 +345,12 @@ const downloadBlob = (blob, fileName) => { setTimeout(() => URL.revokeObjectURL(url), 0); }; +// FilePreviewDialog 下载按钮回调 +const onPreviewDownload = ({ blob, fileName }) => { + if (!blob) return; + downloadBlob(blob, fileName); +}; + const viewReport = async (attachmentPath, originalName) => { if (!attachmentPath) return; const path = String(attachmentPath); @@ -361,9 +358,12 @@ const viewReport = async (attachmentPath, originalName) => { try { const blob = await getUploadFile(path); const type = getFileType(downloadName, blob); + // PDF / 图片走预览弹窗(PDF 走 vue-pdf-embed),其他格式直接下载 if (type === "pdf" || type === "image") { - cleanupPreviewUrl(); + cleanupPreview(); + previewBlob.value = blob; previewUrl.value = URL.createObjectURL(blob); + previewName.value = downloadName; previewType.value = type; previewVisible.value = true; } else {