欧美成人精品手机在线观看_69视频国产_动漫精品第一页_日韩中文字幕网 - 日本欧美一区二区

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

[點(diǎn)晴永久免費(fèi)OA][轉(zhuǎn)帖]面試官:怎樣實(shí)現(xiàn)PDF 預(yù)覽和下載?

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

熊的貓 前端技術(shù)江湖 2023-03-12 23:50 發(fā)表于北京

前言

在開(kāi)發(fā)過(guò)程中要求對(duì) PDF 類(lèi)型的發(fā)票提供 預(yù)覽 和 下載 功能,**PDF** 類(lèi)型文件的來(lái)源又包括 H5 移動(dòng)端 和 **PC 端**,而針對(duì)這兩個(gè)不同端的處理會(huì)有些許不同,下文會(huì)有所提及。

針對(duì) PDF 預(yù)覽 的文章不在少數(shù),但似乎都沒(méi)有提及可能遇到的問(wèn)題,或是提供對(duì)應(yīng)的具體需求場(chǎng)景下如何選擇,因此,本文的核心就是結(jié)合實(shí)際需求場(chǎng)景下,看看目前各種實(shí)現(xiàn)方案到底哪一個(gè)更適合,當(dāng)然希望大家可以在評(píng)論區(qū)對(duì)文中的內(nèi)容進(jìn)行斧正,或是提供更優(yōu)質(zhì)的方案。

基本要求:

  • 支持 pdf 文件 內(nèi)容的 完整預(yù)覽
  • 多頁(yè) pdf 文件 支持 分頁(yè)查看
  • PC 端 和 移動(dòng)端 都需支持 下載 和 預(yù)覽

產(chǎn)品要求:

  • PC 端 的預(yù)覽要支持在 當(dāng)前頁(yè) 進(jìn)行預(yù)覽
  • pdf 文件 預(yù)覽時(shí)的字體要 和 實(shí)際文件的 字體保證一致性

PDF 預(yù)覽

先拋開(kāi)上面的各種要求,咱們先總結(jié)下目前實(shí)現(xiàn) PDF 預(yù)覽的幾種常用方式:

  • 借助各種類(lèi)庫(kù),基于代碼實(shí)現(xiàn)預(yù)覽,如基于 **`pdfjs-dist`**[1] 的包
  • 直接基于各個(gè)瀏覽器內(nèi)置的 PDF 預(yù)覽插件,如 <iframe src="xxx">、<embed src="xxx" >
  • 服務(wù)端將 PDF 文件轉(zhuǎn)換成圖片

接下來(lái)分別看看以上方案如何實(shí)現(xiàn),以及是否符合上述提供的要求!

<embed> / <iframe> 實(shí)現(xiàn)預(yù)覽

<embed> 標(biāo)簽

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

說(shuō)簡(jiǎn)單點(diǎn),就是使用 <embed> 來(lái)展示的資源是完全交由它所在的環(huán)境提供的展示功能,即如果當(dāng)前的應(yīng)用環(huán)境支持這個(gè)資源的展示那么就可以正常展示,如果不支持那就無(wú)法展示。

使用起來(lái)也是非常簡(jiǎn)單:

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

多數(shù)現(xiàn)代瀏覽器已經(jīng)棄用并取消了對(duì)瀏覽器插件的支持,現(xiàn)在已經(jīng)不建議使用 <embed> 標(biāo)簽,但可以使用 <img>、<iframe>、<video>、<audio> 等標(biāo)簽代替

<iframe> 標(biāo)簽

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

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

值得注意的是,即便使用的是 <iframe> 但實(shí)際展開(kāi)其內(nèi)層結(jié)構(gòu)后你會(huì)發(fā)現(xiàn):

其內(nèi)部還是 <embed> 標(biāo)簽?這是怎么回事,不是說(shuō)最好不建議使用 <embed> 嗎?

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


<embed> 在不兼容的環(huán)境直接無(wú)法顯示,而 <iframe> 是能夠正常識(shí)別的,只不過(guò) <iframe> 加載的資源無(wú)法被 IE 瀏覽器處理,即本質(zhì)原因是 IE 瀏覽器根本就不支持對(duì)類(lèi)似 PDF 等文件的預(yù)覽


