Files
jinshen-website/app/components/FilePreviewer.vue
R2m1liA ff143f980a
All checks were successful
deploy to server / build-and-deploy (push) Successful in 2m59s
feat: 添加preview路由用于文件预览
- preview路由预览文件
- FilePreviewer组件
- 删除Document卡片的下载按钮,使用单独的页面用于文件下载
- preview布局
2025-10-28 14:35:50 +08:00

178 lines
5.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<section class="h-screen flex flex-col">
<!-- 头部工具栏 -->
<header
v-if="showToolbar && fileMeta"
class="p-3 border-b flex items-center justify-between"
>
<div class="min-w-0">
<h2 class="truncate font-medium" :title="fileMeta.filename_download">
{{ fileMeta.filename_download }}
</h2>
<p class="text-xs text-gray-500">
{{ fileMeta.type }} · {{ formatedSize }} · {{ formatedDate }}
</p>
</div>
<div class="shrink-0 flex items-center gap-2">
<button
class="px-3 py-1.5 rounded border hover:bg-gray-50"
type="button"
:disabled="!fileMeta"
@click="openInNewTab"
>
在新标签打开
</button>
<button
class="px-3 py-1.5 rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50"
type="button"
:disabled="!fileMeta"
@click="download"
>
下载
</button>
</div>
</header>
<div class="flex-1 overflow-hidden">
<!-- 加载状态 -->
<div v-if="pending" class="h-48 grid place-items-center border rounded">
正在加载...
</div>
<div
v-else-if="errorText"
class="h-48 grid place-items-center border rounded text-red-600"
>
{{ errorText }}
</div>
<!-- 文件预览 -->
<ClientOnly v-else>
<div
v-if="fileMeta && previewable"
class="h-full w-full flex justify-center bg-gray-50"
>
<!-- 图片 -->
<el-image
v-if="isImage"
fit="contain"
class="max-w-full max-h-full select-none"
:src="src"
:alt="fileMeta.title || fileMeta.filename_download"
/>
<!-- PDF -->
<iframe
v-else-if="isPdf"
:src="src"
title="PDF 预览"
class="w-full h-full border-0"
/>
<!-- 视频 -->
<video
v-else-if="isVideo"
:src="src"
controls
class="w-full bg-black"
/>
<!-- 文本简单方式用 iframe如需代码高亮可改为拉取文本并渲染 <pre> -->
<iframe
v-else-if="isText"
:src="src"
title="文本预览"
class="w-full h-full border-0"
/>
</div>
</ClientOnly>
</div>
</section>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
/** 预览的文件 ID */
fileId?: string;
file?: FileMeta;
/** 是否显示上方工具栏(文件名、大小、按钮) */
showToolbar?: boolean;
/** 下载 API 基础路径(你的后端流接口),用于“下载”按钮 */
downloadApiBase?: string;
/** 追加到 file.url 的查询(如临时 token形如 { token: 'xxx' } */
extraQuery?: Record<string, string | number | boolean>;
}>(),
{
fileId: undefined,
file: undefined,
showToolbar: true,
downloadApiBase: '/api/download',
extraQuery: undefined,
}
);
const { data, pending, error } = await useFetch<FileMeta>(
() => (props.file ? null : `/api/file/${props.fileId}`),
{ server: true }
);
const errorText = computed(() => error.value?.message ?? null);
const fileMeta = computed(() => props.file ?? data.value ?? null);
/** 预览源地址:支持在 file.url 上追加额外 query如临时 token、inline */
const src = computed<string>(() => {
if (!fileMeta.value) return '';
const url = new URL(fileMeta.value.url, window?.location?.origin);
if (props.extraQuery) {
Object.entries(props.extraQuery).forEach(([k, v]) =>
url.searchParams.set(k, String(v))
);
}
return url.toString();
});
/** 类型判定 */
const isImage = computed(
() => fileMeta.value?.type.startsWith('image/') === true
);
const isPdf = computed(() => fileMeta.value?.type === 'application/pdf');
const isVideo = computed(
() => fileMeta.value?.type.startsWith('video/') === true
);
const isText = computed(
() => fileMeta.value?.type.startsWith('text/') === true
);
const previewable = computed(() => fileMeta.value?.previewable === true);
const formatedSize = computed(() => {
const size = fileMeta.value?.filesize ?? 0;
return formatFileSize(size);
});
const formatedDate = computed(() => {
if (!fileMeta.value?.uploaded_on) return '';
return new Date(fileMeta.value.uploaded_on).toLocaleDateString();
});
/** 下载动作:走你自己的流式后端,避免直链暴露(便于权限与统计) */
function download(): void {
if (!fileMeta.value) return;
const id = fileMeta.value.id;
const a = document.createElement('a');
a.href = `${props.downloadApiBase}/${encodeURIComponent(id)}`;
a.download = fileMeta.value.filename_download;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
/** 新标签打开(直接访问直链,适合预览失败时的兜底体验) */
function openInNewTab(): void {
if (!src.value) return;
window.open(src.value, '_blank', 'noopener,noreferrer');
}
</script>