当前位置: 首页 > news >正文

项目:从零开始制作一个微信小程序(第六天)

目录

1. 渲染分类页面的基本结构

2. 获取分类数据

3. 动态渲染左侧的一级分类列表

4. 动态渲染右侧的二级分类列表

5. 动态渲染右侧的三级分类列表

6. 切换一级分类后重置滚动条的位置

7. 点击三级分类跳转到商品列表页面

8. 搜索

8.1 自定义搜索组件(my-search 组件)

8.2 通过自定义属性增强组件的通用性

8.3 为自定义组件封装 click 事件

8.4 实现首页搜索组件的吸顶效果

9. 搜索建议

9.1 实现搜索框自动获取焦点

9.2 实现搜索框的防抖处理

9.3 根据关键词查询搜索建议列表和渲染搜索建议列表

10. 搜索历史

10.1 渲染搜索历史记录的基本结构

10.2 实现搜索建议和搜索历史的按需展示

10.3 将搜索关键词存入 historyList

10.4 将搜索历史记录持久化存储到本地

10.5 清空搜索历史记录

10.6 点击搜索历史跳转到商品列表页面

11. 最终实现代码(在上次项目基础之上实现)

12. 最终产品效果

13. 下面是之前和本次项目的源码需要的请自取哦


大家好!接下来我们将在上次项目的基础之上继续开发分类页面和搜索栏,话不多说,让我们开始吧!

1. 渲染分类页面的基本结构

1. 定义页面结构如下:

<template>
<view>
<view class="scroll-view-container">
<!-- 左侧的滚动视图区域 -->
<scroll-view class="left-scroll-view" scroll-y :style="{height: wh
+ 'px'}">
<view class="left-scroll-view-item active">xxx</view>
<view class="left-scroll-view-item">xxx</view>
<view class="left-scroll-view-item">xxx</view>
<view class="left-scroll-view-item">xxx</view>
<view class="left-scroll-view-item">xxx</view>
<view class="left-scroll-view-item">多复制一些节点,演示纵向滚动效
果...</view>
</scroll-view>
<!-- 右侧的滚动视图区域 -->
<scroll-view class="right-scroll-view" scroll-y :style="{height: wh
+ 'px'}">
<view class="left-scroll-view-item">zzz</view>
<view class="left-scroll-view-item">zzz</view>
<view class="left-scroll-view-item">zzz</view>
<view class="left-scroll-view-item">zzz</view>
<view class="left-scroll-view-item">多复制一些节点,演示纵向滚动效果
</view>
</scroll-view>
</view>
</view>
</template>

2. 动态计算窗口的剩余高度:

<script>
export default {
data() {
return {
// 窗口的可用高度 = 屏幕高度 - navigationBar高度 - tabBar 高度
wh: 0
};
},
onLoad() {
// 获取当前系统的信息
const sysInfo = uni.getSystemInfoSync()
// 为 wh 窗口可用高度动态赋值
this.wh = sysInfo.windowHeight
}
}
</script>

3. 美化页面结构:

.scroll-view-container {
display: flex;
.left-scroll-view {
width: 120px;
.left-scroll-view-item {
line-height: 60px;
background-color: #f7f7f7;
text-align: center;
font-size: 12px;
// 激活项的样式
&.active {
background-color: #ffffff;
position: relative;
// 渲染激活项左侧的红色指示边线
&::before {
content: ' ';
display: block;
width: 3px;
height: 30px;
background-color: #c00000;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
}
}
}
}

2. 获取分类数据

1. data 中定义分类数据节点:

data() {
return {
// 分类数据列表
cateList: []
}
}

2. 调用获取分类列表数据的方法:

onLoad() {
// 调用获取分类列表数据的方法
this.getCateList()
}

3. 定义获取分类列表数据的方法:

methods: {
async getCateList() {
// 发起请求
const { data: res } = await
uni.$http.get('/api/public/v1/categories')
// 判断是否获取失败
if (res.meta.status !== 200) return uni.$showMsg()
// 转存数据
this.cateList = res.message
}
}

3. 动态渲染左侧的一级分类列表

1. 循环渲染列表结构:
<!-- 左侧的滚动视图区域 -->
<scroll-view class="left-scroll-view" scroll-y :style="{height: wh +
'px'}">
<block v-for="(item, i) in cateList" :key="i">
<view class="left-scroll-view-item">{{item.cat_name}}</view>
</block>
</scroll-view>
2. data 中定义默认选中项的索引:
data() {
return {
// 当前选中项的索引,默认让第一项被选中
active: 0
}
}
3. 循环渲染结构时,为选中项动态添加 .active 类名:
<block v-for="(item, i) in cateList" :key="i">
<view :class="['left-scroll-view-item', i === active ? 'active' : '']">
{{item.cat_name}}</view>
</block>
4. 为一级分类的 Item 项绑定点击事件处理函数 activeChanged
<block v-for="(item, i) in cateList" :key="i">
<view :class="['left-scroll-view-item', i === active ? 'active' : '']"
@click="activeChanged(i)">{{item.cat_name}}</view>
</block>
5. 定义 activeChanged 事件处理函数,动态修改选中项的索引:
methods: {
// 选中项改变的事件处理函数
activeChanged(i) {
this.active = i
}
}

