npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue3-files-preview

v1.6.1

Published

Vue3 多格式文档/模型预览组件:3D 模型(glb/obj/fbx/stl/ply)、DXF 图纸、PDF、Office、Markdown、图片、音视频,内置视图立方体、模型树、图层面板

Readme

vue3-files-preview

Vue3 多格式文档 / 模型预览组件。单个 <FilePreview> 按文件类型自动分发到对应查看器,支持本地 File 对象与远程 URL 两种数据源。

支持格式

| 类型 | 扩展名 | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 3D 模型 | glb / gltf / obj / fbx / stl / ply(含视图立方体 26 方位、模型树楼层/构件显隐、点击拾取/隔离/透明化、漫游、剖切、动画播放、环境光照、截图、网格/坐标轴) | | CAD 图纸 | dxf(图层面板、图层拾取、距离测量、截图) | | 文档 | pdf / docx / xlsx / pptx / md / csv / tsv / txt 等(xlsx 支持内嵌图片预览、虚拟滚动;md 提供目录大纲) | | 媒体 | 常见图片、音频、视频格式(视频默认原生播放;videoPlayer="xgplayer" 时换用西瓜播放器,.flv 与 .m3u8(HLS)经官方引擎全平台可播——原生 <video> 不支持 flv 容器,HLS 的原生支持随浏览器而定(Chrome 146+ / Safari 支持,Firefox 等不支持时给出切换提示)。.avi/.wmv 浏览器与 JS 引擎均无解码器,仅可下载。注意:m3u8 分片按播放列表相对地址拉取,需服务端允许跨域;带鉴权请求头的源会先整体转 blob,相对地址分片将无法解析,此类场景请让分片使用绝对地址) | | 压缩文件 | zip(左侧条目列表带路径过滤与排序、单条目下载、嵌套压缩包预览、内嵌查看器工具栏由外层顶栏托管) |

旧版二进制格式(.doc / .ppt / .xls)无法在浏览器端解析,会回退到 UnsupportedViewer 并给出「另存为新格式」的补救提示;其余不支持的格式同样回退到 UnsupportedViewer。

安装

npm install vue3-files-preview

yarn add vue3-files-preview

pnpm add vue3-files-preview

本包以 peerDependencies 声明全部运行时依赖(vue 之外还有 docx-preview、exceljs、pdfjs-dist、three、dxf-viewer、mermaid、katex、marked、highlight.js、dompurify、pptx-preview、vue-pdf-embed、jszip、ssf、xgplayer、xgplayer-flv、xgplayer-hls、core-js)。npm 7+ / pnpm / yarn 会自动安装 peer 依赖;如果手动管理依赖,请按需安装上表格式对应的库。其中:

  • pdfjs-dist 与 vue-pdf-embed 为精确锁定版本(两者必须共享同一个 pdf.js 实例),请勿自行升级;
  • three / dxf-viewer 体积较大且仅 3D / DXF 格式用到,宿主若确认不预览这两类格式,可自行省略(对应查看器分包不会被加载);
  • xgplayer / xgplayer-flv / xgplayer-hls / core-js 仅视频的第三方播放器模式(videoPlayer="xgplayer")用到,分包按需懒加载;native 模式(默认)不下载对应代码。core-js 是 xgplayer 自身声明的依赖。

Vite 插件(必须)

本包依赖 vue-pdf-embed、pdfjs-dist、dxf-viewer 等库,在 Vite 消费时存在预构建兼容、CJS 互操作等问题。请务必在消费项目的 vite.config.ts 中添加内置 Vite 插件:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { vue3FilesPreviewPlugin } from "vue3-files-preview/plugin";

export default defineConfig({
  plugins: [vue(), vue3FilesPreviewPlugin()]
});

该插件一站式解决以下问题:

| 配置项 | 原因 | | ---------------------- | ------------------------------------------------------------------------------------------------------ | | PDF Worker 自动部署 | 将包内 pdfjs-dist 的 worker 文件自动复制到消费项目的 public/ 目录 | | opentype.js alias | dxf-viewer 依赖的 opentype.js 是 CJS 格式,自动 resolve 到正确的入口 | | optimizeDeps.include | vue-pdf-embed、pdfjs-dist、jszip、docx-preview 等库的 ESM 导出方式与 Vite 预构建不兼容,自动强制预构建 |

