狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

[點晴永久免費OA][轉帖]面試官:怎樣實現PDF 預覽和下載?

freeflydom
2023年3月23日 16:37 本文熱度 823

熊的貓 前端技術江湖 2023-03-12 23:50 發表于北京

前言

在開發過程中要求對 PDF 類型的發票提供 預覽 和 下載 功能,**PDF** 類型文件的來源又包括 H5 移動端 和 **PC 端**,而針對這兩個不同端的處理會有些許不同,下文會有所提及。

針對 PDF 預覽 的文章不在少數,但似乎都沒有提及可能遇到的問題,或是提供對應的具體需求場景下如何選擇,因此,本文的核心就是結合實際需求場景下,看看目前各種實現方案到底哪一個更適合,當然希望大家可以在評論區對文中的內容進行斧正,或是提供更優質的方案。

基本要求:

  • 支持 pdf 文件 內容的 完整預覽
  • 多頁 pdf 文件 支持 分頁查看
  • PC 端 和 移動端 都需支持 下載 和 預覽

產品要求:

  • PC 端 的預覽要支持在 當前頁 進行預覽
  • pdf 文件 預覽時的字體要 和 實際文件的 字體保證一致性

PDF 預覽

先拋開上面的各種要求,咱們先總結下目前實現 PDF 預覽的幾種常用方式:

  • 借助各種類庫,基于代碼實現預覽,如基于 **`pdfjs-dist`**[1] 的包
  • 直接基于各個瀏覽器內置的 PDF 預覽插件,如 <iframe src="xxx">、<embed src="xxx" >
  • 服務端將 PDF 文件轉換成圖片

接下來分別看看以上方案如何實現,以及是否符合上述提供的要求!

<embed> / <iframe> 實現預覽

<embed> 標簽

<embed> 元素 將外部內容嵌入文檔中的指定位置,此內容由 外部應用程序 或 其他交互式內容源(如 瀏覽器插件)提供。

說簡單點,就是使用 <embed> 來展示的資源是完全交由它所在的環境提供的展示功能,即如果當前的應用環境支持這個資源的展示那么就可以正常展示,如果不支持那就無法展示。

使用起來也是非常簡單:

<embed type="application/pdf" :src="pdfUrl" width="800" height="600" />復制代碼

多數現代瀏覽器已經棄用并取消了對瀏覽器插件的支持,現在已經不建議使用 <embed> 標簽,但可以使用 <img>、<iframe>、<video>、<audio> 等標簽代替

<iframe> 標簽

基于 <iframe> 的方式和以上差不多,整體效果也一致,這里這就不在額外展示:

<iframe :src="pdfUrl" width="800" height="600" />復制代碼

值得注意的是,即便使用的是 <iframe> 但實際展開其內層結構后你會發現:

其內部還是 <embed> 標簽?這是怎么回事,不是說最好不建議使用 <embed> 嗎?

首先來在 **`caniuse`**[2] 查看兼容情況,如下:


<embed> 在不兼容的環境直接無法顯示,而 <iframe> 是能夠正常識別的,只不過 <iframe> 加載的資源無法被 IE 瀏覽器處理,即本質原因是 IE 瀏覽器根本就不支持對類似 PDF 等文件的預覽


vue3-pdfjs 實現預覽

為什么不直接使用 pdfjs-dist?

**pdf.js**[4] 幾個明顯的可吐槽的點:

  • 包名稱不統一,npm 上的包名叫 pdfjs-dist,然而在 Readme 中自己又稱其為 pdf.js
  • 沒有清晰的文檔作為指引,只能通過其倉庫中的 examples 目錄的內容作為參考
  • 官方示例不夠友好,例如沒有提供 vue/react 等相關的示例
  • 直接使用需要引入很多文檔沒有指明的內容
  • 有時展示的 pdf 內容文字模糊或缺少部分等
  • ...

存在問題

看上去加載正常的 pdf 文檔 似乎沒啥大問題,來試試加載 pdf 發票 看看,但由于實際發票敏感信息較多,這里就不貼出原本的發票內容,直接來看預覽后的發票內容:

  • 顯然整體發票的 內容缺失得非常多,雖然某些發票大部分能夠展示,但如 發票抬頭 和 印章 部分可能無法正常顯示等

注意】無法顯示完整的內容是因為 pdf.js 是需要一些字體庫的支持,如果 原 PDF 文件 中部分字體沒有匹配到字體庫將無法在 pdf.js 中顯示,而字體庫存放在 cmaps 文件夾下

另外,預覽的字體 和 實際的字體 是 不一致 的,而由于發票的特殊性,對字體的一致性是有較大的要求,畢竟如果同一張發票字體不一致會缺乏 規范性 和 合法性(~~被要求字體一致時的說法~~)

常見的解決方案:**解決 pdf.js 無法完全顯示 pdf 文件內容的問題**[6],實際上還是根據執行環境的錯誤信息進行分析,需要強行修改源碼內容。

PDF 轉 圖片 實現預覽

這種方式應該不用多說了,核心是服務端在響應 pdf 文件時,先轉換成圖片類型再返回,前端直接展示具體圖片內容即可。

下載

這里的下載實際不僅指 pdf 的下載,而是客戶端方面所能支持的下載方式,最常見的如下幾種:

  • a 標簽,例如 <a href="xxxx" download="xxx">下載</a>
  • location.href,例如 window.location.href = xxx
  • window.open,例如 window.open(xxx)
  • Content-disposition,例如 Content-disposition:attachment;filename="xxx"

<a> 實現下載

<a> 的 download 屬性用于指示瀏覽器 下載 href 指定的 URL,而不是導航到該資源,通常會提示用戶將其保存為本地文件,如果 download 屬性有指定內容,這個值就會在下載保存過程中作為 預填充的文件名,主要是因為如下原因:

  • 這個值可能會通過 Javascript 進行動態修改
  • 或者 Content-Disposition 中指定的 download 屬性優先級高于 a.download

這種應該是大家最熟悉的方式了,但熟悉歸熟悉,還有一些值得注意的點:

  • download 屬性只適用于 同源 URL
    • 同源 URL 會進行 下載 操作
    • 非同源 URL 會進行 導航 操作
    • 非同源的資源 仍需要進行下載,那么可以將其轉換為 **`blob: URL`**[10] 和 **`data: URL`**[11] 形式
  • 若 HTTP 響應頭中的 **`Content-Disposition`**[12] 屬性中指定了一個不同的文件名,那么會優先使用 Content-Disposition 中的內容
  • HTTP 若 HTTP 響應頭中的 **`Content-Disposition`**[13]  被設置為 Content-Disposition='inline',那么在 Firefox 中會優先使用 Content-Disposition 的 download 屬性

靜態方式:

  <a href="http://127.0.0.1:4000/pdf/2-1.png" download="2.pdf">下載</a>
復制代碼

動態方式:

function download(url, filename){
  const a = document.createElement("a"); // 創建 a 標簽
  a.href = url; // 下載路徑
  a.download = filename;  // 下載屬性,文件名
  a.style.display = "none"// 不可見
  document.body.appendChild(a); // 掛載
  a.click(); // 觸發點擊事件
  document.body.removeChild(a); // 移除
}
復制代碼

Blob 方式

if (reqConf.responseType == 'blob') {
    // 返回文件名
    let contentDisposition = config.headers['content-disposition'];

    if (!contentDisposition) {
      contentDisposition = `;filename=${decodeURI(config.headers.filename)}`;
    }

    const fileName = window.decodeURI(contentDisposition.split(`filename=`)[1]);

    // 文件類型
    const suffix = fileName.split('.')[1];

    // 創建 blob 對象
    const blob = new Blob([config.data], {
      type: FileType[suffix],
    });

    const link = document.createElement('a');
    link.style.display = 'none';
    link.href = URL.createObjectURL(blob); // 創建 url 對象
    link.download = fileName; // 下載后文件名
    document.body.appendChild(link);
    link.click();

    document.body.removeChild(link); // 移除隱藏的 a 標簽 
    URL.revokeObjectURL(link.href); // 銷毀 url 對象
  }
復制代碼

Content-disposition 和 location.href/window.open 實現下載

