文章目录

      • 零.首页最终效果
      • 一.自定义导航栏
        • 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.valueres.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.valueres.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-ui的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.wxmlindex.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  
})

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RustDesk自建远程服务器

目录 服务端 环境linux 安装 开放端口 客户端配置 下载客户端 安装后配置网络 参考&#xff1a;RustDesk自建远程服务器_rustdesk自建服务器-CSDN博客 服务端 环境 linux 安装 下载 wget https://github.com/rustdesk/rustdesk-server/releases/download/1.1.8-2/r…

【Axure高保真原型】图片伸缩展示列表

今天和大家分享图片伸缩展示列表的3个原型案例&#xff0c;模版都是用中继器制作的&#xff0c;所以使用也很方便&#xff0c;在中继器表格里导入对应的图片&#xff0c;即可自动生成交互效果&#xff0c;具体效果可以点击下方视频观看或打开下方预览地址查看哦 【原型效果】 …

keil新建工程文件结构和每个文件的作用解析(标准库版本)

通过网盘分享的文件:STM32工程模板 链接:https://pan.baidu.com/s/1YPFgXu1kwuwsCVxrXFSjZg?pwd=1111 提取码: 1111 --来自百度网盘超级会员v5的分享 这个工程模版是来源于B站江科大的模版,每个人搭建工程文件结构不一样,仅供参考。 工程文件目录结构如图所示 1、DebugC…

【AI论文】Saffron-1:LLM安全保证的推理缩放范例

摘要&#xff1a;现有的安全保证研究主要集中在培训阶段的协调&#xff0c;以向LLM灌输安全行为。 然而&#xff0c;最近的研究表明这些方法容易受到各种越狱攻击。 同时&#xff0c;推理扩展显著提高了LLM推理能力&#xff0c;但在安全保证方面仍未得到探索。 为了解决这一差距…

LLM 支持的基于意图的分类 网络钓鱼电子邮件

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 网络钓鱼攻击仍然是现代网络安全的重大威胁&#xff0c;因为它们成功地欺骗了人类和旨在保护他们的防御机制。传统的检测系统主要关注用户在收件箱中看不到的电子邮件元数据。此外&#xff0c;这些…

C++新特性技术发展路径和时间

C 的新特性发展路径和时间线是一个持续演进的过程。以下是一个概览&#xff0c;涵盖了主要的 C 标准及其关键特性&#xff0c;以及它们发布的时间&#xff1a; C 标准版本及发布时间线: C98 (ISO/IEC 14882:1998): 第一个正式的 C 标准。 发布时间: 1998年关键特性: 标准模板库…

OpenAI 如何在激烈的AI人才争夺战中抢占先机?

在这个快速发展的人工智能时代&#xff0c;OpenAI 正处于一个至关重要的发展阶段。随着技术的不断进步&#xff0c;人工智能行业的竞争日益激烈。如何在这场巨大的竞争中立于不败之地&#xff0c;成为了每一个AI公司的核心挑战。就在近日&#xff0c;OpenAI 的新招聘主管华金・…

【Java学习笔记】Java绘图基础

Java绘图基础 一、Java 坐标体系 1. 像素的概念 计算机在屏幕上显示的内容都是由屏幕上的每一个像素组成的 例如&#xff0c;计算机显示器的分辨率是 800600&#xff0c;表示计算机屏幕上的每一行由 800 个点组成&#xff0c;共有 600 行&#xff0c;整个计算机屏幕共有 480…

资深Java工程师的面试题目(一)基础到高级概述

以下是几道面向资深Java工程师的面试题目&#xff0c;涵盖了从基础知识到高级概念及参考答案&#xff1a; 1. Java内存模型和垃圾回收 问题: 请解释一下Java的内存模型&#xff0c;并描述不同类型的内存区域。如何选择适合特定应用需求的垃圾收集器&#xff1f;请比较几种常…

Spring Retry:优雅地实现方法重试机制

前言 在实际的软件开发中&#xff0c;尤其是在涉及网络请求、数据库操作或外部服务调用的场景下&#xff0c;我们常常会遇到一些临时性故障&#xff08;Transient Failures&#xff09;&#xff0c;例如网络波动、数据库连接超时、第三方 API 暂时不可用等。面对这些问题&…

