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

news2024/12/23 18:58:47

这一篇博客是和 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.父组件监听到做出对应的业务需求

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164977.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《小猫猫大课堂》三轮3——字符函数和字符串函数及其模拟实现

宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重…

C语言——自定义类型详解(结构体,联合体,枚举,位段)

专栏&#xff1a;C语言 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些C语言的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 结构体前言一、结构体1.结构体类型的声明2.结构体…

黑马学ElasticSearch(十)

目录&#xff1a; &#xff08;1&#xff09;自动补全-安装品分词器 &#xff08;2&#xff09;自动补全-自定义分词器 &#xff08;3&#xff09;自动补全-DSL实现自动补全查询 &#xff08;4&#xff09; 自动补全-修改酒店索引库结构 &#xff08;5&#xff09;自动补全…

测试开发——用例篇(如何设计一个测试用例,设计测试用例的一些具体方法)

目录 一、测试用例的基本要素 二、设计测试用例的万能公式 (在没有需求文档的情况下&#xff09; 1、水杯的测试用例 2、一个网站的登录测试用例 三、基于需求进行测试用例的设计 四、测试用例的具体设计方法 1、等价类 2、边界问题 3、判定表&#xff08;因果图&#…

协程和线程的区别、协程原理与优缺点分析、在Java中使用协程

文章目录什么是协程协程的优点与缺点协程实现原理.协程与线程在不同编程语言的实现在Java中使用协程Kilim介绍Kilim整合Java,使用举例小总结什么是协程 相对于协程&#xff0c;你可能对进程和线程更为熟悉。进程一般代表一个应用服务&#xff0c;在一个应用服务中可以创建多个…

源码看CAF的线程调度框架

序 本篇文章带着大家来看下CAF&#xff08;C Actor Framwwork&#xff09;的调度框架&#xff0c;也是算现阶段C比较成熟的调度框架&#xff0c;大家如果自己完成一个比较大型的项目&#xff0c;任务调度也可以参照CAF。 鉴于篇幅较长&#xff0c;大家如果学习使用如何使用CAF…

修改jupyter notebook默认路径

修改jupyter notebook默认路径jupyter notebook默认打开C:\Users\你的用户名&#xff0c;用户名是你的电脑用户名&#xff0c;upload文件又会在C盘生成一堆文件&#xff0c;很乱&#xff0c;用notebook打开文件还要跳转到目录&#xff0c;很麻烦&#xff0c;那有没有办法呢&…

【PYTHON】如何配置集成开发环境Geany

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

【笔记】大话设计模式17-20

【笔记】大话设计模式17-20 文章目录【笔记】大话设计模式17-2017 适配器模式17.1 Example17.2 定义17.3 Show me the code17.4 总结18 备忘录模式18.1 Example18.2 定义18.3 Show me the code18.4 总结19 组合模式19.1 Example19.2 定义19.3 Show me the code19.4 总结20 迭代…

基于python的人工智能数据处理常用算法

文章目录二分法求解最小二乘法曲线拟合最小二乘法的来历最小二乘法与曲线拟合多项式曲线拟合SciPy内置最小二乘法应用泰勒级数背景引入泰勒公式泰勒级数展开与多项式近似二分法求解 机器学习过程中往往会用到很多变量&#xff0c;而这些变量之间的复杂关系一般用非线性方程来&…

VS系列知识-VS Code的安装+Vue环境的搭建+Vue指令

一、VS Code下载地址 Visual Studio Code - Code Editing. Redefined 二、VS Code初始化设置 1.安装插件 在安装好的VSCode软件的扩展菜单中查找安装如下4个插件 2、创建项目 vscode本身没有新建项目的选项&#xff0c;所以要先创建一个空的文件夹&#xff0c;如project_xx…

【用三大件写出的开门烟花特效】

又到了一年一度的春节时期啦&#xff01;昨天呢是北方的小年&#xff0c;今天是南方的小年&#xff0c;看到大家可以愉快的放烟花&#xff0c;过大年很是羡慕呀&#xff01;辞旧岁&#xff0c;贺新春&#xff0c;今年我呀要放烟花&#xff0c;过春节&#xff01;&#x1f9e8;。…

云原生|kubernetes|2022年底cks真题解析(1-10)

前言&#xff1a; cka和cks认证真的比较恶心&#xff0c;他们的那个PSI Bridge Secure Browser真的非常卡。 吐槽完毕&#xff0c;不废话&#xff0c;直接上真题解析。 CKS总共是16道题&#xff0c;题目顺序是打乱的&#xff0c;由于认证系统非常卡&#xff0c;因此&#xf…

通讯录最终版——动态存储+文件处理

最终版通讯录即从上一个版本修改过来先看总体代码&#xff0c;我们再看看差异ps&#xff1a;里面涉及到很多函数的使用&#xff0c;后续我会出专栏来书写这些函数的使用和实例&#xff0c;与常见错误大家可以通过https://cplusplus.com查找test.c#define _CRT_SECURE_NO_WARNIN…

Spring入门-IOC/DI入门与使用文件配置管理(1)

文章目录Spring入门1&#xff0c;Spring介绍1.1 为什么要学?1.2 学什么?1.3 怎么学?2&#xff0c;Spring相关概念2.1 初识Spring2.1.1 Spring家族2.1.2 了解Spring发展史2.2 Spring系统架构2.2.1 系统架构图2.2.2 课程学习路线2.3 Spring核心概念2.3.1 目前项目中的问题2.3.…

已解决:无法解析 jdk.tools:jdk.tools:1.6

文章目录问题描述解决方案问题描述 HBase API客户端操作时&#xff0c;报错&#xff1a;无法解析 jdk.tools:jdk.tools:1.6 这种问题司空见惯了&#xff0c;无非是依赖没下载&#xff0c;版本问题&#xff0c;依赖没加载成功&#xff0c;文件索引没更新成功&#xff0c;IDEA文…

大数据-Hadoop的介绍、配置和集群的使用

HDFS分布式文件系统 分布式&#xff1a;将多台服务器集中在一起&#xff0c;每台服务器都实现总体中的不同业务&#xff0c;做不同的事情 单机模式 厨房里只有一个人&#xff0c;这个人既要买菜&#xff0c;又要切菜&#xff0c;还要炒菜&#xff0c;效率低。 分布式模式 厨房…

leetcode2293:极大极小游戏(1.15每日一题)

题目表述&#xff1a; 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 nums 执行下述算法&#xff1a; 设 n 等于 nums 的长度&#xff0c;如果 n 1 &#xff0c;终止 算法过程。否则&#xff0c;创建 一个新的整数数组 newNums &#xff0c;新数…

深浅copy

go 在go语言中值类型赋值都是深拷贝&#xff0c;引用类型一般都是浅拷贝其本质就是&#xff0c;深拷贝会拷贝数据&#xff0c;而浅拷贝只会拷贝内存的地址&#xff0c;所有就会出现&#xff0c;像slice那样修改底层数组的值&#xff0c;slice的值也跟着改动。 深拷贝 修改a的…

[iHooya]1月15日寒假班作业解析

过滤多余的空格 一个句子中也许有多个连续空格&#xff0c;过滤掉多余的空格&#xff0c;只留下一个空格。 输入&#xff1a;一行&#xff0c;一个字符串&#xff08;长度不超过200&#xff09;&#xff0c;句子的头和尾都没有空格。 输出&#xff1a;过滤之后的句子。 样例输…