4. 动态渲染右侧的二级分类列表

1. data 中定义二级分类列表的数据节点:
data() {
return {
// 二级分类列表
cateLevel2: []
}
}
2. 修改 getCateList 方法,在请求到数据之后,为二级分类列表数据赋值:
async getCateList() {
const { data: res } = await uni.$http.get('/api/public/v1/categories')
if (res.meta.status !== 200) return uni.$showMsg()
this.cateList = res.message
// 为二级分类赋值
this.cateLevel2 = res.message[0].children
}
3. 修改 activeChanged 方法,在一级分类选中项改变之后,为二级分类列表数据重新赋值:
activeChanged(i) {
this.active = i
// 为二级分类列表重新赋值
this.cateLevel2 = this.cateList[i].children
}
4. 循环渲染右侧二级分类列表的 UI 结构:
<!-- 右侧的滚动视图区域 -->
<scroll-view class="right-scroll-view" scroll-y :style="{height: wh +
'px'}">
<view class="cate-lv2" v-for="(item2, i2) in cateLevel2" :key="i2">
<view class="cate-lv2-title">/ {{item2.cat_name}} /</view>
</view>
</scroll-view>
5. 美化二级分类的标题样式:
.cate-lv2-title {
font-size: 12px;
font-weight: bold;
text-align: center;
padding: 15px 0;
}

5. 动态渲染右侧的三级分类列表

1. 在二级分类的 <view> 组件中,循环渲染三级分类的列表结构:
<!-- 右侧的滚动视图区域 -->
<scroll-view class="right-scroll-view" scroll-y :style="{height: wh +
'px'}">
<view class="cate-lv2" v-for="(item2, i2) in cateLevel2" :key="i2">
<view class="cate-lv2-title">/ {{item2.cat_name}} /</view>
<!-- 动态渲染三级分类的列表数据 -->
<view class="cate-lv3-list">
<!-- 三级分类 Item 项 -->
<view class="cate-lv3-item" v-for="(item3, i3) in item2.children"
:key="i3">
<!-- 图片 -->
<image :src="item3.cat_icon"></image>
<!-- 文本 -->
<text>{{item3.cat_name}}</text>
</view>
</view>
</view>
</scroll-view>
2. 美化三级分类的样式:
.cate-lv3-list {
display: flex;
flex-wrap: wrap;
.cate-lv3-item {
width: 33.33%;
margin-bottom: 10px;
display: flex;
flex-direction: column;
align-items: center;
image {
width: 60px;
height: 60px;
}
text {
font-size: 12px;
}
}
}

6. 切换一级分类后重置滚动条的位置

1. data 中定义 滚动条距离顶部的距离
data() {
return {
// 滚动条距离顶部的距离
scrollTop: 0
}
}
2. 动态为右侧的 <scroll-view> 组件绑定 scroll-top 属性的值:
<!-- 右侧的滚动视图区域 -->
<scroll-view class="right-scroll-view" scroll-y :style="{height: wh +
'px'}" :scroll-top="scrollTop"></scroll-view>
3. 切换一级分类时,动态设置 scrollTop 的值:
// 选中项改变的事件处理函数
activeChanged(i) {
this.active = i
this.cateLevel2 = this.cateList[i].children
// 让 scrollTop 的值在 0 与 1 之间切换
this.scrollTop = this.scrollTop === 0 ? 1 : 0
// 可以简化为如下的代码:
// this.scrollTop = this.scrollTop ? 0 : 1
}

7. 点击三级分类跳转到商品列表页面

