@eternalheart/angular-file-preview 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +879 -0
- package/README.zh-CN.md +876 -0
- package/lib/LICENSE +22 -0
- package/lib/README.md +879 -0
- package/lib/components/preview/file-preview-toolbar.d.ts +19 -0
- package/lib/components/preview/nav-arrows.d.ts +25 -0
- package/lib/components/preview/renderer-outlet.d.ts +23 -0
- package/lib/components/preview/toolbar-button.d.ts +12 -0
- package/lib/components/resizable-split.d.ts +32 -0
- package/lib/di/locale.service.d.ts +12 -0
- package/lib/di/request.service.d.ts +20 -0
- package/lib/di/theme-mode.d.ts +6 -0
- package/lib/di/theme.service.d.ts +9 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs +55 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs +58 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs +5835 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs +17 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs +284 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs +703 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs +231 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs +552 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs +688 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs +273 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs +288 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs +550 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs +102 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs +516 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs +823 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs +164 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs +655 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs +207 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs +199 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs +194 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs +94 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs +468 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs +193 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs +184 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs +492 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs +68 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs +36 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs +121 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs +78 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs +81 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs +59 -0
- package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs.map +1 -0
- package/lib/fesm2022/eternalheart-angular-file-preview.mjs +2 -0
- package/lib/fesm2022/eternalheart-angular-file-preview.mjs.map +1 -0
- package/lib/file-preview-content.d.ts +65 -0
- package/lib/file-preview-embed.d.ts +30 -0
- package/lib/file-preview-modal.d.ts +34 -0
- package/lib/fp-core.d.ts +1 -0
- package/lib/index.css +2119 -0
- package/lib/index.d.ts +11 -0
- package/lib/renderers/Audio/index.d.ts +74 -0
- package/lib/renderers/Cad/index.d.ts +45 -0
- package/lib/renderers/Cad/toolbar.d.ts +15 -0
- package/lib/renderers/Csv/index.d.ts +26 -0
- package/lib/renderers/Doc/index.d.ts +19 -0
- package/lib/renderers/Docx/index.d.ts +42 -0
- package/lib/renderers/Epub/index.d.ts +57 -0
- package/lib/renderers/Font/font-preview-line.d.ts +26 -0
- package/lib/renderers/Font/index.d.ts +52 -0
- package/lib/renderers/Image/index.d.ts +79 -0
- package/lib/renderers/Json/index.d.ts +61 -0
- package/lib/renderers/Markdown/index.d.ts +35 -0
- package/lib/renderers/Mobi/index.d.ts +51 -0
- package/lib/renderers/Msg/index.d.ts +47 -0
- package/lib/renderers/Pdf/index.d.ts +55 -0
- package/lib/renderers/Ppt/index.d.ts +19 -0
- package/lib/renderers/Pptx/index.d.ts +29 -0
- package/lib/renderers/RendererError.d.ts +9 -0
- package/lib/renderers/RendererLoading.d.ts +7 -0
- package/lib/renderers/Subtitle/index.d.ts +25 -0
- package/lib/renderers/Text/index.d.ts +29 -0
- package/lib/renderers/Unsupported/index.d.ts +15 -0
- package/lib/renderers/Video/index.d.ts +28 -0
- package/lib/renderers/Xlsx/index.d.ts +25 -0
- package/lib/renderers/Xml/index.d.ts +26 -0
- package/lib/renderers/Zip/index.d.ts +58 -0
- package/lib/renderers/Zip/tree-item.d.ts +29 -0
- package/lib/renderers/base.types.d.ts +10 -0
- package/lib/renderers/lazy.d.ts +23 -0
- package/lib/renderers/registry.d.ts +13 -0
- package/lib/renderers/toolbar.types.d.ts +19 -0
- package/lib/types.d.ts +26 -0
- package/lib/utils/audio-player.d.ts +20 -0
- package/lib/utils/shiki-highlight.d.ts +7 -0
- package/package.json +119 -0
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
# Angular File Preview [](https://www.npmjs.com/package/@eternalheart/angular-file-preview)[](https://github.com/wh131462/file-preview/blob/master/LICENSE)[](https://www.npmjs.com/package/@eternalheart/angular-file-preview)
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
一个现代化、功能丰富的 Angular 文件预览组件,支持图片、视频、音频、PDF、Office 文档(Word、Excel、PowerPoint)、Markdown 和代码文件预览。
|
|
6
|
+
|
|
7
|
+
## ✨ 特性
|
|
8
|
+
|
|
9
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3a8.svg" width="16" height="16" alt="🎨" style="vertical-align: middle;" /> **现代化 UI** - 简洁现代的界面设计,流畅动画
|
|
10
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c1.svg" width="16" height="16" alt="📁" style="vertical-align: middle;" /> **多格式支持** - 支持 20+ 种文件格式
|
|
11
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1fa9f.svg" width="16" height="16" alt="🪟" style="vertical-align: middle;" /> **两种展示模式** - 全屏弹窗 **或** 嵌入式内联预览
|
|
12
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f5bc.svg" width="16" height="16" alt="🖼️" style="vertical-align: middle;" /> **强大的图片查看器** - 缩放、旋转、拖拽、滚轮缩放
|
|
13
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ac.svg" width="16" height="16" alt="🎬" style="vertical-align: middle;" /> **自定义视频播放器** - 基于 Video.js,支持多种视频格式
|
|
14
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3b5.svg" width="16" height="16" alt="🎵" style="vertical-align: middle;" /> **自定义音频播放器** - 精美的音频控制界面
|
|
15
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg" width="16" height="16" alt="📄" style="vertical-align: middle;" /> **PDF 查看器** - 支持分页浏览
|
|
16
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4ca.svg" width="16" height="16" alt="📊" style="vertical-align: middle;" /> **Office 文档支持** - Word、Excel、PowerPoint 文件预览
|
|
17
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4dd.svg" width="16" height="16" alt="📝" style="vertical-align: middle;" /> **Markdown 渲染** - 支持 GitHub Flavored Markdown
|
|
18
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4bb.svg" width="16" height="16" alt="💻" style="vertical-align: middle;" /> **代码高亮** - 支持 40+ 种编程语言
|
|
19
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4f1.svg" width="16" height="16" alt="📱" style="vertical-align: middle;" /> **响应式设计** - 适配各种屏幕尺寸
|
|
20
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg" width="16" height="16" alt="⌨️" style="vertical-align: middle;" /> **键盘导航** - 支持方向键和 ESC 键
|
|
21
|
+
|
|
22
|
+
## 📦 安装
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 使用 npm
|
|
26
|
+
npm install @eternalheart/angular-file-preview
|
|
27
|
+
|
|
28
|
+
# 使用 yarn
|
|
29
|
+
yarn add @eternalheart/angular-file-preview
|
|
30
|
+
|
|
31
|
+
# 使用 pnpm
|
|
32
|
+
pnpm add @eternalheart/angular-file-preview
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**重要提示:** 你还需要导入 CSS 文件:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import '@eternalheart/angular-file-preview/style.css';
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> **说明:** PDF 预览所需的 `pdfjs-dist` 依赖会自动安装,无需额外操作。
|
|
42
|
+
|
|
43
|
+
### PDF.js 配置(可选)
|
|
44
|
+
|
|
45
|
+
如果你需要预览 PDF 文件,建议配置 PDF.js 使用本地静态文件以提高性能和稳定性:
|
|
46
|
+
|
|
47
|
+
#### 方式 1: 使用 CDN(默认)
|
|
48
|
+
|
|
49
|
+
默认情况下,组件会自动使用 unpkg CDN 加载 PDF.js,无需额外配置。
|
|
50
|
+
|
|
51
|
+
#### 方式 2: 使用本地静态文件(推荐用于生产环境)
|
|
52
|
+
|
|
53
|
+
1. 将 PDF.js 文件复制到你的 public 目录:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cp -r node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdfjs/
|
|
57
|
+
cp -r node_modules/pdfjs-dist/cmaps public/pdfjs/
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. 在应用入口配置 PDF.js:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import * as pdfjsLib from 'pdfjs-dist/build/pdf.mjs';
|
|
64
|
+
import { configurePdfWorker } from '@eternalheart/angular-file-preview';
|
|
65
|
+
|
|
66
|
+
configurePdfWorker(pdfjsLib, {
|
|
67
|
+
workerSrc: '/pdfjs/pdf.worker.min.mjs',
|
|
68
|
+
cMapUrl: '/pdfjs/cmaps/',
|
|
69
|
+
cMapPacked: true,
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### 使用 Vite 自动复制(推荐)
|
|
74
|
+
|
|
75
|
+
在 `vite.config.ts` 中配置自动复制:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { defineConfig } from 'vite';
|
|
79
|
+
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
|
80
|
+
|
|
81
|
+
export default defineConfig({
|
|
82
|
+
plugins: [
|
|
83
|
+
viteStaticCopy({
|
|
84
|
+
targets: [
|
|
85
|
+
{
|
|
86
|
+
src: 'node_modules/pdfjs-dist/build/pdf.worker.min.mjs',
|
|
87
|
+
dest: 'pdfjs'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
src: 'node_modules/pdfjs-dist/cmaps',
|
|
91
|
+
dest: 'pdfjs'
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Vite 打包提示(AVIF 解码器)
|
|
100
|
+
|
|
101
|
+
如果你的项目使用 Vite 打包,且项目里安装了 `@jsquash/avif`(直接安装或被其它依赖间接引入),生产构建时可能报错:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
[commonjs--resolver] Invalid value "iife" for option "worker.format"
|
|
105
|
+
- UMD and IIFE output formats are not supported for code-splitting builds.
|
|
106
|
+
file: .../@jsquash/avif/codec/enc/avif_enc_mt.js
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
原因:`@jsquash/avif` 内部包含一个使用代码分割的多线程 worker,而 Vite 默认的 `worker.format` 是 `'iife'`,不支持多 chunk 拆分。
|
|
110
|
+
|
|
111
|
+
**解决方法** —— 在你的 `vite.config.ts` 中加入:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
export default defineConfig({
|
|
115
|
+
// ... 原有配置
|
|
116
|
+
worker: {
|
|
117
|
+
format: 'es',
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`'es'` 会产生 module worker(`type: 'module'`),现代浏览器全部支持,且兼容代码分割。
|
|
123
|
+
|
|
124
|
+
> 说明:`@jsquash/avif` 仅在浏览器不原生支持 AVIF 时作为兜底使用(Chrome 85+、Firefox 93+、Safari 16+ 均已原生支持)。如果你的目标浏览器都覆盖原生支持范围,也可以直接从依赖中移除 `@jsquash/avif`。
|
|
125
|
+
|
|
126
|
+
## 🚀 快速开始
|
|
127
|
+
|
|
128
|
+
### 基础用法
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<script setup lang="ts">
|
|
132
|
+
import { ref } from 'vue';
|
|
133
|
+
import { FilePreviewModal } from '@eternalheart/angular-file-preview';
|
|
134
|
+
import '@eternalheart/angular-file-preview/style.css';
|
|
135
|
+
|
|
136
|
+
const files = ref<File[]>([]);
|
|
137
|
+
const currentIndex = ref(0);
|
|
138
|
+
const isOpen = ref(false);
|
|
139
|
+
|
|
140
|
+
const handleFileSelect = (e: Event) => {
|
|
141
|
+
const file = (e.target as HTMLInputElement).files?.[0];
|
|
142
|
+
if (file) {
|
|
143
|
+
files.value = [file];
|
|
144
|
+
currentIndex.value = 0;
|
|
145
|
+
isOpen.value = true;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
</script>
|
|
149
|
+
|
|
150
|
+
<template>
|
|
151
|
+
<input type="file" @change="handleFileSelect" />
|
|
152
|
+
|
|
153
|
+
<FilePreviewModal
|
|
154
|
+
:files="files"
|
|
155
|
+
:current-index="currentIndex"
|
|
156
|
+
:is-open="isOpen"
|
|
157
|
+
@close="isOpen = false"
|
|
158
|
+
@navigate="currentIndex = $event"
|
|
159
|
+
/>
|
|
160
|
+
</template>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 多种输入类型
|
|
164
|
+
|
|
165
|
+
组件支持三种类型的文件输入:
|
|
166
|
+
|
|
167
|
+
```vue
|
|
168
|
+
<script setup lang="ts">
|
|
169
|
+
import { ref } from 'vue';
|
|
170
|
+
import { FilePreviewModal, type PreviewFileInput } from '@eternalheart/angular-file-preview';
|
|
171
|
+
import '@eternalheart/angular-file-preview/style.css';
|
|
172
|
+
|
|
173
|
+
// 假设 file1 来自 File API:<input type="file">、拖拽、
|
|
174
|
+
// 剪贴板粘贴,或 fetch().then(r => r.blob())
|
|
175
|
+
const file1 = new File(['content'], 'example.txt', { type: 'text/plain' });
|
|
176
|
+
|
|
177
|
+
const files: PreviewFileInput[] = [
|
|
178
|
+
// 1. 原生 File 对象(组件卸载时自动释放)
|
|
179
|
+
file1,
|
|
180
|
+
|
|
181
|
+
// 2. HTTP URL 字符串(按需加载)
|
|
182
|
+
'https://example.com/image.jpg',
|
|
183
|
+
|
|
184
|
+
// 3. 带元数据的文件对象(推荐用于远程资源)
|
|
185
|
+
{
|
|
186
|
+
name: 'document.pdf',
|
|
187
|
+
type: 'application/pdf',
|
|
188
|
+
url: '/path/to/document.pdf',
|
|
189
|
+
size: 1024,
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
// 内存提示: 如果你通过 URL.createObjectURL() 生成 URL,
|
|
194
|
+
// 文件移除时请调用 URL.revokeObjectURL() 避免内存泄漏。
|
|
195
|
+
|
|
196
|
+
const isOpen = ref(true);
|
|
197
|
+
</script>
|
|
198
|
+
|
|
199
|
+
<template>
|
|
200
|
+
<FilePreviewModal
|
|
201
|
+
:files="files"
|
|
202
|
+
:current-index="0"
|
|
203
|
+
:is-open="isOpen"
|
|
204
|
+
@close="isOpen = false"
|
|
205
|
+
/>
|
|
206
|
+
</template>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 嵌入模式 (`FilePreviewEmbed`)
|
|
210
|
+
|
|
211
|
+
除了全屏弹窗,组件库还提供了**嵌入式**变体,可以将预览内联渲染到任意容器中,适合详情面板、左右分栏布局、仪表盘等场景。
|
|
212
|
+
|
|
213
|
+
```vue
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
import { ref } from 'vue';
|
|
216
|
+
import { FilePreviewEmbed } from '@eternalheart/angular-file-preview';
|
|
217
|
+
import '@eternalheart/angular-file-preview/style.css';
|
|
218
|
+
|
|
219
|
+
const index = ref(0);
|
|
220
|
+
|
|
221
|
+
const files = [
|
|
222
|
+
'https://example.com/image.jpg',
|
|
223
|
+
{ name: 'document.pdf', type: 'application/pdf', url: '/doc.pdf' },
|
|
224
|
+
];
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<template>
|
|
228
|
+
<div style="width: 100%; height: 520px">
|
|
229
|
+
<FilePreviewEmbed
|
|
230
|
+
:files="files"
|
|
231
|
+
:current-index="index"
|
|
232
|
+
@navigate="index = $event"
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
</template>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
与 `FilePreviewModal` 的区别:
|
|
239
|
+
|
|
240
|
+
- 不使用 Teleport、无全屏遮罩、没有 `isOpen` / `@close`
|
|
241
|
+
- **不显示关闭按钮**
|
|
242
|
+
- 键盘导航 (←/→) 作用域限定在嵌入容器内 (基于 focus)
|
|
243
|
+
- 尺寸默认 `width: 100%; height: 100%`,可通过 `width` / `height` props 覆盖
|
|
244
|
+
|
|
245
|
+
```vue
|
|
246
|
+
<!-- 显式指定尺寸 -->
|
|
247
|
+
<FilePreviewEmbed :files="files" :width="800" :height="500" />
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## 📖 支持的文件格式
|
|
251
|
+
|
|
252
|
+
### 图片
|
|
253
|
+
- **格式**: JPG, PNG, GIF, WebP, SVG, BMP, ICO, HEIC/HEIF, AVIF, TIFF, RAW, PSD, JPEG 2000
|
|
254
|
+
- **功能**: 缩放 (0.01x - 10x)、旋转、拖拽、滚轮缩放、双击重置、多页 TIFF
|
|
255
|
+
- **解码**: HEIC/RAW/PSD 优先通过 Worker 解码,失败时回退主线程;其他高级格式按需加载
|
|
256
|
+
|
|
257
|
+
### 视频
|
|
258
|
+
- **格式**: MP4, WebM, OGG, MOV, AVI, MKV, M4V, 3GP, FLV
|
|
259
|
+
- **功能**: 自定义播放器、进度控制、音量调节、全屏播放
|
|
260
|
+
|
|
261
|
+
### 音频
|
|
262
|
+
- **格式**: MP3, WAV, OGG, M4A, AAC, FLAC
|
|
263
|
+
- **功能**: 自定义播放器、进度条、音量控制、快进/快退
|
|
264
|
+
|
|
265
|
+
### 文档
|
|
266
|
+
- **PDF**: 分页浏览、缩放
|
|
267
|
+
- **Word**: DOCX 与旧版 DOC(97–2003)支持
|
|
268
|
+
- **Excel**: XLSX 与旧版 XLS(97–2003)支持
|
|
269
|
+
- **PowerPoint**: PPTX 与旧版 PPT(97–2003)幻灯片预览
|
|
270
|
+
|
|
271
|
+
### 字体
|
|
272
|
+
- **格式**: TTF, OTF, WOFF, WOFF2
|
|
273
|
+
- **功能**: 字体元数据(字体家族、设计师、版本)、字符集预览、自定义文本输入、多字号展示
|
|
274
|
+
|
|
275
|
+
### CAD / 3D 模型
|
|
276
|
+
- **格式**: DXF, STL, OBJ, GLTF, GLB
|
|
277
|
+
- **功能**: 交互式 3D 查看器(旋转/缩放/平移)、线框/实体切换、网格与坐标轴显示、自动居中
|
|
278
|
+
|
|
279
|
+
### 代码 & 文本
|
|
280
|
+
- **Markdown**: GitHub Flavored Markdown,代码高亮
|
|
281
|
+
- **代码文件**: Vue、Svelte、Astro、JS/TS、Dart、GraphQL、Protobuf、Prisma、Terraform、PowerShell、Scala 等 40+ 种语言
|
|
282
|
+
- **配置 / 日志**: YAML, TOML, INI, ENV, LOG, DIFF, PATCH 等
|
|
283
|
+
|
|
284
|
+
### 结构化数据
|
|
285
|
+
- **JSON**: 自动格式化 + 语法高亮
|
|
286
|
+
- **CSV / TSV**: 零依赖解析,表格视图 + 行列统计
|
|
287
|
+
- **XML**: `DOMParser` 校验 + 自动缩进 + 语法高亮
|
|
288
|
+
|
|
289
|
+
### 字幕 / 歌词
|
|
290
|
+
- **SRT / WebVTT**: 零依赖解析,结构化 cue 列表(索引、时间区间、文本)
|
|
291
|
+
- **LRC / Enhanced LRC**: 歌词文件,`[mm:ss.xx]` 行时间戳(ELRC 额外支持行内 `<mm:ss.xx>` 逐字时间戳),自动解析 `[ti:][ar:][al:]` 等元数据
|
|
292
|
+
- **ASS / SSA**: Advanced SubStation Alpha,提取 Dialogue 事件,自动剥离 `\N` `\h` 与 `{...}` 样式覆盖码,展示 Style 标签
|
|
293
|
+
- **TTML / DFXP**: W3C / Apple Music 使用的 XML 字幕,支持 `begin` / `end` / `dur` 与 `<br/>`
|
|
294
|
+
|
|
295
|
+
### 压缩包
|
|
296
|
+
- **ZIP**: 树形目录 + 内嵌预览文本/代码/图片,其他类型可下载导出
|
|
297
|
+
|
|
298
|
+
### Outlook 邮件
|
|
299
|
+
- **MSG**: 邮件头、正文、附件列表
|
|
300
|
+
|
|
301
|
+
### 电子书
|
|
302
|
+
- **EPUB**: 章节导航、翻页
|
|
303
|
+
|
|
304
|
+
## ⚠️ 功能限制与性能说明
|
|
305
|
+
|
|
306
|
+
### 支持等级
|
|
307
|
+
|
|
308
|
+
**✅ 完全支持(生产可用)**
|
|
309
|
+
- 图片(JPG, PNG, GIF, WebP, SVG, BMP, ICO)
|
|
310
|
+
- 视频(MP4, WebM, OGG)
|
|
311
|
+
- 音频(MP3, WAV, OGG)
|
|
312
|
+
- PDF
|
|
313
|
+
- Markdown
|
|
314
|
+
- 代码文件(通过 Shiki 支持 40+ 种语言,按需加载)
|
|
315
|
+
- JSON, CSV, XML
|
|
316
|
+
|
|
317
|
+
**⚠️ 部分支持(仅供预览)**
|
|
318
|
+
- **Office(DOCX, XLSX, PPTX)**: 基础布局和文本渲染。复杂格式(图表、宏、嵌入对象)可能无法准确渲染。
|
|
319
|
+
- **ZIP**: 目录树浏览 + 文本/代码/图片内联预览。大型压缩包(>100MB)可能导致性能问题。
|
|
320
|
+
- **字体(TTF, OTF, WOFF)**: 元数据 + 字符预览。不支持完整字体特性测试。
|
|
321
|
+
|
|
322
|
+
**🧪 实验性支持**
|
|
323
|
+
- **MSG(Outlook 邮件)**: 邮件头和纯文本正文。复杂 HTML 正文可能无法正确渲染。
|
|
324
|
+
- **EPUB**: 基础章节导航。CSS 样式可能与原生阅读器有差异。不支持 DRM 保护文件。
|
|
325
|
+
- **字幕格式(SRT, ASS, TTML, LRC)**: 仅文本显示。不支持视频同步或高级样式。
|
|
326
|
+
|
|
327
|
+
### 性能边界
|
|
328
|
+
|
|
329
|
+
| 文件大小 | 状态 | 说明 |
|
|
330
|
+
|---------|------|------|
|
|
331
|
+
| < 50MB | ✅ 推荐 | 流畅的预览体验 |
|
|
332
|
+
| 50-100MB | ⚠️ 可能卡顿 | 加载时 UI 可能无响应 |
|
|
333
|
+
| > 100MB | ❌ 不推荐 | 可能超出浏览器内存限制 |
|
|
334
|
+
|
|
335
|
+
**特殊情况:**
|
|
336
|
+
- **ZIP 压缩包**: 性能取决于文件数量,而非仅体积
|
|
337
|
+
- **Office 文档**: 复杂文件(>200 页、大量图片)可能超时
|
|
338
|
+
- **代码高亮**: >5MB 的文件可能需要 3-5 秒高亮时间
|
|
339
|
+
|
|
340
|
+
### 浏览器兼容性
|
|
341
|
+
|
|
342
|
+
**最低要求:**
|
|
343
|
+
- Chrome 90+ / Edge 90+
|
|
344
|
+
- Firefox 88+
|
|
345
|
+
- Safari 14+
|
|
346
|
+
|
|
347
|
+
**已知限制:**
|
|
348
|
+
- **Safari iOS**: 视频自动播放需要用户交互
|
|
349
|
+
- **Firefox**: AVIF 支持需要 Firefox 93+(已包含降级解码器)
|
|
350
|
+
- **Office 格式**: 不同浏览器渲染质量有差异
|
|
351
|
+
- **EPUB**: 旧版浏览器可能不支持某些 CSS 特性
|
|
352
|
+
|
|
353
|
+
## 🎮 API 参考
|
|
354
|
+
|
|
355
|
+
### FilePreviewModal Props
|
|
356
|
+
|
|
357
|
+
| 属性 | 类型 | 必填 | 说明 |
|
|
358
|
+
|------|------|------|------|
|
|
359
|
+
| `files` | `PreviewFileInput[]` | ✅ | 文件列表(支持 File 对象、文件对象或 URL 字符串) |
|
|
360
|
+
| `currentIndex` | `number` | ✅ | 当前文件索引 |
|
|
361
|
+
| `isOpen` | `boolean` | ✅ | 是否打开预览 |
|
|
362
|
+
| `customRenderers` | `CustomRenderer[]` | ❌ | 自定义渲染器 |
|
|
363
|
+
| `locale` | `Locale` | ❌ | 界面语言(默认 `'zh-CN'`,内置 `'en-US'`) |
|
|
364
|
+
| `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | 自定义翻译覆盖 |
|
|
365
|
+
| `headless` | `boolean` | ❌ | 无头模式,隐藏工具栏和导航箭头 |
|
|
366
|
+
| `theme` | `Theme` | ❌ | 主题模式: `'auto' \| 'dark' \| 'light'`(默认 `'dark'`) |
|
|
367
|
+
| `showDownload` | `boolean` | ❌ | 是否显示下载按钮(默认 `true`) |
|
|
368
|
+
| `showClose` | `boolean` | ❌ | 是否显示关闭按钮(modal 模式默认 `true`) |
|
|
369
|
+
|
|
370
|
+
### FilePreviewModal 事件
|
|
371
|
+
|
|
372
|
+
| 事件 | 参数 | 说明 |
|
|
373
|
+
|------|------|------|
|
|
374
|
+
| `close` | - | 关闭预览时触发 |
|
|
375
|
+
| `navigate` | `number` | 导航到其他文件时触发 |
|
|
376
|
+
|
|
377
|
+
### FilePreviewEmbed Props
|
|
378
|
+
|
|
379
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
380
|
+
|------|------|------|--------|------|
|
|
381
|
+
| `files` | `PreviewFileInput[]` | ✅ | - | 文件列表 |
|
|
382
|
+
| `currentIndex` | `number` | ❌ | `0` | 当前文件索引 |
|
|
383
|
+
| `customRenderers` | `CustomRenderer[]` | ❌ | - | 自定义渲染器 |
|
|
384
|
+
| `width` | `number \| string` | ❌ | `'100%'` | 容器宽度 |
|
|
385
|
+
| `height` | `number \| string` | ❌ | `'100%'` | 容器高度 |
|
|
386
|
+
| `locale` | `Locale` | ❌ | `'zh-CN'` | 界面语言(`'zh-CN'` 或 `'en-US'`) |
|
|
387
|
+
| `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | - | 自定义翻译覆盖 |
|
|
388
|
+
| `headless` | `boolean` | ❌ | `false` | 无头模式,隐藏工具栏和导航箭头 |
|
|
389
|
+
| `theme` | `Theme` | ❌ | `'dark'` | 主题模式: `'auto' \| 'dark' \| 'light'` |
|
|
390
|
+
| `showDownload` | `boolean` | ❌ | `true` | 是否显示下载按钮 |
|
|
391
|
+
| `showClose` | `boolean` | ❌ | `false` | 是否显示关闭按钮(embed 模式默认 `false`) |
|
|
392
|
+
|
|
393
|
+
### FilePreviewEmbed 事件
|
|
394
|
+
|
|
395
|
+
| 事件 | 参数 | 说明 |
|
|
396
|
+
|------|------|------|
|
|
397
|
+
| `navigate` | `number` | 导航到其他文件时触发 |
|
|
398
|
+
|
|
399
|
+
### FilePreviewContent(高级用法)
|
|
400
|
+
|
|
401
|
+
`FilePreviewModal` 和 `FilePreviewEmbed` 都是基于底层 `FilePreviewContent` 组件的薄包装。当你需要构建完全自定义的容器时,可以直接使用它:
|
|
402
|
+
|
|
403
|
+
```vue
|
|
404
|
+
<FilePreviewContent
|
|
405
|
+
mode="embed"
|
|
406
|
+
:files="files"
|
|
407
|
+
:current-index="index"
|
|
408
|
+
@navigate="index = $event"
|
|
409
|
+
/>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### 文件类型定义
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// 支持三种文件输入类型
|
|
416
|
+
type PreviewFileInput = File | PreviewFileLink | string;
|
|
417
|
+
|
|
418
|
+
// 1. 原生 File 对象(浏览器 File API)
|
|
419
|
+
const file: File = ...;
|
|
420
|
+
|
|
421
|
+
// 2. 文件对象
|
|
422
|
+
interface PreviewFileLink {
|
|
423
|
+
id?: string; // 可选的唯一标识符
|
|
424
|
+
name: string; // 文件名
|
|
425
|
+
type: string; // MIME 类型
|
|
426
|
+
url: string; // 文件 URL (支持 blob URL 和 HTTP URL)
|
|
427
|
+
size?: number; // 文件大小(字节)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 3. HTTP URL 字符串
|
|
431
|
+
const url: string = 'https://example.com/file.pdf';
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## 🧩 自定义渲染器
|
|
435
|
+
|
|
436
|
+
本库支持自定义渲染器以处理内置不支持的文件类型。自定义渲染器可以可选地提供工具栏配置并集成到本库的架构中。
|
|
437
|
+
|
|
438
|
+
### 事件驱动的工具栏更新
|
|
439
|
+
|
|
440
|
+
自定义渲染器可以通过 Angular 的响应式系统实现实时工具栏更新:
|
|
441
|
+
|
|
442
|
+
**优势:**
|
|
443
|
+
- **实时更新**:工具栏通过 Vue 响应式立即反映状态变化
|
|
444
|
+
- **更好的性能**:无轮询开销,利用 Vue 的高效变更检测
|
|
445
|
+
- **类型安全**:完整的 TypeScript 接口支持
|
|
446
|
+
|
|
447
|
+
**实现方式:**
|
|
448
|
+
|
|
449
|
+
```vue
|
|
450
|
+
<script setup lang="ts">
|
|
451
|
+
import { ref, watch } from 'vue';
|
|
452
|
+
import { ChevronLeft, ChevronRight } from 'lucide-vue-next';
|
|
453
|
+
import type { ToolbarGroup } from '@eternalheart/angular-file-preview';
|
|
454
|
+
|
|
455
|
+
interface Props {
|
|
456
|
+
url: string;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const props = defineProps<Props>();
|
|
460
|
+
const emit = defineEmits<{
|
|
461
|
+
pageChange: [current: number, total: number];
|
|
462
|
+
}>();
|
|
463
|
+
|
|
464
|
+
const currentPage = ref(1);
|
|
465
|
+
const totalPages = ref(10);
|
|
466
|
+
|
|
467
|
+
// 发送页码变化
|
|
468
|
+
watch([currentPage, totalPages], () => {
|
|
469
|
+
emit('pageChange', currentPage.value, totalPages.value);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const getToolbarGroups = (): ToolbarGroup[] => [
|
|
473
|
+
{
|
|
474
|
+
items: [
|
|
475
|
+
{
|
|
476
|
+
type: 'button',
|
|
477
|
+
icon: ChevronLeft,
|
|
478
|
+
tooltip: '上一页',
|
|
479
|
+
action: () => currentPage.value = Math.max(1, currentPage.value - 1),
|
|
480
|
+
disabled: currentPage.value <= 1
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
type: 'text',
|
|
484
|
+
content: `${currentPage.value} / ${totalPages.value}`,
|
|
485
|
+
minWidth: '4rem'
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
type: 'button',
|
|
489
|
+
icon: ChevronRight,
|
|
490
|
+
tooltip: '下一页',
|
|
491
|
+
action: () => currentPage.value = Math.min(totalPages.value, currentPage.value + 1),
|
|
492
|
+
disabled: currentPage.value >= totalPages.value
|
|
493
|
+
}
|
|
494
|
+
]
|
|
495
|
+
}
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
// 暴露给父组件
|
|
499
|
+
defineExpose({
|
|
500
|
+
getToolbarGroups
|
|
501
|
+
});
|
|
502
|
+
</script>
|
|
503
|
+
|
|
504
|
+
<template>
|
|
505
|
+
<div>你的自定义渲染器 UI</div>
|
|
506
|
+
</template>
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
**主组件使用:**
|
|
510
|
+
|
|
511
|
+
```vue
|
|
512
|
+
<script setup>
|
|
513
|
+
import { CustomRenderer } from './CustomRenderer.vue';
|
|
514
|
+
|
|
515
|
+
const files = [
|
|
516
|
+
{ name: 'custom.xyz', type: 'application/custom', url: '/path/to/file' }
|
|
517
|
+
];
|
|
518
|
+
</script>
|
|
519
|
+
|
|
520
|
+
<template>
|
|
521
|
+
<FilePreviewModal
|
|
522
|
+
:files="files"
|
|
523
|
+
:custom-renderers="[
|
|
524
|
+
{
|
|
525
|
+
test: (file) => file.type === 'application/custom',
|
|
526
|
+
render: () => CustomRenderer
|
|
527
|
+
}
|
|
528
|
+
]"
|
|
529
|
+
/>
|
|
530
|
+
</template>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
主组件通过 Vue 的响应式系统自动追踪 `getToolbarGroups()` 的响应式变化。无需手动订阅。
|
|
534
|
+
|
|
535
|
+
### Renderer 懒加载
|
|
536
|
+
|
|
537
|
+
所有内置渲染器通过 `defineAsyncComponent` 实现代码分割,以最小化主包体积并提升初始加载性能。
|
|
538
|
+
|
|
539
|
+
**架构:**
|
|
540
|
+
|
|
541
|
+
- **注册**:渲染器在 `src/renderers/lazy.ts` 中注册,使用 `defineAsyncComponent` 包装
|
|
542
|
+
- **加载**:每个渲染器是独立的 chunk,按需加载
|
|
543
|
+
- **回退**:`RendererLoading` 组件处理加载状态
|
|
544
|
+
|
|
545
|
+
每个渲染器都会输出为独立异步 chunk,并仅在需要时加载。
|
|
546
|
+
|
|
547
|
+
**实现示例:**
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
// src/renderers/lazy.ts
|
|
551
|
+
import { defineAsyncComponent } from 'vue';
|
|
552
|
+
|
|
553
|
+
const wrap = (loader: () => Promise<any>) =>
|
|
554
|
+
defineAsyncComponent({
|
|
555
|
+
loader,
|
|
556
|
+
loadingComponent: RendererLoading,
|
|
557
|
+
delay: 0
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
export const CustomRenderer = wrap(() => import('./Custom/index.vue'));
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
```vue
|
|
564
|
+
<!-- src/FilePreviewContent.vue -->
|
|
565
|
+
<script setup>
|
|
566
|
+
import { CustomRenderer } from './renderers/lazy'; // ✅ 懒加载导入
|
|
567
|
+
// 禁止: import CustomRenderer from './renderers/Custom/index.vue'; // ❌ 直接导入会破坏代码分割
|
|
568
|
+
</script>
|
|
569
|
+
|
|
570
|
+
<template>
|
|
571
|
+
<CustomRenderer
|
|
572
|
+
v-if="fileType === 'custom'"
|
|
573
|
+
ref="rendererRef"
|
|
574
|
+
:url="currentFile.url"
|
|
575
|
+
/>
|
|
576
|
+
</template>
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**用于自定义渲染器:**
|
|
580
|
+
|
|
581
|
+
如果你希望自定义渲染器也享受代码分割,可以使用相同的模式:
|
|
582
|
+
|
|
583
|
+
```vue
|
|
584
|
+
<script setup>
|
|
585
|
+
import { defineAsyncComponent } from 'vue';
|
|
586
|
+
|
|
587
|
+
const MyCustomRenderer = defineAsyncComponent(() => import('./MyCustomRenderer.vue'));
|
|
588
|
+
|
|
589
|
+
const files = [...];
|
|
590
|
+
</script>
|
|
591
|
+
|
|
592
|
+
<template>
|
|
593
|
+
<FilePreviewModal
|
|
594
|
+
:files="files"
|
|
595
|
+
:custom-renderers="[
|
|
596
|
+
{
|
|
597
|
+
test: (file) => file.type === 'application/custom',
|
|
598
|
+
render: () => MyCustomRenderer
|
|
599
|
+
}
|
|
600
|
+
]"
|
|
601
|
+
/>
|
|
602
|
+
</template>
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### i18n 集成
|
|
606
|
+
|
|
607
|
+
自定义渲染器可以通过 `useTranslator()` composable 访问本库的 i18n 系统,实现一致的多语言支持。
|
|
608
|
+
|
|
609
|
+
**架构:**
|
|
610
|
+
|
|
611
|
+
- **字典源**:`file-preview-core/src/i18n/messages/`(zh-CN.ts、en-US.ts)
|
|
612
|
+
- **禁止硬编码**:所有用户可见文案必须使用翻译 key
|
|
613
|
+
- **自动切换语言**:跟随 `FilePreviewModal` 的 `locale` prop
|
|
614
|
+
|
|
615
|
+
**在自定义渲染器中使用:**
|
|
616
|
+
|
|
617
|
+
```vue
|
|
618
|
+
<script setup lang="ts">
|
|
619
|
+
import { ref } from 'vue';
|
|
620
|
+
import { useTranslator } from '@eternalheart/angular-file-preview';
|
|
621
|
+
|
|
622
|
+
interface Props {
|
|
623
|
+
url: string;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const props = defineProps<Props>();
|
|
627
|
+
const { t } = useTranslator();
|
|
628
|
+
const error = ref<string | null>(null);
|
|
629
|
+
</script>
|
|
630
|
+
|
|
631
|
+
<template>
|
|
632
|
+
<div v-if="error" class="vfp-text-fg-primary">
|
|
633
|
+
{{ t('custom.load_failed') }}: {{ error }}
|
|
634
|
+
</div>
|
|
635
|
+
<div v-else>
|
|
636
|
+
<button>{{ t('common.download') }}</button>
|
|
637
|
+
<span>{{ t('custom.loading') }}</span>
|
|
638
|
+
</div>
|
|
639
|
+
</template>
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**使用说明:**
|
|
643
|
+
|
|
644
|
+
- 在 `<template>` 中:直接使用 `t('key')`(自动解包)
|
|
645
|
+
- 在 `<script>` 中:命令式调用使用 `t.value('key')`
|
|
646
|
+
|
|
647
|
+
**新增自定义翻译键:**
|
|
648
|
+
|
|
649
|
+
对于自定义渲染器,通过 `messages` prop 扩展翻译(不要修改 `node_modules` 中的源文件):
|
|
650
|
+
|
|
651
|
+
```vue
|
|
652
|
+
<template>
|
|
653
|
+
<FilePreviewModal
|
|
654
|
+
:files="files"
|
|
655
|
+
locale="en-US"
|
|
656
|
+
:messages="{
|
|
657
|
+
'en-US': {
|
|
658
|
+
'custom.load_failed': 'Failed to load custom file',
|
|
659
|
+
'custom.file_size': 'File size: {size} KB'
|
|
660
|
+
},
|
|
661
|
+
'zh-CN': {
|
|
662
|
+
'custom.load_failed': '自定义文件加载失败',
|
|
663
|
+
'custom.file_size': '文件大小: {size} KB'
|
|
664
|
+
}
|
|
665
|
+
}"
|
|
666
|
+
:custom-renderers="[...]"
|
|
667
|
+
/>
|
|
668
|
+
</template>
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**指南:**
|
|
672
|
+
- 使用 `<scope>.<snake_name>` 格式(如 `custom.load_failed`、`custom.parse_error`)
|
|
673
|
+
- 为所有启用的语言(`zh-CN` 和 `en-US`)提供翻译
|
|
674
|
+
- 已有通用 key:`common.loading`、`common.download`、`common.close`、`toolbar.*`
|
|
675
|
+
|
|
676
|
+
**参数化翻译:**
|
|
677
|
+
|
|
678
|
+
```vue
|
|
679
|
+
<template>
|
|
680
|
+
<!-- 字典: 'custom.file_size': '文件大小: {size} KB' -->
|
|
681
|
+
<span>{{ t('custom.file_size', { size: 1024 }) }}</span>
|
|
682
|
+
<!-- → "文件大小: 1024 KB" -->
|
|
683
|
+
</template>
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
**工具栏集成:**
|
|
687
|
+
|
|
688
|
+
工具栏项也应使用翻译字符串:
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
const getToolbarGroups = (): ToolbarGroup[] => [
|
|
692
|
+
{
|
|
693
|
+
items: [
|
|
694
|
+
{
|
|
695
|
+
type: 'button',
|
|
696
|
+
icon: Download,
|
|
697
|
+
tooltip: t.value('common.download'), // ✅ 已翻译(script 中用 .value)
|
|
698
|
+
action: handleDownload
|
|
699
|
+
}
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
];
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
### 主题适配
|
|
706
|
+
|
|
707
|
+
自定义渲染器必须使用语义化颜色 token 以支持本库的 `'auto' | 'dark' | 'light'` 主题系统。
|
|
708
|
+
|
|
709
|
+
**语义化 Token 系统:**
|
|
710
|
+
|
|
711
|
+
所有颜色定义为 CSS 变量(`--fp-*`),通过 Tailwind 类暴露,前缀为 `vfp-`:
|
|
712
|
+
|
|
713
|
+
| 用途 | 类名 | 说明 |
|
|
714
|
+
|------|------|------|
|
|
715
|
+
| **文字(fg)** | | |
|
|
716
|
+
| 主文本 | `vfp-text-fg-primary` | 最高对比度 |
|
|
717
|
+
| 正文 | `vfp-text-fg-secondary` | 默认文字 |
|
|
718
|
+
| 次要文本 | `vfp-text-fg-tertiary` | 副本、计数器 |
|
|
719
|
+
| 弱化文本 | `vfp-text-fg-muted` | 占位符 |
|
|
720
|
+
| 禁用文本 | `vfp-text-fg-disabled` | 禁用按钮 |
|
|
721
|
+
| **背景(surface)** | | |
|
|
722
|
+
| 表面层 1 | `vfp-bg-surface-1` | 卡片、最弱 |
|
|
723
|
+
| 表面层 2 | `vfp-bg-surface-2` | hover 状态 |
|
|
724
|
+
| 表面层 3 | `vfp-bg-surface-3` | 强调 |
|
|
725
|
+
| 工具栏 | `vfp-bg-surface-toolbar` | 顶部工具栏 |
|
|
726
|
+
| **边框** | | |
|
|
727
|
+
| 弱边框 | `vfp-border-line-weak` | 细线 |
|
|
728
|
+
| 标准边框 | `vfp-border-line` | 默认边框 |
|
|
729
|
+
| 强边框 | `vfp-border-line-strong` | 强调 |
|
|
730
|
+
| **代码** | | |
|
|
731
|
+
| 代码背景 | `vfp-bg-code-bg` | Dark:#1e1e1e / Light:#f6f8fa |
|
|
732
|
+
| 代码文字 | `vfp-text-code-fg` | 跟随主题 |
|
|
733
|
+
| **强调(accent)** | | |
|
|
734
|
+
| 强调背景 | `vfp-bg-accent` | 主按钮 |
|
|
735
|
+
| 强调 hover | `vfp-bg-accent-hover` | hover 状态 |
|
|
736
|
+
|
|
737
|
+
**✅ 正确用法:**
|
|
738
|
+
|
|
739
|
+
```vue
|
|
740
|
+
<template>
|
|
741
|
+
<div class="vfp-bg-surface-1 vfp-border vfp-border-line-weak vfp-rounded">
|
|
742
|
+
<h2 class="vfp-text-fg-primary vfp-text-lg">标题</h2>
|
|
743
|
+
<p class="vfp-text-fg-secondary">正文内容</p>
|
|
744
|
+
<button class="vfp-bg-surface-2 hover:vfp-bg-surface-3 vfp-text-fg-primary">
|
|
745
|
+
点击
|
|
746
|
+
</button>
|
|
747
|
+
<pre class="vfp-bg-code-bg vfp-text-code-fg">{{ code }}</pre>
|
|
748
|
+
</div>
|
|
749
|
+
</template>
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
**对于 `<style scoped>` 块**,使用 CSS 变量:
|
|
753
|
+
|
|
754
|
+
```vue
|
|
755
|
+
<style scoped>
|
|
756
|
+
.my-block {
|
|
757
|
+
color: var(--fp-fg-primary);
|
|
758
|
+
background: var(--fp-surface-2);
|
|
759
|
+
border: 1px solid var(--fp-line);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.my-code {
|
|
763
|
+
background: var(--fp-code-bg);
|
|
764
|
+
color: var(--fp-code-fg);
|
|
765
|
+
}
|
|
766
|
+
</style>
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**❌ 错误用法(禁止使用):**
|
|
770
|
+
|
|
771
|
+
```vue
|
|
772
|
+
<!-- ❌ 字面色类 — 会破坏主题切换 -->
|
|
773
|
+
<div class="vfp-text-white/90 vfp-bg-white/10 vfp-border-white/15">
|
|
774
|
+
<div class="vfp-text-gray-700 vfp-bg-gray-100">
|
|
775
|
+
|
|
776
|
+
<!-- ❌ 内联字面色 -->
|
|
777
|
+
<div :style="{ color: '#ffffff', background: '#1f2937' }">
|
|
778
|
+
|
|
779
|
+
<!-- ❌ scoped style 中硬编码暗色 -->
|
|
780
|
+
<style scoped>
|
|
781
|
+
.foo { color: rgba(255, 255, 255, 0.75); } /* 应该用 var(--fp-fg-secondary) */
|
|
782
|
+
.foo { background: #1e1e1e; } /* 应该用 var(--fp-code-bg) */
|
|
783
|
+
</style>
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**支持主题的三方库:**
|
|
787
|
+
|
|
788
|
+
对于具有 theme prop 的库(如 `shiki`),使用 `useResolvedTheme()`:
|
|
789
|
+
|
|
790
|
+
```vue
|
|
791
|
+
<script setup lang="ts">
|
|
792
|
+
import { ref, watch } from 'vue';
|
|
793
|
+
import { codeToHtml } from 'shiki';
|
|
794
|
+
import { useResolvedTheme } from '@eternalheart/angular-file-preview';
|
|
795
|
+
|
|
796
|
+
const props = defineProps<{ code: string; lang: string }>();
|
|
797
|
+
const resolvedTheme = useResolvedTheme(); // Ref<'dark' | 'light'>
|
|
798
|
+
const highlighted = ref('');
|
|
799
|
+
|
|
800
|
+
const highlightCode = async () => {
|
|
801
|
+
highlighted.value = await codeToHtml(props.code, {
|
|
802
|
+
lang: props.lang,
|
|
803
|
+
theme: resolvedTheme.value === 'light' ? 'github-light' : 'dark-plus'
|
|
804
|
+
});
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
// 主题切换时重新高亮
|
|
808
|
+
watch(resolvedTheme, highlightCode, { immediate: true });
|
|
809
|
+
</script>
|
|
810
|
+
|
|
811
|
+
<template>
|
|
812
|
+
<div v-html="highlighted"></div>
|
|
813
|
+
</template>
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
**测试:**
|
|
817
|
+
|
|
818
|
+
务必在 Light 和 Dark 两个主题下测试你的自定义渲染器:
|
|
819
|
+
|
|
820
|
+
```vue
|
|
821
|
+
<FilePreviewModal
|
|
822
|
+
:files="files"
|
|
823
|
+
theme="light" // 在 'light'、'dark'、'auto' 间切换
|
|
824
|
+
:custom-renderers="[...]"
|
|
825
|
+
/>
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
验证:
|
|
829
|
+
- 文字在两个主题下都可读(无白底白字或黑底黑字)
|
|
830
|
+
- 边框和分隔线清晰可见
|
|
831
|
+
- hover 状态有足够对比度
|
|
832
|
+
- 代码块跟随主题(不固定为暗色)
|
|
833
|
+
|
|
834
|
+
## ⌨️ 键盘快捷键
|
|
835
|
+
|
|
836
|
+
- `ESC` - 关闭预览
|
|
837
|
+
- `←` - 上一个文件
|
|
838
|
+
- `→` - 下一个文件
|
|
839
|
+
- `滚轮` - 缩放图片 (仅图片预览)
|
|
840
|
+
|
|
841
|
+
## 📚 文档
|
|
842
|
+
|
|
843
|
+
- [完整文档](https://wh131462.github.io/file-preview/docs/)
|
|
844
|
+
- [Vue 在线演示](https://wh131462.github.io/file-preview/vue/)
|
|
845
|
+
- [React 在线演示](https://wh131462.github.io/file-preview/)
|
|
846
|
+
|
|
847
|
+
## 🛠️ 开发
|
|
848
|
+
|
|
849
|
+
```bash
|
|
850
|
+
# 克隆仓库
|
|
851
|
+
git clone https://github.com/wh131462/file-preview.git
|
|
852
|
+
|
|
853
|
+
# 安装依赖
|
|
854
|
+
pnpm install
|
|
855
|
+
|
|
856
|
+
# 启动开发服务器(Vue 演示应用)
|
|
857
|
+
pnpm dev:vue-example
|
|
858
|
+
|
|
859
|
+
# 构建库
|
|
860
|
+
pnpm build:vue
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
## 📄 许可证
|
|
864
|
+
|
|
865
|
+
[MIT](./LICENSE) © [EternalHeart](https://github.com/wh131462)
|
|
866
|
+
|
|
867
|
+
## 🤝 贡献
|
|
868
|
+
|
|
869
|
+
欢迎提交 Issue 和 Pull Request!
|
|
870
|
+
|
|
871
|
+
## 🔗 相关链接
|
|
872
|
+
|
|
873
|
+
- [GitHub](https://github.com/wh131462/file-preview)
|
|
874
|
+
- [npm](https://www.npmjs.com/package/@eternalheart/angular-file-preview)
|
|
875
|
+
- [Vue 在线演示](https://wh131462.github.io/file-preview/vue/)
|
|
876
|
+
- [问题反馈](https://github.com/wh131462/file-preview/issues)
|