美文网首页
vue实现搜索功能且关键字高亮,介绍

vue实现搜索功能且关键字高亮,介绍

作者: 流泪手心_521 | 来源:发表于2020-11-17 10:31 被阅读0次

    很多伙伴在自己用vue学习开发的过程中,不像真正的工作开发,有后端支持,可以从后端那里调接口拿到真实的数据进行展示。但为了模拟这个过程,我们经常使用自己在本地编写模拟的数据进行使用,然后通过发送ajax请求得到我们本地的数据。
    这次,超人鸭就使用本地编写的json数据,通过axios发送ajax请求,来获取本地数据,然后实现搜索功能,并在这个功能上加以拓展,实现搜索关键字的高亮。
    我们一步一步来,首先使用axios
    axios是现在vue官方推荐使用一个插件,用来发送http请求,如果只是简单的调用接口,那用法也非常简单。

    //通过npm安装
    npm install axios --save
    //引入和挂载,在main.js里面
    import axios from 'axios'
    Vue.prototype.$axios = axios
    //在组件中使用
    this.$axios.get(url)
     .then((res) => {
       //成功的回调
     })
    

    上面是axios的基础用法,下面也会看到具体的使用

    然后编写我们的json数据
    因为是本地的模拟数据,所以推荐放在项目中static文件夹里面,下面是超人鸭本次编写的json文件device.json,大家可以不用关心数据本身有什么含义,看一下json数据的编写格式

    {
      "data":{
        "deviceList":[
           {"name":"设备一","date":"2019-03-24","adress":"深圳","type":"电动"},
           {"name":"设备二","date":"2019-03-24","adress":"上海","type":"汽油"},
           {"name":"设备三","date":"2019-03-24","adress":"北京","type":"电动"},
           {"name":"设备四","date":"2019-03-24","adress":"广州","type":"汽油"}
           ......
        ]
      }
    }
    

    这就写好一个json文件,我们来看看在组件中如何通过axios调用到这个json文件:
    //在组件的created钩子函数中调用
    created(){
    //写上文件的路径
    this.$axios.get('../../../static/mock/device.json')
    .then((res) => {
    console.log(res)
    })
    }
    看看打印出来的结果:


    image.png

    可以看到已经调用成功,我们要的数据在res.data.data.deviceList里面
    回到这次的主题,实现搜索且关键字高亮
    先看看最终效果:


    image.png

    有一个输入框,当我输入 '设备' 两个字后:


    image.png
    当我输入 '广州' 两个字后:
    image.png
    可以看到完成了超人鸭所描述的效果,下面我将通过代码和代码中的注释来介绍功能的实现,样式部分就不写出来了,没什么影响。
    现在简单的html结构和data里面的变量:
    <template>
      <div class="bright-index">
        <div class="search-box">
          <input type="text" v-model="keyword" class="input" placeholder="请输入搜索内容, 提示:搜索设备">
          <button class="btn" @click="search">搜索</button>
        </div>
        <div class="content-box">
          <div class="content-card" v-for="(item ,index) in resultList" :key="index">
            设备名称:<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">没有搜索到匹配结果</h2>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data () {
        return {
          keyword: '',   //与输入框绑定的变量
          deviceList: [],  // 调用json文件获取的全部数据
          resultList: [],   //真正展示的数据,也就是筛选后的数据
          isShowTip: false  //当搜索不到数据时为true
        }
      }
    </script>
    

    我们先实现搜索功能,高亮下一步再实现

    //首先得到数据,在created里面使用axios
    created() {
      this.$axios.get('../../../static/mock/device.json')
        .then((res) => {
          //将json文件中的数据赋值给data里面的deviceList
          this.deviceList = res.data.data.deviceList
        })
    }
    //然后我们给按钮绑定了一个点击事件search,在这个事件中做处理
    methods:{
      search() {
        if (this.keyword == '') {   //如果没有输入内容,不让往下执行
          return
        }
        this.resultList = []   //每次搜索对结果数组做初始化
        this.deviceList.forEach((item) => {  //把初始数据进行遍历
        /**
          下面是把初始数据中的每一条数据的四个字段分别去和输入的内容进行匹配,
          如果某一字段中包含输入的内容,就往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) {   //如果没有匹配结果,就显示提示信息
          this.isShowTip = true
        }
      }
    }
    

    因为是本地的模拟数据,所以采用最原始的方式,去遍历判断筛选。

    这样就完成了搜索功能,接下来超人鸭来实现关键字高亮
    注意到上面我放的html结构吗,里面展示数据用的不是平常的插值表达式,也就是两个大括号,而是用v-html,可以用来展示html代码的而不是简单的字符串。
    说到这,那实现高亮的思路就是将关键字也就是你输入的内容,在每一条数据中替换成html字符串
    接上上面的代码,还是在search方法里面

    //将得到的每一条数据中的每一个字段进行处理,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方法结束
    ---------------------------------
    brightKeyword(val) {
      let keyword = this.keyword   //获取输入框输入的内容
      if (val.indexOf(keyword) !== -1) {  //判断这个字段中是否包含keyword
        //如果包含的话,就把这个字段中的那一部分进行替换成html字符
        return val.replace(keyword, `<font color='#42cccc'>${keyword}</font>`)
      } else {
        return val
      }
    }
    

    将每一个字段都进行一次处理,来实现关键字高亮的效果

    但是,如果按照超人鸭上面的代码去实现的话,会出现一个问题,也是本地用模拟数据开发才会出现的问题,下面给大家模拟一下:
    当我输入 '设备' 两个字:


    image.png

    看起来没问题,当我再输入 '深圳' 两个字时:


    image.png

    发现没有,数据是对的,但高亮是错的,上一次搜索的关键字高亮在第二次搜索还是高亮的。
    为什么会出现这种情况呢?
    这里要说到一个概念,对象的浅拷贝,看看这种情况:

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

    这就是对象的浅拷贝,数组也会出现这种情况。这是因为js中对象和数组是引用数据类型,存储方式和基本数据类型(字符串、数字等)不一样,直接赋值给另一个对象的话,另一个对象改变属性自己的属性也会跟着改变。(如果这块不明白的话,可以先知道这么一回事就好,等超人鸭再出一篇详细介绍js存储对象的方式)

    回到我们的例子中,为什么例子会出现对象的浅拷贝呢?
    我们的原始数据,也就是从json文件中的获取到的数据,赋值给我们组件中data的deviceList,而这个过程,只执行了一次,也就是说deviceList这个原始数组在之后的执行中都没有重新去获取。然后在点击搜索后遍历匹配的过程中,resultList去push deviceList中的某一项,而deviceList的每一项都是一个对象,而这些过程都是对象的浅拷贝,所以在之后的高亮处理中,改变了某一个对象里面的属性,deviceList也会跟着受影响。
    如何解决
    如果在真正与后端配合开发的过程中,每次点击搜索,都会再去调用一次接口,重新获取数据。那我们也模拟这个过程,每次点击搜索时都重新去获取数据,保证每次原始数据deviceList都是新的。这个实现也非常简单,不要在created中用axios调用json文件,而是search方法一开始调用:

    search() {
        this.$axios.get('../../../static/mock/device.json')
        .then((res) => {
          //将json文件中的数据赋值给data里面的deviceList
          this.deviceList = res.data.data.deviceList
        })
        ....
    }
    

    看起来似乎没问题了?
    熟悉axios插件的伙伴都知道,axios是用promise封装的,而promise是用来处理异步操作的。异步就是不能确定它什么时候能执行完的操作,所以这样写的话,不能确定它是什么时候执行完成,所以不能确定deviceList什么时候才能获取到数据,当方法继续往后走而deviceList 没有数据就会报错。

    正确的写法应该在获取数据成功后,才去执行下面的操作,也就是获取数据成功后的回调函数里,promise支持这样的写法:

    search() {
        this.$axios.get('../../../static/mock/device.json')
        .then((res) => {
          //将json文件中的数据赋值给data里面的deviceList
          this.deviceList = res.data.data.deviceList
        }).then(() => {
            //把上面search写的代码放在这里面
        })
    }
    

    到这里就完成了,确保了每次都是获取新的数据而且在获取数据成功后才执行下一步操作。实现了用模拟数据进行搜索,关键字高亮的效果。下面超人鸭放上完整代码,不足的地方请大家多多指教。

    <template>
      <div class="bright-index">
        <div class="search-box">
          <input type="text" v-model="keyword" class="input" placeholder="请输入搜索内容, 提示:搜索设备">
          <button class="btn" @click="search">搜索</button>
        </div>
        <div class="content-box">
          <div class="content-card" v-for="(item ,index) in resultList" :key="index">
            设备名称:<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">没有搜索到匹配结果</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('请输入搜索内容')
            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>
    

    项目中的html代码

    <div class="search">
        <div class="search_box">
          <el-input
            type="text"
            class="input-search"
            placeholder="请输入搜索内容"
            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">没有搜索到匹配结果</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>
    

    项目中的代码如下js

    export default {
      data () {
        return {
          searchKey: '',
          pageNo: 1,
          pageSize: 10,
          total: 0,
          deviceList: [], // 调用接口获取的全部数据
          resultList: [], // 真正展示的数据,也就是筛选后的数据
          isShowTip: false // 当搜索不到数据时为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 = [] // 每次搜索对结果数组做初始化
                this.deviceList.forEach(item => { // 把初始数据进行遍历
                  // 下面是把初始数据中的每一条数据的四个字段分别去和输入的内容进行匹配,如果某一字段中包含输入的内容,就往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) { // 如果没有匹配结果,就显示提示信息
                  this.isShowTip = true
                }
    
                // 将得到的每一条数据中的每一个字段进行处理,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 // 获取输入框输入的内容
          if (val.indexOf(keyword) !== -1) { // 判断这个字段中是否包含keyword
            return val.replace(keyword, `<font color="red">${keyword}</font>`)
          } else {
            return val
          }
        },
        // 跳转到详情页面
        searchDetail (announceId) {
          this.$router.push({
            path: 'searchDetail',
            query: {
              announceId: announceId
            }
          })
        },
        // 改变每页显示数量
        handleSizeChange (val) {
          this.pageSize = val
          this.searchList()
        },
        // 改变当前页
        handleCurrentChange (val) {
          this.pageNo = val
          this.searchList()
        }
      }
    }
    

    相关文章

      网友评论

          本文标题:vue实现搜索功能且关键字高亮,介绍

          本文链接:https://www.haomeiwen.com/subject/luqxiktx.html