vue實(shí)現(xiàn)搜索功能且關(guān)鍵字高亮,介紹

很多伙伴在自己用vue學(xué)習(xí)開發(fā)的過程中,不像真正的工作開發(fā),有后端支持,可以從后端那里調(diào)接口拿到真實(shí)的數(shù)據(jù)進(jìn)行展示。但為了模擬這個(gè)過程,我們經(jīng)常使用自己在本地編寫模擬的數(shù)據(jù)進(jìn)行使用,然后通過發(fā)送ajax請(qǐng)求得到我們本地的數(shù)據(jù)。
這次,超人鴨就使用本地編寫的json數(shù)據(jù),通過axios發(fā)送ajax請(qǐng)求,來獲取本地?cái)?shù)據(jù),然后實(shí)現(xiàn)搜索功能,并在這個(gè)功能上加以拓展,實(shí)現(xiàn)搜索關(guān)鍵字的高亮。
我們一步一步來,首先使用axios
axios是現(xiàn)在vue官方推薦使用一個(gè)插件,用來發(fā)送http請(qǐng)求,如果只是簡單的調(diào)用接口,那用法也非常簡單。

//通過npm安裝
npm install axios --save
//引入和掛載,在main.js里面
import axios from 'axios'
Vue.prototype.$axios = axios
//在組件中使用
this.$axios.get(url)
 .then((res) => {
   //成功的回調(diào)
 })

上面是axios的基礎(chǔ)用法,下面也會(huì)看到具體的使用

然后編寫我們的json數(shù)據(jù)
因?yàn)槭潜镜氐哪M數(shù)據(jù),所以推薦放在項(xiàng)目中static文件夾里面,下面是超人鴨本次編寫的json文件device.json,大家可以不用關(guān)心數(shù)據(jù)本身有什么含義,看一下json數(shù)據(jù)的編寫格式

{
  "data":{
    "deviceList":[
       {"name":"設(shè)備一","date":"2019-03-24","adress":"深圳","type":"電動(dòng)"},
       {"name":"設(shè)備二","date":"2019-03-24","adress":"上海","type":"汽油"},
       {"name":"設(shè)備三","date":"2019-03-24","adress":"北京","type":"電動(dòng)"},
       {"name":"設(shè)備四","date":"2019-03-24","adress":"廣州","type":"汽油"}
       ......
    ]
  }
}

這就寫好一個(gè)json文件,我們來看看在組件中如何通過axios調(diào)用到這個(gè)json文件:
//在組件的created鉤子函數(shù)中調(diào)用
created(){
//寫上文件的路徑
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
console.log(res)
})
}
看看打印出來的結(jié)果:


image.png

可以看到已經(jīng)調(diào)用成功,我們要的數(shù)據(jù)在res.data.data.deviceList里面
回到這次的主題,實(shí)現(xiàn)搜索且關(guān)鍵字高亮
先看看最終效果:


image.png

有一個(gè)輸入框,當(dāng)我輸入 '設(shè)備' 兩個(gè)字后:


image.png

當(dāng)我輸入 '廣州' 兩個(gè)字后:
image.png

可以看到完成了超人鴨所描述的效果,下面我將通過代碼和代碼中的注釋來介紹功能的實(shí)現(xiàn),樣式部分就不寫出來了,沒什么影響。
現(xiàn)在簡單的html結(jié)構(gòu)和data里面的變量:
<template>
  <div class="bright-index">
    <div class="search-box">
      <input type="text" v-model="keyword" class="input" placeholder="請(qǐng)輸入搜索內(nèi)容, 提示:搜索設(shè)備">
      <button class="btn" @click="search">搜索</button>
    </div>
    <div class="content-box">
      <div class="content-card" v-for="(item ,index) in resultList" :key="index">
        設(shè)備名稱:<span v-html="item.name"></span>
        <span>日期:<span v-html="item.date"></span></span>
        <span>地址:<span v-html="item.adress"></span></span>
        <span>類型:<span v-html="item.type"></span></span>
      </div>
      <h2 v-if="isShowTip">沒有搜索到匹配結(jié)果</h2>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      keyword: '',   //與輸入框綁定的變量
      deviceList: [],  // 調(diào)用json文件獲取的全部數(shù)據(jù)
      resultList: [],   //真正展示的數(shù)據(jù),也就是篩選后的數(shù)據(jù)
      isShowTip: false  //當(dāng)搜索不到數(shù)據(jù)時(shí)為true
    }
  }
