← 全部文章

技术笔记 · 2026-09-13

同一附件入口,如何按格式拆开预览边界

只讨论预览分发:先按扩展名判定能力,再按格式懒加载解析器,并为表格、PDF 和下载兜底设置明确上限,避免一个弹窗承担所有文件类型。

  • 附件预览
  • PDF.js
  • SheetJS
  • docx-preview
  • 懒加载

关联案例:智能台账与订单管理。文章依据本地附件预览模块的分发与渲染边界整理;示例保留格式判定和上限思路,去掉了业务 URL 与宿主桥接细节。兼容性问题另见旧 WebView 中的预览依赖

一个“预览”按钮,其实是多条能力路径

台账或订单里点“预览”,用户期待看到内容;实现上却可能是文本、表格、Word 或 PDF。若在一个组件里堆满所有解析逻辑,会出现:

  • 打开列表就加载 PDF/Excel 依赖,首屏变重;
  • 不支持的类型静默空白,用户不知道该下载还是重试;
  • 大表格或长 PDF 把主线程卡住,看起来像页面崩溃。

更稳的边界是:入口只负责“这是什么类型、要不要预览”,各格式自己负责“怎么渲染、渲染到什么程度”。

先判定能力,再选择渲染器

本地实现先根据 URL/扩展名得到预览类型,再分发:

type PreviewType = 'image' | 'pdf' | 'text' | 'excel' | 'docx' | 'download'

function attachmentPreviewType(url: string): PreviewType {
  if (/\.(?:jpe?g|png|gif|webp|svg)(?:$|[?#])/i.test(url)) return 'image'
  const ext = extensionOf(url)
  if (ext === '.pdf') return 'pdf'
  if (ext === '.txt') return 'text'
  if (ext === '.xlsx' || ext === '.xls') return 'excel'
  if (ext === '.docx') return 'docx'
  return 'download'
}

关键点有三个:

  1. 未知类型明确降级为下载,不要猜 MIME 后强行渲染;
  2. 判定与渲染分离,列表项可以先展示类型标签,不必提前解析文件;
  3. 网络失败与解析失败分开报错,HTTP 读取失败和“Excel 无工作表”不是同一类问题。

按格式懒加载,并写清展示上限

预览时再动态 import 对应库:Excel 用 SheetJS,Word 用 docx-preview,PDF 用 PDF.js。这样未点开的附件不付出解析器体积。

上限要写进交互,而不是只写在代码注释里:

  • Excel:限制行列(例如前 1000 行、100 列),超出时提示“仅显示部分”;
  • PDF:限制页数(例如前 30 页),避免超长文档把 Canvas 打满;
  • 文本:空文件给出“0 字节”说明,而不是空白预览框。
打开附件
  ├─ type = image/pdf/text/excel/docx
  │     └─ 懒加载对应渲染器 → 有限预览 + 可下载
  └─ type = download
        └─ 直接走下载/宿主保存,不进入解析

下载不是失败,而是正式路径

预览能力永远小于“用户可能上传的一切文件”。把“不支持在线预览,请下载后查看”做成成功分支的一部分,比假装所有文件都能渲染更诚实。

在嵌入桌面宿主时,下载还可能需要调用原生保存接口(保留原文件名)。这时应把“浏览器 a[download]”和“宿主桥接保存”放在同一下载入口下分流,而不是让预览组件再去猜运行环境。

验证时按格式准备最小样本

至少覆盖:小 PDF、多页 PDF、多工作表 xlsx、空 txt、不支持的扩展名、下载接口 4xx。检查点包括:是否只加载了用到的依赖、超限提示是否出现、失败文案是否可操作、宿主内下载是否保留文件名。

把预览拆成“判定 → 懒加载渲染 → 明确上限 → 下载兜底”,入口保持稳定,格式变化就不会反复改到弹窗外层。