页面路径:src/pages/index/index.vue
搜索框
首页搜索框点击以后会跳转到课程列表页,所以没有过多的交互,
直接使用uni-ui组件即可
<!-- 搜索框 -->
<v-search-bar @focus="focus" class="header"></v-search-bar>
focus() {
// 点击首页搜索框,跳转到课程页面
uni.switchTab({
url: "/pages/course/index?focus=true",
});
},
url后面带focus参数的目的是跳转到了课程页以后search-bar直接聚焦让用户进行输入操作
轮播图
定义接口
接口文档:https://www.yuque.com/xiumubai/fe/yf8tf3#sHXSf
import Service from '@/utils/http';
class Course extends Service {
/**
* @description: 获取首页banner图
* @param {*} options
* @return {*}
*/
banner(options = {}) {
options.url = '/api/cms/banner';
return this.get(options);
}
}
const courseService = new Course();
export default courseService;
调用接口
import courseService from "@/services/course";
export default {
data() {
return {
bannerList: [],
}
},
methods: {
async getBannerList() {
try {
const res = await courseService.banner();
this.bannerList = res.data.bannerList;
} catch (e) {
console.log("e", e);
}
},
}
}
Tips: 对于api请求部分的代码,一定要
try {} catch(e) {}
,这样即使接口挂掉了,也不会导致整个程序崩溃掉。
渲染视图
<template>
<view class="swiper_box">
<swiper
class="swiper"
circular
indicator-dots
autoplay
:interval="2000"
indicator-color="#fff"
indicator-active-color="#26B7FF"
:duration="500"
>
<swiper-item v-for="item in bannerList" :key="item.src">
<view class="swiper_item uni_bg_red">
<image mode="heightFix" :src="item.imageUrl" />
</view>
</swiper-item>
</swiper>
</view>
</template>
上面是一个基础的从定义接口,到调用接口,渲染数据的三步骤,后面所有的涉及接口操作都是这个步骤,可直接参考源码部分。
课程&大咖
接口地址:https://www.yuque.com/xiumubai/fe/yf8tf3#td9Jy
组件封装
从布局上来,课程和名师卡片样式基本一致,我们可以单独封装成组件做复用,针对个别不一致的地方使用参数进行控制,下面是一个v-vard
组件,后面我们封装的所有组件都会以v-[name]
的形式进行统一的命名。
<template>
<view class="preferences">
<h2 class="preferences_title">
{{ title }}
<navigator
class="link"
:url="link"
:open-type="type === 'course' ? 'switchTab' : 'navigate'"
>
{{ linkTitle }}
<uni-icons type="right" size="12"></uni-icons>
</navigator>
</h2>
<slot></slot>
</view>
</template>
<script>
export default {
name: 'v-card',
props: {
title: {
type: String,
default: '热门',
},
linkTitle: {
type: String,
default: '全部',
},
type: {
type: String,
default: 'course',
},
link: {
type: String,
default: '',
},
},
};
</script>
<style lang="scss" scoped>
.preferences {
padding: 0 16px 24px 16px;
&_title {
color: $uni-text-color-white;
font-size: 14px;
margin-bottom: 10px;
justify-content: space-between;
align-items: center;
display: flex;
.link {
font-size: $uni-font-size-12;
color: $uni-text-color;
}
}
}
</style>
v-vard-list
组件
<template>
<view class="preferences_list">
<view
class="preferences_list_item"
v-for="(item, index) in list"
:key="index"
>
<navigator
class="list_item_card"
:url="
type == 'course'
? '/pages/course/detail?id=' + item.id
: '/pages/teacher/detail?id=' + item.id
"
>
<view class="list_item_card_img">
<image
alt="封面"
:mode="type === 'course' ? 'fill' : 'aspectFit'"
:src="type === 'course' ? item.cover : item.avatar"
/>
</view>
<view class="list_item_card_content">
<h3 class="item_content_name">
{{ type === "course" ? item.title : item.intro }}
</h3>
<view class="item_content__labal">
<uni-icons
v-if="type === 'course'"
type="fire"
size="18"
color="#fa3f4e"
></uni-icons>
<view
:class="['study_num', type === 'teacher' ? 'teacher_name' : '']"
>
<text v-if="type === 'course'">{{ item.lessonNum }}人已学习</text>
<text v-else>{{ item.name }}</text>
</view>
</view>
<view class="item-content_footer" v-if="type === 'course'">
<view class="footer_price">¥{{ item.price }}</view>
<view class="footer_buy_num">{{ item.buyCount }}人购买</view>
</view>
</view>
</navigator>
</view>
</view>
</template>
<script>
export default {
name: "v-card-list",
props: {
list: {
type: Array,
default: [],
},
type: {
type: String,
default: "course",
},
},
};
</script>
<style lang="scss" scoped>
.preferences_list {
&_item {
padding-bottom: 8px;
display: inline-block;
width: 50%;
box-sizing: border-box;
&:nth-child(2n-1) {
padding-right: 4px;
}
&:nth-child(2n) {
padding-left: 4px;
}
.list_item_card {
display: block;
width: 100%;
&_img {
width: 100%;
height: 95px;
background-image: url("https://8.idqqimg.com/edu/mobilev2/m-core/3d1dd248376a6da4a15e0000184f44c6.png");
background-repeat: no-repeat;
background-size: contain;
border-radius: 8px 8px 0 0;
image {
height: 100%;
width: 100%;
border-radius: 8px 8px 0 0;
}
}
&_content {
background: $uni-bg-wrapper-color;
border-radius: 0 0 8px 8px;
.item_content_name {
height: 40px;
padding: 4px 4px;
color: $uni-text-color-name;
font-size: $uni-font-size-14;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.item_content__labal {
display: flex;
align-items: center;
padding: 0 4px;
.study_num {
font-size: $uni-font-size-12;
color: $uni-text-color-name;
margin-left: 4px;
}
.teacher_name {
color: #68cb9b;
margin-left: 0;
font-size: 14px;
margin-bottom: 4px;
}
}
.item-content_footer {
display: flex;
align-items: center;
padding: 4px;
.footer_price {
font-size: $uni-font-size-16;
color: $price-font-color;
margin-right: $uni-spacing-4;
}
.footer_buy_num {
font-size: $uni-font-size-12;
color: $uni-text-color-name;
}
}
}
}
}
}
</style>
使用组件
<view class="preferences_wrapper">
<v-card
title="热门课程"
linkTitle="全部课程"
type="course"
link="/pages/course/index"
>
<v-card-list type="course" :list="courseList"></v-card-list>
</v-card>
<v-card
title="名师大咖"
linkTitle="全部名师"
type="teacher"
link="/pages/teacher/index"
>
<v-card-list type="teacher" :list="teacherList"></v-card-list>
</v-card>
</view>
接口定义和调用参考轮播图部分,或可直接看源码src/services/course.js
,形式一模一样,这里不再过多甩文。
对于跳转链接的区分:
navigateTo(link, type) {
if (type === "course") {
uni.switchTab({ url: link });
} else if (type === "teacher") {
uni.navigateTo({ url: link });
}
},
返回顶部
返回顶部这个组件需要单独讲一讲具体的封装过程,图示如下:
封装这个组件我们需要考虑下面几个点:
- 什么条件显示
返回顶部
按钮 - 点击
返回顶部
如何平滑返回顶部 - 按钮位置需要可配置
下面我们来封装这个组件
<template>
<view
class="back_top"
v-show="show"
@click="backTop"
:style="{ bottom: bottom, right: right }"
>
<image
src="https://cdn-cos-ke.myoed.com/ke_proj/mobilev2/m-core/03de0936a7aafee76646b8b2d5fa5b4f.png"
/>
</view>
</template>
<script>
export default {
name: 'v-back-top',
props: {
bottom: {
type: [String, Number],
default: 20,
},
right: {
type: [String, Number],
default: 10,
},
},
data() {
return {
show: false,
scrollHeight: 0,
};
},
mounted() {
// 必须在page层级页面触发onPageScroll方法,才能接受到事件
uni.$on('onPageScroll', (e) => {
uni.getSystemInfo({
success: (res) => {
this.scrollHeight = e.scrollTop - res.windowHeight;
this.show = this.scrollHeight > 0 ? true : false;
},
});
});
},
methods: {
backTop() {
if (this.scrollHeight > 0) {
uni.pageScrollTo({
duration: 500,
scrollTop: 0,
success: () => {
this.show = false;
},
});
}
},
},
};
</script>
<style lang="scss" scoped>
.back_top {
position: fixed;
right: 10px;
bottom: 30px;
z-index: 10000;
width: 52px;
height: 52px;
image {
width: 100%;
height: 100%;
}
}
</style>
:::info
- 判断
返回顶部
显示条件,当滚动高度-窗口高度>0即可显示; - 点击
返回顶部
,使用uni.pageScrollTo()
方法使滚动条滚动到顶部,并给添加duration参数,平滑滚动; - 定义props为right和bottom来控制按钮位置。
:::
在上面的代码中我们使用了
uni.$on('onPageScroll')
做了事件监听,因为在子组件中无法监听到页面滚动的事件,只能在在父组件来触发,具体代码如下:
具体使用:// 监听滚动事件,控制返回顶部按钮
onPageScroll(res) {
uni.$emit("onPageScroll", res);
},
<template>
<view>
<v-back-top></v-back-top>
</view>
</template>