鸿蒙OS&UniApp实现个性化的搜索框与搜索历史记录#三方框架 #Uniapp

发布于:2025-05-17 ⋅ 阅读:(17) ⋅ 点赞:(0)

使用UniApp实现个性化的搜索框与搜索历史记录

在移动端应用开发中,搜索功能几乎是标配,而一个好的搜索体验不仅仅是功能的实现,更是用户留存的关键。本文将分享如何在UniApp框架下打造一个既美观又实用的搜索框,并实现搜索历史记录功能。

前言

做了这么多年的前端开发,发现搜索功能看似简单,但要做好却不容易。特别是在跨端开发框架UniApp中,既要考虑不同平台的兼容性,又要保证用户体验的一致性。最近在一个电商项目中,我重新设计了搜索模块,今天就把这部分经验分享给大家。

需求分析

首先明确我们要实现的功能:

  1. 一个美观的搜索框,支持输入搜索关键词
  2. 实时显示搜索建议(热门搜索)
  3. 记录用户的搜索历史,并提供查看和清除功能
  4. 点击历史记录可以快速进行搜索

这些看似常见的功能,实现起来却有不少细节需要注意。接下来,我们一步步来实现。

基础界面搭建

首先,我们创建一个搜索页面search.vue

<template>
  <view class="search-container">
    <!-- 搜索框部分 -->
    <view class="search-header">
      <view class="search-box">
        <text class="iconfont icon-search"></text>
        <input 
          type="text" 
          v-model="keyword" 
          confirm-type="search"
          placeholder="请输入搜索关键词" 
          @confirm="handleSearch"
          @input="handleInput"
        />
        <text v-if="keyword" class="iconfont icon-close" @tap="clearKeyword"></text>
      </view>
      <text class="cancel-btn" @tap="goBack">取消</text>
    </view>
    
    <!-- 内容区域 -->
    <view class="search-content">
      <!-- 搜索历史 -->
      <view class="history-section" v-if="searchHistory.length > 0">
        <view class="section-header">
          <text>搜索历史</text>
          <text class="iconfont icon-delete" @tap="clearHistory"></text>
        </view>
        <view class="tag-list">
          <view 
            class="tag-item" 
            v-for="(item, index) in searchHistory" 
            :key="index"
            @tap="searchByTag(item)"
          >
            {{item}}
          </view>
        </view>
      </view>
      
      <!-- 热门搜索 -->
      <view class="hot-section">
        <view class="section-header">
          <text>热门搜索</text>
        </view>
        <view class="tag-list">
          <view 
            class="tag-item" 
            v-for="(item, index) in hotKeywords" 
            :key="index"
            @tap="searchByTag(item)"
          >
            {{item}}
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      keyword: '',
      searchHistory: [],
      hotKeywords: ['手机', '电脑', '耳机', '平板', '相机', '手表', '家电', '配件']
    }
  },
  onLoad() {
    // 页面加载时获取历史记录
    this.getSearchHistory()
  },
  methods: {
    handleSearch() {
      if (!this.keyword.trim()) return
      
      // 保存搜索记录
      this.saveSearchHistory(this.keyword)
      
      // 执行搜索跳转
      this.navigateToResult(this.keyword)
    },
    searchByTag(keyword) {
      this.keyword = keyword
      this.handleSearch()
    },
    clearKeyword() {
      this.keyword = ''
    },
    goBack() {
      uni.navigateBack()
    },
    handleInput() {
      // 可以在这里实现输入联想功能
    },
    getSearchHistory() {
      const history = uni.getStorageSync('searchHistory')
      if (history) {
        this.searchHistory = JSON.parse(history)
      }
    },
    saveSearchHistory(keyword) {
      let history = [...this.searchHistory]
      
      // 如果已存在相同关键词,先移除
      const index = history.findIndex(item => item === keyword)
      if (index !== -1) {
        history.splice(index, 1)
      }
      
      // 将新关键词添加到头部
      history.unshift(keyword)
      
      // 只保留最近10条记录
      if (history.length > 10) {
        history = history.slice(0, 10)
      }
      
      this.searchHistory = history
      uni.setStorageSync('searchHistory', JSON.stringify(history))
    },
    clearHistory() {
      uni.showModal({
        title: '提示',
        content: '确定要清空搜索历史吗?',
        success: res => {
          if (res.confirm) {
            this.searchHistory = []
            uni.removeStorageSync('searchHistory')
          }
        }
      })
    },
    navigateToResult(keyword) {
      uni.navigateTo({
        url: `/pages/search-result/search-result?keyword=${encodeURIComponent(keyword)}`
      })
    }
  }
}
</script>

