身為一個前端工程師常常會遇到處理表格的問題,SA 開出來的需求大概就是排序、一頁顯示幾筆資料及分頁等功能。這裡我先筆記一下,如何達成排序的功能。
基本上我們會希望表格會是一個元件,由父元件定義好表頭欄位及取得API傳來的資料透過 prop 的方式傳入子元件,以達成表格元件重複使用的目的。
建立表格結構
首先建立表格元件,預設 fields 跟 data 是由父元件透過 prop 傳入表格子元件的資料。
<script setup>
import { ref } from 'vue';
const props = defineProps(['fields', 'data']);
</script>而父層的 fields 跟 data 長這個樣子:
const fields = ref([
{
apikey: 'team',
label: '小隊',
sortable: false,
width: 60,
},
{
apikey: 'name',
label: '姓名',
sortable: true,
width: 110,
},
{
apikey: 'gang',
label: '幫派',
sortable: true,
width: 110,
},
{
apikey: 'teacher',
label: '師承',
sortable: true,
width: 150,
},
]);
const data = ref([
{
team: '青龍',
name: '郭靖',
teacher: '洪七公',
gang: '全真教',
},
{
team: '白虎',
name: '黃蓉',
teacher: '黃藥師',
gang: '桃花島',
},
{
team: '玄武',
name: '楊過',
teacher: '小龍女',
gang: '古墓派',
},
]);然後利用 fields 跑 v-for 建立表頭:
<thead>
<tr>
<th v-for="(field, index) in props.fields" :key="field.apikey">
<div class="d-flex flex-row align-items-center">
<span>{{ field.label }}</span>
<div>
<i class="bi bi-caret-up-fill"></i>
<i class="bi bi-caret-down-fill"></i>
</div>
</div>
</th>
</tr>
</thead>表格的主體部分,<tr>的部分是利用 data 跑 v-for以列出每一筆資料,而 <td> 的部分則是對應 fields 來跑 v-for,這裡比較特別的是要用 fields 的 apikey 來取 data 對應的 key 的 value。
<tbody>
<tr v-for="(item, index) in data" :key="item.name">
<td scope="row" v-for="(obj, tdIndex) in fields" :key="obj.name">
{{ item[obj.apikey] }}
</td>
</tr>
</tbody>以上就是表格的基本結構,範例原始碼
建立資料排序功能
如果你有注意到,表頭的每一欄我都有放上 2 個箭頭,一個朝上,一個朝下,我希望點擊朝上箭頭時可以依據那一欄的資料進行升冪排序,而點擊向下箭頭時就是比較該欄資料進行降冪排序。
在這之前需要先理解 number() 跟 sort() 這兩個知識點:
使用 array.sort() 的時候,如果沒有提供比較函式,是依據 Unicode 字典順序排序,排出來的結果可能不如預期,所以關於排序,我們第一個要做的事情就是提供比較(compare)函式:
先判斷傳入比較的資料是否為 number ,如果是數字則用比大小的方式排序,否則使用 JavaScript 提供的 localeCompare 方法進行比較。
另外也要考慮到一種狀況,也就是比較的資料其中之一為數字,另一個為字串時,確保數字排在前面。
const compareValues = (value1, value2) => {
let valueA = Number(value1);
let valueB = Number(value2);
if (!isNaN(valueA) && !isNaN(valueB)) {
// 兩者皆為數字,進行數值比較
return valueA - valueB;
} else if (isNaN(valueA) && isNaN(valueB)) {
// 兩者皆為字串,使用 localeCompare 比較
return String(value1).localeCompare(String(value2));
} else {
// 其中一個是數字,確保數字排在前面
return isNaN(valueA) ? 1 : -1;
}
};然後我們用 isAscend 這個變數來控制升冪還是降冪排序,預設是升冪排序。
const isAscend = ref(true);接下來利用比較函式 compareValues 來建立排序函式:
const sortValue = (key) => {
isAscend.value = !isAscend.value;
const sortedArray = [...tempData.value];
if (isAscend.value) {
sortedArray.sort((a, b) => compareValues(a[key], b[key]));
} else {
// 如果 false 反轉
sortedArray.sort((a, b) => compareValues(b[key], a[key]));
}
tempData.value = sortedArray;
};利用 isAscend.value 來做升降冪切換與判斷:
isAscend.value = !isAscend.value;其中值得注意是,因為單向資料流的關係,我們不對傳入子元件的資料進行處理,所以把 props.data 賦值給 tempData 這個 ref 變數 :
const tempData = ref(props.data);而且在 sortValue 這個函式裡,我們也不直接改變 tempData.value,而是解構後賦值給 sortedArray 這個常數,再來進行排序。
然後在兩個上、下箭頭上綁定 sortValue 這個函式:
<div>
<i
class="bi bi-caret-up-fill"
:class="[isAscend ? 'true-class' : 'false-class']"
@click="sortValue(field.apikey)"
></i>
<i
class="bi bi-caret-down-fill"
:class="[isAscend ? 'false-class' : 'true-class']"
@click="sortValue(field.apikey)"
></i>
</div>這樣就完成排序的功能了,詳見範例程式碼!
