移动端 - 搜索组件(suggest篇)
這一篇博客是和?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) returnawait searchFirst() })// 第一次搜索需要對數據進行重置 const searchFirst = async () => {page.value = 1songs.value = []singer.value = nullhasMore.value = true// 然后調用接口獲取數據, 將模板使用的數據進行覆蓋const result = await search(props.query, page.value, props.showSinger)// 這里是調用獲取歌曲的url數據接口songs.value = await processSongs(result.songs)singer.value = result.singerhasMore.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><liclass="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><divclass="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) returnawait searchFirst()})async function searchFirst () {page.value = 1songs.value = []singer.value = nullhasMore.value = trueconst result = await search(props.query, page.value, props.showSinger)songs.value = await processSongs(result.songs)singer.value = result.singerhasMore.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><divref="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) returnawait searchFirst()})async function searchFirst () {page.value = 1songs.value = []singer.value = nullhasMore.value = trueconst result = await search(props.query, page.value, props.showSinger)songs.value = await processSongs(result.songs)singer.value = result.singerhasMore.value = result.hasMore}// 上拉加載函數async function searchMore () {if (!hasMore.value) returnpage.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 = trueawait requestData()scrollVal.finishPullUp()scrollVal.refresh()isPullUpLoad.value = false}})onUnmounted(() => {scroll.value.destroy()})return { scroll, rootRef, isPullUpLoad } }第六步:
實現點擊歌曲數據的邏輯交互, 這里需要根據業務需求來操作
我們這里需要做的事情就是:
1. 添加點擊事件, 點擊之后 emit 出去父組件需要的數據(畢竟 suggest 是一個公共業務組件)
2.父組件監聽到做出對應的業務需求
第七步:
實現點擊歌手數據的邏輯交互
我們這里需要做的事情就是:
1. 添加點擊事件, 點擊之后 emit 出去父組件需要的數據
2.父組件監聽到做出對應的業務需求
總結
以上是生活随笔為你收集整理的移动端 - 搜索组件(suggest篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: flash图片抗锯齿的方法
- 下一篇: 坑爹的西部数码,悲剧的港台主机