<style lang="scss">
.search-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background-color: #f8f8f8;
  
  .search-header {
    display: flex;
    align-items: center;
    padding: 20rpx 30rpx;
    background-color: #ffffff;
    
    .search-box {
      flex: 1;
      display: flex;
      align-items: center;
      height: 70rpx;
      background-color: #f5f5f5;
      border-radius: 35rpx;
      padding: 0 20rpx;
      
      .iconfont {
        font-size: 36rpx;
        color: #999;
      }
      
      input {
        flex: 1;
        height: 100%;
        margin: 0 15rpx;
        font-size: 28rpx;
      }
      
      .icon-close {
        padding: 10rpx;
      }
    }
    
    .cancel-btn {
      margin-left: 20rpx;
      font-size: 28rpx;
      color: #333;
    }
  }
  
  .search-content {
    flex: 1;
    padding: 30rpx;
    
    .section-header {
      display: flex;
      justify-content: space-between;
      margin-bottom: 20rpx;
      font-size: 30rpx;
      color: #333;
      
      .iconfont {
        font-size: 32rpx;
        color: #999;
      }
    }
    
    .tag-list {
      display: flex;
      flex-wrap: wrap;
      
      .tag-item {
        padding: 10rpx 30rpx;
        margin: 0 20rpx 20rpx 0;
        background-color: #ffffff;
        border-radius: 30rpx;
        font-size: 26rpx;
        color: #666;
      }
    }
    
    .history-section {
      margin-bottom: 50rpx;
    }
  }
}
</style>

实现搜索历史功能

在上面的代码中,我们已经基本实现了搜索历史的保存和展示功能。整个实现思路是:

  1. 在用户进行搜索时,通过saveSearchHistory方法将关键词保存到本地存储
  2. 页面加载时,通过getSearchHistory从本地读取历史记录
  3. 点击历史记录标签可以快速发起搜索
  4. 提供清空历史记录的功能

但是,这里还有一些优化点,比如:

1. 防止重复保存

当用户多次搜索相同关键词时,我们不应该重复保存。在代码中,我们先查找是否已存在相同关键词,如果存在则先移除,然后将新的关键词添加到列表头部,这样可以保证最近搜索的内容总是排在最前面。

2. 限制历史记录数量

为了避免占用过多存储空间,我们限制只保留最近的10条搜索记录:

// 只保留最近10条记录
if (history.length > 10) {
  history = history.slice(0, 10)
}

进阶:实现搜索建议功能

现在,我们的搜索框基本功能已经实现了,但是为了提升用户体验,我们可以增加实时搜索建议功能。当用户输入关键词时,后台可以返回相关的搜索建议。

<template>
  <!-- 搜索建议列表 -->
  <view class="suggestion-list" v-if="keyword && suggestions.length > 0">
    <view 
      class="suggestion-item" 
      v-for="(item, index) in suggestions" 
      :key="index"
      @tap="searchByTag(item)"
    >
      <text class="iconfont icon-search"></text>
      <text>{{item}}</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      // ...现有代码
      suggestions: []
    }
  },
  methods: {
    // ...现有代码
    
    // 修改handleInput方法
    handleInput() {
      if (!this.keyword.trim()) {
        this.suggestions = []
        return
      }
      
      // 这里模拟从服务端获取搜索建议
      // 实际开发中,应该调用API获取数据
      setTimeout(() => {
        if (this.keyword) {
          this.suggestions = this.mockSuggestions(this.keyword)
        }
      }, 300)
    },
    
    mockSuggestions(keyword) {
      // 这只是一个模拟的建议列表,实际开发中应该调用API
      const allSuggestions = {
        '手': ['手机', '手表', '手机壳', '手持云台'],
        '电': ['电脑', '电视', '电饭煲', '电动牙刷'],
        '耳': ['耳机', '耳麦', '耳温枪']
      }
      
      for (const key in allSuggestions) {
        if (keyword.includes(key)) {
          return allSuggestions[key]
        }
      }
      
      return []
    }
  }
}
</script>

<style lang="scss">
// 添加搜索建议样式
.suggestion-list {
  position: absolute;
  top: 110rpx;
  left: 0;
  right: 0;
  background-color: #fff;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
  z-index: 999;
  
  .suggestion-item {
    display: flex;
    align-items: center;
    padding: 20rpx 30rpx;
    border-bottom: 1px solid #f5f5f5;
    
    .iconfont {
      font-size: 32rpx;
      color: #999;
      margin-right: 15rpx;
    }
    
    text {
      font-size: 28rpx;
      color: #333;
    }
  }
}
</style>