使用

<template>
  <FilePreview :url="fileUrl" style="width: 100%; height: 600px" @close="onClose" />
</template>

<script setup>
import { FilePreview } from "vue3-files-preview";
import "vue3-files-preview/dist/style.css";

const fileUrl = "https://example.com/model.glb";
</script>

本地文件预览:

<FilePreview :file="fileObj" />

单独使用某个查看器

除聚合入口 FilePreview 外,各格式的查看器也可单独按需引入(均为异步组件,用到哪个才加载对应分包):

<template>
  <ImageViewer :source="file" file-name="示例.png" />
</template>

<script setup>
import { ImageViewer } from "vue3-files-preview";
import "vue3-files-preview/dist/style.css";
</script>

可单独使用的查看器:ThreeViewer、DxfViewer、ImageViewer、DocumentViewer(纯文本)、MarkdownViewer、DocxViewer、XlsxViewer、CsvViewer、PdfViewer、PptxViewer、AudioViewer、VideoViewer、ArchiveViewer、UnsupportedViewer。

  • 公共 props:source(必填,File 对象或 url 字符串)、fileName(可选)、requestHeaders(可选,见「带鉴权的远程文件」一节;传入本地 File 时查看器内部会自动忽略)。
  • 专属 props:ThreeViewer / UnsupportedViewer 需 ext(扩展名);PdfViewer 支持 mode / showModeToggle;PptxViewer 支持 mode / showPageNav;VideoViewer 支持 videoPlayer("native" 默认 / "xgplayer" 西瓜播放器,flv 与 m3u8 仅后者可播);ThreeViewer 还支持 background / autoRotate / showGrid / showAxis / dracoDecoderPath。注意 ThreeViewer 单独使用时自身的默认值与 FilePreview 聚合入口不同:showGrid 为 true、background 为 #171717(聚合入口由容器统一下发,故 showGrid 默认 false、背景随主题令牌)。
  • 事件:loaded / error 照常触发(3D、DXF 另有 progress)。
  • 上下文:单独使用时不会注入 FilePreview 的上下文,顶栏的保存 / 关闭 / 主题按钮会自动隐藏,不影响核心预览。

Props

