更新於 2024/10/08閱讀時間約 22 分鐘

EP31 - ex3. Grid with Sort and Filter

表格耶!這幾個範例真的是蠻實用的!~
快學起來~看看這個表格會用到幾行程式碼就完成?
前端好好玩~ Grid with Sort and Filter

App.vue

<!--
An example of creating a reusable grid component and using it with external data.
-->

<script setup>
import DemoGrid from './Grid.vue'
import { ref } from 'vue'

const searchQuery = ref('')
const gridColumns = ['name', 'power']
const gridData = [
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 }
]
</script>

<template>
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<DemoGrid
:data="gridData"
:columns="gridColumns"
:filter-key="searchQuery">
</DemoGrid>
</template>

這是一個示例,展示了如何創建一個可重用的表格組件並使用外部數據。

<script setup> 部分

使用 Vue 3 的 <script setup> 語法來簡化組件的定義和設置。

import DemoGrid from './Grid.vue'

導入一個名為 DemoGrid 的網格組件,這個組件定義在 Grid.vue 文件中。

import { ref } from 'vue'

從 Vue 中導入 ref 函數,ref 用於創建響應式的引用變數。

const searchQuery = ref('')

創建一個響應式變數 searchQuery,初始值為空字符串。這個變數將用來存儲用戶的搜索查詢。

const gridColumns = ['name', 'power']

定義一個數組 gridColumns,其中包含表格的列名,分別是 'name' 和 'power'。

const gridData = [
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 }
]

定義一個數組 gridData,其中包含要顯示在網格中的數據。每個數據項是一個物件,包含兩個屬性:'name' 和 'power'。

<template> 部分

開始模板區塊,用於定義組件的 HTML 結構。

<form id="search">
Search <input name="query" v-model="searchQuery">
</form>

創建一個帶有 ID 為 "search" 的表單,其中包含一個輸入框。輸入框的 v-model 綁定到 searchQuery,這樣當用戶輸入內容時,searchQuery 的值會自動更新。

<DemoGrid
:data="gridData"
:columns="gridColumns"
:filter-key="searchQuery">
</DemoGrid>

使用 DemoGrid 組件,並傳遞三個屬性:

  • data:傳遞 gridData 作為網格數據。
  • columns:傳遞 gridColumns 作為網格列。
  • filter-key:傳遞 searchQuery 作為過濾關鍵字。

這段代碼創建了一個簡單的應用,該應用展示了一個可重用的表格組件 DemoGrid,並使用外部數據 gridData 來填充網格。用戶可以通過搜索框輸入文字,並根據輸入的關鍵字來過濾顯示的數據。

Grid.vue

<script setup>
import { ref, computed } from 'vue'

const props = defineProps({
data: Array,
columns: Array,
filterKey: String
})

const sortKey = ref('')
const sortOrders = ref(
props.columns.reduce((o, key) => ((o[key] = 1), o), {})
)

const filteredData = computed(() => {
let { data, filterKey } = props
if (filterKey) {
filterKey = filterKey.toLowerCase()
data = data.filter((row) => {
return Object.keys(row).some((key) => {
return String(row[key]).toLowerCase().indexOf(filterKey) > -1
})
})
}
const key = sortKey.value
if (key) {
const order = sortOrders.value[key]
data = data.slice().sort((a, b) => {
a = a[key]
b = b[key]
return (a === b ? 0 : a > b ? 1 : -1) * order
})
}
return data
})

function sortBy(key) {
sortKey.value = key
sortOrders.value[key] *= -1
}

function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
</script>

<template>
<table v-if="filteredData.length">
<thead>
<tr>
<th v-for="key in columns"
@click="sortBy(key)"
:class="{ active: sortKey == key }">
{{ capitalize(key) }}
<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in filteredData">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
<p v-else>No matches found.</p>
</template>

<style>
table {
border: 2px solid #42b983;
border-radius: 3px;
background-color: #fff;
}

th {
background-color: #42b983;
color: rgba(255, 255, 255, 0.66);
cursor: pointer;
user-select: none;
}

td {
background-color: #f9f9f9;
}

th,
td {
min-width: 120px;
padding: 10px 20px;
}

th.active {
color: #fff;
}

th.active .arrow {
opacity: 1;
}

.arrow {
display: inline-block;
vertical-align: middle;
width: 0;
height: 0;
margin-left: 5px;
opacity: 0.66;
}

.arrow.asc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #fff;
}

.arrow.dsc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #fff;
}
</style>

這段代碼展示了一個可過濾和排序的表格組件。以下是逐行解釋:

<script setup> 部分

import { ref, computed } from 'vue'

從 Vue 中導入 refcomputed 函數,用於創建響應式變數和計算屬性。

const props = defineProps({
data: Array,
columns: Array,
filterKey: String
})

使用 defineProps 定義組件的 props,包括 data (數據數組),columns (列名數組),和 filterKey (過濾關鍵字)。

const sortKey = ref('')

創建一個響應式變數 sortKey,用來存儲當前的排序鍵,初始值為空字符串。

const sortOrders = ref(
props.columns.reduce((o, key) => ((o[key] = 1), o), {})
)

創建一個響應式變數 sortOrders,用來存儲每個列的排序順序。初始化時,使用 props.columns 中的每個列名將 sortOrders 設置為 1 (升序)。

Q: 什麼是reduce方法?

