移动端 - 搜索组件(suggest篇)

2023-11-01

这一篇博客是和 search-input篇 衔接的, 需要的可以看上文

移动端 - 搜索组件(search-list篇)

这里我们需要去封装这么一个组件

 先说一下大致的方向:

1. 根据父组件传入的关键字数据发送请求获取后端数据, 进行模板渲染

2. 处理一些边界情况(后端返回数据为空, 初次加载数据的时候触发了上拉加载)

3. 实现上拉加载

4. 实现点击歌曲数据的逻辑交互

5. 实现点击歌手数据的逻辑交互


第一步:

首先定义 suggest 组件大体结构和样式

<template>
  <div class="suggest">
    <ul class="suggest-list">

      <!-- 歌手名 -->
      <li class="suggest-item" v-if="singer">
        <div class="icon">
          <i class="icon-mine"></i>
        </div>
        <div class="name">
          <p class="text">{{ singer.name }}</p>
        </div>
      </li>

      <!-- 歌名和专辑名 -->
      <li class="suggest-item" v-for="song in songs" :key="song.id">
        <div class="icon">
          <i class="icon-music"></i>
        </div>
        <div class="name">
          <p class="text">
            {{song.singer}}-{{song.name}}
          </p>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'suggestCom',
  setup () {
    
  }
}
</script>

<style lang="scss" scoped>
.suggest {
  height: 100%;
  overflow: hidden;
  .suggest-list {
    padding: 0 30px;
    .suggest-item {
      display: flex;
      align-items: center;
      padding-bottom: 20px;
      .icon {
        flex: 0 0 30px;
        width: 30px;
        [class^="icon-"] {
          font-size: 14px;
          color: $color-text-d;
        }
      }
      .name {
        flex: 1;
        font-size: $font-size-medium;
        color: $color-text-d;
        overflow: hidden;
        .text {
          @include no-wrap();
        }
      }
    }
  }
}
</style>

第二步:

根据父组件传入的关键字数据的改变, 发送请求获取后端数据; 改变模板数据

因为 suggest 组件是一个公共业务组件, 为了更好的进行复用; 所以需要接收到两个数据

1. query (父组件传入的搜索关键字)

2. showSinger (后端发送请求时所需参数, 返回数据中是否添加歌手数据)

 我们将 showSinger 通过父组传入的是有原因的

因为 suggest 组件必经是一个公共的业务组件, 有这样一个场景; 就是添加歌曲数据, 这个需求是可以继续搜索的

因为指明了说是添加歌曲数据, 所以搜索的结果数据是不需要显示歌手的数据的; 所以后端接收到的 showSinger 是 false

<template>
    ...
</template>

<script>
export default {
  name: 'suggestCom',
  props: {
    query: {
        type: String,
        default: ''
    },
    showSinger: {
        type: Boolean,
        default: true
    }
  },
  setup () {
    
  }
}
</script>

<style lang="scss" scoped>
...
</style>

然后定义后端需要的数据和存储后端响应式数据:

<template>
    ...
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'suggestCom',
  props: {
    query: {
        type: String,
        default: ''
    },
    showSinger: {
        type: Boolean,
        default: true
    }
  },
  setup () {
    // 模板渲染所需数据
    const singer = ref(null)
    const songs = ref([])

    // 后端请求所需数据
    const hasMore = ref(true) // 初始化是否可以加载更多数据
    const page = ref(1)
  }
}
</script>

<style lang="scss" scoped>
...
</style>

然后我们需要监听 query 的数据变化, 调用请求接口 api 发送请求获取数据

<script>
watch(() => props.query, async (newQuery) => {
    // 如果搜索数据query为空就什么都不做
    if (!newQuery) return
    await searchFirst()
})

// 第一次搜索需要对数据进行重置
const searchFirst = async () => {
    page.value = 1
    songs.value = []
    singer.value = null
    hasMore.value = true

    // 然后调用接口获取数据, 将模板使用的数据进行覆盖
    const result = await search(props.query, page.value, props.showSinger)
    // 这里是调用获取歌曲的url数据接口
    songs.value = await processSongs(result.songs)
    singer.value = result.singer
    hasMore.value = result.hasMore
}
</script>

这里所说的 "第一次搜索" , 并不是真正的第一次搜索; 因为后面会涉及到加载更多

