在開發(fā)過(guò)程中要求對(duì) PDF
類型的發(fā)票提供 預(yù)覽 和 下載 功能,**PDF
** 類型文件的來(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í)際文件的 字體保證一致性 9E27229A.gif PDF 預(yù)覽 先拋開上面的各種要求,咱們先總結(jié)下目前實(shí)現(xiàn) PDF
預(yù)覽的幾種常用方式:
借助各種類庫(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í)際展開其內(nèi)層結(jié)構(gòu)后你會(huì)發(fā)現(xiàn):
其內(nèi)部還是 <embed>
標(biāo)簽?這是怎么回事,不是說(shuō)最好不建議使用 <embed>
嗎?
首先來(lái)在 **`caniuse`** [2] 查看兼容情況,如下:
我們?cè)僬乙粋€(gè)不支持 <embed>
的瀏覽器,比如 IE
,來(lái)試試效果:
換成 <iframe>
試試,如下:
顯然,<embed>
在不兼容的環(huán)境直接無(wú)法顯示,而 <iframe>
是能夠正常識(shí)別的,只不過(guò) <iframe>
加載的資源無(wú)法被 IE
瀏覽器處理,即本質(zhì)原因是 IE
瀏覽器根本就不支持對(duì)類似 PDF
等文件的預(yù)覽,比如當(dāng)嘗試直接在地址欄中輸入 http://127.0.0.1:3000/src/assets/2.pdf
時(shí)會(huì)得到:
因此,通常情況下當(dāng)瀏覽器不支持內(nèi)聯(lián) PDF
時(shí),應(yīng)該提供一個(gè) PDF
的回退鏈接,即以下載的方式來(lái)實(shí)現(xiàn),而這就是 **pdfobject** [3] 做的事情,實(shí)際上它的源碼內(nèi)容比較簡(jiǎn)單,核心就是 PDFObject 會(huì)檢測(cè)瀏覽器對(duì)內(nèi)聯(lián)/嵌入 PDF 的支持,如果支持嵌入,則嵌入 PDF,如果瀏覽器不支持嵌入,則不會(huì)嵌入 PDF,并提供一個(gè)指向 PDF 的回退鏈接 ,例如在 IE
中的表現(xiàn):
事實(shí)上,這其實(shí)只是幫我們少寫了一些兼容性的代碼而已,也不一定符合大部分人的場(chǎng)景,在這里提到只是因?yàn)槠渑c <embed>
之間存在的聯(lián)系。
vue3-pdfjs 實(shí)現(xiàn)預(yù)覽 為什么不直接使用 pdfjs-dist
? **pdf.js** [4] 幾個(gè)明顯的可吐槽的點(diǎn):
包名稱不統(tǒng)一,npm
上的包名叫 pdfjs-dist
,然而在 Readme
中自己又稱其為 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)容文字模糊或缺少部分等 因此,既然已經(jīng)有基于 vue/react
封裝好的包,這里就直接用來(lái)作為演示。
具體使用 安裝和使用過(guò)程可參考 **`vue3-pdfjs`** [5] ,具體 Vue3
示例代碼如下:
<script setup lang="ts" >import { onMounted, ref } from 'vue' import { VuePdf, createLoadingTask } from 'vue3-pdfjs/esm' import type { VuePdfPropsType } from 'vue3-pdfjs/components/vue-pdf/vue-pdf-props' // Prop type definitions can also be imported import type { PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api' import pdfUrl from './assets/You-Dont-Know-JS.pdf' const pdfSrc = ref<VuePdfPropsType['src' ]>(pdfUrl)const numOfPages = ref(0 ) onMounted(() => { const loadingTask = createLoadingTask(pdfSrc.value) loadingTask.promise.then((pdf: PDFDocumentProxy ) => { numOfPages.value = pdf.numPages }) }) </script> <template> <VuePdf v-for="page in numOfPages" :key="page" :src="pdfSrc" :page="page" / > </template> <style> @import '@/ assets/base.css'; </style> 復(fù)制代碼
效果如下:
存在問(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)容。
Mozilla Firefox(火狐瀏覽器) Mozilla Firefox 內(nèi)置的 PDF 閱讀器實(shí)際就是 pdf.js
,你可以直接用火狐瀏覽器預(yù)覽一下 pdf
文件,如下:
并且大多基于 pdf.js
二次封裝的庫(kù) vue-pdf、vue3-pdfjs
等在預(yù)覽 pdf
文件的發(fā)票時(shí)通常無(wú)法顯示完整內(nèi)容,需要或多或少的涉及對(duì)源碼的更改,而在 Firefox
中內(nèi)置的 pdf.js
卻能夠完整的顯示對(duì)應(yīng)的 pdf
文件的內(nèi)容。
PDF
轉(zhuǎn) 圖片
實(shí)現(xiàn)預(yù)覽這種方式應(yīng)該不用多說(shuō)了,核心是服務(wù)端在響應(yīng) pdf
文件時(shí),先轉(zhuǎn)換成圖片類型再返回,前端直接展示具體圖片內(nèi)容即可。
具體實(shí)現(xiàn) 下面通過(guò)用 node
來(lái)模擬:
const pdf = require ('pdf-poppler' )const path = require ('path' )const Koa = require ('koa' )const koaStatic = require ('koa-static' )const cors = require ('koa-cors' )const app = new Koa()// 跨域 app.use(cors())// 靜態(tài)資源 app.use(koaStatic('./server' ))function getFileName (filePath ) { return filePath .split('/' ) .pop() .replace(/\.[^/.]+$/ , '' ) }function pdf2png (filePath ) { // 獲取文件名 const fileName = getFileName(filePath); const dir = path.dirname(filePath); // 配置參數(shù) const options = { format : 'png' , out_dir : dir, out_prefix : fileName, page : null , } // pdf 轉(zhuǎn)換 png return pdf .convert(filePath, options) .then((res ) => { console .log('Successfully converted !' ) return `http://127.0.0.1:4000${dir.replace('./server' ,'' )} /${fileName} -1.png` }) .catch((error ) => { console .error(error) }) }// 響應(yīng) app.use(async (ctx) => { if (ctx.path.endsWith('/getPdf' )){ const url = await pdf2png('./server/pdf/2.pdf' ) ctx.body = { url } }else { ctx.body = 'hello world!' } }) app.listen(4000 ) 復(fù)制代碼
避免踩一些坑 坑一:不推薦 pdf-image
在實(shí)現(xiàn)服務(wù)端將 pdf
文件轉(zhuǎn)換成圖片時(shí)需要依賴到一些第三方包,一開始使用了 **`pdf-image`** [7] 這個(gè)包,但在實(shí)際轉(zhuǎn)換時(shí)發(fā)生較多的異常錯(cuò)誤,順著錯(cuò)誤查看源碼后發(fā)現(xiàn)其內(nèi)部需要依賴一些額外的工具,因?yàn)槠渲行枰褂?nbsp;pdfinfo xxx
相關(guān)命令,并且其對(duì)應(yīng)的 `issue` [8] 上也存在著一些類似問(wèn)題,但都試了試最后還是沒(méi)有成功!
因此,更推薦使用 `pdf-poppler` [9] 其中附帶了一個(gè) pdftocairo
的程序可以實(shí)現(xiàn) pdf
到 圖片 的轉(zhuǎn)換能力,不過(guò)它目前版本支持 Windows 和 Mac OS ,如下:
坑二:path.basename not a function
在上述的代碼內(nèi)容中需要獲取文件的名稱,實(shí)際上我們可以簡(jiǎn)單直接的使用 Node Api
中 path.basename(path[, suffix])
來(lái)達(dá)到目的:
但是在程序運(yùn)行時(shí)發(fā)生了如下 異常 ,對(duì)應(yīng)的 代碼內(nèi)容 和 運(yùn)行結(jié)果 如下:
// 配置參數(shù) const options = { format : 'png' , out_dir : dir, out_prefix : path.baseName(filePath, path.extname(filePath)), // 發(fā)生異常 page : null , } 復(fù)制代碼
這個(gè)暫時(shí)沒(méi)有找到是什么原因,只能自己簡(jiǎn)單實(shí)現(xiàn)了一個(gè) getFileName
方法用于獲取文件的名稱。
報(bào)錯(cuò)原因 :太依賴編輯器的自動(dòng)提示,將 basename 輸出成 baseName ,沒(méi)錯(cuò)就是 n 和 N 的區(qū)別.
坑三:細(xì)節(jié)
上述內(nèi)容通過(guò) koa
啟動(dòng)模擬業(yè)務(wù)服務(wù),由于 業(yè)務(wù)服務(wù)(http://127.0.0.1:4000
) 和 應(yīng)用服務(wù) (http://127.0.0.1:3000
) 間的端口不一致,因此會(huì)產(chǎn)生 跨域 ,此時(shí)可以通過(guò) koa-cors
來(lái)解決,值得注意的是有時(shí)候的那個(gè)業(yè)務(wù)服務(wù)器重啟時(shí) koa-cors
可能不起作用。
由于響應(yīng)的內(nèi)容直接在 koa
通用中間件中返回,因此,如果你需要支持業(yè)務(wù)服務(wù)提供 靜態(tài)資源 的訪問(wèn)能力,就可以通過(guò) koa-static
來(lái)實(shí)現(xiàn),值得注意的是,當(dāng)你通過(guò) koa-static
指定靜態(tài)文件資源后,如 **app.use(koaStatic('./static'))
**,此時(shí)如果你直接通過(guò) http://127.0.0.1:4000/static/pdf/xxx.png
時(shí),那么會(huì)得到 404 Not Found 的錯(cuò)誤,原因在于 koa-static
是直接把 /static/ 設(shè)置成了 根路徑 ,因此正確的訪問(wèn)路徑為:http://127.0.0.1:4000/pdf/xxx.png
。
效果演示 發(fā)票內(nèi)容不方便展示這里就不直接展示了,只需要關(guān)注生成的圖片和路徑即可:
PDF 下載 這里的下載實(shí)際不僅指 pdf
的下載,而是客戶端方面所能支持的下載方式,最常見(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ì)提示用戶將其保存為本地文件,如果 download
屬性有指定內(nèi)容,這個(gè)值就會(huì)在下載保存過(guò)程中作為 預(yù)填充的文件名 ,主要是因?yàn)槿缦略颍?/p>
這個(gè)值可能會(huì)通過(guò) Javascript
進(jìn)行動(dòng)態(tài)修改 或者 Content-Disposition
中指定的 download
屬性優(yōu)先級(jí)高于 a.download
這種應(yīng)該是大家最熟悉的方式了,但熟悉歸熟悉,還有一些值得注意的點(diǎn):
同源 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 ]); // 文件類型 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); // 銷毀 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ì)話框,并將 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ā)了瀏覽器本身的下載行為,滿足了這個(gè)條件,無(wú)論是通過(guò) a
標(biāo)簽跳轉(zhuǎn) 、location.href 導(dǎo)航 、window.open 打開新頁(yè)面 、直接在地址欄上輸入 URL 等都可以實(shí)現(xiàn)下載。
H5 移動(dòng)端的下載 H5
移動(dòng)端針對(duì)于 預(yù)覽 操作而言基于以上的方式都是可以實(shí)現(xiàn),但是 下載 操作可就不同了,因?yàn)檫@是要區(qū)分場(chǎng)景:
基于 手機(jī)瀏覽器 的下載方式和上述提到的內(nèi)容大致上也是一致的,本質(zhì)上只要所在的客戶端支持下載那就沒(méi)有問(wèn)題,然而在 微信內(nèi)置瀏覽器 中你使用常規(guī)的下載方式可能達(dá)不到預(yù)期:
在 Android
中使用常規(guī)的下載方式,通常會(huì)彈出對(duì)話框,詢問(wèn)你是否需要喚醒 手機(jī)瀏覽器 來(lái)實(shí)現(xiàn)對(duì)應(yīng)資源的下載,部分機(jī)型卻不會(huì) 在 IOS
中以上方式都 無(wú)法實(shí)現(xiàn)下載 ,因此通常情況下會(huì)打開一個(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)置瀏覽器 ,若是則幫助用戶自動(dòng)喚起 手機(jī)瀏覽器 實(shí)現(xiàn)下載,但并不是所有機(jī)型都支持 喚起 操作,因此最好是提示使用用戶直接通過(guò) 手機(jī)瀏覽器 實(shí)現(xiàn)下載,為了方便用戶,可以實(shí)現(xiàn) 一鍵復(fù)制 的功能進(jìn)行輔助 另一種就直接提示只支持 PC
端下載 ,放棄對(duì)移動(dòng)端的下載操作 BD272F44.gif 最后 綜上所述,實(shí)際在實(shí)現(xiàn) pdf
預(yù)覽的過(guò)程中可能暫時(shí)沒(méi)有辦法達(dá)到完美的方式,特別是針對(duì)類似 發(fā)票類 的 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)查看源文件。
實(shí)現(xiàn)不了產(chǎn)品的要求怎么辦?
例如上述探討的方案其實(shí)無(wú)法滿足文章開頭提到的部分要求。產(chǎn)品提出需求的目的也是為了提供更好的用戶體驗(yàn)(~~正常情況下
),但是這些要求仍然要落實(shí)到技術(shù)上,而技術(shù)支持程度如何需要我們及時(shí)反饋( 除非你的產(chǎn)品是技術(shù)經(jīng)驗(yàn)
~~),因此作為開發(fā)者你需要提供充足的內(nèi)容向產(chǎn)品證明,然后自己再給出一些間接實(shí)現(xiàn)的方案(又或者產(chǎn)品自己就給出新的方案
),看是否符合 第二預(yù)期 ,核心就是 合理溝通 + 其他方案 (每個(gè)人的處境不同,實(shí)際情況也許 ... 懂得都懂
)。
9E38506E.jpg 以上是個(gè)人的一些看法和理解,有不當(dāng)之處,可以在評(píng)論區(qū)指正?。?!
希望本文對(duì)你有所幫助?。。?/strong>