Vue 3 文件预览上线后 404:从 URL、Blob 到 Worker/WASM 的完整交付
开发环境里,附件预览往往半天就能跑起来:选中一个 DOCX,组件出现;换成 PDF,也能翻页。可一旦部署到 /oa/ 这样的子路径,页面却开始白屏,Network 里跟着出现一串 Worker、WASM 或字体 404。
这不是一个“改路径就结束”的偶发问题。浏览器端文件预览至少包含两类交付物:业务组件的 JavaScript,以及解析器按需加载的运行时资源。前者进入了前端产物,不等于后者也已经上线。
本文用 Vue 3 + Vite 把这条链路从头走一遍,重点解决四件事:
什么文件应该传 URL,什么文件应该先下载成
File;Worker、WASM、字体和 vendor 资源怎样随应用发布;
子路径部署为什么会改变资源地址;
上线前怎样用真实文件验收,而不是只看组件有没有渲染。

一、先把“文件从哪里来”说清楚
预览组件最终接收的输入通常可以归为两类:URL,或者已经在浏览器内存中的 File/Blob。
可以直接使用 URL 的典型场景包括:
同源地址,登录态由 Cookie 自动携带;
公开文件;
有短期签名、无需额外 Header 的对象存储地址;
服务端支持 Range,希望 PDF 能渐进读取。
更适合由业务层先下载的场景包括:
请求必须携带自定义 Token;
下载前要做解密或二次鉴权;
401、重试、审计必须经过统一请求层;
返回地址不能被预览器直接访问。
关键不是 URL 和 Blob 谁“更先进”,而是权限边界在哪里。URL 保留了浏览器按需请求的可能;fetch + Blob 让业务层拿回控制权,但通常意味着完整下载和额外内存占用。

二、先用完整入口跑通,再谈拆包
文件类型较多、尚未做过体积测量时,可以先用完整入口把行为和部署链路跑通:
pnpm add @file-viewer/vue3-full
pnpm add -D @file-viewer/vite-plugin
一个最小的本地文件预览组件如下:
<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 selectFile(event: Event) {
const input = event.target as HTMLInputElement
selected.value = input.files?.[0]
}
</script>
<template>
<input
type="file"
accept=".docx,.xlsx,.pptx,.pdf"
@change="selectFile"
>
<FileViewer
v-if="selected"
:file="selected"
:options="options"
class="viewer"
/>
</template>
<style scoped>
.viewer {
display: block;
height: min(760px, 78vh);
}
</style>
Full 包已经带完整 preset,不需要再重复安装和注入同一批 renderer。等真实业务的文件分布、首屏体积和缓存命中率都有数据后,再决定是否按能力拆包。
三、组件能渲染,不代表运行时已经交付
DOCX、XLSX、PPTX 和 PDF 看起来进入同一个 Vue 组件,底层却是不同的解析链路。它们可能按需请求 Worker、WASM、字体、字典或 vendor 文件。
Vite 配置需要同时声明站点基址和资源复制:
// 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 }),
],
})

这段配置完成两件互相独立的事:
base: '/oa/'告诉 Vite,应用不是部署在域名根目录;copyAssets: true把与当前包版本匹配的运行时资源复制到构建结果。
生产环境应当命中 /oa/file-viewer/...。如果请求变成 /file-viewer/...,通常是基址丢失;如果目录存在但某些文件 404,则要检查应用包和静态资产是不是来自不同版本。
静态资源由统一服务托管时,可以显式设置地址:
import { setDefaultFullAssetBaseUrl } from '@file-viewer/vue3-full'
setDefaultFullAssetBaseUrl('/static/file-viewer/')
非 Vite 项目则可以在构建阶段复制同版本资产:
npx --no-install file-viewer-copy-assets ./public/file-viewer
不要从旧服务器手工拷一份 WASM 继续用。JavaScript、Worker 和 vendor 之间可能存在内部协议与导出差异,版本错配经常只表现为空白页或一句很模糊的初始化失败。
四、鉴权下载后要保留真实文件名
需要自定义 Header 的附件,可以让业务请求层完成下载,再构造一个带扩展名的 File:
<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>
这里不能只传一个没有后缀的 Blob。Office 容器的 MIME 在不同下载服务中并不总是可靠,正确扩展名仍是选择解析链路的重要依据。
同时要接受这条方案的成本。一个 600 MB 文件完整下载为 Blob 后,浏览器需要持有这份二进制,解析期间还可能产生额外对象。大 PDF 如果可以走同源 URL 和 Range,请优先保留渐进读取;必须使用自定义鉴权头时,再单独评估文件上限、取消请求和内存告警。
五、同一个组件背后不是同一个解析器
不同文件的验收点不能合并成一句“能打开”:
DOCX 要看关系文件、图片、字体、页眉页脚和复杂表格;
XLSX 要看工作表、合并单元格、样式和大表格滚动;
PPTX 要看页面顺序、主题、图片与独立 Worker;
PDF 要看目录、旋转页、中文字体,以及服务端是否真正支持 Range。

当前实现中,208 个已注册扩展名映射到 25 条预览链路。这个数字描述的是扩展名路由矩阵,不是 208 套独立渲染器,也不意味着所有文档都能像原生 Office 一样像素级还原。
本文只讨论 .pptx,不把结构完全不同的旧版 .ppt 混进同一条接入路径。
六、生产验收清单
我现在会固定执行下面六步:
用真实
base执行 build 和 preview,在/oa/下访问,不用开发服务器代替生产验证;准备 DOCX、XLSX、PPTX、PDF 各一个脱敏样本,逐个查看 Network;
确认 Worker、WASM、字体和 vendor 都来自自己的域名,且没有 404;
同时覆盖 URL 与 File 两种输入,测试 401、CORS、Range、取消下载和快速切换;
检查文档内容,而不只检查首屏:表格、页序、目录、旋转页都要进入回归;
断开公网再测一次,确认所谓内网部署没有暗中依赖公共 CDN。
性能记录至少要包含浏览器版本、设备内存、文件大小、页数和峰值内存。没有这些条件,“大文件可用”只是无法复现的印象。
七、什么时候不应该选浏览器端预览
浏览器端解析适合文件已经处在业务权限体系内、团队希望减少额外副本,并愿意把运行时和兼容性测试纳入前端交付的系统。
如果目标是像素级统一输出、服务器全文检索、病毒扫描、集中水印或归档 PDF,服务端转换通常更合适。公开低敏文件且团队不想维护运行时时,可以评估成熟在线服务。需要编辑、批注协同或复杂公式重算时,则应该使用专业编辑器,而不是让只读预览器承担不属于它的职责。
组件标签只是入口。文件输入、静态资产、版本、鉴权、内存和生产回归一起闭环,预览功能才算真正交付。
文中的接口以 File Viewer v2.2.5 为验证基线,完整参数可查阅 Vue 3 接入与部署文档。
浙公网安备 33010602011771号