# @eternalheart/react-file-preview 中文文档

规范地址：https://npmcn.com/package/%40eternalheart/react-file-preview/

版本：1\.6\.13
npm 元数据更新时间：2026-11-03T12:28:13.265Z
中文译文更新时间：2026-11-14T00:11:59.249Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40eternalheart/react-file-preview
原文对照：https://npmcn.com/package/%40eternalheart/react-file-preview/?lang=original
项目主页：https://wh131462.github.io/file-preview
源码仓库：https://github.com/wh131462/file-preview

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：14.6 MB，94 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：25 个
- peer 依赖：react ^18\.0\.0，react-dom ^18\.0\.0
- 首次发布：2025-11-24T11:53:58.140Z
- 最新版本发布：2026-11-03T12:28:13.013Z
- 近 30 天下载量：2751（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：49（近 12 个月 49 个）

## 中文简介

用于React的现代化,功能丰富的文件预览组件,支持图像,视频,音频,PDF,Office文档(Word,Excel,PowerPoint),Markdown,和代码文件.

## 安装

```sh
npm install @eternalheart/react-file-preview
```

也可以使用：`pnpm add @eternalheart/react-file-preview`、`yarn add @eternalheart/react-file-preview`、`bun add @eternalheart/react-file-preview`

## README

# React File Preview [![npm version](https://img.shields.io/npm/v/@eternalheart/react-file-preview.svg)](https://www.npmjs.com/package/@eternalheart/react-file-preview)[![license](https://img.shields.io/npm/l/@eternalheart/react-file-preview.svg)](https://github.com/wh131462/file-preview/blob/master/LICENSE)[![downloads](https://img.shields.io/npm/dm/@eternalheart/react-file-preview.svg)](https://www.npmjs.com/package/@eternalheart/react-file-preview)

