01. 搜索 - 静态布局准备
- 静态结构和代码
<template>
<div class="search">
<van-nav-bar title="商品搜索" left-arrow @click-left="$router.go(-1)" />
<van-search show-action placeholder="请输入搜索关键词" clearable>
<template #action>
<div>搜索</div>
</template>
</van-search>
<!-- 搜索历史 -->
<div class="search-history">
<div class="title">
<span>最近搜索</span>
<van-icon name="delete-o" size="16" />
</div>
<div class="list">
<div class="list-item" @click="$router.push('/searchlist')">炒锅</div>
<div class="list-item" @click="$router.push('/searchlist')">电视</div>
<div class="list-item" @click="$router.push('/searchlist')">冰箱</div>
<div class="list-item" @click="$router.push('/searchlist')">手机</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SearchIndex'
}
</script>
<style lang="less" scoped>
.search {
.searchBtn {
background-color: #fa2209;
color: #fff;
}
::v-deep .van-search__action {
background-color: #c21401;
color: #fff;
padding: 0 20px;
border-radius: 0 5px 5px 0;
margin-right: 10px;
}
::v-deep .van-icon-arrow-left {
color: #333;
}
.title {
height: 40px;
line-height: 40px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 15px;
}
.list {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
padding: 0 10px;
gap: 5%;
}
.list-item {
width: 30%;
text-align: center;
padding: 7px;
line-height: 15px;
border-radius: 50px;
background: #fff;
font-size: 13px;
border: 1px solid #efefef;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-bottom: 10px;
}
}
</style>
- 组件按需导入
import { Icon } from 'vant'
Vue.use(Icon)
02. 搜索 - 历史记录 - 基本管理
- data 中提供数据,和搜索框双向绑定 (实时获取用户内容)
data () {
return {
search: ''
}
}
<van-search v-model="search" show-action placeholder="请输入搜索关键词" clearable>
<template #action>
<div>搜索</div>
</template>
</van-search>
- 准备假数据,进行基本的历史纪录渲染
data () {
return {
...
history: ['手机', '空调', '白酒', '电视']
}
},
<div class="search-history" v-if="history.length > 0">
...
<div class="list">
<div v-for="item in history" :key="item" @click="goSearch(item)" class="list-item">
{{ item }}
</div>
</div>
</div>
- 点击搜索,或者下面搜索历史按钮,都要进行搜索历史记录更新 (去重,新搜索的内容置顶)
<div @click="goSearch(search)">搜索</div>
<div class="list">
<div v-for="item in history" :key="item" @click="goSearch(item)" class="list-item">
{{ item }}
</div>
</div>
goSearch (key) {
const index = this.history.indexOf(key)
if (index !== -1) {
this.history.splice(index, 1)
}
if (key !== '') {
this.history.unshift(key)
}
this.$router.push(`/searchlist?search=${key}`)
}
- 清空历史
<van-icon @click="clear" name="delete-o" size="16" />
clear () {
this.history = []
}
03. 搜索 - 历史记录 - 持久化
- 持久化到本地 - 封装方法
const HISTORY_KEY = 'hm_history_list'
// 获取搜索历史
export const getHistoryList = () => {
const result = localStorage.getItem(HISTORY_KEY)
return result ? JSON.parse(result) : []
}
// 设置搜索历史
export const setHistoryList = (arr) => {
localStorage.setItem(HISTORY_KEY, JSON.stringify(arr))
}
- 页面中调用 - 实现持久化
data () {
return {
search: '',
history: getHistoryList()
}
},
methods: {
goSearch (key) {
...
setHistoryList(this.history)
this.$router.push(`/searchlist?search=${key}`)
},
clear () {
this.history = []
setHistoryList([])
this.$toast.success('清空历史成功')
}
}
04. 搜索列表 - 静态布局
<template>
<div class="search">
<van-nav-bar fixed title="商品列表" left-arrow @click-left="$router.go(-1)" />
<van-search
readonly
shape="round"
background="#ffffff"
value="手机"
show-action
@click="$router.push('/search')"
>
<template #action>
<van-icon class="tool" name="apps-o" />
</template>
</van-search>
<!-- 排序选项按钮 -->
<div class="sort-btns">
<div class="sort-item">综合</div>
<div class="sort-item">销量</div>
<div class="sort-item">价格 </div>
</div>
<div class="goods-list">
<GoodsItem v-for="item in 10" :key="item"></GoodsItem>
</div>
</div>
</template>
<script>
import GoodsItem from '@/components/GoodsItem.vue'
export default {
name: 'SearchIndex',
components: {
GoodsItem
}
}
</script>
<style lang="less" scoped>
.search {
padding-top: 46px;
::v-deep .van-icon-arrow-left {
color: #333;
}
.tool {
font-size: 24px;
height: 40px;
line-height: 40px;
}
.sort-btns {
display: flex;
height: 36px;
line-height: 36px;
.sort-item {
text-align: center;
flex: 1;
font-size: 16px;
}
}
}
// 商品样式
.goods-list {
background-color: #f6f6f6;
}
</style>
05. 搜索列表 - 动态渲染
(1) 搜索关键字搜索
- 计算属性,基于query 解析路由参数
computed: {
querySearch () {
return this.$route.query.search
}
}
- 根据不同的情况,设置输入框的值
<van-search
...
:value="querySearch || '搜索商品'"
></van-search>
api/product.js
封装接口,获取搜索商品
import request from '@/utils/request'
// 获取搜索商品列表数据
export const getProList = (paramsObj) => {
const { categoryId, goodsName, page } = paramsObj
return request.get('/goods/list', {
params: {
categoryId,
goodsName,
page
}
})
}
- 页面中基于 goodsName 发送请求,动态渲染
data () {
return {
page: 1,
proList: []
}
},
async created () {
const { data: { list } } = await getProList({
goodsName: this.querySearch,
page: this.page
})
this.proList = list.data
}
<div class="goods-list">
<GoodsItem v-for="item in proList" :key="item.goods_id" :item="item"></GoodsItem>
</div>
(2) 分类id搜索
1 封装接口 api/category.js
import request from '@/utils/request'
// 获取分类数据
export const getCategoryData = () => {
return request.get('/category/list')
}
2 分类页静态结构
<template>
<div class="category">
<!-- 分类 -->
<van-nav-bar title="全部分类" fixed />
<!-- 搜索框 -->
<van-search
readonly
shape="round"
background="#f1f1f2"
placeholder="请输入搜索关键词"
@click="$router.push('/search')"
/>
<!-- 分类列表 -->
<div class="list-box">
<div class="left">
<ul>
<li v-for="(item, index) in list" :key="item.category_id">
<a :class="{ active: index === activeIndex }" @click="activeIndex = index" href="javascript:;">{{ item.name }}</a>
</li>
</ul>
</div>
<div class="right">
<div @click="$router.push(`/searchlist?categoryId=${item.category_id}`)" v-for="item in list[activeIndex]?.children" :key="item.category_id" class="cate-goods">
<img :src="item.image?.external_url" alt="">
<p>{{ item.name }}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { getCategoryData } from '@/api/category'
export default {
name: 'CategoryPage',
created () {
this.getCategoryList()
},
data () {
return {
list: [],
activeIndex: 0
}
},
methods: {
async getCategoryList () {
const { data: { list } } = await getCategoryData()
this.list = list
}
}
}
</script>
<style lang="less" scoped>
// 主题 padding
.category {
padding-top: 100px;
padding-bottom: 50px;
height: 100vh;
.list-box {
height: 100%;
display: flex;
.left {
width: 85px;
height: 100%;
background-color: #f3f3f3;
overflow: auto;
a {
display: block;
height: 45px;
line-height: 45px;
text-align: center;
color: #444444;
font-size: 12px;
&.active {
color: #fb442f;
background-color: #fff;
}
}
}
.right {
flex: 1;
height: 100%;
background-color: #ffffff;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-content: flex-start;
padding: 10px 0;
overflow: auto;
.cate-goods {
width: 33.3%;
margin-bottom: 10px;
img {
width: 70px;
height: 70px;
display: block;
margin: 5px auto;
}
p {
text-align: center;
font-size: 12px;
}
}
}
}
}
// 导航条样式定制
.van-nav-bar {
z-index: 999;
}
// 搜索框样式定制
.van-search {
position: fixed;
width: 100%;
top: 46px;
z-index: 999;
}
</style>
3 搜索页,基于分类 ID 请求
async created () {
const { data: { list } } = await getProList({
categoryId: this.$route.query.categoryId,
goodsName: this.querySearch,
page: this.page
})
this.proList = list.data
}
06. route和router的区别
项目中有时候会通过this.$route.query.id
去获取地址栏中的id值也会通过this.$router.push('/')
去访问首页地址,route是用于获取局部的路由,当A页面携带参数跳转到B页面时,B页面要获取地址栏中的数据就要通过route
,因为此时路由是局部的,这个参数值并不能在别的页面被获取到,而router是获取全局的路由,当我们通过this.$router.push('/')
进行页面跳转时,所有的页面记录都会记录到这个全局路由router
中,当要进行浏览记录回退时,就可以通过$router.go(-1)
。