olp中的搜索功能
OLP项目搜索功能实现
#tech / dev / project
#type / howto
#status / seed
olp中的搜索功能
目标
在 OLP 项目中实现高效的搜索功能,支持:
- 关键词搜索(标题、内容、标签)
- 多条件筛选与组合查询
- 搜索结果高亮与分页
- 搜索历史与热门推荐
前置条件
- OLP 项目前后端分离架构已就绪
- 后端数据库已配置(MySQL / PostgreSQL / Elasticsearch)
- 了解防抖(debounce)与请求取消机制
- 了解前后端搜索方案的取舍
技术方案
方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 前端过滤 | 数据量小(<1000条) | 无网络请求,即时响应 | 数据量大时卡顿 |
| 后端模糊查询 | 中等数据量 | 实现简单 | SQL LIKE 性能差 |
| Elasticsearch | 大数据量/全文搜索 | 高性能,支持分词 | 架构复杂,需维护 |
OLP 项目采用 前后端分离 方案:前端负责筛选参数的获取与传递,后端根据参数在数据库中查询并返回结果。
步骤
1. 搜索框组件
<template>
<div class="search-box">
<el-input
v-model="keyword"
placeholder="搜索..."
clearable
@input="onInput"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDebounceFn } from '@vueuse/core'
const keyword = ref('')
const emit = defineEmits<{
search: [keyword: string]
}>()
const onInput = useDebounceFn(() => {
emit('search', keyword.value)
}, 300)
</script>
2. 防抖与请求取消
输入时使用防抖避免频繁请求,同时取消上一次未完成的请求:
import { useDebounceFn } from '@vueuse/core'
import axios from 'axios'
let abortController: AbortController | null = null
const debouncedSearch = useDebounceFn(async (keyword: string) => {
abortController?.abort()
abortController = new AbortController()
try {
const res = await api.search(keyword, { signal: abortController.signal })
results.value = res.data
} catch (e) {
if (e.name !== 'AbortError') throw e
}
}, 300)
3. 后端 API 设计
GET /api/search?q={keyword}&type={type}&page={page}&size={size}
Response:
{
"total": 42,
"page": 1,
"size": 20,
"items": [
{ "id": "...", "title": "...", "excerpt": "...", "highlight": "..." }
]
}
后端实现要点:
- 使用数据库全文索引或 Elasticsearch 分词查询
- 返回匹配片段(excerpt)和高亮标记(highlight)
- 支持分页以控制单次返回数据量
4. 结果展示与高亮
<template>
<div class="search-results">
<div v-for="item in results.items" :key="item.id" class="result-item">
<h3 v-html="highlightText(item.title, keyword)"></h3>
<p v-html="highlightText(item.excerpt, keyword)"></p>
</div>
</div>
</template>
<script setup lang="ts">
function highlightText(text: string, keyword: string): string {
if (!keyword) return text
const regex = new RegExp(`(${keyword})`, 'gi')
return text.replace(regex, '<mark>$1</mark>')
}
</script>
5. 搜索历史与推荐
const searchHistory = useStorage<string[]>('search-history', [])
function addToHistory(keyword: string) {
searchHistory.value = [
keyword,
...searchHistory.value.filter(k => k !== keyword)
].slice(0, 10)
}
验证
- 输入关键词后 300ms 内不重复请求(防抖生效)
- 快速切换输入时旧请求被取消
- 搜索结果正确展示,匹配内容高亮显示
- 分页功能正常,翻页后结果正确
- 搜索历史正确记录和展示
- 空关键词时不发起搜索请求
常见问题
Q: 搜索结果不准确?
检查后端查询逻辑。SQL LIKE ‘%keyword%’ 无法处理分词场景,建议引入全文索引(MySQL FULLTEXT)或 Elasticsearch。
Q: 搜索响应慢?
- 检查是否缺少数据库索引
- 使用 Elasticsearch 替代 SQL 模糊查询
- 对搜索结果做缓存(热门关键词缓存 5 分钟)
- 限制返回字段,避免 SELECT *
Q: 中文搜索分词不准确?
使用支持中文分词的搜索引擎,如 Elasticsearch + IK 分词器,或 MySQL 的 ngram parser。