页面路径:src/pages/index/index.vue

搜索框

首页搜索框点击以后会跳转到课程列表页,所以没有过多的交互,
直接使用uni-ui组件即可

  1. <!-- 搜索框 -->
  2. <v-search-bar @focus="focus" class="header"></v-search-bar>
  1. focus() {
  2. // 点击首页搜索框,跳转到课程页面
  3. uni.switchTab({
  4. url: "/pages/course/index?focus=true",
  5. });
  6. },

url后面带focus参数的目的是跳转到了课程页以后search-bar直接聚焦让用户进行输入操作

轮播图

定义接口

接口文档:https://www.yuque.com/xiumubai/fe/yf8tf3#sHXSf

  1. import Service from '@/utils/http';
  2. class Course extends Service {
  3. /**
  4. * @description: 获取首页banner图
  5. * @param {*} options
  6. * @return {*}
  7. */
  8. banner(options = {}) {
  9. options.url = '/api/cms/banner';
  10. return this.get(options);
  11. }
  12. }
  13. const courseService = new Course();
  14. export default courseService;

调用接口

  1. import courseService from "@/services/course";
  2. export default {
  3. data() {
  4. return {
  5. bannerList: [],
  6. }
  7. },
  8. methods: {
  9. async getBannerList() {
  10. try {
  11. const res = await courseService.banner();
  12. this.bannerList = res.data.bannerList;
  13. } catch (e) {
  14. console.log("e", e);
  15. }
  16. },
  17. }
  18. }

Tips: 对于api请求部分的代码,一定要try {} catch(e) {},这样即使接口挂掉了,也不会导致整个程序崩溃掉。

渲染视图

  1. <template>
  2. <view class="swiper_box">
  3. <swiper
  4. class="swiper"
  5. circular
  6. indicator-dots
  7. autoplay
  8. :interval="2000"
  9. indicator-color="#fff"
  10. indicator-active-color="#26B7FF"
  11. :duration="500"
  12. >
  13. <swiper-item v-for="item in bannerList" :key="item.src">
  14. <view class="swiper_item uni_bg_red">
  15. <image mode="heightFix" :src="item.imageUrl" />
  16. </view>
  17. </swiper-item>
  18. </swiper>
  19. </view>
  20. </template>

上面是一个基础的从定义接口,到调用接口,渲染数据的三步骤,后面所有的涉及接口操作都是这个步骤,可直接参考源码部分。

课程&大咖

接口地址:https://www.yuque.com/xiumubai/fe/yf8tf3#td9Jy
image.png

组件封装

从布局上来,课程和名师卡片样式基本一致,我们可以单独封装成组件做复用,针对个别不一致的地方使用参数进行控制,下面是一个v-vard组件,后面我们封装的所有组件都会以v-[name]的形式进行统一的命名。

  1. <template>
  2. <view class="preferences">
  3. <h2 class="preferences_title">
  4. {{ title }}
  5. <navigator
  6. class="link"
  7. :url="link"
  8. :open-type="type === 'course' ? 'switchTab' : 'navigate'"
  9. >
  10. {{ linkTitle }}
  11. <uni-icons type="right" size="12"></uni-icons>
  12. </navigator>
  13. </h2>
  14. <slot></slot>
  15. </view>
  16. </template>
  17. <script>
  18. export default {
  19. name: 'v-card',
  20. props: {
  21. title: {
  22. type: String,
  23. default: '热门',
  24. },
  25. linkTitle: {
  26. type: String,
  27. default: '全部',
  28. },
  29. type: {
  30. type: String,
  31. default: 'course',
  32. },
  33. link: {
  34. type: String,
  35. default: '',
  36. },
  37. },
  38. };
  39. </script>
  40. <style lang="scss" scoped>
  41. .preferences {
  42. padding: 0 16px 24px 16px;
  43. &_title {
  44. color: $uni-text-color-white;
  45. font-size: 14px;
  46. margin-bottom: 10px;
  47. justify-content: space-between;
  48. align-items: center;
  49. display: flex;
  50. .link {
  51. font-size: $uni-font-size-12;
  52. color: $uni-text-color;
  53. }
  54. }
  55. }
  56. </style>

