上一章節中,我們為wordpress網站的首頁添加了全站文章列表。一個wordpress網站不可能只有幾篇文章,可能會有成千上萬的文章,作為一個wordpress博客主題模板,一般情況下,不可能讓所有的文章在一個頁面一下子全部顯示出來,如果真這樣,網頁的負載會非常大。正確的處理方式,是先顯示最前面的十幾篇或二十幾篇文章,然后,“點擊一個分頁按鈕”或者“鼠標向下滾動觸發一個事件”后,再顯示后面的十幾篇或二十幾篇文章,以此類推。本節,我們來介紹“如何給wordpress網站的文章列表添加分頁效果”,一起來看看吧。
在wordpress網站模板的functions.php文件中添加如下代碼:
//分頁函數function pages($query_string){
global $posts_per_page, $paged; //全局變量
$my_query = new WP_Query($query_string ."&posts_per_page=-1"); //創建查詢對象
$total_posts = $my_query->post_count; //通過查詢對象獲取文章總數
if(empty($paged)) $paged = 1; //當前頁碼數,如是$paged為空,就讓
$paged=1$prev = $paged - 1; //上一頁變量
$next = $paged + 1; //下一頁變量
$range = 4; //
$showitems = ($range * 2)+1; //顯示多少個分頁頁碼按鈕
$pages = ceil($total_posts/$posts_per_page); //獲取總頁數
if(1 != $pages){
echo "<div class='pagination'>";
echo ($paged > 2 && $paged+$range+1 > $pages && $showitems < $pages)? "<a href='".get_pagenum_link(1)."'>最前</a>":"";
echo ($paged > 1 && $showitems < $pages)? "<a href='".get_pagenum_link($prev)."'>上一頁</a>":"";
for ($i=1; $i <= $pages; $i++){
if (1 != $pages &&( !($i >= $paged+$range+1 || $i <= $paged-$range-1) || $pages <= $showitems )){
echo ($paged == $i)? "<span class='current'>".$i."</span>":"<a href='".get_pagenum_link($i)."' class='inactive' >".$i."</a>";
}
}
echo ($paged < $pages && $showitems < $pages) ? "<a href='".get_pagenum_link($next)."'>下一頁</a>" :"";
echo ($paged < $pages-1 && $paged+$range-1 < $pages && $showitems < $pages) ? "<a href='".get_pagenum_link($pages)."'>最后</a>":"";echo "</div>\n";
}
}
然后,在wordpress網站前臺模板的文章列表的代碼下方調用這個分頁函數,代碼如下:
<?php pages($query_string); //列表分頁 ?>
然后,我們就可以在wordpress網站的文章列表下方看到分頁按鈕。如果想要樣式好看一點,可以對分頁按鈕添加CSS樣式,來進行美化一下。
為了更方便wordpress網站模板開發者開發模板主題,wordpress從4.0版本開始,就提供了一個分頁函數the_posts_pagination(),通過這個函數,我們同樣可能實現wordpress網站的文章列表的分頁效果。代碼如下:
the_posts_pagination( array('mid_size' => 3, //當前頁碼數的 兩邊 顯示幾個頁碼。'prev_text' =>'<', //上一頁'next_text' =>'>', //下一南) );
這個函數跟wordpress其wp_list_pages()等函數類似,有一個參數,這個參數可是一個數組類型(如上面的代碼,參數就是數組類型),也可以是一個字符串類型,上面的的代碼的參數修改成字符串類型后,如下:
the_posts_pagination("mid_size=3&prev_text=<&next_text=>");
是使用字符串類型,還是使用數組類型,主要根據開發者的個人愛好,我個人還是比較喜歡用數組類型,看起來比較清楚。
在wordpress網站模板開發中,使用上面任何一種方式來添加文章列表的分頁效果都可以,不過,我個人還是建議使用wordpress自帶的函數the_posts_pagination(),對開發更省事,效率更高,而且是wordpress自己的函數,后期都不需要維護。如果想讓分頁按鈕布局更加個性化,可以考第一種方式。
如果還有什么不太明白的,或者你想了解什么,可以在下面評論中給我留言。
昨天介紹了Oracle分頁實現方案,那么,mysql又是如何實現分頁呢?
參考官網:https://dev.mysql.com/doc/refman/5.7/en/select.html
MySQL中實現分頁查詢:在數據量較小的情況下可使用limit查詢來實現分頁查詢,在數據量大的情況下使用建立主鍵或唯一索引來實現,另外可通過order by對其排序。
The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants, with these exceptions:
先看一下limit語法
SELECT * FROM TABLE
[ORDER BY {col_name | expr | position}
[ASC | DESC], ... [WITH ROLLUP]]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
LIMIT子句可以被用于強制 SELECT 語句返回指定的記錄數。LIMIT接受一個或兩個數字參數。參數必須是一個整數常量。
如果給定兩個參數,第一個參數指定第一個返回記錄行的偏移量,第二個參數指定返回記錄行的最大數目。初始記錄行的偏移量是 0(而不是 1)。
1.1、傳統實現方式
一般情況下,客戶端通過傳遞 pageNo(頁碼)、pageSize(每頁條數)兩個參數去分頁查詢數據庫中的數據,在數據量較小(元組百/千級)時使用 MySQL自帶的 limit 來解決這個問題
--pageNo:頁碼
--pagesize:每頁顯示的條數
select * from table limit (pageNo-1)*pageSize,pageSize;
1.2、建立主鍵或者唯一索引(高效)
在數據量較小的時候簡單的使用 limit 進行數據分頁在性能上面不會有明顯的緩慢,但是數據量達到了 萬級到百萬級 sql語句的性能將會影響數據的返回。這時需要利用主鍵或者唯一索引進行數據分頁;
--pageNo:頁碼
--pagesize:每頁顯示的條數
--假設主鍵或者唯一索引為 t_id
select * from table where t_id > (pageNo-1)*pageSize limit pageSize;
1.3、基于數據再排序
當需要返回的信息為順序或者倒序時,對上面的語句基于數據再排序。order by ASC/DESC 順序或倒序 默認為順序
select * from table where t_id > (pageNo-1)*pageSize order by t_id limit pageSize;
Oracle中有專門的rownum()顯示行號的函數,而MySQL沒有專門的顯示行號函數,但可以通過用@rownum自定義變量顯示行號。
一般實現過程如下:
SELECT
(@rownum := @rownum + 1) AS rownum,
t.*
FROM
table t,
(SELECT @rownum := 0) AS rn
3.1、環境準備
CREATE TABLE t (
EMPNO BIGINT ( 4 ) NOT NULL,
ENAME VARCHAR ( 10 ),
JOB VARCHAR ( 9 ),
MGR BIGINT ( 4 ),
HIREDATE date,
SAL BIGINT ( 10 ),
COMM BIGINT ( 10 ),
DEPTNO BIGINT ( 2 ),
PRIMARY KEY ( `EMPNO` )
) ENGINE = INNODB;
INSERT INTO t VALUES ('7369', 'SMITH', 'CLERK', '7902', '1980-12-17', '800', NULL, '20');
INSERT INTO t VALUES ('7499', 'ALLEN', 'SALESMAN', '7698', '1981-02-20', '1600', '300', '30');
INSERT INTO t VALUES ('7521', 'WARD', 'SALESMAN', '7698', '1981-02-22', '1250', '500', '30');
INSERT INTO t VALUES ('7566', 'JONES', 'MANAGER', '7839', '1981-04-02', '2975', NULL, '20');
INSERT INTO t VALUES ('7654', 'MARTIN', 'SALESMAN', '7698', '1981-09-28', '1250', '1400', '30');
INSERT INTO t VALUES ('7698', 'BLAKE', 'MANAGER', '7839', '1981-05-01', '2850', NULL, '30');
INSERT INTO t VALUES ('7782', 'CLARK', 'MANAGER', '7839', '1981-06-09', '2450', NULL, '10');
INSERT INTO t VALUES ('7788', 'SCOTT', 'ANALYST', '7566', '1987-04-19', '3000', NULL, '20');
INSERT INTO t VALUES ('7839', 'KING', 'PRESIDENT', NULL, '1981-11-17', '5000', NULL, '10');
INSERT INTO t VALUES ('7844', 'TURNER', 'SALESMAN', '7698', '1981-09-08', '1500', '0', '30');
INSERT INTO t VALUES ('7876', 'ADAMS', 'CLERK', '7788', '1987-05-23', '1100', NULL, '20');
INSERT INTO t VALUES ('7900', 'JAMES', 'CLERK', '7698', '1981-12-03', '950', NULL, '30');
INSERT INTO t VALUES ('7902', 'FORD', 'ANALYST', '7566', '1981-12-03', '3000', NULL, '20');
INSERT INTO t VALUES ('7934', 'MILLER', 'CLERK', '7782', '1982-01-23', '1300', NULL, '10');
commit;
3.2、limit分頁
--查詢第一頁,每頁顯示5條數據
select * from t order by empno desc limit (1-1)*5,5;
--查詢第二頁,每頁顯示4條數據
select * from t order by empno desc limit (2-1)*4,4;
3.3、查詢顯示行號
--查詢第二頁,每頁顯示4條數據,并在第一列加上行號
select (@rownum := @rownum + 1) AS rownum,t.* from t,
(SELECT @rownum := 0) AS rn
order by t.empno desc
limit 4,4;
覺得有用的朋友多幫忙轉發哦!后面會分享更多devops和DBA方面的內容,感興趣的朋友可以關注下~
最近在業務上遇到了一個問題是要將頁面打印輸出成pdf文件,通過點擊一個按鈕,就能夠將頁面寫在一個pdf上,并下載下來,需要保證pdf的內容具有很好的可讀性。
經評估要實現這個需求,一種可行的方案是將HTML頁面轉為PDF,并實現下載。通過技術調研,最終的方案確定為通過html2canvas + jspdf這兩個庫來實現,通過使用html2canvas提供的方法,將頁面元素轉為base64圖片流,然后將其插入jspdf插件中,實現保存并下載pdf。
html2canvas + jspdf方案是前端實現頁面打印的一種常用方案,但是在實踐過程中,遇到的最大問題就是分頁截斷的問題:當頁面元素超過一頁A4紙的時候,連續的頁面就會因為分頁而導致內容被截斷,進而影響了pdf的可讀性。
由于網上關于分頁截斷的解決思路比較少,所以特意將此次的解決方案記錄下來。
首先,我們開始使用 JSPDF 和 html2canvas 生成一個簡單的 PDF文件。
創建一個 JSPDF 實例,設置頁面的大小、方向和其他參數。參考官網可以寫一個很簡單的實例
var doc = new jsPDF({
orientation: 'landscape',
unit: 'in',
format: [4, 2]
}
doc.text('Hello world!', 1, 1)
doc.save('two-by-four.pdf')
生成一個pdf文件,并且在文件中寫入一定內容,其實JSPDF這個庫就能做到。
但是很多業務場景下,我們的目標內容會更復雜,而且還要考慮樣式,所以最好的方式是引入html2canvas這個庫,將頁面元素轉換成base64數據,然后貼在pdf中(使用addImage方法),這樣就能保證頁面的內容。
引入了html2canvas庫后,我們更多關注是利用現成組件庫、框架或者原生html和css實現更復雜的頁面內容。
使用 html2canvas 捕捉 HTML 內容或特定的 HTML 元素,并將其轉換為 Canvas。其中,html2canvas 函數的主要用法是:
html2canvas(element, options);
以下是一些常見的配置選項:
下面是一個簡單的demo,可以看到html2canvas能夠將dom元素轉化為一張base64圖片,將鼠標選中元素,可以感受到圖片和文字的不同。
<div id="capture" style="padding: 10px; background: #f5da55">
<h4 style="color: #000; ">Hello world!</h4>
</div>
html2canvas(document.querySelector("#capture")).then(canvas => {
document.body.appendChild(canvas)
});
Untitled.png
這一步我們需要使用JSPDF 的addImage方法,其語法如下:
addImage(imageData, format, x, y, width, height, alias, compression)
下面是一串示例代碼:
import jsPDF from 'jspdf';
export default function addImageUsage() {
const doc = new jsPDF();
const imageData = 【替換成base64數據流】;
doc.addImage(imageData, 'png', 0, 0, 10, 10);
doc.addImage(imageData, 'png', 100, 100, 10, 10);
doc.addImage(imageData, 'png', 200, 200, 10, 10);
drawNet(doc);
doc.save('output.pdf');
}
const drawNet = (doc) => {
const gap = 10;
const start = [0, 0];
const end = [595.28, 841.89];
// 所有橫線
for (let i = start[0]; i < end[0]; i = i + gap) {
doc.line(i, 0, i, end[0]);
}
// 所有縱線
for (let j = start[1]; j < end[1]; j = j + gap) {
doc.line(0, j, end[1], j);
}
};
此示例將在 PDF 文檔(默認是A4紙大小,寬高為[595.28, 841.89]像素)的 (10, 10) 、(100, 100) 、(200, 200) 坐標處,添加一張png 圖像。圖像的寬度和高度將分別為 10 和 10 像素,為了了解pdf中的坐標系統,此示例還在pdf文檔中生成了間距為10px的網格系統。
了解了上面的三個關鍵點,接下來我們將這三個步驟串聯起來,實現一個基本的html→pdf的方案。大致步驟如下:
基于這5個步驟,可以實現基本的頁面打印。
import html2canvas from 'html2canvas';
import jsPDF, { RGBAData } from 'jspdf';
// 將元素轉化為canvas元素
// 通過 放大 提高清晰度
// width為內容寬度
async function toCanvas(element: HTMLElement) {
if (!element) return { width: 0, height: 0 };
// canvas元素
const canvas = await html2canvas(element, {
scale: window.devicePixelRatio * 2, // 增加清晰度
useCORS: true // 允許跨域
});
// 獲取canvas轉化后的寬高
const { width: canvasWidth, height: canvasHeight } = canvas;
// 轉化成圖片Data
const canvasData = canvas.toDataURL('image/jpeg', 1.0);
return { width: canvasWidth, height: canvasHeight, data: canvasData };
}
/**
* 生成pdf(A4多頁pdf截斷問題, 包括頁眉、頁腳 和 上下左右留空的護理)
*/
export async function generatePDF({
/** pdf內容的dom元素 */
element,
/** pdf文件名 */
filename
}) {
if (!(element instanceof HTMLElement)) {
return;
}
const pdf = new jsPDF();
// 一頁的高度, 轉換寬度為一頁元素的寬度
const {
width: imageWidth,
height: imageHeight,
data
} = await toCanvas(element);
// 添加圖片
function addImage(
_x: number,
_y: number,
pdfInstance: jsPDF,
base_data:
| string
| HTMLImageElement
| HTMLCanvasElement
| Uint8Array
| RGBAData,
_width: number,
_height: number
) {
pdfInstance.addImage(base_data, 'JPEG', _x, _y, _width, _height);
}
addImage(0, 0, pdf, data!, imageWidth, imageHeight);
return pdf.save(filename);
}
通常,在我們的實踐中,會發現2個問題:
這兩個問題的解決方案是等比例縮放+循環移位:
通過比例縮放,實現頁面內容等比例展示在pdf文檔中
令頁面元素的寬高為x, y(轉化成canvas圖片的寬高),pdf文檔的寬高為w, h。因為高度可以通過加頁延伸,所以可以按照寬度進行縮放,縮放后的圖片高度可以通過下列公式計算
如果頁面的高度超出了pdf文檔的高度,即y > h,使用addPage方法添加一頁即可。但是在新的一頁中,我們的圖片內容的高度需要調整。
假設y = 2 * h,這意味我們需要兩頁才能完整得展示頁面內容。在一頁pdf中,圖片在起始位置插入即可,即
PDF.addImage(pageData, 'JPEG', 0, 0, x, y)// 注意x,y 是縮放后的大小
在第二頁pdf中,圖片的縱向位置需要調整一頁pdf的高度,即
PDF.addImage(pageData, 'JPEG', 0, -h, x, y)// 注意x,y 是縮放后的大小
通過循環計算剩余高度,然后不停調整縱向位置移動base64的圖片位置,可以解決多頁的問題。
盡管 JSPDF 和 html2canvas 是功能強大的工具,但是他們也有很多槽點,比如得手動分頁,手動處理分頁截斷的問題。等你實踐到這一步,就開始面臨分頁截斷的問題,類似的問題也有網友在Github上提出,但是底下依然沒有很好的解決思路。
好在掘金上有人分享了一個不錯的方法:
jsPDF + html2canvas A4分頁截斷 完美解決方案(含代碼 + 案例) - 掘金
概括一下,其處理分頁截斷的原理就是在使用addImage之前,將html進行分頁,通過維護一個高度位置數據,來記錄每次循環迭代addImage的位置。
從高到低遍歷維護一個分頁數組pages,該數組記錄每一頁的起始位置,如:pages[0] 對應 第一頁起始位置,pages[1] 對應第二頁起始位置
Untitled2.png
接下來我們重點討論如何將頁面進行切割,然后生成pages這個數組。
假設頁面的高度是1500,pdf寬高是[500, 900],如果不用處理分頁截斷的問題,我們可以想到第一頁(0-900)是用來承載頁面從高度為0到900的信息;
第二頁(900-1800)是用來承載頁面從高度900到1500的,所以pages數組為[0, 900]。
如果要處理分頁截斷呢,這時候就需要計算頁面元素的距離pdf文檔起始位置的高度h1,以及該元素的內部高度h2,通過這兩個高度來判斷這個元素要不要放在下一頁,防止截斷,示意圖如下:
Untitled4.png
如果h1 + h2 > 頁面高度, 這時候說明這個元素不處理的就會被分頁截斷,所以應該要把這個元素放到第二頁去渲染,這就意味著pages記錄的數據要變化,示意圖如下,可以看到pages[1]我們往上調整了,比第二頁pdf的起始位置更高。
Untitled5.png
說明渲染第二頁pdf的時候,要從h1開始渲染,pages數組為[0, h1],解釋為第一頁pdf渲染頁面高度區域為0-900, 第二頁pdf渲染html高度區域為h1-1500。注意到第一頁渲染的時候到尾部的時候,會有部分內容和第二頁頭部內容重合。因為h1到900這部分的內容肯定會渲染,這部分內容一直都是頁面元素,我們改變pages[1]的值的原因只是創建一個副本,讓頁面看起來內容沒有被截斷。
為了解決這個問題(為了美觀),我們用填充一塊白色區域遮掉它!此處使用jspdf的rect和setFillColor方法,把重合的區域遮白處理。
pdf.setFillColor(255, 255, 255);
pdf.rect(x, y, Math.ceil(_width), Math.ceil(_height), 'F');
上面我們談到了h1和h2,其中h1是元素盒子的上邊距到打印區域的高度(比例縮放后的高度),h2是元素盒子的內部高度。
計算h1: getBoundingClientRect方法
const rect = contentElement.getBoundingClientRect() || {};
const topDistance = rect.top;
return topDistance;
Untitled6.png
計算h2:offsetHeight方法
Untitled7.png
值得注意的是,因為打印區域的html元素不一定是從窗口頂部開始,所以為了計算實際的h1(元素到打印區域的頂部距離),可以采用這樣的方法:
// 對pages進行一個值的修正,因為pages生成是根據根元素來的,根元素并不是我們實際要打印的元素,而是element,
// 所以要把它修正,讓其值是以真實的打印元素頂部節點為準
const newPages = pages.map((item) => item - pages[0]);
上述即是在實現前端頁面生成pdf的過程中遇到的問題,以及解決思路。
為了更直觀得感受效果,本文也給出了不同場景(單頁、多頁、多頁截斷、自定義頁眉頁腳、橫向)下的pdf生成效果,可以通過此鏈接體驗:https://pdf-demo-phi.vercel.app/
此demo的源代碼如下:pdf-demo
與現有文章不同的是,本倉庫的代碼特點在于:
作者:燕平
來源:微信公眾號:Goodme前端團隊
出處:https://mp.weixin.qq.com/s/-1nA-VI6kmgqHRcYs_NZFA
*請認真填寫需求信息,我們會在24小時內與您取得聯系。