1.创建组件并配置路由
1、创建 src/views/search/index.vue
<template> <div class="search-container">搜索页面</div> </template> <script> export default { name: "SearchPage", components: {}, props: {}, data() { return {}; }, computed: {}, watch: {}, created() {}, methods: {} }; </script> <style scoped></style>
2、把搜索页面的路由配置到根组件路由(一级路由)
{ path: '/search', name: 'search', component: () => import ('@/views/search') }
3.为首页的搜索按钮添加to属性,实现路径跳转
<van-button class="search-btn" slot="title" type="info" size="small" round icon="search" to="/search" >搜索</van-button >
2.页面布局
1.创建 src/views/search/components/search-history.vue(使用cell组件)
<template> <div class="search-history"> <van-cell title="搜索历史"> <span>全部删除</span> <span>完成</span> <van-icon name="delete" /> </van-cell> <van-cell title="hello"> <van-icon name="close" /> </van-cell> <van-cell title="hello"> <van-icon name="close" /> </van-cell> <van-cell title="hello"> <van-icon name="close" /> </van-cell> <van-cell title="hello"> <van-icon name="close" /> </van-cell> </div> </template> <script> export default { name: 'SearchHistory', components: {}, props: {}, data () { return {} }, computed: {}, watch: {}, created () {}, mounted () {}, methods: {} } </script> <style scoped lang="less"></style>
2.创建 src/views/search/components/search-suggestion.vue(使用cell组件)
<template> <div class="search-suggestion"> <van-cell title="黑马程序员..." icon="search"></van-cell> <van-cell title="黑马程序员..." icon="search"></van-cell> <van-cell title="黑马程序员..." icon="search"></van-cell> <van-cell title="黑马程序员..." icon="search"></van-cell> <van-cell title="黑马程序员..." icon="search"></van-cell> </div> </template> <script> export default { name: 'SearchSuggestion', components: {}, props: {}, data () { return {} }, computed: {}, watch: {}, created () {}, mounted () {}, methods: {} } </script> <style scoped lang="less"></style>
3.创建 src/views/search/components/search-result.vue(使用list组件)
<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> <script> export default { name: 'SearchResult', components: {}, props: {}, data () { return { list: [], loading: false, finished: false } }, computed: {}, watch: {}, created () {}, mounted () {}, methods: { onLoad () { // 异步更新数据 // setTimeout 仅做示例,真实场景中一般为 ajax 请求 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 } }, 1000) } } } </script> <style scoped lang="less"></style>
4.搜索组件(search组件):
取消按钮注册onCancel事件,调用this.$router.back()方法返回页面上一级
<template> <div class="search-container"> <!-- 搜索栏 --> <!-- Tips: 在 van-search 外层增加 form 标签,且 action 不为空,即可在 iOS 输入法中显示搜索按钮 --> <form action="/"> <van-search v-model="searchText" show-action placeholder="请输入搜索关键词" background="#3296fa" @search="onSearch" @cancel="onCancel" /> </form> <!-- /搜索栏 --> <!-- 搜索历史记录 --> <search-history /> <!-- /搜索历史记录 --> <!-- 联想建议 --> <search-suggestion /> <!-- /联想建议 --> <!-- 历史记录 --> <search-result /> <!-- /历史记录 --> </div> </template> <script> import SearchHistory from './components/search-history' import SearchSuggestion from './components/search-suggestion' import SearchResult from './components/search-result' export default { name: 'SearchIndex', components: { SearchHistory, SearchSuggestion, SearchResult }, props: {}, data () { return { searchText: '' } }, computed: {}, watch: {}, created () {}, mounted () {}, methods: { onSearch (val) { console.log(val) }, onCancel () { this.$router.back() } } } </script> <style scoped lang="less"> .search-container { .van-search__action { color: #fff; } } </style>
3.处理页面显示状态
1、在 data 中添加数据用来控制搜索结果的显示状态
data () { ... isResultShow: false }
2.在模板中绑定条件渲染
<!-- 搜索结果 --> <search-result v-if="isResultShow" /> <!-- /搜索结果 --> <!-- 联想建议 --> <search-suggestion v-else-if="searchText" /> <!-- /联想建议 --> <!-- 搜索历史记录 --> <search-history v-else /> <!-- /搜索历史记录 -->
4.搜索联想建议
当搜索框输入内容的时候,请求加载联想建议的数据,并将请求得到的结果绑定到模板中
一、将父组件中搜索框输入的内容传给联想建议子组件
二、在子组件中监视搜索框输入内容的变化,如果变化则请求获取联想建议数据
2.1封装联想建议的请求方法(search.js):
/** * 用户相关请求模块 */ import request from '@/utils/request' export const getSearchSuggestions = q => { return request({ method: 'GET', url: '/v1_0/suggestion', params: { q } }) }
<script> import { getSearchSuggestions } from '@/api/search' export default { name: 'SearchSuggestion', components: {}, props: { searchText: { type: String, required: true } }, data () { return { suggestions: [], // 联想建议数据列表 } }, computed: {}, watch: { searchText: { // 当 searchText 发生改变的时候就会调用 handler 函数 // 注意:handler 函数名称是固定的 handler (value) { this.loadSearchSuggestions(value) }, immediate: true // 该回调将会在侦听开始之后被立即调用 } }, created () {}, mounted () {}, methods: { async loadSearchSuggestions (q) { try { const { data } = await getSearchSuggestions(q) this.suggestions = data.data.options } catch (err) { this.$toast('数据获取失败,请稍后重试') } }, } } </script>
三、将获取到的联想建议数据展示到列表中
<van-cell icon="search" v-for="(text, index) in suggestions" :key="index" > </van-cell>
5.联想建议优化——防抖
第三方包:lodash
1、安装 lodash
2、防抖处理
// lodash 支持按需加载,有利于打包结果优化 import { debounce } from "lodash"
// debounce 函数 // 参数1:函数 // 参数2:防抖时间 // 返回值:防抖之后的函数,和参数1功能是一样的 handler: debounce(function (value) { this.loadSearchSuggestions(value) }, 200), // handler (value) { // this.loadSearchSuggestions(value) // }, immediate: true // 该回调将会在侦听开始之后被立即调用 }
6.联想建议优化——搜索关键字高亮
保证不修改原始数据!!
data () { return { htmlStr: 'Hello <span style="color: red">World</span>' } }
<div>{{ htmlStr }}</div> <div v-html="htmlStr"></div>
双花括号绑定会直接输出纯文本内容 : <div>{{ htmlStr }}</div>
使用 v-html 指令可以绑定渲染带有 HTML 标签的字符串 :<div v-html="htmlStr"></div>
使用插槽:
<van-cell icon="search" v-for="(text, index) in suggestions" :key="index" > <div slot="title" v-html="highlight(text)"></div> </van-cell>
highlight (text) { const highlightStr = `<span class="active">${this.searchText}</span>` // 正则表达式 // 中间的内容都会当作匹配字符来使用,而不是数据变量 // 如果需要根据数据变量动态的创建正则表达式,则手动 new RegExp // RegExp 正则表达式构造函数 // 参数1:匹配模式字符串,它会根据这个字符串创建正则对象 // 参数2:匹配模式,要写到字符串中 const reg = new RegExp(this.searchText, 'gi') return text.replace(reg, highlightStr) }
bug:// 正则表达式 // 中间的内容都会当作匹配字符来使用,而不是数据变量
// 如果需要根据数据变量动态的创建正则表达式,则手动 new RegExp
7.搜索结果
一、获取搜索关键字
1、为搜索建议添加点击按钮,将选中的建议传入父组件的搜索框(子传父):
@click="$emit('search', text)"
父组件:
<van-search v-model="searchText" show-action placeholder="请输入搜索关键词" background="#3296fa" @search="onSearch" @cancel="onCancel" @focus="isResultShow = false" />
onSearch(val) { // 更新文本框内容 this.searchText = val // 渲染搜索结果 this.isResultShow = true },
2、父组件给子组件(搜索结果)传递数据(父传子)
<!-- 搜索结果 --> <search-result v-if="isResultShow" :q="searchText" /> <!-- /搜索结果 -->
props: { q: { type: String, require: true } },
二、请求获取数据
1、在 api/serach.js添加封装获取搜索结果的请求方法
/** * 获取搜索结果 */ export function getSearch(params) { return request({ method: "GET", url: "/app/v1_0/search", params }) }
2、请求获取
+ import { getSearch } from '@/api/search' export default { name: 'SearchResult', components: {}, props: { q: { type: String, require: true } }, data () { return { list: [], loading: false, finished: false, + page: 1, + perPage: 20 } }, computed: {}, watch: {}, created () {}, mounted () {}, methods: { +++ async onLoad () { // 1. 请求获取数据 const { data } = await getSearch({ page: this.page, // 页码 per_page: this.perPage, // 每页大小 q: this.q // 搜索关键字 }) // 2. 将数据添加到列表中 const { results } = data.data this.list.push(...results) // 3. 设置加载状态结束 this.loading = false // 4. 判断数据是否加载完毕 if (results.length) { this.page++ // 更新获取下一页数据的页码 } else { this.finished = true // 没有数据了,将加载状态设置结束,不再 onLoad } } } }
三、最后,模板绑定
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" > <van-cell + v-for="(article, index) in list" + :key="index" + :title="article.title" /> </van-list>
8.历史记录 ——添加历史记录
要求:不要有重复历史记录,最新的排在最前面
1、在 data 中添加一个数据用来存储历史记录
data () { return { ... searchHistories: [] } }
2、在触发搜索的时候,记录历史记录
onSearch (val) { // 更新文本框内容 this.searchText = val // 存储搜索历史记录 // 要求:不要有重复历史记录、最新的排在最前面 const index = this.searchHistories.indexOf(val) if (index !== -1) { this.searchHistories.splice(index, 1) } this.searchHistories.unshift(val) // 渲染搜索结果 this.isResultShow = true },
9.历史记录 ——添加历史记录
父传子:
父:
<search-history v-else :search-histories="searchHistories" />
子:
<!-- 历史记录 --> <van-cell-group v-else> <van-cell title="历史记录"> <van-icon name="delete" /> <span>全部删除</span> <span>完成</span> </van-cell> <van-cell :title="item" v-for="(item, index) in searchHistories" :key="index" > <van-icon name="close"></van-icon> </van-cell> </van-cell-group> <!-- /历史记录 -->
props: { searchHistories: { type: Array, required: true } },
10.历史记录 ——删除
- 给历史记录中的每一项注册点击事件
- 在处理函数中判断
- 如果是删除状态,则执行删除操作
- 如果是非删除状态,则执行搜索操作一、处理删除相关元素的展示状态
1、在 data 中添加一个数据用来控制删除相关元素的显示状态
data () { return { ... isDeleteShow: false } }
2、绑定使用(v-if及v-else)
<!-- 历史记录 --> <van-cell-group v-else> <van-cell title="历史记录"> <template v-if="isDeleteShow"> <span @click="searchHistories = []">全部删除</span> <span @click="isDeleteShow = false">完成</span> </template> <van-icon v-else name="delete" @click="isDeleteShow = true"></van-icon> </van-cell> <van-cell :title="item" v-for="(item, index) in searchHistories" :key="index" @click="onSearch(item)" > <van-icon v-show="isDeleteShow" name="close" @click="searchHistories.splice(index, 1)" ></van-icon> </van-cell> </van-cell-group> <!-- /历史记录 -->
二、处理删除操作
Prop 数据:Prop 是受父组件数据影响的。
如果是普通数据(数字、字符串、布尔值)绝对不能修改,即便改了也不会传导给父组件
如果是引用类型数据(数组、对象)可以修改,例如 [].push(xxx),对象.xxx = xxx,不能重新赋值, xxx = []
解决bug:让父组件删除!!!
子:<span @click="$emit('clear-search-histories')">全部删除</span>
父:
<search-history v-else :search-histories="searchHistories" @clear-search-histories="searchHistories = []" />
<!-- 历史记录 --> <van-cell-group v-else> <van-cell title="历史记录"> <template v-if="isDeleteShow"> + <span @click="searchHistories = []">全部删除</span> <span @click="isDeleteShow = false">完成</span> </template> <van-icon v-else name="delete" @click="isDeleteShow = true" /> </van-cell> <van-cell :title="item" v-for="(item, index) in searchHistories" :key="index" + @click="onHistoryClick(item, index)" > <van-icon v-show="isDeleteShow" name="close"></van-icon> </van-cell> </van-cell-group> <!-- /历史记录 -->
onHistoryClick (item, index) { // 如果是删除状态,则执行删除操作 if (this.isDeleteShow) { this.searchHistories.splice(index, 1) } else { // 否则执行搜索操作 this.onSearch(item) } }
11.历史记录 ——数据持久化
1、利用 watch 监视统一存储数据
watch: { searchHistories (val) { // 同步到本地存储 setItem('serach-histories', val) } },
2、初始化的时候从本地存储获取数据
data () { return { ... searchHistories: getItem('serach-histories') || [], } }