EP31 - ex3. Grid with Sort and Filter

更新於 2024/10/08閱讀時間約 22 分鐘
表格耶!這幾個範例真的是蠻實用的!~
快學起來~看看這個表格會用到幾行程式碼就完成?
前端好好玩~ 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>

定義表格的樣式:

  • 設置表格邊框、背景顏色、圓角等樣式。
  • 設置表頭和表格單元格的背景顏色、最小寬度、填充、字體顏色等樣式。
  • 設置表頭在排序時的樣式,包括排序箭頭的樣式。
raw-image
這一篇原來計算屬性是用在sort跟filter的地方,還需要多練習才能好好應用!
這樣以後寫好一次就可以重複使用這個表格摟~yeah!
avatar-img
2會員
71內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
這段代碼示範了如何從 GitHub 的 API 抓取最新的 Vue.js 提交數據並顯示在網頁上。這是後端 API 串接的一個範例,展示了如何動態地獲取和顯示數據。
看官方文件好累,終於要來看實用範例摟!這篇要做一個Markdown編輯器,真的假的?!
使用 defineAsyncComponent 函數可實現此功能,它接受一個返回 Promise 的加載函數。在大型應用中,組件可以按需加載,並且可與 ES 模塊的動態導入結合使用。還可以使用高級選項處理加載和錯誤狀態,例如設置加載組件和超時設定。
在 Vue 中,當需要將數據從父組件傳遞到深層嵌套的子組件時,使用 props 會導致屬性過度傳遞 (Prop Drilling),這樣即使某些中間組件不需要這些數據,也必須聲明並傳遞它們。這種情況會使代碼難以維護。Provide 和 Inject 是解決這個問題的工具。
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
當組件渲染單根元素時,這些屬性會自動添加到根元素中。如果子組件的根元素已有class或style屬性,則會與從父組件繼承的屬性合併。v-on事件監聽器也遵循相同規則。當組件嵌套時,降級屬性會自動轉發給子組件。
這段代碼示範了如何從 GitHub 的 API 抓取最新的 Vue.js 提交數據並顯示在網頁上。這是後端 API 串接的一個範例,展示了如何動態地獲取和顯示數據。
看官方文件好累,終於要來看實用範例摟!這篇要做一個Markdown編輯器,真的假的?!
使用 defineAsyncComponent 函數可實現此功能,它接受一個返回 Promise 的加載函數。在大型應用中,組件可以按需加載,並且可與 ES 模塊的動態導入結合使用。還可以使用高級選項處理加載和錯誤狀態,例如設置加載組件和超時設定。
在 Vue 中,當需要將數據從父組件傳遞到深層嵌套的子組件時,使用 props 會導致屬性過度傳遞 (Prop Drilling),這樣即使某些中間組件不需要這些數據,也必須聲明並傳遞它們。這種情況會使代碼難以維護。Provide 和 Inject 是解決這個問題的工具。
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
當組件渲染單根元素時,這些屬性會自動添加到根元素中。如果子組件的根元素已有class或style屬性,則會與從父組件繼承的屬性合併。v-on事件監聽器也遵循相同規則。當組件嵌套時,降級屬性會自動轉發給子組件。
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
在本章中,我們探討了CSS Grid佈局的基本概念和應用。CSS Grid提供了一個強大而靈活的二維佈局系統,使得處理複雜的網頁佈局變得更加簡單和直觀。我們學習了如何設置Grid容器和Grid項目,以及如何使用各種Grid屬性來定義和管理佈局。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
在本章中,我們探討了CSS Grid佈局的基本概念和應用。CSS Grid提供了一個強大而靈活的二維佈局系統,使得處理複雜的網頁佈局變得更加簡單和直觀。我們學習了如何設置Grid容器和Grid項目,以及如何使用各種Grid屬性來定義和管理佈局。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
一開始你先把你的專案push上去後,修改vite.config.ts ,要在裡面新增  base: "/Cart/" (/放自己的專案名稱/) build: {outDir: "docs"}, 接下來你要去你的github setting 裡面 -> Page ->選Deploy fro
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
Thumbnail
你好,在下最近在學習開發web,學了html css js,也得出一些心得,由於網路上已有許多教學,所以我會著重在如何開發出to do List,以及解釋我寫的程式碼。相關的教學我會直接貼網址。如果我有什麼地方出錯,或者是可以寫得更好,歡迎在下方留言,討論。 首先先介紹我的開發環境: 我用了vs
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。