所以 "第一次搜索" 所说的是, 除了上拉加载以外的数据获取都会去调用 searchFirst 这个方法获取数据

当数据还未返回时, 可以显示一个 loading 的效果, v-loading全局自定义指令封装讲解

<template>
  <div class="suggest" v-loading:[loadingText]="loading">
    ...
  </div>
</template>

<script>
export default {
  name: 'suggestCom',
  setup () {
    const loadingText = ''
    ...
    
    const loading = computed(() => {
      return !singer.value && !songs.value.length
    })

    async function searchFirst () {
      ...
    }

    return {
      singer,
      songs,
      hasMore,
      loadingText,
      loading
    }
  }
}
</script>

<style lang="scss" scoped>
...
</style>

最后实在 search 父组件中进行导入, 给 suggest 传入 query 参数

根据 query 参数动态渲染对应组件(热门搜索和搜索结果组件不会同时显示)

<template>
  <div class="search">
    <!-- search-input组件 -->
    <div class="search-input-wrapper">
      <SearchInput v-model="query"></SearchInput>
    </div>

    <!-- 热门搜索 -->
    <div class="search-content" v-show="!query">
      <div class="hot-keys">
        <h1 class="title">热门搜索</h1>
        <ul>
          <li
            class="item"
            v-for="item in hotKeys"
            :key="item.id"
            @click="addQuery(item.key)"
          >
            <span>{{item.key}}</span>
          </li>
        </ul>
      </div>
    </div>

    <!-- 搜索结果 -->
    <div class="search-result" v-show="query">
	    <Suggest :query="query" />
    </div>
  </div>
</template>

第三步:

 对 suggest 组件根据 query 搜索, 后端返回数据为空的情况进行处理

 其实这里只需要通过判断 songs 的数据和 loading 的数据, 通过一个自定义指令来显示 "没有搜索结果" 结果的效果

因为 v-no-result 的效果和 v-loading 的效果是一样的, 所以直接使用同一个配置项来完成 这里有讲到

<template>
  <div
    class="suggest"
    v-loading:[loadingText]="loading"
    v-no-result:[noResultText]="noResult"
  >
    <ul class="suggest-list">

      <!-- 歌手名 -->
      <li class="suggest-item" v-if="singer">
        <div class="icon">
          <i class="icon-mine"></i>
        </div>
        <div class="name">
          <p class="text">{{ singer.name }}</p>
        </div>
      </li>

      <!-- 歌名和专辑名 -->
      <li class="suggest-item" v-for="song in songs" :key="song.id">
        <div class="icon">
          <i class="icon-music"></i>
        </div>
        <div class="name">
          <p class="text">
            {{song.singer}}-{{song.name}}
          </p>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
import { ref, watch, computed, nextTick } from 'vue'
import { search } from '@/api/search'
import { processSongs } from '@/api/song'
export default {
  name: 'suggestCom',
  props: {
    query: {
      type: String,
      default: ''
    },
    showSinger: {
      type: Boolean,
      default: true
    }
  },
  emits: ['selectSong', 'selectSinger'],
  setup (props, { emit }) {
    const singer = ref(null)
    const songs = ref([])
    const hasMore = ref(true)
    const page = ref(1)
    const loadingText = ref('')
    const noResultText = ref('没有搜索到相关的歌手、歌曲')

    const loading = computed(() => {
      return !singer.value && !songs.value.length
    })

    // singer, songs, hasMore为false的时候就说明后端没有数据了
    // hasMore每一次发送请求获取后端数据式, 会返回这个数据
    // 当hasMore为false的时候, 就说明后端没有数据了
    const noResult = computed(() => {
      return !singer.value && !songs.value.length && !hasMore.value
    })

    watch(() => props.query, async (newQuery) => {
      if (!newQuery) return
      await searchFirst()
    })

    async function searchFirst () {
      page.value = 1
      songs.value = []
      singer.value = null
      hasMore.value = true

      const result = await search(props.query, page.value, props.showSinger)
      songs.value = await processSongs(result.songs)
      singer.value = result.singer
      hasMore.value = result.hasMore
    }

    return {
      singer,
      songs,
      hasMore,
      loadingText,
      noResult,
      noResultText,
    }
  }
}
</script>

<style lang="scss" scoped>
...
</style>

第四步:

实现上拉加载交互

这里我们会使用到 betterScroll 中的 pullup 插件来完成

