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. 删除64位ODBC数据源DNS

    1.按照打开管理工具-打开数据源(ODBC),进入如下界面,选择用户DSN删除,发现报错一直删除不了. 2.成功删除:进入如下图路径,打开ODBC数据源管理工具,选择要删除的DSN就可以成功删除啦.

  2. python基础之 基本数据类型,str方法和for循环

    1.概念 1.十进制转二进制,对2取余,余数倒序排列 2.字符串为空的时候,bool值为false,字符串非空就是True3.字符串转化成int时,必须是只包含数字才能转化.4.字符串转化成int时可 ...

  3. 七、UIViewController导航栏

    概述 上一节我们算是跟UIViewController打了个招呼,同时也表示我们正式介入iOS开发.本节我们将介绍UI界面的一个常用元素:导航栏. iOS为UIViewController提供了内置导 ...

  4. Servlet Analysis

    @WebServlet("/cdiservlet") //url映射 public class NewServlet extends HttpServlet { private M ...

  5. Mysql模糊查询Like传递参数的语句

    set @keyWord='我的': select * from tblcontent where content like CONCAT('%',@keyWord,'%')

  6. 为VisualStudio2017添加bits/stdc++.h

    在算法编程中经常有人只写一个头文件"bits/stdc++.h" 其实这个是很多头文件的集合,写了它后相当于包含了所有常用的C++头文件,可是需要注意的是并不是所有的OJ系统都支持 ...

  7. Python 两个星号(**)的 参数

    将参数以字典的形式导入

  8. Bamboo基础概念

    1.project     1)提供报告.展板.连接   |——2.plan       1)指定默认代码仓库(同一个仓库)       2)构建触发条件的配置       3)构建结果的发送与通知 ...

  9. c# 结构的使用

    类的定义的是引用类型,重点在堆上创建,有的时候类只包含极少的数据,因为管理堆而造成的开销是很大的.这时候更好的做法就是将类型定义成结构.结构是值类型,在栈上存储,能有效的减小内存管理的开销.c#基元类 ...

  10. LeetCode169 求众数

    题目链接:https://leetcode-cn.com/problems/majority-element/ 给定一个大小为 n 的数组,找到其中的众数.众数是指在数组中出现次数大于 ⌊ n/2 ⌋ ...