1. 为三级分类的 Item 项绑定点击事件处理函数如下:
<view class="cate-lv3-item" v-for="(item3, i3) in item2.children"
:key="i3" @click="gotoGoodsList(item3)">
<image :src="item3.cat_icon"></image>
<text>{{item3.cat_name}}</text>
</view>
2. 定义事件处理函数如下:
// 点击三级分类项跳转到商品列表页面
gotoGoodsList(item3) {
uni.navigateTo({
url: '/subpkg/goods_list/goods_list?cid=' + item3.cat_id
})
}

8. 搜索

8.1 自定义搜索组件(my-search 组件)

1. 在项目根目录的 components 目录上,鼠标右键,选择 新建组件 ,填写组件信息后,最后点
创建 按钮:

2. 定义 my-search 组件的 UI 结构如下:

<template>
<view class="my-search-container">
<!-- 使用 view 组件模拟 input 输入框的样式 -->
<view class="my-search-box">
<uni-icons type="search" size="17"></uni-icons>
<text class="placeholder">搜索</text>
</view>
</view>
</template>
注意:在当前组件中,我们使用 view 组件模拟 input 输入框的效果;并不会在页面上渲染
真正的 input 输入框

3. 美化自定义 search 组件的样式:

.my-search-container {
background-color: #c00000;
height: 50px;
padding: 0 10px;
display: flex;
align-items: center;
}
.my-search-box {
height: 36px;
background-color: #ffffff;
border-radius: 15px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.placeholder {
font-size: 15px;
margin-left: 5px;
}
}
4. 由于自定义的 my-search 组件高度为 50px ,因此,需要重新计算分类页面窗口的可用高
度:
onLoad() {
const sysInfo = uni.getSystemInfoSync()
// 可用高度 = 屏幕高度 - navigationBar高度 - tabBar高度 - 自定义的search组件高
度
this.wh = sysInfo.windowHeight - 50
}

8.2 通过自定义属性增强组件的通用性

1. 通过 props 定义 bgcolor radius 两个属性,并指定值类型和属性默认值:
props: {
// 背景颜色
bgcolor: {
type: String,
default: '#C00000'
},
// 圆角尺寸
radius: {
type: Number,
// 单位是 px
default: 18
}
}
2. 通过属性绑定的形式,为 .my-search-container 盒子和 .my-search-box 盒子动态绑定
style 属性:
<view class="my-search-container" :style="{'background-color': bgcolor}">
<view class="my-search-box" :style="{'border-radius': radius + 'px'}">
<uni-icons type="search" size="17"></uni-icons>
<text class="placeholder">搜索</text>
</view>
</view>
3. 移除对应 scss 样式中的 背景颜色 圆角尺寸
.my-search-container {
// 移除背景颜色,改由 props 属性控制
// background-color: #C00000;
height: 50px;
padding: 0 10px;
display: flex;
align-items: center;
}
.my-search-box {
height: 36px;
background-color: #ffffff;
// 移除圆角尺寸,改由 props 属性控制
// border-radius: 15px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.placeholder {
font-size: 15px;
margin-left: 5px;
}
}

8.3 为自定义组件封装 click 事件

1. my-search 自定义组件内部,给类名为 .my-search-box view 绑定 click 事件处
理函数:
<view class="my-search-box" :style="{'border-radius': radius + 'px'}"
@click="searchBoxHandler">
<uni-icons type="search" size="17"></uni-icons>
<text class="placeholder">搜索</text>
</view>
2. my-search 自定义组件的 methods 节点中,声明事件处理函数如下:
methods: {
// 点击了模拟的 input 输入框
searchBoxHandler() {
// 触发外界通过 @click 绑定的 click 事件处理函数
this.$emit('click')
}
}
3. 在分类页面中使用 my-search 自定义组件时,即可通过 @click 为其绑定点击事件处理函
数:
<!-- 使用自定义的搜索组件 -->
<my-search @click="gotoSearch"></my-search>
1
2
methods: {
// 跳转到分包中的搜索页面
gotoSearch() {
uni.navigateTo({
url: '/subpkg/search/search'
})
}
}

8.4 实现首页搜索组件的吸顶效果

通过如下的样式实现吸顶的效果:
<!-- 使用自定义的搜索组件 -->
<view class="search-box">
<my-search @click="gotoSearch"></my-search>
</view>
1
2
3
4
gotoSearch() {
uni.navigateTo({
url: '/subpkg/search/search'
})
}
.search-box {
// 设置定位效果为“吸顶”
position: sticky;
// 吸顶的“位置”
top: 0;
// 提高层级,防止被轮播图覆盖
z-index: 999;
}

9. 搜索建议

实现搜索框的吸顶效果:
<view class="search-box">
<!-- 使用 uni-ui 提供的搜索组件 -->
<uni-search-bar @input="input" :radius="100" cancelButton="none"></unisearch-bar>
</view>
.uni-searchbar {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
position: relative;
padding: 16rpx;
/* 将默认的 #FFFFFF 改为 #C00000 */
background-color: #c00000;
}
.search-box {
position: sticky;
top: 0;
z-index: 999;
}
methods: {
input(e) {
// e.value 是最新的搜索内容
console.log(e.value)
}
}

9.1 实现搜索框自动获取焦点

1. 修改 components -> uni-search-bar -> uni-search-bar.vue 组件,把 data 数据中的
show showSync 的值,从默认的 false 改为 true 即可:
data() {
return {
show: true,
showSync: true,
searchVal: ""
}
}

9.2 实现搜索框的防抖处理

实现:

data() {
return {
// 延时器的 timerId
timer: null,
// 搜索关键词
kw: ''
}
}
input(e) {
// 清除 timer 对应的延时器
clearTimeout(this.timer)
// 重新启动一个延时器,并把 timerId 赋值给 this.timer
this.timer = setTimeout(() => {
// 如果 500 毫秒内,没有触发新的输入事件,则为搜索关键词赋值
this.kw = e.value
console.log(this.kw)
}, 500)
}

9.3 根据关键词查询搜索建议列表和渲染搜索建议列表

实现:

data() {
return {
// 搜索结果列表
searchResults: []
}
}
this.timer = setTimeout(() => {
this.kw = e.value
// 根据关键词,查询搜索建议列表
this.getSearchList()
}, 500)
// 根据搜索关键词,搜索商品建议列表
async getSearchList() {
// 判断关键词是否为空
if (this.kw === '') {
this.searchResults = []
return
}
// 发起请求,获取搜索建议列表
const { data: res } = await
uni.$http.get('/api/public/v1/goods/qsearch', { query: this.kw })
if (res.meta.status !== 200) return uni.$showMsg()
this.searchResults = res.message
}
<!-- 搜索建议列表 -->
<view class="sugg-list">
<view class="sugg-item" v-for="(item, i) in searchResults" :key="i"
@click="gotoDetail(item.goods_id)">
<view class="goods-name">{{item.goods_name}}</view>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
.sugg-list {
padding: 0 5px;
.sugg-item {
font-size: 12px;
padding: 13px 0;
border-bottom: 1px solid #efefef;
display: flex;
align-items: center;
justify-content: space-between;
.goods-name {
// 文字不允许换行(单行文本)
white-space: nowrap;
// 溢出部分隐藏
overflow: hidden;
// 文本溢出后,使用 ... 代替
text-overflow: ellipsis;
margin-right: 3px;
}
}
}
gotoDetail(goods_id) {
uni.navigateTo({
// 指定详情页面的 URL 地址,并传递 goods_id 参数
url: '/subpkg/goods_detail/goods_detail?goods_id=' + goods_id
})
}

10. 搜索历史

10.1 渲染搜索历史记录的基本结构

1. 实现:

data() {
return {
// 搜索关键词的历史记录
historyList: ['a', 'app', 'apple']
}
}
<!-- 搜索历史 -->
<view class="history-box">
<!-- 标题区域 -->
<view class="history-title">
<text>搜索历史</text>
<uni-icons type="trash" size="17"></uni-icons>
</view>
<!-- 列表区域 -->
<view class="history-list">
<uni-tag :text="item" v-for="(item, i) in historyList" :key="i">
</uni-tag>
</view>
</view>
.history-box {
padding: 0 5px;
.history-title {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
font-size: 13px;
border-bottom: 1px solid #efefef;
}
.history-list {
display: flex;
flex-wrap: wrap;
.uni-tag {
margin-top: 5px;
margin-right: 5px;
}
}
}

10.2 实现搜索建议和搜索历史的按需展示

1. 当搜索结果列表的长度 不为 0 的时候( searchResults.length !== 0 ),需要展示搜索建
议区域,隐藏搜索历史区域
2. 当搜索结果列表的长度 等于 0 的时候( searchResults.length === 0 ),需要隐藏搜索建
议区域,展示搜索历史区域
3. 使用 v-if v-else 控制这两个区域的显示和隐藏,示例代码如下:
<!-- 搜索建议列表 -->
<view class="sugg-list" v-if="searchResults.length !== 0">
<!-- 省略其它代码... -->
</view>
<!-- 搜索历史 -->
<view class="history-box" v-else>
<!-- 省略其它代码... -->
</view>

10.3 将搜索关键词存入 historyList

1. 直接将搜索关键词 push historyList 数组中即可
methods: {
// 根据搜索关键词,搜索商品建议列表
async getSearchList() {
// 省略其它不必要的代码...
// 1. 查询到搜索建议之后,调用 saveSearchHistory() 方法保存搜索关键词
this.saveSearchHistory()
},
// 2. 保存搜索关键词的方法
saveSearchHistory() {
// 2.1 直接把搜索关键词 push 到 historyList 数组中
this.historyList.push(this.kw)
}
}
解决关键字前后顺序的问题;
1. data 中的 historyList 不做任何修改,依然使用 push 进行末尾追加
2. 定义一个计算属性 historys ,将 historyList 数组 reverse 反转之后,就是此计算属性
的值:
computed: {
historys() {
// 注意:由于数组是引用类型,所以不要直接基于原数组调用 reverse 方法,以免修改原数
组中元素的顺序
// 而是应该新建一个内存无关的数组,再进行 reverse 反转
return [...this.historyList].reverse()
}
}<view class="history-list">
<uni-tag :text="item" v-for="(item, i) in historys" :key="i"></uni-tag>
</view>
解决关键词重复的问题:
// 保存搜索关键词为历史记录
saveSearchHistory() {
// this.historyList.push(this.kw)
// 1. 将 Array 数组转化为 Set 对象
const set = new Set(this.historyList)
// 2. 调用 Set 对象的 delete 方法,移除对应的元素
set.delete(this.kw)
// 3. 调用 Set 对象的 add 方法,向 Set 中添加元素
set.add(this.kw)
// 4. 将 Set 对象转化为 Array 数组
this.historyList = Array.from(set)
}

10.4 将搜索历史记录持久化存储到本地

// 保存搜索关键词为历史记录
saveSearchHistory() {
const set = new Set(this.historyList)
set.delete(this.kw)
set.add(this.kw)
this.historyList = Array.from(set)
// 调用 uni.setStorageSync(key, value) 将搜索历史记录持久化存储到本地
uni.setStorageSync('kw', JSON.stringify(this.historyList))
}
onLoad() {
this.historyList = JSON.parse(uni.getStorageSync('kw') || '[]')
}

10.5 清空搜索历史记录

<uni-icons type="trash" size="17" @click="cleanHistory"></uni-icons>
// 清空搜索历史记录
cleanHistory() {
// 清空 data 中保存的搜索历史
this.historyList = []
// 清空本地存储中记录的搜索历史
uni.setStorageSync('kw', '[]')
}

10.6 点击搜索历史跳转到商品列表页面

<uni-tag :text="item" v-for="(item, i) in historys" :key="i"
@click="gotoGoodsList(item)"></uni-tag>
// 点击跳转到商品列表页面
gotoGoodsList(kw) {
uni.navigateTo({
url: '/subpkg/goods_list/goods_list?query=' + kw
})
}

11. 最终实现代码(在上次项目基础之上实现)

1. pages文件中建立cate.vue文件:

