微信小程序 搜索

自定义搜索框、搜索框封装 click 事件、绑定属性、吸顶效果、防抖处理。


image.png

一、自定义搜索组件

1.1、自定义 my-search 组件

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


image.png
1.2、在分类页面的 UI 结构中,直接以标签的形式使用 my-search 自定义组件:
<!-- 使用自定义的搜索组件 -->
<my-search></my-search>
1.3、定义 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 输入框

1.4、美化自定义 search 组件的样式:
<style lang="scss">
   .my-search-container{
      background-color: #c00000;
      height: 50px;
      padding: 0 10px 0 10px;
      display: flex;
      align-items: center;
     .my-search-box{
        background-color: #FFFFFF;
        width: 100%;
        height: 36px;
        border-radius: 15px;
        display: flex;
        align-items: center;
        justify-content: center;
       .placeholder{
         font-size: 15px;
         margin-left: 5px;
       }
     }
   }
</style>
1.6、由于自定义的 my-search 组件高度为 50px,因此,需要重新计算分类页面窗口的可用高度:
onLoad() {
      //获取设备的高度
      const sysInfo = uni.getSystemInfoSync()
      //获取设备的可使用的高度,此处减去搜索框高度50px
     // 可用高度 = 屏幕高度 - navigationBar高度 - tabBar高度 - 自定义的search组件高度
      const height = sysInfo.windowHeight-50;
      this.wh = height
      this.getCateList();
    },
1.7、通过自定义属性增强组件的通用性

为了增强组件的通用性,我们允许使用者自定义搜索组件的 背景颜色 和 圆角尺寸。
通过 props 定义 bgcolor 和 radius 两个属性,并指定值类型和属性默认值:

export default {
    name:"my-search",
    props:{
      //背景颜色
      backgroundColor:{
        type: String,
        default:'#c00000'
      },
      //圆角尺寸
      redius:{
        type:Number,
        default:18
      },
    },
    
    data() {
      return {
        
      };
    }
  }
1.8、通过属性绑定的形式,为 .my-search-container 盒子和 .my-search-box 盒子动态绑定 style 属性:
<template>
  <!-- 自定义搜索组件 -->
  <view class="my-search-container" :style="{'background-color':backgroundColor}">
    <view class = "my-search-box" :style="{'border-radius':redius +'px'}">
       <uni-icons type="search" size="17"></uni-icons>
       <text class = "placeholder">搜索</text>
    </view>
  </view>
</template>
1.9、移除对应 scss 样式中的 背景颜色 和 圆角尺寸:
<style lang="scss">
   .my-search-container{
      //移除搜索框背景颜色,改由 props 属性控制
      // background-color: #c00000;
      height: 50px;
      padding: 0 10px 0 10px;
      display: flex;
      align-items: center;
     .my-search-box{
        background-color: #FFFFFF;
        width: 100%;
        height: 36px;
       //移除搜索框圆角尺寸,改由 props 属性控制
        // border-radius: 15px;
        display: flex;
        align-items: center;
        justify-content: center;
       .placeholder{
         font-size: 15px;
         margin-left: 5px;
       }
     }
   }
</style>
1.10、引用搜索组件
image.png

二、为自定义组件封装 click 事件

2.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.2、在 my-search 自定义组件的 methods 节点中,声明事件处理函数如下:
methods: {
  // 点击了模拟的 input 输入框
  searchBoxHandler() {
    // 触发外界通过 @click 绑定的 click 事件处理函数
    this.$emit('click')
  }
}

调用this.$emit()进行组件事件触发

2.3、在分类页面中使用 my-search 自定义组件时,即可通过 @click 为其绑定点击事件处理函数:
<!-- 使用自定义的搜索组件 -->
<my-search @click="gotoSearch"></my-search>

同时在分类页面中,定义 gotoSearch 事件处理函数如下:

methods: {
   // 跳转到分包中的搜索页面
   gotoSearch() {
     uni.navigateTo({
       url: '/subpkg/search/search'
     })
   }
}
2.4、点击搜索框进行页面跳转
image.png

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

3.1、在 home 首页定义如下的 UI 结构:
<!-- 使用自定义的搜索组件 -->
<view class="search-box">
  <my-search @click="gotoSearch"></my-search>
</view>
3.2、在 home 首页定义如下的事件处理函数:
gotoSearch() {
  uni.navigateTo({
    url: '/subpkg/search/search'
  })
}
3.3、通过如下的样式实现吸顶的效果:
.search-box {
  // 设置定位效果为“吸顶”
  position: sticky;
  // 吸顶的“位置”
  top: 0;
  // 提高层级,防止被轮播图覆盖
  z-index: 999;
}

四、 搜索建议

4.1、 渲染搜索页面的基本结构
<view class="search-box">
  <!-- 使用 uni-ui 提供的搜索组件 -->
  <uni-search-bar @input="input" :radius="100" cancelButton="none"></uni-search-bar>
</view>
4.2、修改 uni-modules -> uni-search-bar -> uni-search-bar.vue 组件,将默认的白色搜索背景改为 #C00000 的红色背景:
.uni-searchbar {
  /* #ifndef APP-NVUE */
  display: flex;
  /* #endif */
  flex-direction: row;
  position: relative;
  padding: 16rpx;
  /* 将默认的 #FFFFFF 改为 #C00000 */
  background-color: #c00000;
}
4.3、实现搜索框的吸顶效果:
.search-box {
  position: sticky;
  top: 0;
  z-index: 999;
}
4.4、定义如下的 input 事件处理函数:
methods: {
  input(e) {
    // e是最新的搜索内容
    console.log(e)
  }
}

五、实现搜索框自动获取焦点

5.1、修改 uni-modules-> uni-search-bar -> uni-search-bar.vue 组件,把 data 数据中的 show 和 showSync 的值,从默认的 false 改为 true 即可:
data() {
  return {
    show: true,
    showSync: true,
    searchVal: ""
  }
}
image.png
5.2、 使用手机扫码预览,即可在真机上查看效果
真机效果图.jpg

六、实现搜索框的防抖处理

问题:搜索框在进行内容输入时,会多次触发input事件,导致在输入期间连续发送多次网络请求。如下图:


image.png
6.1、在 data 中定义防抖的延时器 timerId 如下:
data() {
  return {
    // 延时器的 timerId
    timer: null,
    // 搜索关键词
    kw: ''
  }
}
6.2、修改 input 事件处理函数如下:
input(e) {
  // 清除 timer 对应的延时器
  clearTimeout(this.timer)
  // 重新启动一个延时器,并把 timerId 赋值给 this.timer
  this.timer = setTimeout(() => {
    // 如果 500 毫秒内,没有触发新的输入事件,则为搜索关键词赋值
    //this.kw = e.value
    this.kw = e
    console.log(this.kw)
  }, 500)
}
6.3、效果如下
image.png

七、搜索历史

7.1、渲染搜索历史记录的基本结构
7.1.1、在 data 中定义搜索历史的假数据:
data() {
      return {
          // 延时器的 timer
          timer: null,
          // 搜索关键词
          kw: '',
          //搜索结果列表
          searchList:[],
          //搜索历史
          historyList:['a','app','apple']
        }
    },
7.1.2、渲染搜索历史区域的 UI 结构:
<!-- 搜索历史 -->
<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>
7.1.3、美化搜索历史区域的样式:
.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;
    }
  }
}
7.1.4、实现搜索建议和搜索历史的按需展示

当搜索结果列表的长度不为 0的时候(searchResults.length !== 0),需要展示搜索建议区域,隐藏搜索历史区域

当搜索结果列表的长度等于 0的时候(searchResults.length === 0),需要隐藏搜索建议区域,展示搜索历史区域

使用 v-if 和 v-else 控制这两个区域的显示和隐藏,示例代码如下:

<!-- 搜索建议列表 -->
<view class="sugg-list" v-if="searchResults.length !== 0">
  <!-- 省略其它代码... -->
</view>

<!-- 搜索历史 -->
<view class="history-box" v-else>
  <!-- 省略其它代码... -->
</view>
7.1.5、将搜索关键词存入 historyList

直接将搜索关键词 push 到 historyList 数组中即可

methods: {
  // 根据搜索关键词,搜索商品建议列表
  async getSearchList() {
    // 省略其它不必要的代码...

    // 1. 查询到搜索建议之后,调用 saveSearchHistory() 方法保存搜索关键词
    this.saveSearchHistory()
  },
  // 2. 保存搜索关键词的方法
  saveSearchHistory() {
    // 2.1 直接把搜索关键词 push 到 historyList 数组中
    this.historyList.push(this.kw)
  }
}
  1. 上述实现思路存在的问题:
    关键词前后顺序的问题(可以调用数组的 reverse() 方法对数组进行反转)
    关键词重复的问题(可以使用set对象进行去重)
7.1.5、解决关键字前后顺序的问题

data 中的 historyList 不做任何修改,依然使用 push 进行末尾追加

2.定义一个计算属性 historys,将 historyList 数组 reverse 反转之后,就是此计算属性的值:

computed: {
  historys() {
    // 注意:由于数组是引用类型,所以不要直接基于原数组调用 reverse 方法,以免修改原数组中元素的顺序
    // 而是应该新建一个内存无关的数组,再进行 reverse 反转
    return [...this.historyList].reverse()
  }
}

3.页面中渲染搜索关键词的时候,不再使用 data 中的 historyList,而是使用计算属性 historys:

<view class="history-list">
  <uni-tag :text="item" v-for="(item, i) in historys" :key="i"></uni-tag>
</view>
7.1.7、解决关键词重复的问题

1、修改 saveSearchHistory 方法如下:

// 保存搜索关键词为历史记录
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))
}

2、在 onLoad 生命周期函数中,加载本地存储的搜索历史记录:

onLoad() {
  this.historyList = JSON.parse(uni.getStorageSync('kw') || '[]')
}
7.1.8、清空搜索历史记录

1、为清空的图标按钮绑定 click 事件:

<uni-icons type="trash" size="17" @click="cleanHistory"></uni-icons>

2、在 methods 中定义 cleanHistory 处理函数:

// 清空搜索历史记录
cleanHistory() {
  // 清空 data 中保存的搜索历史
  this.historyList = []
  // 清空本地存储中记录的搜索历史
  uni.setStorageSync('kw', '[]')
}
7.1.9、 点击搜索历史跳转到商品列表页面

1、为搜索历史的 Item 项绑定 click 事件处理函数:

<uni-tag :text="item" v-for="(item, i) in historys" :key="i" @click="gotoGoodsList(item)"></uni-tag>

2、在 methods 中定义 gotoGoodsList 处理函数:

// 点击跳转到商品列表页面
gotoGoodsList(kw) {
  uni.navigateTo({
    url: '/subpkg/goods_list/goods_list?query=' + kw
  })
}

八、代码

<template>
  <view>
  <view class="search-box">
    <!-- 使用 uni-ui 提供的搜索组件 -->
    <uni-search-bar @input="input" :radius="100" cancelButton="none"></uni-search-bar>
  </view>
  <!--商品搜索列表-->
  <view class = "sugg-list" v-if="searchList.length !== 0">
    <view class = "sugg-item" v-for="(item,i) in searchList" key="i" @click="gotoDetail(item)">
      <view class="goods-desc" >{{item.desc}}</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="clearHitory"></uni-icons>
    </view>
    <!--列表区域-->
    <view class = "history-list">
      <uni-tag :text="item" v-for="(item,i) in histoies" key="i" @click="gotoList(item)"></uni-tag>
    </view>
  </view>
  </view>
</template>

<script>
  export default {
    data() {
      return {
          // 延时器的 timer
          timer: null,
          // 搜索关键词
          kw: '',
          //搜索结果列表
          searchList:[],
          //搜索历史
          historyList:['a','app','apple']
        }
    },
    onLoad() {
      this.historyList = JSON.parse(uni.getStorageSync('kw')||'[]')
    },
    methods: {
      input(e) {
        // 清除 timer 对应的延时器
        clearTimeout(this.timer)
        // 重新启动一个延时器,并把 timerId 赋值给 this.timer
        this.timer = setTimeout(() => {
          // 如果 500 毫秒内,没有触发新的输入事件,则为搜索关键词赋值
          this.kw = e
          console.log(this.kw)
          this.findSearch(this.kw)
        }, 500)
      },
      async findSearch(kw){
        //判断关键词是否为空
        if(this.kw === ''){
          this.searchList = []
          return
          }
        const res = await uni.$http.get('/goods_list/v1/findSearch?kw='+kw)
        if(res.statusCode!==200){return uni.$showMsg()}
        this.searchList = res.data.data
        //保存搜索历史
        this.saveSearchHistory(kw)
        
        console.log("searchList==>",this.searchList)
      },
      gotoDetail(item){
        console.log("gotoDetail")
        uni.navigateTo({
           // 指定详情页面的 URL 地址,并传递 goods_id 参数
          url:'/subpkg/goods_detail/goods_detail?goods_id='+item.id
        })
      },
      //将搜索关键词保存到搜索历史中
      saveSearchHistory(kw){
        let set = new Set(this.historyList)
        set.delete(kw)
        set.add(kw)
        this.historyList = Array.from(set)
         // 调用 uni.setStorageSync(key, value) 将搜索历史记录持久化存储到本地
          uni.setStorageSync('kw', JSON.stringify(this.historyList))
      },
      //点击搜索历史标签跳转到商品列表页
      gotoList(kw){
        uni.navigateTo({
          url:'/subpkg/goods_list/goods_list?query='+kw
        })
      },
      //清空搜索历史记录
      clearHitory(){
        // 清空 data 中保存的搜索历史
          this.historyList = []
          // 清空本地存储中记录的搜索历史
          uni.setStorageSync('kw', '[]')
      }
    },
    computed:{
      histoies(){
       return  [...this.historyList].reverse()
      }
    }
  }
</script>

<style lang="scss">
.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;
}

.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-desc{
    /* 文字不允许换行(单行文本) */
    white-space: nowrap;
    /* 益出部分隐藏 */
    overflow: hidden;
    /* 文本益出部分用省略号...代替 */
    text-overflow: ellipsis;
    margin-right: 3px;
  }
  
  .history-box{
    padding: 0 5px;
    .history-title{
      display: flex;
      align-items: center;
      justify-content: space-between;
      font-size: 13px;
      height: 40px;
      border-bottom: 1px solid #efefef;
    }
    
    .history-list{
      display: flex;
      flex-wrap: wrap;
      .uni-tag{
        margin-top: 5px;
        margin-right: 5px;
      }
    }
  }
</style>

九、效果图

image.png

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容