vue3-pdfjs 實(shí)現(xiàn)預(yù)覽

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

**pdf.js**[4] 幾個(gè)明顯的可吐槽的點(diǎn):

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

存在問(wèn)題

看上去加載正常的 pdf 文檔 似乎沒(méi)啥大問(wèn)題,來(lái)試試加載 pdf 發(fā)票 看看,但由于實(shí)際發(fā)票敏感信息較多,這里就不貼出原本的發(fā)票內(nèi)容,直接來(lái)看預(yù)覽后的發(fā)票內(nèi)容:

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

注意】無(wú)法顯示完整的內(nèi)容是因?yàn)?nbsp;pdf.js 是需要一些字體庫(kù)的支持,如果 原 PDF 文件 中部分字體沒(méi)有匹配到字體庫(kù)將無(wú)法在 pdf.js 中顯示,而字體庫(kù)存放在 cmaps 文件夾下

另外,預(yù)覽的字體 和 實(shí)際的字體 是 不一致 的,而由于發(fā)票的特殊性,對(duì)字體的一致性是有較大的要求,畢竟如果同一張發(fā)票字體不一致會(huì)缺乏 規(guī)范性 和 合法性(~~被要求字體一致時(shí)的說(shuō)法~~)

常見(jiàn)的解決方案:**解決 pdf.js 無(wú)法完全顯示 pdf 文件內(nèi)容的問(wèn)題**[6],實(shí)際上還是根據(jù)執(zhí)行環(huán)境的錯(cuò)誤信息進(jìn)行分析,需要強(qiáng)行修改源碼內(nèi)容。

PDF 轉(zhuǎn) 圖片 實(shí)現(xiàn)預(yù)覽

這種方式應(yīng)該不用多說(shuō)了,核心是服務(wù)端在響應(yīng) pdf 文件時(shí),先轉(zhuǎn)換成圖片類(lèi)型再返回,前端直接展示具體圖片內(nèi)容即可。

下載

這里的下載實(shí)際不僅指 pdf 的下載,而是客戶(hù)端方面所能支持的下載方式,最常見(jiàn)的如下幾種:

  • a 標(biāo)簽,例如 <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> 實(shí)現(xiàn)下載

<a> 的 download 屬性用于指示瀏覽器 下載 href 指定的 URL,而不是導(dǎo)航到該資源,通常會(huì)提示用戶(hù)將其保存為本地文件,如果 download 屬性有指定內(nèi)容,這個(gè)值就會(huì)在下載保存過(guò)程中作為 預(yù)填充的文件名,主要是因?yàn)槿缦略颍?/p>

  • 這個(gè)值可能會(huì)通過(guò) Javascript 進(jìn)行動(dòng)態(tài)修改
  • 或者 Content-Disposition 中指定的 download 屬性?xún)?yōu)先級(jí)高于 a.download

這種應(yīng)該是大家最熟悉的方式了,但熟悉歸熟悉,還有一些值得注意的點(diǎn):

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

靜態(tài)方式:

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

動(dòng)態(tài)方式:

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

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]);

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

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

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

    document.body.removeChild(link); // 移除隱藏的 a 標(biāo)簽 
    URL.revokeObjectURL(link.href); // 銷(xiāo)毀 url 對(duì)象
  }
復(fù)制代碼

Content-disposition 和 location.href/window.open 實(shí)現(xiàn)下載

這看似是三種下載方式,但實(shí)際上就是一種,而且還是以 Content-disposition 為準(zhǔn)。

Content-Disposition 響應(yīng)頭 指示回復(fù)的內(nèi)容該以何種形式展示,是以 內(nèi)聯(lián) 的形式(即網(wǎng)頁(yè)或頁(yè)面的一部分)展示,還是以 附件 的形式 下載 并保存到本地,如下:

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