npm install @better-scroll/core --save
npm install @better-scroll/pull-up --save
npm install @better-scroll/observe-dom --save

然后我们会将上拉加载的逻辑进行抽离成单独的 js 文件(use-pull-up-load.js), 预防其他组件会使用到

// 导入核心滚动BScroll
import BScroll from '@better-scroll/core'
// 导入上拉加载插件PullUp
import PullUp from '@better-scroll/pull-up'
// 导入动态监听DOM变化插件
import ObserveDOM from '@better-scroll/observe-dom'

import { ref, onMounted, onUnmounted } from 'vue'

// 将插件注册到BScroll中
BScroll.use(PullUp)
BScroll.use(ObserveDOM)

// 向外默认提供一个钩子函数
export default function usePullUpLoad (requestData) {
  // new BScroll存储实例对象
  const scroll = ref(null)
  // 模板DOM元素实例
  const rootRef = ref(null)
  // 是否正在加载变量, 模板中需要使用到
  const isPullUpLoad = ref(false)

  // 在组件挂载的时候
  onMounted(() => {
    // new BScroll获取实例对象
    const scrollVal = scroll.value = new BScroll(rootRef.value, {
      // 上拉加载的配置
      pullUpLoad: true,
      observeDOM: true,
      click: true
    })
    
    // 那到实例之后, 监听上拉加载事件
    scrollVal.on('pullingUp', pullingUpHandler)

    // 上拉加载的事件处理函数
    async function pullingUpHandler () {
      isPullUpLoad.value = true
      // 调用请求获取数据
      await requestData()
      // 数据返回后, 结束上拉加载行为
      scrollVal.finishPullUp()
      // 刷新模板数据高度
      scrollVal.refresh()
      isPullUpLoad.value = false
    }
  })

  onUnmounted(() => {
    scroll.value.destroy()
  })

  return { scroll, rootRef, isPullUpLoad }
}

usePullUpLoad 这一个钩子函数做的事情也很简单:

1. 告知 BScroll 页面中哪一个 DOM 需要去做上拉加载操作

2. 拿到 scroll 实例之后, 监听上拉加载的事件

3. 触发上拉行为的时候, 发送请求获取数据; 最后更新原先获取 DOM 元素的模板高度

然后在 suggest 组件中进行导入使用

<template>
  <div
    ref="rootRef"
    class="suggest"
    ...
  >
    <ul class="suggest-list">

      <!-- 歌手名 -->
      ...

      <!-- 歌名和专辑名 -->
      ...

      <!-- 上拉加载行为 -->
      <div class="suggest-item" v-loading:[loadingText]="pullUpLoading"></div>
    </ul>
  </div>
</template>

<script>
import { ref, watch, computed, nextTick } from 'vue'
import { search } from '@/api/search'
import { processSongs } from '@/api/song'
import usePullUpLoad from './use-pull-up-load'
export default {
  name: 'suggestCom',
  props: {
    query: {
      type: String,
      default: ''
    },
    showSinger: {
      type: Boolean,
      default: true
    }
  },
  emits: ['selectSong', 'selectSinger'],
  setup (props, { emit }) {
    const singer = ref(null)
    const songs = ref([])
    const hasMore = ref(true)
    const page = ref(1)
    const loadingText = ref('')
    const noResultText = ref('没有搜索到相关的歌手、歌曲')

    const loading = computed(() => {
      return !singer.value && !songs.value.length
    })

    const noResult = computed(() => {
      return !singer.value && !songs.value.length && !hasMore.value
    })

    // 正在加载中的loading效果
    const pullUpLoading = computed(() => {
      return isPullUpLoad.value && hasMore.value
    })

    // 使用钩子函数, 传入上拉加载函数; 拿到rootRef和isPullUpLoad数据
    const { rootRef, isPullUpLoad, scroll } = usePullUpLoad( preventPullUpLoad)

    watch(() => props.query, async (newQuery) => {
      if (!newQuery) return
      await searchFirst()
    })

    async function searchFirst () {
      page.value = 1
      songs.value = []
      singer.value = null
      hasMore.value = true

      const result = await search(props.query, page.value, props.showSinger)
      songs.value = await processSongs(result.songs)
      singer.value = result.singer
      hasMore.value = result.hasMore
    }

    // 上拉加载函数
    async function searchMore () {
      if (!hasMore.value) return
      page.value++
      const result = await search(props.query, page.value, props.showSinger)
      songs.value = songs.value.concat(await processSongs(result.songs))
      hasMore.value = result.hasMore
    }

    return {
      singer,
      songs,
      hasMore,
      loadingText,
      noResult,
      noResultText,
      rootRef,
      pullUpLoading
    }
  }
}
</script>

