@work-zhanguo/light-file-preview 0.0.13 → 0.0.15

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,7 +1,9 @@
1
1
  <h1 align="center">轻量预览 Light Preview</h1>
2
2
 
3
3
  <p align="center">
4
- 用于业务系统里的附件预览,支持远程 URL、本地 File、Blob,接入方式尽量保持简单。
4
+ Lightweight file preview component for Vue 3, Vue 2, and standalone usage.
5
+ <br />
6
+ 用于业务系统附件预览的轻量级文件预览组件,支持 Vue 3、Vue 2 与独立产物嵌入。
5
7
  </p>
6
8
 
7
9
  <p align="center">
@@ -10,17 +12,27 @@
10
12
  <a href="https://www.npmjs.com/package/@work-zhanguo/light-file-preview" target="_blank" rel="noreferrer">npm</a>
11
13
  </p>
12
14
 
15
+ <p align="center">
16
+ <strong>Language</strong>: expand the section you want to read.
17
+ <br />
18
+ <strong>语言切换</strong>:展开你想查看的语言版本即可。
19
+ </p>
20
+
21
+ <details open>
22
+ <summary><strong>简体中文</strong></summary>
23
+
13
24
  ## 项目简介
14
25
 
15
- - 当前版本:`0.0.13`
26
+ - 当前版本:`0.0.15`
16
27
  - 适用场景:业务系统附件预览、在线查看、弹窗预览、新页面预览
17
28
  - 支持 Vue 3、Vue 2 适配入口,以及非 Vue 项目的 standalone 产物
18
29
 
19
30
  最近更新:
20
31
 
32
+ - `0.0.15`:新增 npm `README.md` 的中英文双版本折叠切换展示,同步调整包描述与文档版本信息
33
+ - `0.0.14`:优化 `xlsx` 空白区域填充,移除未声明的表头/首列固定效果,并为 `PDF`、`DOCX` 增加默认分页与全部展示切换
21
34
  - `0.0.13`:修复弹窗失败后同文件无法重试、运行时配置更新不生效的问题,补充 README 示例图与发包说明
22
35
  - `0.0.12`:修复 `xlsx` 部分单元格背景色、文字颜色和边框颜色未正确展示的问题,补充 `theme / tint / indexed` 颜色解析
23
- - `0.0.11`:修复 `xlsx` 图片拉伸和底色覆盖问题,优化单元格内容的对齐、换行和数值展示
24
36
 
25
37
  详细版本记录见 [CHANGELOG.md](./CHANGELOG.md)。
26
38
 
@@ -271,3 +283,273 @@ npm run build:site
271
283
  站点部署目录:
272
284
 
273
285
  - `dist-site/`