因此,基于 location.href='xxx' 和 window.open(xxx) 的方式能實(shí)現(xiàn)下載就是基于 Content-Disposition: attachment; filename="filename.jpg" 的形式,又或者說(shuō)是觸發(fā)了瀏覽器本身的下載行為,滿(mǎn)足了這個(gè)條件,無(wú)論是通過(guò) a 標(biāo)簽跳轉(zhuǎn)location.href 導(dǎo)航window.open 打開(kāi)新頁(yè)面直接在地址欄上輸入 URL 等都可以實(shí)現(xiàn)下載。

H5 移動(dòng)端的下載

H5 移動(dòng)端針對(duì)于 預(yù)覽 操作而言基于以上的方式都是可以實(shí)現(xiàn),但是 下載 操作可就不同了,因?yàn)檫@是要區(qū)分場(chǎng)景:

  • 基于 手機(jī)瀏覽器
  • 基于 微信內(nèi)置瀏覽器

基于 手機(jī)瀏覽器 的下載方式和上述提到的內(nèi)容大致上也是一致的,本質(zhì)上只要所在的客戶(hù)端支持下載那就沒(méi)有問(wèn)題,然而在 微信內(nèi)置瀏覽器 中你使用常規(guī)的下載方式可能達(dá)不到預(yù)期:

  • 在 Android 中使用常規(guī)的下載方式,通常會(huì)彈出對(duì)話(huà)框,詢(xún)問(wèn)你是否需要喚醒 手機(jī)瀏覽器 來(lái)實(shí)現(xiàn)對(duì)應(yīng)資源的下載,部分機(jī)型卻不會(huì)
  • 在 IOS 中以上方式都 無(wú)法實(shí)現(xiàn)下載,因此通常情況下會(huì)打開(kāi)一個(gè)新的 webview 來(lái)提供預(yù)覽,部分機(jī)型在新的頁(yè)面中支持 長(zhǎng)按屏幕 的方式進(jìn)行保存操作,但并不是所有機(jī)型都支持

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

H5 移動(dòng)端的下載還能怎么做?

由于這是 微信內(nèi)置瀏覽器 環(huán)境對(duì)下載功能的屏蔽,因此 不用再考慮(~~想都不敢想~~)基于 微信內(nèi)置瀏覽器 來(lái)實(shí)現(xiàn)下載功能,轉(zhuǎn)而應(yīng)該考慮的是如何實(shí)現(xiàn) 間接下載

  • 判斷當(dāng)前是否是屬于 微信內(nèi)置瀏覽器,若是則幫助用戶(hù)自動(dòng)喚起 手機(jī)瀏覽器 實(shí)現(xiàn)下載,但并不是所有機(jī)型都支持 喚起 操作,因此最好是提示使用用戶(hù)直接通過(guò) 手機(jī)瀏覽器 實(shí)現(xiàn)下載,為了方便用戶(hù),可以實(shí)現(xiàn) 一鍵復(fù)制 的功能進(jìn)行輔助
  • 另一種就直接提示只支持 PC 端下載,放棄對(duì)移動(dòng)端的下載操作

最后

綜上所述,實(shí)際在實(shí)現(xiàn) pdf 預(yù)覽的過(guò)程中可能暫時(shí)沒(méi)有辦法達(dá)到完美的方式,特別是針對(duì)類(lèi)似 發(fā)票類(lèi) 的 pdf 文件,仍存在如下的問(wèn)題:

  • 無(wú)法保證 h5 移動(dòng)端都具備 下載 功能
  • 無(wú)法保證 pdf 預(yù)覽 時(shí),預(yù)覽的字體和實(shí)際發(fā)票 字體 保持一致

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

pdf.js 帶來(lái)相關(guān)問(wèn)題就是如果對(duì)應(yīng)的 pdf 文件中包含了 pdf.js 中不存在的字體,那么就無(wú)法完整渲染,另外渲染出來(lái)的字體和原本的 pdf 文件字體會(huì)存在差異。

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

作者:熊的貓

https://juejin.cn/post/7207078219215732794



該文章在 2023/3/23 16:40:09 編輯過(guò)
關(guān)鍵字查詢(xún)
相關(guān)文章
正在查詢(xún)...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專(zhuān)業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車(chē)隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類(lèi)企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷(xiāo)售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶(hù)的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved