选课功能
Course组件为选课页面,分为上中下三个部分,顶部为显示logo的导航区域,中间为课程选择区域(轮播+课程列表),底部使用公共组件LayoutFooter
结构大概是长这个样子的
course
├── components
│ ├── CourseHeader.vue
│ └── CourseContent.vue
└── index.vue
在course/index.vue中引入CourseHeader,CourseContent,LayoutFooter
<template>
<div class="course">
<layout-footer></layout-footer>
<course-header></course-header>
<course-content></course-content>
</div>
</template>
<script>
import CourseHeader from './components/courseHeader'
import CourseContent from './components/courseContent'
import LayoutFooter from '@/components/LayoutFooter'
export default {
name: 'Course',
components: {
LayoutFooter,
CourseHeader,
CourseContent
}
}
</script>
<style lang="scss" scoped>
</style>
CourseHeader组件
导航部分只有logo图显示,直接设置就行了
logo图使用vant的Image组件,引入图片,调整样式就好
<template>
<div class="course-header">
<van-image
:src="require('@/assets/logo.png')"
></van-image>
</div>
</template>
<script>
export default {
name: 'CourseHeader'
}
</script>
<style lang="scss" scoped>
.course-header {
height: 50px;
}
.van-image {
width: 180px;
margin-left: -20px;
}
</style>
CourseContent
选课内容区域分为上下两部分,顶部为轮播图,底部为课程列表
广告轮播图
布局处理
使用vant的Swipe轮播组件
结构设置完毕之后,需要请求广告数据动态创建
封装接口
广告位需要用到两个接口,我们这边涉及到的项目与上一个后台管理其实是共通的功能,所以还是以前的地址
- 获取所有的广告位:接口
- 获取广告位及其对应某个约定id的广告:接口
但是!这里获取所有广告位的接口无需再进行封装了,因为我们通过接口的响应数据(参考上一个项目)得到一个讯息那就是“首页顶部轮播图”的位置标记spaceKey为999,我们可以将这个数值固定使用,这个算是我们和后端的一种约定吧
新建src/services/course.js 封装获取广告的接口功能就可以了
import request from '@/utils/request'
// 获取广告位及其对应的广告
export const getAllAds = params => {
return request({
method: 'GET',
url: '/front/ad/getAllAds',
params
})
}
1.引入,请求数据,存储,保存在data
- 根据数据响应的格式,我们可以得知content[0].adDTOList就是当前广告位的广告列表
- img:图片地址
- id:课程id
2.将轮播图项根据广告数据设置,进行一波样式处理
3.状态筛选,由于广告返回数据中只有上架状态的数据才能展示,我们要进行一定程度的筛选,上架的status为1,下架为0,需要对结果进行遍历,我们推荐使用计算属性,这在一定程度上优化了性能
综上所述:
<template>
<div class="course-content">
<van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="item in ativeAdList" :key="item.id">
<img :src="item.img" alt="">
</van-swipe-item>
</van-swipe>
</div>
</template>
<script>
import { getAllAds } from '@/services/course'
export default {
name: 'CourseContent',
data () {
return {
// 轮播图图片列表
adList: []
}
},
created () {
// 请求轮播图图片信息
this.loadAds()
},
methods: {
async loadAds () {
const { data } = await getAllAds({
// 此处的999代表首页顶部轮播图的广告位
spaceKeys: '999'
})
// 存储数据到adList
this.adList = data.content[0].adDTOList
}
},
computed: {
ativeAdList () {
return this.adList.filter(item => item.status === 1)
}
}
}
</script>
<style lang="scss" scoped>
.my-swipe {
width: 100%;
}
.my-swipe img {
height: 170px;
}
.my-swipe .van-swipe-item {
display: flex;
justify-content: center;
overflow: hidden;
}
</style>
轮播图完成
课程列表
基础布局
创建course/components/CourseContentList.vue
//CourseContentList.vue
<template>
<div class="course-content-list"></div>
</template>
<script>
export default {
name: 'CourseContentList'
}
</script>
<style lang="scss" scoped></style>
将该组件引入到CourseContent.vue中
这个功能采用的是Vant的List列表组件
设置到页面中
- load事件:用于进行数据请求
- list组件初始化之后会触发一次load事件,用于加载首屏的数据
- 如果一次请求记载的数据较少,列表内容无法铺满屏幕,就会自动再次触发load,直到内容铺满屏幕或者加载全部的数据
- 滑动列表触底时也会触发load
- loading:控制(触底后)新数据是否加载
- 未加载时loading为false,当load事件触发,loading自动变更为true,显示加载状态提示,请求过程中,无法再次触发load事件,请求完毕之后loading为false取消加载提示即可
- finished:
- 每次请求完毕之后,需要手动将loading设置为false,表示本次加载结束
- 所有数据加载结束,finished变为true,这个时候就不会触发load事件了
<template>
<div class="course-content-list">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item" :title="item" />
</van-list>
</div>
</template>
<script>
export default {
name: 'CourseContentList',
data () {
return {
// 用于存储数据,这里我们模拟一下
list: [1, 2, 3, 4, 5],
// 是否处于加载中
loading: false,
// 是否加载完毕
finished: false
}
},
methods: {
onLoad () {
console.log('发送请求')
}
}
}
</script>
<style lang="scss" scoped>
</style>
固定列表
上述代码出现之后有两个问题
- 列表滚动时实际上是整个course组件都在进行滚动,这个时候顶部的导航与轮播就会跟着一起滚动
- 加载完毕之后,列表底部的提示内容被LayoutFooter的路由按钮功能遮挡住了
解决方法: - 设置列表固定定位
- CourseHeader 高度定为50px
- 轮播高度定为170px
- LayoutFooter高度定为50px
故而我们进行样式设置:
// CourseContentList.vue
...
<style lang="scss" scoped>
.course-content-list {
position: fixed;
left: 0;
right: 0;
top: 220px;
bottom: 50px;
overflow-y: auto;
}
</style>
功能是没什么问题了,但是要注意有一个很明显的问题,课程列表组件是一个独立的组件功能,应该只对自身负责,当前操作中设置的top和bottom实际上是根据父组件的布局来设置的,那么要是父组件的布局变化,或者其他组件需要用到这个列表组件,都需要修改子组件的数据,这是非常不合理的
说了这么多,总结一下也就是说:CourseContentList和CourseContent耦合了
我们呢,应该将与父组件布局相关的top和left由父组件设置,进行解耦
将CourseContentList的bottom和top修改为0
// CourseContentList.vue
...
<style lang="scss" scoped>
.course-content-list {
...
top: 0;
bottom: 0;
}
</style>
在父组件CourseContent中设置子组件容器的位置就好
// CourseContent.vue
...
// 底部课程列表的位置样式,不应该设置在组件内容
.course-content-list {
top: 220px;
bottom: 50px;
}
封装接口
需要使用以下接口:
- 分页查询课程内容:接口
...
// 分页查询课程信息
export const getQueryCourses = data => {
return request({
method: 'POST',
url: '/boss/course/getQueryCourses',
data
})
}
引入,并请求数据
声明一个currentPage,来记录第几次触发下拉,触发一次请求一次数据,请求参数有三个,一个是当前请求次数,一个是一次请求多少数据,一个是上架课程状态码1,每次请求之后都要手动更新加载状态位false,请求结束后要finished结束
布局与数据绑定
根据数据进行布局设置,并且绑定数据
在给list赋值之前要判断一下传来的数据是否有值,因为这个组件将来也是其他组件要使用的子组件
// CourseContentList.vue
...
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell
v-for="item in list"
:key="item.id"
>
<div>
<img :src="item.courseImgUrl" alt="">
</div>
<div class="course-info">
<h3 v-text="item.courseName"></h3>
<p class="course-preview" v-html="item.previewFirstField"></p>
<p class="price-container">
<span class="course-discounts">¥{{item.discounts}}</span>
<s class="course-price">¥{{item.price}}</s>
</p>
</div>
</van-cell>
</van-list>
...
<script>
...
methods: {
async onLoad () {
const { data } = await getQueryCourses(...)
// 检测,如果没有数据了,结束,如果有,保存
if (data.data && data.data.records && data.data.records.length !== 0) {
this.list.push(...data.data.records)
}
...
}
}
...
</script>
...
<style lang="scss" scoped>
.course-content-list {
position: fixed;
left: 0;
right: 0;
top: 220px;
bottom: 50px;
overflow-y: auto;
}
// 课程条目设置flex,内部元素左右显示
.van-cell__value {
height: 100px;
padding: 10px 0;
display: flex;
}
// 左侧图设置固定尺寸
.van-cell__value img {
width: 75px;
height: 100%;
border-radius: 5px;
}
// 右侧内容区域 flex: 1 撑满父元素
.course-info {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 10px;
}
.course-info .course-preview {
flex-grow: 1;
}
.course-info .course-discounts {
color: #ff7452;
margin-right: 10px;
}
p, h3 {
margin: 0;
}
</style>
下拉刷新
下拉的时候,CourseContentList需要刷新
这里我们使用了Vant的PullRefresh下拉刷新组件
- 这里示例中使用了Toast轻提示组件,在我们这种使用了全局导入vant的项目基础之下可以使用
this.$toast()
调用 -
也可以使用下拉刷新组件的success-text与success-duration配合使用(自行探索)
处理函数
结构
用于控制的数据