286
+
287
+ </details>
288
+
289
+ <details>
290
+ <summary><strong>English</strong></summary>
291
+
292
+ ## Overview
293
+
294
+ - Current version: `0.0.15`
295
+ - Use cases: attachment preview in business systems, inline viewing, dialog preview, and standalone preview pages
296
+ - Supports Vue 3, a Vue 2 adapter entry, and standalone assets for non-Vue projects
297
+
298
+ Recent updates:
299
+
300
+ - `0.0.15`: rewrote `README.md` for npm with collapsible Chinese and English sections, and synchronized package description and documentation version info
301
+ - `0.0.14`: optimized blank area handling in `xlsx`, removed undeclared frozen header/column behavior, and added paged/all display switching for `PDF` and `DOCX`
302
+ - `0.0.13`: fixed retry behavior after preview failures in dialog mode, refreshed README examples, and clarified packaging details
303
+ - `0.0.12`: fixed missing background, text, and border colors for some `xlsx` cells, and added `theme / tint / indexed` color parsing
304
+
305
+ For the full release history, see [CHANGELOG.md](./CHANGELOG.md).
306
+
307
+ ## Preview Screenshots
308
+
309
+ Original screenshots are stored in `public/screenshots/`.
310
+
311
+ The images below use the npm CDN URL. Local repository changes do not affect the already published package immediately; after publishing a new version, npm will show the latest screenshots directly.
312
+
313
+ ### DOCX
314
+
315
+ ![DOCX preview](https://unpkg.com/@work-zhanguo/light-file-preview/public/screenshots/effect-docx.png)
316
+
317
+ ### XLSX
318
+
319
+ ![XLSX preview](https://unpkg.com/@work-zhanguo/light-file-preview/public/screenshots/effect-xlsx.png)
320
+
321
+ ### PDF
322
+
323
+ ![PDF preview](https://unpkg.com/@work-zhanguo/light-file-preview/public/screenshots/effect-pdf.png)
324
+
325
+ ### PPTX fallback
326
+
327
+ `PPT` / `PPTX` files are intentionally not parsed as online previews for now. The component keeps a download entry instead of pretending the format is fully supported.
328
+
329
+ ![PPTX fallback](https://unpkg.com/@work-zhanguo/light-file-preview/public/screenshots/effect-pptx.png)
330
+
331
+ ## Supported Files
332
+
333
+ Supported for online preview:
334
+
335
+ - `PNG`
336
+ - `JPG`
337
+ - `JPEG`
338
+ - `GIF`
339
+ - `WEBP`
340
+ - `BMP`
341
+ - `SVG`
342
+ - `PDF`
343
+ - `TXT`
344
+ - `JSON`
345
+ - `JS`
346
+ - `TS`
347
+ - `JSX`
348
+ - `TSX`
349
+ - `HTML`
350
+ - `CSS`
351
+ - `MD`
352
+ - `DOCX`
353
+ - `XLS`
354
+ - `XLSX`
355
+ - `CSV`
356
+ - `MP4`
357
+ - `WEBM`
358
+ - `MP3`
359
+ - `WAV`
360
+
361
+ Fallback to download entry:
362
+
363
+ - `DOC`
364
+ - `PPT`
365
+ - `PPTX`
366
+ - any unrecognized format
367
+
368
+ Notes:
369
+
370
+ - Supports remote `URL`, local `File`, and `Blob`
371
+ - Supports both inline embedding and dialog preview
372
+ - Unsupported formats are not force-rendered and always keep a download entry
373
+
374
+ ## Dependencies
375
+
376
+ - [pdf.js](https://github.com/mozilla/pdf.js)
377
+ - [docx-preview](https://github.com/VolodymyrBaydalka/docxjs)
378
+ - [SheetJS](https://github.com/SheetJS/sheetjs)
379
+ - [marked](https://github.com/markedjs/marked)
380
+ - [DOMPurify](https://github.com/cure53/DOMPurify)
381
+
382
+ ## Installation
383
+
384
+ ```bash
385
+ npm install @work-zhanguo/light-file-preview
386
+ ```
387
+
388
+ ## Component Props
389
+
390
+ | Prop | Type | Default | Description |
391
+ | --- | --- | --- | --- |
392
+ | `source` | `string \| File \| Blob` | - | Required. Supports remote URLs, local `File`, and `Blob`. |
393
+ | `fileName` | `string` | - | Optional file name. Recommended when a remote URL has no extension. |
394
+ | `mode` | `'inline' \| 'dialog'` | `'inline'` | Inline preview or dialog preview. |
395
+ | `visible` | `boolean` | `true` | Controls dialog visibility and works with `v-model:visible`. |
396
+ | `loadingText` | `string` | `'文件加载中...'` | Loading text displayed during parsing. |
397
+ | `textEncoding` | `string` | `'utf-8'` | Encoding used for text-like files. |
398
+ | `maxTextBytes` | `number` | `2097152` | Max size for text preview, default is 2 MB. |
399
+ | `maxSheetRows` | `number` | `200` | Maximum number of rows rendered in sheet preview. |
400
+ | `maxSheetCols` | `number` | `50` | Maximum number of columns rendered in sheet preview. |
401
+ | `pdfScale` | `number` | `1.35` | Render scale used for PDF pages. |
402
+ | `showToolbar` | `boolean` | `true` | Whether to show the top toolbar. |
403
+ | `dialogTitle` | `string` | `'文件预览'` | Fallback title used in dialog mode. |
404
+
405
+ Events:
406
+
407
+ - `update:visible`: emitted when the dialog closes
408
+ - `error`: emitted with the parsing error object
409
+
410
+ ## Vue 3 Integration
411
+
412
+ ```ts
413
+ import { createApp } from 'vue';
414
+ import App from './App.vue';
415
+ import LightFilePreview from '@work-zhanguo/light-file-preview';
416
+ import '@work-zhanguo/light-file-preview/style.css';
417
+
418
+ createApp(App).use(LightFilePreview).mount('#app');
419
+ ```
420
+
421
+ ```vue
422
+ <template>
423
+ <LightFilePreview source="/files/demo.pdf" />
424
+ </template>
425
+ ```
426
+
427
+ ## Vue 2 Integration
428
+
429
+ ```js
430
+ import Vue from 'vue';
431
+ import LightFilePreview from '@work-zhanguo/light-file-preview/vue2';
432
+ import '@work-zhanguo/light-file-preview/style.css';
433
+
434
+ Vue.use(LightFilePreview);
435
+ ```
436
+
437
+ ```vue
438
+ <template>
439
+ <light-file-preview :source="fileUrl" />
440
+ </template>
441
+ ```
442
+
443
+ ## Dialog Preview
444
+
445
+ ```vue
446
+ <template>
447
+ <button @click="show = true">Open dialog preview</button>
448
+
449
+ <LightFilePreview
450
+ v-model:visible="show"
451
+ source="https://501351981.github.io/vue-office/examples/dist/static/test-files/test.docx"
452
+ mode="dialog"
453
+ />
454
+ </template>
455
+
456
+ <script setup lang="ts">
457
+ import { ref } from 'vue';
458
+
459
+ const show = ref(false);
460
+ </script>
461
+ ```
462
+
463
+ ## Standalone Preview Page
464
+
465
+ It is recommended to pass `name` as well so extension detection stays stable.
466
+
467
+ ```js
468
+ const remoteUrl = 'https://example.com/files/test.docx';
469
+ const previewUrl =
470
+ '/?preview=1&src=' +
471
+ encodeURIComponent(remoteUrl) +
472
+ '&name=' +
473
+ encodeURIComponent('test.docx');
474
+
475
+ window.open(previewUrl, '_blank');
476
+ ```
477
+
478
+ ## Native Project Integration
479
+
480
+ Non-Vue projects can directly use the standalone bundle.
481
+
482
+ If you copy static assets from the npm package, the recommended files are:
483
+
484
+ - `dist/standalone/light-file-preview.iife.js`
485
+ - `dist/standalone/style.css`
486
+
487
+ ```html
488
+ <link rel="stylesheet" href="/vendor/light-file-preview/style.css" />
489
+ <div id="preview-root"></div>
490
+ <script src="/vendor/light-file-preview/light-file-preview.iife.js"></script>
491
+ <script>
492
+ window.LightFilePreview.mount('#preview-root', {
493
+ source: '/uploads/report.xlsx'
494
+ });
495
+ </script>
496
+ ```
497
+
498
+ ## Odoo Integration
499
+
500
+ For Odoo projects, the recommended approach is to mount the standalone bundle through static assets and an Owl component.
501
+
502
+ ```js
503
+ /** @odoo-module **/
504
+
505
+ import { Component, onMounted, useRef } from '@odoo/owl';
506
+
507
+ export class FilePreviewBlock extends Component {
508
+ setup() {
509
+ this.rootRef = useRef('root');
510
+
511
+ onMounted(() => {
512
+ window.LightFilePreview.mount(this.rootRef.el, {
513
+ source: this.props.source,
514
+ fileName: this.props.fileName || 'report.pdf'
515
+ });
516
+ });
517
+ }
518
+ }
519
+
520
+ FilePreviewBlock.template = 'your_module.FilePreviewBlock';
521
+ ```
522
+
523
+ ```xml
524
+ <templates xml:space="preserve">
525
+ <t t-name="your_module.FilePreviewBlock">
526
+ <div class="o_file_preview_root" t-ref="root" />
527
+ </t>
528
+ </templates>
529
+ ```
530
+
531
+ ## Deployment
532
+
533
+ Build the component library:
534
+
535
+ ```bash
536
+ npm install
537
+ npm run build
538
+ ```
539
+
540
+ Output directories:
541
+
542
+ - `dist/`
543
+ - `dist/standalone/`
544
+
545
+ If you want to deploy both the demo home page and the docs page as a static site:
546
+
547
+ ```bash
548
+ npm run build:site
549
+ ```
550
+
551
+ Static site output:
552
+
553
+ - `dist-site/`
554
+
555
+ </details>
@@ -0,0 +1,110 @@
1
+ import { defineComponent as P, ref as i, computed as L, watch as _, onMounted as N, onBeforeUnmount as T, openBlock as p, createElementBlock as d, createElementVNode as n, toDisplayString as v, normalizeClass as b, Fragment as B, createCommentVNode as m } from "vue";
2
+ import { r as D } from "./text-Bqxn_tMI.js";
3
+ const R = { class: "lfp-document" }, $ = {
4
+ key: 0,
5
+ class: "lfp-page-toolbar"
6
+ }, H = { class: "lfp-page-toolbar__meta" }, S = { class: "lfp-page-toolbar__status" }, V = { class: "lfp-page-toolbar__actions" }, A = ["disabled"], O = { class: "lfp-page-toolbar__pager" }, U = ["disabled"], z = /* @__PURE__ */ P({
7
+ __name: "DocxRenderer",
8
+ props: {
9
+ source: {}
10
+ },
11
+ emits: ["loading", "error"],
12
+ setup(h, { emit: y }) {
13
+ const x = h, u = y, l = i(null), o = i(0), a = i(1), s = i("paged"), f = new AbortController(), C = L(() => o.value ? s.value === "paged" ? `第 ${a.value} / ${o.value} 页` : `共 ${o.value} 页` : "");
14
+ function w() {
15
+ if (!l.value)
16
+ return [];
17
+ const t = l.value.querySelector(".docx-wrapper");
18
+ if (!t)
19
+ return [];
20
+ const e = Array.from(t.children).filter(
21
+ (r) => r instanceof HTMLElement && (r.tagName === "SECTION" || r.classList.contains("docx"))
22
+ );
23
+ return e.length ? e : [t];
24
+ }
25
+ function c() {
26
+ const t = w();
27
+ o.value = t.length || 1, a.value = Math.min(Math.max(a.value, 1), o.value), t.forEach((e, r) => {
28
+ e.classList.add("lfp-docx-page"), e.dataset.page = String(r + 1), e.hidden = s.value === "paged" && r !== a.value - 1;
29
+ });
30
+ }
31
+ function g(t) {
32
+ s.value = t;
33
+ }
34
+ function M() {
35
+ a.value = Math.max(a.value - 1, 1);
36
+ }
37
+ function k() {
38
+ a.value = Math.min(a.value + 1, o.value);
39
+ }
40
+ async function E() {
41
+ u("loading", !0);
42
+ try {
43
+ const t = await D(x.source, f.signal), { renderAsync: e } = await import("./docx-preview-BV-bQyiM.js");
44
+ if (!l.value)
45
+ return;
46
+ l.value.innerHTML = "", await e(t, l.value, void 0, {
47
+ className: "lfp-docx-content",
48
+ ignoreWidth: !1,
49
+ ignoreHeight: !1,
50
+ useBase64URL: !0
51
+ }), c();
52
+ } catch (t) {
53
+ u("error", t instanceof Error ? t : new Error("DOCX 预览失败"));
54
+ } finally {
55
+ u("loading", !1);
56
+ }
57
+ }
58
+ return _(s, () => {
59
+ c();
60
+ }), _(a, () => {
61
+ s.value === "paged" && c();
62
+ }), N(() => {
63
+ E();
64
+ }), T(() => {
65
+ f.abort(), l.value && (l.value.innerHTML = "");
66
+ }), (t, e) => (p(), d("div", R, [
67
+ o.value ? (p(), d("div", $, [
68
+ n("div", H, [
69
+ e[2] || (e[2] = n("span", { class: "lfp-page-toolbar__title" }, "Word 视图", -1)),
70
+ n("span", S, v(C.value), 1)
71
+ ]),
72
+ n("div", V, [
73
+ n("button", {
74
+ class: b(["lfp-button is-ghost lfp-page-toolbar__toggle", { "is-active": s.value === "paged" }]),
75
+ type: "button",
76
+ onClick: e[0] || (e[0] = (r) => g("paged"))
77
+ }, " 分页 ", 2),
78
+ n("button", {
79
+ class: b(["lfp-button is-ghost lfp-page-toolbar__toggle", { "is-active": s.value === "all" }]),
80
+ type: "button",
81
+ onClick: e[1] || (e[1] = (r) => g("all"))
82
+ }, " 全部 ", 2),
83
+ s.value === "paged" && o.value > 1 ? (p(), d(B, { key: 0 }, [
84
+ n("button", {
85
+ class: "lfp-button is-ghost lfp-page-toolbar__nav",
86
+ type: "button",
87
+ disabled: a.value <= 1,
88
+ onClick: M
89
+ }, " 上一页 ", 8, A),
90
+ n("span", O, v(a.value) + " / " + v(o.value), 1),
91
+ n("button", {
92
+ class: "lfp-button is-ghost lfp-page-toolbar__nav",
93
+ type: "button",
94
+ disabled: a.value >= o.value,
95
+ onClick: k
96
+ }, " 下一页 ", 8, U)
97
+ ], 64)) : m("", !0)
98
+ ])
99
+ ])) : m("", !0),
100
+ n("div", {
101
+ ref_key: "containerRef",
102
+ ref: l,
103
+ class: "lfp-docx"
104
+ }, null, 512)
105
+ ]));
106
+ }
107
+ });
108
+ export {
109
+ z as default
110
+ };