[English](https://npmcn.com/package/%40eternalheart/react-file-preview/README.md) | 简体中文

一个现代化、功能丰富的 React 文件预览组件,支持图片、视频、音频、PDF、Office 文档(Word、Excel、PowerPoint)、Markdown 和代码文件预览。

## ![✨](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2728.svg) 特性

![🎨](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3a8.svg) **现代化 UI** - 简洁现代的界面设计，流畅动画 ![📁](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c1.svg) **多格式支持** - 支持 20+ 种文件格式 ![🪟](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1fa9f.svg) **两种展示模式** - 全屏弹窗 **或** 嵌入式内联预览 ![🖼️](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f5bc.svg) **强大的图片查看器** - 缩放、旋转、拖拽、滚轮缩放 ![🎬](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ac.svg) **自定义视频播放器** - 基于 Video.js,支持多种视频格式 ![🎵](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3b5.svg) **自定义音频播放器** - 精美的音频控制界面 ![📄](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg) **PDF 查看器** - 支持分页浏览 ![📊](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4ca.svg) **Office 文档支持** - Word、Excel、PowerPoint 文件预览 ![📝](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4dd.svg) **Markdown 渲染** - 支持 GitHub Flavored Markdown ![💻](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4bb.svg) **代码高亮** - 支持 40+ 种编程语言 ![🎭](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ad.svg) **流畅动画** - 基于 Framer Motion ![📱](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4f1.svg) **响应式设计** - 适配各种屏幕尺寸 ![⌨️](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg) **键盘导航** - 支持方向键、Home、End 和 ESC 键

## ![📦](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4e6.svg) 安装

```bash
# 使用 npm
npm install @eternalheart/react-file-preview

# 使用 yarn
yarn add @eternalheart/react-file-preview

# 使用 pnpm
pnpm add @eternalheart/react-file-preview
```

**重要提示：** 你还需要导入 CSS 文件：

```tsx
import '@eternalheart/react-file-preview/style.css';
```

> **说明：** PDF 预览所需的 `pdfjs-dist` 依赖会自动安装，无需额外操作。

### PDF.js 配置（可选）

如果你需要预览 PDF 文件，建议配置 PDF.js 使用本地静态文件以提高性能和稳定性：

#### 方式 1: 使用 CDN（默认）

默认情况下，组件会自动使用 unpkg CDN 加载 PDF.js，无需额外配置。

#### 方式 2: 使用本地静态文件（推荐用于生产环境）

1.  将 PDF.js 文件复制到你的 public 目录：

```bash
# 从 node_modules 复制 PDF.js 文件到 public 目录
cp -r node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs public/pdfjs/
cp -r node_modules/pdfjs-dist/cmaps public/pdfjs/
cp -r node_modules/pdfjs-dist/standard_fonts public/pdfjs/
cp -r node_modules/pdfjs-dist/wasm public/pdfjs/
```

2.  在应用入口配置 PDF.js：

```tsx
import { configurePdfjs } from '@eternalheart/react-file-preview';

// 配置使用本地静态文件
configurePdfjs({
  workerSrc: '/pdfjs/pdf.worker.min.mjs',
  cMapUrl: '/pdfjs/cmaps/',
  cMapPacked: true,
  standardFontDataUrl: '/pdfjs/standard_fonts/',
  wasmUrl: '/pdfjs/wasm/'
});
```

#### 使用 Vite 自动复制（推荐）

在 `vite.config.ts` 中配置自动复制：

```ts
import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';

export default defineConfig({
  plugins: [
    viteStaticCopy({
      targets: [
        {
          src: 'node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs',
          dest: 'pdfjs'
        },
        {
          src: 'node_modules/pdfjs-dist/cmaps',
          dest: 'pdfjs'
        },
        {
          src: 'node_modules/pdfjs-dist/standard_fonts',
          dest: 'pdfjs'
        },
        {
          src: 'node_modules/pdfjs-dist/wasm',
          dest: 'pdfjs'
        }
      ]
    })
  ]
});
```

然后在应用入口配置：

```tsx
import { configurePdfjs } from '@eternalheart/react-file-preview';

configurePdfjs({
  workerSrc: '/pdfjs/pdf.worker.min.mjs',
  cMapUrl: '/pdfjs/cmaps/',
  cMapPacked: true,
  standardFontDataUrl: '/pdfjs/standard_fonts/',
  wasmUrl: '/pdfjs/wasm/'
});
```

请在客户端应用初始化阶段、首次渲染 PDF 前调用。Worker 必须使用与当前 `pdfjs-dist` 版本匹配的 legacy 构建。

### Vite 打包提示（AVIF 解码器）

如果你的项目使用 Vite 打包,且项目里安装了 `@jsquash/avif`(直接安装或被其它依赖间接引入),生产构建时可能报错:

```
[commonjs--resolver] Invalid value "iife" for option "worker.format"
- UMD and IIFE output formats are not supported for code-splitting builds.
file: .../@jsquash/avif/codec/enc/avif_enc_mt.js
```

原因:`@jsquash/avif` 内部包含一个使用代码分割的多线程 worker,而 Vite 默认的 `worker.format` 是 `'iife'`,不支持多 chunk 拆分。

**解决方法** —— 在你的 `vite.config.ts` 中加入:

```ts
export default defineConfig({
  // ... 原有配置
  worker: {
    format: 'es',
  },
});
```

`'es'` 会产生 module worker(`type: 'module'`),现代浏览器全部支持,且兼容代码分割。

> 说明:`@jsquash/avif` 仅在浏览器不原生支持 AVIF 时作为兜底使用(Chrome 85+、Firefox 93+、Safari 16+ 均已原生支持)。如果你的目标浏览器都覆盖原生支持范围,也可以直接从依赖中移除 `@jsquash/avif`。

## ![🚀](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f680.svg) 快速开始

📖 **第一次使用？** 查看 [快速开始指南](https://npmcn.com/package/%40eternalheart/react-file-preview/QUICK_START.md) 获取 5 分钟入门教程！

### 基础用法

```tsx
import { FilePreviewModal } from 'react-file-preview';
import 'react-file-preview/style.css';
import { useState } from 'react';

function App() {
  const [files, setFiles] = useState([]);
  const [currentIndex, setCurrentIndex] = useState(0);
  const [isOpen, setIsOpen] = useState(false);

  const handleFileSelect = (file: File) => {
    // 方法 1: 直接传入 File 对象（推荐）
    setFiles([file]);
    setCurrentIndex(0);
    setIsOpen(true);
  };

  return (
    <>
      <input
        type="file"
        onChange={(e) => e.target.files?.[0] && handleFileSelect(e.target.files[0])}
      />

      <FilePreviewModal
        files={files}
        currentIndex={currentIndex}
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        onNavigate={setCurrentIndex}
      />
    </>
  );
}
```

### 多种输入类型

组件支持三种类型的文件输入：

```tsx
import { FilePreviewModal, PreviewFileInput } from '@eternalheart/react-file-preview';
import '@eternalheart/react-file-preview/style.css';

function App() {
  // 假设 file1 来自 File API：<input type="file">、拖拽、
  // 剪贴板粘贴,或 fetch().then(r => r.blob())
  const file1 = new File(['content'], 'example.txt', { type: 'text/plain' });

  const files: PreviewFileInput[] = [
    // 1. 原生 File 对象（组件卸载时自动释放）
    file1,

    // 2. HTTP URL 字符串（按需加载）
    'https://example.com/image.jpg',

    // 3. 带元数据的文件对象（推荐用于远程资源）
    {
      name: 'document.pdf',
      type: 'application/pdf',
      url: '/path/to/document.pdf',
      size: 1024,
    },
  ];

  // 内存提示: 如果你通过 URL.createObjectURL() 生成 URL,
  // 文件移除时请调用 URL.revokeObjectURL() 避免内存泄漏。

  return (
    <FilePreviewModal
      files={files}
      currentIndex={0}
      isOpen={true}
      onClose={() => {}}
    />
  );
}
```

### 嵌入模式 (`FilePreviewEmbed`)

除了全屏弹窗,组件库还提供了**嵌入式**变体,可以将预览内联渲染到任意 div 容器中,适合详情面板、左右分栏布局、仪表盘等场景。

```tsx
import { FilePreviewEmbed } from '@eternalheart/react-file-preview';
import '@eternalheart/react-file-preview/style.css';
import { useState } from 'react';

function InlinePreview() {
  const [index, setIndex] = useState(0);

  const files = [
    'https://example.com/image.jpg',
    { name: 'document.pdf', type: 'application/pdf', url: '/doc.pdf' },
  ];

  return (
    // 嵌入式预览默认填充父容器
    <div style={{ width: '100%', height: 520 }}>
      <FilePreviewEmbed
        files={files}
        currentIndex={index}
        onNavigate={setIndex}
      />
    </div>
  );
}
```

与 `FilePreviewModal` 的区别:

-   不使用 Portal、无全屏遮罩、没有 `isOpen` / `onClose`
-   **不显示关闭按钮**
-   键盘导航 (←/→/Home/End) 作用域限定在嵌入容器内 (基于 focus)
-   尺寸默认 `width: 100%; height: 100%`,可通过 `width` / `height` props 覆盖

```tsx
// 显式指定尺寸
<FilePreviewEmbed files={files} width={800} height={500} />
```

## ![💡](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4a1.svg) 使用示例

### 预览 PowerPoint 文件

```tsx
import { FilePreviewModal } from '@eternalheart/react-file-preview';
import { useState } from 'react';

function PptPreview() {
  const [isOpen, setIsOpen] = useState(false);

  const pptFile = {
    name: 'presentation.pptx',
    type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    url: '/path/to/your/presentation.pptx',
  };

  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        预览 PPT
      </button>

      <FilePreviewModal
        files={[pptFile]}
        currentIndex={0}
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
      />
    </>
  );
}
```

### 预览多个文件

```tsx
const files = [
  { name: 'image.jpg', type: 'image/jpeg', url: '/path/to/image.jpg' },
  { name: 'document.pdf', type: 'application/pdf', url: '/path/to/document.pdf' },
  { name: 'presentation.pptx', type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', url: '/path/to/presentation.pptx' },
  { name: 'spreadsheet.xlsx', type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', url: '/path/to/spreadsheet.xlsx' },
];

<FilePreviewModal
  files={files}
  currentIndex={0}
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  onNavigate={setCurrentIndex}
/>
```

## ![📖](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4d6.svg) 支持的文件格式

### 图片

-   **格式**: JPG, PNG, GIF, WebP, SVG, BMP, ICO, HEIC/HEIF, AVIF, TIFF, RAW, PSD, JPEG 2000
-   **功能**: 缩放 (0.01x - 10x)、旋转、拖拽、滚轮缩放、双击重置、多页 TIFF
-   **解码**: HEIC/RAW/PSD 优先通过 Worker 解码，失败时回退主线程；其他高级格式按需加载

### 视频

-   **格式**: MP4, WebM, OGG, MOV, AVI, MKV, M4V, 3GP, FLV
-   **功能**: 自定义播放器、进度控制、音量调节、全屏播放

### 音频

-   **格式**: MP3, WAV, OGG, M4A, AAC, FLAC
-   **功能**: 自定义播放器、进度条、音量控制、快进/快退

### 文档

-   **PDF**: 分页浏览、缩放
-   **Word**: DOCX 与旧版 DOC（97–2003）支持
-   **Excel**: XLSX 与旧版 XLS（97–2003）支持
-   **PowerPoint**: PPTX 与旧版 PPT（97–2003）幻灯片预览

### 字体

-   **格式**: TTF, OTF, WOFF, WOFF2
-   **功能**: 字体元数据（字体家族、设计师、版本）、字符集预览、自定义文本输入、多字号展示

### CAD / 3D 模型

-   **格式**: DXF, STL, OBJ, GLTF, GLB
-   **功能**: 交互式 3D 查看器（旋转/缩放/平移）、线框/实体切换、网格与坐标轴显示、自动居中

### 代码 & 文本

-   **Markdown**: GitHub Flavored Markdown,代码高亮
-   **代码文件**: Vue、Svelte、Astro、JS/TS、Dart、GraphQL、Protobuf、Prisma、Terraform、PowerShell、Scala 等 40+ 种语言
-   **配置 / 日志**: YAML, TOML, INI, ENV, PROPERTIES, BAT, CMD, TEX, MAP, LOG, DIFF, PATCH 等

### 结构化数据

-   **JSON**: 自动格式化 + 语法高亮
-   **CSV / TSV**: 零依赖解析,表格视图 + 行列统计
-   **XML**: `DOMParser` 校验 + 自动缩进 + 语法高亮

### 字幕 / 歌词

-   **SRT / WebVTT**: 零依赖解析,结构化 cue 列表(索引、时间区间、文本)
-   **LRC / Enhanced LRC**: 歌词文件,`[mm:ss.xx]` 行时间戳(ELRC 额外支持行内 `<mm:ss.xx>` 逐字时间戳),自动解析 `[ti:][ar:][al:]` 等元数据
-   **ASS / SSA**: Advanced SubStation Alpha,提取 Dialogue 事件,自动剥离 `\N` `\h` 与 `{...}` 样式覆盖码,展示 Style 标签
-   **TTML / DFXP**: W3C / Apple Music 使用的 XML 字幕,支持 `begin` / `end` / `dur` 与 `<br/>`

### 压缩包

-   **ZIP**: 树形目录 + 内嵌预览文本/代码/图片,其他类型可下载导出

### Outlook 邮件

-   **MSG**: 邮件头、正文、附件列表

### 电子书

-   **EPUB**: 章节导航、翻页

## ⚠️ 功能限制与性能说明

### 支持等级

**✅ 完全支持（生产可用）**

-   图片（JPG, PNG, GIF, WebP, SVG, BMP, ICO）
-   视频（MP4, WebM, OGG）
-   音频（MP3, WAV, OGG）
-   PDF
-   Markdown
-   代码文件（通过 Shiki 支持 40+ 种语言，按需加载）
-   JSON, CSV, XML

**⚠️ 部分支持（仅供预览）**

-   **Office（DOCX, XLSX, PPTX）**: 基础布局和文本渲染。复杂格式（图表、宏、嵌入对象）可能无法准确渲染。
-   **ZIP**: 目录树浏览 + 文本/代码/图片内联预览。大型压缩包（>100MB）可能导致性能问题。
-   **字体（TTF, OTF, WOFF）**: 元数据 + 字符预览。不支持完整字体特性测试。

**🧪 实验性支持**

-   **MSG（Outlook 邮件）**: 邮件头和纯文本正文。复杂 HTML 正文可能无法正确渲染。
-   **EPUB**: 基础章节导航。CSS 样式可能与原生阅读器有差异。不支持 DRM 保护文件。
-   **字幕格式（SRT, ASS, TTML, LRC）**: 仅文本显示。不支持视频同步或高级样式。

### 性能边界

| 文件大小 | 状态 | 说明 |
| --- | --- | --- |
| < 50MB | ✅ 推荐 | 流畅的预览体验 |
| 50-100MB | ⚠️ 可能卡顿 | 加载时 UI 可能无响应 |
| \> 100MB | ❌ 不推荐 | 可能超出浏览器内存限制 |

**特殊情况：**

-   **ZIP 压缩包**: 性能取决于文件数量，而非仅体积
-   **Office 文档**: 复杂文件（>200 页、大量图片）可能超时
-   **代码高亮**: >5MB 的文件可能需要 3-5 秒高亮时间

### 浏览器兼容性

**最低要求：**

-   Chrome 90+ / Edge 90+
-   Firefox 88+
-   Safari 14+

**已知限制：**

-   **Safari iOS**: 视频自动播放需要用户交互
-   **Firefox**: AVIF 支持需要 Firefox 93+（已包含降级解码器）
-   **Office 格式**: 不同浏览器渲染质量有差异
-   **EPUB**: 旧版浏览器可能不支持某些 CSS 特性

## ![🎮](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ae.svg) API 参考

### FilePreviewModal Props

| 属性 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `files` | `PreviewFileInput[]` | ✅ | 文件列表（支持 File 对象、文件对象或 URL 字符串） |
| `currentIndex` | `number` | ✅ | 当前文件索引 |
| `isOpen` | `boolean` | ✅ | 是否打开预览 |
| `onClose` | `() => void` | ✅ | 关闭回调 |
| `onNavigate` | `(index: number) => void` | ❌ | 导航回调 |
| `customRenderers` | `CustomRenderer[]` | ❌ | 自定义渲染器 |
| `locale` | `Locale` | ❌ | 界面语言（默认 `'en-US'`，内置 `'zh-CN'`） |
| `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | 自定义翻译覆盖 |
| `headless` | `boolean` | ❌ | 无头模式，隐藏工具栏和导航箭头 |
| `theme` | `Theme` | ❌ | 主题模式: `'auto' | 'dark' | 'light'`（默认 `'dark'`） |
| `showDownload` | `boolean` | ❌ | 是否显示下载按钮（默认 `true`） |
| `showClose` | `boolean` | ❌ | 是否显示关闭按钮（modal 模式默认 `true`） |
| `showNavigation` | `boolean` | ❌ | 是否显示文件导航箭头（默认 `true`） |
| `loopNavigation` | `boolean` | ❌ | 是否循环导航文件（默认 `false`） |
| `emptyHint` | `'auto' | 'always' | 'never'` | ❌ | 空内容提示策略（默认 `auto`）：文本类保持空白，结构化类型显示提示 |

### FilePreviewEmbed Props

| 属性 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `files` | `PreviewFileInput[]` | ✅ | \- | 文件列表 |
| `currentIndex` | `number` | ❌ | `0` | 当前文件索引 |
| `onNavigate` | `(index: number) => void` | ❌ | \- | 导航回调 |
| `customRenderers` | `CustomRenderer[]` | ❌ | \- | 自定义渲染器 |
| `width` | `number | string` | ❌ | `'100%'` | 容器宽度 |
| `height` | `number | string` | ❌ | `'100%'` | 容器高度 |
| `className` | `string` | ❌ | \- | 根节点额外 className |
| `style` | `CSSProperties` | ❌ | \- | 根节点额外内联样式 |
| `locale` | `Locale` | ❌ | `'en-US'` | 界面语言（`'zh-CN'` 或 `'en-US'`） |
| `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | \- | 自定义翻译覆盖 |
| `headless` | `boolean` | ❌ | `false` | 无头模式，隐藏工具栏和导航箭头 |
| `theme` | `Theme` | ❌ | `'dark'` | 主题模式: `'auto' | 'dark' | 'light'` |
| `showDownload` | `boolean` | ❌ | `true` | 是否显示下载按钮 |
| `showClose` | `boolean` | ❌ | `false` | 是否显示关闭按钮（embed 模式默认 `false`） |
| `showNavigation` | `boolean` | ❌ | `true` | 是否显示文件导航箭头 |
| `loopNavigation` | `boolean` | ❌ | `false` | 是否循环导航文件 |
| `emptyHint` | `'auto' | 'always' | 'never'` | ❌ | `'auto'` | 空内容提示策略：文本类保持空白，结构化类型显示提示 |

> `FilePreviewEmbed` 没有 `isOpen` / `onClose`,若要显示/隐藏,请在父组件中条件渲染。关闭按钮默认隐藏，但可通过 `showClose` 启用。

### FilePreviewContent（高级用法）

`FilePreviewModal` 和 `FilePreviewEmbed` 都是基于底层 `FilePreviewContent` 组件的薄包装。当你需要构建完全自定义的容器时,可以直接使用它:

```tsx
import { FilePreviewContent } from '@eternalheart/react-file-preview';

<FilePreviewContent
  mode="embed"       // 或 "modal"
  files={files}
  currentIndex={index}
  onNavigate={setIndex}
/>
```

### 文件类型定义

```typescript
// 支持三种文件输入类型
type PreviewFileInput = File | PreviewFileLink | string;

// 1. 原生 File 对象（浏览器 File API）
const file: File = ...;

// 2. 文件对象
interface PreviewFileLink {
  id?: string;       // 可选的唯一标识符
  name: string;      // 文件名
  type: string;      // MIME 类型
  url: string;       // 文件 URL (支持 blob URL 和 HTTP URL)
  size?: number;     // 文件大小（字节）
}

// 3. HTTP URL 字符串
const url: string = 'https://example.com/file.pdf';
```

### 使用示例

```typescript
// 方式 1: 使用原生 File 对象
const files = [file1, file2]; // File 对象数组

// 方式 2: 使用 HTTP URL 字符串
const files = [
  'https://example.com/image.jpg',
  'https://example.com/document.pdf',
];

// 方式 3: 使用文件对象
const files = [
  {
    name: 'presentation.pptx',
    type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    url: '/path/to/presentation.pptx',
  },
];

// 方式 4: 混合使用
const files = [
  file1,  // File 对象
  'https://example.com/image.jpg',  // URL 字符串
  { name: 'doc.pdf', type: 'application/pdf', url: '/doc.pdf' },  // 文件对象
];
```

### 支持的 MIME 类型

#### Office 文档

-   **Word**: `application/vnd.openxmlformats-officedocument.wordprocessingml.document` (.docx)
-   **Word (旧版)**: `application/msword` (.doc)
-   **Excel**: `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` (.xlsx)
-   **Excel (旧版)**: `application/vnd.ms-excel` (.xls)
-   **PowerPoint**: `application/vnd.openxmlformats-officedocument.presentationml.presentation` (.pptx)
-   **PowerPoint (旧版)**: `application/vnd.ms-powerpoint` (.ppt)

#### 其他文档

-   **PDF**: `application/pdf`

#### 字体

-   **TrueType**: `application/x-font-ttf`, `font/ttf` (.ttf)
-   **OpenType**: `application/x-font-otf`, `font/otf` (.otf)
-   **WOFF**: `application/font-woff`, `font/woff` (.woff)
-   **WOFF2**: `application/font-woff2`, `font/woff2` (.woff2)

#### CAD / 3D 模型

-   **DXF**: `application/dxf` (.dxf)
-   **STL**: `model/stl`, `application/vnd.ms-pki.stl` (.stl)
-   **OBJ**: `model/obj` (.obj)
-   **GLTF**: `model/gltf+json` (.gltf)
-   **GLB**: `model/gltf-binary` (.glb)

#### 媒体文件

-   **图片**: `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/svg+xml`, 等
-   **视频**: `video/mp4`, `video/webm`, `video/ogg`, 等
-   **音频**: `audio/mpeg`, `audio/wav`, `audio/ogg`, 等

#### 文本文件

-   **Markdown**: 文件扩展名 `.md` 或 `.markdown`
-   **代码**: 根据文件扩展名自动识别 (`.js`, `.ts`, `.py`, `.java`, 等)
-   **配置 / 日志**: `.yaml`, `.yml`, `.toml`, `.ini`, `.conf`, `.env`, `.properties`, `.bat`, `.cmd`, `.tex`, `.map`, `.log`, `.diff`, `.patch`
-   **纯文本**: `text/plain`

#### 结构化数据

-   **JSON**: `application/json` (.json)
-   **CSV / TSV**: `text/csv` (.csv), `text/tab-separated-values` (.tsv)
-   **XML**: `application/xml`, `text/xml` (.xml)

#### 字幕 / 歌词

-   **SRT**: `application/x-subrip` (.srt)
-   **WebVTT**: `text/vtt` (.vtt)
-   **LRC**: (.lrc)
-   **Enhanced LRC**: (.elrc)
-   **ASS / SSA**: (.ass, .ssa)
-   **TTML / DFXP**: `application/ttml+xml` (.ttml, .dfxp)

#### 压缩包

-   **ZIP**: `application/zip`, `application/x-zip-compressed` (.zip)

#### Outlook 邮件

-   **MSG**: `application/vnd.ms-outlook` (.msg)

#### 电子书

-   **EPUB**: `application/epub+zip` (.epub)

## ![🌐](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f310.svg) 国际化（i18n）

内置中文（默认）和英文，零外部依赖。

```tsx
// 切换为英文
<FilePreviewModal files={files} locale="en-US" ... />

// 自定义覆盖某些翻译
<FilePreviewModal
  files={files}
  locale="en-US"
  messages={{ 'en-US': { 'toolbar.zoom_in': 'Zoom ++' } }}
/>
```

在自定义渲染器中可通过 `useTranslator()` hook 获取翻译函数。

## ![🧩](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f9e9.svg) 自定义渲染器

本库支持自定义渲染器以处理内置不支持的文件类型。自定义渲染器可以可选地提供工具栏配置并集成到本库的架构中。

### 事件驱动的工具栏更新

自定义渲染器可以通过事件驱动机制实现实时工具栏更新：

**优势：**

-   **实时更新**：工具栏立即反映状态变化
-   **更好的性能**：无轮询开销或不必要的重新渲染
-   **类型安全**：完整的 TypeScript 接口支持

**实现方式：**

```tsx
import { forwardRef, useImperativeHandle, useState, useEffect, useMemo, useCallback } from 'react';
import { ToolbarEventEmitter } from '@eternalheart/react-file-preview';
import type { RendererHandle, ToolbarGroup } from '@eternalheart/react-file-preview';

interface CustomRendererProps {
  url: string;
  onPageChange?: (current: number, total: number) => void;
}

export const CustomRenderer = forwardRef<RendererHandle, CustomRendererProps>((props, ref) => {
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(10);
  const emitter = useMemo(() => new ToolbarEventEmitter(), []);
  
  // 状态变化时通知工具栏
  useEffect(() => {
    emitter.notify();
    props.onPageChange?.(currentPage, totalPages);
  }, [currentPage, totalPages, emitter, props]);
  
  const getToolbarGroups = useCallback((): ToolbarGroup[] => [
    {
      items: [
        {
          type: 'button',
          icon: <ChevronLeft className="w-4 h-4" />,
          tooltip: '上一页',
          action: () => setCurrentPage(p => Math.max(1, p - 1)),
          disabled: currentPage <= 1
        },
        {
          type: 'text',
          content: `${currentPage} / ${totalPages}`,
          minWidth: '4rem'
        },
        {
          type: 'button',
          icon: <ChevronRight className="w-4 h-4" />,
          tooltip: '下一页',
          action: () => setCurrentPage(p => Math.min(totalPages, p + 1)),
          disabled: currentPage >= totalPages
        }
      ]
    }
  ], [currentPage, totalPages]);
  
  useImperativeHandle(ref, () => ({
    getToolbarGroups,
    onToolbarChange: (listener) => emitter.subscribe(listener)
  }), [getToolbarGroups, emitter]);
  
  return <div>你的自定义渲染器 UI</div>;
});
```

**主组件使用：**

```tsx
import { CustomRenderer } from './CustomRenderer';

<FilePreviewModal
  files={files}
  customRenderers={[
    {
      test: (file) => file.type === 'application/custom',
      render: () => <CustomRenderer />
    }
  ]}
/>
```

自定义渲染器如需扩展顶层工具栏，应在 renderer 描述对象上实现 `getToolbarGroups(file, ctx)`；`onToolbarChange` 是内置 RendererHandle 的内部机制。

### Renderer 懒加载

所有内置渲染器通过 `React.lazy` 实现代码分割，以最小化主包体积并提升初始加载性能。

**架构：**

-   **注册**：渲染器在 `src/renderers/lazy.tsx` 中注册，使用 named export 并通过 `React.lazy` 包装
-   **加载**：每个渲染器是独立的 chunk，按需加载
-   **回退**：`<Suspense>` 配合 `<RendererLoading />` 处理加载状态

每个渲染器都会输出为独立异步 chunk，并仅在需要时加载。

**实现示例：**

```tsx
// src/renderers/lazy.tsx
import { lazy } from 'react';
import type { CustomRenderer as CustomRendererImpl } from './Custom';

export const CustomRenderer: Lazy<typeof CustomRendererImpl> = lazy(() =>
  import('./Custom').then((m) => ({ default: m.CustomRenderer }))
);
```

```tsx
// src/FilePreviewContent.tsx
import { CustomRenderer } from './renderers/lazy';  // ✅ 懒加载导入
// 禁止: import { CustomRenderer } from './renderers/Custom';  // ❌ 直接导入会破坏代码分割

<Suspense fallback={<RendererLoading />}>
  {fileType === 'custom' && <CustomRenderer ref={rendererRef} url={currentFile.url} />}
</Suspense>
```

**用于自定义渲染器：**

如果你希望自定义渲染器也享受代码分割，可以使用相同的模式：

```tsx
import { lazy, Suspense } from 'react';

const MyCustomRenderer = lazy(() => import('./MyCustomRenderer'));

<FilePreviewModal
  files={files}
  customRenderers={[
    {
      test: (file) => file.type === 'application/custom',
      render: () => (
        <Suspense fallback={<div>加载中...</div>}>
          <MyCustomRenderer />
        </Suspense>
      )
    }
  ]}
/>
```

### i18n 集成

自定义渲染器可以通过 `useTranslator()` hook 访问本库的 i18n 系统，实现一致的多语言支持。

**架构：**

-   **字典源**：`file-preview-core/src/i18n/messages/`（zh-CN.ts、en-US.ts）
-   **禁止硬编码**：所有用户可见文案必须使用翻译 key
-   **自动切换语言**：跟随 `FilePreviewModal` 的 `locale` prop

**在自定义渲染器中使用：**

```tsx
import { useTranslator } from '@eternalheart/react-file-preview';

export const CustomRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
  const t = useTranslator();
  const [error, setError] = useState<string | null>(null);
  
  if (error) {
    return (
      <div className="rfp-text-fg-primary">
        {t('custom.load_failed')}: {error}
      </div>
    );
  }
  
  return (
    <div>
      <button>{t('common.download')}</button>
      <span>{t('custom.loading')}</span>
    </div>
  );
});
```

**新增自定义翻译键：**

对于自定义渲染器，通过 `messages` prop 扩展翻译（不要修改 `node_modules` 中的源文件）：

```tsx
<FilePreviewModal
  files={files}
  locale="en-US"
  messages={{
    'en-US': {
      'custom.load_failed': 'Failed to load custom file',
      'custom.file_size': 'File size: {size} KB'
    },
    'zh-CN': {
      'custom.load_failed': '自定义文件加载失败',
      'custom.file_size': '文件大小: {size} KB'
    }
  }}
  customRenderers={[...]}
/>
```

**指南：**

-   使用 `<scope>.<snake_name>` 格式（如 `custom.load_failed`、`custom.parse_error`）
-   为所有启用的语言（`zh-CN` 和 `en-US`）提供翻译
-   已有通用 key：`common.loading`、`common.download`、`common.close`、`toolbar.*`

**参数化翻译：**

```tsx
// 字典: 'custom.file_size': '文件大小: {size} KB'
t('custom.file_size', { size: 1024 })  // → "文件大小: 1024 KB"
```

**工具栏集成：**

工具栏项也应使用翻译字符串：

```tsx
const getToolbarGroups = useCallback((): ToolbarGroup[] => [
  {
    items: [
      {
        type: 'button',
        icon: <Download className="rfp-w-4 rfp-h-4" />,
        tooltip: t('common.download'),  // ✅ 已翻译
        action: handleDownload
      }
    ]
  }
], [t]);
```

### 主题适配

自定义渲染器必须使用语义化颜色 token 以支持本库的 `'auto' | 'dark' | 'light'` 主题系统。

**语义化 Token 系统：**

所有颜色定义为 CSS 变量（`--fp-*`），通过 Tailwind 类暴露，前缀为 `rfp-`：

| 用途 | 类名 | 说明 |
| --- | --- | --- |
| **文字（fg）** |  |  |
| 主文本 | `rfp-text-fg-primary` | 最高对比度 |
| 正文 | `rfp-text-fg-secondary` | 默认文字 |
| 次要文本 | `rfp-text-fg-tertiary` | 副本、计数器 |
| 弱化文本 | `rfp-text-fg-muted` | 占位符 |
| 禁用文本 | `rfp-text-fg-disabled` | 禁用按钮 |
| **背景（surface）** |  |  |
| 表面层 1 | `rfp-bg-surface-1` | 卡片、最弱 |
| 表面层 2 | `rfp-bg-surface-2` | hover 状态 |
| 表面层 3 | `rfp-bg-surface-3` | 强调 |
| 工具栏 | `rfp-bg-surface-toolbar` | 顶部工具栏 |
| **边框** |  |  |
| 弱边框 | `rfp-border-line-weak` | 细线 |
| 标准边框 | `rfp-border-line` | 默认边框 |
| 强边框 | `rfp-border-line-strong` | 强调 |
| **代码** |  |  |
| 代码背景 | `rfp-bg-code-bg` | Dark：#1e1e1e / Light：#f6f8fa |
| 代码文字 | `rfp-text-code-fg` | 跟随主题 |
| **强调（accent）** |  |  |
| 强调背景 | `rfp-bg-accent` | 主按钮 |
| 强调 hover | `rfp-bg-accent-hover` | hover 状态 |

**✅ 正确用法：**

```tsx
export const CustomRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
  return (
    <div className="rfp-bg-surface-1 rfp-border rfp-border-line-weak rfp-rounded">
      <h2 className="rfp-text-fg-primary rfp-text-lg">标题</h2>
      <p className="rfp-text-fg-secondary">正文内容</p>
      <button className="rfp-bg-surface-2 hover:rfp-bg-surface-3 rfp-text-fg-primary">
        点击
      </button>
      <pre className="rfp-bg-code-bg rfp-text-code-fg">
        {code}
      </pre>
    </div>
  );
});
```

**❌ 错误用法（禁止使用）：**

```tsx
// ❌ 字面色类 — 会破坏主题切换
<div className="rfp-text-white/90 rfp-bg-white/10 rfp-border-white/15">
<div className="rfp-text-gray-700 rfp-bg-gray-100">

// ❌ 内联字面色
<div style={{ color: '#ffffff', background: '#1f2937' }}>

// ❌ 硬编码暗色
<div style={{ background: '#1e1e1e' }}>  // 应改用 rfp-bg-code-bg
```

**支持主题的三方库：**

对于具有 theme prop 的库（如 `react-syntax-highlighter`），使用 `useResolvedTheme()`：

```tsx
import { useResolvedTheme } from '@eternalheart/react-file-preview';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';

export const CodeRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
  const resolvedTheme = useResolvedTheme();  // 'dark' | 'light'
  
  return (
    <SyntaxHighlighter
      language="javascript"
      style={resolvedTheme === 'light' ? vs : vscDarkPlus}
    >
      {code}
    </SyntaxHighlighter>
  );
});
```

**测试：**

务必在 Light 和 Dark 两个主题下测试你的自定义渲染器：

```tsx
<FilePreviewModal
  files={files}
  theme="light"  // 在 'light'、'dark'、'auto' 间切换
  customRenderers={[...]}
/>
```

验证：

-   文字在两个主题下都可读（无白底白字或黑底黑字）
-   边框和分隔线清晰可见
-   hover 状态有足够对比度
-   代码块跟随主题（不固定为暗色）

## ![🎨](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3a8.svg) 自定义样式

组件使用 Tailwind CSS 构建,您可以通过覆盖 CSS 变量来自定义样式:

```css
/* 自定义主题色 */
:root {
  --primary-color: #8b5cf6;
  --secondary-color: #ec4899;
}
```

## ![⌨️](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg) 键盘快捷键

-   `ESC` - 关闭预览
-   `←` - 上一个文件
-   `→` - 下一个文件
-   `Home` - 第一个文件
-   `End` - 最后一个文件
-   `滚轮` - 缩放图片 (仅图片预览)

## ![📚](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4da.svg) 文档

-   [在线演示](https://wh131462.github.io/file-preview) - 在线 Demo

## ![🤖](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f916.svg) Context7 支持

本项目支持 [Context7](https://context7.com/) MCP Server。如果你正在使用 AI 编程助手（如 Claude Code、Cursor 等），可以配置 Context7 MCP Server 来获取 `@eternalheart/react-file-preview` 的最新文档和代码示例，从而获得更好的 AI 辅助开发体验。

### 如何使用

1.  将 Context7 MCP Server 添加到你的 AI 工具配置中
2.  在与 AI 交互时，Context7 会自动提供本库的最新 API 文档和使用示例
3.  无需手动查阅文档，即可获得更精准的代码建议和解答

> 更多关于 Context7 的配置方式，请访问 [Context7 官方文档](https://github.com/upstash/context7)。

## ![🛠️](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f6e0.svg) 开发

### 库开发

```bash
# 克隆仓库
git clone https://github.com/wh131462/file-preview.git

# 安装依赖
pnpm install

# 启动开发服务器（演示应用）
pnpm dev

# 构建库（用于 npm 发布）
pnpm build:lib

# 构建演示应用（用于 GitHub Pages）
pnpm build:demo
```

### 项目结构

```
react-file-preview/
├── src/
│   ├── index.ts              # 库入口文件
│   ├── FilePreviewModal.tsx  # 主组件
│   ├── types.ts              # 类型定义
│   ├── utils/                # 工具函数
│   ├── renderers/            # 文件类型渲染器
│   ├── App.tsx               # 演示应用
│   └── main.tsx              # 演示应用入口
├── lib/                      # 构建后的库（npm 包）
├── dist/                     # 构建后的演示应用（GitHub Pages）
└── vite.config.lib.ts        # 库构建配置
```

## ![📄](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg) 许可证

[MIT](https://npmcn.com/package/%40eternalheart/react-file-preview/LICENSE) © [EternalHeart](https://github.com/wh131462)

## ![🤝](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f91d.svg) 贡献

欢迎提交 Issue 和 Pull Request!

## ![🔗](https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f517.svg) 相关链接

-   [GitHub](https://github.com/wh131462/file-preview)
-   [npm](https://www.npmjs.com/package/@eternalheart/react-file-preview)
-   [在线演示](https://wh131462.github.io/file-preview)
-   [问题反馈](https://github.com/wh131462/file-preview/issues)

## 常见问题

### @eternalheart/react-file-preview 是什么？

用于React的现代化,功能丰富的文件预览组件,支持图像,视频,音频,PDF,Office文档\(Word,Excel,PowerPoint\),Markdown,和代码文件\.

### 如何安装 @eternalheart/react-file-preview？

在项目目录运行 npm install @eternalheart/react-file-preview，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：react（^18\.0\.0）、react-dom（^18\.0\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @eternalheart/react-file-preview 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @eternalheart/react-file-preview 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @eternalheart/react-file-preview 可以商用吗？

@eternalheart/react-file-preview 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @eternalheart/react-file-preview 的下载量有多少？

根据 npm 官方下载统计，@eternalheart/react-file-preview 在 2026/10/14 – 2026/11/12 这 30 天内被下载 2,751 次，日均约 92 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @eternalheart/react-file-preview 还在维护吗？

最新版本 1\.6\.13 发布于 2026/11/03（10 天前）；至今共发布 49 个版本，其中过去 12 个月发布了 49 个；首次发布于 2025/11/24。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