</script>

我們先實(shí)現(xiàn)搜索功能,高亮下一步再實(shí)現(xiàn)

//首先得到數(shù)據(jù),在created里面使用axios
created() {
  this.$axios.get('../../../static/mock/device.json')
    .then((res) => {
      //將json文件中的數(shù)據(jù)賦值給data里面的deviceList
      this.deviceList = res.data.data.deviceList
    })
}
//然后我們給按鈕綁定了一個(gè)點(diǎn)擊事件search,在這個(gè)事件中做處理
methods:{
  search() {
    if (this.keyword == '') {   //如果沒有輸入內(nèi)容,不讓往下執(zhí)行
      return
    }
    this.resultList = []   //每次搜索對(duì)結(jié)果數(shù)組做初始化
    this.deviceList.forEach((item) => {  //把初始數(shù)據(jù)進(jìn)行遍歷
    /**
      下面是把初始數(shù)據(jù)中的每一條數(shù)據(jù)的四個(gè)字段分別去和輸入的內(nèi)容進(jìn)行匹配,
      如果某一字段中包含輸入的內(nèi)容,就往resultList中加
    **/
      if (item.name.indexOf(this.keyword) > -1 ||
        item.date.indexOf(this.keyword) > -1 ||
        item.adress.indexOf(this.keyword) > -1 ||
        item.type.indexOf(this.keyword) > -1) {
        this.resultList.push(item)
      }
    })
    if (this.resultList.length == 0) {   //如果沒有匹配結(jié)果,就顯示提示信息
      this.isShowTip = true
    }
  }
}

因?yàn)槭潜镜氐哪M數(shù)據(jù),所以采用最原始的方式,去遍歷判斷篩選。

這樣就完成了搜索功能,接下來超人鴨來實(shí)現(xiàn)關(guān)鍵字高亮
注意到上面我放的html結(jié)構(gòu)嗎,里面展示數(shù)據(jù)用的不是平常的插值表達(dá)式,也就是兩個(gè)大括號(hào),而是用v-html,可以用來展示html代碼的而不是簡單的字符串。
說到這,那實(shí)現(xiàn)高亮的思路就是將關(guān)鍵字也就是你輸入的內(nèi)容,在每一條數(shù)據(jù)中替換成html字符串
接上上面的代碼,還是在search方法里面

//將得到的每一條數(shù)據(jù)中的每一個(gè)字段進(jìn)行處理,brightKeyword就是變高亮的方法
this.resultList.map((item) => {  //遍歷
  item.name = this.brightKeyword(item.name)
  item.date = this.brightKeyword(item.date)
  item.adress = this.brightKeyword(item.adress)
  item.type = this.brightKeyword(item.type)
}) //到這里search方法結(jié)束
---------------------------------
brightKeyword(val) {
  let keyword = this.keyword   //獲取輸入框輸入的內(nèi)容
  if (val.indexOf(keyword) !== -1) {  //判斷這個(gè)字段中是否包含keyword
    //如果包含的話,就把這個(gè)字段中的那一部分進(jìn)行替換成html字符
    return val.replace(keyword, `<font color='#42cccc'>${keyword}</font>`)
  } else {
    return val
  }
}

將每一個(gè)字段都進(jìn)行一次處理,來實(shí)現(xiàn)關(guān)鍵字高亮的效果

