学习功能
布局处理
src/views/learn/index.vue```vue<a name="VLbUS"></a> ### 公共组件处理 将 `src/views/course/components/CourseContentList.vue` 剪切到 `src/components/` 目录下作为公共组件 - 修改 `src/views/course/components/courestContent.vue` 引入组件的路径 ```vue <script> import CourseContentList from '@/components/CourseContentList' </script>接口抽离
src/components/CourseCountentList.vue```vue
- `src/views/course/components/courestContent.vue` ```vue <template> <div class="course-content"> <!-- 顶部广告轮播图 --> <!-- 底部课程列表 --> <course-content-list :fetchData="fetchData"></course-content-list> </div> </template> <script> import { getAllAds, getQueryCourses } from '@/services/course' export default { name: 'CourseContent', components: { CourseContentList }, methods: { // 传入请求 fetchData (options) { return getQueryCourses(options) }, }, } </script>接口封装与数据绑定改进
src/services/course.js// 获取已购课程 export const getPurchaseCourse = () => { return request({ method: 'GET', url: '/front/course/getPurchaseCourse' }) }src/components/CourseContentList.vue<template> <div class="course-content-list"> <!-- 下拉刷新 --> <van-pull-refresh v-model="isRefreshing" @refresh='onRefresh'> <!-- 课程列表 --> <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 || item.image" alt=""> </div> <div class="course-info"> <h3 v-text="item.courseName || item.name"></h3> <p class="course-preview" v-html="item.previewFirstField"></p> <!-- 检测是否有价格,如果没有,说明为已购商品,不展示价格区域 --> <p v-if="item.price" class="price-container"> <span class="course-discounts">¥{{item.discounts}}</span> <s class="course-price">¥{{item.price}}</s> </p> </div> </van-cell> </van-list> </van-pull-refresh> </div> </template> <script> // import { getQueryCourses } from '@/services/course' export default { name: 'CourseContentList', props: { // 用于请求数据的函数 fetchData: { type: Function, required: true } }, data () { return { // 数据列表 list: [], // 是否要进行加载 loading: false, // 是否加载完毕 finished: false, // 数据页数 currentPage: 1, // 下拉刷新状态 isRefreshing: false } }, methods: { async onRefresh () { // 还原数据页数为 1 this.currentPage = 1 // 重新请求数据 const { data } = await this.fetchData({ currentPage: this.currentPage, pageSize: 10 }) // 如果存在数据,清空并课程数据,否则结束 if (data.data && data.data.records && data.data.records.length !== 0) { // 重新赋值课程数据 this.list = data.data.records } else if (data.content && data.content.length !== 1) { this.list = data.content } else { this.finished = true } // 提示 this.$toast('刷新成功') // 关闭下拉提示框 this.isRefreshing = false }, async onLoad () { // 请求数据 const { data } = await this.fetchData({ currentPage: this.currentPage, pageSize: 10, status: 1 }) // 检测,如果没有数据了,结束,如果有,保存 if (data.data && data.data.records && data.data.records.length !== 0) { this.list.push(...data.data.records) } else if (data.content && data.content.length !== 1) { this.list = data.content } else { this.finished = true } // 下一次请求下一页 this.currentPage++ // 加载状态结束 this.loading = false // 数据全部加载完成 if (this.list.length < 10) { this.finished = true } } } } </script>
课程详情
组件准备
src/views/course-info/index.vue```javascript课程详情- `src/router/index.js` ```javascript const routes = [ // 课程详情页 { path: '/course-info/:courseId/', name: 'course-info', component: () => import(/* webpackChunkName: 'course-info' */'@/views/course-info/index'), props: true }, // 404 ]src/compontents/CourseContentList.vue跳转课程详情 ```vue
<a name="dOz6Y"></a> ### 接口封装 - `scr/services/course.js` ```javascript // 获取课程详情 export const getCourseById = params => { return request({ method: 'GET', url: '/front/course/getCourseById', params }) }主体内容区域
src/views/course-info/indec.vue```vue<!-- 课程图片 --> <van-cell class="course-img"> <img :src="course.courseImgUrl" alt=""> </van-cell> <!-- 课程描述 --> <van-cell class="course-desctription"> <!-- 课程名称 --> <h2 v-text="course.courseName"></h2> <!-- 课程概述 --> <p v-text="course.previewFirstField"></p> <!-- 课程销售信息 --> <div class="course-saleInfo"> <p class="course-price"> <span class="discounts">¥{{ course.discounts }} </span> <span>¥{{ course.price }}</span> </p> <span class="tag">{{course.sales}}人已购</span> <span class="tag">每周三、五更新</span> </div> </van-cell> <!-- 课程详细内容 --> <van-cell class="course-detail"></van-cell><a name="rGOH9"></a> ### 选项卡处理 - `src/views/course-info/index.vue` ```vue <!-- 课程详细内容 --> <van-cell class="course-detail"> <!-- 选项卡 --> <van-tabs sticky scrollspy> <van-tab title="详情"> <!-- 详情在后台是通过富文本编辑器设置的,内容为 HTML 文本 --> <div v-html="course.courseDescription"></div> </van-tab> <van-tab title="内容">内容</van-tab> </van-tabs> </van-cell>章节组件封装
src/services/course.js// 获取课程章节 export const getSectionAndLesson = params => { return request({ method: 'GET', url: '/front/course/session/getSectionAndLesson', params }) }src/views/course-info/index.vue```vue<!-- 课程图片 --> <!-- 课程描述 --> <!-- 课程详细内容 --> <van-cell class="course-detail"> <!-- 选项卡 --> <van-tabs sticky scrollspy> <van-tab title="内容"> <course-section-and-lesson v-for="item in sections" :key="item.id" :section-data="item"> </course-section-and-lesson> </van-tab> </van-tabs> </van-cell>
<a name="Hsewr"></a> #### 章节组件布局 - `src/views/course-info/components/CourseSectionAndLesson.vue` ```vue <template> <div class="section-and-lesson"> <!-- 章节标题 --> <h2 class="section" v-text="sectionData.sectionName"></h2> <!-- 课时标题 --> <p v-for="item in sectionData.courseLessons" :key="item.id" class="lesson" @click="handleClick(item)"> <span v-text="item.theme"></span> <van-icon v-if="item.canPlay" name='play-circle' size='20'></van-icon> <van-icon v-else name='lock' size='20'></van-icon> </p> </div> </template> <script> export default { name: 'CourseSectionAndLesson', props: { // 章节信息 sectionData: { type: Object, required: true } } } </script> <style lang="scss" scoped> .section-and-lesson { padding: 0 20px; } .lesson { display: flex; justify-content: space-between; } </style>底部支付处理
src/views/course-info/index.vue ```vue
<div class="price"> <span v-text="course.discountsTag"></span> <span class="discounts">¥{{ course.discounts }}</span> <span>¥{{ course.price }}</span> </div> <van-button type='primary' @click="handlePay">立即购买</van-button><a name="ptkJx"></a> ## 课时视频 <a name="bwQq7"></a> ### 组件准备 - src/router/index.js ```javascript const router = [ // 视频页 { path: '/lesson-video/:lessonId/', name: 'lesson-video', component: () => import(/* webpackChunkName: 'lesson-video' */'@/views/course-info/video'), props: true }, ]- src/views/course-info/CourseSectionAndLesson.vue
methods: { handleClick (lessonInfo) { if (lessonInfo.canPlay) { this.$router.push({ name: 'lesson-video', params: { lessonId: lessonInfo.id } }) } } }
接口封装
src/services/course.js
// 根据fileId获取阿里云对应的视频播放信息 export const getVideoInfo = params => { return request({ method: 'GET', url: '/front/course/media/videoPlayInfo', params }) }src/views/course-info/video.vue ```vue
<a name="Hv8DG"></a> ### 阿里云视频播放 - public/index.html ```html <!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> <!-- 播放器样式 --> <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.9.3/skins/default/aliplayer-min.css" /> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- 播放器插件 --> <script src="https://g.alicdn.com/de/prismplayer/2.9.3/aliplayer-h5-min.js"></script> <!-- built files will be auto injected --> </body> </html>
- src/views/course-info/CourseSectionAndLesson.vue