<style lang="scss" scoped>
...
</style>

第五步:

处理 "初次加载" 发送请求时, 用户上拉触发上拉加载行为; 页面中就会出现两个 loading 效果问题

 我们的处理方法是, 当 "初次加载" 还在进行的时候不要让用户触发上拉加载行为

// loading为真时, 说明 "初次加载" 还未结束
const preventPullUpLoad = computed(() => {
	return loading.value
})

// 然后传给钩子函数
const { rootRef, isPullUpLoad, scroll } = usePullUpLoad(searchMore, preventPullUpLoad)

不让用户触发上拉加载就是取消上拉加载行为

export default function usePullUpLoad (requestData, preventPullUpLoad) {
  const scroll = ref(null)
  const rootRef = ref(null)
  const isPullUpLoad = ref(false)

  onMounted(() => {
    const scrollVal = scroll.value = new BScroll(rootRef.value, {
      pullUpLoad: true,
      observeDOM: true,
      click: true
    })

    scrollVal.on('pullingUp', pullingUpHandler)

    async function pullingUpHandler () {
      // "初次加载" 还未加载完的时候, 阻止isPullUpLoad值的变化
      if (preventPullUpLoad.value) {
        scrollVal.finishPullUp()
        return
      }
      isPullUpLoad.value = true
      await requestData()
      scrollVal.finishPullUp()
      scrollVal.refresh()
      isPullUpLoad.value = false
    }
  })

  onUnmounted(() => {
    scroll.value.destroy()
  })

  return { scroll, rootRef, isPullUpLoad }
}

第六步:

实现点击歌曲数据的逻辑交互, 这里需要根据业务需求来操作

我们这里需要做的事情就是:

1. 添加点击事件, 点击之后 emit 出去父组件需要的数据(毕竟 suggest 是一个公共业务组件)

2.父组件监听到做出对应的业务需求


第七步:

实现点击歌手数据的逻辑交互

我们这里需要做的事情就是:

1. 添加点击事件, 点击之后 emit 出去父组件需要的数据

2.父组件监听到做出对应的业务需求

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

移动端 - 搜索组件(suggest篇) 的相关文章