這看似是三種下載方式,但實際上就是一種,而且還是以 Content-disposition 為準。

Content-Disposition 響應頭 指示回復的內容該以何種形式展示,是以 內聯 的形式(即網頁或頁面的一部分)展示,還是以 附件 的形式 下載 并保存到本地,如下:

  • inline: 是 默認值,表示回復中的消息體會以頁面的一部分或者整個頁面的形式展示
    Content-Disposition: inline復制代碼
  • attachment: 設置為此值意味著消息體應該被下載到本地,大多數瀏覽器會呈現一個 "保存為" 的對話框,并將 filename 的值預填為下載后的文件名
    Content-Disposition: attachment; filename="filename.jpg"復制代碼

因此,基于 location.href='xxx' 和 window.open(xxx) 的方式能實現下載就是基于 Content-Disposition: attachment; filename="filename.jpg" 的形式,又或者說是觸發了瀏覽器本身的下載行為,滿足了這個條件,無論是通過 a 標簽跳轉、location.href 導航、window.open 打開新頁面直接在地址欄上輸入 URL 等都可以實現下載。

H5 移動端的下載

H5 移動端針對于 預覽 操作而言基于以上的方式都是可以實現,但是 下載 操作可就不同了,因為這是要區分場景:

  • 基于 手機瀏覽器
  • 基于 微信內置瀏覽器

基于 手機瀏覽器 的下載方式和上述提到的內容大致上也是一致的,本質上只要所在的客戶端支持下載那就沒有問題,然而在 微信內置瀏覽器 中你使用常規的下載方式可能達不到預期:

  • 在 Android 中使用常規的下載方式,通常會彈出對話框,詢問你是否需要喚醒 手機瀏覽器 來實現對應資源的下載,部分機型卻不會
  • 在 IOS 中以上方式都 無法實現下載,因此通常情況下會打開一個新的 webview 來提供預覽,部分機型在新的頁面中支持 長按屏幕 的方式進行保存操作,但并不是所有機型都支持

本質原因是在 微信內置瀏覽器 中屏蔽任何的 下載鏈接,如 APP 的下載鏈接普通文件 的下載鏈接 等等。

H5 移動端的下載還能怎么做?

由于這是 微信內置瀏覽器 環境對下載功能的屏蔽,因此 不用再考慮(~~想都不敢想~~)基于 微信內置瀏覽器 來實現下載功能,轉而應該考慮的是如何實現 間接下載

  • 判斷當前是否是屬于 微信內置瀏覽器,若是則幫助用戶自動喚起 手機瀏覽器 實現下載,但并不是所有機型都支持 喚起 操作,因此最好是提示使用用戶直接通過 手機瀏覽器 實現下載,為了方便用戶,可以實現 一鍵復制 的功能進行輔助
  • 另一種就直接提示只支持 PC 端下載,放棄對移動端的下載操作

最后

綜上所述,實際在實現 pdf 預覽的過程中可能暫時沒有辦法達到完美的方式,特別是針對類似 發票類 的 pdf 文件,仍存在如下的問題:

  • 無法保證 h5 移動端都具備 下載 功能
  • 無法保證 pdf 預覽 時,預覽的字體和實際發票 字體 保持一致

現有大部分的預覽方式都基于 pdf.js 的方式實現,而 pdf.js 內部通過 PDFJs.getDocument(url/buffer) 的方式基于 文件地址 或 數據流 來獲取內容,再通過 canvas 處理渲染 pdf 文件,感興趣可以去研究 pdf.js 源碼。

pdf.js 帶來相關問題就是如果對應的 pdf 文件中包含了 pdf.js 中不存在的字體,那么就無法完整渲染,另外渲染出來的字體和原本的 pdf 文件字體會存在差異。

針對這兩點,目前發現谷歌內置的 pdf 插件似乎提供了很好的支持,意味著其他瀏覽器如果包含了谷歌相關的插件(如:Edge、QQ Browser),就可以直接基于 <iframe> 的方式實現預覽,又或者為了更嚴謹字體一致性只能通過下載的方式來查看源文件。

作者:熊的貓

https://juejin.cn/post/7207078219215732794



該文章在 2023/3/23 16:40:09 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved