历史记录功能
目标:实现搜索历史记录功能
- 初始化历史记录数据
data () {
return {
// 搜索关键字
q: '',
// 历史关键字
history: JSON.parse(localStorage.getItem('keywords') || '[]')
}
},
- 历史记录有数据才显示
<div class="history-box" v-else-if="history.length">
- 渲染历史数据
<van-cell v-for="(item, index) in history" :key="index">
<a class="word_btn">{{item}}</a>
<van-icon @click="delHistory(key)" class="close_btn" slot="right-icon" name="cross"/>
</van-cell>
总结:
- 回车后,缓存历史关键字
- 进入搜索组件,初始化数据时,直接读取缓存
- 动态渲染历史数据
- 数组去重操作
handleSearch () {
// 回车触发后:1、保存历史关键字;2、跳转到搜索列表页面
this.history.unshift(this.q)
// Set是ES6引入的一个构造函数,存储结构和数组类似,其中不允许放重复数据
this.history = [...new Set(this.history)]
// 把history数组放入缓存
localStorage.setItem('keywords', JSON.stringify(this.history))
}
总结:基于ES6提供Set构造函数进行去重比较方便
注意:Set是ES6引入的一个构造函数,存储结构和数组类似,其中不允许放重复数据
- 删除历史
deleteSingle (index) {
// 点击叉号删除对应的历史关键字
this.history.splice(index, 1)
// 缓存也要更新
window.localStorage.setItem(SEARCHKEY, JSON.stringify(this.history))
},
- 清空历史
<van-icon name="delete" @click="deleteAll"></van-icon>
deleteAll () {
// 清除所有的历史关键字
this.history = []
// 缓存也要清空
window.localStorage.removeItem('keywords')
},
注意:删除时要删除两个部分:1、data中的数据;2、缓存中的数据
联想搜索功能
目标:实现联想搜索功能
- 封装搜索接口方法
export const searchList = (q) => {
return request({
method: 'get',
url: 'v1_0/suggestion',
params: {
q
}
})
}
<van-search @input='keywordList'
// 联想查询
async keywordList () {
const ret = await searchList(this.q)
this.keylist = ret.data.options
},
总结:输入一次关键字,调用一次接口得到关键字匹配的列表数据。
- 实现函数防抖效果debounce/throttle(节流)
函数防抖的作用:限制任务执行的频率
如果连续两次触发条件的时间间隔超过规定的时间,那么才触发一次任务,如果两次触发条件的间隔小于这个固定时间,那么始终不触发任务。
触发条件:输入一个字符
规定的时间:1秒
任务:调用接口发送请求
// 联想查询
keywordList () {
// 如下的写法就是函数防抖
clearTimeout(this.timer)
if (!this.q.trim()) return
this.timer = setTimeout(async () => {
const ret = await searchList(this.q)
this.keylist = ret.data.options
}, 1000)
},
总结:
- 函数防抖 debounce :连续两次触发条件超过特定时间才会执行一次任务。(关键字搜索、账号重复性验证)
- 函数节流 throttle:在固定的时间内,无论触发多少次条件,仅仅执行一次任务。(分页动态加载)
- 渲染搜索列表结果
<van-cell :key='index' v-for='(item, index) in keylist' icon="search">
<p v-html='item'></p>
</van-cell>
注意:展示内容需要使用v-html指令
- 实现联想列表的高亮控制
// 需求:让列表项内容与关键自匹配后高亮(包裹一层span标签)
this.keylist = ret.data.options.map(item => {
// const arr = []
// const arr = new Array()
// const obj = {}
// const obj = new Object()
// const reg = /\d/
// const reg = new RegExp('\d')
// 原生js方法replace用于替换整个字符串中的子串
const reg = new RegExp(this.q, 'ig')
return item.replace(reg, `<span>${this.q}</span>`)
})
总结:熟悉基于正则表达式构造函数的用法,以及js的replace方法的用法。
- 路由跳转传递参数的另一种方式
总结:通过编程式导航跳转路由时,通过?方式将参数拼接到路由路径之后
this.$router.push('/sresult?kw=' + this.q)
// 路由映射
{
path: '/sresult',
component: Sresult
}
组件内部如何得到这个问号后的参数?this.$route.query.kw
this.$router.push('/sresult/' + this.q)
// 路由映射
{
path: '/sresult/:kw',
component: Sresult
}
组件内部获取这种路由参数的方式:this.$route.params.kw
- 实现搜索页面的跳转
- 回车后跳转,参数为输入的关键字
// 实现搜索
handleSearch () {
// 防止输入空字符串
// q.trim()是原生js的方法,用于去掉两侧的空格 ''
if (!this.q.trim()) return
// 回车触发后:1、保存历史关键字;2、跳转到搜索列表页面
this.history.unshift(this.q)
// Set是ES6引入的一个构造函数,存储结构和数组类似,其中不允许放重复数据
this.history = [...new Set(this.history)]
// 把history数组放入缓存
window.localStorage.setItem('keywords', JSON.stringify(this.history))
// 跳转到搜索列表页面
// this.$router.push('/sresult?kw=' + this.q)
// this.$router.push('/sresult/' + this.q)
this.$router.push({
// path: 'sresult',
name: 'myresult',
query: {
kw: this.q
}
})
}
总结:
- 编程式导航的跳转有多种用法
- 可以这路由映射中设置name属性,编程式导航可以基于name值跳转
- 不同的传参方式属性不同 query/params
- 点击联想的列表项跳转,参数为列表项内容
// 点击联想条目跳转
handleJump (kw) {
// kw此时包括span高亮的标签,需要去掉
const reg = new RegExp(`<span>${this.q}</span>`, 'ig')
kw = kw.replace(reg, this.q)
this.$router.push({
name: 'myresult',
query: {
kw: kw
}
})
},
总结:点击跳转之前,传递的参数需要去掉span高亮标签。
搜索结果-基本布局
目标:实现搜索列表页面基本布局
- 搜索列表布局
<div class="container">
<!-- 导航固定定位 fixed -->
<van-nav-bar fixed title="搜索结果" left-arrow @click-left="$router.back()" />
<!-- 文章列表 -->
<van-list v-model="loading" :finished="finished" finished-text="没有更多了">
<van-cell-group>
<van-cell>
<div class="article_item">
<h3 class="van-ellipsis">PullRefresh下拉刷新PullRefresh下拉刷新下拉刷新下拉刷新</h3>
<div class="img_box">
<van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg" />
<van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg" />
<van-image class="w33" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg" />
</div>
<div class="img_box">
<van-image class="w100" fit="cover" src="https://img.yzcdn.cn/vant/cat.jpeg" />
</div>
<div class="info_box">
<span>你像一阵风</span>
<span>8评论</span>
<span>10分钟前</span>
</div>
</div>
</van-cell>
</van-cell-group>
</van-list>
</div>
- 搜索结果列表样式
.container {
padding-top: 92px;
height: 100%;
overflow-y: auto;
box-sizing: border-box;
}
.article_item {
h3 {
font-weight: normal;
line-height: 2;
}
.img_box {
display: flex;
justify-content: space-between;
.w33 {
width: 33%;
height: 180px;
}
.w100 {
height: 360px;
width: 100%;
}
}
.info_box {
color: #999;
line-height: 2;
position: relative;
span {
padding-right: 20px;
}
}
}
搜索结果-上拉功能
目标:实现搜索上拉功能
- 封装上拉加载接口
// 根据关键字搜索文章列表
export const searchArticles = (options) => {
return request({
method: 'get',
url: 'v1_0/search',
// axios传递get请求参数本来就是使用params
params: {
// 当前页码
page: options.pagenum,
// 每页的条数
per_page: options.pagesize,
// 查询的关键字
q: options.kw
}
})
}
- 列表相关数据
data () {
return {
loading: false,
finished: false,
// 搜索参数
queryData: {
page: 1,
perPage: 10,
// 通过路由参数初始化数据
q: this.$route.query.q
},
// 搜索结果
list: [],
// 列表总数
total: 0
}
},
methods: {
async searchArticles () {
// 根据关键字搜索文章列表结果
const [err, ret] = await searchArticles(this.queryData)
if (err) {
this.$toast.fail('查询文章失败')
} else {
// 获取文章的列表数据和总数
this.list = ret.data.results
this.total = ret.data.total_count
}
}
},
- 触发上拉加载更多动作
methods: {
async onl oad () {
// 页面加载时,触发一次,如果不够一屏,再次调用一次
const ret = await searchArticles(this.queryData)
this.list.push(...ret.data.results)
this.total = ret.data.total_count
// 结束这次的加载
this.loading = false
// 判断加载完成的状态
if (this.list.length >= this.total) {
// 没有更多数据需要加载
this.finished = true
} else {
// 还有更多数据可以加载:页码累加
this.queryData.pagenum += 1
}
}
},
created () {
// 获取路由参数中的查询关键字
this.queryData.kw = this.$route.query.kw
}
- 渲染列表内容
<van-cell v-for='item in list' :key='item.art_id.toString()'>
<div class="article_item">
<h3 class="van-ellipsis">{{item.title}}</h3>
<div class="img_box">
<van-image :key='index' v-for='(img, index) in item.cover.images' :class="[{w33: item.cover.type===3}, {w100: item.cover.type===1}]" fit="cover" :src="img" />
</div>
<div class="info_box">
<span>{{item.aut_name}}</span>
<span>{{item.comm_count}}评论</span>
<span>{{item.pubdate|formatTime}}</span>
</div>
</div>
</van-cell>
总结:
- 基于pagenum和pagesize的分页逻辑
- van-list组件的基本使用
- 类名的动态绑定的用法
总结
- 缓存搜索的历史关键字:本地存储API
- 数组去重:基于ES6引入的新的构造函数Set
- 删除历史关键字:单个删除和全部删除(本地存储API)
- 防止输入空字符:原生js的trim方法的用法
- 函数防抖的业务流程(※);对比函数节流
- 概念;应用场景;代码实现
- 关键字匹配基本功能:调用接口;填充页面
- 列表项目的高亮控制:正则的构造函数用法(好处:支持变量)(※)
- 路由传递参数的用法(基于问号传递参数)(※)
- 编程式导航路由跳转,路由映射支持name,也可以用name跳转(※)
- 注意传递参数:query/params
- name的用法
- 基于pagenum和pagesize参数的分页:基于时间的分页