Mysql报错

1.权限问题 MySQL 认证协议不兼容问题解决方案 这个错误表明您的 MySQL 客户端与服务器要求的认证协议不兼容&#xff0c;通常发生在 MySQL 8.0 服务器与旧版客户端之间。 nested exception is org.apache.ibatis.exceptions.PersistenceException: Error querying database. …

小米汽车5月交付量超过28000台,与上月持平

6月1日&#xff0c;小米汽车公布5月交付数据&#xff0c;2025年5月&#xff0c;小米汽车交付量超过28000台&#xff0c;4月官方披露的交付数据也为28000台。 此外&#xff0c;小米汽车5月新增29家门店&#xff0c;全国82城已有298家门店&#xff1b;6月计划新增37家门店&#x…

严格一致性模型

SC 的第二点约束 :store 必须被 所有(包括自身)执行流 同时看到 ,但是不要求写操作“立即”对其他处理器可见&#xff1b;允许写操作延迟一会儿被其他核观察到。 而 严格一致性模型,包括1. store 必须被 所有(包括自身)执行流 同时看到2. 看到的时间 必须是 某个处理器完成写操…

结合 STM32CubeMX 使用 FreeRTOS 实时操作系统

前言 在STM32CubeMX软件出现以后&#xff0c;创建嵌入式项目变得简洁了许多&#xff0c;开发者无需重复编写MCU的外设初始化配置&#xff0c;只需在STM32CubeMX软件中动动鼠标配置完毕&#xff0c;就可以自动生成基于HAL/LL库的Keil项目文件&#xff0c;提高了开发效率。 最近想…

一致性框架:供应链分布式事务问题解决方案

来源&#xff1a;得物技术 一、前言 二、一致性理论基础 1. 一致性模型概述 2. 最终一致性的必要性 三、供应链一致性框架总体架构 1. 一致性框架的核心功能 2. 一致性框架整体框架 3. 一致性框架整体流程 四、一致性框架实现原理 1. 核心组件设计 2. 异步执行实现原…

民国大模型:智能重构下的乱世觉醒与文明转型

引言&#xff1a;当外滩钟声遇见生成式AI 在历史博物馆的数字化展厅中&#xff0c;一幅动态的《民国百景图》正通过全息投影技术演绎十里洋场的繁华与沧桑。这个虚实交融的场景&#xff0c;恰似民国大模型技术的隐喻——以人工智能为纽带&#xff0c;连接起北洋军阀混战与民族…

ROS2 笔记汇总(2) 通信接口

在 ROS 系统中&#xff0c;通信接口&#xff08;Interface&#xff09; 是节点之间传递信息的标准“语言协议”&#xff0c;确保了不同功能节点之间可以正确理解和使用彼此传送的数据内容。我们可以将其理解为“数据结构格式定义”&#xff0c;贯穿于话题&#xff08;Topic&…

微信小程序:将搜索框和表格封装成组件,页面调用组件

一、实现效果 实现搜索框,表格和翻页效果 二、组件实现 1、创建表格组件页面 (1)创建文件 在文件根目录(与pages同级)直接创建components文件夹,并创建表格的页面common-table/index (2)视图层 a、写入表头 循环由主页面传递的columns,数据为字段名label,宽度为设置…

基于贝叶斯学习方法的块稀疏信号压缩感知算法

基于贝叶斯学习方法的块稀疏信号压缩感知算法 BSBL-FM-master/BSBL_BO.m , 15593 BSBL-FM-master/BSBL_FM.m , 12854 BSBL-FM-master/Phi.mat , 131256 BSBL-FM-master/README.md , 3954 BSBL-FM-master/demo.mat , 1610 BSBL-FM-master/demo_fecg.m , 1481 BSBL-FM-master/de…

【Python爬虫】requests知识点讲解

目录 前言1. requests库基础1.1 安装requests1.2 基本导入 2. HTTP请求方法2.1 GET请求2.2 POST请求2.3 其他HTTP方法 3. 请求头设置3.1 User-Agent设置3.2 常用请求头 4. 响应处理4.1 响应内容获取4.2 响应状态码4.3 响应头信息 5. 会话管理5.1 Session对象5.2 Cookie处理 6. …