day82:luffy:课程详情页面显示&章节和课时显示&视频播放
目录1.初始课程详情页面 2.视频播放组件 3.课程详情页面后端接口实现 4.课程详情页面-前端 5.CKEditor富文本编辑器 6.课程章节和课时显示-后端接口 7.课程章节和课时显示-前端 1.初始课程详情页面1.Detail.vue<!-- 课程详情页面初始页面 --> <template> div class="detail"> Vheader/> ="main"> ="course-info"> ="wrap-left"> </div="wrap-right"> h3 ="course-name">flaskh3p ="data">111人在学 课程总时长:111课时/12小时 难度:p="sale-time"> ="sale-type">限时免费="expire">距离结束:仅剩 01天 04小时 33分 span ="second">08span> 秒="course-price">活动价="discount">¥0.00="original">¥1111="buy"="buy-btn"> button ="buy-now">立即购买button="free">免费试学="add-cart"><img src="/static/img/cart-yellow.svg" alt="">加入购物车="course-tab"ul ="tab-list"li :class="tabIndex==1?'active':''" @click="tabIndex=1">详情介绍li="tabIndex==2?'active':''"="tabIndex=2">课程章节 ="tabIndex!=2?'free':''">(试学)></="tabIndex==3?'active':''"="tabIndex=3">用户评论 (42)="tabIndex==4?'active':''"="tabIndex=4">常见问题ul="course-content"="course-tab-list"="tab-item" v-if="tabIndex==1"="course-brief" v-html="tabIndex==2"="tab-item-title"="chapter">课程章节="chapter-length">共11章 147个课时="chapter-item"="chapter-title"="/static/img/1.png">第1章·Linux硬件基础="lesson-list"> ="lesson-item"> ="name"="index">1-1> 课程介绍-学习流程>免费="time">07:30 ="/static/img/chapter-player.svg"="try">立即试学>1-2> 服务器硬件-详解>第2章·Linux发展过程>2-1> 操作系统组成-Linux发展过程>2-2> 自由软件-GNU-GPL核心讲解="tabIndex==3"> 用户评论 ="tabIndex==4" 常见问题 ="course-side"> ="teacher-info"> h4 ="side-title">授课老师h4="teacher-content"> ="cont1"> > ="teacher-name">xxx="teacher-title">ssss="narrative" >kkkkFooter/> > > script> import Vheader from "./common/Vheader import Footer from ./common/Footer export default { name: Detail,data(){ return { tabIndex:1style scoped .main{ background: #fff; padding-top 30px; } .course-info width 1200px margin 0 auto overflow hidden .wrap-left float left 690px height 388px background-color #000 .wrap-right position relative .course-name font-size 20px color #333 padding 10px 23px letter-spacing .45px .data padding-left 23px padding-right padding-bottom 16px 14px #9b9b9b .sale-time 464px #fa6240 #4a4a4a .sale-type .36px .sale-time .expire right .sale-time .expire .second 24px display inline-block #fafafa #5e5e5e 6px 0 text-align center .course-price 5px 23px .discount 26px margin-left 10px margin-bottom -5px .original text-decoration line-through .buy 0px 23px absolute left 0 bottom .buy .buy-btn .buy .buy-now 125px 40px border #ffc210 border-radius 4px cursor pointer margin-right 15px outline none .buy .free 1px solid #ffc210 .add-cart margin-top .add-cart img 18px 7px vertical-align middle .course-tab width 100% background margin-bottom box-shadow 0 2px 4px 0 #f0f0f0; .course-tab .tab-list margin auto color overflow .tab-list li float margin-right padding 26px 20px 16px font-size 17px cursor .tab-list .active border-bottom 2px solid #ffc210 .tab-list .free #fb7c55 .course-content #FAFAFA padding-bottom .course-tab-list 880px height box-sizing border-box position .tab-item .tab-item-title justify-content space-between 25px 20px 11px border-radius 1px solid #333 border-bottom-color rgba(51,51,.05) .chapter .chapter-length letter-spacing .19px .chapter-title .26px 12px #eee 2px display -ms-flexbox flex -ms-flex-align align-items .chapter-title img vertical-align .lesson-list0 20px .lesson-list .lesson-item 15px 20px 15px 36px .lesson-item .name #666 .lesson-item .index 5px .lesson-item .free 100px 1px 9px margin-left .lesson-item .time .23px opacity 1 transition all .15s ease-in-out .lesson-item .time img text-bottom .lesson-item .try 86px 28px right top all .2s ease-in-out outline border .lesson-item:hover #fcf7ef 0 0 0 0 #f3f3f3 .lesson-item:hover .name .lesson-item:hover .try .course-side 300px .teacher-info .side-title font-weight normal 18px 14px .side-title span border-left padding-left .teacher-content 30px 20px .teacher-content .cont1 .teacher-content .cont1 img 54px .teacher-content .cont1 .name .teacher-content .cont1 .teacher-name 188px .teacher-content .cont1 .teacher-title 13px white-space nowrap .teacher-content .narrative line-height} style> 2.index.js注册组件import Detail from "@/components/Detail" { path:'/course/detail/:id',// 前端页面动态路由匹配 component:Detail } :id ===> this.$route.params.id // course/detail/1 3.course.vue实现:在课程列表页面点击不同的课程可以进入到不同的课程详情页面 router-link :to="'/course/detail/'+course.id+'/'">django基础知识router-link> ="/static/img/avatar1.svg">5000人已加入学习> 此时 点击可进入课程详情页面 2.视频播放组件1.安装npm install vue-video-player --save 2.main.js注册组件main.js require('video.js/dist/video-js.css'); require('vue-video-player/src/custom-theme.css'); import VideoPlayer from 'vue-video-player' Vue.use(VideoPlayer); 3.Detail.vue引入HTML部分html > videoPlayer ="video-player vjs-custom-skin" ref="videoPlayer" :playsinline="true" :options="playerOptions" @play="onPlayerPlay($event)" @pause="onPlayerPause($event)"> videoPlayer> JS部分js import {VideoPlayer} from 'vue-video-player' data(){ return{ ... playerOptions: { playbackRates: [0.7,1.0,1.5,2.0],1)"> 播放速度 autoplay: false,1)"> 如果true,则自动播放 muted: 默认情况下将会消除任何音频。 loop: 循环播放 preload: 'auto',1)"> 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) language: 'zh-CN' 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") fluid: true,1)"> 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 sources: [{ 播放资源和资源格式 type: "video/mp4" 你的视频地址(必填) }],poster: "",1)"> 视频封面图 width: document.documentElement.clientWidth,1)"> 默认视频全屏时的最大宽度 notSupportedMessage: '此视频暂无法播放,请稍后再试',1)"> 允许覆盖Video.js无法播放媒体源时显示的默认信息。 } } } method:{ ... 视频播放时触发此函数 onPlayerPlay:{ ... } 视频暂停时触发此函数 onPlayerPause:{ ... } } components:{ ... videoplayer 挂载一下视频播放组件 } 4.在Xadmin上传视频
所以需要在course表中添加一个course_video字段 # 将上传的视频保存在本地的video文件夹中
course_video = models.FileField(upload_to='video',verbose_name=封面视频
在xadmin上传视频,即可在前端页面看到自己上传的视频 3.课程详情页面后端接口实现
urlpatterns = [ ...... re_path(rdetail/(?P<pk>d+)/'CourseDetailView(RetrieveAPIView): queryset = models.Course.objects.filter(is_deleted=False,is_show=True) serializer_class = CourseDetailModelSerializer models.pyCourse(BaseModel): ...... level_choices = ( (0,初级),(1,中级高级"难度等级") ...... def level_name(self): '''level字段默认显示的是数字,通过返回get_字段_display可以返回数字对应的名字''' return self.get_level_display() serializers.pyCourseDetailModelSerializer(serializers.ModelSerializer): 序列化器嵌套 teacher = TeacherModelSerializer() 将外键关联的属性指定为关联表的序列化器对象,就能拿到关联表序列化出来的所有数据,还需要在fields中指定一下,注意,名称必须和外键属性名称相同 Meta: model = models.Course fields = [id",1)">namecourse_imgstudentslessonspub_lessonspriceteacherlevel_namecourse_video] 后端接口测试drf后端接口测试 /course/detail/1 可得到course=1所需要的所有数据 ? 4.课程详情页面-前端1.注意点
现在我们是需要所有章节和所有课时信息、老师信息和课程信息。 如果将所有的信息都定义到一个序列化器的字段中,数据量有些太大。 我们可以利用axios可以发送异步请求的这个特点,分成两次请求来获取数据 将章节信息和课时信息放在一个序列化器中 其它的放在另一个序列化器中 我们先去请求除了章节信息和课时信息的其他信息 现在后端数据已经准备好了,接下来就是前端发送axios请求获取数据了 2.Detail.vue--> >{{ course_data.name }}>{{course_data.students}}人在学 课程总时长:{{course_data.lessons}} 难度:{{course_data.level_name}}>¥{{course_data.price}}> 老师部分 >{{course_data.teacher.name}}>{{course_data.teacher.title}}>{{course_data.teacher.signature}} 视频播放 ref :playsinline :options @play @pause> js <script> export default { name: "Detail" { ...... course_id:0 播放资源和资源格式 type: "video/mp4" }],1)"> 视频封面图 ...... },created(){ this.get_course_id(); .get_course_data(); },methods: { 获取课程id,用处是请求不同的课程详情页面数据时带上不同的url参数来请求不同的课程详情数据 get_course_id(){ this.course_id = .$route.params.id; 可以判断course_id的合法性 todo },get_course_data(){ this.$axios.get(`${this.$settings.Host}/course/detail/${this.course_id}/`) .then((res)=>{ this.course_data = res.data; 获取课程详情页数据 this.playerOptions.sources[0].src = res.data.course_video; 获取视频数据 this.playerOptions.poster = res.data.course_img 获取视频封面数据 }) },},} </script> 5.CKEditor富文本编辑器1.安装pip install django-ckeditor 2.settings/dev.py INSTALLAPP配置INSTALLED_APPS = [ ... ckeditor 富文本编辑器 ckeditor_uploader 富文本编辑器上传图片模块 ... ] 3.setting/dev.py 配置富文本编辑器ckeditor配置 CKEDITOR_CONFIGS = { default: { toolbar': full 工具条功能,full表示全部,Basic表示基本功能,功能少很多,还有个Custom自定义功能选项 height': 300,1)"> 编辑器高度 'width': 300,# 编辑器宽 },} CKEDITOR_UPLOAD_PATH = '' 上传图片保存路径,留空则调用django的文件上传功能 也可以自定义配置 CKEDITOR_CONFIGS =Customtoolbar_Custom: [ [BoldItalicUnderlineImage'],1)"> 通过浏览器f12来查看每个功能的标签,就看到了类值cke_button_工具名称[注意使用驼峰式来写] [NumberedListBulletedList-OutdentIndentJustifyLeftJustifyCenterJustifyRightJustifyBlock],[LinkUnlinkRemoveFormatSource] ] } } 4.在总路由lyapi/urls.py添加路由path(r^ckeditor/ckeditor_uploader.urls')), 5.将brief字段升级course/models.py from ckeditor_uploader.fields import RichTextUploadingField Course(models.Model): 课程概述变为富文本编辑器显示 brief = RichTextUploadingField(max_length=2048,1)">课程概述True) 6.brief图片路径转化问题
在brief中,存放的都是一些各种标签组成的字符串,而用户在使用富文本编辑器时,有可能会使用上传图片的功能。而图片上传后,默认都存在了后端的media文件夹中。但是前端并不会将我们的后端地址识别出来。它会默认被存放到前端:www.lycity.com/media中,所以需要我们手动更改一下上传图片存储的路径。这样用户上传的图片才能显示出来。 models.py# course/models.py Course: ... new_brief(self): data = self.brief server_addr = contains.SERVER_ADDR data = data.replace(/media{server_addr}/media) data settings/constants.pySERVER_ADDR = http://www.lyapi.com:8001' serializers.pyCourseDetailModelSerializer: ... teacher = TeacherModelSerializer() model.Course fields = [.........,teacher,level_name,new_brief] 将new_brief添加到字段中 7.表情和图片应用不同的CSS样式contains.SERVER_ADDR 做了两件事: 1.将用户上传图片的相对路径改成了绝对路径 2.让图片和表情应用不同的CSS样式 ''' data = data.replace(src="/mediaclass="img_xx" src="{server_addr}/mediareturn data 6.课程章节和课时显示-后端接口urls.pyre_path(rchapter/from django_filters.rest_framework DjangoFilterBackend ChapterView(ListAPIView): queryset = models.CourseChapter.objects.filter(is_deleted=False,1)">True) serializer_class= CourseChapterModelSerializer filter_backends = [DjangoFilterBackend,] filter_fields = (courseCourseLessonModelSerializer: models.CourseLesson fields = [section_linkdurationfree_traillesson] CourseChapterModelSerialzer: 在一的序列化器嵌套多的序列化器,切记要加参数many=True coursesection = CourseLessonModelSerializer(many=True) 1201 models.CourseChapter fields = [chapter] drf测试接口:course/chapter/?course=1 7.课程章节和课时显示-前端>共{{chapter_data.length}}章 ="chapter-item" v-for="(chapter,chapterindex) in chapter_data">第{{chapter.chapter}}章·{{chapter.name}}="lesson-item"="(lesson,lesson_index) in chapter.coursesections">{{chapter.chapter}}-{{lesson.lesson}}> 课程介绍-{{lesson.name}}v-show="lesson.free_trail" class>{{lesson.duration}} ="try"="lesson.free_trail" v-else>立即buy> > js export { chapter_data:{},} },created(){ .get_chapter_data(); },methods: { get_chapter_data(){ this.$settings.Host}/course/chapter/`,{ params:{ course:.course_id,} }).then((res)=>{ console.log(res.data); this.chapter_data = res.data }) },} ? (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |