组件用法
`file-viewer` 的 API 非常克制,目前真正需要记住的是两条输入路径:`url` 和 `file`,以及一个可选的 `options`。 但要把它接进真实业务里,光知道“有这两个参数”还不够,你还得知道渲染器是怎么识别文件类型的、什么时候该传 URL、什么时候应该先把结果包装成带扩展名的 `File`。
这套 API 在多个 npm 包中保持一致: Vue3 使用 @flyfish-group/file-viewer3@1.0.22,Vue2.7 使用 @flyfish-group/file-viewer@1.0.22,React 使用 @flyfish-group/file-viewer-react@1.0.22,纯 JS 使用 @flyfish-group/file-viewer-web@1.0.22。React 和纯 JS 包只负责 iframe、参数和二进制推送,默认加载私有化静态目录 /file-viewer/index.html。
Vue3 和 Vue2 的安装器都会自动带上组件样式,不需要额外引入 CSS。
先记住这 4 条规则
- 渲染器靠文件扩展名选择处理链路,所以文件名本身非常重要。
- 当
file和url同时存在时,组件会优先渲染file。 - 如果你拿到的是
Blob或ArrayBuffer,推荐先包装成带扩展名的File再传入。 - 组件会默认撑满父容器,所以父容器必须有明确高度。
- 同源 PDF URL 默认交给 PDF.js 渐进读取,首屏不再等待外层预览器整包 Blob 下载;文件服务支持 Range 时会自动分片加载,跨域 URL 默认仍走兼容下载链路。
- React、纯 JS 和 iframe 模式默认使用
/file-viewer/index.html,如果静态目录不同,请显式传入viewerUrl。
输入方式怎么选
| 输入方式 | 推荐程度 | 适合场景 | 说明 |
|---|---|---|---|
url | 推荐 | 文件地址可直接访问、链路简单 | 同源 PDF 会直接交给 PDF.js 渐进读取;其他格式会在浏览器内下载文件,再按扩展名选择渲染器 |
file: File | 强烈推荐 | 本地上传、鉴权下载后预览、宿主系统已拿到文件对象 | 最稳妥的二进制接入方式 |
Blob / ArrayBuffer | 先包装再用 | SDK 返回二进制、接口已返回文件流 | 建议先包装成 new File([...], 'demo.pdf'),把文件名和扩展名补全 |
React、纯 JS 和 iframe 适配层允许直接传 Blob 或 ArrayBuffer,但仍然需要同时提供 name,例如 contract.pdf。底层会把二进制推送给私有化 viewer iframe,渲染规则仍由 Vue3 基线预览器决定。
行为规则
- 预览器会根据文件名扩展名自动选择渲染器
- 当
file和url同时存在时,优先渲染file - 当
file被清空后,如果url仍然存在,会自动回退到url - 组件默认撑满父容器,因此父容器必须有稳定高度
- 扩展名匹配会自动转成小写,所以
PDF、DocX这类大小写差异不会影响命中 - OFD、Typst、压缩包、邮件、OLB/DRA、CAD、3D 模型、绘图、EPUB、UMD、PDF、Office、Markdown、音频和代码高亮等渲染器均按需异步加载,只有命中文件类型时才拉取对应代码块
- PPTX 属于浏览器端近似渲染链路,已增强组合图形、主题背景、图片裁剪和 EMF 矢量图片;如果业务材料大量使用复杂动画或专有 Office 特效,建议把真实样本加入上线前回归。
options可以配置内置操作栏、水印、压缩包 Worker、缓存和体积上限。
URL 预览
<script setup lang="ts">
import { ref } from 'vue'
const url = ref('https://example.com/demo.pdf')
</script>
<template>
<div style="height: 100vh">
<file-viewer :url="url" />
</div>
</template>适合场景:
- 文件地址可直接访问
- 文件服务已正确配置 CORS
- 业务无需先进行额外下载或鉴权处理
/download?id=123,路径里没有明确扩展名,预览器就很难准确判断该走哪条渲染链路。遇到这种带签名、带鉴权或通过接口中转的地址,建议直接先把结果取回来,再包装成 File 传入。 文件对象预览
<script setup lang="ts">
import { ref } from 'vue'
const file = ref<File | undefined>()
function onChange(event: Event) {
const input = event.target as HTMLInputElement
const value = input.files?.item(0)
if (value) file.value = value
}
</script>
<template>
<div style="height: 100vh">
<input type="file" @change="onChange" />
<div style="height: calc(100vh - 40px)">
<file-viewer :file="file" />
</div>
</div>
</template>适合场景:
- 用户本地上传后立即预览
- 宿主系统已经拿到了最终文件对象
- 文件访问链路涉及鉴权,不方便直接暴露 URL
鉴权接口返回 Blob 时怎么接
很多业务系统真正拿到的不是公开 URL,而是一个需要携带登录态的下载接口。这种情况下,推荐你先把 Blob 包成 File:
const response = await fetch('/api/preview/contract/123', {
credentials: 'include'
})
const blob = await response.blob()
file.value = new File([blob], 'contract.pdf', {
type: blob.type
})这样做的好处是:
- 文件内容已经在宿主系统的权限链路里拿到了
- 预览器可以通过
contract.pdf正确识别扩展名 - 业务侧可以自己决定缓存、重试和错误提示策略
SDK 返回 ArrayBuffer 时怎么接
如果你的下载 SDK 返回的是 ArrayBuffer,思路也一样,先补一个正确的文件名:
const buffer = await sdk.downloadAttachment(id)
file.value = new File([buffer], 'report.xlsx')这里最重要的不是 ArrayBuffer 本身,而是你给它补上的 report.xlsx 这个文件名。没有扩展名,渲染器就不知道该走哪条链路。
预览器 options
options 用于配置通用交互和重型格式的运行参数。Vue2 / Vue3 组件、React 组件、纯 JS helper 和 iframe 查询参数都使用同一套语义。
<script setup lang="ts">
import { ref } from 'vue'
const url = ref('/example/archive.zip')
const options = {
theme: 'light',
toolbar: {
position: 'bottom-right',
download: true,
print: true,
exportHtml: true
},
watermark: {
text: '内部资料',
opacity: 0.16,
rotate: -24,
color: '#1f7a58'
},
archive: {
workerUrl: '/vendor/libarchive/worker-bundle.js',
cache: true,
maxArchiveSize: 320 * 1024 * 1024,
maxEntryPreviewSize: 64 * 1024 * 1024
},
pdf: {
streaming: 'same-origin',
rangeChunkSize: 64 * 1024
}
}
</script>
<template>
<div style="height: 100vh">
<file-viewer :url="url" :options="options" />
</div>
</template>| 选项 | 说明 |
|---|---|
theme | 预览器主题,支持 light、dark、system。默认 system,继续跟随浏览器 prefers-color-scheme;浅色业务系统建议显式传 light,避免操作系统深色模式把预览区、工具栏或支持主题切换的渲染器自动切成深色 |
toolbar | true 或对象;声明是否允许下载原文件、打印完整渲染结果和导出渲染后 HTML。toolbar.position 支持 auto、top、bottom-right,默认 auto,PDF 会自动悬浮到右下角以避开自身导航栏,其他格式保持顶部。打印按钮还会结合当前文件类型、渲染完成状态和导出适配器动态显隐,Excel 等虚拟表格链路会隐藏打印按钮 |
watermark | true、文字配置或图片配置;支持 text、image、opacity、rotate、gapX/gapY、width/height、字体和颜色 |
archive.workerUrl | libarchive.js Worker 地址;私有化部署时建议把 worker-bundle.js 与 libarchive.wasm 放在同一目录 |
archive.cache | 是否使用 IndexedDB 缓存已解压的压缩包内文件 |
archive.maxArchiveSize | 单个压缩包允许读取目录的最大体积,默认 320MB |
archive.maxEntryPreviewSize | 压缩包内单文件允许预览的最大体积,默认 64MB |
pdf.streaming | PDF URL 渐进读取策略,默认 same-origin;设为 true 时跨域也尝试 URL 直连读取,设为 false 时完全回到 Blob 下载后预览 |
pdf.rangeChunkSize | PDF.js Range 请求分片大小,默认 64KB;仅在文件服务支持 Range 时生效 |
pdf.withCredentials | PDF.js URL 读取是否携带浏览器凭据,默认 false |
图片水印可以传 https URL、相对路径或 data URL。开启图片水印时,文字水印不会重复绘制。
Typst 文件通过 .typ / .typst 扩展名识别。组件会直接读取 Typst 源文件,并在命中格式时按需加载浏览器 WASM 编译器和 SVG 渲染链路;不会自动探测、替换或优先使用同名 PDF。默认 compiler WASM 使用固定 CDN 地址,也可以按私有化部署要求指定自己的地址。
const options = {
typst: {
compilerWasmUrl: 'https://cdn.example.com/typst/typst_ts_web_compiler_bg.wasm'
}
}当前浏览器端编译更适合单文件 Typst 源文档;如果文档依赖同目录图片、字体或拆分源码,建议在业务侧先打包成压缩包预览,保留完整项目结构后再选择内部 .typ 文件。
生命周期钩子和按钮前置校验
Vue2 / Vue3 组件可以直接通过 options.hooks 接收文档生命周期。每个回调都会拿到同一套上下文: type、filename、source、url、file、size、version、timestamp,加载完成时还会带上 duration。
const options = {
hooks: {
onLoadStart(context) {
console.log('开始加载', context.type, context.filename)
},
onLoadComplete(context) {
console.log('加载完成', context.duration)
},
onUnloadStart(context) {
console.log('开始卸载', context.reason)
},
onUnloadComplete(context) {
console.log('卸载完成', context.filename)
}
},
async beforeOperation(context) {
if (context.operation === 'print') {
return await checkCanPrint(context.filename)
}
return true
},
toolbar: {
position: 'bottom-right',
download: true,
print: true,
exportHtml: true,
beforeDownload(context) {
return confirm(`下载 ${context.filename}?`)
}
}
}内置操作当前包括 download、print 和 export-html。options.beforeOperation 是全局前置钩子,toolbar.beforeOperation 会在工具栏层统一执行,toolbar.beforeDownload / toolbar.beforePrint / toolbar.beforeExportHtml 可以对单个按钮做精确控制。任意钩子返回 false 都会取消本次操作。预览器还会在文件切换、渲染完成和能力变化时抛出 operation-availability-change,宿主可以用它同步外部操作按钮。
React、纯 JS 和 iframe 集成无法把函数序列化进 iframe 查询参数,但可以通过事件监听拿到同样的生命周期和操作上下文。纯 JS 使用 onEvent,React 使用 onViewerEvent。
mountViewerFrame(container, {
url: '/files/demo.pdf',
onEvent(event) {
if (event.type === 'flyfish-viewer:lifecycle') {
console.log(event.event, event.payload)
}
}
})打印、导出和水印的交付行为
- 下载原文件会保留用户传入的原始二进制内容,不会把渲染后的页面反向写回文件。
- 打印会生成只包含预览内容和水印的独立打印窗口,不带 Demo 侧边栏、示例选择器或操作工具条。
- PDF 打印和导出 HTML 使用 PDF 专属导出适配器逐页生成完整页面,和当前滚动位置、当前可见页、导航窗格显隐状态都解耦,避免只输出当前页或被滚动容器截断。
- Word 打印和导出会清理预览阶段的缩放、绝对定位、滚动容器和 Demo 全局布局样式,把
.docx/.doc还原成完整白色页面,避免只打印当前视口或第一页。 - 图片、Markdown、代码、PPTX、OFD、CAD、绘图、UMD、OLB/DRA 等可以稳定克隆当前渲染结果的格式会保留打印按钮;Excel 当前使用
styled-exceljs+e-virt-table虚拟渲染,完整工作表不会一次性存在于 DOM 中,因此表格、压缩包、邮件、EPUB、音视频、3D / 模型等更适合交互查看或原文件下载的格式会隐藏打印按钮。 - 导出 HTML 会尽量克隆当前渲染结果,并把 canvas 转成图片,保证图纸、绘图、文档和代码在离线 HTML 中仍有可读内容。
- 水印会同时参与预览、打印和 HTML 导出。文字水印适合内部资料、审批流和归档场景;图片水印适合品牌 Logo 或业务系统标识。
典型切换方式
<script setup lang="ts">
import { ref } from 'vue'
const url = ref('https://example.com/demo.docx')
const file = ref<File | undefined>()
function useRemote() {
file.value = undefined
url.value = 'https://example.com/demo.docx'
}
async function useLocal(blob: Blob) {
file.value = new File([blob], 'local-preview.pdf', { type: blob.type })
}
</script>这一组行为在组件里是稳定的:
file一旦有值,就优先走filefile清空后,如果url还在,就会自动回退到url
常见注意事项
父容器高度
这类问题最容易被忽略。如果父容器没有高度,预览器会跟着塌陷,最终看起来像“没有渲染”。
URL 请求失败
如果控制台里能看到 403、404 或 CORS 报错,问题一般不在预览器本身,而是在目标文件地址的可访问性上。
文件名要尽量准确
预览器依赖文件扩展名选择渲染器,所以无论你传入的是 URL 还是二进制结果,文件名都应该尽量带上正确扩展名。
OFD、Typst、压缩包、邮件、EDA、CAD、3D 模型、绘图和电子书怎么接
.ofd 会使用 DLTech21/ofd.js 仓库源码在浏览器端解析,避开 npm dist 的授权 wasm 分支。.dxf 会使用 CAD 预览器显示图纸。DWG 会先识别误命名 DXF,再尝试提取真实 DWG 的内嵌预览图;完整几何解析仍建议在业务侧转换为 DXF,避免把 GPL 或闭源 DWG 解析运行时打入组件包。
.typ / .typst 会直接读取源文件并加载 Typst WASM 编译和 SVG 渲染链路,组件会按 Typst 输出的页面元数据拆页显示。当前更适合单文件 Typst 文档;如果文档依赖外部图片、字体或拆分源码,建议用压缩包保留项目结构。
.zip、.7z、.rar、.tar、.gz、.xz、.cab、.iso、.jar、.apk、.cbz、.cbr 等压缩包会使用 libarchive.js Worker 读取目录。内部文件在点击后按需解压,并继续交给对应格式预览器。私有化部署时请确认 /vendor/libarchive/worker-bundle.js 和同目录下的 libarchive.wasm 可访问,或者通过 options.archive.workerUrl 指定自己的静态地址。
.eml 使用 postal-mime,.msg 使用 @kenjiuno/msgreader。邮件正文会在安全 iframe 中展示,附件可以下载,也可以继续在线预览。
.olb 与 .dra 使用 cfb 做 OrCAD / Allegro 常见复合文档结构预览。组件会展示结构树、流类型、元件符号、封装、Padstack、属性、可读字符串和诊断信息;它适合附件初筛和内容确认,不替代专业 EDA 软件里的封装编辑、规则校核和电气验证。
3D 模型使用 Three.js,支持 glb/gltf/obj/stl/ply/fbx/dae/3ds/3mf/amf/usd/usda/usdc/usdz/kmz/pcd/wrl/vrml/xyz/vtk/vtp。如果模型有外部贴图、材质或 .bin,远程 url 预览会按原始文件目录继续加载;本地上传时更推荐使用单文件 .glb。step/stp/iges/igs/ifc/3dm 会给出需要 CAD/BIM/WASM 几何内核的原因和转换建议。
.excalidraw 会使用官方 @excalidraw/excalidraw 的 exportToSvg 生成只读 SVG 预览;.drawio / .dio 会使用官方 diagrams.net GraphViewer 渲染,不在组件里手写 mxGraphModel 解析逻辑。
.epub 会使用 epubjs 解析电子书包、目录和章节资源,并在浏览器内提供只读滚动阅读。阅读器会默认打开第一个正文章节,避免停留在封面或空白包装页。
.umd 会按早期移动电子书结构在浏览器端解析文件头、元数据、章节偏移、章节标题和压缩正文。正文数据块使用 pako 解压并按 UTF-16LE 解码,适合历史小说附件和旧移动阅读文件。Kindle 专有格式或 DRM 电子书建议先转换为 EPUB / UMD 文本电子书 / PDF 后再传入预览器。
音频怎么接
.mp3、.mpeg、.wav、.ogg、.oga、.opus、.m4a、.aac、.flac、.weba 会走浏览器原生 <audio> 播放器。不同浏览器对音频编码支持不完全一致,如果要保证最稳的跨端体验,建议优先输出 MP3 或 OGG。
html 会被当网页渲染吗
不会。html 在当前版本属于代码/文本类型,会按源码内容高亮显示,而不是作为真正网页执行。这一层策略更安全,也更适合做代码、模板和片段查看。