随机推荐

  • kaldi中SHELL调用C++程序过程源码分析

    引入 kaldi真正的核心源码 都是C 写成的 这个结论可以从如下两点得以确认 1 在kaldi的源码kaldi src目录下 能看到很多扩展名为 cc的源程序 这是linux下C 源码 2 在源码中 比如kaldi src featbin
  • 和导师的第二次探讨

    Jason提问 导师 最近读文献的方面我碰到两个问题 一 就是感觉读的太杂了 人工智能方向太大 文章五花八门 二 内容刚接触感觉晦涩难懂 特别是英文文献 而且用翻译软件意思有时候翻译成中文就感觉也不对 我应该如何解决呢 导师回答 对于问题一
  • sourceInsight官网介绍及插入定制语言支持

    sourceInsight官网介绍及插入定制语言支持 版本说明 版本 作者 日期 备注 0 1 ZY 2019 6 4 初稿 目录 文章目录 sourceInsight官网介绍及插入定制语言支持 版本说明 目录 一 sourceinsigh
  • 已经有了ERP,为什么还要上MES?

    当前 制造企业面临着巨大的竞争和成本压力 利润越来越少 交货时间要求越来越短 人力成本越来越高 产品越来越复杂 大多数企业已经在使用ERP系统了 他们会想 我已经上了ERP了 为什么还需要MES系统 许多工厂车间只有很有限的IT系统 比如自
  • Vector简介说明

    转自 Vector简介说明 下文笔者讲述Vector简介说明 如下所示 Vector简介 Vector集合和ArrayList集合功能相似 底层都是通过数组来实现集合的 Vector和ArrayList最大的区别是Vector的很多方法都是
  • 开发Android硬件访问服务

    在http blog csdn net getnextwindow article details 47731597中 为Android系统添加了HAL模块 开发好一个硬件抽象层以后 我们通常还需要在应用程序框架中实现一个硬件访问服务 硬件
  • 数据结构实验--带环、相交链表问题

    一 问题描述 基于课程上机关于单链表的作业 要求进一步实现以下需求 1 构造链表后 将元素值为 m 和 n 从键盘输入 如有多个相同元素值 仅考虑首个出现的元素 的节点建立连接 注意判断节点出现的先后关系 将后面出现的节点 假设为 n 的链
  • 【数据结构】栈和队列

    栈和队列 一 栈 1 栈的简单介绍 2 栈的相关函数接口实现 1 初始化 2 销毁 3 压栈 4 弹栈 5 判空 6 取栈顶元素 7 栈的大小 二 队列 1 队列的简单介绍 2 队列的相关函数接口实现 1 初始化 2 销毁 3 插入 4 删
  • 台湾海峡隧道工程线路初定3个方案(图)

    台湾海峡隧道工程线路初定3个方案 图 http www sina com cn 2007年04月22日01 50 新京报 3方案示意图 本报讯 昨日 第一届海峡两岸通道 桥隧 工程学术研讨会新闻发布会在福州举行 与会专家称 台湾海峡隧道工程
  • PLC驱动伺服电机、步进电机共阳极接法-20230701

    由于工作需要 需要测试一台小型伺服电机是否好坏 记录一下接线方法 设备如下 信捷XDM 60T10 C PLC 安诺机器人 57AIM30一体化伺服电机 官网找了下技术手册 可以看到这是一款24V供电的小型伺服驱动器 供电可以用手头的这款P
  • 高通平台(8917/8937/8953...) secure boot 软件配置

    以下以8917平台为例 其他平台类似 找到对应配置文件即可 1 新建临时目录 mkdir tmp cd tmp 2 复制openssl文件到临时目录 cp LA UM LINUX android vendor qcom proprietar
  • Pytorch(GPU)详细安装教程

    如果你也是为了安装Pytorch的话 然后在安装CUDA时出现上述错误时 那么就有必要往下看了 我电脑小白 自己摸索安装 一点一点搜索 然后在我不断努力下终于安装成功 最近也是在学习深度学习 把自己安装软件过程中遇到的问题很大家分享一下 在
  • Apollo CyberRT编译问题‘Socket closed‘

    Apollo 编译问题 Socket closed apollo CyberRT编译错误 错误原因 解决方法 apollo CyberRT编译错误 Server terminated abruptly error code 14 error
  • Oracle函数 获得一个UUID

    通过Oracle函数返回一个UUID create or replace function F GET UUID p length in INT return varchar2 is Result varchar2 200 说明 返回一个指
  • GitHub入门教程

    一 注册GitHub账号 GitHub官网https github com 注册之后 登录注册的邮箱验证后注册完成 二 下载Git 有Mac Windows Linux版本的 下载地址 https git scm com downloads
  • js 提示crypto is not defined

    在使用python 调用js的时候 Crypto enc UTF8 parse 引用这个函数的时候提示这个模块没有 没定义 随即 npm install crypto 但是又报错 cryptois not found 换了一些源也没用 最后
  • r语言聚类分析_R语言ggtree画圆形的树状图展示聚类分析的结果

    今天的主要内容是实现下面这幅图 做完聚类分析通常可以选择 树形图来展示聚类分析的结果 之前公众号也分享过一篇文章 如果样本数不是很多 可以选择矩形的树状图 但是样本数如果比较多 比如今天一位公众号的读者留言说他有160多个样本 这样 矩形的
  • 微信小程序------联动选择器

    2019独角兽企业重金招聘Python工程师标准 gt gt gt picker 从底部弹起的滚动选择器 现支持五种选择器 通过mode来区分 分别是普通选择器 多列选择器 时间选择器 日期选择器 省市区选择器 默认是普通选择器 先来看看效
  • VM12+CentOS6.5+hadoop2.7.3 搭建hadoop完全分布式集群

    参考 http blog csdn net gamer gyt article details 51991893 一 安装VM 12 x 下载地址 链接 http pan baidu com s 1c2KA3gW密码 3r67 二 安装Ce
  • 移动端 - 搜索组件(suggest篇)

    这一篇博客是和 search input篇 衔接的 需要的可以看上文 移动端 搜索组件 search list篇 这里我们需要去封装这么一个组件 先说一下大致的方向 1 根据父组件传入的关键字数据发送请求获取后端数据 进行模板渲染 2 处理