| 属性 | 类型 | 默认值 | 说明 | | ------------------- | ---------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | url | string | "" | 远程文件地址,与 file 二选一 | | file | File \| null | null | 本地文件对象;与 url 同时存在时 file 优先 | | type | string | "" | 强制指定文件扩展名(不含点,如 glb/docx),用于 url 无扩展名或扩展名与实际格式不符的场景;缺省时从文件名推断 | | background | string | 随主题 | 3D 场景背景色;默认读取主题令牌(深色 #171717 / 浅色 #f8f8f8),显式传入后固定为该值 | | autoRotate | boolean | false | 3D 模型自动旋转。运行时改 prop 即时生效,也可用视图工具栏切换 | | showGrid | boolean | false | 3D 视图显示地面网格。运行时改 prop 即时生效 | | showAxis | boolean | false | 3D 视图显示坐标轴。运行时改 prop 即时生效 | | dracoDecoderPath | string | "" | glTF 内嵌 DRACO 压缩时的解码器目录;默认留空即可,使用 three 自带的本地 wasm 解码器,仅在需指向自建/CDN 路径时覆盖 | | pptxMode | "slide" \| "list" | — | PPTX 呈现方式:slide=单页翻页,list=全部页纵向连续滚动。不传时由组件内部维护(默认 list)并在工具栏提供「单页/连续」切换;传入后由宿主固定,用户无法切换 | | pptxShowPageNav | boolean | false | 是否在 PPTX 工具栏显示「上一页/下一页」按钮,仅 slide 模式有意义 | | pdfMode | "iframe" \| "pdfjs" | — | PDF 呈现方式:iframe=浏览器原生,pdfjs=pdf.js 自绘。不传时由组件内部维护(默认 pdfjs);传入后由宿主固定 | | pdfShowModeToggle | boolean | false | 是否在 PDF 工具栏显示「原生 / 自绘」切换按钮,仅 pdfMode 未显式指定时生效 | | videoPlayer | "native" \| "xgplayer" | "native" | 视频播放器模式:native=浏览器原生 <video>(零额外依赖);xgplayer=西瓜播放器(自带倍速/截图/画中画/设置面板,.flv/.m3u8 经官方引擎可播)。仅对 video 生效;分包(xgplayer/xgplayer-flv/xgplayer-hls)按需懒加载,native 模式不下载 | | showClose | boolean | false | 顶栏最右侧显示「关闭」按钮,点击触发 close 事件;开启后聚焦在组件内按 Esc 等同点击关闭(不劫持宿主页面的 Esc) | | showThemeToggle | boolean | true | 顶栏显示「深/浅主题切换」按钮 | | showTopBar | boolean | true | 是否显示顶栏(文件名 / 工具栏 / 保存 / 全屏 / 关闭),对所有查看器生效 | | showBottomBar | boolean | true | 是否显示底栏(帮助提示 / 媒体信息),对所有查看器生效 | | toolsPosition | "top" \| "bottom" | "top" | 查看器工具按钮(缩放 / 翻页 / 旋转 / 测量 / 图层等)的摆放位置。"bottom" 时这批按钮改为悬浮在内容区域底部(浮于预览内容之上、居中、不占用底栏),右上角公共按钮(主题 / 保存 / 全屏 / 关闭)不受影响;无工具按钮的查看器(音视频等)不渲染悬浮工具条。工具条含下拉按钮(3D 剖切 / DXF 测量)时菜单自动改为向上弹出 | | maxFileSize | number | 200 | 本地文件大小上限(MB),超限直接报错不进入解析;传 0 或负数表示不限制。仅对 file 生效 | | archiveEntryMaxSize | number | 200 | 压缩包内单个条目解压后体积上限(MB),超限直接拒绝解压并提示可下载;传 0 或负数不限制。仅对 zip 生效——zip 压缩比可达上千倍,按中央目录声明的原始体积事前拦截比解压到一半内存耗尽更可控 | | archiveShowList | boolean | true | 压缩包预览时是否显示左侧条目列表。关闭后列表(含折叠/展开入口)整体隐藏,视口占满并自动预览主条目;嵌套压缩包的内层列表跟随同一开关。条目切换只能由宿主换源完成 | | disableCache | boolean | false | 是否关闭远程源内存缓存(LRU,5 条 / 256MB 预算)。开启缓存时同地址重复预览与顶栏保存直接复用、不再二次下载;内存敏感的宿主可关闭。本地 file 不受影响,PDF url 模式走 pdf.js 自己的流式请求、不经过该缓存。缓存按组件实例隔离(每个实例一份、卸载即释放),本开关只作用于当前实例 | | requestHeaders | Record<string, string> | — | 远程地址请求附带的自定义请求头(鉴权 Authorization、临时签名等)。所有远程读取统一携带:文档解析、PDF 预检与渲染、图片/音视频/3D/DXF 加载、顶栏保存。本地 file 不受影响。注意:带请求头的音视频与 iframe 模式 PDF 会先整包下载为 blob 再展示(无 Range 流式) | | watermark | string \| WatermarkOptions | — | 预览区水印。传字符串等价 { text };不传或 text 为空不渲染。平铺覆盖整个预览区、点击穿透,详见「水印」一节 | | locale | Partial<ViewerLocale> | — | 界面文案覆盖,与内置中文浅合并(见「文案(locale)」一节);初始化后固定,切换语言请配合 :key 重建组件 |

事件

| 事件 | 载荷 | 说明 | | -------- | --------------------------- | --------------------------------------------------------- | | loaded | { type, ... } | 解析成功;type 为命中的查看器类型,其余字段随查看器不同 | | error | unknown(通常为 Error) | 文件加载 / 解析失败 | | progress | number(0-100) | 加载进度,仅 3D / DXF 等有进度的查看器触发 | | close | — | 点击顶栏「关闭」按钮,由宿主决定关闭弹窗 / 清空预览 |

宿主方法(ref expose)

通过模板 ref 可调用组件暴露的两个方法:

const previewRef = ref();
previewRef.value.reload(); // 重新加载当前源(整只查看器重建,重新拉取/解析)
previewRef.value.clearCache(); // 清空本实例的远程源内存缓存,下次访问重新拉取

reload() 在没有当前源时为空操作(不会报错)。

另外,切换 url / file 时在途的网络请求会被自动取消(AbortController),不会等旧文件下载完才切换;url 与 file 同时清空时组件回到空态拖拽提示。

主题

组件内置深 / 浅两态:默认跟随系统 prefers-color-scheme,用户点击顶栏主题按钮后以手动选择为准(可用 showThemeToggle 关闭该按钮)。根元素带 data-theme="light|dark",颜色统一走 --mv-* 设计令牌,宿主可覆盖以自定义配色。

令牌清单

在宿主样式里覆盖令牌即可定制配色(写在能作用到 .file-preview 的选择器上):

.file-preview {
  --mv-accent: #7c5cff; /* 例如:把品牌色换成紫罗兰 */
}

| 令牌 | 深色默认 | 浅色 | | ----------------------- | -------------------------------------------------------- | --------------------------------- | | --mv-surface | rgb(38 38 38 / 62%) | rgb(10 10 10 / 3%) | | --mv-surface-strong | rgb(38 38 38 / 82%) | rgb(255 255 255 / 92%) | | --mv-panel-bg | #262626 | #ffffff | | --mv-bar-bg | #171717 | #f5f5f5 | | --mv-border | rgb(250 250 250 / 10%) | rgb(10 10 10 / 10%) | | --mv-border-strong | rgb(250 250 250 / 30%) | rgb(10 10 10 / 20%) | | --mv-canvas | transparent | #f8f8f8 | | --mv-scene-bg | #171717 | #f8f8f8 | | --mv-viewer-bg | radial-gradient(120% 120% at 50% 0%, #1d1d1d, #131313) | #f8f8f8 | | --mv-text | #e5e5e5 | #404040 | | --mv-text-dim | rgb(229 229 229 / 60%) | rgb(64 64 64 / 60%) | | --mv-text-mute | rgb(229 229 229 / 30%) | rgb(64 64 64 / 40%) | | --mv-accent | #00a6f4 | #00bcff | | --mv-accent-strong | #0084d1 | #00a6f4 | | --mv-accent-soft | rgb(0 166 244 / 16%) | rgb(0 188 255 / 12%) | | --mv-accent-soft-strong | rgb(0 166 244 / 26%) | rgb(0 188 255 / 18%) | | --mv-accent-ring | rgb(0 166 244 / 50%) | rgb(0 188 255 / 40%) | | --mv-accent-text | #74d4ff | #0084d1 | | --mv-danger | #fb2c36 | #e03131 | | --mv-content-text | #d4d4d4 | #404040 | | --mv-content-heading | #e5e5e5 | #262626 | | --mv-code-bg | #262626 | rgb(10 10 10 / 5%) | | --mv-code-text | #e5e5e5 | #404040 | | --mv-pre-bg | #0a0a0a | #fafafa | | --mv-pre-border | #262626 | rgb(10 10 10 / 10%) | | --mv-pre-text | #d4d4d4 | #171717 | | --mv-th-bg | #262626 | rgb(10 10 10 / 3%) | | --mv-row-alt | rgb(255 255 255 / 2%) | rgb(10 10 10 / 3%) | | --mv-toc-bg | rgb(0 0 0 / 16%) | rgb(10 10 10 / 3%) | | --mv-desk-bg | #262626 | #ececee | | --mv-selected | rgb(255 255 255 / 10%) | rgb(10 10 10 / 10%) | | --mv-surface-hover | rgb(255 255 255 / 10%) | rgb(10 10 10 / 5%) | | --mv-tag-bg | #363636 | #e6e6e6 | | --mv-radius-sm | 6px | 同深色 | | --mv-radius | 10px | 同深色 | | --mv-radius-lg | 16px | 同深色 | | --mv-shadow | 0 12px 40px rgb(0 0 0 / 45%) | 0 12px 40px rgb(10 10 10 / 16%) | | --mv-blur | blur(16px) saturate(140%) | 同深色 | | --mv-ease | cubic-bezier(0.4, 0, 0.2, 1) | 同深色 |

尺寸 / 圆角 / 阴影 / 缓动等结构令牌(--mv-radius*、--mv-shadow、--mv-blur、--mv-ease)两套主题共用,仅颜色类令牌随主题切换。

文案(locale)

界面默认为中文,可通过 locale prop 覆盖任意条目(浅合并,未提供的键回落默认值):

<FilePreview :url="url" :locale="{ save: 'Save', close: 'Close', loadFailedTitle: 'Failed to load' }" />

可覆盖键见类型 ViewerLocale(包的类型导出):顶栏按钮(save / close / toLightTheme / toDarkTheme …)、容器状态(loading / loadFailedTitle / dropToPreview / 错误映射 errNetwork / err403 …)、面板(panelModelTree / panelLayers / hideAll / showAll / 页签 tabFloor / tabComponent 等)、Markdown 目录(toc / tocAria / tocExpand / tocCollapse / tocChildExpandPrefix / tocChildCollapsePrefix / tocChildSuffix)、表格图片预览(clickPreviewTitle / imagePreviewAria / closeEscTitle / closeImagePreviewAria / imagePreviewAlt)、PPTX 失败兜底(pptxNoSlides)。完整键位与内置中文文案见类型声明。注意 locale 初始化后固定,不响应式更新。

交互

  • 空态:未提供数据源时显示拖拽提示。
  • 拖拽:支持直接把文件拖入组件区域预览。
  • 全屏:图片 / 音视频 / 3D 等查看器顶栏提供全屏按钮。

水印

企业预览场景(图纸 / 合同类文档)常要求在预览区叠加当前用户标识,防止截屏外传:

<!-- 字符串简写 -->
<FilePreview :url="url" watermark="预览人:张三 2026-10-06" />

<!-- 完整配置 -->
<FilePreview
  :url="url"
  :watermark="{
    text: ['内部资料', '预览人:张三'],
    opacity: 0.16,
    fontSize: 15,
    rotate: -22,
    gapX: 180,
    gapY: 140,
    color: 'rgba(255, 255, 255, 0.9)'
  }"
