最近在一个 Vue 3 + Vite 的 OA 附件页里,我重新走了一遍 DOCX、XLSX、PPTX 和 PDF 的接入链路。开发环境只有一个目标:点附件后在当前页面预览;生产环境却多了三个硬约束:文件接口需要鉴权、站点部署在 /oa/ 子路径、运行时不能访问公网 CDN。
这类需求真正容易出错的地方并不是组件标签,而是文件怎么进入组件、Worker/WASM 从哪里加载,以及构建产物是否把整条预览链路一起交付。
本文给出一套可复现的 Vue 3 落地方法。示例以 2026-08-05 核验的 File Viewer v2.2.5 为基线;我在维护 File Viewer,但下面关于 URL、Blob、Range 和静态资源版本一致性的判断,同样适用于其他浏览器预览方案。

1. 先确定文件输入,不要先纠结组件参数
文件来源通常只有两类:可以直接访问的 URL,或者业务已经拿到的 File/Blob。
公开 URL、同源 Cookie 接口、带短期签名的对象存储地址,可以直接交给预览组件。需要自定义鉴权头、解密或统一处理 401 的接口,应该由业务层完成下载,再把二进制包装为带正确扩展名的 File。
先用 Full 包跑通完整链路:
bash
pnpm add @file-viewer/vue3-full
pnpm add -D @file-viewer/vite-plugin
vue
<script setup lang="ts">
import { ref } from 'vue'
import { FileViewer, type ViewerOptions } from '@file-viewer/vue3-full'
const selected = ref<File>()
const options: ViewerOptions = {
theme: 'light',
styleIsolation: 'auto',
toolbar: { position: 'bottom-right' }
}
function openLocalFile(event: Event) {
const input = event.target as HTMLInputElement
selected.value = input.files?.[0]
}
</script>
<template>
<input
type="file"
accept=".docx,.xlsx,.pptx,.pdf"
@change="openLocalFile"
>
<FileViewer
v-if="selected"
:file="selected"
:options="options"
class="viewer"
/>
</template>
<style scoped>
.viewer {
display: block;
height: min(760px, 78vh);
}
</style>
Full 包已经带完整 preset,不要再次安装并注入同一套 renderer。只有文件类型明确、体积预算经过测量后,再考虑按能力拆包。
2. Worker、WASM 和字体必须跟着前端产物走
本地能打开、部署后白屏,最常见的原因不是 Vue 组件失效,而是只发布了 JavaScript 入口,没有发布解析器运行时。
Vite 配置需要复制同版本的 Worker、WASM、字体和 vendor 资源:
ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileViewerRenderers } from '@file-viewer/vite-plugin'
export default defineConfig({
base: '/oa/',
plugins: [
vue(),
fileViewerRenderers({ copyAssets: true })
]
})

默认资源地址是部署基址下的 file-viewer/。站点基址是 /oa/ 时,应在生产 Network 面板里看到 /oa/file-viewer/...,而不是根路径 /file-viewer/... 或公共 CDN。
如果资产统一放在独立静态目录,可以显式设置:
ts
import { setDefaultFullAssetBaseUrl } from '@file-viewer/vue3-full'
setDefaultFullAssetBaseUrl('/static/file-viewer/')
非 Vite 项目可以在构建阶段复制同版本资源:
bash
npx --no-install file-viewer-copy-assets ./public/file-viewer
不要从另一版本的部署目录手工拷贝 WASM。组件、Worker 和 vendor 资源版本不一致时,业务层往往只能看到模糊的加载失败或空白页。
3. 鉴权接口先下载,再构造带文件名的 File
自定义 Header 不应该塞进预览器的每条内部请求。让业务请求层负责鉴权、重试和审计,预览器只接收已经授权的二进制,边界更清楚。
vue
<script setup lang="ts">
import { ref } from 'vue'
import { FileViewer } from '@file-viewer/vue3-full'
const previewFile = ref<File>()
const errorMessage = ref('')
async function openAttachment(id: string, filename: string) {
errorMessage.value = ''
const response = await fetch(`/api/attachments/${id}`, {
credentials: 'include',
headers: { 'X-Preview-Request': '1' }
})
if (!response.ok) {
errorMessage.value = `附件读取失败:${response.status}`
return
}
const blob = await response.blob()
previewFile.value = new File([blob], filename, {
type: blob.type,
lastModified: Date.now()
})
}
</script>
<template>
<button @click="openAttachment('42', '合同.docx')">
预览合同
</button>
<p v-if="errorMessage">{{ errorMessage }}</p>
<FileViewer v-if="previewFile" :file="previewFile" />
</template>

文件名不能随意丢掉。很多容器格式的 MIME 都不够稳定,正确扩展名仍是选择预览链路的重要依据。
这条方案也有明确代价:fetch + Blob 通常要等完整下载,并在浏览器内存中保留一份二进制。对超大 PDF,如果服务端支持同源 Range 请求,直接 URL 更适合渐进读取;需要强制自定义鉴权头时,则要在流式能力、内存和安全边界之间做取舍。
4. 四个扩展名背后是四条不同链路
DOCX、XLSX、PPTX、PDF 最终进入同一个组件,不代表底层是同一个解析器。
- DOCX 需要处理 OpenXML 关系、样式、图片、字体和分批渲染。
- XLSX 重点是工作表、合并单元格、样式和大表格 Worker。
- PPTX 涉及幻灯片关系、主题、图片以及独立 Worker。
- PDF 由 PDF.js 链路处理,服务端支持 Range 才有条件渐进读取。
发布日验证结果为:208 个已注册扩展名映射到 25 条预览链路。这里描述的是扩展名路由矩阵,不是 208 套独立渲染器,也不代表所有文件都能像素级还原。

本文的 PowerPoint 范围只包含 .pptx,不展开旧版 .ppt。
5. 上线验收必须在生产基址和断网环境完成
我会固定检查下面五项,而不是只看开发服务器有没有报错:
- 使用真实
base执行 build 和 preview,在/oa/下打开页面。 - 用 DOCX、XLSX、PPTX、PDF 各一个脱敏样本检查 Worker、WASM、字体和 vendor 请求,确认没有 404。
- 同时验证 URL 与 File 两条输入,覆盖 401、CORS、Range、下载中断和快速切换附件。
- 验证内容而非首屏:Word 看页眉页脚和表格,Excel 看合并单元格和大表滚动,PPTX 看页序与图片,PDF 看目录、旋转页和打印。
- 断开公网再次测试,确认所有运行时资源都来自自己的网络。
最后还要记录浏览器版本、设备内存、文件大小、页数和峰值内存。没有这些条件,"大文件可用"没有可比较意义。
适用边界
浏览器本地预览适合文件已有业务权限、团队希望减少额外副本,并愿意把运行时资产和兼容性测试纳入前端交付的场景。
如果需要像素级统一输出、服务器全文检索、归档 PDF、病毒扫描或集中水印,服务端转码通常更合适。公开低敏文件且团队不想维护运行时,可以直接评估成熟 SaaS。需要编辑、批注协同或复杂公式重算时,应选 Office、CAD 或专业编辑器,而不是把只读预览器当成编辑器。
组件标签只是入口。文件输入、运行时版本、鉴权、内存和生产回归能一起闭环,才算真正交付了预览功能。
完整 API 与部署参数见 Vue 3 接入与内网部署文档。