在线课堂
一、功能需求分析
1. 分析
在线直播,或点播设计到视频的存储,转码,加密,播放很多细节,个人单独开发不现实。本项目的在线课堂选择在线播放视频的形式。实际项目中一般选择云点播或者内嵌视频网站的方式进行。本项目选择是用百度云VOD点播来实现。
2. 功能
视频展示页面
视频播放详情
二、模型设计
1. 表字段分析
-
老师表
姓名
职称
简介
头像
-
课程分类表
- 名称
-
课程表
课程名称
封面
视频地址
时长
简介
大纲
老师
分类
2.模型定义
在course/models.py中定义如下模型
from django.db import models
from utils.models import BaseModel
class Teacher(BaseModel):
name = models.CharField('讲师姓名', max_length=150, help_text='讲师姓名')
title = models.CharField('职称', max_length=150, help_text='职称')
profile = models.TextField('简介', help_text='简介')
photo = models.URLField('头像url', default='', help_text='头像url')
class Meta:
db_table = 'tb_teachers'
verbose_name = '讲师'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class CourseCategory(BaseModel):
name = models.CharField('课程分类名', max_length=100, help_text='课程分类名')
class Meta:
db_table = 'tb_course_category'
verbose_name = '课程分类'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Course(BaseModel):
title = models.CharField('课程名', max_length=150, help_text='课程名')
cover_url = models.URLField('封面url', help_text='封面url')
video_url = models.URLField('课程视频url', help_text='课程视频url')
duration = models.DurationField('课程时长', help_text='课程时长')
profile = models.TextField('课程简介', null=True, blank=True, help_text='课程简介')
outline = models.TextField('课程大纲', null=True, blank=True, help_text='课程大纲')
teacher = models.ForeignKey('Teacher', on_delete=models.SET_NULL, null=True, blank=True)
category = models.ForeignKey('CourseCategory', on_delete=models.SET_NULL, null=True, blank=True)
class Meta:
db_table = 'tb_course'
verbose_name = '课程'
verbose_name_plural = verbose_name
def __str__(self):
return self.title
三、百度云VOD音视频点播
1. 开通百度VOD音频点播功能
- 注册后登陆首页,找到vod服务
开通即送55元包
2.添加媒资
3.导入测试数据
# 在mysql数据库中添加自己的测试数据
INSERT INTO `tb_teachers`
(create_time, update_time, is_delete, name, title,`profile`,photo)
VALUES
( '2019-07-17 14:26:05.000000', '2019-07-17 14:26:09.000000', '0', '心蓝', 'python高级讲师', 'python学院最帅的老师', '/media/xinlan.jpg');
# 导入课程分类数据
INSERT INTO `tb_course_category` VALUES ('1', '2019-07-17 14:34:33.000000', '2019-07-17 14:34:36.000000', '0', 'python基础');
INSERT INTO `tb_course_category` VALUES ('2', '2019-07-17 14:34:52.000000', '2019-07-17 14:34:55.000000', '0', 'python高级');
INSERT INTO `tb_course_category` VALUES ('3', '2019-07-17 14:35:20.000000', '2019-07-17 14:35:16.000000', '0', 'python框架');
# 导入课程数据
insert into tb_course (title, cover_url, video_url, duration, `profile`, outline, teacher_id, category_id, create_time, update_time, is_delete) values
('你的测试视频1名称', '你的测试视频缩略图URL', '你的测试视频URL', 212000000, '你的测试视频简介', '你的视频大纲', 1, 2, now(), now(), 0),
('你的测试视频2名称', '你的测试视频缩略图URL', '你的测试视频URL', '你的测试视频时长整数表示微秒', '你的测试视频简介', '你的视频大纲', 1, 2, now(), now(), 0);
四、视频展示列表
1.接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /course/ |
参数格式 | 无参数 |
-
返回结果:
文档下载页面
2.后端代码
- 视图
# 在course/views.py文件下创建如下视图
from django.shortcuts import render, Http404
from django.views import View
from . import models
def course_list(request):
"""
在线课程列表
url:/course/
:param request:
:return:
"""
courses = models.Course.objects.only('title', 'cover_url', 'teacher__title', 'teacher__name').filter(
is_delete=False).select_related(
'teacher')
return render(request, 'course/course.html', context={'courses': courses})
- 路由
# 在course/urs.py中定义如下路由
from django.urls import path
from . import views
app_name = 'course'
urlpatterns = [
path('', views.course_list, name='index'),
]
3.前端代码
- html
<!-- 创建模板templates/course/course.html -->
{% extends 'base/base.html' %}
{% load static %}
{% block title %}在线课堂{% endblock %}
{% block link %}
<link rel="stylesheet" href="{% static 'css/course/course.css' %}">
{% endblock %}
{% block main_start %}
<main id="course-container">
<div class="w1200">
<ul class="course-list">
{% for course in courses %}
<li class="course-item">
<a href="{% url 'course:course_detail' course.id %}">
<img class="course-img" src="{{ course.cover_url }}"
alt="{{ course.title }}">
<div class="course-content">
<p class="course-info">{{ course.title }}</p>
<p class="course-author">{{ course.teacher.name}}({{ course.teacher.title }})</p>
<p class="course-price free">免费</p>
</div>
</a>
</li>
{% endfor %}
</ul>
</div>
</main>
{% endblock %}
2. css
/* ==== 修改course.css如下 ====*/
/* ========== top-wrap start ========= */
#top-wrap {
background: #fff;
line-height: 60px;
box-shadow: 0 4px 4px rgba(0,0,0,.1);
}
#top-wrap .top-title {
float: left;
font-size: 22px;
margin-right: 140px;
}
#top-wrap .top-nav {
display: flex;
justify-content: space-between;
width: 400px;
color: #878787;
font-size: 18px;
}
#top-wrap .top-nav li.active {
color: #212121;
}
#top-wrap .top-nav li:hover {
text-shadow: 1px 1px 2px #212121;
}
/* ========== top-wrap end ========= */
/* ========== course-container start ========= */
#course-container {
flex: 1;
}
#course-container .course-list {
display: flex;
flex-flow: row wrap;
margin-bottom: 20px;
}
#course-container .course-list .course-item {
margin: 20px;
width: 260px;
height: 260px;
background: #fff;
position: relative;
/* float: left; */
}
.course-list .course-item .course-img {
width: 100%;
height: 61.8%;
}
.course-list .course-item .course-content {
padding: 0 20px;
height: 150px;
box-sizing: border-box;
}
.course-list .course-item:hover {
box-shadow: 0 4px 8px rgba(0,0,0,.1);
}
.course-item .course-content .course-info {
font-size: 16px;
line-height: 1.5;
max-height: 50px;
overflow: hidden;
}
.course-item .course-content .course-author{
color: #848383;
font-size: 14px;
line-height: 36px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.course-item .course-content .course-price {
position: absolute;
bottom: 10px;
right: 21px;
font-size: 16px;
line-height: 30px;
color: #6fa026;
}
.course-item .course-content .course-price span{
font-size: 14px;
}
.course-item .course-content .course-price.free {
color: green;
float: right;
}
/* ========== course-container end ========= */
五、视频播放详情页面
1.接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /course/<int:course_id>/ |
参数格式 | 路径参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
course_id | 整数 | 是 | 视频id |
-
返回结果:
视频播放详情页面
2. 后端代码
- 视图
# 在course/views.py文件下创建如下视图
class CourseDetailView(View):
"""
课程详情视图
url:/course/<int:course_id>/
"""
def get(self, request, course_id):
course = models.Course.objects.only('title', 'cover_url', 'video_url', 'profile', 'outline', 'teacher__name',
'teacher__photo', 'teacher__title', 'teacher__profile').select_related(
'teacher').filter(is_delete=False, id=course_id).first()
if course:
return render(request, 'course/course_detail.html', context={'course': course})
else:
return Http404('此课程不存在')
- 路由
# 在course/urs.py中添加如下路由
path('<int:course_id>/', views.CourseDetailView.as_view(), name='course_detail')
3.前端代码
- html
<!-- 创建模板templates/course/course_detail.html -->
{% extends 'base/base.html' %}
{% load static %}
{% block title %}视频详情-{{ course.title }}{% endblock %}
{% block link %}
<link rel="stylesheet" href="{% static 'css/course/course-detail.css' %}">
{% endblock %}
{% block main_start %}
<main id="main">
<div class="w1200">
<div class="course-contain">
<div class="course-top-contain">
<h4 class="course-title">{{ course.title }}</h4>
<div class="course-other clearfix">
<div class="share">
<i></i>
<span>分享</span>
<div class="share-list">
<div class="bshare-custom icon-medium"><div class="bsPromo bsPromo2"></div><a title="分享到QQ空间" class="bshare-qzone"></a><a title="分享到新浪微博" class="bshare-sinaminiblog"></a><a title="分享到QQ好友" class="bshare-qqim" href="javascript:void(0);"></a><a title="分享到微信" class="bshare-weixin" href="javascript:void(0);"></a><a title="更多平台" class="bshare-more bshare-more-icon more-style-addthis"></a><span class="BSHARE_COUNT bshare-share-count" style="float: none;">62.9K</span></div><script type="text/javascript" charset="utf-8" src="http://static.bshare.cn/b/buttonLite.js#style=-1&uuid=&pophcol=2&lang=zh"></script><script type="text/javascript" charset="utf-8" src="http://static.bshare.cn/b/bshareC0.js"></script>
</div>
</div>
<div class="buy-list">
<span class="price">免费</span>
</div>
</div>
</div>
<div class="course-video" id="course-video">
<span class="course-data" style="display: none" data-video-url="{{ course.video_url }}"
data-cover-url="{{ course.cover_url }}"></span>
</div>
<div class="course-bottom-contain">
<div class="course-detail-list">
<div class="course-item clearfix">
<h5 class="course-title">{{ course.teacher.name }}</h5>
<div class="teacher-box clearfix">
<img src="{{ course.teacher.photo }}" alt="{{ course.teacher.name }}"
title="{{ course.teacher.name }}" class="teacher-avatar">
<div class="teacher-info">
<p class="teacher-name">{{ course.teacher.name }}</p>
<p class="teacher-identify"> {{ course.teacher.title }}</p>
</div>
</div>
<div class="item-content">
{{ course.teacher.profile }}
</div>
</div>
<div class="course-item clearfix">
<h5 class="course-title">课程简介</h5>
<div class="item-content">
{{ course.profile }}
</div>
</div>
<div class="course-item clearfix">
<h5 class="course-title">课程大纲</h5>
<div class="item-content">
<p>{{ course.outline }}</p>
</div>
</div>
<div class="course-item clearfix">
<h5 class="course-title">帮助中心</h5>
<div class="item-content">
<p>1. 购买后的课程在线可反复观看学习,视频有效期以具体课程信息为准。 </p>
<p>2. 课程暂不支持下载观看,均为在线观看视频。 </p>
<p>3. 课程一经购买,不可转让、不可退款;仅限购买账号观看。</p>
<p>4. 如有问题请咨询客服: 400-1567-315 </p>
</div>
</div>
<div class="course-item clearfix">
<h5 class="course-title">关于潭州课堂</h5>
<div class="item-content">
湖南潭州教育网络科技有限公司拥有千余人的优秀师资团队,是一家师资丰富、教育产品类别众多的在线培训公司。公司总部座落于美丽的星城长沙,2015年9月正式入驻芯城科技园目前拥有近两万平米办公面积。
在潭州学习的学员已突破1000万人次在线学员覆盖全球,包括中国、加拿大、日本、美国、韩国等诸多国家。
</div>
</div>
</div>
<div class="course-side">
<h4 class="side-title">推荐课程</h4>
</div>
</div>
</div>
</div>
</main>
{% endblock %}
{% block script %}
<script src="https://cdn.bdstatic.com/jwplayer/latest/cyberplayer.js"></script>
<script src="{% static 'js/course/course_detail.js' %}"></script>
{% endblock %}
- js
// 创建js文件 static/js/course/course.js
$(function () {
let $course_data = $(".course-data");
let sVideoUrl = $course_data.data('video-url');
let sCoverUrl = $course_data.data('cover-url');
let player = cyberplayer("course-video").setup({
width: '100%',
height: 650,
file: sVideoUrl,
image: sCoverUrl,
autostart: false,
stretching: "uniform",
repeat: false,
volume: 100,
controls: true,
ak: '你自己的ak'
});
});
3. css
/*== 修改course_detail.css 代码如下===*/
#main {
flex: 1;
}
.course-contain {
width: 100%;
}
.course-contain .course-top-contain, #course-video{
width: 100%;
background: #fff;
/*padding:0 20px;*/
}
.course-top-contain .course-title {
font-size: 30px;
line-height: 2.5;
margin: 0 20px;
}
.course-top-contain .course-other {
line-height: 3.5;
margin: 0 20px;
}
.course-other .share {
float: left;
}
.share span {
margin-right: 8px;
}
.share i {
display: inline-block;
vertical-align: middle;
width: 14px;
height: 14px;
margin-right: 5px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABM0lEQVQokY2Sr0tEQRSFv/dYsIiwYVFkg/deERZBbEaDiJpEBP8Ck6jYxCBuMlgNikGTIIjBIIhbBDGISUSwzIwW4wbtrmHnwWNxf5w099z7MXeYk1SrVbpJ1IrANjAKnAXvbgpdqaYegPF4Xha1hbSH2yQHZVrpCIpaCdj9p/WVxoFhUdsQtcVY94vaHhCAErAD/EboGTgoiNpYLAYiVAMmgU9gLnj3GP0ToAy8Bu8aBWArg6JmgbXg3VF+t+BdHahndQo0WvZvAFed3p6Bh8B3zvsB7kRtKj8oamOiNiFqCUAavHsHKsAmMAMMAtfAvahdiFpZ1NaBN+AFeBK1YtIuOaI2AhwD00AC9OXa+23/MXj3EbybB1ZbIIChrskJ3p0DtRb7stesLtEMQQU4Dd7d/gHKLE8gSrHhJgAAAABJRU5ErkJggg==);
}
.share .share-list{
display: none;
/*width: 130px;*/
position: relative;
background: #fff;
text-align: center;
line-height: 45px;
left: 8px;
/*box-shadow: 0 1px 2px #ccc;*/
/*border: 1px solid #ddd;*/
border-radius: 5px;
z-index: 1;
}
.share:hover .share-list{
display: inline-block;
}
.share-list:after{
content: "";
border: 12px solid transparent;
border-right-color: #eee;
position: absolute;
top: 0px;
left: -25px;
}
.share-list a{
cursor: pointer;
}
.share-list a img{
vertical-align: middle;
}
.course-other .buy-list {
float: right;
}
.price {
color: #f76363;
float: right;
margin-right: 20px;
line-height: 40px;
font-size: 20px;
}
.buy-btn {
background-color: #ff8d3f;
border: none;
width: 120px;
text-align: center;
line-height: 40px;
color: #fff;
border-radius: 5px;
font-size: 16px;
float: right;
}
.course-video {
border-top: 1px solid #ddd;
height: 400px;
}
.course-bottom-contain {
width: 100%;
margin-top: 30px;
}
.curse-bottom-contain {
margin-top: 30px;
width: 100%;
}
.course-detail-list {
width: 800px;
float: left;
}
.course-detail-list .course-item {
margin-bottom: 20px;
background-color: #fff;
padding: 20px;
}
.course-item .course-title{
border-left: 6px solid #5b86db;
padding: 0 10px;
color: #202020;
font-size: 18px;
}
.course-item .teacher-box {
margin-top: 20px;
}
.teacher-box .teacher-avatar {
width: 62px;
height: 62px;
border-radius: 50%;
float: left;
}
.teacher-box .teacher-info {
float: left;
margin-left: 10px;
position: relative;
font-size: 16px;
color: #696969;
height: 62px;
width: 90%;
}
.teacher-name {
position: absolute;
left: 0;
top: 8px;
}
.teacher-identify {
position: absolute;
bottom: 8px;
left: 0;
}
.item-content {
font-size: 14px;
color: #888;
line-height: 2em;
margin-top: 20px;
}
.item-course-title {
font-size: 18px;
color: #202020;
float: left;
line-height: 40px;
}
.item-buy-list {
float: right;
margin-top: 0;
}
.course-side {
float: right;
width: 360px;
background-color: #fff;
padding: 20px;
padding-bottom: 0;
box-sizing: border-box;
}
.course-side .side-title {
font-size: 18px;
line-height: 2.4;
}