• 黑马 小兔鲜儿 uniapp 小程序开发- 推荐模块- day03


    黑马 小兔鲜儿 uniapp 小程序开发- 02首页模块_软工菜鸡的博客-CSDN博客​​​​​​

    本课程是全网首套用 vue3 加 TS 写的 uniapp 项目, 里面大量封装自己的组件库,课程从 uni-app 基础入手,按照9大电商业务模块逐步实现完整的电商购物流程业务;涵盖了猜你喜欢、热门推荐、商品分类、商品详情、微信登录、用户管理、地址管理、购物车管理、订单管理等功能。包含微信登录,微信支付等业务。一套代码多端全面覆盖微信小程序端、H5端、APP端。

    学完本课程能够收获:使用 uni-app + Vue3 开发中型项目的能力

    小兔鲜儿 - 推荐模块- day03

    主要实现 Tabs 交互、多 Tabs 列表分页加载数据。

    动态获取数据

    参考效果

    推荐模块的布局结构是相同的,因此我们可以复用相同的页面及交互,只是所展示的数据不同。

    静态结构

    新建热门推荐页面文件,并在 pages.json 中添加路由(VS Code 插件自动完成)。

    1. // /src/pages/hot/hot.vue
    2. <script setup lang="ts">
    3. // 热门推荐页 标题和url
    4. const hotMap = [
    5. { type: '1', title: '特惠推荐', url: '/hot/preference' },
    6. { type: '2', title: '爆款推荐', url: '/hot/inVogue' },
    7. { type: '3', title: '一站买全', url: '/hot/oneStop' },
    8. { type: '4', title: '新鲜好物', url: '/hot/new' },
    9. ]
    10. </script>
    11. <template>
    12. <view class="viewport">
    13. <!-- 推荐封面图 -->
    14. <view class="cover">
    15. <image
    16. src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-05-20/84abb5b1-8344-49ae-afc1-9cb932f3d593.jpg"
    17. ></image>
    18. </view>
    19. <!-- 推荐选项 -->
    20. <view class="tabs">
    21. <text class="text active">抢先尝鲜</text>
    22. <text class="text">新品预告</text>
    23. </view>
    24. <!-- 推荐列表 -->
    25. <scroll-view scroll-y class="scroll-view">
    26. <view class="goods">
    27. <navigator
    28. hover-class="none"
    29. class="navigator"
    30. v-for="goods in 10"
    31. :key="goods"
    32. :url="`/pages/goods/goods?id=`"
    33. >
    34. <image
    35. class="thumb"
    36. src="https://yanxuan-item.nosdn.127.net/5e7864647286c7447eeee7f0025f8c11.png"
    37. ></image>
    38. <view class="name ellipsis">不含酒精,使用安心爽肤清洁湿巾</view>
    39. <view class="price">
    40. <text class="symbol">¥</text>
    41. <text class="number">29.90</text>
    42. </view>
    43. </navigator>
    44. </view>
    45. <view class="loading-text">正在加载...</view>
    46. </scroll-view>
    47. </view>
    48. </template>
    49. <style lang="scss">
    50. page {
    51. height: 100%;
    52. background-color: #f4f4f4;
    53. }
    54. .viewport {
    55. display: flex;
    56. flex-direction: column;
    57. height: 100%;
    58. padding: 180rpx 0 0;
    59. position: relative;
    60. }
    61. .cover {
    62. width: 750rpx;
    63. height: 225rpx;
    64. border-radius: 0 0 40rpx 40rpx;
    65. overflow: hidden;
    66. position: absolute;
    67. left: 0;
    68. top: 0;
    69. }
    70. .scroll-view {
    71. flex: 1;
    72. }
    73. .tabs {
    74. display: flex;
    75. justify-content: space-evenly;
    76. height: 100rpx;
    77. line-height: 90rpx;
    78. margin: 0 20rpx;
    79. font-size: 28rpx;
    80. border-radius: 10rpx;
    81. box-shadow: 0 4rpx 5rpx rgba(200, 200, 200, 0.3);
    82. color: #333;
    83. background-color: #fff;
    84. position: relative;
    85. z-index: 9;
    86. .text {
    87. margin: 0 20rpx;
    88. position: relative;
    89. }
    90. .active {
    91. &::after {
    92. content: '';
    93. width: 40rpx;
    94. height: 4rpx;
    95. transform: translate(-50%);
    96. background-color: #27ba9b;
    97. position: absolute;
    98. left: 50%;
    99. bottom: 24rpx;
    100. }
    101. }
    102. }
    103. .goods {
    104. display: flex;
    105. flex-wrap: wrap;
    106. justify-content: space-between;
    107. padding: 0 20rpx 20rpx;
    108. .navigator {
    109. width: 345rpx;
    110. padding: 20rpx;
    111. margin-top: 20rpx;
    112. border-radius: 10rpx;
    113. background-color: #fff;
    114. }
    115. .thumb {
    116. width: 305rpx;
    117. height: 305rpx;
    118. }
    119. .name {
    120. height: 88rpx;
    121. font-size: 26rpx;
    122. }
    123. .price {
    124. line-height: 1;
    125. color: #cf4444;
    126. font-size: 30rpx;
    127. }
    128. .symbol {
    129. font-size: 70%;
    130. }
    131. .decimal {
    132. font-size: 70%;
    133. }
    134. }
    135. .loading-text {
    136. text-align: center;
    137. font-size: 28rpx;
    138. color: #666;
    139. padding: 20rpx 0 50rpx;
    140. }
    141. </style>

    获取页面参数

    热门推荐页要根据页面参数区分需要获取的是哪种类型的推荐列表,然后再去调用相应的接口,来获取不同的数据,再渲染到页面当中。

    项目首页(传递参数)

    1. // src/pages/index/components/HotPanel.vue
    2. <navigator :url="`/pages/hot/hot?type=${item.type}`">
    3. …省略
    4. </navigator>

    热门推荐页(获取参数)

    1. // src/pages/hot/hot.vue
    2. <script setup lang="ts">
    3. // 热门推荐页 标题和url
    4. const hotMap = [
    5. { type: '1', title: '特惠推荐', url: '/hot/preference' },
    6. { type: '2', title: '爆款推荐', url: '/hot/inVogue' },
    7. { type: '3', title: '一站买全', url: '/hot/oneStop' },
    8. { type: '4', title: '新鲜好物', url: '/hot/new' },
    9. ]
    10. // uniapp 获取页面参数
    11. const query = defineProps<{
    12. type: string
    13. }>()
    14. // console.log(query)
    15. const currHot = hotMap.find((v) => v.type === query.type)
    16. // 动态设置标题
    17. uni.setNavigationBarTitle({ title: currHot!.title })
    18. </script>

    传递不同的页面参数,动态设置推荐页标题。

    获取数据

    地址参数

    不同类型的推荐,需要调用不同的 API 接口:

    type

    推荐类型

    接口路径

    1

    特惠推荐

    /hot/preference

    2

    爆款推荐

    /hot/inVogue

    3

    一站买全

    /hot/oneStop

    4

    新鲜好物

    /hot/new

    接口调用

    调用接口获取推荐商品列表的数据,然后再将这些数据渲染出来。

    接口地址:见上表

    请求方式:GET

    请求参数:

    Query:

    字段名称

    是否必须

    默认值

    备注

    subType

    推荐列表 Tab 项的 id

    page

    1

    页码

    pageSize

    10

    每页商品数量

    请求封装

    经过分析,尽管不同类型推荐的请求 url 不同,但请求参数及响应格式都具有一致性,因此可以将接口的调用进行封装,参考代码如下所示:

    1. import { http } from '@/utils/http'
    2. import type { PageParams } from '@/types/global'
    3. type HotParams = PageParams & {
    4. /** Tab 项的 id,默认查询全部 Tab 项的第 1 页数据 */
    5. subType?: string
    6. }
    7. /**
    8. * 通用热门推荐类型
    9. * @param url 请求地址
    10. * @param data 请求参数
    11. */
    12. export const getHotRecommendAPI = (url: string, data?: HotParams) => {
    13. return http<HotResult>({
    14. method: 'GET',
    15. url,
    16. data,
    17. })
    18. }

    类型声明

    电商项目较为常见商品展示,商品的类型是可复用的,封装到 src/types/global.d.ts 文件中:

    1. // src/types/global.d.ts
    2. /** 通用商品类型 */
    3. export type GoodsItem = {
    4. /** 商品描述 */
    5. desc: string
    6. /** 商品折扣 */
    7. discount: number
    8. /** id */
    9. id: string
    10. /** 商品名称 */
    11. name: string
    12. /** 商品已下单数量 */
    13. orderNum: number
    14. /** 商品图片 */
    15. picture: string
    16. /** 商品价格 */
    17. price: number
    18. }

    其实猜你喜欢的商品类型也相同,可复用通用商品类型,封装到 src/services/home.ts 文件中:

    1. // src/services/home.ts
    2. import type { GoodsItem } from '@/types/global'
    3. // GuessItem 和 GoodsItem 类型相同
    4. export type GuessItem = GoodsItem

    热门推荐类型如下,新建 src/types/hot.d.ts 文件:

    1. import type { PageResult, GoodsItem } from './global'
    2. /** 热门推荐 */
    3. export type HotResult = {
    4. /** id信息 */
    5. id: string
    6. /** 活动图片 */
    7. bannerPicture: string
    8. /** 活动标题 */
    9. title: string
    10. /** 子类选项 */
    11. subTypes: SubTypeItem[]
    12. }
    13. /** 热门推荐-子类选项 */
    14. export type SubTypeItem = {
    15. /** 子类id */
    16. id: string
    17. /** 子类标题 */
    18. title: string
    19. /** 子类对应的商品集合 */
    20. goodsItems: PageResult<GoodsItem>
    21. }

    最后,把获取到的数据结合模板语法渲染到页面中。

    多 Tabs 分页加载

    需要根据当前用户选中的 Tabs 加载对应的列表数据。

    Tabs 交互基础

    当用户点击页面中的 Tab 后,切换展示相应的商品列表,功能相对简单,快速实现即可。

    参考代码

    1. <script setup lang="ts">
    2. // 高亮的下标
    3. const activeIndex = ref(0)
    4. </script>
    5. <template>
    6. <!-- 推荐选项 -->
    7. <view class="tabs">
    8. <text
    9. class="text"
    10. v-for="(item, index) in subTypes"
    11. :key="item.id"
    12. :class="{ active: index === activeIndex }"
    13. @tap="activeIndex = index"
    14. >
    15. {{ item.title }}
    16. </text>
    17. </view>
    18. <!-- 推荐列表 -->
    19. <scroll-view
    20. scroll-y
    21. class="scroll-view"
    22. v-for="(item, index) in subTypes"
    23. :key="item.id"
    24. v-show="activeIndex === index"
    25. >
    26. ...省略
    27. </scroll-view>
    28. </template>

    加载选中 Tabs 分页数据

    根据当前用户选中的 Tabs 加载对应的列表数据。

    操作流程

    1. 根据高亮下标,获取对应列表数据
    2. 提取列表的分页参数,用于发送请求
    3. 滚动触底事件,页码累加,数组追加,退出判断等业务和常规分页基本一致

    参考代码(总)

    热门推荐页

    1. <script setup lang="ts">
    2. import { getHotRecommendAPI } from '@/services/hot'
    3. import type { SubTypeItem } from '@/types/hot'
    4. import { onLoad } from '@dcloudio/uni-app'
    5. import { ref } from 'vue'
    6. // 热门推荐页 标题和url
    7. const hotMap = [
    8. { type: '1', title: '特惠推荐', url: '/hot/preference' },
    9. { type: '2', title: '爆款推荐', url: '/hot/inVogue' },
    10. { type: '3', title: '一站买全', url: '/hot/oneStop' },
    11. { type: '4', title: '新鲜好物', url: '/hot/new' },
    12. ]
    13. // uniapp 获取页面参数
    14. const query = defineProps<{
    15. type: string
    16. }>()
    17. // 获取当前推荐信息
    18. const currHot = hotMap.find((v) => v.type === query.type)
    19. // 动态设置标题
    20. uni.setNavigationBarTitle({ title: currHot!.title })
    21. // 推荐封面图
    22. const bannerPicture = ref('')
    23. // 推荐选项
    24. const subTypes = ref<(SubTypeItem & { finish?: boolean })[]>([])
    25. // 高亮的下标
    26. const activeIndex = ref(0)
    27. // 获取热门推荐数据
    28. const getHotRecommendData = async () => {
    29. const res = await getHotRecommendAPI(currHot!.url, {
    30. // 技巧:环境变量,开发环境,修改初始页面方便测试分页结束
    31. page: import.meta.env.DEV ? 30 : 1,
    32. pageSize: 10,
    33. })
    34. // 保存封面
    35. bannerPicture.value = res.result.bannerPicture
    36. // 保存列表
    37. subTypes.value = res.result.subTypes
    38. }
    39. // 页面加载
    40. onLoad(() => {
    41. getHotRecommendData()
    42. })
    43. // 滚动触底
    44. const onScrolltolower = async () => {
    45. // 获取当前选项
    46. const currsubTypes = subTypes.value[activeIndex.value]
    47. // 分页条件
    48. if (currsubTypes.goodsItems.page < currsubTypes.goodsItems.pages) {
    49. // 当前页码累加
    50. currsubTypes.goodsItems.page++
    51. } else {
    52. // 标记已结束
    53. currsubTypes.finish = true
    54. // 退出并轻提示
    55. return uni.showToast({ icon: 'none', title: '没有更多数据了~' })
    56. }
    57. // 调用API传参
    58. const res = await getHotRecommendAPI(currHot!.url, {
    59. subType: currsubTypes.id,
    60. page: currsubTypes.goodsItems.page,
    61. pageSize: currsubTypes.goodsItems.pageSize,
    62. })
    63. // 新的列表选项
    64. const newsubTypes = res.result.subTypes[activeIndex.value]
    65. // 数组追加
    66. currsubTypes.goodsItems.items.push(...newsubTypes.goodsItems.items)
    67. }
    68. </script>
    69. <template>
    70. <view class="viewport">
    71. <!-- 推荐封面图 -->
    72. <view class="cover">
    73. <image :src="bannerPicture"></image>
    74. </view>
    75. <!-- 推荐选项 -->
    76. <view class="tabs">
    77. <text
    78. v-for="(item, index) in subTypes"
    79. :key="item.id"
    80. class="text"
    81. :class="{ active: index === activeIndex }"
    82. @tap="activeIndex = index"
    83. >{{ item.title }}</text
    84. >
    85. </view>
    86. <!-- 推荐列表 -->
    87. <scroll-view
    88. v-for="(item, index) in subTypes"
    89. :key="item.id"
    90. v-show="activeIndex === index"
    91. scroll-y
    92. class="scroll-view"
    93. @scrolltolower="onScrolltolower"
    94. >
    95. <view class="goods">
    96. <navigator
    97. hover-class="none"
    98. class="navigator"
    99. v-for="goods in item.goodsItems.items"
    100. :key="goods.id"
    101. :url="`/pages/goods/goods?id=${goods.id}`"
    102. >
    103. <image class="thumb" :src="goods.picture"></image>
    104. <view class="name ellipsis">{{ goods.name }}</view>
    105. <view class="price">
    106. <text class="symbol">¥</text>
    107. <text class="number">{{ goods.price }}</text>
    108. </view>
    109. </navigator>
    110. </view>
    111. <view class="loading-text">
    112. {{ item.finish ? '没有更多数据了~' : '正在加载...' }}
    113. </view>
    114. </scroll-view>
    115. </view>
    116. </template>

  • 相关阅读:
    Spring AOP使用与原理
    2023秋招nlp笔试题-太初
    Flink container exit 143 问题排查
    Python MongoDB
    ChatGPT帮助一名儿童确诊病因,之前17位医生无法确诊
    LLM Saturation与多模态AI的崛起
    JenKins使用(Linux)
    java输入输出方式
    svg图片代码data:image/svg+xml转png图片方法
    离线数据仓库第二讲
  • 原文地址:https://blog.csdn.net/m0_67184231/article/details/132842089