需要实现的主要功能如下:
资讯列表、标签页切换,文章举报,频道管理、文章详情、阅读记忆,关注功能、点赞功能、评论功能、回复评论、搜索功能、登录功能、个人中心、编辑资料、小智同学 ...
今天要实现的功能主要是:搜索功能
在layout/layout.vue中
0 实现根据用户是否登录显示我的和未登录功能
<van-tabbar route>
<van-tabbar-item to="/" icon="home-o">
首页
</van-tabbar-item>
<van-tabbar-item to="/question" icon="chat-o">
问答
</van-tabbar-item>
<van-tabbar-item to="/video" icon="video-o">
视频
</van-tabbar-item>
<van-tabbar-item to="/user" icon="search">
+ {{$store.state.tokenInfo.token ? '我的' : '未登陆' }}
</van-tabbar-item>
</van-tabbar>
1 搜索页面
1.1新建 src/views/search/search.vue页面
<template>
<div>搜索页面</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
</style>
1.2 补充路由配置
const routes = [
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/',
name: 'layout',
component: Layout,
// ....
},
{
+ path: '/search',
+ name: 'search',
+ component: () => import(/* webpackChunkName: "search" */ '../views/search/search.vue')
}
]
1.3 设置路由跳转
在src\views\layout\layout.vue中, 点击按钮实现路由跳转。
<!-- 顶部logo搜索导航区域 -->
<van-nav-bar
fixed
>
<div slot="left" class="logo"></div>
<van-button
slot="right"
class="search-btn"
round
type="info"
size="small"
icon="search"
+ @click="$router.push('/search')"
>
搜索
</van-button>
</van-nav-bar>
1.4 直接在地址栏中测试http://localhost:8080/#/search
2搜索页面-组件布局
2.1 分析结构
从上到下,页面结构可以分成四部分
2.2布局
$router.back()表示后退的意思
<template>
<div>
<!-- nav-bar
this.$router.push() : 路由跳转
this.$router.back() : 路由后退 ===== 页面中的后退按钮
-->
<van-nav-bar title="搜索中心" left-arrow @click-left="$router.back()"></van-nav-bar>
<!-- 1. 搜索区域 输入框 -->
<van-search
v-model.trim="keyword"
show-action
placeholder="请输入搜索关键词"
>
<!-- #action ==== slot="action" -->
<!-- <template slot="action">
<div>搜索</div>
</template> -->
<div slot="action">搜索</div>
</van-search>
<!-- 2. 搜索建议 -->
<van-cell-group>
<van-cell title="单元格" icon="search"/>
<van-cell title="单元格" icon="search"/>
<van-cell title="单元格" icon="search"/>
</van-cell-group>
<!-- 3. 历史记录 -->
<van-cell-group>
<van-cell title="历史记录"/>
<van-cell title="单元格">
<van-icon name="close"></van-icon>
</van-cell>
<van-cell title="单元格">
<van-icon name="close"></van-icon>
</van-cell>
</van-cell-group>
</div>
</template>
<script>
export default {
name: 'Search',
data () {
return {
keyword: ''
}
}
}
</script>
2.3 效果如图
3 搜索页面-实现联想建议功能
用户在输入框中写入内容的同时,在输入框下方显示联想建议内容
3.1封装api
创建 api/search.js 并写入
// 用来封装所有与搜索操作相关的业务
import request from '../utils/request'
/**
* 获取搜索建议
* @param {*} keyword 搜索关键字
* @returns
*/
export const getSuggestion = (keyword) => {
return request({
url: 'v1_0/suggestion',
method: 'GET',
params: {
q: keyword
}
})
}
3.2当搜索内容变化时请求获取数据
<!-- 1. 搜索区域 输入框 -->
<van-search
+ @input="hInput"
v-model.trim="keyword"
show-action
placeholder="请输入搜索关键词"
>
import { getSuggestion } from '@/api/search.js'
return {
keyword: '',
+ suggestions: [] // 联想建议
}
// 用户在搜索框输入的内容发生了变化
async hInput () {
console.log(this.keyword)
// 如果用户没有输入任何内容,则清空建议,不要发请求了
if (this.keyword === '') {
this.suggestions = []
return
}
try {
const { data: { data } } = await getSuggestion(this.keyword)
this.suggestions = data.options
} catch (err) {
console.log(err)
}
},
3.3 数据渲染
<!-- 2. 搜索建议
根据你在上面输入的关键字,后端会返回建议
-->
<van-cell-group>
<van-cell
v-for="(suggestion,idx) in suggestions"
:key="idx"
:title="suggestion"
icon="search"
/>
</van-cell-group>
4 搜索页面-高亮展示搜索关键字
由于原数据在后续操作中还会用到,所以,不能直接去修改原数据,而是应该产生一个副本
使用一个计算属性来保存高亮处理之后的建议项。
在计算属性中,对原数据要做加工:用正则+replace对内容进行字符串替换,达成高亮处理的目标。
用v-html显示数据。
4.1 封装辅助高亮函数
// 源字符串,要高亮的片段
export const heightLight = (str, key) => {
const reg = new RegExp(key, 'ig')
return str.replace(reg, (val) => {
return `<span style="color:red">${val}</span>`
})
}
4.2 添加计算属性
computed: {
cSuggestions () {
// this.suggestions中的每一项都去做替换
// suggestions中的每一项 ====> heightLight(suggestions中的每一项, this.keyword)
return this.suggestions.map(item => {
return heightLight(item, this.keyword)
})
}
},
4.3 渲染
<!-- 2. 搜索建议
根据你在上面输入的关键字,后端会返回建议
-->
<van-cell-group>
<van-cell
v-for="(item,idx) in cSuggestions"
:key="idx"
icon="search">
<div v-html="item"></div>
</van-cell>
</van-cell-group>
4.4 查看效果
5搜索页面-显示搜索历史
用户在搜索某个关键字后,把它记录下来,以便后期快速搜索
1. 保存记录的格式和位置
格式:数组。例如:['a','手机','javascript']
2. 在如下两种情况下要保存记录:
在搜索框上点击搜索时保存
在系统给出的联想建议项上点击时保存
5.1搜索页面-添加搜索记录
data () {
return {
keyword: '',
+ historys: ['天津', '万达'],
suggestions: []
}
},
<!-- 搜索历史记录 -->
<van-cell-group>
<van-cell title="历史记录"></van-cell>
<van-cell v-for="(item,idx) in historys"
:key="idx"
:title="item">
<van-icon name="close" />
</van-cell>
</van-cell-group>
<!-- /搜索历史记录 -->
封装添加历史记录的方法,方便之后调用
// 添加一条历史
addHistory (str) {
this.historys.push(str)
// todo:
},
5.2 点击联想建议时
<van-cell-group>
<van-cell
v-for="(item,idx) in cSuggestions"
:key="idx"
icon="search"
+ @click="hClickSuggetion(idx)">
<div v-html="item"></div>
</van-cell>
</van-cell-group>
// 情况2: 用户点击了搜索建议
hClickSuggetion (idx) {
// 1. 添加一条历史
this.addHistory(this.suggestions[idx])
// 2. 跳转到搜索结果页
}
5.3 点击 搜索按钮时
<div slot="action" @click="hSearch">搜索</div>
// 情况1:用户点击了搜索
hSearch () {
// 1. 添加一条历史
this.addHistory(this.keyword)
// 2. 跳转到搜索结果页
// todo
},
5.4 检查所绑定的事件是否生效
图略
5.5 搜索页面-优化添加历史记录的方法
完善添加历史记录功能
在添加历史记录时,有如下两个注意事项:
- 不能有重复项
- 后加入要放在数组的最前面
// 添加一条历史
// 1. 不能有重复项
// 2. 后加入要放在数组的最前面
addHistory (str) {
// (1) 找一下,是否有重复,如果有,就是删除
const idx = this.history.findIndex(item => item === str)
// idx !== -1 && this.historys.splice(idx, 1)
if (idx > -1) {
this.history.splice(idx, 1)
}
// (2) 加在数组的前面
this.history.unshift(str)
}
6 搜索页面-删除历史记录
给用户提供删除历史记录的功能:在每条记录的后边都有一个关闭按钮,点击这个按钮就可以删除这条记录
6.1给X图标添加点击事件
<!-- 搜索历史记录 -->
<van-cell-group>
<van-cell title="历史记录"></van-cell>
<van-cell v-for="(item,idx) in historys"
:key="idx"
:title="item">
+ <van-icon name="close" @click="hDelHistory(idx)"/>
</van-cell>
</van-cell-group>
<!-- /搜索历史记录 -->
// 用户点击了删除历史记录
hDelHistory (idx) {
this.historys.splice(idx, 1)
}
6.2 搜索页面-搜索历史持久化
将历史记录保存到localstorage中
- 封装一个用来持久化历史记录的模块(设置,删除)
- 当搜索历史变化 (添加,删除)时保存一次
- 在初始时使用引入本地数据(从localstorage中去取出数据)
6.2.1 创建utils/storageHistory.js
// 消除魔术字符串
const HISTORY_STR = 'HistoryInfo'
export const getHistory = () => {
return JSON.parse(localStorage.getItem(HISTORY_STR))
}
export const setHistory = HistoryInfo => {
localStorage.setItem(HISTORY_STR, JSON.stringify(HistoryInfo))
}
export const removeHistory = () => {
localStorage.removeItem(HISTORY_STR)
}
6.2.2 引入并使用
`import { setHistory, getHistory } from '@/utils/storageHistory.js'`
data () {
return {
keyword: '', // 搜索关键字
// 初始化,先从本地存储中取值,取不到,则用[]
historys: getHistory() || [], // 保存历史记录 ['正则', 'javascript']
suggestions: [] // 当前的搜索建议
}
}
// 添加一条历史
// 1. 不能有重复项
// 2. 后加入要放在数组的最前面
// 3. 持久化
addHistory (str) {
// (1) 找一下,是否有重复,如果有,就是删除
const idx = this.history.findIndex(item => item === str)
if (idx > -1) {
this.historys.splice(idx, 1)
}
// (2) 加在数组的前面
this.historys.unshift(str)
// (3) 持久化
+ setHistory(this.history)
},
// 用户点击了删除历史记录
hDelHistory (idx) {
this.historys.splice(idx, 1)
// 持久化
+ setHistory(this.history)
}
6.3 搜索页面-联想建议和历史记录的切换显示
联想建议 和 搜索历史 这两个区域是互斥的:
- 如果当前开始去搜索内容,则不显示搜索历史,而显示联想建议。
- 如果当前并没有搜索内容,则显示搜索搜索历史,不显示联想建议。
<!-- 联想建议
v-html来正常显示html字符串效果-->
<!-- 2. 搜索建议
根据你在上面输入的关键字,后端会返回建议
v-if="suggestion.length":如果有搜索建议
-->
<van-cell-group v-if="suggestions.length">
...
</van-cell-group>
<!-- /联想建议 -->
<!-- 搜索历史记录 -->
<van-cell-group v-else>
...
</van-cell-group>
<!-- /搜索历史记录 -->
7 防抖和节流功能
在输入框中字符变化会立刻去发请求获取搜索建议。
- 对用户来说,虽然能及时收到搜索建议,但在此搜索的过程,你录入的单词并没有写完,你得到的搜索建议多半是无用
- 对服务器来说,调用这个接口的频率太高了,给服务器添加了负担。
7.1 实现防抖功能
// 用户的输入发生了变化
hInput () {
clearTimeout(this.timer)
console.log(this.keyword)
this.timer = setTimeout(() => {
this.doAjax()
}, 200)
},
async doAjax () {
if (this.keyword === '') {
this.suggestions = []
return
}
try {
const res = await getSuggestion(this.keyword)
// console.log(res)
this.suggestions = res.data.data.options
} catch (err) {
console.log(err)
}
},
7.2 实现节流功能
// 节流
hInput () {
const dt = Date.now() // 获取当前时间戳 ms为单位
if (dt - this.startTime > 500) {
this.doAjax()
this.startTime = dt
} else {
console.log('当前的时间戳是', dt, '距离上一次执行不够500ms,所以不执行')
}
},
async doAjax () {
if (this.keyword === '') {
this.suggestions = []
return
}
try {
const res = await getSuggestion(this.keyword)
// console.log(res)
this.suggestions = res.data.data.options
} catch (err) {
console.log(err)
}
}
7.3 节流和防抖只需二选一即可
8 搜索结果
搜索结果是单独在另一个页面显示的:
- 路由转跳,并传入你要搜索的关键字
- 收到关键字后,调接口
- 取回查询结果,并显示。
8.1 创建组件
views/search/result.vue
<template>
<div class="serach-result">
<!-- 导航栏 -->
<van-nav-bar
title="xxx 的搜索结果"
left-arrow
fixed
@click-left="$router.back()"
/>
<!-- /导航栏 -->
<!-- 文章列表 -->
<van-list
class="article-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>
<script>
export default {
name: 'SearchResult',
data () {
return {
list: [],
loading: false,
finished: false
}
},
methods: {
onLoad () {
// 异步更新数据
setTimeout(() => {
for (let i = 0; i < 10; i++) {
this.list.push(this.list.length + 1)
}
// 加载状态结束
this.loading = false
// 数据全部加载完成
if (this.list.length >= 40) {
this.finished = true
}
}, 500)
}
}
}
</script>
<style lang="less" scoped>
.serach-result {
height: 100%;
overflow: auto;
.article-list {
margin-top: 39px;
}
}
</style>
8.2 创建路由
{
path: '/searchResult',
name: 'searchResult',
component: () => import('../views/search/searchResult.vue')
},
8.3 使用路由传参并跳转页面
// 情况1:用户点击了搜索
hSearch () {
if (this.keyword === '') {
return
}
// 1. 添加一条历史
this.addHistory(this.keyword)
// 2. 跳转到搜索结果页
+ this.$router.push('/search/result?keyword=' + this.keyword)
}
<!-- 2. 历史记录 -->
<van-cell-group v-else>
<van-cell title="历史记录"/>
<van-cell
v-for="(item,idx) in history"
:key="idx"
:title="item"
+ @click="$router.push('/search/result?keyword=' + item)">
<van-icon name="close" @click.stop="hDelHistory(idx)"></van-icon>
</van-cell>
</van-cell-group>
// 情况3: 用户点击了搜索建议
hClickSuggetion (idx) {
// 1. 添加一条历史
this.addHistory(this.suggestion[idx])
// 2. 跳转到搜索结果页
+ this.$router.push('/search/result?keyword=' + this.suggestions[idx])
}
9 搜索结果-获取查询结果并展示
search/result.vue 中,我们可以通过this.$route.query.keyword来获取传入的查询关键字
created () {
var keyword = this.$route.query.keyword
alert(keyword)
}
9.1 封装接口
在 api/serach.js 封装请求方法
/**
* 获取查询结果
* @param {*} keyword 关键字
* @param {*} page 页码
*/
export const getSearchResult = (keyword, page) => {
return request({
method: 'GET',
url: 'v1_0/search',
params: {
q: keyword,
page: page
}
})
}
9.2 调用接口获取数据
import { getSearchResult } from '@/api/search.js'
export default {
name: 'SearchResult',
data () {
return {
list: [],
page: 1, // 当前的页码
loading: false,
finished: false
}
}
// ---
async onLoad () {
console.log(this.$route.query.keyword)
// 1. 发请求
const res = await getSearchResult(this.$route.query.keyword, this.page)
const arr = res.data.data.results
// 2. 数据回来之后,填充到list中
this.list.push(...arr)
// 3. 手动结束加载状态
this.loading = false
// 4. 判断是否还有更多数据
this.finished = !arr.length
// 5. 页码+1
this.page++
}
9.3 数据渲染
<template>
<div class="serach-result">
<!-- 导航栏 -->
<van-nav-bar
:title="`${$route.query.keyword} 的搜索结果`"
left-arrow
fixed
@click-left="$router.back()"
/>
<!-- /导航栏 -->
<!-- 文章列表 -->
<van-list
class="article-list"
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell
v-for="item in list"
@click="$router.push('/article/' + item.art_id)"
:key="item.art_id"
:title="item.title"
/>
</van-list>
<!-- /文章列表 搜索到的结果-->
</div>
</template>