防抖处理

为了避免频繁请求服务器,我们可以对输入事件进行防抖处理:

// 需要先定义一个定时器变量
data() {
  return {
    // ...现有数据
    debounceTimer: null
  }
},

// 修改handleInput方法
handleInput() {
  if (!this.keyword.trim()) {
    this.suggestions = []
    return
  }
  
  // 清除之前的定时器
  clearTimeout(this.debounceTimer)
  
  // 设置新的定时器
  this.debounceTimer = setTimeout(() => {
    // 调用API获取搜索建议
    this.getSuggestions()
  }, 300)
},

// 获取搜索建议的方法
getSuggestions() {
  // 实际开发中应该调用API
  // 这里使用模拟数据
  this.suggestions = this.mockSuggestions(this.keyword)
  
  // 真实API调用可能是这样:
  /*
  uni.request({
    url: 'https://your-api.com/suggestions',
    data: {
      keyword: this.keyword
    },
    success: (res) => {
      this.suggestions = res.data.suggestions || []
    }
  })
  */
}

实战案例:电商搜索页面

在实际的电商项目中,我们不仅需要基础的搜索功能,还需要分类筛选、排序等高级功能。以下是一个简化的电商搜索结果页面:

<!-- search-result.vue -->
<template>
  <view class="result-container">
    <!-- 搜索框 -->
    <view class="search-bar">
      <view class="search-box" @tap="goToSearch">
        <text class="iconfont icon-search"></text>
        <text class="placeholder">{{keyword || '请输入搜索关键词'}}</text>
      </view>
    </view>
    
    <!-- 筛选条件 -->
    <view class="filter-bar">
      <view 
        class="filter-item" 
        :class="{active: currentSort === 'default'}"
        @tap="changeSort('default')"
      >
        综合
      </view>
      <view 
        class="filter-item" 
        :class="{active: currentSort === 'sales'}"
        @tap="changeSort('sales')"
      >
        销量
      </view>
      <view 
        class="filter-item" 
        :class="{active: currentSort === 'price'}"
        @tap="changeSort('price')"
      >
        价格
        <text class="iconfont" :class="priceOrder === 'asc' ? 'icon-up' : 'icon-down'"></text>
      </view>
      <view 
        class="filter-item" 
        :class="{active: showFilter}"
        @tap="toggleFilter"
      >
        筛选
        <text class="iconfont icon-filter"></text>
      </view>
    </view>
    
    <!-- 商品列表 -->
    <view class="goods-list">
      <view 
        class="goods-item" 
        v-for="(item, index) in goodsList" 
        :key="index"
        @tap="goToDetail(item.id)"
      >
        <image :src="item.image" mode="aspectFill"></image>
        <view class="goods-info">
          <view class="goods-title">{{item.title}}</view>
          <view class="goods-price">¥{{item.price}}</view>
          <view class="goods-extra">
            <text class="sales">销量 {{item.sales}}</text>
            <text class="comment">评价 {{item.comment}}</text>
          </view>
        </view>
      </view>
    </view>
    
    <!-- 无结果提示 -->
    <view class="empty-tip" v-if="goodsList.length === 0">
      <image src="/static/images/empty.png"></image>
      <text>没有找到相关商品</text>
    </view>
  </view>
</template>

总结与优化建议

通过本文,我们实现了一个功能完善的搜索框和搜索历史记录功能。但在实际应用中,还可以做以下优化:

  1. 搜索框自动聚焦:页面加载时,自动让输入框获得焦点,提高用户体验
  2. 历史记录去重:除了当前的去重逻辑,还可以考虑对相似关键词进行合并
  3. 个性化推荐:根据用户的历史搜索记录,提供个性化的搜索推荐
  4. 搜索结果缓存:对热门搜索结果进行缓存,提高加载速度
  5. 语音搜索:集成语音识别API,支持语音搜索功能
  6. 图片搜索:支持上传图片进行搜索,特别适用于电商场景

最后的思考

在移动端应用中,搜索功能是连接用户与内容的桥梁。一个好的搜索体验,能够大大提升用户对应用的满意度。因此,我们不仅要关注功能的实现,还要从用户体验的角度出发,打造更加人性化的搜索功能。

希望本文能对你在UniApp中实现搜索功能有所帮助。如果有任何问题或建议,欢迎在评论区交流讨论!


网站公告

今日签到

点亮在社区的每一天
去签到