但是,如果按照超人鴨上面的代碼去實(shí)現(xiàn)的話,會(huì)出現(xiàn)一個(gè)問題,也是本地用模擬數(shù)據(jù)開發(fā)才會(huì)出現(xiàn)的問題,下面給大家模擬一下:
當(dāng)我輸入 '設(shè)備' 兩個(gè)字:


image.png

看起來沒問題,當(dāng)我再輸入 '深圳' 兩個(gè)字時(shí):


image.png

發(fā)現(xiàn)沒有,數(shù)據(jù)是對(duì)的,但高亮是錯(cuò)的,上一次搜索的關(guān)鍵字高亮在第二次搜索還是高亮的。
為什么會(huì)出現(xiàn)這種情況呢?
這里要說到一個(gè)概念,對(duì)象的淺拷貝,看看這種情況:

    let obj1 = { foo: 1 }
    let obj2 = obj1
    obj2.foo = 3
    console.log(obj1.foo)  //打印出來之后是3

這就是對(duì)象的淺拷貝,數(shù)組也會(huì)出現(xiàn)這種情況。這是因?yàn)閖s中對(duì)象和數(shù)組是引用數(shù)據(jù)類型,存儲(chǔ)方式和基本數(shù)據(jù)類型(字符串、數(shù)字等)不一樣,直接賦值給另一個(gè)對(duì)象的話,另一個(gè)對(duì)象改變屬性自己的屬性也會(huì)跟著改變。(如果這塊不明白的話,可以先知道這么一回事就好,等超人鴨再出一篇詳細(xì)介紹js存儲(chǔ)對(duì)象的方式)

回到我們的例子中,為什么例子會(huì)出現(xiàn)對(duì)象的淺拷貝呢?
我們的原始數(shù)據(jù),也就是從json文件中的獲取到的數(shù)據(jù),賦值給我們組件中data的deviceList,而這個(gè)過程,只執(zhí)行了一次,也就是說deviceList這個(gè)原始數(shù)組在之后的執(zhí)行中都沒有重新去獲取。然后在點(diǎn)擊搜索后遍歷匹配的過程中,resultList去push deviceList中的某一項(xiàng),而deviceList的每一項(xiàng)都是一個(gè)對(duì)象,而這些過程都是對(duì)象的淺拷貝,所以在之后的高亮處理中,改變了某一個(gè)對(duì)象里面的屬性,deviceList也會(huì)跟著受影響。
如何解決
如果在真正與后端配合開發(fā)的過程中,每次點(diǎn)擊搜索,都會(huì)再去調(diào)用一次接口,重新獲取數(shù)據(jù)。那我們也模擬這個(gè)過程,每次點(diǎn)擊搜索時(shí)都重新去獲取數(shù)據(jù),保證每次原始數(shù)據(jù)deviceList都是新的。這個(gè)實(shí)現(xiàn)也非常簡單,不要在created中用axios調(diào)用json文件,而是search方法一開始調(diào)用:

search() {
    this.$axios.get('../../../static/mock/device.json')
    .then((res) => {
      //將json文件中的數(shù)據(jù)賦值給data里面的deviceList
      this.deviceList = res.data.data.deviceList
    })
    ....
}

看起來似乎沒問題了?
熟悉axios插件的伙伴都知道,axios是用promise封裝的,而promise是用來處理異步操作的。異步就是不能確定它什么時(shí)候能執(zhí)行完的操作,所以這樣寫的話,不能確定它是什么時(shí)候執(zhí)行完成,所以不能確定deviceList什么時(shí)候才能獲取到數(shù)據(jù),當(dāng)方法繼續(xù)往后走而deviceList 沒有數(shù)據(jù)就會(huì)報(bào)錯(cuò)。

正確的寫法應(yīng)該在獲取數(shù)據(jù)成功后,才去執(zhí)行下面的操作,也就是獲取數(shù)據(jù)成功后的回調(diào)函數(shù)里,promise支持這樣的寫法:

