如何设计一个 Vue 应用的搜索功能,支持模糊搜索、高亮显示、搜索建议和历史记录?

各位掘友,大家好!我是你们的老朋友,今天咱们来聊聊 Vue 应用中的搜索功能,保证让你的应用像装了涡轮增压一样快!

开场白:搜索,应用的灵魂伴侣

话说回来,一个好的搜索功能,就像是应用的灵魂伴侣。用户想找什么,嗖的一下就出来,体验简直不要太好!但是呢,要实现一个用户体验良好的搜索功能,可不是简单的输入框 + filter 就能搞定的。咱们今天就来深入剖析一下,如何打造一个功能强大、体验优秀的 Vue 搜索功能。

第一部分:需求分析和技术选型

在开始撸代码之前,咱们先来捋一捋需求,明确目标,才能事半功倍嘛!

  • 核心功能:

    • 模糊搜索: 用户输入关键词,能够匹配到包含关键词的相关内容。
    • 高亮显示: 将搜索结果中的关键词高亮显示,让用户一眼就能看到重点。
    • 搜索建议(自动补全): 在用户输入时,提供相关的搜索建议,提高搜索效率。
    • 历史记录: 记录用户的搜索历史,方便用户快速搜索。
  • 技术选型:

    • Vue.js: 毋庸置疑,咱们的主角。
    • Vuex (可选): 如果应用规模较大,需要共享搜索历史记录,建议使用 Vuex 进行状态管理。
    • lodash (可选): 提供一些实用的工具函数,比如 debounce 防抖函数,防止用户频繁输入导致不必要的请求。
    • CSS: 用于样式美化,让搜索框看起来更漂亮。

第二部分:核心功能实现

好啦,需求明确了,技术也选好了,咱们开始进入正题,撸起袖子写代码!

1. 模糊搜索

模糊搜索的实现方式有很多种,最简单的方式就是使用 JavaScript 的 String.prototype.includes() 方法。

<template>
  <div>
    <input type="text" v-model="searchText" placeholder="请输入关键词">
    <ul>
      <li v-for="(item, index) in filteredData" :key="index">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      searchText: '',
      data: [
        { id: 1, name: 'Apple iPhone 13' },
        { id: 2, name: 'Samsung Galaxy S22' },
        { id: 3, name: 'Xiaomi 12 Pro' },
        { id: 4, name: 'OnePlus 10 Pro' },
      ],
    };
  },
  computed: {
    filteredData() {
      if (!this.searchText) {
        return this.data;
      }
      return this.data.filter(item =>
        item.name.toLowerCase().includes(this.searchText.toLowerCase())
      );
    },
  },
};
</script>

这段代码的核心在于 filteredData 这个计算属性。它会根据 searchText 的值,过滤 data 数组,只保留包含 searchText 的元素。为了保证搜索不区分大小写,咱们使用了 toLowerCase() 方法。

2. 高亮显示

高亮显示的关键在于,找到搜索结果中的关键词,然后用 <span> 标签包裹起来,并给它加上特定的样式。

<template>
  <div>
    <input type="text" v-model="searchText" placeholder="请输入关键词">
    <ul>
      <li v-for="(item, index) in filteredData" :key="index" v-html="highlightText(item.name)"></li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      searchText: '',
      data: [
        { id: 1, name: 'Apple iPhone 13' },
        { id: 2, name: 'Samsung Galaxy S22' },
        { id: 3, name: 'Xiaomi 12 Pro' },
        { id: 4, name: 'OnePlus 10 Pro' },
      ],
    };
  },
  computed: {
    filteredData() {
      if (!this.searchText) {
        return this.data;
      }
      return this.data.filter(item =>
        item.name.toLowerCase().includes(this.searchText.toLowerCase())
      );
    },
  },
  methods: {
    highlightText(text) {
      if (!this.searchText) {
        return text;
      }
      const regex = new RegExp(this.searchText, 'gi');
      return text.replace(regex, match => `<span class="highlight">${match}</span>`);
    },
  },
};
</script>

<style scoped>
.highlight {
  background-color: yellow;
}
</style>

这里我们使用了一个 highlightText 方法,它接收一个文本字符串作为参数,然后使用正则表达式找到所有匹配的关键词,并用 <span class="highlight"> 标签包裹起来。注意 v-html 指令的使用,它可以将 HTML 字符串渲染到页面上。

3. 搜索建议(自动补全)

搜索建议的实现方式通常是:在用户输入时,向后端发送请求,获取相关的搜索建议。为了避免用户频繁输入导致不必要的请求,咱们可以使用 lodashdebounce 函数进行防抖处理。

<template>
  <div>
    <input type="text" v-model="searchText" @input="handleInput" placeholder="请输入关键词">
    <ul>
      <li v-for="(suggestion, index) in suggestions" :key="index" @click="selectSuggestion(suggestion)">{{ suggestion }}</li>
    </ul>
  </div>
</template>

<script>
import debounce from 'lodash/debounce';

