上一篇《VUE 表格元件進階-排序》有提到把表格做成公用的子元件,子元件本身有排序的功能,透過父元件的傳入的資料設定表格的欄位,表格的內容也透過父元件傳入子元件進行排列,也就是父元件負責資料,表格子元件負責排列及排序的功能。
在這一篇我要加上分頁的功能,把分頁元件當成表格元件的子元件,如此一個表格元件的功能才算完整。
以下說明一下相關元件要處理的任務。

父元件:
負責取得資料與傳資料給表格子元件。
表格元件:
- 新增一個 currnetPage 的 ref 變數,用來儲存 user 點擊的頁次,初始時預設為 1 。
- 新增一個 perPage 的 ref 變數,用來儲存每一頁要渲染在表格上的資料筆數。
- 把資料的總筆數傳遞給分頁元件,在上一篇中我用 tempData 這個 ref 儲存取得的資料。
- 透過 props 把 currnetPage 與 perPage 傳給分頁元件(pagination)。
- 當使用者點擊分頁元件上的頁次時,分頁元件 把 currentPage 的值 emits 傳給表格元件,表格元件透過 computed 觀察 currentPage 的變化,計算出那個分頁要呈現的資料,然後渲染在表格的 DOM 上。
分頁元件:
- 取得表格元件 props 進來的 currentPage、perPage 與總筆數。
- User 點擊分頁元件的頁次後,emits 給表格元件使用者點擊的是那個分頁。
- 點擊分頁元件後,被點擊的那一格應該呈現 active 的狀態。
- 往前一頁跟後一頁的功能。
Step1: 表格元件新增 currentPage、perPage 的 ref 變數
//DataTable 元件
const perPage = ref(10);
const currentPage = ref(1);
Step2: 表格元件傳遞 props 給分頁元件
表格元件把 currentPage、perPage 與資料總筆數傳遞給分頁元件,其中 currentPage 可以用 v-model 來雙向綁定。
<pagination
:perPage="perPage"
:totalRows="tempData.length"
v-model="currentPage"
></pagination>
Step3: 分頁元件接收表格元件傳入的 props
分頁元件透過 props 取得表格元件傳入的 totalRows 、 perPage 與 currentPage,currentPage 因為是使用 v-model 的方式取得,所以分頁元件以 modelValue 承接,同時也要設定 emit = defineEmits(['update:modelValue']),當使用者點擊頁次時觸發 emit 傳遞事件跟值給表格元件。
// Props
const props = defineProps({
align: {
type: String,
default: 'left',
},
totalRows: {
type: Number,
default: 0,
},
perPage: {
type: Number,
default: 10,
},
modelValue: {
type: Number,
default: 1,
},
});
// Emits
const emit = defineEmits(['update:modelValue']);
// Reactive state
const currentPage = ref(props.modelValue);
Step3:分頁元件的DOM布局
以下為幾個確保數字為整數的 help function,用來處理計算分頁時不要出錯,說明詳見註解
// Constants
const DEFAULT_PER_PAGE = 10;
const DEFAULT_TOTAL_ROWS = 0;
// Helper functions
//若 toInteger(value) 轉換後為 0、NaN、null、undefined,則會回傳 DEFAULT_PER_PAGE(預設每頁顯示的數量)。
//如果轉換後有數值,則使用該數值。
const sanitizePerPage = (value) =>
// 確保 perPage 至少是 1,避免無效的 0 或負數
Math.max(toInteger(value) || DEFAULT_PER_PAGE, 1);
//若轉換後的數值為 0、NaN、null、undefined,則使用 DEFAULT_TOTAL_ROWS(預設總筆數)。
//若有正常數值,則使用該數值。
const sanitizeTotalRows = (value) =>
// 確保 totalRows 至少是 0,因為總筆數不能是負數。
Math.max(toInteger(value) || DEFAULT_TOTAL_ROWS, 0);
const toInteger = (value, defaultValue = NaN) => {
//轉換為整數,忽略小數和非數字部分。
//強制使用十進位,避免舊瀏覽器誤判 0 或 0x 為其他進位。
//轉換失敗時回傳 NaN。
const integer = parseInt(value, 10);
return isNaN(integer) ? defaultValue : integer;
};
利用 computed 算出有幾個分頁
// Computed properties 計算總頁數
const numberOfPages = computed(() => {
const result = Math.ceil(
sanitizeTotalRows(props.totalRows) / sanitizePerPage(props.perPage)
);
console.log(result);
return result < 1 ? 1 : result;
});
在DOM上渲染出分頁數與綁定事件
<nav aria-label="Page navigation example">
<ul class="pagination pagination-sm" :class="justifyContent">
<!-- 往前的箭號 如果currentPage為1 則呈現 disabled 樣式 -->
<li class="page-item" :class="currentPage === 1 ? 'disabled' : ''">
<a class="page-link" @click="Previous" aria-label="Previous">
<span aria-hidden="true">‹</span>
</a>
</li>
<!-- 用 v-for 選染出頁次 -->
<li
class="page-item"
:class="currentPage === item ? 'active' : ''"
v-for="item in numberOfPages"
:key="item"
>
<!-- 如果頁次等於 1 或是最後頁 或頁次小於等於5 或目前頁次減掉item小於2 則顯示數字 -->
<!-- 綁定一個 onClick 事件 -->
<a
v-if="
Math.abs(currentPage - item) < 2 ||
item === 1 ||
item === numberOfPages ||
numberOfPages <= 5
"
class="page-link"
@click="onClick(item)"
>{{ item }}</a
>
<a v-else-if="Math.abs(currentPage - item) === 2" class="page-link"
>...</a
>
</li>
<!-- 往前的箭號 如果currentPage為總頁數 則呈現 disabled 樣式 -->
<li
class="page-item"
:class="currentPage === numberOfPages ? 'disabled' : ''"
>
<a class="page-link" @click="Next" aria-label="Next">
<span aria-hidden="true">›</span>
</a>
</li>
</ul>
</nav>
Step4: 分頁元件點擊事件處理
在這裡透過 emit('update:modelValue', currentPage.value),把雙向綁定的 currentPage 透過事件傳遞給表格元件。
// Methods
// 往前一頁
const Previous = () => {
if (currentPage.value !== 1) {
currentPage.value -= 1;
emit('update:modelValue', currentPage.value);
}
};
// 往後一頁
const Next = () => {
if (currentPage.value !== numberOfPages.value) {
currentPage.value += 1;
emit('update:modelValue', currentPage.value);
}
};
// 點擊分頁
const onClick = (pageNumber) => {
console.log(pageNumber);
if (pageNumber === currentPage.value) return;
currentPage.value = pageNumber;
emit('update:modelValue', currentPage.value);
};
Step5: 表格元件觀察到 currentPage 的改變,渲染分頁的資料
// 計算當前頁面的資料
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * perPage.value;
return tempData.value.slice(start, start + perPage.value);
});
備註:在 VUE 3.4 之後,使用元件 v-model 的時候,可以不用再 const emit = defineEmits(['update:modelValue']);
而是使用 defineModel(),讓後面的事件更為直覺。
const currentPage = defineModel();
在點擊事件上也要把 emit('update:modelValue', currentPage.value); 拿掉
// Methods
// 往前一頁
const Previous = () => {
if (currentPage.value !== 1) {
currentPage.value -= 1;
}
};
// 往後一頁
const Next = () => {
if (currentPage.value !== numberOfPages.value) {
currentPage.value += 1;
}
};
// 點擊分頁
const onClick = (pageNumber) => {
console.log(pageNumber);
if (pageNumber === currentPage.value) return;
currentPage.value = pageNumber;
};