search() {
    this.$axios.get('../../../static/mock/device.json')
    .then((res) => {
      //將json文件中的數(shù)據(jù)賦值給data里面的deviceList
      this.deviceList = res.data.data.deviceList
    }).then(() => {
        //把上面search寫的代碼放在這里面
    })
}

到這里就完成了,確保了每次都是獲取新的數(shù)據(jù)而且在獲取數(shù)據(jù)成功后才執(zhí)行下一步操作。實(shí)現(xiàn)了用模擬數(shù)據(jù)進(jìn)行搜索,關(guān)鍵字高亮的效果。下面超人鴨放上完整代碼,不足的地方請(qǐng)大家多多指教。

<template>
  <div class="bright-index">
    <div class="search-box">
      <input type="text" v-model="keyword" class="input" placeholder="請(qǐng)輸入搜索內(nèi)容, 提示:搜索設(shè)備">
      <button class="btn" @click="search">搜索</button>
    </div>
    <div class="content-box">
      <div class="content-card" v-for="(item ,index) in resultList" :key="index">
        設(shè)備名稱:<span v-html="item.name" style="color:#000;"></span>
        <span>日期:<span v-html="item.date"></span></span>
        <span>地址:<span v-html="item.adress"></span></span>
        <span>類型:<span v-html="item.type"></span></span>
      </div>
      <h2 v-if="isShowTip">沒有搜索到匹配結(jié)果</h2>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      keyword: '',
      deviceList: [],
      resultList: [],
      isShowTip: false
    }
  },
  methods: {
    search () {
      this.isShowTip = false
      if (this.keyword == '') {
        this.$message.warning('請(qǐng)輸入搜索內(nèi)容')
        return
      }
      this.$axios.get('../../../static/mock/device.json')
        .then((res) => {
          this.deviceList = res.data.data.deviceList
        }).then(() => {
          this.resultList = []
          this.deviceList.forEach((item) => {
            if (item.name.indexOf(this.keyword) > -1 ||
              item.date.indexOf(this.keyword) > -1 ||
              item.adress.indexOf(this.keyword) > -1 ||
              item.type.indexOf(this.keyword) > -1) {
              this.resultList.push(item)
            }
          })
          if (this.resultList.length == 0) {
            this.isShowTip = true
          }
          this.resultList.map((item) => {
            item.name = this.brightKeyword(item.name)
            item.date = this.brightKeyword(item.date)
            item.adress = this.brightKeyword(item.adress)
            item.type = this.brightKeyword(item.type)
          })
        })
    },
    brightKeyword (val) {
      let keyword = this.keyword
      if (val.indexOf(keyword) !== -1) {
        return val.replace(keyword, `<font color='#42cccc'>${keyword}</font>`)
      } else {
        return val
      }
    }
  }
}
</script>

項(xiàng)目中的html代碼

<div class="search">
    <div class="search_box">
      <el-input
        type="text"
        class="input-search"
        placeholder="請(qǐng)輸入搜索內(nèi)容"
        v-model.trim="searchKey "
        @input="searchList"
        ref="searchInput"
        autocomplete="off"
        autocapitalize="off"
        autocorrect="off"
        autofocus
      ></el-input>
      <el-button type="primary" class="search_btn" @click="searchList">搜索</el-button>
    </div>
    <div>
        <ul>
          <li v-for="item in resultList"
               :key="item.announceId"
          >
            <span v-html="item.contentKey1" @click="searchDetail(item.announceId)"></span>
            <br/>
            <span v-html="item.contentKey2" @click="searchDetail(item.announceId)"></span>
            <br/>
            <span v-html="item.contentKey3" @click="searchDetail(item.announceId)"></span>
            <br/>
            <span v-html="item.contentKey4" @click="searchDetail(item.announceId)"></span>
            <br/>
            <span v-html="item.contentKey5" @click="searchDetail(item.announceId)"></span>
            <br/>
            <span v-html="item.announceName" @click="searchDetail(item.announceId)"></span>
          </li>
        </ul>
       <h2 v-if="isShowTip">沒有搜索到匹配結(jié)果</h2>
    </div>
    <div class="page-footer">
      <el-pagination
        v-if="total>0"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="pageNo"
        :page-sizes="[10,15, 30, 45, 60]"
        :page-size="pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total">
      </el-pagination>
    </div>
  </div>