/>

水印为 canvas 绘制的平铺图层,覆盖整个预览区(含顶栏/底栏),pointer-events 穿透不影响任何交互与全屏;未配置 color 时按深/浅主题自动取前景色,主题切换自动重绘。tile 按设备像素比绘制、按逻辑像素平铺(background-size),高分屏上文字不发虚。

防篡改

水印默认开启防篡改保护:水印节点被删除、style/class 被改动时自动恢复——MutationObserver 即时校正 + 周期巡检兜底(观察器本身被脚本 disconnect 也能恢复),且关键属性(背景图/平铺尺寸/层级/点击穿透)以 !important 行内样式写入,可抵御外部样式表的 !important 覆盖。宿主需要自行操控水印 DOM 时可关闭:

<FilePreview :watermark="{ text: '内部资料', tamperProof: false }" />

查看器工具栏的全屏按钮全屏的是组件根容器而非查看器自身区域,水印与顶栏/底栏在全屏下同样覆盖——不存在「全屏后无水印可截图」的绕过口(独立使用某个查看器组件、无容器注入时回落为全屏查看器自身)。

防篡改针对的是 DevTools 手动删改与常规脚本移除,无法对抗页面内任意脚本——客户端水印本质是视觉威慑,强对抗需求请配合服务端手段(下载/导出文件烙印水印、访问审计等)。

