Luffy之课程详情页

提前写好课程详情的template,并放入到Vue中

注册路由

import CourseDetail from "../components/CourseDetail"

    {
name:"CourseDetail",
path: "/detail",
component: CourseDetail,
}

在页面中引入vue-video组件实现视频播放

# 1. 安装依赖
npm install vue-video-player --save # 2. 在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);

在课程详情页中的script标签里面加入以下代码:

import {videoPlayer} from 'vue-video-player';

export default {
data () {
return {
playerOptions: {
playbackRates: [0.7, 1.0, 1.5, 2.0], // 播放速度
autoplay: false, //如果true,则自动播放
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 循环播放
preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [{
type: "video/mp4",
src: "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4" //你的视频地址(必填)
}],
poster: "../static/courses/675076.jpeg", //视频封面图
width: document.documentElement.clientWidth,
notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。
}
}
},
components: {
videoPlayer
},
methods: {
onPlayerPlay(player) {
alert("play");
},
onPlayerPause(player){
alert("pause");
},
},
computed: {
player() {
return this.$refs.videoPlayer.player
}
}
}

最后前端向后端发送数据请求就可以了,后端提供数据接口

完整代码:

前端(coursedetail),包括倒计时等的js实现

 <template>
<div class="detail">
<Header></Header>
<div class="warp">
<div class="course-info">
<div class="warp-left" style="width: 690px;height: 388px;background-color: #000;">
<video-player class="video-player vjs-custom-skin"
ref="videoPlayer"
:playsinline="true"
:options="playerOptions"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
>
</video-player>
</div>
<div class="warp-right">
<h3 class="course-title">{{course.name}}</h3>
<p class="course-data">{{course.students}}人在学&nbsp;&nbsp;&nbsp;&nbsp;课程总时长:{{course.lessons}}课时/{{course.pub_lessons}}小时&nbsp;&nbsp;&nbsp;&nbsp;难度:{{course.level_name}}</p>
<div class="preferential">
<p class="price-service">{{course.price_service_type.name}}</p>
<p class="timer">距离结束:仅剩 {{Math.floor(sale_time/86400)}}天 {{Math.floor(sale_time%86400/3600)}}小时 {{Math.floor(sale_time%3600/60)}}分 <span>{{Math.floor(sale_time%60)}}</span> 秒</p>
</div>
<p class="course-price">
<span>活动价</span>
<span class="real-price">¥{{course.real_price}}</span>
<span class="old-price">¥{{course.price}}</span>
</p>
<div class="buy-course">
<p class="buy-btn">
<span class="btn1">立即购买</span>
<span class="btn2">免费试学</span>
</p>
<p class="add-cart">
<img src="../../static/images/cart.svg" alt="">加入购物车
</p>
</div>
</div>
</div>
<div class="course-tab">
<ul>
<li class="active">详情介绍</li>
<li>课程章节 <span>(试学)</span></li>
<li>用户评论 (83)</li>
<li>常见问题</li>
</ul>
</div>
<div class="course-section">
<section class="course-section-left">
<img src="../../static/images/21天01_1547098127.6672518.jpeg" alt="">
</section> </div>
</div>
<Footer></Footer>
</div>
</template> <script>
import Header from "./common/Header"
import Footer from "./common/Footer" import {videoPlayer} from 'vue-video-player'; export default {
name: 'CourseDetail',
data(){
return {
course_id:sessionStorage.course_id,
course:{},
sale_time: 0,
// vue-video的配置选项
playerOptions: {
playbackRates: [0.7, 1.0, 1.5, 2.0], // 播放速度
autoplay: false, //如果true,则打开页面以后自动播放
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 循环播放
preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [{ // 播放资源类型和地址
type: "video/mp4",
src: "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4" //你的视频地址(必填)
}],
poster: "../static/courses/675076.jpeg", //视频封面图
width: document.documentElement.clientWidth,
notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。
}
}
},
components:{
Header,
Footer,
videoPlayer, // 引入vue-viedeo播放器组件
},
methods:{
intervaltimer(){
// 课程优惠倒计时
if(this.sale_time > 0 ){
let timer = setInterval(()=>{
if( this.sale_time < 0 ){
clearInterval(timer)
}else{
--this.sale_time;
}
},1000);
}
}
},
computed: {
player() {
return this.$refs.videoPlayer.player
}
},
created(){
this.$axios.get("http://127.0.0.1:8000/courses/"+this.course_id).
then(response=>{
this.course = response.data;
// this.playerOptions.poster = response.data.course_img;
this.sale_time = response.data.price_service_type.priceservices[0].endtime_stamp
this.intervaltimer()
}).catch(error=>{
console.log(error.response);
})
},
};
</script> <style scoped>
.detail{
margin-top: 80px;
}
.course-info{
padding-top: 30px;
width:1200px;
height: 388px;
margin: auto;
}
.warp-left,.warp-right{
float: left;
}
.warp-right{
height: 388px;
position: relative;
}
.course-title{
font-size: 20px;
color: #333;
padding: 10px 23px;
letter-spacing: .45px;
font-weight: normal;
}
.course-data{
padding-left: 23px;
padding-right: 23px;
padding-bottom: 16px;
font-size: 14px;
color: #9b9b9b;
}
.preferential{
width: 100%;
height: auto;
background: #fa6240;
font-size: 14px;
color: #4a4a4a;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
padding: 10px 23px;
}
.price-service{
font-size: 16px;
color: #fff;
letter-spacing: .36px;
}
.timer{
font-size: 14px;
color: #fff;
}
.course-price{
width: 100%;
background: #fff;
height: auto;
font-size: 14px;
color: #4a4a4a;
display: -ms-flexbox;
display: flex;
-ms-flex-align: end;
align-items: flex-end;
padding: 5px 23px;
}
.real-price{
font-size: 26px;
color: #fa6240;
margin-left: 10px;
display: inline-block;
margin-bottom: -5px;
}
.old-price{
font-size: 14px;
color: #9b9b9b;
margin-left: 10px;
text-decoration: line-through;
}
.buy-course{
position: absolute;
left: 0;
bottom: 20px;
width: 100%;
height: auto;
-ms-flex-pack: justify;
justify-content: space-between;
padding-left: 23px;
padding-right: 23px;
}
.buy-btn{
float: left;
}
.buy-btn .btn1{
display: inline-block;
width: 125px;
height: 40px;
background: #ffc210;
border-radius: 4px;
color: #fff;
cursor: pointer;
margin-right: 15px;
text-align: center;
vertical-align: middle;
line-height: 40px;
}
.buy-btn .btn2{
width: 125px;
height: 40px;
border-radius: 4px;
cursor: pointer;
margin-right: 15px;
display: inline-block;
background: #fff;
color: #ffc210;
border: 1px solid #ffc210;
text-align: center;
vertical-align: middle;
line-height: 40px;
}
.add-cart{
font-size: 14px;
color: #ffc210;
text-align: center;
cursor: pointer;
float: right;
margin-top: 10px;
}
.add-cart img{
width: 20px;
height: auto;
margin-right: 7px;
}
.course-tab{
width: 1200px;
margin: auto;
height: auto;
background: #fff;
margin-bottom: 30px;
box-shadow: 0 2px 4px 0 #f0f0f0;
}
.course-tab>ul{
padding: 0;
margin: 0;
list-style: none;
width: 1200px;
height: auto;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
color: #4a4a4a;
}
.course-tab>ul>li{
margin-right: 15px;
padding: 26px 20px 16px;
font-size: 17px;
cursor: pointer;
}
.course-tab>ul>.active{
color: #ffc210;
border-bottom: 2px solid #ffc210;
}
.course-section{
background: #FAFAFA;
overflow: hidden;
padding-bottom: 40px;
width: 1200px;
height: auto;
margin: 0 auto;
}
.course-section-left{
width: 880px;
height: auto;
padding: 20px;
background: #fff;
float: left;
box-sizing: border-box;
overflow: hidden;
position: relative;
box-shadow: 0 2px 4px 0 #f0f0f0;
}
</style>

