Files
jinshen-website/app/components/shared/FilePreviewer.vue

178 lines
5.3 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"
>
{{ $t('document-action.open-in-new-tab') }}
</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"
>
{{ $t('document-action.download') }}
</button>
</div>
</header>
<div class="flex-1 overflow-hidden">
<!-- 加载状态 -->
<div v-if="pending" class="h-48 grid place-items-center border rounded">
{{ $t('loading') }}
</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>