目录
文章搜索模块
一、创建组件并配置路由
1.创建src/views/search/index.vue
2.然后把搜索页面的路由配置到根组件路由(一级路由)
{
path: '/search',
name: 'search',
component: () => import('@/views/search/')
}
3.home/index.vue搜索按钮添加页面导航,点击搜索跳转到search页
<van-button
slot="title"
icon="search"
type="info"
round
size="small"
class="search-btn"
to="/search"
>搜索</van-button
>
二、页面布局
搜索栏 search/index.vue
<!-- Tips: 在van-search外层增加form标签,且action不为空,即可在ios输入法中显示搜索按钮 -->
<form action="/">
<van-search
placeholder="请输入搜索关键词"
v-model="searchText"
@search="onSearch"
@cancel="$router.back()"
show-action
/>
</form>
联想建议
1.创建独立组件search/components/search-suggestion.vue
2.search/index.vue中注册search-suggestion.vue组件
3.布局
<template>
<div class="search-suggestion">
<van-cell title="hello" icon="search"> </van-cell>
</div>
</template>
搜索历史
创建独立组件search/components/search-history.vue
<template>
<div>
<van-cell title="搜索历史">
<div>
<span>全部删除</span>
<span>完成</span>
</div>
<!-- <van-icon name="delete"></van-icon> -->
</van-cell>
<van-cell title="hello">
<van-icon name="close"></van-icon>
</van-cell>
</div>
</template>
搜索结果
创建独立组件search/components/search-result.vue
<template>
<div class="search-result">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item" :title="item" />
</van-list>
</div>
</template>
三、处理页面显示状态
输入框有内容,显示联想建议;没有内容显示历史记录;前两个都不成立,则显示搜索结果
在data中初始化isResultShow: false // 控制搜索结果的显示状态
<!-- 搜索结果 -->
<search-result v-if="isResultShow" />
<!-- 联想建议 -->
<search-suggestion v-else-if="searchText" />
<!-- 历史记录 -->
<search-history v-else />
调整搜索结果样式:设置搜索结果的内部滚动条,不让搜索框随着下划滚动而滚动,只让搜索结果滚动,类似头部固定效果
.search-result {
position: fixed;
left: 0;
right: 0;
/* top: 54px; */
top: 100px;
bottom: 0;
overflow-y: auto;
}
当输入框获得焦点时关闭搜索结果 @focus="isResultShow = false"
四、搜索联想建议
用户输入一个v,下方会显示有关v的搜索条目,效果如下显示:
4.1 基本功能
第一步-》监视数据变化:根据输入的内容展示联想建议的内容,每次输入需要发起请求
1.在父组件search/index.vue中将输入框里的内容searchText通过父子传值传给search-suggestion.vue
父组件: <search-suggestion v-else-if="searchText" :search-text="searchText" />
子组件:
props: {
searchText: {
type: String,
required: true
}
},
2.子组件监听数据变化
watch: {
// 要监视的数据名称
searchText(){
console.log('hello')
}
},
上方代码等价写法:
watch: {
searchText: {
handler() {
console.log("hello");
}
}
},
提示:此时会存在一个问题,组件未渲染出来,所以第一次的数据未监听到(只有数据渲染了才可以监听数据变化 )
优化: immediate: true 数据初始化时就监听,该回调将会在侦听开始之后立即调用
searchText: {
handler() {
console.log("hello");
},
immediate: true // 数据初始化时就监听
}
第二步-》请求获取数据:
配置网络请求接口 api/search.js
// 搜索相关请求模块
import request from '@/utils/request'
export const getSearchSuggestions = value => {
return request({
method: 'GET',
url: '/app/v1_0/suggestion',
params: {
q: value
}
})
}
search-suggestion.vue请求获取数据、模板绑定展示
watch: {
searchText: {
async handler() {
// 找到数据接口
// 请求获取数据
const { data } = await getSearchSuggestions(this.searchText);
console.log(data);
// 模块绑定展示
this.suggestions = data.data.options;
},
immediate: true // 数据初始化时就监听
}
},
用户输入一个v,就能出现含有v的数据即搜索建议,测试页面如下:
4.2 防抖优化 ***
用户输入一个字符就会发起一个请求,如输入abcd时a会发一次请求,b发一次,c发一次,d发一次,这里就会发4次请求,而实际上用户只想发一次请求 abcd。
解决:
1.下载第三方包,包含函数节流和防抖功能: npm i lodash
2. 加载模块
// import _ from "lodash"; //导入所有函数
import { debounce } from "lodash"; // 按需导入 防抖函数
3.将handler处理函数进行防抖优化(到了1秒之后就开始调用 handler,不足1秒的话则请求发不出去)
searchText: {
// async handler() {
// // 找到数据接口
// // 请求获取数据
// const { data } = await getSearchSuggestions(this.searchText);
// // 模块绑定展示
// this.suggestions = data.data.options;
// },
// immediate: true // 数据初始化时就监听
// 对上面代码进行 防抖函数 处理
// 到了1秒之后就开始调用 handler,不足1秒的话则请求发不出去
handler: debounce(async function() {
const { data } = await getSearchSuggestions(this.searchText);
this.suggestions = data.data.options;
}, 1000),
immediate: true
}
4.输入abcd时只会发一次请求,测试结果如下:
提示:创建一个debounce防抖函数,该函数会从上一次被调用后,延迟wait毫秒后调用func方法
4.3 搜索关键字高亮
将搜索的关键字能高亮显示,如输入vue,即在搜索建议里能把匹配的vue都设置高亮
如何将字符串中的指定字符在网页中高亮展示?
1.显示字符的时候调用highlight()方法
<!-- <van-cell
:title="str"
icon="search"
v-for="(str, index) in suggestions"
:key="index"
>
</van-cell> -->
<!-- 将上方代码进行高亮优化 -->
<!-- v-html可以识别html的标签内容 -->
<van-cell icon="search" v-for="(str, index) in suggestions" :key="index">
<div slot="title" v-html="highlight(str)"></div>
</van-cell>
2.编写highlight()方法
highlight(str) {
console.log(str); // 打印搜索建议里的每一项字符
// 正则表达式:/中间的内容/ 都会当作正则匹配模式字符来对待
// 所以在这里不能写 /this.searchText/gi
// RegExp是正则表达式的构造函数,参数1:字符串;参数2:匹配模式;返回值:正则对象
return str.replace(
new RegExp(this.searchText, "gi"),
`<span style="color: red">${this.searchText}</span>`
);
}
五、搜索结果
1.在父组件search/index.vue中将输入框里的内容searchText通过父子传值传给search-result.vue
父组件: <search-result v-if="isResultShow" :search-text="searchText" />
子组件:用props接收searchText值
2.封装数据接口
// 获取搜索结果
export const getSearchResult = params => {
return request({
method: 'GET',
url: '/app/v1_0/search',
params
})
}
3.加载接口模块 import { getSearchResult } from "@/api/search";
4.处理onLoad事件函数
-
先请求获取数据
-
然后将数据放到数据列表中
-
接着关闭本次的loading
-
最近判断是否还有数据,有则更新获取下一页;没有则把finished设置为true,关闭加载更多
async onLoad() {
// 1.请求获取数据
const { data } = await getSearchResult({
q: this.searchText, //检索关键词
page: this.page, //页数
per_page: this.perPage //每页数量,per_page是接口里的字段值,要对应
});
// console.log(data);
// 2.将数据放到数据列表中
const { results } = data.data;
this.list.push(...results);
// 3.关闭本次的loading
this.loading = false;
// 4.判断是否还有数据,有则更新获取下一页;
if (results.length) {
page++;
}
// 没有则把finished设置为true,关闭加载更多
else {
this.finished = true;
}
}
5.页面遍历list数组,渲染在页面上
补充:
点击联想建议进入搜索
1.搜索栏绑定搜索事件@search="onSearch(searchText)",将点击的联想建议的标题填入输入框内进行搜索 this.searchText = searchText;
onSearch(searchText) {
// 把输入框设置为你要搜索的文本
this.searchText = searchText;
// 展示搜索结果
this.isResultShow = true;
}
2.search-suggestion.vue自定义一个点击事件 @click="$emit('search', str)"
3.search/index.vue父组件接收该自定义事件
<search-suggestion
v-else-if="searchText"
:search-text="searchText"
@search="onSearch"
/>
六、搜索历史记录
6.1 添加历史记录
6.2 展示历史记录
记录并展示搜索历史记录
1.将存储搜索历史数据的数组searchHistories进行去重,若有重复的则splice去除
const index = this.searchHistories.indexOf(searchText)
if(index !== -1){
this.searchHistories.splice(index,1)
}
2.将最新的历史记录放在最前面 this.searchHistories.unshift(searchText);
3.遍历历史记录的数组,由于searchHistories数组是写在父组件search/index.vue中的,需要通过父子传值方式把数组给search-history.vue子组件,
效果:
搜索历史记录的数据持久化
1.加载模块 import { setItem, getItem } from "@/utils/storage";
2.数据初始化 searchHistories: getItem("search-histories") || []
3.onSearch事件处理函数
// 如果用户已登录,则把搜索历史记录存储到线上,这个操作无需手动存储
// 提示:只要我们调用获取搜索结果的数据接口,后台会给我们自动存储用户的搜索历史记录
// 如果没有登录,则把搜索历史记录存储到本地
setItem("search-histories", this.searchHistories);
效果:
补充:ES6数组去重
合并数组: [...数组,...数组] 把Set转为数组:[...Set对象]
数组去重:[...new Set([...数组, ...数组])];
// 数组合并,去重
searchHistories = [...new Set([...searchHistories, ...data.data.keywords])];
6.3 删除历史记录
1.设置删除历史记录的处理状态
isDeleteShow: false // 删除的显示状态
2.点击删除按钮时处理删除函数,这里还未进行数据持久化存储
onDelete(history, index) {
// 如果是删除状态
if (this.isDeleteShow) {
return this.searchHistories.splice(index, 1);
// 持久化
// 1.修改本地存储的数据
// 2.请求接口删除线的数据
}
// 非删除状态,执行搜索状态,展示搜索结果
this.$emit("search", history);
}
3.父组件监听search事件,触发onSearch搜索结果的事件
<search-history
v-else
:search-histories="searchHistories"
@search="onSearch"
/>
6.4 数据持久化
1.删除全部历史记录:
因为searchHistories数组search-history.vue组件是props里的数据,如果是引用类型(数组,对象)可以修改,注意这个修改指的是 user.name = 'jack‘,arr.push(123), arr.splice(0,1)
但是任何prop数据都不能重新赋值,xxx = xxx
如果想要让prop数据 = 新的数据,让父组件自己修改
<span @click="emit('update-histories', [])">全部删除</span>
父组件index.vue
<!-- 历史记录 -->
<search-history
v-else
:search-histories="searchHistories"
@search="onSearch"
@update-histories="searchHistories = $event"
/>
2.删除全部历史记录数据持久化:
把index.vue和search-history.vue组件里的setItem("search-histories", this.searchHistories);全部注释,在index.vue中watch()里统一处理持久化操作
watch: {
// 监视搜索历史记录的变化,存储到本地存储,删除全部历史记录的持久化
searchHistories() {
setItem("search-histories", this.searchHistories);
}
},