带鉴权的远程文件

文件服务需要登录态/签名时,通过 requestHeaders 传入请求头,组件内所有远程读取统一携带(各查看器也可单独使用时各自传入):

<FilePreview :url="fileUrl" :request-headers="{ Authorization: `Bearer ${token}` }" />

实现方式按载体自动区分:文档解析(docx/xlsx/pptx/md/文本)、pdf.js 渲染(httpHeaders,支持 Range 分片)、3D 模型(three loader 原生携带)直接随请求发送;无法携带请求头的载体(<img>、<video>/<audio>、iframe 模式 PDF、dxf-viewer)自动先拉取为 blob 中转。注意:

  • blob 中转意味着整包下载,大视频/大 PDF(iframe 模式)没有流式范围请求;
  • 3D 多文件格式(.gltf + 外部贴图、.obj + .mtl)中主文件携带请求头,由 three 内部加载器发起的贴图等子资源不保证携带——此类场景建议 .glb 内嵌资源或经代理直连;
  • 顶栏「保存」同样携带请求头。带请求头的地址若 fetch 失败(鉴权过期、断网),保存会判定为失败并在顶栏给出提示,而不是静默降级为必然失败的直链;未带请求头时跨域 fetch 失败仍会降级为直链下载。预览与保存共用内存缓存,保存不触发二次整包下载。