<template><view><!-- 使用自定义的搜索组件 --><!-- <my-search :bgcolor="'pink'" :radius="3"></my-search> --><my-search @click="gotoSearch"></my-search><view class="scroll-view-container"><!-- 左侧的滑动区域 --><scroll-view class="left-scroll-view" scroll-y="true" :style="{height: wh + 'px'}"><block v-for="(item, i) in cateList" :key="i"><view :class="['left-scroll-view-item', i === active ? 'active' : '']" @click="activeChanged(i)">{{item.cat_name}}</view></block></scroll-view><!-- 右侧的滑动区域 --><scroll-view scroll-y="true" :style="{height: wh + 'px'}" :scroll-top="scrollTop"><view class="cate-lv2" v-for="(item2, i2) in cateLevel2" :key="i2"><!-- 二级分类的标题 --><view class="cate-lv2-title">/ {{item2.cat_name}} /</view><!-- 当前二级分类下的三级分类列表 --><view class="cate-lv3-list"><!-- 三级分类的Item项 --><view class="cate-lv3-item" v-for="(item3, i3) in item2.children" :key="i3" @click="gotoGoodsList(item3)"><!-- 三级分类的图片 --><image :src="item3.cat_icon"></image><!-- 三级分类的文本 --><text>{{item3.cat_name}}</text></view></view></view></scroll-view></view></view>
</template><script>export default {data() {return {// 当前设备可用的高度wh: 0,cateList: [],active: 0,// 二级分类的列表cateLevel2: [],scrollTop: 0};},onLoad() {const sysInfo = uni.getSystemInfoSync()this.wh = sysInfo.windowHeight - 50this.getCateList()},methods: {// 获取分类列表的数据async getCateList() {const { data: res } = await uni.$http.get('/api/public/v1/categories')if (res.meta.status !== 200) return uni.$showMsg()this.cateList = res.message// 为二级分类赋值this.cateLevel2 = res.message[0].children},activeChanged(i) {this.active = i// 重新为二级分类赋值this.cateLevel2 = this.cateList[i].childrenthis.scrollTop = this.scrollTop === 0 ? 1 : 0},// 跳转到商品列表页面gotoGoodsList(item) {uni.navigateTo({url: '/subpkg/goods_list/goods_list?cid=' + item.cat_id})},gotoSearch() {uni.navigateTo({url: '/subpkg/search/search'})}}}
</script><style lang="scss">.scroll-view-container {display: flex;.left-scroll-view {width: 120px;.left-scroll-view-item {background-color: #F7F7F7;line-height: 60px;text-align: center;font-size: 12px;&.active {background-color: #FFFFFF;position: relative;&::before {content: ' ';display: block;width: 3px;height: 30px;background-color: #C00000;position: absolute;top: 50%;left: 0;transform: translateY(-50%);}}}}}.cate-lv2-title {font-size: 12px;font-weight: bold;text-align: center;padding: 15px 0;}.cate-lv3-list {display: flex;flex-wrap: wrap;.cate-lv3-item {width: 33.33%;display: flex;flex-direction: column;justify-content: center;align-items: center;margin-bottom: 10px;image {width: 60px;height: 60px;}text {font-size: 12px;}}}
</style>

2. pages文件中建立my.vue文件:

<template><view>My</view>
</template><script>export default {data() {return {};}}
</script><style lang="scss"></style>

3. search.vue文件:

<template><view><view class="search-box"><uni-search-bar @input="input" :radius="100" cancelButton="none"></uni-search-bar></view><!-- 搜索建议列表 --><view class="sugg-list" v-if="searchResults.length !== 0"><view class="sugg-item" v-for="(item, i) in searchResults" :key="i" @click="gotoDetail(item)"><view class="goods-name">{{item.goods_name}}</view><uni-icons type="arrowright" size="16"></uni-icons></view></view><!-- 搜索历史 --><view class="history-box" v-else><!-- 标题区域 --><view class="history-title"><text>搜索历史</text><uni-icons type="trash" size="17" @click="clean"></uni-icons></view><!-- 列表区域 --><view class="history-list"><uni-tag :text="item" v-for="(item, i) in histories" :key="i" @click="gotoGoodsList(item)"></uni-tag></view></view></view>
</template><script>export default {data() {return {timer: null,kw: '',// 搜索的结果列表searchResults: [],// 搜索历史的数组historyList: []};},onLoad() {this.historyList = JSON.parse(uni.getStorageSync('kw') || '[]')},methods: {// input 输入事件的处理函数input(e) {clearTimeout(this.timer)this.timer = setTimeout(() => {this.kw = e.valuethis.getSearchList()}, 500)},async getSearchList() {// 判断搜索关键词是否为空if (this.kw.length === 0) {this.searchResults = []return}const { data: res } = await uni.$http.get('/api/public/v1/goods/qsearch', { query: this.kw })if (res.meta.status !== 200) return uni.$showMsg()this.searchResults = res.messagethis.saveSearchHistory()},gotoDetail(item) {uni.navigateTo({url: '/subpkg/goods_detail/goods_detail?goods_id=' + item.goods_id})},saveSearchHistory() {// this.historyList.push(this.kw)const set = new Set(this.historyList)set.delete(this.kw)set.add(this.kw)this.historyList = Array.from(set)// 对搜索历史数据,进行持久化的存储uni.setStorageSync('kw', JSON.stringify(this.historyList))},clean() {this.historyList = []uni.setStorageSync('kw', '[]')},gotoGoodsList(kw) {uni.navigateTo({url: '/subpkg/goods_list/goods_list?query=' + kw})}},computed: {histories() {return [...this.historyList].reverse()}}}
</script><style lang="scss">.search-box {position: sticky;top: 0;z-index: 999;}.sugg-list {padding: 0 5px;.sugg-item {display: flex;align-items: center;justify-content: space-between;font-size: 12px;padding: 13px 0;border-bottom: 1px solid #efefef;.goods-name {white-space: nowrap;overflow: hidden;text-overflow: ellipsis;margin-right: 3px;}}}.history-box {padding: 0 5px;.history-title {display: flex;justify-content: space-between;height: 40px;align-items: center;font-size: 13px;border-bottom: 1px solid #efefef;}.history-list {display: flex;flex-wrap: wrap;.uni-tag {margin-top: 5px;margin-right: 5px;}}}
</style>

4. search-taber文件:

<template><view class="uni-searchbar"><view :style="{borderRadius:radius+'px',backgroundColor: bgColor}" class="uni-searchbar__box" @click="searchClick"><!-- #ifdef MP-ALIPAY --><view class="uni-searchbar__box-icon-search"><uni-icons color="#999999" size="18" type="search" /></view><!-- #endif --><!-- #ifndef MP-ALIPAY --><uni-icons color="#999999" class="uni-searchbar__box-icon-search" size="18" type="search" /><!-- #endif --><input v-if="show" :focus="showSync" :placeholder="placeholder" :maxlength="maxlength" @confirm="confirm" class="uni-searchbar__box-search-input" confirm-type="search" type="text" v-model="searchVal" /><text v-else class="uni-searchbar__text-placeholder">{{ placeholder }}</text><view v-if="show && (clearButton==='always'||clearButton==='auto'&&searchVal!=='')" class="uni-searchbar__box-icon-clear" @click="clear"><uni-icons color="#999999" class="" size="24" type="clear" /></view></view><text @click="cancel" class="uni-searchbar__cancel" v-if="cancelButton ==='always' || show && cancelButton ==='auto'">{{cancelText}}</text></view>
</template><script>import uniIcons from "../uni-icons/uni-icons.vue";/*** SearchBar 搜索栏* @description 评分组件* @tutorial https://ext.dcloud.net.cn/plugin?id=866* @property {Number} radius 搜索栏圆角* @property {Number} maxlength 输入最大长度* @property {String} placeholder 搜索栏Placeholder* @property {String} clearButton = [always|auto|none] 是否显示清除按钮* 	@value always 一直显示* 	@value auto 输入框不为空时显示* 	@value none 一直不显示* @property {String} cancelButton = [always|auto|none] 是否显示取消按钮* 	@value always 一直显示* 	@value auto 输入框不为空时显示* 	@value none 一直不显示* @property {String} cancelText 取消按钮的文字* @property {String} bgColor 输入框背景颜色* @event {Function} confirm uniSearchBar 的输入框 confirm 事件,返回参数为uniSearchBar的value,e={value:Number}* @event {Function} input uniSearchBar 的 value 改变时触发事件,返回参数为uniSearchBar的value,e={value:Number}* @event {Function} cancel 点击取消按钮时触发事件,返回参数为uniSearchBar的value,e={value:Number}*/export default {name: "UniSearchBar",components: {uniIcons},props: {placeholder: {type: String,default: "请输入搜索内容"},radius: {type: [Number, String],default: 5},clearButton: {type: String,default: "auto"},cancelButton: {type: String,default: "auto"},cancelText: {type: String,default: '取消'},bgColor: {type: String,default: "#F8F8F8"},maxlength: {type: [Number, String],default: 100}},data() {return {show: true,showSync: true,searchVal: ""}},watch: {searchVal() {this.$emit("input", {value: this.searchVal})}},methods: {searchClick() {if (this.show) {return}this.searchVal = ""this.show = true;this.$nextTick(() => {this.showSync = true;})},clear() {this.searchVal = ""},cancel() {this.$emit("cancel", {value: this.searchVal});this.searchVal = ""this.show = falsethis.showSync = false// #ifndef APP-PLUSuni.hideKeyboard()// #endif// #ifdef APP-PLUSplus.key.hideSoftKeybord()// #endif},confirm() {// #ifndef APP-PLUSuni.hideKeyboard();// #endif// #ifdef APP-PLUSplus.key.hideSoftKeybord()// #endifthis.$emit("confirm", {value: this.searchVal})}}};
</script><style scoped>.uni-searchbar {/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;position: relative;padding: 16rpx;background-color: #C00000;}.uni-searchbar__box {/* #ifndef APP-NVUE */display: flex;box-sizing: border-box;/* #endif */overflow: hidden;position: relative;flex: 1;justify-content: center;flex-direction: row;align-items: center;height: 36px;padding: 5px 8px 5px 0px;border-width: 0.5px;border-style: solid;border-color: #e5e5e5;}.uni-searchbar__box-icon-search {/* #ifndef APP-NVUE */display: flex;/* #endif */flex-direction: row;width: 32px;justify-content: center;align-items: center;color: #808080;}.uni-searchbar__box-search-input {flex: 1;font-size: 28rpx;color: #333;}.uni-searchbar__box-icon-clear {align-items: center;line-height: 24px;padding-left: 5px;}.uni-searchbar__text-placeholder {font-size: 28rpx;color: #808080;margin-left: 5px;}.uni-searchbar__cancel {padding-left: 10px;line-height: 36px;font-size: 14px;color: #333;}
</style>

5. my-search文件:

<template><view class="my-search-container" :style="{ 'background-color': bgcolor }" @click="searchBoxHandler"><view class="my-search-box" :style="{ 'border-radius': radius + 'px' }"><!-- 使用 uni-ui 提供的图标组件 --><uni-icons type="search" size="17"></uni-icons><text class="placeholder">搜索</text></view></view>
</template><script>export default {props: {// 背景颜色bgcolor: {type: String,default: '#C00000'},// 圆角尺寸radius: {type: Number,default: 18 // px}},data() {return {}},methods: {searchBoxHandler() {this.$emit('click')}}}
</script><style lang="scss">.my-search-container {height: 50px;// background-color: #C00000;display: flex;align-items: center;padding: 0 10px;.my-search-box {height: 36px;background-color: #FFFFFF;// border-radius: 18px;width: 100%;display: flex;justify-content: center;align-items: center;.placeholder {font-size: 15px;margin-left: 5px;}}}
</style>

12. 最终产品效果

13. 下面是之前和本次项目的源码需要的请自取哦

爱吃橘子的猫 - Gitee.comhttps://gitee.com/dragon-rain-tree/

http://www.lryc.cn/news/589516.html

相关文章:

  • 专业文档搜索工具,快速定位文本内容
  • Spring AI Alibaba 1.0 vs Spring AI 深度对比
  • EPLAN 电气制图(九):直流电源绘制+端子排绘制
  • 3.创建表-demo
  • 深入解析环境变量:从基础概念到系统级应用
  • 墨刀原型图的原理、与UI设计图的区别及转换方法详解-卓伊凡|贝贝
  • TypeScript之旅
  • 基于STM32与中航ZH-E3L字符卡通信在LED屏显示数据
  • ArrayList列表解析
  • [附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+vue实现的酒店预订管理系统,推荐!
  • Nestjs框架: 数据库架构设计与 NestJS 多 ORM 动态数据库应用与连接池的配置
  • 缓存穿透的“黑暗森林”假说——当攻击者学会隐藏恶意流量
  • 园区用电成本直降方案:智能微网调控系统一键峰谷优化
  • PHP语法高级篇(三):Cookie与会话
  • OSPF过滤
  • 数据结构——顺序表的相关操作
  • Enhancing Input-Label Mapping in In-Context Learning withContrastive Decoding
  • C++(STL源码刨析/stack/queue/priority_queue)
  • 解决了困扰我的upload靶场无法解析phtml等后缀的问题
  • 辨析git reset三种模式以及和git revert的区别:回退到指定版本和撤销指定版本的操作
  • Python:消息队列(RabbitMQ)应用开发实践
  • BlueLotus XSS管理后台使用指南
  • 数据结构自学Day7-- 二叉树
  • 自增主键为什么不是连续的?
  • 策略设计模式分析
  • Git Bash 实战操作全解析:从初始化到版本管理的每一步细节
  • Spring Boot 启动原理揭秘:从 main 方法到自动装配
  • c#进阶之数据结构(字符串篇)----String
  • HTTP常见误区
  • 跨平台移动开发技术深度分析:uni-app、React Native与Flutter的迁移成本、性能、场景与前景