v-vard-list组件

  1. <template>
  2. <view class="preferences_list">
  3. <view
  4. class="preferences_list_item"
  5. v-for="(item, index) in list"
  6. :key="index"
  7. >
  8. <navigator
  9. class="list_item_card"
  10. :url="
  11. type == 'course'
  12. ? '/pages/course/detail?id=' + item.id
  13. : '/pages/teacher/detail?id=' + item.id
  14. "
  15. >
  16. <view class="list_item_card_img">
  17. <image
  18. alt="封面"
  19. :mode="type === 'course' ? 'fill' : 'aspectFit'"
  20. :src="type === 'course' ? item.cover : item.avatar"
  21. />
  22. </view>
  23. <view class="list_item_card_content">
  24. <h3 class="item_content_name">
  25. {{ type === "course" ? item.title : item.intro }}
  26. </h3>
  27. <view class="item_content__labal">
  28. <uni-icons
  29. v-if="type === 'course'"
  30. type="fire"
  31. size="18"
  32. color="#fa3f4e"
  33. ></uni-icons>
  34. <view
  35. :class="['study_num', type === 'teacher' ? 'teacher_name' : '']"
  36. >
  37. <text v-if="type === 'course'">{{ item.lessonNum }}人已学习</text>
  38. <text v-else>{{ item.name }}</text>
  39. </view>
  40. </view>
  41. <view class="item-content_footer" v-if="type === 'course'">
  42. <view class="footer_price">¥{{ item.price }}</view>
  43. <view class="footer_buy_num">{{ item.buyCount }}人购买</view>
  44. </view>
  45. </view>
  46. </navigator>
  47. </view>
  48. </view>
  49. </template>
  50. <script>
  51. export default {
  52. name: "v-card-list",
  53. props: {
  54. list: {
  55. type: Array,
  56. default: [],
  57. },
  58. type: {
  59. type: String,
  60. default: "course",
  61. },
  62. },
  63. };
  64. </script>
  65. <style lang="scss" scoped>
  66. .preferences_list {
  67. &_item {
  68. padding-bottom: 8px;
  69. display: inline-block;
  70. width: 50%;
  71. box-sizing: border-box;
  72. &:nth-child(2n-1) {
  73. padding-right: 4px;
  74. }
  75. &:nth-child(2n) {
  76. padding-left: 4px;
  77. }
  78. .list_item_card {
  79. display: block;
  80. width: 100%;
  81. &_img {
  82. width: 100%;
  83. height: 95px;
  84. background-image: url("https://8.idqqimg.com/edu/mobilev2/m-core/3d1dd248376a6da4a15e0000184f44c6.png");
  85. background-repeat: no-repeat;
  86. background-size: contain;
  87. border-radius: 8px 8px 0 0;
  88. image {
  89. height: 100%;
  90. width: 100%;
  91. border-radius: 8px 8px 0 0;
  92. }
  93. }
  94. &_content {
  95. background: $uni-bg-wrapper-color;
  96. border-radius: 0 0 8px 8px;
  97. .item_content_name {
  98. height: 40px;
  99. padding: 4px 4px;
  100. color: $uni-text-color-name;
  101. font-size: $uni-font-size-14;
  102. white-space: normal;
  103. overflow: hidden;
  104. text-overflow: ellipsis;
  105. display: -webkit-box;
  106. -webkit-line-clamp: 2;
  107. -webkit-box-orient: vertical;
  108. }
  109. .item_content__labal {
  110. display: flex;
  111. align-items: center;
  112. padding: 0 4px;
  113. .study_num {
  114. font-size: $uni-font-size-12;
  115. color: $uni-text-color-name;
  116. margin-left: 4px;
  117. }
  118. .teacher_name {
  119. color: #68cb9b;
  120. margin-left: 0;
  121. font-size: 14px;
  122. margin-bottom: 4px;
  123. }
  124. }
  125. .item-content_footer {
  126. display: flex;
  127. align-items: center;
  128. padding: 4px;
  129. .footer_price {
  130. font-size: $uni-font-size-16;
  131. color: $price-font-color;
  132. margin-right: $uni-spacing-4;
  133. }
  134. .footer_buy_num {
  135. font-size: $uni-font-size-12;
  136. color: $uni-text-color-name;
  137. }
  138. }
  139. }
  140. }
  141. }
  142. }
  143. </style>