export default {
  data() {
    return {
      searchText: '',
      suggestions: [],
    };
  },
  mounted() {
    this.debouncedGetSuggestions = debounce(this.getSuggestions, 300);
  },
  methods: {
    handleInput() {
      this.debouncedGetSuggestions();
    },
    async getSuggestions() {
      if (!this.searchText) {
        this.suggestions = [];
        return;
      }
      // 模拟后端请求
      await new Promise(resolve => setTimeout(resolve, 500)); // 模拟网络延迟
      const mockSuggestions = [
        'Apple iPhone 13 Pro',
        'Apple iPhone 13 Pro Max',
        'Samsung Galaxy S22 Ultra',
        'Samsung Galaxy S22 Plus',
        'Xiaomi 12 Pro Global Version',
        'Xiaomi 12 Pro Price',
      ];
      this.suggestions = mockSuggestions.filter(suggestion =>
        suggestion.toLowerCase().includes(this.searchText.toLowerCase())
      );
    },
    selectSuggestion(suggestion) {
      this.searchText = suggestion;
      this.suggestions = [];
    },
  },
};
</script>

这段代码中,handleInput 方法会在用户输入时触发,然后调用 debouncedGetSuggestions 方法。debouncedGetSuggestions 方法是 getSuggestions 方法的防抖版本,它会在 300 毫秒内只执行一次。getSuggestions 方法会向后端发送请求,获取相关的搜索建议,并将结果更新到 suggestions 数组中。

4. 历史记录

历史记录的实现方式通常是将用户的搜索历史保存在 localStorage 中,然后在页面加载时读取出来。

<template>
  <div>
    <input type="text" v-model="searchText" @keyup.enter="addHistory" placeholder="请输入关键词">
    <ul>
      <li v-for="(history, index) in historyList" :key="index" @click="selectHistory(history)">{{ history }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      searchText: '',
      historyList: [],
    };
  },
  mounted() {
    this.historyList = JSON.parse(localStorage.getItem('searchHistory') || '[]');
  },
  methods: {
    addHistory() {
      if (!this.searchText) {
        return;
      }
      // 去重
      if (this.historyList.includes(this.searchText)) {
        return;
      }
      this.historyList.unshift(this.searchText); // 添加到数组头部
      localStorage.setItem('searchHistory', JSON.stringify(this.historyList));
    },
    selectHistory(history) {
      this.searchText = history;
    },
  },
};
</script>

这段代码中,addHistory 方法会在用户按下回车键时触发,它会将用户的搜索内容添加到 historyList 数组中,并保存到 localStorage 中。mounted 钩子函数会在页面加载时读取 localStorage 中的搜索历史。

第三部分:优化和扩展

以上只是最基本的功能实现,如果想要进一步提升用户体验,还可以进行以下优化和扩展:

  • 服务端搜索: 如果数据量非常大,建议将搜索逻辑放在后端进行,可以提高搜索效率。
  • 索引: 使用索引可以加快搜索速度,比如 ElasticSearch。
  • 拼写纠错: 当用户输入错误的关键词时,可以提供拼写纠错功能。
  • 热词推荐: 推荐当前热门的搜索关键词。
  • 分类搜索: 允许用户按照不同的分类进行搜索。

表格总结:核心功能对比

功能 实现方式 优点 缺点
模糊搜索 String.prototype.includes() 简单易用 性能较低,不适合大数据量
高亮显示 正则表达式 + v-html 效果直观 需要注意 XSS 攻击
搜索建议 防抖函数 + 后端请求 体验好,减少不必要的请求 需要后端支持
历史记录 localStorage 简单易用 存储容量有限,只能保存少量数据

Vuex 集成(可选)

如果你的应用需要多个组件共享搜索历史,那么 Vuex 就派上用场了。

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    searchHistory: JSON.parse(localStorage.getItem('searchHistory') || '[]')
  },
  mutations: {
    ADD_HISTORY (state, history) {
      if (state.searchHistory.includes(history)) {
        return;
      }
      state.searchHistory.unshift(history);
      localStorage.setItem('searchHistory', JSON.stringify(state.searchHistory));
    },
    CLEAR_HISTORY (state) {
      state.searchHistory = [];
      localStorage.removeItem('searchHistory');
    }
  },
  actions: {
    addHistory ({ commit }, history) {
      commit('ADD_HISTORY', history)
    },
    clearHistory ({ commit }) {
      commit('CLEAR_HISTORY')
    }
  },
  getters: {
    searchHistory: state => state.searchHistory
  }
})

然后在组件中使用:

<template>
  <div>
    <input type="text" v-model="searchText" @keyup.enter="addHistory" placeholder="请输入关键词">
    <ul>
      <li v-for="(history, index) in searchHistory" :key="index" @click="selectHistory(history)">{{ history }}</li>
    </ul>
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';

export default {
  data() {
    return {
      searchText: '',
    };
  },
  computed: {
    ...mapGetters(['searchHistory'])
  },
  methods: {
    ...mapActions(['addHistory']),
    selectHistory(history) {
      this.searchText = history;
    },
    addHistory() {
        if (!this.searchText) {
          return;
        }
        this.addHistory(this.searchText);
      }
  },
};
</script>

结尾:总结与展望

今天咱们一起学习了 Vue 应用中搜索功能的实现方式,包括模糊搜索、高亮显示、搜索建议和历史记录。希望这些知识能够帮助你打造一个功能强大、体验优秀的搜索功能。

记住,技术是不断发展的,要保持学习的热情,不断探索新的技术,才能成为一名优秀的程序员! 下次有机会,咱们再聊聊如何使用 ElasticSearch 构建更强大的搜索功能! 祝大家编码愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注