reduce 方法是一個用於累加數組中所有元素的高階函數。它會遍歷數組中的每一個元素,並將其累加到一個累加器(accumulator)中。這個累加器的初始值可以通過參數設定。 reduce 方法最終返回累加器的值。它通常用於需要從數組生成單個值(如總和、乘積、平均值、或轉換為另一種資料結構)的場合。

array.reduce(callback, initialValue)
  • callback: 在每個元素上執行的函數,接收四個參數:
    • accumulator: 累加器,累積回調的返回值。
    • currentValue: 當前正在處理的數組元素。
    • currentIndex (可選): 當前正在處理的數組元素的索引。
    • array (可選): 調用 reduce 的數組。
  • initialValue: 作為第一次調用 callbackaccumulator 的值。如果未提供 initialValue,將使用數組中的第一個元素,並從第二個元素開始迭代。
  1. 計算數組元素的總和
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 15
  1. 將數組轉換為物件

假設我們有一個包含列名的數組,我們希望將其轉換為一個物件,並將每個列名的排序順序設為 1。

const columns = ['name', 'power'];
const sortOrders = columns.reduce((accumulator, key) => {
accumulator[key] = 1;
return accumulator;
}, {});
console.log(sortOrders); // { name: 1, power: 1 }

在這個範例中:

  • columns.reduce 方法遍歷 columns 數組中的每一個元素。
  • accumulator 初始為空物件 {}
  • 對於數組中的每個 key,設置 accumulator[key] = 1
  • 最終返回累加結果 accumulator,也就是 { name: 1, power: 1 }

讓我們回到您的原始代碼:

const sortOrders = ref(
props.columns.reduce((o, key) => ((o[key] = 1), o), {})
);
  1. props.columns 是一個數組,包含列名。
  2. reduce 方法遍歷 props.columns 中的每個元素(列名)。
  3. 初始值 initialValue 為空物件 {}
  4. 對於每個列名 key,執行 (o[key] = 1),將 key 對應的值設為 1
  5. 返回累加結果 o,最終 o 變成 { name: 1, power: 1 }
  6. ref 函數將生成的物件包裝成響應式的,存儲在 sortOrders 中。

這段代碼的作用是創建一個物件,其中包含列名作為鍵,並將每個鍵的值設置為初始排序順序(1 表示升序)。

const filteredData = computed(() => {
let { data, filterKey } = props
if (filterKey) {
filterKey = filterKey.toLowerCase()
data = data.filter((row) => {
return Object.keys(row).some((key) => {
return String(row[key]).toLowerCase().indexOf(filterKey) > -1
})
})
}
const key = sortKey.value
if (key) {
const order = sortOrders.value[key]
data = data.slice().sort((a, b) => {
a = a[key]
b = b[key]
return (a === b ? 0 : a > b ? 1 : -1) * order
})
}
return data
})

創建一個計算屬性 filteredData,用於根據 filterKey 過濾和根據 sortKey 排序數據:

  • 如果 filterKey 不為空,則將 filterKey 轉為小寫,並過濾 data,只保留包含 filterKey 的行。
  • 如果 sortKey 不為空,則根據 sortKeysortOrders 對數據進行排序。
function sortBy(key) {
sortKey.value = key
sortOrders.value[key] *= -1
}

定義 sortBy 函數,用於根據給定的鍵進行排序。點擊某列時,設置 sortKey 為該列,並反轉 sortOrders 中該列的排序順序。

function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}

定義 capitalize 函數,用於將字符串的首字母大寫。

<template> 部分

<template>
<table v-if="filteredData.length">
<thead>
<tr>
<th v-for="key in columns"
@click="sortBy(key)"
:class="{ active: sortKey == key }">
{{ capitalize(key) }}
<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in filteredData">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
<p v-else>No matches found.</p>
</template>

定義模板結構:

  • 如果 filteredData 不為空,則顯示一個表格:
    • 表頭 (<thead>):使用 v-for 渲染每個列名,並設置 @click 事件以觸發 sortBy 函數。根據當前排序鍵設置 class
    • 表身 (<tbody>):使用 v-for 渲染每行數據,並在每行中使用 v-for 渲染每個列的數據。
  • 如果 filteredData 為空,則顯示 "No matches found." 提示。

<style> 部分

<style>
table {
border: 2px solid #42b983;
border-radius: 3px;
background-color: #fff;
}

th {
background-color: #42b983;
color: rgba(255, 255, 255, 0.66);
cursor: pointer;
user-select: none;
}

td {
background-color: #f9f9f9;
}

th,
td {
min-width: 120px;
padding: 10px 20px;
}

th.active {
color: #fff;
}

th.active .arrow {
opacity: 1;
}

.arrow {
display: inline-block;
vertical-align: middle;
width: 0;
height: 0;
margin-left: 5px;
opacity: 0.66;
}

.arrow.asc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #fff;
}

.arrow.dsc {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #fff;
}
</style>

定義表格的樣式:

  • 設置表格邊框、背景顏色、圓角等樣式。
  • 設置表頭和表格單元格的背景顏色、最小寬度、填充、字體顏色等樣式。
  • 設置表頭在排序時的樣式,包括排序箭頭的樣式。
這一篇原來計算屬性是用在sort跟filter的地方,還需要多練習才能好好應用!
這樣以後寫好一次就可以重複使用這個表格摟~yeah!
分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.