导出

除默认导出的 FilePreview 组件外,还导出:

  • 工具函数:detectViewerType、getFileExtension、getFileName、normalizeExtension、formatDuration,以及类型判断 isImage / isText / isCsv / isMarkdown / isPdf / isOffice / isAudio / isVideo / isThree / isSupportedExtension。
  • 扩展名常量:IMAGE_EXTENSIONS、TEXT_EXTENSIONS、MARKDOWN_EXTENSIONS、PDF_EXTENSIONS、OFFICE_EXTENSIONS、LEGACY_OFFICE_EXTENSIONS、CSV_EXTENSIONS、AUDIO_EXTENSIONS、VIDEO_EXTENSIONS、THREE_EXTENSIONS、ARCHIVE_EXTENSIONS、SUPPORTED_EXTENSIONS,以及可直接用于 <input type="file" :accept="ACCEPT_ATTR"> 的 ACCEPT_ATTR。
  • 文案:zhCN(内置中文文案全量对象,可作为自定义 locale 的基底)、mergeLocale(Partial<ViewerLocale> 与内置值的浅合并函数)。
  • 类型声明:ModelViewerProps、ModelViewerEmits、ViewerType、FileSource、ViewerLoadedResult、ViewerProps、ViewerEmits、ProgressViewerEmits、ViewerTheme、ViewerLocale、ThreeViewerProps、PdfMode、PdfViewerProps、PptxMode、PptxViewerProps、UnsupportedViewerProps、ModelInfo、DxfLayer、MediaInfo、WatermarkOptions。

注意事项

PDF 大文件

pdf.js 模式按页虚拟渲染:只有视口附近(前后各 2 页)的页面实际渲染成 canvas,其余页以同高占位槽撑出滚动条,几百上千页的文档不会把内存打爆;页码跟踪、缩放锚点均基于占位槽定位。

Excel 解析器

xlsx 由 exceljs 解析(值、公式缓存结果、合并区、单元格样式、行高列宽、WPS 内嵌图片 ID);数值与日期按 Excel 数字格式(numFmt)渲染。整表超过 5000 行 / 200 列会截断并在底栏提示。旧版二进制 .xls(BIFF 格式)无法解析,见「支持格式」一节的回退说明。

大表走虚拟滚动:只渲染视口附近的行(视口前后留 buffer),上下用同高占位行撑出滚动条。

表格图片预览

xlsx 里的图片统一按「锚点单元格内绝对定位」渲染,支持两类来源:WPS 单元格函数图(DISPIMG)与 drawing.xml 浮动图。

  • 定位:以图片所在 td 为坐标系,位置由表格布局本身决定,不受视口尺寸、滚动位置、是否全屏影响;尺寸取文件里记录的行高/列宽(或 ext 原始像素),与 Excel / WPS 保存的值一致。
  • 放大:点击单元格图片打开灯箱浮层,原图按 object-fit: contain 铺满视口;点击空白处或右上角关闭按钮关闭。
  • 与全屏叠加:全屏状态下浮层自动 teleport 到全屏元素内部(浏览器只会渲染全屏子树,挂在 body 上的浮层会被整块盖住、表现为「点了没反应」);退出全屏后自动搬回 body。
  • Esc 分层退出:全屏 + 浮层同时存在时,第一次 Esc 只退出全屏、图片保留,再按一次才关闭图片;未全屏时 Esc 直接关闭。ESC 处理挂在 document 捕获阶段,不会冒泡到外层导致整个预览被关掉。