后端数据接口 views:

class CourseDetailAPIView(RetrieveAPIView):
queryset = models.Course.objects.all()
serializer_class = CourseDetailSerializer

serlizer,序列化器:

 from rest_framework import serializers

 from luffy.apps.courses import models

 class CourseCateserializer(serializers.ModelSerializer):
class Meta:
model = models.CourseCategory
fields = ('id','name','orders') class CourseChaptersSerializer(serializers.ModelSerializer):
class Meta:
model= models.CourseChapter
fields = ("id","chapter","name") class Teacherserializer(serializers.ModelSerializer):
class Meta:
model = models.Teacher
fields = ("id","name","title",) class PriceServiceserializer(serializers.ModelSerializer):
class Meta:
model = models.PriceService
fields = ("condition","sale","start_time","end_time","endtime_stamp") class PriceServiceTypeserializer(serializers.ModelSerializer):
priceservices = PriceServiceserializer(many=True)
class Meta:
model = models.PriceServiceType
fields =("id","name","priceservices") class Courseserializer(serializers.ModelSerializer):
teacher = Teacherserializer()
price_service_type = PriceServiceTypeserializer()
class Meta:
model = models.Course
fields = ("id","name","course_img","students","lessons","pub_lessons","price","teacher","course_category","price_service_type") class CourseDetailSerializer(serializers.ModelSerializer):
coursechapters = CourseChaptersSerializer(many=True)
price_service_type = PriceServiceTypeserializer()
teacher = Teacherserializer()
class Meta:
model = models.Course
fields = (
"id", "name", "course_img", "students",
"lessons", "brief", "level_name", "pub_lessons","price",
"teacher", "real_price", "price_service_type","coursechapters",)

