iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0
自我挑戰組

程式小白的 vue.js 學習筆記系列 第 27

Day27 : 如何用Vue寫一個搜尋功能?

  • 分享至 

  • xImage
  •  

利用 Computed + filter 簡單搜尋

透過 Computed + filter 雖然可以快速達成搜尋功能,但這個方式卻不能同時篩選兩個以上的關鍵字。

舉例來說,如果我今天使用 「電影」 這個關鍵字可以找到資料,但如改成使用 「電 影」 就會查無此人了qvq

此外,我們還可以利用v-model的 lazy修飾符,當我們輸入關鍵字時,input不會即時將內容綁定,除非我們按enter,或點選外面空白處,來避免一直重複觸發。

<input type="search" v-model.lazy.trim="search" placeholder="輸入書籍、作者名稱">

<li v-for="item in filterProducts" :key="item.id"></li>
data(){
  return{
    data: [], // 取得遠端資料
    keyWord: ''
  }
},
computed: {
  filterProducts() {
    return this.data.filter(searchResult => searchResult.title.match(this.keyWord));
  }
}

多關鍵字搜尋功能

  1. 使用 split 並以空白格切割搜尋的字串,以形成一個陣列 : split(' ')
  2. 利用forEach將keyWords和data的資料作比對
<input type="search" v-model.lazy.trim="search" placeholder="輸入書籍、作者名稱">

<li v-for="item in filterProducts" :key="item.id"></li>
data(){
  return{
    data: [], // 取得遠端資料
    keyWords: ''
  }
},
computed: {
  filterProducts () {
      const strArr = this.keywords.split(' ') // 以空白格切分字串
      const arr = [] // 篩選出搜尋結果的陣列
      // 比對字串
      strArr.forEach((str) => {
        this.data.forEach((item) => {
          if (item.title.includes(str) || item.author.includes(str)) {
            arr.push(item)
          }
        })
      })
      // 如果輸入兩個關鍵字就會出現重複的資料,所以需要刪除重複資料。
      // 過濾出重複的元素
      return [...new Set(arr)]
}

除了 new Set() 方法,我們也可以透過 findIndex 來去除陣列中重複的元素。

const filterArr = arr.filter((item, i) => {
    // 將 當前陣列的索引 與 findIndex() 回傳出的索引值進行比對,
    // 但 findIndex() 的方法 只會回傳 第一個 符合條件的陣列元素的索引
    const index = arr.findIndex((book) => book.title === item.title)
    return i === index
  })
  return filterArr

AutoComplete - input的自動完成效果

說到為什麼突然想起要做這個功能,是因為看了這篇 網頁效能問題改善之 Debounce & Throttle

雖然標題完全跟這個主題不合,但裡面提到了autoComplete的圖解,的確在很多搜尋的頁面都會看到這個設計,不然來實作看看如何?

<form class="autocomplete-container position-relative w-100 mb-4 mb-md-0" >
<!--     這裡是搜尋列 -->
  <div class="input-group w-md-50 me-2">
    <input class="form-control" type="search" v-model.trim="search" @keyup="keyboardEvent"
    placeholder="輸入書籍、作者名稱" aria-label="Search" aria-describedby="button-addon2">
    <button class="btn btn-primary" type="submit" id="button-addon2" @click="searchProducts">
        <i class="fa-solid fa-magnifying-glass text-white"></i>
    </button>
</div>
<!--     這裡是搜尋列 -->
    
<!--     這裡才是autoComplete的選單 -->
<!--     控制autoComplete的開闔 -->
<ul class="autoComplete position-absolute box-shadow bg-white w-100 w-md-50 z-index-3"
:class=" autoComplete ? '' : 'd-none'">
<!--     控制按鈕事件的選取背景 -->
  <li class="searchHover p-2 w-100" v-for="(item,i) in filterProducts" :key="item.id"
  :class=" selectedIndex === i ?'bg-light': ''">
    <router-link class="text-dark d-inline-block w-100" :to="`/product/${item.id}`">{{item.title}}
    </router-link>
  </li>
</ul>
<!--     這裡才是autoComplete的選單 -->
    
</form>

我們可以在 autoComplete 選單內將 filterProducts 用 v-for 呈現,並且在 li 裡面放 router-link 讓我們可以直接透過點選 autoComplete 選單,連到書籍內頁~

data(){
    return{
        products:[] // 遠端資料庫 分頁資料十筆
        productsAll: [] //遠端資料庫 所有資料
        // 搜尋內容
        search: '',
        // 用 autoComplete 來控制是否顯示選單
        autoComplete: false,
        // 用 selectedIndex 來控制選單項目的選取
        selectedIndex: 0
    }
}
methods:{
    // 定義一個 searchProducts 的方法,在搜尋按鈕綁上這個事件
    // 這裡是真正篩選書籍列表的地方,這裡不需要debouced的原因在於,
   //第一次就已載入productsAll,剩下都是依資料做篩選而已,不需要一直戳API。
    searchProducts () {
      this.products = this.filterProducts
      this.autoComplete = false
    },
    // 使用v-on的監聽按鍵事件,控制按鈕事件的選取背景
    keyboardEvent (e) {
      // 按鈕向上
      if (e.keyCode === 38) {
        if (this.selectedIndex > 0) {
          this.selectedIndex--
        }
      // 按鈕向下
      } else if (e.keyCode === 40) {
        this.selectedIndex++
        // enter
      } else if (e.keyCode === 13) {
        this.filterProducts.forEach((item, i) => {
          if (this.selectedIndex === i) {
            // 當 selectedIndex 與 filterProducts 的 index相同,就將 search 改成選取項目的書名
            this.search = item.title
          }
        })
      }
    }
}

watch:{
    search () {
        // 如果有搜尋字詞的話,就顯示autoComplete選單
      if (this.search) {
        this.autoComplete = true
      } else {
        this.autoComplete = false
        this.getProducts()
      }
    },
    // 當選定選項,將 search 改成選取項目的書名後,關閉autoComplete選單
    products () {
      if (this.products.length <= 1) {
        this.autoComplete = false
      }
    }
},
// 搜尋功能
computed: {
filterProducts () {
      const strArr = this.search.split(' ') // 以空白格切分字串
      const arr = []
      // 比對字串
      strArr.forEach((str) => {
        this.productsAll.forEach((item) => {
          if (item.title.includes(str) || item.author.includes(str)) {
            arr.push(item)
          }
        })
      })
      // 如果輸入兩個關鍵字就會出現重複的資料,所以需要刪除重複資料。
      // 過濾出重複的元素
      return [...new Set(arr)]
    },
},
    mounted(){
        this.getProducts() // 表示已取得 this.products的資料
        this.getAllProducts() // 表示已取得 this.productsAll的資料
    }

參考資料 :

[JavaScript] Array.prototype.findIndex() 檢查陣列元素
Vue.js 用 computed 跟 filter 做一個簡易搜尋功能


上一篇
Day26 : 自訂元件生成位置 teleport
下一篇
Day 28 : 如何用Vue輕鬆篩選資料與排序
系列文
程式小白的 vue.js 學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言