項(xiàng)目中的代碼如下js

export default {
  data () {
    return {
      searchKey: '',
      pageNo: 1,
      pageSize: 10,
      total: 0,
      deviceList: [], // 調(diào)用接口獲取的全部數(shù)據(jù)
      resultList: [], // 真正展示的數(shù)據(jù),也就是篩選后的數(shù)據(jù)
      isShowTip: false // 當(dāng)搜索不到數(shù)據(jù)時(shí)為true
    }
  },
  created () {
    if (this.$route.query.ytoSearchKey) {
      this.searchKey = this.$route.query.ytoSearchKey
    }
    this.searchList()
  },
  methods: {
    searchList () {
      this.isShowTip = false
      if (this.searchKey === '') {
        return
      }
      if (this.searchKey && this.searchKey.length > 0) {
        return new Promise((resolve, reject) => {
          const params = {
            searchKey: this.searchKey,
            pageNo: this.pageNo,
            limit: this.pageSize
          }
          announceList(params).then(res => {
            this.pageNo = res.data.data.pageNo
            this.deviceList = res.data.data.resultData
            this.total = res.data.data.total
          }).then(() => {
            this.resultList = [] // 每次搜索對(duì)結(jié)果數(shù)組做初始化
            this.deviceList.forEach(item => { // 把初始數(shù)據(jù)進(jìn)行遍歷
              // 下面是把初始數(shù)據(jù)中的每一條數(shù)據(jù)的四個(gè)字段分別去和輸入的內(nèi)容進(jìn)行匹配,如果某一字段中包含輸入的內(nèi)容,就往resultList中加
              if (item.announceName.indexOf(this.searchKey) > -1 ||
                item.contentKey1.indexOf(this.searchKey) > -1 ||
                item.contentKey2.indexOf(this.searchKey) > -1 ||
                item.contentKey3.indexOf(this.searchKey) > -1 ||
                item.contentKey4.indexOf(this.searchKey) > -1 ||
                item.contentKey5.indexOf(this.searchKey) > -1) {
                this.resultList.push(item)
              }
            })
            if (this.resultList.length === 0) { // 如果沒有匹配結(jié)果,就顯示提示信息
              this.isShowTip = true
            }

            // 將得到的每一條數(shù)據(jù)中的每一個(gè)字段進(jìn)行處理,brightKeyword就是變高亮的方法
            this.resultList.map(item => {
              item.announceName = this.brightKeyword(item.announceName)
              item.contentKey1 = this.brightKeyword(item.contentKey1)
              item.contentKey2 = this.brightKeyword(item.contentKey2)
              item.contentKey3 = this.brightKeyword(item.contentKey3)
              item.contentKey4 = this.brightKeyword(item.contentKey4)
              item.contentKey5 = this.brightKeyword(item.contentKey5)
            })
          }).catch((err) => {
            reject(err)
          })
        })
      }
    },
    brightKeyword (val) {
      const keyword = this.searchKey // 獲取輸入框輸入的內(nèi)容
      if (val.indexOf(keyword) !== -1) { // 判斷這個(gè)字段中是否包含keyword
        return val.replace(keyword, `<font color="red">${keyword}</font>`)
      } else {
        return val
      }
    },
    // 跳轉(zhuǎn)到詳情頁面
    searchDetail (announceId) {
      this.$router.push({
        path: 'searchDetail',
        query: {
          announceId: announceId
        }
      })
    },
    // 改變每頁顯示數(shù)量
    handleSizeChange (val) {
      this.pageSize = val
      this.searchList()
    },
    // 改變當(dāng)前頁
    handleCurrentChange (val) {
      this.pageNo = val
      this.searchList()
    }
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容