文章目錄
- 零.首頁最終效果
- 一.自定義導航欄
- 1.新建`pages/index/components/CustomNavbar.vue`首頁子組件
- 2.在首頁`pages/index/index.vue`中引入
- 3.隱藏默認導航欄+修改標題顏色
- 4.適配不同機型
- 使用到了uniapp的一個api:獲取屏幕邊界到安全區域的距離
- 在子組件中使用
- 二.輪撥圖
- 1.新建 `src/components/XtxSwiper.vue`全局子組件
- 2.自動導入通用組件的步驟
- 3.添加類型聲明
- 4.輪撥圖的指示點
- step1:獲取輪撥圖滾動時,當前圖片的索引
- step2:提供類型聲明
- step3:把拿到的下標更新給activeIndex
- 5.獲取輪撥圖的數據
- 5.1.封裝api
- 5.2.頁面調用
- 6.輪撥圖的數據類型
- 6.1.定義輪撥圖的數據類型:`res.result`
- 6.2.定義輪撥圖的數據類型:`bannerList`
- 7.父傳子+動態渲染
- 三.前臺分類
- 1.組件封裝
- 1.1.準備組件
- 1.2.導入并使用組件
- 1.3.設置首頁底色
- 2.獲取數據
- 2.1.封裝api
- 2.2.頁面調用
- 2.3.聲明類型
- 2.4.父傳子+動態渲染
- 四.熱門推薦
- 1.組件封裝
- 1.1.準備組件
- 1.2.導入組件
- 2.獲取數據
- 2.1.封裝api
- 2.2.頁面調用
- 2.3.類型聲明
- 2.4.父傳子+動態渲染
- 五.猜你喜歡【難點】
- 1.組件封裝
- 1.1.準備組件
- 1.2.直接使用
- 2.定義組件類型
- 3.添加滾動容器`scroll-view`
- 3.1.滾動容器包裹需要的子組件
- 3.2.設置滾動的高度
- 4.獲取數據
- 4.1.封裝api
- 4.2.聲明類型
- 4.3.列表數據的頁面調用
- 4.3.1.為何不是在首頁中請求獲取數據然后父傳子?
- 4.3.2.為啥要在組件掛載完畢時調用api?
- 4.3.3.實現代碼
- 4.4.列表數據的動態渲染
- 4.5.什么時候以及如何調用分頁數據?
- 4.5.1.加載分頁數據的時機以及如何實現
- 4.5.2.注意事項
- 4.5.3.實現步驟
- 4.6.分頁數據的動態渲染
- 4.6.1.聲明類型
- 4.6.2.升級`getHomeGoodsGuessLikeAPI`接口
- 4.6.3.調用子組件并傳入頁面參數
- 4.6.4.什么時候退出分頁?
- 六.優化:下拉刷新
- 1.設置下拉刷新
- 2.監聽到用戶的下拉行為后需要做些什么?
- 3.下拉刷新時獲取猜你喜歡組件,獲取之后應該做什么?
- 七.優化:骨架屏
- 什么是骨架屏?
- 如何編寫骨架屏文件?
零.首頁最終效果
一.自定義導航欄
要求把默認的導航欄升級成自行以導航欄,并進行樣式適配,做成可復用的組件
1.新建pages/index/components/CustomNavbar.vue
首頁子組件
并復制靜態結構如下
<script setup lang="ts">
//
</script>
<template><view class="navbar"><!-- logo文字 --><view class="logo"><image class="logo-image" src="@/static/images/logo.png"></image><text class="logo-text">新鮮 · 親民 · 快捷</text></view><!-- 搜索條 --><view class="search"><text class="icon-search">搜索商品</text><text class="icon-scan"></text></view></view>
</template>
<style lang="scss">
/* 自定義導航條 */
.navbar {background-image: url(@/static/images/navigator_bg.png);background-size: cover;position: relative;display: flex;flex-direction: column;padding-top: 20px;.logo {display: flex;align-items: center;height: 64rpx;padding-left: 30rpx;padding-top: 20rpx;.logo-image {width: 166rpx;height: 39rpx;}.logo-text {flex: 1;line-height: 28rpx;color: #fff;margin: 2rpx 0 0 20rpx;padding-left: 20rpx;border-left: 1rpx solid #fff;font-size: 26rpx;}}.search {display: flex;align-items: center;justify-content: space-between;padding: 0 10rpx 0 26rpx;height: 64rpx;margin: 16rpx 20rpx;color: #fff;font-size: 28rpx;border-radius: 32rpx;background-color: rgba(255, 255, 255, 0.5);}.icon-search {&::before {margin-right: 10rpx;}}.icon-scan {font-size: 30rpx;padding: 15rpx;}
}
</style>
2.在首頁pages/index/index.vue
中引入
<script setup lang="ts">
//引入子組件CustomNavBar
import CustomNavbar from './components/CustomNavbar.vue'
</script>
<template><!-- 使用子組件 --><CustomNavbar /><view class="index">index我是首頁</view>
</template>
3.隱藏默認導航欄+修改標題顏色
//pages.json{"path": "pages/index/index","style": {"navigationBarTitleText": "首頁","navigationStyle": "custom",//隱藏默認導航欄"navigationBarTextStyle": "white",//修改標題顏色}}
4.適配不同機型
對安全區域進行樣式適配
使用到了uniapp的一個api:獲取屏幕邊界到安全區域的距離
//獲取屏幕邊界到安全區域的距離
const { safeAreaInsets } = uni.getSystemInfoSync()
在子組件中使用
//使用uniapp的api,獲取屏幕邊界到安全區域的距離
const { safeAreaInsets } = uni.getSystemInfoSync()
......<!-- 把該距離變量動態綁定style,可以實現導航條跟隨屏幕劉海區域變化 --><view class="navbar" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"><!-- logo文字 --></view>
二.輪撥圖
輪撥圖不僅在首頁中使用到,在商品分類頁中也有,因此也封裝成一個通用組件
1.新建 src/components/XtxSwiper.vue
全局子組件
并準備靜態結構如下:
<script setup lang="ts">
import { ref } from 'vue'
const activeIndex = ref(0)
</script>
<template><vi ew class="carousel">
使用到了小程序的標簽:swiper<swiper :circular="true" :autoplay="false" :interval="3000"><swiper-item>點擊圖片跳轉:<navigator url="/pages/index/index" hover-class="none" class="navigator"><imagemode="aspectFill"class="image"src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_1.jpg"></image></navigator></swiper-item><swiper-item><navigator url="/pages/index/index" hover-class="none" class="navigator"><imagemode="aspectFill"class="image"src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_2.jpg"></image></navigator></swiper-item><swiper-item><navigator url="/pages/index/index" hover-class="none" class="navigator"><imagemode="aspectFill"class="image"src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_3.jpg"></image></navigator></swiper-item></swiper><!-- 指示點(自定義) --><view class="indicator"><textv-for="(item, index) in 3":key="item"class="dot":class="{ active: index === activeIndex }"></text></view></view>
</template>
<style lang="scss">
/* 輪播圖 */
.carousel {height: 280rpx;position: relative;overflow: hidden;transform: translateY(0);background-color: #efefef;.indicator {position: absolute;left: 0;right: 0;bottom: 16rpx;display: flex;justify-content: center;.dot {width: 30rpx;height: 6rpx;margin: 0 8rpx;border-radius: 6rpx;background-color: rgba(255, 255, 255, 0.4);}.active {background-color: #fff;}}.navigator,.image {width: 100%;height: 100%;}
}
</style>
2.自動導入通用組件的步驟
(參考之前在pages.json中對uni-ui的配置)
//pages.json
{//組件自動導入"easycom": {//是否開啟自動掃描"autoscan": true,//以正則方式自定義組件的匹配規則(添加后需重啟服務器才能生效)"custom": {// 之前的:uni-ui 規則如下配置"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",//新增的:以Xtx開頭的組件"^xtx(.*)": "@/components/xtx$1.vue"}},"pages": [......],
}
驗證:在首頁中不導入直接使用輪撥圖組件
<script setup lang="ts">
//引入子組件CustomNavBar
import CustomNavbar from './components/CustomNavbar.vue'
</script>
<template><!-- 使用子組件 --><CustomNavbar /><!-- 不用導入,直接使用輪撥圖通用子組件 --><XtxSwiper /><view class="index">index我是首頁</view>
</template>
3.添加類型聲明
此時自動導入的 <XtxSwiper />
和手動導入的<CustomNavbar />
仍有區別----沒有類型聲明
知識點:為已有的js文件提供類型聲明,關鍵字declare
// src/types/components.d.ts//導入輪撥圖組件
import XtxSwiper from './XtxSwiper.vue'
//擴展全局組件類型,聲明全局組件的類型
declare module '@vue/runtime-core' {// 注意:此處更新了要寫成:declare module 'vue'export interface GlobalComponents {XtxSwiper: typeof XtxSwiper//typeof拿到組件的類型,然后賦值給全局組件類型}
}
注:declare module '@vue/runtime-core'
應為declare module 'vue'
4.輪撥圖的指示點
此時的指示點僅是靜態結構
<!-- 指示點(自定義) --><view class="indicator"><textv-for="(item, index) in 3":key="item"class="dot":class="{ active: index === activeIndex }"//動態綁定.active類實現高亮==>通過比較index===activeIndex></text></view>
最終目標是讓指示點跟著輪撥圖的切換而切換
step1:獲取輪撥圖滾動時,當前圖片的索引
uniapp官網>Swiper>@change事件中event.detail.current
就是下標
//當輪撥圖滾動時觸發
function onChange(e) {//此時提示e為any類型==>缺少類型聲明console.log(e) //e.current為當前輪播圖的索引
}<!-- 指示點(自定義) --><view class="indicator"><textv-for="(item, index) in 3"@change='onChange':key="item"class="dot":class="{ active: index === activeIndex }"//動態綁定.active類實現高亮==>通過比較index===activeIndex></text></view>
step2:提供類型聲明
const activeIndex=ref(0)
const onChange: UniHelper.SwiperOnChange = (e) => {console.log(e) //e.current為當前輪播圖的索引 activeIndex.value=e.detail?.current
}
step3:把拿到的下標更新給activeIndex
activeIndex.value=e.detail?.current此時報錯:不能將"number|undefine"分配給"number"
因為detail后面加了可選符,當沒有時可能為undefined解決方式:把可選鏈調整為非空斷言
activeIndex.value=e.detail!.current
5.獲取輪撥圖的數據
當前輪撥圖的圖片使用的是靜態資源,現在優化為:從后臺獲取數據并動態渲染
5.1.封裝api
新建services/home.js
import { http } from "@/utils/http"
const getHomeBannerAPI= (distributionSite=1)=>{//調用http中封裝的發起請求的函數(基于uni.request)return http({methods:'GET',url:'/home/banner',data:{distributionSite}})
}
5.2.頁面調用
//index.vue
import {getHomeBannerAPI} from '@/services/home.ts'
import {onLoad} from '@dcloundio/uni-app'
const bannerList=ref([])
//先封裝一個調用函數
const getHomeBannerData=async()=>{const res=await getHomeBannerAPI()const bannerList=res.result//缺乏類型聲明
}onLoad(()=>{//記得這個鉤子也需導入//頁面加載時調用該函數getHomeBannerData()
})
此時:
bannerList和res.result
都缺乏類型聲明
6.輪撥圖的數據類型
6.1.定義輪撥圖的數據類型:res.result
- 復制指定類型文件并粘貼到新建的
types/home.d.ts
如下:
/*首頁-廣告區域數據類型 */
export type BannerItem = {/** 跳轉鏈接 */hrefUrl: string/** id */id: string/** 圖片鏈接 */imgUrl: string/** 跳轉類型 */type: number
}
注:code,msg,result的類型不用再聲明了,已經有了
前面的代碼:
interface Data<T> {code: string //狀態碼:'1"msg: string //提示信息:'請求成功'result: T //核心數據類型:{{}.{},{},{},{}}
}
- 在
service/home.ts
中,導入types/home.d.ts
中的BannerItem
//service/home.ts
import type {BannerItem} from '@/types/home'
import { http } from "@/utils/http"
const getHomeBannerAPI= (distributionSite=1)=>{//調用http中封裝的發起請求的函數(基于uni.request)return http<BannerItem[]>({//指定類型為一個對象數組methods:'GET',url:'/home/banner',data:{distributionSite}})
}
6.2.定義輪撥圖的數據類型:bannerList
//index.vue
import type {BannerItem} from '@/types/home'
const bannerList=ref<BannerItem[]>([])
7.父傳子+動態渲染
此時數據是在首頁中發起請求并獲取的,而輪撥圖是一個封裝子組件,因此需要父傳子
- 父組件:
pages/index/index.vue
<XtxSwiper :list='bannerList'/>
- 子組件:
src/components/XtxSwiper.vue
<script setup lang="ts">
import { ref } from 'vue'
import type { BannerItem } from '@/types/home'
// 子組件接收自定義屬性list
defineProps<{list: BannerItem[]
}>()
const activeIndex = ref(0)
//當輪撥圖滾動時觸發
const onChange: UniHelper.SwiperOnChange = (e) => {console.log(e) //e.current為當前輪播圖的索引activeIndex.value = e.detail!.current //非空斷言:回調參數是可選鏈式的
}
</script><template><view class="carousel"><swiper :circular="true" :autoplay="false" :interval="3000"><!-- 動態渲染:輪撥圖 --><swiper-item v-for="item in list" :key="item.id"><navigator url="/pages/index/index" hover-class="none" class="navigator"><image mode="aspectFill" class="image" :src="item.imgUrl"></image></navigator></swiper-item></swiper><!-- 指示點 --><view class="indicator"><!-- 動態渲染:指示點 --><textv-for="(item, index) in list":key="item.id"@change="onChange"class="dot":class="{ active: index === activeIndex }"></text></view></view>
</template>
三.前臺分類
前臺分類也要封裝成獨立子組件,但是與輪撥圖不同的是,輪撥圖除了在首頁中使用之外,在分類頁中也用上,
但是前臺分類僅僅在首頁中用到,因此可以寫在index/components/CategoryPanel.vue
中.(類似自定義導航欄)
1.組件封裝
1.1.準備組件
準備靜態結構如下:
<script setup lang="ts">
//
</script>
<template><view class="category"><navigator//導航鏈接class="category-item"hover-class="none"url="/pages/index/index"v-for="item in 10"//列表循環:key="item">內部結構:圖片+文本<imageclass="icon"src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/images/nav_icon_1.png"></image><text class="text">居家</text></navigator></view>
</template>
<style lang="scss">
/* 前臺類目 */
.category {margin: 20rpx 0 0;padding: 10rpx 0;display: flex;flex-wrap: wrap;min-height: 328rpx;.category-item {width: 150rpx;display: flex;justify-content: center;flex-direction: column;align-items: center;box-sizing: border-box;.icon {width: 100rpx;height: 100rpx;}.text {font-size: 26rpx;color: #666;}}
}
</style>
1.2.導入并使用組件
//index.vue
......
//導入分類面板組件
import CategoryPanel from './components/CategoryPanel.vue'<!-- 自定義導航欄 --><CustomNavbar /><!-- 輪撥圖通用子組件 --><XtxSwiper :list="bannerList" /><!-- 分類面板 --><CategoryPanel />
1.3.設置首頁底色
//index.vue
<style lang="scss">
//更改頁面的底色
page {background-color: #f5f5f5;
}
</style>
2.獲取數據
2.1.封裝api
// services/home.ts
export const getHomeCategoryAPI = () => {return http({method: 'GET',url: '/home/category/mutli',})
}
2.2.頁面調用
//index.vueimport { getHomeBannerAPI, getHomeCategoryAPI } from '@/services/home.ts'
const categoryList = ref([])
// 獲取首頁分類數據
const getHomeCategoryData = async () => {const res = await getHomeCategoryAPI()categoryList.value = res.result
}
onLoad(() => {getHomeBannerData()getHomeCategoryData()
})
*此時categoryList.value
和 res.result
都沒有類型聲明
2.3.聲明類型
- 粘貼分類數據的聲明類型到已有的
home.d.ts
文件中
/** 首頁-前臺類目數據類型 */
export type CategoryItem = {/** 圖標路徑 */icon: string/** id */id: string/** 分類名稱 */name: string
}
- 給
res.result
指定類型
// services/home.ts
import type {CategoryItem} from '@/types/home'export const getHomeCategoryAPI = () => {return http<CategoryItem[]>({method: 'GET',url: '/home/category/mutli',})
}
- 給
categoryList
指定類型
import type {CategoryItem} from '@/types/home'
const categoryList = ref<CategoryItem[]>([])
// 獲取首頁分類數據
const getHomeCategoryData = async () => {const res = await getHomeCategoryAPI()categoryList.value = res.result
}
2.4.父傳子+動態渲染
父組件:index.vue
<CategoryPanel :list='categoryList' />
子組件:CategoryPanel.vue
<script setup lang="ts">
import type { CategoryItem } from '@/types/home'
// 定義 props 接收數據
defineProps<{list: CategoryItem[]
}>()
</script>
<template><view class="category"><navigatorclass="category-item"hover-class="none"url="/pages/index/index"v-for="item in list":key="item.id"><image class="icon" :src="item.icon"></image><text class="text">{{ item.name }}</text></navigator></view>
</template>
四.熱門推薦
后端根據用戶的消費習慣等信息向用戶推薦的一系列商品,前端負責展示這些商品展示給用戶
1.組件封裝
1.1.準備組件
創建index/components/HotPanel.vue
作為首頁的子組件,并準備靜態結構如下:
<script setup lang="ts">
//
</script>
<template><!-- 推薦專區 --><view class="panel hot"><view class="item" v-for="item in 4" :key="item"><view class="title"><text class="title-text">特惠推薦</text><text class="title-desc">精選全攻略</text></view><navigator hover-class="none" url="/pages/hot/hot" class="cards"><imageclass="image"mode="aspectFit"src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/goods_small_1.jpg"></image><imageclass="image"mode="aspectFit"src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/goods_small_2.jpg"></image></navigator></view></view>
</template>
<style lang="scss">
/* 熱門推薦 */
.hot {display: flex;flex-wrap: wrap;min-height: 508rpx;margin: 20rpx 20rpx 0;border-radius: 10rpx;background-color: #fff;.title {display: flex;align-items: center;padding: 24rpx 24rpx 0;font-size: 32rpx;color: #262626;position: relative;.title-desc {font-size: 24rpx;color: #7f7f7f;margin-left: 18rpx;}}.item {display: flex;flex-direction: column;width: 50%;height: 254rpx;border-right: 1rpx solid #eee;border-top: 1rpx solid #eee;.title {justify-content: start;}&:nth-child(2n) {border-right: 0 none;}&:nth-child(-n + 2) {border-top: 0 none;}.image {width: 150rpx;height: 150rpx;}}.cards {flex: 1;padding: 15rpx 20rpx;display: flex;justify-content: space-between;align-items: center;}
}
</style>
1.2.導入組件
//導入熱門推薦組件
import HotPanel from './components/HotPanel.vue'<!-- 熱門推薦 --><HotPanel :list="hotList" />
2.獲取數據
2.1.封裝api
<!-- 熱門推薦 --><HotPanel :list="hotList" />
2.2.頁面調用
//index.vueimport type { BannerItem, CategoryItem, HotItem } from '@/types/home'
import { getHomeBannerAPI, getHomeCategoryAPI, getHomeHotAPI } from '@/services/home.ts'
const hotList = ref<HotItem[]>([])
// 獲取首頁熱門推薦數據
const getHomeHotData = async () => {const res = await getHomeHotAPI()hotList.value = res.result
}
onLoad(() => {getHomeBannerData()getHomeCategoryData()getHomeHotData()
})
*此時hotList.value
和res.result
還沒有聲明類型
2.3.類型聲明
- 粘貼類型聲明文件到
home.d.ts
中
//types/home.d.ts/** 首頁-熱門推薦數據類型 */
export type HotItem = {/** 說明 */alt: string/** id */id: string/** 圖片集合[ 圖片路徑 ] */pictures: string[]/** 跳轉地址 */target: string/** 標題 */title: string/** 推薦類型 */type: string
}
- 為
res.result
提供類型聲明
//home.ts
import type { BannerItem, CategoryItem, HotItem, GuessItem } from '@/types/home'const hotList = ref<HotItem[]>([])
// 獲取首頁熱門推薦數據
export const getHomeHotAPI = () => {return http<HotItem[]>({method: 'GET',url: '/home/hot/mutli',})
}
- 為
hotList
提供類型聲明
//index.vue
import type { BannerItem, CategoryItem, HotItem } from '@/types/home'
const hotList = ref<HotItem[]>([])
2.4.父傳子+動態渲染
父組件:index.vue
<!-- 熱門推薦 --><HotPanel :list="hotList" />
子組件:src\pages\index\components\HotPanel.vue
<script setup lang="ts">
import type { HotItem } from '@/types/home'
// 定義 props 接收數據
defineProps<{list: HotItem[]
}>()
</script>
<template><!-- 推薦專區 --><view class="panel hot"><view class="item" v-for="item in list" :key="item.id"><view class="title"><text class="title-text">{{ item.title }}</text><text class="title-desc">{{ item.alt }}</text></view><navigator hover-class="none" :url="`/pages/hot/hot?type=${item.type}`" class="cards">
<!-- 動態渲染:第二層v-for --><imagev-for="src in item.pictures":key="src"class="image"mode="aspectFit":src="src"></image></navigator></view></view>
</template>
五.猜你喜歡【難點】
后端根據用戶的瀏覽記錄等信息向用戶隨機推薦的一系列商品,前端負責把商品在多個頁面中展示。
猜你喜歡要封裝成全局通用子組件,因為多個頁面(購物車,結算頁,首頁)用到該組件
1.組件封裝
1.1.準備組件
新建src/components/XtxGuess.vue
并粘貼靜態結構代碼如下:
<script setup lang="ts">
//
</script>
<template><!-- 猜你喜歡 --><view class="caption"><text class="text">猜你喜歡</text></view><view class="guess"><navigatorclass="guess-item"v-for="item in 10":key="item":url="`/pages/goods/goods?id=4007498`"><imageclass="image"mode="aspectFill"src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/goods_big_1.jpg"></image><view class="name"> 德國THORE男表 超薄手表男士休閑簡約夜光石英防水直徑40毫米 </view><view class="price"><text class="small">¥</text><text>899.00</text></view></navigator></view><view class="loading-text"> 正在加載... </view>
</template>
<style lang="scss">
:host {display: block;
}
/* 分類標題 */
.caption {display: flex;justify-content: center;line-height: 1;padding: 36rpx 0 40rpx;font-size: 32rpx;color: #262626;.text {display: flex;justify-content: center;align-items: center;padding: 0 28rpx 0 30rpx;&::before,&::after {content: '';width: 20rpx;height: 20rpx;background-image: url(@/static/images/bubble.png);background-size: contain;margin: 0 10rpx;}}
}
/* 猜你喜歡 */
.guess {display: flex;flex-wrap: wrap;justify-content: space-between;padding: 0 20rpx;.guess-item {width: 345rpx;padding: 24rpx 20rpx 20rpx;margin-bottom: 20rpx;border-radius: 10rpx;overflow: hidden;background-color: #fff;}.image {width: 304rpx;height: 304rpx;}.name {height: 75rpx;margin: 10rpx 0;font-size: 26rpx;color: #262626;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;}.price {line-height: 1;padding-top: 4rpx;color: #cf4444;font-size: 26rpx;}.small {font-size: 80%;}
}
// 加載提示文字
.loading-text {text-align: center;font-size: 28rpx;color: #666;padding: 20rpx 0;
}
</style>
1.2.直接使用
類似輪撥圖組件,無需導入直接使用
//獲取猜你喜歡的組件實例
import type { XtxGuessInstance } from '@/types/component'
const guessRef = ref<XtxGuessInstance>()<!-- 猜你喜歡 -->
<XtxGuess />
2.定義組件類型
// types/components.d.ts
import XtxSwiper from '@/components/XtxSwiper.vue'
import XtxGuess from '@/components/XtxGuess.vue'
declare module 'vue' {export interface GlobalComponents {XtxSwiper: typeof XtxSwiperXtxGuess: typeof XtxGuess}
}
// 組件實例類型
export type XtxGuessInstance = InstanceType<typeof XtxGuess>
3.添加滾動容器scroll-view
3.1.滾動容器包裹需要的子組件
使用滾動容器scroll-view
把需要滾動的子組件占位符(除自定義導航欄都滾動)包起來
<!-- 自定義導航欄 --><CustomNavbar /><scroll-view class='scroll-view' scroll-y><!-- 輪撥圖通用子組件 --><!-- 父傳子:自定義屬性為list --><XtxSwiper :list="bannerList" /><!-- 分類面板 --><CategoryPanel :list="categoryList" /><!-- 熱門推薦 --><HotPanel :list="hotList" /><!-- 猜你喜歡 --><XtxGuess /></scroll-view>
3.2.設置滾動的高度
滾動的高度=頁面高度-自定義導航欄高度
- 頁面高度:
page {//更改頁面的底色background-color: #f5f5f5;//設置頁面高度為100%height: 100%;// 設置彈性布局和排列方向display: flex;flex-direction: column;
}
- 滾動高度:
.scroll-view{flex:1//height:0//若還滾動不了就加上這句
}
4.獲取數據
4.1.封裝api
// src/services/home.ts
/**
* 猜你喜歡-小程序
*/
export const getHomeGoodsGuessLikeAPI = (data?: PageParams) => {return http<PageResult<GuessItem>>({method: 'GET',url: '/home/goods/guessLike',data,})
}
4.2.聲明類型
猜你喜歡后臺返回的數據類型中大致可以分為:列表數據,總條數,當前頁數,其中:
列表數據是根據調用的頁面而相應變化的,考慮將其抽離出來聲明為泛型數據
分頁數據在分頁功能中調用,不論哪個頁面調用,都是相同的數據類型,因此同樣可以抽離出來并聲明
- 猜你喜歡的商品數據類型
放在已存在的src/types/home.d.ts
文件中
//src/types/home.d.ts /** 猜你喜歡-商品類型 */
export type GuessItem = {/** 商品描述 */desc: string/** 商品折扣 */discount: number/** id */id: string/** 商品名稱 */name: string/** 商品已下單數量 */orderNum: number/** 商品圖片 */picture: string/** 商品價格 */price: number
}
- 分頁:分頁結果+分頁參數的數據類型
新建src/types/global.d.ts
/** 通用分頁結果類型 */
export type PageResult<T> = {/** 列表數據 */items: T[]/** 總條數 */counts: number/** 當前頁數 */page: number/** 總頁數 */pages: number/** 每頁條數 */pageSize: number
}/** 通用分頁參數類型 */
export type PageParams = {/** 頁碼:默認值為 1 */page?: number/** 頁大小:默認值為 10 */pageSize?: number
}
4.3.列表數據的頁面調用
4.3.1.為何不是在首頁中請求獲取數據然后父傳子?
因為這個組件多次被復用了,然后數據又都是一樣的,
所以放在組件內部中,可以一次請求就能完成數據的展示。
4.3.2.為啥要在組件掛載完畢時調用api?
使用組件生命周期鉤子而非頁面生命周期函數
的原因是:僅調用一次,而不是在每個用到"猜你喜歡"功能的頁面中各調用一次
使用組件生命周期鉤子中的onMounted
的原因是:等dom樹生成之后才能渲染,不然獲取到的數據沒有dom元素渲染
4.3.3.實現代碼
//XtxGuess.vue
import type {GuessItem} from '@/type/home.d.ts'
const guessList=ref<GuessItem[]>([])
//獲取猜你喜歡數據
const getHomeGoodsGuessLikeData=async()=>{const res=await getHomeGoodsGuessLikeAPI()guessList.value=res.result.items//為啥要加items:查看通用分頁結果類型
}
//組件掛載完畢
onMounted(){getHomeGoodsGuessLikeData()
}
4.4.列表數據的動態渲染
不用父傳子:用goodsList
直接在子組件Xtxguess.vue
中動態渲染
<!-- 猜你喜歡 --><view class="caption"><text class="text">猜你喜歡</text></view><view class="guess"><navigatorclass="guess-item"v-for="item in guessList":key="item.id":url="`/pages/goods/goods?id=4007498`"><imageclass="image"mode="aspectFill":src="item.picture"></image><view class="name">{{item.name}}</view><view class="price"><text class="small">¥</text><text>{{item.price}}</text></view></navigator></view>
4.5.什么時候以及如何調用分頁數據?
4.5.1.加載分頁數據的時機以及如何實現
當滾動容器scroll-view滾動觸底的時候,才開始加載分頁數據
此時觸發"加載分頁數據"的事件是綁定在首頁的scroll-view
上的,
而數據的獲取和加載是在猜你喜歡子組件XtxGuess
當中的,
為了實現這業務邏輯(父組件調用子組件的方法)–需要用到模板引用(ref標識
),它可以獲取當前組件的DOM對象和其他組件的實例對象
4.5.2.注意事項
???? 1)當前是TypeScript項目,因此還需要指定組件實例的類型
???? 2)在setup語法糖是,所有子組件默認是封閉的,需要手動設置暴露子組件
4.5.3.實現步驟
- step1:滾動容器綁定滾動觸底事件
- step2:在事件中調用子組件獲取分頁數據的方法
// pages/index/index.vue
<script setup lang="ts">
import type { XtxGuessInstance } from '@/types/components'
import { ref } from 'vue'
// 獲取猜你喜歡組件實例
const guessRef = ref<XtxGuessInstance>()
// 滾動觸底事件
const onScrolltolower = () => {guessRef.value?.getMore()
}
</script>
<template><!-- 滾動容器 --><scroll-view scroll-y @scrolltolower="onScrolltolower">......<!-- 猜你喜歡 --><XtxGuess ref="guessRef" /></scroll-view>
</template>
- step3:給ref指定類型
使用到了TS的方法InstanceType
,用于獲取組件類型
//types/components.d.ts
//組件實例類型
export type XtxGuessInstance=InstanceType<typeof XtxGuess>
- step4:暴露子組件的獲取數據的方法
//XtxGuess.vue
defineExpose({getMore:getHomeGoodsGuessLikeData//可以不暴露原有方法名,而是自定義
})
4.6.分頁數據的動態渲染
業務邏輯:
在已封裝的"獲取猜你喜歡"的接口api函數中getHomeGoodsGuessLikeAPI
,把添加分類的參數添加進去并且為其指定類型,
然后調用該函數并傳參,獲得的分頁數據追加到guessList
數組里面
最后對頁碼進行累加,目的是為下一次傳參時,可以傳入不同的數據,即下一頁的數據
4.6.1.聲明類型
(用到了通用分頁參數類型,上面已經聲明過)
4.6.2.升級getHomeGoodsGuessLikeAPI
接口
// src/services/home.ts
/**
* 猜你喜歡-小程序
*/
export const getHomeGoodsGuessLikeAPI = (data?: PageParams) => {return http<PageResult<GuessItem>>({method: 'GET',url: '/home/goods/guessLike',data,//導入并指定類型<==在子組件調用傳參的時候定義一個分頁參數pageParams})
}
4.6.3.調用子組件并傳入頁面參數
// 分頁參數
const pageParams: Required<PageParams> = {//ts的工具函數page: 1,pageSize: 10,
}
// 獲取猜你喜歡數據
const getHomeGoodsGuessLikeData = async () => {// 退出分頁判斷if (finish.value === true) {return uni.showToast({ icon: 'none', title: '沒有更多數據~' })}const res = await getHomeGoodsGuessLikeAPI(pageParams)// 數組追加guessList.value.push(...res.result.items)//數組追加到另一個數組:拓展運算符// 分頁條件if (pageParams.page < res.result.pages) {//要不要加value// 頁碼累加的條件是頁碼<總頁數pageParams.page++//當前是可選的,當沒有數據時會報錯,把其類型改為必選Required} else {finish.value = true//標記結束}
}
4.6.4.什么時候退出分頁?
數據總數和總頁數是有限的,
由此判斷:當頁碼小于總頁數時,可以繼續進行頁碼累加,否則標記結束
//已結束的標記
const finish=ref(false)替換"正在加載"的文字:
<view class="loading-text"> {{finish?"沒有更多數據~":"正在加載..."}} </view>
六.優化:下拉刷新
1.設置下拉刷新
下拉刷新使用到了uni-u
i的scroll-view
組件上的如下屬性:
- 配置
refresher-enabled
屬性,開啟下拉刷新交互 - 監聽
@refresherrefresh
事件,判斷用戶是否執行了下拉操作 - 配置
refresher-triggered
屬性,關閉下拉狀態
//index .vue<!-- 滾動容器 -->
<scroll-viewrefresher-enabled@refresherrefresh="onRefresherrefresh":refresher-triggered="isTriggered"class="scroll-view"scroll-y
>
........
</scroll-view>
2.監聽到用戶的下拉行為后需要做些什么?
- 刷新:輪撥圖,前臺分類,當前熱賣,猜你喜歡
- 開啟和關閉下拉刷新的動畫
//index.vue//監聽用戶的下拉行為
const onRefreshrefresh = async () => {//開啟動畫isTriggered.value = true//先重置"猜你喜歡"組件的數據guessRef.value?.resetData()//刷新數據,重新獲取數據==>確保全部加載完畢后關閉動畫await Promise.all([getHomeBannerData(),getHomeCategoryData(),getHomeHotData(),guessRef.value?.getMore(), //再調用猜你喜歡的組件的獲取更多數據的方法])//關閉動畫isTriggered.value = false
}
3.下拉刷新時獲取猜你喜歡組件,獲取之后應該做什么?
- 重置頁碼
- 重置列表
- 重置結束標記
(以上數據都是存在子組件中,首頁需要使用ref標識來調用)
- 數據重置后再加載數據
子組件:XtxGuess.vue
const resetData = () => {
//重置數據
const resetData = () => {
pageParams.page = 1
guessList.value = []
finish.value = false
}
// 暴露方法
defineExpose({
resetData,
getMore: getHomeGoodsGuessLikeData,
})
父組件:index.vue
//index.vue//監聽用戶的下拉行為
const onRefreshrefresh = async () => {//開啟動畫isTriggered.value = true//先重置"猜你喜歡"組件的數據guessRef.value?.resetData()//刷新數據,重新獲取數據==>確保全部加載完畢后關閉動畫await Promise.all([getHomeBannerData(),getHomeCategoryData(),getHomeHotData(),guessRef.value?.getMore(), //再調用猜你喜歡的組件的獲取更多數據的方法])//關閉動畫isTriggered.value = false
}
*使用Promise.all的優勢是減少等待時間:
七.優化:骨架屏
什么是骨架屏?
骨架屏是頁面加載出來之前的空白頁面
骨架屏顯示的邏輯:數據是否在加載中
如何編寫骨架屏文件?
-
微信開發者工具可以快速生成骨架屏的結構和樣式:
微信開發者工具(模擬器)>(右上角)頁面信息>生成骨架屏>確認生成index.skeleton.wxml
和index.skeleton.wxss
兩個文件 -
找到這兩個文件,轉換為vue組件即可
新建pages/index/components/PageSkeleton.vue
刪掉其他多余代碼只保留:輪撥圖,前臺分類,猜你喜歡
代碼略
- 首頁調用子組件
<!-- 自定義導航欄 --><CustomNavbar /><scroll-view class='scroll-view' scroll-y><PageSkeleton v-if="true" /><template v-else><!-- 輪撥圖通用子組件 --><!-- 父傳子:自定義屬性為list --><XtxSwiper :list="bannerList" /><!-- 分類面板 --><CategoryPanel :list="categoryList" /><!-- 熱門推薦 --><HotPanel :list="hotList" /><!-- 猜你喜歡 --><XtxGuess /></template></scroll-view>
- 判斷骨架屏的加載時機
const isLoading=ref(false)
onLoad(() => {isLoading.value=true//getHomeBannerData()//getHomeCategoryData()//getHomeHotData()Promise.all([getHomeBannerData()getHomeCategoryData()getHomeHotData()])isLoading.value=false
})