Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
接上代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>分頁功能</title>
<script src="js/jquery-1.11.0.js"></script>
<style>
*{margin:0;padding:0}
#paging{
width:100%;
height:auto;
margin-top:100px;
}
/* 內(nèi)容樣式 */
#paging .paging-content{
width:100%;
height:auto;
text-align: center;
}
#paging .paging-content .paging-content-text{
width:100%;
height:50px;
line-height:50px;
}
/*分頁樣式*/
#paging .paging-footer{
width:100%;
height:50px;
line-height:50px;
font-size:20px;
color:#fff;
background-color:#d3d3d3;
}
#paging .paging-footer .paging-footer-content{
text-align:center;
margin:auto;
}
#paging .paging-footer-content .content-text{
display:inline-block;
margin-right:15px;
}
#paging .paging-footer-content .content-text button{
color:#fff;
font-size:20px;
background-color: #d3d3d3;
outline: none;
border:none;
cursor:pointer;
}
#paging .paging-footer-content .content-text button:hover{
color:#2d8cf0;
}
#paging .paging-footer-content .content-text .upward{
cursor:no-drop;
}
#paging .paging-footer-content .content-text span{
color:#d3d3d3;
font-size:16px;
background-color:#fff;
border-radius:8px;
padding:1px 15px;
margin:0 10px;
}
#paging .paging-footer-content .content-text input{
width:30px;
font-size:16px;
background-color:#fff;
border-radius:8px;
padding:1px 15px;
margin:0 10px;
outline: none;
border:none;
}
</style>
</head>
<body>
<div id="paging">
<div class="paging-content">
</div>
<div class="paging-footer">
<div class="paging-footer-content">
<div class="content-text">
<button class="upward" disabled="disabled">上一頁</button>
</div>
<div class="content-text">
第<span class="nowPage">1</span>頁
</div>
<div class="content-text">
<button class="down">下一頁</button>
</div>
<div class="content-text">
跳轉(zhuǎn)<input class="pageVal" type="text" value="1" onKeyUp="value=value.replace(/\D/g,'')"/>頁
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(function(){
//頁面加載初始化數(shù)據(jù)
var page=1; //請求頁數(shù)
var pageSize=10; //每頁返回數(shù)據(jù)條數(shù)
getData();
function getData(){
$.ajax({
type : 'GET',
url : 'datas.json',
data : {
page : page,
pageSize : pageSize
},
dataType: "json",
success: function(data){
//var res=data.data;
console.log(data);
$('#paging .paging-content').html(''); //每次請求清空原來的數(shù)據(jù)
$.each(res,function(index,res){
pagingContent=
'<div class="paging-content-text">'+res.bumenname+'</div>'
//將請求的數(shù)據(jù)append到頁面上
$('#paging .paging-content').append(pagingContent);
})
}
});
}
//分頁
var upwardPage=$(' #paging .content-text .upward'); //上一頁
var downPage=$(' #paging .content-text .down'); //下一頁
var nowPage=$(' #paging .content-text .nowPage'); //當前頁數(shù)
var nowPageNum=Number(nowPage.html());
var pageVal=$(' #paging .content-text .pageVal'); //跳轉(zhuǎn)頁數(shù)
//上一頁
upwardPage.click(function(){
nowPageNum=page;
if(nowPageNum > 1){
nowPageNum --;
}else{
nowPageNum==1;
}
if(nowPageNum==1){
upwardPage.attr('disabled',true);
upwardPage.css('cursor','no-drop');
nowPage.html(1);
pageVal.val(1);
page=nowPageNum;
getData();
}
else{
nowPage.html(nowPageNum);
pageVal.val(nowPageNum);
page=nowPageNum;
getData();
}
})
//下一頁
downPage.click(function(){
nowPageNum=page;
upwardPage.attr('disabled',false);
upwardPage.css('cursor','pointer');
nowPageNum++;
nowPage.html(nowPageNum);
pageVal.val(nowPageNum);
page=nowPageNum;
getData();
})
//跳轉(zhuǎn)
//失去焦點
pageVal.blur(function(){
var pageval=Number(pageVal.val());
var nowpage=Number(nowPage.html());
//如果失去焦點前后的值一樣,不進行數(shù)據(jù)請求
if(pageval !=nowpage){
//小于1跳轉(zhuǎn)第一頁
if(pageval > 1){
upwardPage.attr('disabled',false);
upwardPage.css('cursor','pointer');
nowPage.html(pageval);
page=pageval;
getData();
}else{
upwardPage.attr('disabled',true);
upwardPage.css('cursor','no-drop');
nowPage.html(1);
pageVal.val(1);
page=1;
getData();
}
}
})
//回車
pageVal.keyup(function(event){
if(event.keyCode==13){
var pageval=Number(pageVal.val());
var nowpage=Number(nowPage.html());
//如果失去焦點前后的值一樣,不進行數(shù)據(jù)請求
if(pageval !=nowpage){
//小于1跳轉(zhuǎn)第一頁
if(pageval > 1){
upwardPage.attr('disabled',false);
upwardPage.css('cursor','pointer');
nowPage.html(pageval);
page=pageval;
getData();
}else{
upwardPage.attr('disabled',true);
upwardPage.css('cursor','no-drop');
nowPage.html(1);
pageVal.val(1);
page=1;
getData();
}
}
}
})
})
</script>
</body>
</html>
效果圖:(內(nèi)容倒是后換個地址就可以顯示)
水平有限,歡迎指教。
在Android應用程序中渲染HTML并實現(xiàn)分頁,可以使用WebView組件。WebView是Android提供的用于顯示W(wǎng)eb內(nèi)容的控件,可以加載HTML頁面并進行渲染。下面是一個簡單的示例代碼,展示了如何在Android應用程序中使用WebView實現(xiàn)HTML渲染和分頁功能:
在上述代碼中,我們首先在布局文件(activity_main.xml)中添加一個WebView組件。然后,在MainActivity中,我們獲取WebView實例,并對其進行一些設(shè)置,例如啟用JavaScript和加載HTML頁面。
HTML頁面可以通過loadUrl方法加載。在示例中,我們加載的是file:///android_asset/page.html,它假設(shè)HTML文件存儲在應用的assets目錄中。
為了在WebView中實現(xiàn)分頁功能,可以在HTML頁面中使用CSS的分頁屬性。例如,在CSS中可以設(shè)置-webkit-column-count屬性來指定列數(shù),從而實現(xiàn)分頁效果。具體的CSS設(shè)置可以根據(jù)實際需求進行調(diào)整。
通過以上代碼,Android應用程序?qū)⒓虞d并渲染HTML頁面,并在WebView中顯示。如果HTML頁面包含分頁屬性,將會自動分頁顯示內(nèi)容。
需要注意的是,為了能夠加載本地的HTML文件,需要在AndroidManifest.xml文件中添加適當?shù)臋?quán)限,例如:
你可以根據(jù)自己的需求對WebView進行更多的定制和擴展,以實現(xiàn)更復雜的HTML渲染和分頁功能。
言
目前,教學、教研各種內(nèi)容線上沉淀、展示豐富多彩,但線上內(nèi)容“線下化”能力不足或過分依賴人力,比如,線上練習題組卷后以PDF形式分發(fā)給學生,家長希望將考試、練習題目打印后,學生帶到學校去做(高中生使用手機等電子設(shè)備的時間有限),線上各類分析報告以PDF形式分享給學生/家長等。
從業(yè)務方面看,不同業(yè)務線的多個業(yè)務場景都有輸出PDF的訴求,如果各業(yè)務線自己設(shè)計、實現(xiàn)符合自身業(yè)務場景的具體方案,除調(diào)研、開發(fā)工作量較大之外,還會有重復調(diào)研,踩坑的情況。
從技術(shù)角度看,線上內(nèi)容轉(zhuǎn)PDF的內(nèi)容源頭來自于H5富文本內(nèi)容,業(yè)界內(nèi)以此為基礎(chǔ)的PDF生成方案多種多樣,也各有優(yōu)劣,比如:
方案對比-表格-1
因此,我們綜合了各種PDF生成方案并總結(jié)了在探索講義生成PDF過程中的經(jīng)驗,抽象出了一套通用的,可復用的能力供各業(yè)務線快速利用,基本方案和優(yōu)劣如下:
最終方案-表格-2
目 標
旨在提供一套以H5為載體的PDF通用生成方案,這套方案有如下特點:
這套方案可分為兩個核心部分,頁面展示側(cè) - Medusa,PDF生成側(cè) - Hydra
頁面展示側(cè) - Medusa
我們頁面展示側(cè)的通用能力——Medusa,是基于Paged.js的二次封裝,并以NPM包形式提供給業(yè)務方使用。Medusa可對任何HTML進行分頁、并根據(jù)配置添加頁眉、頁腳等,最終將處理后的HTML渲染到頁面中。Medusa封裝并簡化了對PDF格式的配置,可覆蓋絕大多數(shù)業(yè)務場景,使得各業(yè)務場景將更多精力投入其自身業(yè)務邏輯的開發(fā)。
之所以選擇Pagedjs為基礎(chǔ)開發(fā)我們自己的SDK,是因為它是目前我們能找到的唯一開源的、具有HTML內(nèi)容分頁,樣式處理的前端庫,同時我們也在講義中經(jīng)過了長期的摸索與沉淀。
接下來將詳細介紹Paged.js原理、Medusa支持的功能與使用方法。
一 Paged.js是如何工作的
Paged.js包含了 3 個大模塊
這里將主要介紹 Previewer 和 Chunker,因為我們的二次開發(fā)和維護不涉及到Polisher。
Previewer
Previewer 的工作非常簡單,但我們會主要利用它封裝我們的Medusa,初始化一個Previewer對象,Previewer初始化了Chunker和Polisher對象:
Medusa-代碼-1
再調(diào)用Previewer的preview()方法,preview()方法做了兩件事:
Medusa-代碼-2
當chunker.flow結(jié)束,即可在瀏覽器看到整個頁面處理完之后的樣子。
Chunker
首先,Chunker解析、預處理需要分頁的HTML,為其添加一些必要的屬性
Medusa-代碼-3
然后創(chuàng)建容納所有頁(pages)的容器,并掛載到renderTo容器下(默認Body),以備組織后續(xù)的所有頁:
Medusa-代碼-4
接著,chunker創(chuàng)建了一個page模版,以便增加頁面使用:
Medusa-代碼-5
其中,TEMPLATE是Pagedjs內(nèi)部創(chuàng)建頁面時所使用的基礎(chǔ)模版。
Medusa-代碼-6
接下來,chunker進入了渲染+分頁過程(這個過程我們不會在二次開發(fā)中做修改,但需要了解其基本思路以便在出問題時能有解決思路),這個過程在循環(huán)一個迭代器(*layout),迭代器一直在做3件事:
原則:
尋找overflow時會將盡可能多的內(nèi)容節(jié)點插入內(nèi)容區(qū)域,這里,“盡可能多”分為幾種情況,比如:
步驟:
Pagedjs遵循了如下步驟去尋找overflow:
兩個前置條件:
i. 從需要處理的內(nèi)容第一個節(jié)點開始,判斷是否 node.left >=contentArea.right || node.top >=contentArea.bottom
Medusa-代碼-7
ii.如果不滿足,則判斷 node.right <=contentArea.right && node.bottom <=contentArea.bottom
Medusa-代碼-8
iii.如果不滿足,那說明有子節(jié)點overflow了,則繼續(xù)深入其子節(jié)點查找即可。
3.使用模版添加新的頁面,并從BreakToken處繼續(xù)上述動作。
二 Medusa支持的功能及使用方法
基于Paged.js,Medusa支持了如下功能,并為業(yè)務方提供了更加簡潔、定制化的配置。
下方是調(diào)用Medusa的代碼示例:
Medusa-代碼-9
1.1 動態(tài)頁面分頁能力
Medusa核心功能,可將連續(xù)的HTML頁面轉(zhuǎn)化成一頁頁PDF樣式的HTML。
1.2 單頁模版配置 -> 生成能力
通過Grid布局,Paged.js將一個單頁模版分為多個區(qū)域,整體分為2個大的部分:
業(yè)務方通過簡單的配置,即可還原UI設(shè)計稿中的PDF樣式,例子如下圖:
1.2.1 base
頁面基礎(chǔ)配置是對每頁的。支持紙型或頁面寬高、內(nèi)容區(qū)域margin、padding、背景及水印的設(shè)置。
在封裝Medusa時,Medusa將讀取傳入的頁面模版配置、靜態(tài)頁內(nèi)容配置,并將樣式上的配置解析并轉(zhuǎn)化為Previewer可理解的樣式內(nèi)容,比如頁面寬高的設(shè)置:
Medusa-代碼-10
將被轉(zhuǎn)化為:
Medusa-代碼-11
1.2.2 surround
2. 目前支持3種類型的surround item:
example:
Medusa-代碼-12
1.3 前/后置靜態(tài)頁面
業(yè)務方可通過如下方式配置靜態(tài)頁面的具體內(nèi)容:
Medusa-代碼-13
其中,傳入的React JSX Element將會被這樣處理:
Medusa-代碼-14
處理完成后,將HTML String拼接到頁面模版中,再插入分頁后內(nèi)容的前后。
PDF生成側(cè) - Hydra:
頁面展示側(cè)為PDF生成做好了頁面的準備,對于PDF生成側(cè),需要做的工作就更純粹了,業(yè)務方除了請求生成PDF,定期檢查PDF生成的進度,無需做任何額外工作。
1.整體流程:
PDF生成是CPU和內(nèi)存密集型的,由于頁面內(nèi)容的不確定性,也意味著頁面渲染時間與生成PDF的時間都是不確定的,因此整體PDF生成的鏈路被設(shè)計成是異步的,如下圖:
整體流程上,業(yè)務方在請求生成PDF時,會先在后端做一條記錄,后端再將任務發(fā)送給Node服務,即Hydra;
在生成PDF時, 第 1 步是做頁面上的準備,一個生成任務可能有多個URL頁面需要生成PDF,所以我們預先啟動對應URL數(shù)量的PPTR Page,頁面都啟動完成后,進入下一步;
第 2 步:渲染頁面,這個過程中,如果請求是包含多個URL的,這些頁面會同步渲染,在所有頁面渲染完成后,進入下一步。
第 2.5 步,如果是需要生成連續(xù)頁碼的一整個PDF,還會做額外的一個動作:頁碼矯正,通過頁碼矯正,可以將同步渲染的每個頁面,按照其之前頁面的頁碼數(shù)修正,以保證整體PDF的頁碼的連貫。
第 3 步,通過PPTR Page的能力將頁面轉(zhuǎn)換為PDF buffer,如有必要,再將生成的PDF buffer拼接到一起生成一整個PDF,或者將每個PDF buffer都生成一個PDF,壓縮成zip文件。
第 4 步,文件上傳OSS,最終返回OSS CDN鏈接。
2.請求生成PDF:
業(yè)務側(cè)請求將對應頁面生成PDF的時,只需傳入如下字段:
Hydra-代碼-1
3.PDF生成過程:
正如在整體流程中所述,PDF生成側(cè),我們借助 PPTR 的能力打開頁面并生成PDF流。
在頁面調(diào)用 Medusa 分頁、組裝能力時,所有內(nèi)容分頁組裝完成后會向body中插入了一個額外的DOM以標識該頁面處理完成:
Hydra-代碼-2
這是為了 Hydra 感知頁面渲染完成所做的準備,當生成服務的 PPTR 等到該DOM出現(xiàn)時,則表示頁面成功渲染并處理完成了:
Hydra-代碼-3
此后,在上面已經(jīng)提到過,對于需要將多個頁面生成的PDF拼接成一個PDF的情況,在生成PDF之前需要做一個重要的動作,即頁碼矯正,原因如下:
并且我們不希望頁面的處理是串行的,因為串行勢必導致速度較慢,生成時間長。
這個問題的解決方案如下:
1. 對于每個頁面都啟用一個page,并同時處理
2. 每個頁面處理完成后(pdfLastDOM出現(xiàn)),通過Page.$eval()來統(tǒng)計頁數(shù)并記錄:
Hydra-代碼-4
3. 計算出頁面中分頁之后每一個頁面的起始頁碼,以及所有頁面的頁碼總和
4. 再修改頁碼容器樣式的 counterReset 值即可,其后續(xù)頁碼可自遞增。
Hydra-代碼-5
5. 之后,再通過 Medusa 在頁面window對象中Polyfill的相關(guān)配置,比如需要生成的PDF的單頁寬、高以生成PDF流。
Hydra-代碼-6
6. 最后如有必要,通過pdf-lib拼接這些 pdfBuffer 即可。
Hydra-代碼-7
7. PDF生成完成后,上傳OSS并返回URL鏈接
4.性能、穩(wěn)定性保證:
在整體方案落地前,我們對服務進行了多次性能測試:
以下載題目為例,在4個容器,每個容器 3C 12G 的配置下的并行處理能力如下:
對于 20 道題目,每個PDF生成任務在 15 頁左右,平均 1 分鐘內(nèi)能完成 280 個任務的處理。
對于 40 道題目,每個PDF生成任務在 30 頁左右,平均 1 分鐘內(nèi)能完成 105 個任務的處理。
對于 60 到題目,每個PDF生成任務在 40 頁左右,平均 1 分鐘內(nèi)能完成 54 個任務的處理。
同時,根據(jù) Hydra 服務的整體的處理能力,后端通過任務隊列的形式幫助我們保證服務不被瞬間的突刺流量擊垮。
已接入/正在接入的相關(guān)業(yè)務線及場景:
目前,公司有 5 大業(yè)務線,8 個場景已經(jīng)完全接入我們的能力用于 H5 轉(zhuǎn) PDF,如下是錯題本、內(nèi)容資料庫接入后生成的PDF樣例:
錯題本:
內(nèi)容資料庫試卷:
未來展望
目前整體的PDF生成方案已經(jīng)能夠滿足大多數(shù)場景和內(nèi)容,但依然有可改進空間。
HTML的流式布局要求我們必須手動的對內(nèi)容分頁,才能添加頁眉,頁腳等(即Mdusa做的工作),正因為如此,在處理復雜的內(nèi)容時,可能會出現(xiàn)一些問題:比如,遇到復雜表格時,由于表格可能會有多種多樣的行、列合并,同時表格單元格內(nèi)的內(nèi)容也可以多種多樣,在分頁過程中,Medusa內(nèi)部的PagedJS并不能完美的處理對于長、且復雜的表格的分割,因此可能遇到分割后表格單元格缺失、錯亂或?qū)捀咤e誤的問題,這些問題在講義中體現(xiàn)較明顯。
我們?nèi)栽诔掷m(xù)關(guān)注與研究復雜DOM內(nèi)容的分割問題,會嘗試加以優(yōu)化和改進PagedJS的能力,同時,我們也以另外一種思路設(shè)計了自己的DOM分頁器方案,但經(jīng)過評估,由于實現(xiàn)比較復雜,成本較高,暫時沒有投入開發(fā)資源。
不過,我們相信,未來我們一定能以更完美的方式分割DOM以生成更高質(zhì)量的PDF。
作者:高源、陳欣博
來源:微信公眾號:高途技術(shù)
出處:https://mp.weixin.qq.com/s/c_N7jdNklrNFKR_Cub2Tgg
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。