jit-viewer 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -1,59 +1,531 @@
1
- # @jit-viewer/core
1
+ # jit-viewer
2
2
 
3
- 跨框架文档预览 SDK,支持 PDFDOCXXLSXPPTXOFDTXTMarkdown 等多种格式。
3
+ Document preview SDK for Vue, React, and vanilla JavaScript. Supports PDF, DOCX, XLSX/XLS, PPTX/PPT, OFD, TXT, Markdown, HTML, images, and built-in watermark rendering.
4
+
5
+ Chinese docs:
6
+ - [README.zh-CN.md](./README.zh-CN.md)
7
+ - [View on GitHub](https://github.com/jitOffice/jit-viewer-sdk/blob/main/packages/core/README.zh-CN.md)
8
+
9
+ ## Features
10
+
11
+ - Multi-format preview: PDF, Office, OFD, Markdown, HTML, images
12
+ - Cross-framework usage: Vue 3, React, vanilla HTML/JS
13
+ - Built-in toolbar: zoom, rotate, print, download, pagination
14
+ - Theme and locale support: light / dark, zh-CN / en
15
+ - Watermark support: text watermark, image watermark, opacity, rotation, tiling gap, top/bottom layer
16
+ - Supports local files, remote URLs, Blob, ArrayBuffer, proxy URL, and custom request adapters
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # npm
22
+ npm install jit-viewer
23
+
24
+ # yarn
25
+ yarn add jit-viewer
26
+
27
+ # pnpm
28
+ pnpm add jit-viewer
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ### Vue 3
34
+
35
+ ```vue
36
+ <template>
37
+ <div ref="containerRef" class="viewer-container"></div>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { onMounted, onUnmounted, ref } from 'vue'
42
+ import { createViewer } from 'jit-viewer'
43
+ import 'jit-viewer/style.css'
44
+
45
+ const containerRef = ref<HTMLElement | null>(null)
46
+ let viewer: ReturnType<typeof createViewer> | null = null
47
+
48
+ onMounted(() => {
49
+ viewer = createViewer({
50
+ target: containerRef.value!,
51
+ file: '/demo/test.pdf',
52
+ toolbar: true,
53
+ theme: 'light',
54
+ locale: 'en',
55
+ width: '100%',
56
+ height: 640
57
+ })
58
+
59
+ viewer.mount()
60
+ })
61
+
62
+ onUnmounted(() => {
63
+ viewer?.destroy()
64
+ })
65
+ </script>
66
+
67
+ <style scoped>
68
+ .viewer-container {
69
+ width: 100%;
70
+ height: 640px;
71
+ }
72
+ </style>
73
+ ```
74
+
75
+ ### React
76
+
77
+ ```tsx
78
+ import { useEffect, useRef } from 'react'
79
+ import { createViewer, type ViewerInstance } from 'jit-viewer'
80
+ import 'jit-viewer/style.css'
81
+
82
+ export default function App() {
83
+ const containerRef = useRef<HTMLDivElement | null>(null)
84
+ const viewerRef = useRef<ViewerInstance | null>(null)
85
+
86
+ useEffect(() => {
87
+ if (!containerRef.current) return
88
+
89
+ viewerRef.current = createViewer({
90
+ target: containerRef.current,
91
+ file: '/demo/test.pdf',
92
+ toolbar: true,
93
+ height: 640
94
+ })
95
+
96
+ viewerRef.current.mount()
97
+
98
+ return () => viewerRef.current?.destroy()
99
+ }, [])
100
+
101
+ return <div ref={containerRef} style={{ width: '100%', height: 640 }} />
102
+ }
103
+ ```
104
+
105
+ ### Vanilla HTML / JS
106
+
107
+ ```html
108
+ <!DOCTYPE html>
109
+ <html lang="en">
110
+ <head>
111
+ <meta charset="UTF-8" />
112
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
113
+ <title>JitViewer Demo</title>
114
+ <link rel="stylesheet" href="https://unpkg.com/jit-viewer/dist/iife/jit-viewer.min.css" />
115
+ </head>
116
+ <body>
117
+ <div id="viewer" style="width:100%;height:640px;"></div>
118
+
119
+ <script src="https://unpkg.com/jit-viewer/dist/iife/jit-viewer.min.js"></script>
120
+ <script>
121
+ const viewer = JitViewer.createViewer({
122
+ target: '#viewer',
123
+ file: '/demo/test.pdf',
124
+ toolbar: true,
125
+ theme: 'light',
126
+ locale: 'en',
127
+ width: '100%',
128
+ height: 640
129
+ })
130
+
131
+ viewer.mount()
132
+ </script>
133
+ </body>
134
+ </html>
135
+ ```
136
+
137
+ ## Watermark
138
+
139
+ Watermarks are configured via `ViewerOptions.watermark`. They affect only the preview layer and do not modify the source file.
140
+
141
+ ### Text Watermark
142
+
143
+ ```ts
144
+ import { createViewer } from 'jit-viewer'
145
+ import 'jit-viewer/style.css'
146
+
147
+ const viewer = createViewer({
148
+ target: '#viewer',
149
+ file: '/demo/confidential.pdf',
150
+ toolbar: true,
151
+ watermark: {
152
+ type: 'text',
153
+ content: 'CONFIDENTIAL',
154
+ fontSize: 20,
155
+ color: '#bfbfbf',
156
+ opacity: 0.22,
157
+ rotate: -25,
158
+ gapX: 140,
159
+ gapY: 90,
160
+ position: 'top'
161
+ }
162
+ })
163
+
164
+ viewer.mount()
165
+ ```
166
+
167
+ ### Image Watermark
168
+
169
+ ```ts
170
+ import { createViewer } from 'jit-viewer'
171
+ import 'jit-viewer/style.css'
172
+
173
+ const viewer = createViewer({
174
+ target: '#viewer',
175
+ file: '/demo/report.pdf',
176
+ watermark: {
177
+ type: 'image',
178
+ image: '/assets/company-logo.png',
179
+ imageWidth: 96,
180
+ imageHeight: 96,
181
+ opacity: 0.16,
182
+ rotate: -20,
183
+ gapX: 180,
184
+ gapY: 120,
185
+ position: 'bottom'
186
+ }
187
+ })
188
+
189
+ viewer.mount()
190
+ ```
191
+
192
+ ### Vue Component Example
193
+
194
+ ```vue
195
+ <template>
196
+ <Viewer
197
+ :file="file"
198
+ :toolbar="true"
199
+ :theme="'light'"
200
+ :locale="'en'"
201
+ :watermark="watermark"
202
+ width="100%"
203
+ height="640px"
204
+ />
205
+ </template>
206
+
207
+ <script setup lang="ts">
208
+ import { ref } from 'vue'
209
+ import { Viewer } from 'jit-viewer'
210
+ import 'jit-viewer/style.css'
211
+
212
+ const file = ref('/demo/test.pdf')
213
+
214
+ const watermark = ref({
215
+ type: 'text' as const,
216
+ content: 'JitViewer Demo',
217
+ fontSize: 18,
218
+ color: '#9aa5b1',
219
+ opacity: 0.18,
220
+ rotate: -30,
221
+ gapX: 120,
222
+ gapY: 80,
223
+ position: 'top' as const
224
+ })
225
+ </script>
226
+ ```
227
+
228
+ ### Re-render With Different Watermarks
229
+
230
+ ```ts
231
+ import { createViewer, type ViewerInstance, type ViewerOptions } from 'jit-viewer'
232
+
233
+ let viewer: ViewerInstance | null = null
234
+
235
+ const baseOptions: ViewerOptions = {
236
+ target: '#viewer',
237
+ file: '/demo/test.pdf',
238
+ toolbar: true,
239
+ width: '100%',
240
+ height: 640
241
+ }
242
+
243
+ function renderWithWatermark(watermark: ViewerOptions['watermark']) {
244
+ viewer?.destroy()
245
+
246
+ viewer = createViewer({
247
+ ...baseOptions,
248
+ watermark
249
+ })
250
+
251
+ viewer.mount()
252
+ }
253
+
254
+ renderWithWatermark({
255
+ type: 'text',
256
+ content: 'First Review',
257
+ opacity: 0.2,
258
+ rotate: -20,
259
+ gapX: 140,
260
+ gapY: 90
261
+ })
262
+
263
+ document.querySelector('#switchImage')?.addEventListener('click', () => {
264
+ renderWithWatermark({
265
+ type: 'image',
266
+ image: '/assets/logo.png',
267
+ imageWidth: 88,
268
+ imageHeight: 88,
269
+ opacity: 0.14,
270
+ rotate: -18,
271
+ gapX: 170,
272
+ gapY: 110
273
+ })
274
+ })
275
+
276
+ document.querySelector('#clearWatermark')?.addEventListener('click', () => {
277
+ renderWithWatermark(null)
278
+ })
279
+ ```
280
+
281
+ ## Detailed Examples
282
+
283
+ ### Example 1: Contract Preview With Top-Level Confidential Watermark
284
+
285
+ ```ts
286
+ createViewer({
287
+ target: '#viewer',
288
+ file: '/contracts/nda.pdf',
289
+ filename: 'nda.pdf',
290
+ toolbar: true,
291
+ pdfRender: 'inset',
292
+ watermark: {
293
+ type: 'text',
294
+ content: 'CONFIDENTIAL',
295
+ fontSize: 24,
296
+ color: '#d4380d',
297
+ opacity: 0.18,
298
+ rotate: -32,
299
+ gapX: 130,
300
+ gapY: 85,
301
+ position: 'top'
302
+ }
303
+ }).mount()
304
+ ```
305
+
306
+ ### Example 2: Handbook Preview With Brand Logo Watermark
307
+
308
+ ```ts
309
+ createViewer({
310
+ target: '#viewer',
311
+ file: '/manuals/employee-handbook.docx',
312
+ toolbar: true,
313
+ watermark: {
314
+ type: 'image',
315
+ image: '/assets/brand-watermark.svg',
316
+ imageWidth: 72,
317
+ imageHeight: 72,
318
+ opacity: 0.1,
319
+ rotate: -15,
320
+ gapX: 210,
321
+ gapY: 140,
322
+ position: 'bottom'
323
+ }
324
+ }).mount()
325
+ ```
326
+
327
+ ### Example 3: Remote File With Auth Headers and Watermark
328
+
329
+ ```ts
330
+ createViewer({
331
+ target: '#viewer',
332
+ file: {
333
+ url: 'https://api.example.com/files/statement.pdf',
334
+ headers: {
335
+ Authorization: 'Bearer your-token'
336
+ }
337
+ },
338
+ requestAdapter: async (url, options) => {
339
+ const response = await fetch(url, {
340
+ method: options?.method || 'GET',
341
+ headers: options?.headers
342
+ })
343
+
344
+ if (!response.ok) {
345
+ throw new Error(`Request failed: ${response.status}`)
346
+ }
347
+
348
+ return response.arrayBuffer()
349
+ },
350
+ watermark: {
351
+ type: 'text',
352
+ content: 'Review Copy',
353
+ fontSize: 16,
354
+ color: '#7d8b99',
355
+ opacity: 0.16,
356
+ rotate: -28,
357
+ gapX: 150,
358
+ gapY: 100
359
+ }
360
+ }).mount()
361
+ ```
362
+
363
+ ## API
364
+
365
+ ### `createViewer(options)`
366
+
367
+ Creates a viewer instance and returns `ViewerInstance`.
368
+
369
+ ### `ViewerOptions`
370
+
371
+ | Option | Type | Default | Description |
372
+ |------|------|--------|------|
373
+ | `target` | `HTMLElement \| string` | - | Mount target |
374
+ | `file` | `FileSource` | - | File source: URL, File, Blob, ArrayBuffer, or request object |
375
+ | `type` | `FileType` | auto | Explicit file type |
376
+ | `filename` | `string` | - | Display and download filename |
377
+ | `toolbar` | `boolean \| ToolbarConfig` | `true` | Toolbar config |
378
+ | `theme` | `'light' \| 'dark' \| ThemeConfig` | `'light'` | Theme |
379
+ | `locale` | `'zh-CN' \| 'en' \| LocaleConfig` | `'zh-CN'` | Locale |
380
+ | `width` | `string \| number` | `'100%'` | Viewer width |
381
+ | `height` | `string \| number` | `'100%'` | Viewer height |
382
+ | `className` | `string` | - | Custom class name |
383
+ | `style` | `Record<string, string>` | - | Custom inline style |
384
+ | `pdfRender` | `'native' \| 'inset'` | `'inset'` | PDF rendering mode |
385
+ | `watermark` | `WatermarkConfig \| null` | `null` | Watermark config |
386
+ | `proxyUrl` | `string` | - | Proxy URL |
387
+ | `requestAdapter` | `RequestAdapter` | - | Custom fetch adapter |
388
+ | `renderOptions` | `object` | - | Initial render options such as zoom, rotate, page |
389
+ | `onReady` | `() => void` | - | Ready callback |
390
+ | `onLoad` | `() => void` | - | Load callback |
391
+ | `onError` | `(error: Error) => void` | - | Error callback |
392
+ | `onDestroy` | `() => void` | - | Destroy callback |
393
+
394
+ ### `WatermarkConfig`
395
+
396
+ | Option | Type | Description |
397
+ |------|------|------|
398
+ | `type` | `'text' \| 'image'` | Watermark type |
399
+ | `content` | `string` | Text content for `type: 'text'` |
400
+ | `image` | `string` | Image URL for `type: 'image'` |
401
+ | `fontSize` | `number` | Text font size in px |
402
+ | `color` | `string` | Text color |
403
+ | `fontFamily` | `string` | Font family |
404
+ | `fontWeight` | `string \| number` | Font weight |
405
+ | `imageWidth` | `number` | Image width in px |
406
+ | `imageHeight` | `number` | Image height in px |
407
+ | `opacity` | `number` | Opacity, usually between `0` and `1` |
408
+ | `rotate` | `number` | Rotation angle in deg |
409
+ | `gapX` | `number` | Horizontal gap |
410
+ | `gapY` | `number` | Vertical gap |
411
+ | `position` | `'top' \| 'bottom'` | Overlay above or below document content |
412
+ | `width` | `string \| number` | Watermark container width |
413
+ | `height` | `string \| number` | Watermark container height |
414
+
415
+ ### `ViewerInstance`
416
+
417
+ | Method | Description |
418
+ |------|------|
419
+ | `mount(target?)` | Mount viewer |
420
+ | `destroy()` | Destroy viewer |
421
+ | `setFile(file, filename?)` | Replace current file |
422
+ | `getFile()` | Get current file info |
423
+ | `zoom(scale)` | Set zoom |
424
+ | `rotate(degree)` | Set rotation |
425
+ | `reset()` | Reset zoom and rotation |
426
+ | `fullscreen(enable?)` | Toggle fullscreen |
427
+ | `prevPage()` | Previous page |
428
+ | `nextPage()` | Next page |
429
+ | `gotoPage(page)` | Go to page |
430
+ | `getPageInfo()` | Get page info |
431
+ | `print()` | Print current document |
432
+ | `download()` | Download current document |
433
+ | `setTheme(theme)` | Set theme |
434
+ | `setLocale(locale)` | Set locale |
435
+ | `setToolbar(config)` | Set toolbar |
436
+ | `on(event, handler)` | Register event |
437
+ | `off(event, handler)` | Remove event |
438
+ | `getState()` | Get current state |
439
+
440
+ ## Supported File Types
441
+
442
+ | Format | Extensions | Description |
443
+ |------|--------|------|
444
+ | PDF | `.pdf` | PDF files |
445
+ | Word | `.docx` | Microsoft Word |
446
+ | Excel | `.xlsx`, `.xls` | Microsoft Excel |
447
+ | PowerPoint | `.pptx`, `.ppt` | Microsoft PowerPoint |
448
+ | OFD | `.ofd` | Open Fixed-layout Document |
449
+ | Text | `.txt` | Plain text |
450
+ | Markdown | `.md`, `.markdown` | Markdown files |
451
+ | HTML | `.html`, `.htm` | HTML files or web content |
452
+ | Images | `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.svg`, `.bmp`, `.tiff`, `.tif`, `.ico` | Image preview |
453
+
454
+ ## Browser Support
455
+
456
+ - Chrome >= 80
457
+ - Firefox >= 75
458
+ - Safari >= 13
459
+ - Edge >= 80
460
+
461
+ ## License
462
+
463
+ [Apache-2.0](./LICENSE)
464
+
465
+ ## Links
466
+
467
+ - [GitHub](https://github.com/jitOffice/jit-viewer-sdk)
468
+ - [Homepage](https://jitword.com/jit-viewer.html)
469
+ - [Issues](https://github.com/jitOffice/jit-viewer-sdk/issues)
470
+
471
+ ---
472
+
473
+ ## 中文文档
474
+
475
+ 跨框架文档预览 SDK,支持 PDF、DOCX、XLSX/XLS、PPTX/PPT、OFD、TXT、Markdown、HTML、图片等多种格式,并内置文档水印能力。
476
+
477
+ [Back to English](#jit-viewer)
4
478
 
5
479
  ## 特性
6
480
 
7
- - 📄 **多格式支持** - PDF、DOCXXLSX/XLS、PPTX、OFD、TXTMarkdown
8
- - 🔧 **跨框架** - 支持 Vue3、React、原生 HTML/JS
9
- - 🎨 **主题切换** - 内置浅色/深色主题
10
- - 🌐 **国际化** - 支持中文、英文
11
- - 📦 **开箱即用** - 内置工具栏,支持缩放、旋转、打印、下载
12
- - 📱 **响应式** - 适配各种屏幕尺寸
481
+ - 多格式预览:PDF、Office、OFD、MarkdownHTML、图片
482
+ - 跨框架使用:Vue 3、React、原生 HTML/JS
483
+ - 内置工具栏:缩放、旋转、打印、下载、分页
484
+ - 主题与国际化:浅色 / 深色,中文 / 英文
485
+ - 水印能力:文本水印、图片水印、透明度、旋转、平铺间距、顶层/底层显示
486
+ - 支持本地文件、远程 URL、Blob、ArrayBuffer、自定义请求适配器
13
487
 
14
488
  ## 安装
15
489
 
16
490
  ```bash
17
491
  # npm
18
- npm install @jit-viewer/core
492
+ npm install jit-viewer
19
493
 
20
494
  # yarn
21
- yarn add @jit-viewer/core
495
+ yarn add jit-viewer
22
496
 
23
497
  # pnpm
24
- pnpm add @jit-viewer/core
498
+ pnpm add jit-viewer
25
499
  ```
26
500
 
27
501
  ## 快速开始
28
502
 
29
- ### Vue3 项目
503
+ ### Vue 3
30
504
 
31
505
  ```vue
32
506
  <template>
33
507
  <div ref="containerRef" class="viewer-container"></div>
34
508
  </template>
35
509
 
36
- <script setup>
37
- import { ref, onMounted, onUnmounted } from 'vue'
38
- import { createViewer } from '@jit-viewer/core'
39
- import '@jit-viewer/core/style.css'
510
+ <script setup lang="ts">
511
+ import { onMounted, onUnmounted, ref } from 'vue'
512
+ import { createViewer } from 'jit-viewer'
513
+ import 'jit-viewer/style.css'
40
514
 
41
- const containerRef = ref(null)
42
- let viewer = null
515
+ const containerRef = ref<HTMLElement | null>(null)
516
+ let viewer: ReturnType<typeof createViewer> | null = null
43
517
 
44
518
  onMounted(() => {
45
519
  viewer = createViewer({
46
- target: containerRef.value,
47
- file: '/path/to/document.pdf',
520
+ target: containerRef.value!,
521
+ file: '/demo/test.pdf',
48
522
  toolbar: true,
49
523
  theme: 'light',
50
524
  locale: 'zh-CN',
51
525
  width: '100%',
52
- height: '600px',
53
- onReady: () => console.log('Viewer ready'),
54
- onLoad: () => console.log('File loaded'),
55
- onError: (err) => console.error('Error:', err)
526
+ height: 640
56
527
  })
528
+
57
529
  viewer.mount()
58
530
  })
59
531
 
@@ -63,95 +535,369 @@ onUnmounted(() => {
63
535
  </script>
64
536
  ```
65
537
 
66
- ### React 项目
538
+ ### React
67
539
 
68
540
  ```tsx
69
- import { useRef, useEffect } from 'react'
70
- import { createViewer } from '@jit-viewer/core'
71
- import '@jit-viewer/core/style.css'
541
+ import { useEffect, useRef } from 'react'
542
+ import { createViewer, type ViewerInstance } from 'jit-viewer'
543
+ import 'jit-viewer/style.css'
72
544
 
73
- function App() {
74
- const containerRef = useRef<HTMLDivElement>(null)
75
- const viewerRef = useRef(null)
545
+ export default function App() {
546
+ const containerRef = useRef<HTMLDivElement | null>(null)
547
+ const viewerRef = useRef<ViewerInstance | null>(null)
76
548
 
77
549
  useEffect(() => {
550
+ if (!containerRef.current) return
551
+
78
552
  viewerRef.current = createViewer({
79
553
  target: containerRef.current,
80
- file: '/path/to/document.pdf',
81
- toolbar: true
554
+ file: '/demo/test.pdf',
555
+ toolbar: true,
556
+ height: 640
82
557
  })
558
+
83
559
  viewerRef.current.mount()
84
560
 
85
561
  return () => viewerRef.current?.destroy()
86
562
  }, [])
87
563
 
88
- return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />
564
+ return <div ref={containerRef} style={{ width: '100%', height: 640 }} />
89
565
  }
90
566
  ```
91
567
 
92
- ### 原生 HTML/JS
568
+ ### 原生 HTML / JS
93
569
 
94
570
  ```html
95
571
  <!DOCTYPE html>
96
- <html>
572
+ <html lang="zh-CN">
97
573
  <head>
98
- <link rel="stylesheet" href="https://unpkg.com/@jit-viewer/core/dist/iife/jit-viewer.min.css">
574
+ <meta charset="UTF-8" />
575
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
576
+ <title>JitViewer Demo</title>
577
+ <link rel="stylesheet" href="https://unpkg.com/jit-viewer/dist/iife/jit-viewer.min.css" />
99
578
  </head>
100
579
  <body>
101
- <div id="viewer"></div>
580
+ <div id="viewer" style="width:100%;height:640px;"></div>
102
581
 
103
- <script src="https://unpkg.com/@jit-viewer/core/dist/iife/jit-viewer.min.js"></script>
582
+ <script src="https://unpkg.com/jit-viewer/dist/iife/jit-viewer.min.js"></script>
104
583
  <script>
105
584
  const viewer = JitViewer.createViewer({
106
585
  target: '#viewer',
107
- file: '/path/to/document.pdf',
586
+ file: '/demo/test.pdf',
108
587
  toolbar: true,
588
+ theme: 'light',
589
+ locale: 'zh-CN',
109
590
  width: '100%',
110
- height: '600px'
591
+ height: 640
111
592
  })
593
+
112
594
  viewer.mount()
113
595
  </script>
114
596
  </body>
115
597
  </html>
116
598
  ```
117
599
 
600
+ ## 水印使用
601
+
602
+ 水印通过 `ViewerOptions.watermark` 配置,支持文本水印和图片水印。水印只作用于预览层,不会修改原始文件内容。
603
+
604
+ ### 文本水印
605
+
606
+ ```ts
607
+ import { createViewer } from 'jit-viewer'
608
+ import 'jit-viewer/style.css'
609
+
610
+ const viewer = createViewer({
611
+ target: '#viewer',
612
+ file: '/demo/confidential.pdf',
613
+ toolbar: true,
614
+ watermark: {
615
+ type: 'text',
616
+ content: '内部资料 严禁外传',
617
+ fontSize: 20,
618
+ color: '#bfbfbf',
619
+ opacity: 0.22,
620
+ rotate: -25,
621
+ gapX: 140,
622
+ gapY: 90,
623
+ position: 'top'
624
+ }
625
+ })
626
+
627
+ viewer.mount()
628
+ ```
629
+
630
+ ### 图片水印
631
+
632
+ ```ts
633
+ import { createViewer } from 'jit-viewer'
634
+ import 'jit-viewer/style.css'
635
+
636
+ const viewer = createViewer({
637
+ target: '#viewer',
638
+ file: '/demo/report.pdf',
639
+ watermark: {
640
+ type: 'image',
641
+ image: '/assets/company-logo.png',
642
+ imageWidth: 96,
643
+ imageHeight: 96,
644
+ opacity: 0.16,
645
+ rotate: -20,
646
+ gapX: 180,
647
+ gapY: 120,
648
+ position: 'bottom'
649
+ }
650
+ })
651
+
652
+ viewer.mount()
653
+ ```
654
+
655
+ ### Vue 组件方式
656
+
657
+ ```vue
658
+ <template>
659
+ <Viewer
660
+ :file="file"
661
+ :toolbar="true"
662
+ :theme="'light'"
663
+ :locale="'zh-CN'"
664
+ :watermark="watermark"
665
+ width="100%"
666
+ height="640px"
667
+ />
668
+ </template>
669
+
670
+ <script setup lang="ts">
671
+ import { ref } from 'vue'
672
+ import { Viewer } from 'jit-viewer'
673
+ import 'jit-viewer/style.css'
674
+
675
+ const file = ref('/demo/test.pdf')
676
+
677
+ const watermark = ref({
678
+ type: 'text' as const,
679
+ content: 'JitViewer Demo',
680
+ fontSize: 18,
681
+ color: '#9aa5b1',
682
+ opacity: 0.18,
683
+ rotate: -30,
684
+ gapX: 120,
685
+ gapY: 80,
686
+ position: 'top' as const
687
+ })
688
+ </script>
689
+ ```
690
+
691
+ ### 动态切换不同水印
692
+
693
+ ```ts
694
+ import { createViewer, type ViewerInstance, type ViewerOptions } from 'jit-viewer'
695
+
696
+ let viewer: ViewerInstance | null = null
697
+
698
+ const baseOptions: ViewerOptions = {
699
+ target: '#viewer',
700
+ file: '/demo/test.pdf',
701
+ toolbar: true,
702
+ width: '100%',
703
+ height: 640
704
+ }
705
+
706
+ function renderWithWatermark(watermark: ViewerOptions['watermark']) {
707
+ viewer?.destroy()
708
+
709
+ viewer = createViewer({
710
+ ...baseOptions,
711
+ watermark
712
+ })
713
+
714
+ viewer.mount()
715
+ }
716
+
717
+ renderWithWatermark({
718
+ type: 'text',
719
+ content: '首次预览',
720
+ opacity: 0.2,
721
+ rotate: -20,
722
+ gapX: 140,
723
+ gapY: 90
724
+ })
725
+
726
+ document.querySelector('#switchImage')?.addEventListener('click', () => {
727
+ renderWithWatermark({
728
+ type: 'image',
729
+ image: '/assets/logo.png',
730
+ imageWidth: 88,
731
+ imageHeight: 88,
732
+ opacity: 0.14,
733
+ rotate: -18,
734
+ gapX: 170,
735
+ gapY: 110
736
+ })
737
+ })
738
+
739
+ document.querySelector('#clearWatermark')?.addEventListener('click', () => {
740
+ renderWithWatermark(null)
741
+ })
742
+ ```
743
+
744
+ ## 详细示例
745
+
746
+ ### 示例 1:合同预览 + 顶层保密文本水印
747
+
748
+ ```ts
749
+ createViewer({
750
+ target: '#viewer',
751
+ file: '/contracts/nda.pdf',
752
+ filename: 'nda.pdf',
753
+ toolbar: true,
754
+ pdfRender: 'inset',
755
+ watermark: {
756
+ type: 'text',
757
+ content: 'CONFIDENTIAL',
758
+ fontSize: 24,
759
+ color: '#d4380d',
760
+ opacity: 0.18,
761
+ rotate: -32,
762
+ gapX: 130,
763
+ gapY: 85,
764
+ position: 'top'
765
+ }
766
+ }).mount()
767
+ ```
768
+
769
+ ### 示例 2:员工手册预览 + 底层品牌 Logo 水印
770
+
771
+ ```ts
772
+ createViewer({
773
+ target: '#viewer',
774
+ file: '/manuals/employee-handbook.docx',
775
+ toolbar: true,
776
+ watermark: {
777
+ type: 'image',
778
+ image: '/assets/brand-watermark.svg',
779
+ imageWidth: 72,
780
+ imageHeight: 72,
781
+ opacity: 0.1,
782
+ rotate: -15,
783
+ gapX: 210,
784
+ gapY: 140,
785
+ position: 'bottom'
786
+ }
787
+ }).mount()
788
+ ```
789
+
790
+ ### 示例 3:自定义请求头 + 水印
791
+
792
+ ```ts
793
+ createViewer({
794
+ target: '#viewer',
795
+ file: {
796
+ url: 'https://api.example.com/files/statement.pdf',
797
+ headers: {
798
+ Authorization: 'Bearer your-token'
799
+ }
800
+ },
801
+ requestAdapter: async (url, options) => {
802
+ const response = await fetch(url, {
803
+ method: options?.method || 'GET',
804
+ headers: options?.headers
805
+ })
806
+
807
+ if (!response.ok) {
808
+ throw new Error(`Request failed: ${response.status}`)
809
+ }
810
+
811
+ return response.arrayBuffer()
812
+ },
813
+ watermark: {
814
+ type: 'text',
815
+ content: '审阅版',
816
+ fontSize: 16,
817
+ color: '#7d8b99',
818
+ opacity: 0.16,
819
+ rotate: -28,
820
+ gapX: 150,
821
+ gapY: 100
822
+ }
823
+ }).mount()
824
+ ```
825
+
118
826
  ## API
119
827
 
120
- ### createViewer(options)
828
+ ### `createViewer(options)`
121
829
 
122
- 创建预览器实例。
830
+ 创建预览器实例,返回 `ViewerInstance`。
123
831
 
124
- #### Options
832
+ ### `ViewerOptions`
125
833
 
126
834
  | 参数 | 类型 | 默认值 | 说明 |
127
835
  |------|------|--------|------|
128
836
  | `target` | `HTMLElement \| string` | - | 挂载目标元素 |
129
- | `file` | `FileSource` | - | 文件源(URL、File、ArrayBuffer |
130
- | `filename` | `string` | - | 文件名 |
131
- | `toolbar` | `boolean \| ToolbarConfig` | `true` | 是否显示工具栏 |
132
- | `theme` | `'light' \| 'dark'` | `'light'` | 主题 |
133
- | `locale` | `'zh-CN' \| 'en'` | `'zh-CN'` | 语言 |
134
- | `width` | `string \| number` | `'100%'` | 宽度 |
135
- | `height` | `string \| number` | `'100%'` | 高度 |
136
- | `onReady` | `() => void` | - | 就绪回调 |
137
- | `onLoad` | `() => void` | - | 加载完成回调 |
837
+ | `file` | `FileSource` | - | 文件源,支持 URL、File、Blob、ArrayBuffer、带请求参数对象 |
838
+ | `type` | `FileType` | 自动识别 | 手动指定文件类型 |
839
+ | `filename` | `string` | - | 文件名,用于显示和下载 |
840
+ | `toolbar` | `boolean \| ToolbarConfig` | `true` | 工具栏配置 |
841
+ | `theme` | `'light' \| 'dark' \| ThemeConfig` | `'light'` | 主题配置 |
842
+ | `locale` | `'zh-CN' \| 'en' \| LocaleConfig` | `'zh-CN'` | 语言配置 |
843
+ | `width` | `string \| number` | `'100%'` | 预览器宽度 |
844
+ | `height` | `string \| number` | `'100%'` | 预览器高度 |
845
+ | `className` | `string` | - | 自定义类名 |
846
+ | `style` | `Record<string, string>` | - | 自定义样式 |
847
+ | `pdfRender` | `'native' \| 'inset'` | `'inset'` | PDF 预览方式 |
848
+ | `watermark` | `WatermarkConfig \| null` | `null` | 文档水印配置 |
849
+ | `proxyUrl` | `string` | - | 自定义代理地址 |
850
+ | `requestAdapter` | `RequestAdapter` | - | 自定义请求适配器 |
851
+ | `renderOptions` | `object` | - | 初始缩放、旋转、页码等渲染配置 |
852
+ | `onReady` | `() => void` | - | 预览器就绪回调 |
853
+ | `onLoad` | `() => void` | - | 文件加载完成回调 |
138
854
  | `onError` | `(error: Error) => void` | - | 错误回调 |
139
-
140
- #### ViewerInstance
855
+ | `onDestroy` | `() => void` | - | 销毁回调 |
856
+
857
+ ### `WatermarkConfig`
858
+
859
+ | 参数 | 类型 | 说明 |
860
+ |------|------|------|
861
+ | `type` | `'text' \| 'image'` | 水印类型 |
862
+ | `content` | `string` | 文本水印内容,`type: 'text'` 时使用 |
863
+ | `image` | `string` | 图片地址,`type: 'image'` 时使用 |
864
+ | `fontSize` | `number` | 文字字号,单位 `px` |
865
+ | `color` | `string` | 文字颜色 |
866
+ | `fontFamily` | `string` | 文字字体 |
867
+ | `fontWeight` | `string \| number` | 字重 |
868
+ | `imageWidth` | `number` | 图片水印宽度,单位 `px` |
869
+ | `imageHeight` | `number` | 图片水印高度,单位 `px` |
870
+ | `opacity` | `number` | 透明度,范围建议 `0 ~ 1` |
871
+ | `rotate` | `number` | 旋转角度,单位 `deg` |
872
+ | `gapX` | `number` | 水平平铺间距 |
873
+ | `gapY` | `number` | 垂直平铺间距 |
874
+ | `position` | `'top' \| 'bottom'` | 显示在文档上方或下方 |
875
+ | `width` | `string \| number` | 水印容器宽度 |
876
+ | `height` | `string \| number` | 水印容器高度 |
877
+
878
+ ### `ViewerInstance`
141
879
 
142
880
  | 方法 | 说明 |
143
881
  |------|------|
144
- | `mount()` | 挂载到 DOM |
882
+ | `mount(target?)` | 挂载到目标容器 |
145
883
  | `destroy()` | 销毁实例 |
146
- | `setFile(file, filename?)` | 设置文件 |
884
+ | `setFile(file, filename?)` | 动态切换文件 |
885
+ | `getFile()` | 获取当前文件信息 |
147
886
  | `zoom(scale)` | 设置缩放比例 |
148
887
  | `rotate(degree)` | 设置旋转角度 |
149
888
  | `reset()` | 重置缩放和旋转 |
150
889
  | `fullscreen(enable?)` | 切换全屏 |
151
- | `print()` | 打印文档 |
152
- | `download()` | 下载文档 |
890
+ | `prevPage()` | 上一页 |
891
+ | `nextPage()` | 下一页 |
892
+ | `gotoPage(page)` | 跳转到指定页 |
893
+ | `getPageInfo()` | 获取页码信息 |
894
+ | `print()` | 打印当前文档 |
895
+ | `download()` | 下载当前文档 |
153
896
  | `setTheme(theme)` | 设置主题 |
154
897
  | `setLocale(locale)` | 设置语言 |
898
+ | `setToolbar(config)` | 设置工具栏 |
899
+ | `on(event, handler)` | 监听事件 |
900
+ | `off(event, handler)` | 取消监听事件 |
155
901
  | `getState()` | 获取当前状态 |
156
902
 
157
903
  ## 支持的文件格式
@@ -165,10 +911,8 @@ function App() {
165
911
  | OFD | `.ofd` | 电子公文格式 |
166
912
  | 文本 | `.txt` | 纯文本文件 |
167
913
  | Markdown | `.md`, `.markdown` | Markdown 文档 |
168
-
169
- ## 依赖
170
-
171
- - Vue ^3.4.0 (peer dependency)
914
+ | HTML | `.html`, `.htm` | HTML 文件或网页内容 |
915
+ | 图片 | `.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.svg`, `.bmp`, `.tiff`, `.tif`, `.ico` | 图片预览 |
172
916
 
173
917
  ## 浏览器支持
174
918