PPTX 解析失败

第三方生成器(如 pptxgenjs)产出的结构不完整文件,pptx-preview 可能静默吞掉解析异常并表现为「成功解析出 0 页」。此时查看器按 locale.pptxNoSlides 给出明确提示(建议用 PowerPoint 重新导出),而不是留一个空白画面。

该兜底在 slide(单页翻页)与 list(连续滚动)两种模式下均生效——slide 模式下库会尝试渲染不存在的首页导致 TypeError,查看器捕获后统一转为友好报错,不会把原始技术错误泄漏给用户,也不会因 ResizeObserver 触发重建循环而刷屏。

安全

Office 文档(docx / pptx)由 docx-preview / pptx-preview 把文档 XML 直接转成 HTML 插入页面;上传方可控的恶意 XML 可能携带脚本、事件属性或 javascript: 链接,构成存储型 XSS 面。库在渲染完成后对产物 DOM 执行一次 DOMPurify 就地消毒(IN_PLACE 模式,保留元素实例与 data:/blob: 图片 URI),Markdown 同样经 DOMPurify 处理。xlsx 走 Vue 文本插值(自动转义)且图片为 blob: src,构造上安全,不额外消毒。

压缩文件预览

zip 压缩包由 jszip 解析,预览时左侧显示条目列表(文件名、扩展名标签、体积),点击条目即在右侧内联预览——条目按扩展名自动分发到与直接预览同一文件完全相同的查看器(3D / PDF / Office / 图片 / 音视频等),内嵌查看器的工具栏经 Teleport 托管到外层顶栏,视觉与独立预览一致。

  • 条目管理:列表支持按包内路径前缀过滤(大小写不敏感)、循环切换排序(默认 → 名称 → 大小 → 类型);条目按目录分组显示。列表可收起让预览视口占满,收起后展开入口位于压缩包顶栏工具栏(不与预览区左上角冲突——那里是内层查看器的面板位:Markdown 目录、3D 模型树、DXF 图层面板都会占);仅当宿主隐藏了顶栏(showTopBar=false)时才回落到预览区左上角的悬浮按钮。宿主通过 archiveShowList=false 关闭列表时不给任何展开入口。
  • 单条目下载:每个条目右侧带下载按钮,带实时进度百分比;单个条目下载失败时内联红字提示,不影响其余条目预览。
  • 嵌套压缩包:zip 内嵌 zip 时自动递归预览,内层压缩包保持自带顶栏(显示内层包名),列表跟随外层 archiveShowList 开关。
  • 体积防线:archiveEntryMaxSize(默认 200MB)按中央目录声明的原始体积事前拦截超大条目,超限直接拒绝解压并提示可下载,而非解压到一半内存耗尽才崩溃。条目解析失败只在该条目的预览区给出内联提示,列表保持可用可换条目。
  • 保存:顶栏「保存」下载的是压缩包本身(当前层),而非当前预览的条目。

UnsupportedViewer 连续切换

连续切换多个不支持的文件(如 .doc → .xls → .ppt → .xyz)时,Vue 会复用同一个 UnsupportedViewer 实例(组件类型相同且 key 未变),onMounted 不会再次触发。查看器内部 watch 了 source 变化,每次切源都会重新上报 loaded 事件,确保宿主的 loading 遮罩不会永久停留。

Draco 压缩模型

预览 draco 压缩的 glb/gltf 无需额外配置:three 的 DRACOLoader 默认使用自带的本地 wasm 解码器,打包器会自动产出对应静态资源。仅当需要指向自建 / CDN 上的 three 官方解码器 目录时,才通过 draco-decoder-path 传入(该目录需含 draco_wasm_wrapper.js 与 draco_decoder.wasm)。