<div id="dataContainer"><!-- 數據列表渲染區 --></div>
<div id="pageNumbers"><!-- 頁碼按鈕組 --></div>
<div id="pageNavigator"><!-- 首頁/上頁/下頁/尾頁/跳轉 --></div>
<div id="pageStatus"><!-- 當前頁/總頁數/總條數 --></div>
const PAGE_SIZE = 50; // 每頁條數
const GROUP_SIZE = 7; // 每組顯示頁碼數
let currentPage = 0;
let totalPages = 0;
let currentGroup = 0;
turnGroup(delta)function turnGroup(delta) {
// delta: 1=下一組, -1=上一組, 0=首組, -2=尾組
let startPage;
switch (delta) {
case 1: currentGroup++; startPage = (currentGroup - 1) * GROUP_SIZE + 1; break;
case -1: currentGroup--; startPage = (currentGroup - 1) * GROUP_SIZE + 1; break;
case 0: currentGroup = 1; startPage = 1; break;
case -2: currentGroup = Math.floor((totalPages - 1) / GROUP_SIZE) + 1;
startPage = (currentGroup - 1) * GROUP_SIZE + 1; break;
}
renderPageNumbers(startPage);
renderNavigator();
}
turnPage(pageNo)async function turnPage(pageNo) {
if (pageNo === currentPage || pageNo < 1 || pageNo > totalPages) return;
currentPage = pageNo;
const rowStart = (pageNo - 1) * PAGE_SIZE + 1;
const rowEnd = pageNo * PAGE_SIZE;
// UI 反饋
document.getElementById('dataContainer').innerHTML = '載入中...';
try {
const params = {
method: 'GetCarTrack',
sid: '80100117',
time1: t1,
time2: t2,
rowStart,
rowEnd
};
const result = await ajaxPost('MapQuery.ashx', params);
renderData(result); // 具體渲染邏輯略
} catch (err) {
showError(err);
}
// 邊界自動翻組
if (pageNo === 1) turnGroup(0);
if (pageNo === totalPages) turnGroup(-2);
document.getElementById('pageStatus').textContent = `第 ${currentPage} 頁 / 共 ${totalPages} 頁`;
}
function nextPage() {
if (currentPage < totalPages) {
turnPage(currentPage + 1);
if (currentPage > currentGroup * GROUP_SIZE) turnGroup(1);
}
}
function prevPage() {
if (currentPage > 1) {
turnPage(currentPage - 1);
if (currentPage <= (currentGroup - 1) * GROUP_SIZE) turnGroup(-1);
}
}
XMLHttpRequest (兼容性最佳)function ajaxPost(url, params) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText); // 或 JSON.parse
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
}
};
xhr.onerror = () => reject(new Error('Network error'));
const body = Object.entries(params)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
xhr.send(body);
});
}
-- 查詢第 1-50 條 (第 1 頁)
SELECT * FROM (
SELECT /*+ FULL(t) */ field1, field2, ROWNUM rn
FROM big_table t
WHERE condition
ORDER BY sort_field DESC
) WHERE rn BETWEEN 1 AND 50;
-- 通用模板: 第 N 頁, 每頁 M 條
SELECT * FROM (
SELECT cols, ROWNUM rn FROM (
SELECT cols FROM table WHERE cond ORDER BY sort_col
) WHERE ROWNUM <= :endRow
) WHERE rn >= :startRow;
注意:Oracle 12c+ 可用標準 OFFSET ... FETCH NEXT ... ROWS ONLY。
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 原生 JS + 模板字符串 | 零依賴、體積極小、完全可控 | 手寫渲染繁瑣、易 XSS | 極簡頁面、舊專案維護 |
| Vue/React + 分頁組件 | 響應式、組件化、生態豐富 | 引入構建鏈、包體積大 | 現代 SPA 項目 |
| jQuery DataTables / Bootstrap Table | 功能全 (排序/過濾/導出)、配置式 | 重、難深度定制 | 後台管理系統、快速原型 |
| 虛擬滾動 + 無限加載 | 無分頁概念、體驗連貫、大數據友好 | SEO 難、定位跳轉複雜 | 移動端列表、Feed 流 |
LIMIT/OFFSET 或 ROWNUM,嚴禁全表查詢再前端切片WHERE + ORDER BY 列,避免 Filesort分頁本質是「空間換時間」:犧牲完整性換取響應速度。百萬級數據下,後端分頁 + 前端異步渲染 + 智能緩存 是經典且有效的工程解法。