使用组件

  1. <view class="preferences_wrapper">
  2. <v-card
  3. title="热门课程"
  4. linkTitle="全部课程"
  5. type="course"
  6. link="/pages/course/index"
  7. >
  8. <v-card-list type="course" :list="courseList"></v-card-list>
  9. </v-card>
  10. <v-card
  11. title="名师大咖"
  12. linkTitle="全部名师"
  13. type="teacher"
  14. link="/pages/teacher/index"
  15. >
  16. <v-card-list type="teacher" :list="teacherList"></v-card-list>
  17. </v-card>
  18. </view>

接口定义和调用参考轮播图部分,或可直接看源码src/services/course.js,形式一模一样,这里不再过多甩文。
对于跳转链接的区分:

  1. navigateTo(link, type) {
  2. if (type === "course") {
  3. uni.switchTab({ url: link });
  4. } else if (type === "teacher") {
  5. uni.navigateTo({ url: link });
  6. }
  7. },

返回顶部

返回顶部这个组件需要单独讲一讲具体的封装过程,图示如下:
image.png
封装这个组件我们需要考虑下面几个点:

  • 什么条件显示返回顶部按钮
  • 点击返回顶部如何平滑返回顶部
  • 按钮位置需要可配置

下面我们来封装这个组件

  1. <template>
  2. <view
  3. class="back_top"
  4. v-show="show"
  5. @click="backTop"
  6. :style="{ bottom: bottom, right: right }"
  7. >
  8. <image
  9. src="https://cdn-cos-ke.myoed.com/ke_proj/mobilev2/m-core/03de0936a7aafee76646b8b2d5fa5b4f.png"
  10. />
  11. </view>
  12. </template>
  13. <script>
  14. export default {
  15. name: 'v-back-top',
  16. props: {
  17. bottom: {
  18. type: [String, Number],
  19. default: 20,
  20. },
  21. right: {
  22. type: [String, Number],
  23. default: 10,
  24. },
  25. },
  26. data() {
  27. return {
  28. show: false,
  29. scrollHeight: 0,
  30. };
  31. },
  32. mounted() {
  33. // 必须在page层级页面触发onPageScroll方法,才能接受到事件
  34. uni.$on('onPageScroll', (e) => {
  35. uni.getSystemInfo({
  36. success: (res) => {
  37. this.scrollHeight = e.scrollTop - res.windowHeight;
  38. this.show = this.scrollHeight > 0 ? true : false;
  39. },
  40. });
  41. });
  42. },
  43. methods: {
  44. backTop() {
  45. if (this.scrollHeight > 0) {
  46. uni.pageScrollTo({
  47. duration: 500,
  48. scrollTop: 0,
  49. success: () => {
  50. this.show = false;
  51. },
  52. });
  53. }
  54. },
  55. },
  56. };
  57. </script>
  58. <style lang="scss" scoped>
  59. .back_top {
  60. position: fixed;
  61. right: 10px;
  62. bottom: 30px;
  63. z-index: 10000;
  64. width: 52px;
  65. height: 52px;
  66. image {
  67. width: 100%;
  68. height: 100%;
  69. }
  70. }
  71. </style>

:::info

  1. 判断返回顶部显示条件,当滚动高度-窗口高度>0即可显示;
  2. 点击返回顶部,使用uni.pageScrollTo()方法使滚动条滚动到顶部,并给添加duration参数,平滑滚动;
  3. 定义props为right和bottom来控制按钮位置。 ::: 在上面的代码中我们使用了uni.$on('onPageScroll')做了事件监听,因为在子组件中无法监听到页面滚动的事件,只能在在父组件来触发,具体代码如下:
    1. // 监听滚动事件,控制返回顶部按钮
    2. onPageScroll(res) {
    3. uni.$emit("onPageScroll", res);
    4. },
    具体使用:
    1. <template>
    2. <view>
    3. <v-back-top></v-back-top>
    4. </view>
    5. </template>