Luffy之课程详情页的更多相关文章

  1. mxonline实战11,课程详情页2,课程章节页

    对应github地址:第11天   一. 课程详情页2   1. 课程详情页第2块中的课程介绍中,修改course-detail.html中代码,搜索课程详情,找到如下代码

  2. mxonline实战10,课程列表页,课程详情页1

    对应github地址:第10天   一. 课程列表页   1. 拷贝course-list.html到templates目录中 2. 编写url和view 在courses/views.py中新加

  3. 20、Django实战第20天:课程详情页

    1.把course-detail.html复制到templates目录下 2.编辑course-detail.html,分析页面,继承base.html 3.编辑courses.views .... ...

  4. day82:luffy:课程详情页面显示&章节和课时显示&视频播放组件&CKEditor富文本编辑器

    目录 1.初始课程详情页面 2.视频播放组件 3.课程详情页面后端接口实现 4.课程详情页面-前端 5.CKEditor富文本编辑器 6.课程章节和课时显示-后端接口 7.课程章节和课时显示-前端 1 ...

  5. mxonline实战12, 课程评论,相关课程推荐,课程视频页

    对应github地址:第12天   一. 课程评论   1. 创建URL, VIEW courses/views.py -> Course

  6. java亿级流量电商详情页系统的大型高并发与高可用缓存架构实战视频教程

    亿级流量电商详情页系统的大型高并发与高可用缓存架构实战 完整高清含源码,需要课程的联系QQ:2608609000 1[免费观看]课程介绍以及高并发高可用复杂系统中的缓存架构有哪些东西2[免费观看]基于 ...

  7. mxonline实战13,授课讲师列表页,详情页,index页面全局导航

    对应github地址:第13天   把teacher-list.html和teacher-detail.html拷贝过来   一. 授课讲师列表页   1. 修改html文件 把org-list.ht ...

  8. 25、Django实战第25天:讲师详情页

    1.复制teacher-detail.html到templates目录下 2.编辑teacher-detail.html,继承base.html 3.编辑organization.view.py cl ...

  9. 【音乐App】—— Vue-music 项目学习笔记:歌手详情页开发

    前言:以下内容均为学习慕课网高级实战课程的实践爬坑笔记. 项目github地址:https://github.com/66Web/ljq_vue_music,欢迎Star. 歌曲列表 歌曲播放 一.子 ...

随机推荐

  1. 20165317Java实验五 网络编程与安全

    实验五 网络编程与安全 一.中缀转后缀 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA 结对实现中缀表达式转后缀表达式的功能 MyBC.ja ...

  2. android AsyncTask异步任务(笔记)

    AsyncTask是一个专门用来处理后台进程与UI线程的工具.通过AsyncTask,我们可以非常方便的进行后台线程和UI线程之间的交流. 那么AsyncTask是如何工作的哪. AsyncTask拥 ...

  3. 代码中特殊的注释技术——TODO、FIXME和XXX的用处(转)

    1.声明 本篇转自博客:http://blog.csdn.net/reille/ 2.转载内容 2.1.前言 今天在阅读 Qt Creator 的源代码时,发现一些注释中有 FIXME 英文单词,用英 ...

  4. 一年工作经验的大专生程序员(java后台)

    1.文章前言     作为18应届毕业大专生已工作一年,相信这也是大部分同届生的现状.       那么,一个萌新进入职场一年都经历了什么呢?在校那会我是挺好奇的.       这篇文章是根据自己一年 ...

  5. 原生js阻止表单跳转

    /* W3C浏览器下的 */ var forms = document.getElementById("from") forms.addEventListener('submit' ...

  6. Python基础(七) python自带的三个装饰器

    说到装饰器,就不得不说python自带的三个装饰器: 1.@property   将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @prope ...

  7. Thinkphp----------Thinkphp3.2的目录结构介绍

    ThinkPHP框架目录结构\index.php      入口文件\Application    应用目录\Public            资源文件目录\ThinkPHP      框架核心目录 ...

  8. CentOS 7 使用SVN+Apache搭建版本控制服务器

    svn简介 Subversion是一个免费/开源的版本控制系统, Subversion 可以跨越时间地对文件和目录, 以及它们的修改进行管理. 这就允许你恢复 数据的旧版本, 或检查数据的修改历史. ...

  9. K-wolf Number (数位DP)

    题意:求区间内有多少个数满足条件:任意相邻的k个数位都不相等. 思路:老套路 #include<bits/stdc++.h> using namespace std; typedef lo ...

  10. 初入MEF-IOC导入导出

    DDD,领域驱动开发,听起来高端大气,这本书买回来翻了几下,实在是晦涩难懂