# open-file-viewer
**Repository Path**: gitstr/open-file-viewer
## Basic Information
- **Project Name**: open-file-viewer
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-09-06
- **Last Updated**: 2026-09-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Open File Viewer
简体中文
|
English
|
日本語
|
한국어
|
Español
|
Português
Open File Viewer 是一个面向现代 Web 产品的文件预览 SDK。它把 PDF、Office、图片、音视频、压缩包、邮件、图纸、3D、GIS 和代码文件放进同一个可控容器里,并同时支持原生 JavaScript、React、Vue 和 Svelte。
官网
·
关于我们
·
GitHub
·
NPM Core
·
React
·
Vue
·
Svelte
[](https://github.com/xushanpei/open-file-viewer)
[](https://www.npmjs.com/package/@open-file-viewer/core)
[](https://www.npmjs.com/package/@open-file-viewer/react)
[](https://www.npmjs.com/package/@open-file-viewer/vue)
[](https://www.npmjs.com/package/@open-file-viewer/svelte)
[](./LICENSE)
## 为什么选择它
多数业务系统都会遇到附件预览:合同、表格、图纸、压缩包、邮件、图片、视频、代码文件。Open File Viewer 的目标不是做一个只能打开 PDF 的 demo,而是提供一套可以长期演进的文件预览基础设施。
- **容器优先**:所有内容渲染在你传入的 DOM 容器内,不跳窗口,不打断业务页面。
- **多框架兼容**:原生 JavaScript、React、Vue、Svelte 共用同一套 core 能力。
- **格式插件化**:不同文件格式由独立插件负责,方便替换、裁剪和扩展。
- **响应式预览**:支持 `px`、`%`、`vh`、`vw`、`rem`、`calc()` 等 CSS 尺寸,自动响应容器变化。
- **产品级状态**:内置 loading、error、unsupported、download fallback、工具栏、主题和多文件队列。
- **复杂格式可进化**:浏览器能直接预览的格式优先本地渲染,复杂格式可以逐步接入 WASM、专用解析器或服务端转换。
## 安装
```bash
pnpm add @open-file-viewer/core
```
React:
```bash
pnpm add @open-file-viewer/core @open-file-viewer/react
```
Vue:
```bash
pnpm add @open-file-viewer/core @open-file-viewer/vue
```
Svelte:
```bash
pnpm add @open-file-viewer/core @open-file-viewer/svelte
```
PDF 预览需要安装 `pdfjs-dist`:
```bash
pnpm add pdfjs-dist
```
### Vite / Rollup 依赖分包
文本语法、Markdown 和邮件解析依赖会在对应插件首次使用时异步加载。Prism 语言组件会按照依赖关系
顺序加载(例如 `java` 完成后才执行 `scala`),不需要在业务入口手工预加载全部语言。
如果 monorepo 或组件库中存在多份 Prism,可以在 Vite 中启用依赖去重。不要把全部 Prism 语言
组件手工合并到同一个 vendor chunk;保留默认拆分才能让依赖按照异步调用顺序执行。邮件解析器可以
按需单独分包:
```ts
// vite.config.ts
export default defineConfig({
resolve: {
dedupe: ["prismjs"]
},
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("/postal-mime/") || id.includes("/@kenjiuno/msgreader/")) return "ofv-email";
}
}
}
}
});
```
`manualChunks` 不是必需配置。如果项目已有统一的 vendor 分包函数,请让 `/prismjs/components/`
返回 `undefined`,避免把所有带副作用的语言组件折叠到同一个 chunk。
也可以使用 npm 或 yarn:
```bash
npm install @open-file-viewer/core
yarn add @open-file-viewer/core
```
## 快速开始
### 原生 JavaScript
```ts
import {
createViewer,
imagePlugin,
videoPlugin,
audioPlugin,
textPlugin,
pdfPlugin,
officePlugin,
archivePlugin,
emailPlugin,
drawingPlugin,
xmindPlugin,
cadPlugin,
model3dPlugin,
gisPlugin,
fallbackPlugin
} from "@open-file-viewer/core";
import "@open-file-viewer/core/style.css";
import pdfWorkerSrc from "pdfjs-dist/build/pdf.worker.mjs?url";
const plugins = [
imagePlugin(),
videoPlugin(),
audioPlugin(),
textPlugin(),
pdfPlugin({ workerSrc: pdfWorkerSrc }),
officePlugin(),
archivePlugin(),
emailPlugin(),
drawingPlugin(),
xmindPlugin(),
cadPlugin(),
model3dPlugin(),
gisPlugin(),
fallbackPlugin()
];
const viewer = createViewer({
container: "#viewer",
file: fileOrUrl,
fileName: "contract.pdf",
width: "100%",
height: "70vh",
fit: "contain",
toolbar: true,
theme: "auto",
plugins
});
viewer.resize();
viewer.destroy();
```
使用同一组插件,可在挂载预览器前判断文件是否有可用预览路径:
```ts
import { isPreviewSupported } from "@open-file-viewer/core";
const supported = await isPreviewSupported(fileOrUrl, plugins, {
fileName: "contract.pdf",
mimeType: "application/pdf"
});
```
该函数与 `createViewer()` 共用文件规范化逻辑,并按顺序调用
`plugin.match()`;它不会挂载 DOM,也不会调用 `plugin.render()`,且不会把
`fallbackPlugin()` 计为原生预览支持。判断时请保持与 viewer 相同的插件顺序;
匹配到 `fallbackPlugin()` 后会终止判断,不应将它放在原生插件前。
### Umi / utoo 中 PDF 预览失败
如果在 Umi Max、utoo pack 等环境中看到 PDF fallback,并且控制台里有 pdf.js 的
`Cannot set properties of undefined (setting 'onPull')`,通常是构建器和 pdf.js worker
的流式读取通道不兼容。可以开启 `useFetchData`,由主线程先把 PDF 拉成字节后再交给
pdf.js 渲染:
```ts
pdfPlugin({
workerSrc,
useFetchData: true
});
```
这个选项会多占用一份 PDF 文件内存,建议只在遇到上述兼容问题时开启。
### 360 浏览器 PDF 兼容
`pdfPlugin()` 默认使用 `compatibilityMode: "auto"`。当检测到 360 浏览器标识,或当前 Chromium
缺少 PDF.js 4 依赖的 `Promise.withResolvers` 时,会自动补齐兼容实现并切换到 PDF.js legacy worker。
如果企业环境修改了浏览器 UA,无法被自动识别,可以显式开启:
```ts
pdfPlugin({
compatibilityMode: "legacy"
});
```
需要自行托管 worker 时,请让 `workerSrc` 指向同版本的
`pdfjs-dist/legacy/build/pdf.worker.min.mjs`。确认只面向现代 Chrome、Edge 时,也可以设置
`compatibilityMode: "modern"`。
### 远程 PDF fallback 的跨浏览器兼容
当 PDF.js 无法解析或加载远程 PDF URL 时,PDF 插件会使用浏览器内置的 PDF 阅读器作为
iframe fallback。不同 Chromium 浏览器对沙箱 iframe 内置阅读器的脚本权限要求并不完全一致,
因此 `webFallbackScripts` 默认使用 `"auto"`:
- 跨域 PDF URL 会在保留 sandbox 的同时加入 `allow-scripts`,兼容 Edge、360 等内置阅读器;
- 同源 URL 默认仍禁止脚本,避免同时开放脚本执行和同源权限。
业务方可以收紧策略,或只在完全可信的同源 PDF 服务中主动放开:
```ts
pdfPlugin({ webFallbackScripts: "never" }); // 始终使用严格沙箱
pdfPlugin({ webFallbackScripts: "always" }); // 仅用于可信 PDF 地址
```
该配置只影响远程 iframe fallback,不改变正常 PDF.js 渲染,也不会强制设置
`referrerpolicy`。
### qiankun / micro-app 子应用中 Office 预览一直加载
在 qiankun、micro-app 等微前端沙箱里,jszip 依赖的 `setImmediate` polyfill 基于
window `message` 事件监听器实现,而沙箱会在子应用卸载时移除这些监听器,导致
`JSZip.loadAsync` 永远不返回——docx / xlsx / pptx / epub / ofd 等所有 zip 类预览会
一直停留在"正在加载"(参见 [qiankun#2589](https://github.com/umijs/qiankun/issues/2589))。
`createViewer()` 检测到 `__POWERED_BY_QIANKUN__`、`__MICRO_APP_ENVIRONMENT__` 等
标志时,会自动换成基于 MessageChannel 的安全调度器,无需额外配置。如果使用的是
0.1.27 及更早版本,可以在子应用入口的所有 import 之前手动补丁:
```js
if (window.__POWERED_BY_QIANKUN__) {
window.setImmediate = (fn, ...args) => setTimeout(fn, 0, ...args);
}
```
### React
```tsx
import { FileViewer } from "@open-file-viewer/react";
import { imagePlugin, pdfPlugin, officePlugin, textPlugin } from "@open-file-viewer/core";
import "@open-file-viewer/core/style.css";
import pdfWorkerSrc from "pdfjs-dist/build/pdf.worker.mjs?url";
const plugins = [
imagePlugin(),
textPlugin(),
pdfPlugin({ workerSrc: pdfWorkerSrc }),
officePlugin()
];
export function AttachmentPreview({ file }: { file: File }) {
return (
);
}
```
### Vue
```vue
```
### Svelte
```svelte
```
## 适合的场景
| 场景 | Open File Viewer 提供什么 |
| --- | --- |
| OA / ERP / CRM 附件中心 | 合同、表格、图片、邮件、压缩包统一容器预览 |
| 网盘 / 知识库 / 文档系统 | 多文件队列、下载、搜索、全屏、主题适配 |
| 低代码 / 表单系统 | 原生 JS 接入,不强依赖 React、Vue 或 Svelte |
| 工程 / 制造 / GIS 系统 | CAD、3D、GIS、图纸类文件识别和渐进增强 |
| 开发者平台 / 日志平台 | 文本、配置、Markdown、代码高亮和大文件保护 |
## 能力概览
| 能力 | 状态 |
| --- | --- |
| 原生 JS / React / Vue / Svelte 接入 | 已支持 |
| 自定义容器、宽高和响应式尺寸 | 已支持 |
| 多文件队列、切换、当前索引 | 已支持 |
| 工具栏、下载、全屏、打印、搜索 | 已支持 |
| 明暗主题和 `auto` 主题 | 已支持 |
| 本地 `File` / `Blob` / URL / `ArrayBuffer` | 已支持 |
| 插件协议和自定义 fallback | 已支持 |
| PDF、图片、音视频、文本/代码 | 已支持 |
| Office、OFD、EPUB、XPS、邮件、压缩包 | 基础到增强预览,XPS 含轻量 FixedPage SVG 版式 |
| CAD、3D、GIS、绘图白板、设计资产、数据文件 | 识别、基础预览和增强中,SQLite/PSD/PDF-compatible AI 已内置前端内容预览 |
## 格式覆盖
| 类别 | 插件 | 代表格式 |
| --- | --- | --- |
| 图片 | `imagePlugin()` | `jpg`, `png`, `gif`, `webp`, `avif`, `svg`, `bmp`, `tiff`, `heic`, `heif` |
| 视频 | `videoPlugin()` | `mp4`, `webm`, `mov`, `m4v`, `avi`, `mkv`, `flv`, `wmv`, `m3u8`, `m2ts` |
| 音频 | `audioPlugin()` | `mp3`, `wav`, `ogg`, `aac`, `m4a`, `flac`, `opus`, `mid`, `wma` |
| 文本 / 代码 | `textPlugin()` | `txt`, `lrc`, `md`, `json`, `yaml`, `xml`, `csv`, `js`, `ts`, `tsx`, `vue`, `html`, `css`, `py`, `go`, `rs`, `sql`, `sh` |
| PDF / 电子书 | `pdfPlugin()`, `epubPlugin()`, `xpsPlugin()` | `pdf`, `epub`, `xps`, `oxps` |
| Office | `officePlugin()` | `doc`, `docx`, `docm`, `dot`, `rtf`, `odt`, `xls`, `xlsx`, `xlsm`, `xlsb`, `csv`, `ppt`, `pps`, `pptx`, `pptm`, `odp`, `wps`, `et`, `dps` |
| OFD | `ofdPlugin()` | `ofd` |
| 压缩包 | `archivePlugin()` | `zip`, `rar`, `7z`, `tar`, `gz`, `tgz`, `bz2`, `xz` |
| 数据 / 资产 | `assetPlugin()` | `sqlite`, `db`, `parquet`, `avro`, `wasm`, `psd`, `psb`, `ai`, `eps`, `ps`, `webarchive`, `ttf`, `otf`, `woff`, `woff2` |
| 邮件 | `emailPlugin()` | `eml`, `msg`, `mbox` |
| 绘图 / 白板 | `drawingPlugin()` | `drawio`, `dio`, `excalidraw`, `tldraw` |
| 思维导图 | `xmindPlugin()` | `xmind` |
| CAD / 工程 / 芯片版图 | `cadPlugin()` | `dxf`, `dwg`, `dwf`, `step`, `stp`, `iges`, `igs`, `ifc`, `skp`, `sldprt`, `gds`, `gdsii`, `oas`, `oasis` |
| 3D 模型 | `model3dPlugin()` | `gltf`, `glb`, `obj`, `stl`, `fbx`, `dae`, `ply`, `3mf`, `usd`, `usdz` |
| GIS | `gisPlugin()` | `geojson`, `topojson`, `kml`, `kmz`, `gpx`, `shp` |
| 资产识别 | `assetPlugin()` | `ttf`, `woff2`, `psd`, `ai`, `eps`, `sqlite`, `wasm`, `parquet`, `avro` |
复杂格式的预览质量会受浏览器能力、文件结构和依赖解析器影响。当前版本优先保证所有格式都在容器内走可控预览路径;高保真 Office、CAD、设计稿和专有二进制格式可以继续接入专用引擎或服务端转换。
旧版 `ppt` / `pps` 默认走本地 OLE 与 PowerPoint 二进制格式解析,可还原幻灯片尺寸、定位文本、母版位图、JPEG/PNG/TIFF 图片以及常见的压缩 EMF/WMF 图形,文件不会被自动上传。不支持的绘图记录会安全降级;如果业务要求与 Office 像素级一致,仍建议配置 `officePlugin({ convert })`。
### 高保真 Office 转 PDF
浏览器端 DOCX/PPTX/XLSX 解析无法完全复刻 Word/WPS 的排版引擎。带有文本框、绝对定位、复杂字体、页眉页脚或旧版二进制格式的 Office 文件,建议在业务服务端用 LibreOffice、OnlyOffice 或 Microsoft Graph 转成 PDF,再交给内置 PDF 预览渲染。
`officePlugin` 提供可选的 `convert` 钩子。默认不会上传文件;只有业务显式配置这个钩子时,复杂 DOCX 和旧版 Office 才会走转换链路:
```ts
officePlugin({
pdf: {
workerSrc: pdfWorkerSrc
},
async convert({ file, arrayBuffer, reason }) {
const form = new FormData();
form.append("file", new Blob([arrayBuffer]), file.name);
form.append("reason", reason);
const response = await fetch("/api/office/convert-to-pdf", {
method: "POST",
body: form
});
if (!response.ok) {
throw new Error("Office 转 PDF 失败");
}
return {
blob: await response.blob(),
fileName: file.name.replace(/\.[^.]+$/, ".pdf"),
mimeType: "application/pdf"
};
}
});
```
`reason` 目前会标记为 `complex-docx` 或 `legacy-office`。如果转换接口返回的是可访问的 PDF URL,也可以直接 `return { url, fileName: "preview.pdf", mimeType: "application/pdf" }`。
视频预览中,MP4、WebM、MOV 等浏览器原生格式不需要额外依赖;HLS 由内置的 `hls.js` 处理;FLV 和 MPEG-TS/M2TS 属于可选增强能力,需要业务项目自行安装 `mpegts.js`。未安装时会展示下载 fallback,避免安装 `@open-file-viewer/core` 时被 `mpegts.js` 的 git 子依赖影响。
如果业务确实需要 FLV/M2TS,并且 pnpm 11 开启了 `blockExoticSubdeps`,可以在业务项目中覆盖 `mpegts.js` 的传递依赖:
```json
{
"pnpm": {
"overrides": {
"webworkify-webpack": "2.1.5"
}
}
}
```
### DWG / DWF 预览模型
DWG 是 AutoCAD 专有二进制格式,`cadPlugin()` 可以使用高保真 WebGL 场景、内置轻量 SVG 链路或业务自定义渲染器。
- **高保真 WebGL 能力**:配置 `webglDwg` 后,DWG 会在 Worker 中解析,并在可交互 CAD Canvas 中绘制图层、块、填充、线型和文字。配置后若渲染失败会直接抛出错误,不会静默切回 SVG。
- **默认内置能力**:`cadPlugin()` 会自动尝试 LibreDWG WASM 渲染 DWG 模型空间线稿;如果线稿不可靠但文件包含内置缩略图,会展示 DWG 缩略图;如果 LibreDWG 未安装、WASM 未配置或解析失败,则展示 DWG/DWF 元信息、版本、结构线索和转换建议。
- **外部增强能力**:通过 `cadPlugin({ binaryRenderer })` 接入自己的前端引擎、CADViewer、MxCAD、后端转换 PNG/PDF/SVG/DXF 等。`binaryRenderer` 优先级最高,返回实例后会完全接管 DWG/DWF 预览。
- **高保真商用链路**:复杂字体、外部参照、布局/打印空间、大图纸和专业 CAD 效果,建议接入成熟 CAD SDK 或服务端转换。
推荐按下面的方式接入高保真渲染:
```bash
npm install @mlightcad/cad-simple-viewer@1.5.9 @mlightcad/data-model@1.12.3 lodash-es@4.17.21
```
将已安装的 `@mlightcad/cad-simple-viewer/dist/` 目录中的
`libredwg-parser-worker.js` 和 `mtext-renderer-worker.js` 复制到业务的公开静态目录,
然后配置对应目录:
```ts
cadPlugin({
webglDwg: {
engineLoader: () => import("@mlightcad/cad-simple-viewer"),
workerBaseUrl: "/vendor/cad-engine"
}
});
```
显式传入 `engineLoader` 可以让未配置 WebGL DWG 预览的构建不包含这个可选引擎,
也让严格的 esbuild 依赖解析能够在业务侧解析该包。
WebGL 包使用 MIT 许可;其发布的 DWG 解析 Worker 基于 LibreDWG,业务仍需检查该
Worker 的许可证要求。可通过 `webglDwg.baseUrl` 配置 CAD 字体资源,只有确认拥有
分发权的字体才应自行托管。
轻量 LibreDWG SVG 链路可按下面方式接入:
1. 安装可选依赖。固定版本可以保证每次复制出的浏览器资源一致。
```bash
npm install @mlightcad/libredwg-web@0.7.4
```
2. 在业务项目中新增 `scripts/copy-libredwg-assets.mjs`。下面以 Vite、Next.js
常用的 `public/` 静态目录为例;其他框架只需修改 `targetRoot`。
```js
import { cp, mkdir } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const moduleEntry = fileURLToPath(import.meta.resolve("@mlightcad/libredwg-web"));
const packageRoot = dirname(dirname(moduleEntry));
const targetRoot = fileURLToPath(
new URL("../public/vendor/libredwg-web/", import.meta.url)
);
await mkdir(targetRoot, { recursive: true });
await Promise.all([
cp(join(packageRoot, "dist"), join(targetRoot, "dist"), {
recursive: true,
force: true
}),
cp(join(packageRoot, "wasm"), join(targetRoot, "wasm"), {
recursive: true,
force: true
})
]);
```
3. 在本地开发和生产构建之前执行复制脚本:
```json
{
"scripts": {
"assets:dwg": "node scripts/copy-libredwg-assets.mjs",
"predev": "npm run assets:dwg",
"prebuild": "npm run assets:dwg"
}
}
```
如果项目已经有 `predev` 或 `prebuild`,把资源复制命令追加到原脚本中,不要直接
覆盖原有命令。
4. 保留 LibreDWG 包内的 `dist/`、`wasm/` 目录结构,并配置浏览器可直接加载的
ESM 入口。推荐开启 Worker,避免大 DWG 在主线程解析时卡住界面:
```ts
cadPlugin({
libreDwg: {
wasmBaseUrl: "/vendor/libredwg-web/wasm",
workerModuleUrl: "/vendor/libredwg-web/dist/libredwg-web.js",
workerTimeoutMs: 120_000
}
});
```
启动项目后,确认下面两个地址都能返回 `200`:
```text
/vendor/libredwg-web/dist/libredwg-web.js
/vendor/libredwg-web/wasm/libredwg-web.wasm
```
如果应用部署在二级路径下,需要给两个配置地址都加上应用的公共基础路径;服务器
应使用 `application/wasm` 作为 `.wasm` 文件的 MIME 类型。
这里配置的是业务自行托管的静态资源,并不是运行时从外部 CDN 加载。文档站会在
`predev` 和 `prebuild` 阶段执行 `doc/scripts/copy-libredwg-assets.mjs`,把锁定版本
的 npm 包中的 `dist/`、`wasm/` 复制到
`doc/public/vendor/libredwg-web/`。这两个目录可以由依赖稳定重建,并且包含约 6 MB
的 WASM 文件,因此仓库通过 `.gitignore` 排除生成结果。业务项目也应在自己的构建
或部署流程中增加同等的复制步骤;如果部署平台无法执行构建脚本,则需要预先发布
复制后的静态资源。
`@mlightcad/libredwg-web` 是可选的 GPL-3.0 依赖,因此不会直接打包进 MIT 许可的
core 包。启用该能力的业务需要结合自己的分发方式确认上游许可证要求。
配置后,DWG 副本会作为 transferable 传入独立 Worker;切换文件、销毁 viewer
或解析超时都会终止该 Worker。ESM 与 WASM 需要同源部署,或者返回正确的 CORS
响应头;CSP 还需要在 `worker-src` 中允许模块 Worker 与 `blob:`。不能允许
`blob:` 时可通过 `workerFactory` 提供自托管 Worker。未配置 Worker 地址或显式
设置 `useWorker: false` 时,会保留原来的主线程 SVG/缩略图回退链路。
```ts
cadPlugin({
async binaryRenderer({ panel, extension, arrayBuffer, fileName }) {
if (extension !== "dwg") return;
const stage = document.createElement("div");
stage.className = "my-dwg-stage";
panel.append(stage);
// 在这里按需加载你的 DWG 引擎、worker、字体和资源包。
// 例如:await renderDwgWithYourEngine(stage, arrayBuffer, { fileName });
return {
destroy() {
stage.remove();
}
};
}
});
```
## 核心 API
```ts
createViewer(options: PreviewOptions): FileViewer;
```
### PreviewOptions
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `container` | `HTMLElement \| string` | 必填 | 预览容器 |
| `file` | `File \| Blob \| string \| ArrayBuffer` | - | 单文件预览源 |
| `files` | `(PreviewSource \| PreviewItem)[]` | - | 多文件预览队列 |
| `initialIndex` | `number` | `0` | 初始文件索引 |
| `initialPage` | `number` | `1` | 分页预览的初始页码,从 1 开始 |
| `fileName` | `string` | 自动推断 | 文件名,用于扩展名识别 |
| `mimeType` | `string` | 自动推断 | MIME 类型。显式传入的文本 MIME 类型优先于文件扩展名,因此可用 `text/plain` 展示 Markdown 源码、用 `text/markdown` 展示渲染结果。 |
| `width` | `number \| string` | 容器原始宽度 | 预览容器宽度 |
| `height` | `number \| string` | 容器原始高度 | 预览容器高度 |
| `zoom` | `number` | `1` | 初始缩放比例,`1` 表示 100% |
| `fit` | `contain \| cover \| width \| height \| actual \| scale-down` | `contain` | 内容适配方式。直接预览 PDF 且未传该参数时,连续阅读器默认按 `width` 适宽;显式传 `contain` 时仍同时适配宽高。 |
| `plugins` | `PreviewPlugin[]` | `[]` | 插件列表,按顺序匹配 |
| `fallback` | `inline \| download \| custom` | `inline` | 不支持时的兜底策略 |
| `locale` | `zh-CN \| en-US` | `en-US` | 内置状态、fallback、工具栏及插件文案语言 |
| `messages` | `Partial` | - | 覆盖基础文案及 PDF、图片、文本、Office 等插件文案,也允许自定义 key |
| `renderFallback` | `(ctx) => PreviewInstance` | - | 自定义 fallback 渲染器 |
| `toolbar` | `boolean \| PreviewToolbarOptions` | `false` | 工具栏配置 |
| `theme` | `light \| dark \| auto` | `light` | 预览器主题 |
| `className` | `string` | - | 容器附加类名 |
| `onLoad` | `(file) => void` | - | 加载完成回调 |
| `onError` | `(error, file) => void` | - | 错误回调 |
| `onUnsupported` | `(file) => void` | - | 不支持格式回调 |
### 多语言和 fallback 文案
内置状态、fallback、默认工具栏和插件提示文案均为英文。中文产品可以设置 `locale: "zh-CN"`;`messages` 可覆盖 PDF、图片、文本、Office 等插件的单条文案,也允许为自定义插件增加任意字符串 key。工具栏仍可通过 `toolbar.labels` / `toolbar.titles` 进一步定制:
```ts
createViewer({
container: "#viewer",
file,
locale: "zh-CN",
messages: {
unsupportedTitle: "No inline preview available",
downloadFile: "Download original file",
pdfPreviewFailedTitle: "Unable to open report",
imageDownload: "Save original image"
},
plugins
});
```
PDF 预览会显示当前页码、总页数以及上一页/下一页按钮。可以直接在页码输入框中输入目标页并按 Enter 跳转,滚动文档时页码也会自动同步。
## 工具栏自定义
`toolbar: true` 会启用默认工具栏,其文案自动跟随顶层 `locale`。需要业务化时可以逐步扩展,不必重写整套预览器。
### 自定义文案、顺序和图标
```ts
createViewer({
container: "#viewer",
file,
toolbar: {
zoom: true,
rotate: true,
download: true,
fullscreen: true,
search: true,
labels: {
download: "下载",
fullscreen: "全屏",
search: "搜索",
"zoom-in": "放大",
"zoom-out": "缩小",
"zoom-reset": "原始比例",
"rotate-right": "旋转"
},
titles: {
download: "下载当前文件"
},
icons: {
download: ''
},
order: ["search", "zoom-out", "zoom-in", "zoom-reset", "rotate-right", "download", "fullscreen"]
},
plugins
});
```
### 增加业务按钮
```ts
createViewer({
container: "#viewer",
file,
toolbar: {
order: ["download", "favorite", "approve", "share", "fullscreen"],
actions: [
{
id: "favorite",
label: "收藏",
onClick(ctx) {
favoriteFile(ctx.file);
}
},
{
id: "approve",
label: "审批",
onClick(ctx) {
openApprovalDialog(ctx.file);
}
},
{
id: "share",
label: "分享",
disabled(ctx) {
return !ctx.file;
},
onClick(ctx) {
shareFile(ctx.file);
}
}
]
},
plugins
});
```
### 完全替换工具栏
```ts
createViewer({
container: "#viewer",
files,
toolbar: {
render(ctx) {
const bar = document.createElement("div");
bar.className = "business-toolbar";
const name = document.createElement("strong");
name.textContent = ctx.file?.name || "";
const next = document.createElement("button");
next.type = "button";
next.textContent = "下一份";
next.disabled = !ctx.canNext;
next.onclick = () => void ctx.next();
const download = document.createElement("button");
download.type = "button";
download.textContent = "下载";
download.onclick = ctx.download;
bar.append(name, next, download);
return bar;
}
},
plugins
});
```
`render(ctx)` 的上下文包含 `file`、`index`、`length`、`previous()`、`next()`、`goToPage(page)`、`command()`、`download()`、`fullscreen()`、`print()`、`search()` 和 `clearSearch()`。
### React 自定义工具栏
```tsx
(
<>
{ctx.index + 1} / {ctx.length}
>
)}
/>
```
### Vue 自定义工具栏
```vue
{{ ctx.index + 1 }} / {{ ctx.length }}
```
### Svelte 自定义工具栏
```svelte
{#if ctx}
{ctx.index + 1} / {ctx.length}
{/if}
```
样式层面仍然可以覆盖 `.ofv-toolbar`、`.ofv-toolbar button`、`.ofv-toolbar-search` 等 class。自定义图标按钮会额外生成 `.ofv-toolbar-icon` 和 `.ofv-toolbar-label`,方便控制对齐、间距和省略。
### FileViewer
| 方法 | 说明 |
| --- | --- |
| `reload(file?)` | 重新加载当前文件或指定文件 |
| `next()` / `previous()` | 多文件队列切换 |
| `goTo(index)` | 跳转到指定文件 |
| `goToPage(page)` | 跳转到 PDF、DOCX、OFD、XPS、TIFF 等分页预览的指定页码(从 1 开始) |
| `getCurrentIndex()` | 获取当前索引 |
| `resize()` | 主动触发尺寸重算 |
| `destroy()` | 销毁预览器并清理资源 |
## 插件开发
每一种格式都通过插件接入。插件只需要回答两个问题:这个文件是否匹配,以及如何渲染到 `ctx.viewport`。
```ts
import type { PreviewPlugin } from "@open-file-viewer/core";
export function customPlugin(): PreviewPlugin {
return {
name: "custom",
match(file) {
return file.extension === "custom";
},
async render(ctx) {
const element = document.createElement("div");
element.textContent = ctx.file.name;
ctx.viewport.append(element);
return {
resize(size) {
console.log("container resized", size);
},
destroy() {
element.remove();
}
};
}
};
}
```
插件约束:
- 只渲染到 `ctx.viewport` 中。
- 不默认打开新窗口。
- 需要响应容器变化时实现 `resize(size)`。
- 需要清理事件、Object URL、定时器、Canvas/WebGL 资源时实现 `destroy()`。
## 包结构
```txt
packages/
core/ # 框架无关的预览核心和插件
react/ # React 适配层
vue/ # Vue 适配层
svelte/ # Svelte 适配层
examples/
vanilla/ # 原生 JavaScript 示例
react/ # React 示例
vue/ # Vue 示例
svelte/ # Svelte 示例
doc/ # 官网和在线体验
```
## 本地开发
```bash
pnpm install
pnpm check
```
常用命令:
```bash
pnpm dev:doc
pnpm dev:vanilla
pnpm dev:react
pnpm dev:vue
pnpm dev:svelte
pnpm test
pnpm typecheck
pnpm build
pnpm build:examples
pnpm build:doc
pnpm pack:check
```
`pnpm check` 会依次执行测试、类型检查、packages 构建、examples 构建、官网构建和 package exports 校验。
## 路线图
| 版本 | 重点 |
| --- | --- |
| `0.1.x` | Core 插件系统、容器内预览、React/Vue/Svelte/Vanilla 接入、多格式基础预览 |
| `0.2.x` | 工具栏、主题、图片交互、PDF 搜索、统一状态和 fallback |
| `0.3.x` | Markdown/代码阅读器、Office 表格和文档体验增强 |
| `0.4.x` | OFD、邮件、压缩包、绘图和国内业务高频格式增强 |
| `0.5.x` | CAD、3D、GIS、专用解析器和服务端转换协作 |
| `1.0.0` | API 稳定、完整文档站、视觉回归测试和插件开发指南 |
## 社区与支持
Open File Viewer 会持续完善更多格式预览、框架接入和真实业务场景。开源项目不容易,如果它帮你节省了开发时间,欢迎给项目点一个免费的 Star,这对项目后续迭代非常重要。
- 反馈问题:欢迎通过 GitHub Issue 反馈文件样例、排版问题、容器适配问题和新的格式诉求。
## 链接
- 官网:https://open-file-viewer-workspace.void.app
- 关于我们:https://open-file-viewer-workspace.void.app/about.html
- GitHub:https://github.com/xushanpei/open-file-viewer
- NPM Core:https://www.npmjs.com/package/@open-file-viewer/core
- NPM React:https://www.npmjs.com/package/@open-file-viewer/react
- NPM Vue:https://www.npmjs.com/package/@open-file-viewer/vue
- NPM Svelte:https://www.npmjs.com/package/@open-file-viewer/svelte
## License
[MIT](./LICENSE)