03、课程接口

1、课程分类接口

路由

# http://127.0.0.1:8000/api/v1/course/category/  from django.urls import path, include from rest_framework.routers import SimpleRouter from .views import CourseCategoryView router = SimpleRouter() router.register('category', CourseCategoryView, 'category') urlpatterns = [     path('', include(router.urls)), ]
View Code

序列化类

from rest_framework import serializers from .models import CourseCategory  class CourseCategorySerializer(serializers.ModelSerializer):     class Meta:         model = CourseCategory         fields = ['id', 'name']
View Code

视图类

from django.shortcuts import render  # Create your views here. from .models import CourseCategory from .serializer import CourseCategorySerializer from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin class CourseCategoryView(GenericViewSet,ListModelMixin):     queryset = CourseCategory.objects.all().filter(is_delete=False,is_show=True).order_by('orders')     serializer_class =CourseCategorySerializer
View Code

2、课程列表接口

路由

router.register('actual', CourseView, 'actual')

序列化类

class TeacherSerializer(serializers.ModelSerializer):     class Meta:         model = Teacher         fields = ('id', 'name', 'role_name', 'title', 'signature', 'image', 'brief')   class CourseSerializer(serializers.ModelSerializer):     teacher = TeacherSerializer()  # 子序列化,单单条数据,直接子序列化      class Meta:         model = Course         # fields = ['id', 'name']  # 这里要写很多,自定义字段         fields = [             'id',             'name',             'course_img',             'brief',  # 课程介绍--->后面课程详情使用同一个序列化类             'attachment_path',  # 课件             'pub_sections',  # 发布的课时数             'price',  # 价格             'students',  # 学习人数             'period',  # 学习周期             'sections',  # 总课时数              'course_type_name',  # choice字段---》表模型中写             'level_name',  # choice字段---》表模型中写             'status_name',  # choice字段---》表模型中写              'teacher',  # 表模型中写,序列化类中写,子序列化             'section_list', # 表模型中写 -章节--->Course表中没有---》重写:序列类写,表模型中写         ]
View Code

 安装、注册过滤和排序模块

# 安装过滤和排序模块 pip3 install django-filter  # 注册 INSTALLED_APPS = [ # ...     'django_filters', # ... ]

新建分页类 course/pagination

from rest_framework.pagination import PageNumberPagination   class CommonPageNumberPagination(PageNumberPagination):     page_size = 2  # 每页显示条数,默认     page_query_param = 'page'  # 查询条件叫page --> ?page=3     page_size_query_param = 'page_size'  # 每页显示的条数 ?page=3&size=9 查询第三页,第三页显示9条     max_page_size = 10  # 每页最大限度多少条,如果?page=3&size=9,最终还是显示5条
View Code

课程接口视图类(包括分页、过滤、排序)

# 课程列表接口,课程详情接口 class CourseView(GenericViewSet, ListModelMixin, RetrieveModelMixin):     queryset = Course.objects.all().filter(is_delete=False, is_show=True).order_by('orders')     serializer_class = CourseSerializer      # 加入分页---》随着课程越来越多,要分页     pagination_class = CommonPageNumberPagination     # 加入排序     filter_backends = [DjangoFilterBackend, OrderingFilter]     ordering_fields = ['price', 'students']     # 加入过滤---》不是按名字搜索的这种过滤,而是按课程分类过滤--》第三方django-filter     filter_fields = ['course_category']
View Code

3、课程页面前后端调通

ActualCourse

<template>     <div class=course>         <Header></Header>         <div class=main>             <!-- 筛选条件 -->             <div class=condition>                 <ul class=cate-list>                     <li class=title>课程分类:</li>                     <li :class=filter.course_category==0?'this':'' @click=filter.course_category=0>全部</li>                     <li :class=filter.course_category==category.id?'this':'' v-for=category in category_list                         @click=filter.course_category=category.id :key=category.name>{{category.name}}                     </li>                 </ul>                  <div class=ordering>                     <ul>                         <li class=title>筛&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</li>                         <li class=default :class=(filter.ordering=='id' || filter.ordering=='-id')?'this':''                             @click=filter.ordering='-id'>默认                         </li>                         <li class=hot :class=(filter.ordering=='students' || filter.ordering=='-students')?'this':''                             @click=filter.ordering=(filter.ordering=='-students'?'students':'-students')>人气                         </li>                         <li class=price                             :class=filter.ordering=='price'?'price_up this':(filter.ordering=='-price'?'price_down this':'')                             @click=filter.ordering=(filter.ordering=='-price'?'price':'-price')>价格                         </li>                     </ul>                     <p class=condition-result>共{{course_total}}个课程</p>                 </div>              </div>             <!-- 课程列表 -->             <div class=course-list>                 <div class=course-item v-for=course in course_list :key=course.name>                     <div class=course-image>                         <img :src=course.course_img alt=>                     </div>                     <div class=course-info>                         <h3>                             <router-link :to='/actual-detail/:id'+course.id>{{course.name}}</router-link>                             <span><img src=@/assets/img/avatar1.svg alt=>{{course.students}}人已加入学习</span></h3>                         <p class=teather-info>                             {{course.teacher.name}} {{course.teacher.title}} {{course.teacher.signature}}                             <span v-if=course.sections>course.pub_sections>共{{course.sections}}课时/已更新{{course.pub_sections}}课时</span>                             <span v-else>共{{course.sections}}课时/更新完成</span>                         </p>                         <ul class=section-list>                             <li v-for=(section, key) in course.section_list :key=section.name><span                                     class=section-title>0{{key+1}}  |  {{section.name}}</span>                                 <span class=free v-if=section.free_trail>免费</span></li>                         </ul>                         <div class=pay-box>                             <div v-if=course.discount_type>                                 <span class=discount-type>{{course.discount_type}}</span>                                 <span class=discount-price>¥{{course.real_price}}元</span>                                 <span class=original-price>原价:{{course.price}}元</span>                             </div>                             <span v-else class=discount-price>¥{{course.price}}元</span>                             <span class=buy-now>立即购买</span>                         </div>                     </div>                 </div>             </div>             <div class=course_pagination block>                 <el-pagination                         @size-change=handleSizeChange                         @current-change=handleCurrentChange                         :current-page.sync=filter.page                         :page-sizes=[2, 3, 5, 10]                         :page-size=filter.page_size                         layout=sizes, prev, pager, next                         :total=course_total>                 </el-pagination>             </div>         </div>         <Footer></Footer>     </div> </template>  <script>     import Header from @/components/Header     import Footer from @/components/Footer      export default {         name: Course,         data() {             return {                 category_list: [], // 课程分类列表                 course_list: [],   // 课程列表                 course_total: 0,   // 当前课程的总数量                 filter: {                     course_category: 0, // 当前用户选择的课程分类,刚进入页面默认为全部,值为0                     ordering: -id,    // 数据的排序方式,默认值是-id,表示对于id进行降序排列                     page_size: 2,       // 单页数据量                     page: 1,                 }             }         },         created() {             this.get_category(); // 加载课程分类             this.get_course(); // 加载科创城         },         components: {             Header,             Footer,         },         watch: {             filter.course_category: function () {                 this.filter.page = 1;                 this.get_course();             },             filter.ordering: function () {                 this.get_course();             },             filter.page_size: function () {                 this.get_course();             },             filter.page: function () {                 this.get_course();             }         },         methods: {              handleSizeChange(val) {                 // 每页数据量发生变化时执行的方法                 this.filter.page = 1;                 this.filter.page_size = val;             },             handleCurrentChange(val) {                 // 页码发生变化时执行的方法                 this.filter.page = val;             },             get_category() {                 // 获取课程分类信息                 this.$axios.get(`${this.$settings.base_url}course/category/`).then(response => {                     this.category_list = response.data;                 }).catch(() => {                     this.$message({                         message: 获取课程分类信息有误,请联系客服工作人员,                     })                 })             },             get_course() {                 // 排序                 let filters = {                     ordering: this.filter.ordering, // 排序                 };                 // 判决是否进行分类课程的展示                 if (this.filter.course_category > 0) {                     filters.course_category = this.filter.course_category;                 }                  // 设置单页数据量                 if (this.filter.page_size > 0) {                     filters.page_size = this.filter.page_size;                 } else {                     filters.page_size = 5;                 }                  // 设置当前页码                 if (this.filter.page > 1) {                     filters.page = this.filter.page;                 } else {                     filters.page = 1;                 }                   // 获取课程列表信息                 this.$axios.get(`${this.$settings.base_url}course/actual/`, {                     params: filters                 }).then(response => {                     // console.log(response.data);                     this.course_list = response.data.results;                     this.course_total = response.data.count;                     // console.log(this.course_list);                 }).catch(() => {                     this.$message({                         message: 获取课程信息有误,请联系客服工作人员                     })                 })             }         }     } </script>  <style scoped>     .course {         background: #f6f6f6;     }      .course .main {         width: 1100px;         margin: 35px auto 0;     }      .course .condition {         margin-bottom: 35px;         padding: 25px 30px 25px 20px;         background: #fff;         border-radius: 4px;         box-shadow: 0 2px 4px 0 #f0f0f0;     }      .course .cate-list {         border-bottom: 1px solid #333;         border-bottom-color: rgba(51, 51, 51, .05);         padding-bottom: 18px;         margin-bottom: 17px;     }      .course .cate-list::after {         content: ;         display: block;         clear: both;     }      .course .cate-list li {         float: left;         font-size: 16px;         padding: 6px 15px;         line-height: 16px;         margin-left: 14px;         position: relative;         transition: all .3s ease;         cursor: pointer;         color: #4a4a4a;         border: 1px solid transparent; /* transparent 透明 */     }      .course .cate-list .title {         color: #888;         margin-left: 0;         letter-spacing: .36px;         padding: 0;         line-height: 28px;     }      .course .cate-list .this {         color: #ffc210;         border: 1px solid #ffc210 !important;         border-radius: 30px;     }      .course .ordering::after {         content: ;         display: block;         clear: both;     }      .course .ordering ul {         float: left;     }      .course .ordering ul::after {         content: ;         display: block;         clear: both;     }      .course .ordering .condition-result {         float: right;         font-size: 14px;         color: #9b9b9b;         line-height: 28px;     }      .course .ordering ul li {         float: left;         padding: 6px 15px;         line-height: 16px;         margin-left: 14px;         position: relative;         transition: all .3s ease;         cursor: pointer;         color: #4a4a4a;     }      .course .ordering .title {         font-size: 16px;         color: #888;         letter-spacing: .36px;         margin-left: 0;         padding: 0;         line-height: 28px;     }      .course .ordering .this {         color: #ffc210;     }      .course .ordering .price {         position: relative;     }      .course .ordering .price::before,     .course .ordering .price::after {         cursor: pointer;         content: ;         display: block;         width: 0px;         height: 0px;         border: 5px solid transparent;         position: absolute;         right: 0;     }      .course .ordering .price::before {         border-bottom: 5px solid #aaa;         margin-bottom: 2px;         top: 2px;     }      .course .ordering .price::after {         border-top: 5px solid #aaa;         bottom: 2px;     }      .course .ordering .price_up::before {         border-bottom-color: #ffc210;     }      .course .ordering .price_down::after {         border-top-color: #ffc210;     }      .course .course-item:hover {         box-shadow: 4px 6px 16px rgba(0, 0, 0, .5);     }      .course .course-item {         width: 1100px;         background: #fff;         padding: 20px 30px 20px 20px;         margin-bottom: 35px;         border-radius: 2px;         cursor: pointer;         box-shadow: 2px 3px 16px rgba(0, 0, 0, .1);         /* css3.0 过渡动画 hover 事件操作 */         transition: all .2s ease;     }      .course .course-item::after {         content: ;         display: block;         clear: both;     }      /* 顶级元素 父级元素  当前元素{} */     .course .course-item .course-image {         float: left;         width: 423px;         height: 210px;         margin-right: 30px;     }      .course .course-item .course-image img {         max-width: 100%;         max-height: 210px;     }      .course .course-item .course-info {         float: left;         width: 596px;     }      .course-item .course-info h3 a {         font-size: 26px;         color: #333;         font-weight: normal;         margin-bottom: 8px;     }      .course-item .course-info h3 span {         font-size: 14px;         color: #9b9b9b;         float: right;         margin-top: 14px;     }      .course-item .course-info h3 span img {         width: 11px;         height: auto;         margin-right: 7px;     }      .course-item .course-info .teather-info {         font-size: 14px;         color: #9b9b9b;         margin-bottom: 14px;         padding-bottom: 14px;         border-bottom: 1px solid #333;         border-bottom-color: rgba(51, 51, 51, .05);     }      .course-item .course-info .teather-info span {         float: right;     }      .course-item .section-list::after {         content: ;         display: block;         clear: both;     }      .course-item .section-list li {         float: left;         width: 44%;         font-size: 14px;         color: #666;         padding-left: 22px;         /* background: url(路径) 是否平铺 x轴位置 y轴位置 */         background: url(/src/assets/img/play-icon-gray.svg) no-repeat left 4px;         margin-bottom: 15px;     }      .course-item .section-list li .section-title {         /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */         text-overflow: ellipsis;         overflow: hidden;         white-space: nowrap;         display: inline-block;         max-width: 200px;     }      .course-item .section-list li:hover {         background-image: url(/src/assets/img/play-icon-yellow.svg);         color: #ffc210;     }      .course-item .section-list li .free {         width: 34px;         height: 20px;         color: #fd7b4d;         vertical-align: super;         margin-left: 10px;         border: 1px solid #fd7b4d;         border-radius: 2px;         text-align: center;         font-size: 13px;         white-space: nowrap;     }      .course-item .section-list li:hover .free {         color: #ffc210;         border-color: #ffc210;     }      .course-item {         position: relative;     }      .course-item .pay-box {         position: absolute;         bottom: 20px;         width: 600px;     }      .course-item .pay-box::after {         content: ;         display: block;         clear: both;     }      .course-item .pay-box .discount-type {         padding: 6px 10px;         font-size: 16px;         color: #fff;         text-align: center;         margin-right: 8px;         background: #fa6240;         border: 1px solid #fa6240;         border-radius: 10px 0 10px 0;         float: left;     }      .course-item .pay-box .discount-price {         font-size: 24px;         color: #fa6240;         float: left;     }      .course-item .pay-box .original-price {         text-decoration: line-through;         font-size: 14px;         color: #9b9b9b;         margin-left: 10px;         float: left;         margin-top: 10px;     }      .course-item .pay-box .buy-now {         width: 120px;         height: 38px;         background: transparent;         color: #fa6240;         font-size: 16px;         border: 1px solid #fd7b4d;         border-radius: 3px;         transition: all .2s ease-in-out;         float: right;         text-align: center;         line-height: 38px;         position: absolute;         right: 0;         bottom: 5px;     }      .course-item .pay-box .buy-now:hover {         color: #fff;         background: #ffc210;         border: 1px solid #ffc210;     }      .course .course_pagination {         margin-bottom: 60px;         text-align: center;     } </style>
View Code

新建CourseDetail

<template>     <div class=detail>         <Header/>         <div class=main>             <div class=course-info>                 <div class=wrap-left>                     <vue-core-video-player :src=mp4_url                                            controls=auto                                            autoplay                                            :muted=true                                            title=致命诱惑                                            @play=playFunc                                            @pause=pauseFunc                     ></vue-core-video-player>                 </div>                 <div class=wrap-right>                     <h3 class=course-name>{{course_info.name}}</h3>                     <p class=data>{{course_info.students}}人在学&nbsp;&nbsp;&nbsp;&nbsp;课程总时长:{{course_info.sections}}课时/{{course_info.pub_sections}}小时&nbsp;&nbsp;&nbsp;&nbsp;难度:{{course_info.level_name}}</p>                     <div class=sale-time>                         <p class=sale-type>价格 <span class=original_price>¥{{course_info.price}}</span></p>                         <p class=expire></p>                     </div>                     <div class=buy>                         <div class=buy-btn>                             <button class=buy-now>立即购买</button>                             <button class=free>免费试学</button>                         </div>                         <div class=add-cart @click=add_cart(course_info.id)>                             <img src=@/assets/img/cart-yellow.svg alt=>加入购物车                         </div>                     </div>                 </div>             </div>             <div class=course-tab>                 <ul class=tab-list>                     <li :class=tabIndex==1?'active':'' @click=tabIndex=1>详情介绍</li>                     <li :class=tabIndex==2?'active':'' @click=tabIndex=2>课程章节 <span :class=tabIndex!=2?'free':''>(试学)</span>                     </li>                     <li :class=tabIndex==3?'active':'' @click=tabIndex=3>用户评论</li>                     <li :class=tabIndex==4?'active':'' @click=tabIndex=4>常见问题</li>                 </ul>             </div>             <div class=course-content>                 <div class=course-tab-list>                     <div class=tab-item v-if=tabIndex==1>                         <div class=course-brief v-html=course_info.brief></div>                     </div>                     <div class=tab-item v-if=tabIndex==2>                         <div class=tab-item-title>                             <p class=chapter>课程章节</p>                             <p class=chapter-length>共{{course_chapters.length}}章 {{course_info.sections}}个课时</p>                         </div>                         <div class=chapter-item v-for=chapter in course_chapters :key=chapter.name>                             <p class=chapter-title><img src=@/assets/img/enum.svg alt=>第{{chapter.chapter}}章·{{chapter.name}}                             </p>                             <ul class=section-list>                                 <li class=section-item v-for=section in chapter.coursesections :key=section.name>                                     <p class=name><span class=index>{{chapter.chapter}}-{{section.orders}}</span>                                         {{section.name}}<span class=free v-if=section.free_trail>免费</span></p>                                     <p class=time>{{section.duration}} <img src=@/assets/img/chapter-player.svg></p>                                     <button class=try v-if=section.free_trail>立即试学</button>                                     <button class=try v-else>立即购买</button>                                 </li>                             </ul>                         </div>                     </div>                     <div class=tab-item v-if=tabIndex==3>                         用户评论                     </div>                     <div class=tab-item v-if=tabIndex==4>                         常见问题                     </div>                 </div>                 <div class=course-side>                     <div class=teacher-info>                         <h4 class=side-title><span>授课老师</span></h4>                         <div class=teacher-content>                             <div class=cont1>                                 <img :src=course_info.teacher.image>                                 <div class=name>                                     <p class=teacher-name>{{course_info.teacher.name}}                                         {{course_info.teacher.title}}</p>                                     <p class=teacher-title>{{course_info.teacher.signature}}</p>                                 </div>                             </div>                             <p class=narrative>{{course_info.teacher.brief}}</p>                         </div>                     </div>                 </div>             </div>         </div>         <Footer/>     </div> </template>  <script>     import Header from @/components/Header     import Footer from @/components/Footer      export default {         name: Detail,         data() {             return {                 tabIndex: 2,   // 当前选项卡显示的下标                 course_id: 0, // 当前课程信息的ID                 course_info: {                     teacher: {},                 }, // 课程信息                 course_chapters: [], // 课程的章节课时列表                 // mp4_url:'http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4',                 mp4_url: [                     {                         src: 'http://rb1x3v1fm.sabkt.gdipper.com/%E8%87%B4%E5%91%BD%E8%AF%B1%E6%83%91320p.mp4',                         resolution: 360,                     },                     {                         src: 'http://rb1x3v1fm.sabkt.gdipper.com/%E8%87%B4%E5%91%BD%E8%AF%B1%E6%83%91720p.mp4',                         resolution: 720,                     },                     {                         src: 'http://rb1x3v1fm.sabkt.gdipper.com/%E8%87%B4%E5%91%BD%E8%AF%B1%E6%83%914k.mp4',                         resolution: '4k',                      }],               }         },         created() {             this.get_course_id();             this.get_course_data();             this.get_chapter();         },         methods: {             playFunc(){                 console.log('开始了')             },             pauseFunc(){                 console.log('暂停了')             },             get_course_id() {                 // 获取地址栏上面的课程ID                 this.course_id = this.$route.params.pk                 if (this.course_id < 1) {                     let _this = this;                     _this.$alert(对不起,当前视频不存在!, 警告, {                         callback() {                             _this.$router.go(-1);                         }                     });                 }             },             get_course_data() {                 // ajax请求课程信息                 this.$axios.get(`${this.$settings.base_url}course/actual/${this.course_id}/`).then(response => {                     // window.console.log(response.data);                     this.course_info = response.data;                     console.log(this.course_info)                 }).catch(() => {                     this.$message({                         message: 对不起,访问页面出错!请联系客服工作人员!                     });                 })             },              get_chapter() {                 // 获取当前课程对应的章节课时信息                 // http://127.0.0.1:8000/course/chapters/?course=(pk)                 this.$axios.get(`${this.$settings.base_url}course/chapter/`, {                     params: {                         course_id: this.course_id,                     }                 }).then(response => {                     this.course_chapters = response.data;                 }).catch(error => {                     window.console.log(error.response);                 })             },         },         components: {             Header,             Footer,         }     } </script>  <style scoped>     .main {         background: #fff;         padding-top: 30px;     }      .course-info {         width: 1200px;         margin: 0 auto;         overflow: hidden;     }      .wrap-left {         float: left;         width: 690px;         height: 388px;         background-color: #000;     }      .wrap-right {         float: left;         position: relative;         height: 388px;     }      .course-name {         font-size: 20px;         color: #333;         padding: 10px 23px;         letter-spacing: .45px;     }      .data {         padding-left: 23px;         padding-right: 23px;         padding-bottom: 16px;         font-size: 14px;         color: #9b9b9b;     }      .sale-time {         width: 464px;         background: #fa6240;         font-size: 14px;         color: #4a4a4a;         padding: 10px 23px;         overflow: hidden;     }      .sale-type {         font-size: 16px;         color: #fff;         letter-spacing: .36px;         float: left;     }      .sale-time .expire {         font-size: 14px;         color: #fff;         float: right;     }      .sale-time .expire .second {         width: 24px;         display: inline-block;         background: #fafafa;         color: #5e5e5e;         padding: 6px 0;         text-align: center;     }      .course-price {         background: #fff;         font-size: 14px;         color: #4a4a4a;         padding: 5px 23px;     }      .discount {         font-size: 26px;         color: #fa6240;         margin-left: 10px;         display: inline-block;         margin-bottom: -5px;     }      .original {         font-size: 14px;         color: #9b9b9b;         margin-left: 10px;         text-decoration: line-through;     }      .buy {         width: 464px;         padding: 0px 23px;         position: absolute;         left: 0;         bottom: 20px;         overflow: hidden;     }      .buy .buy-btn {         float: left;     }      .buy .buy-now {         width: 125px;         height: 40px;         border: 0;         background: #ffc210;         border-radius: 4px;         color: #fff;         cursor: pointer;         margin-right: 15px;         outline: none;     }      .buy .free {         width: 125px;         height: 40px;         border-radius: 4px;         cursor: pointer;         margin-right: 15px;         background: #fff;         color: #ffc210;         border: 1px solid #ffc210;     }      .add-cart {         float: right;         font-size: 14px;         color: #ffc210;         text-align: center;         cursor: pointer;         margin-top: 10px;     }      .add-cart img {         width: 20px;         height: 18px;         margin-right: 7px;         vertical-align: middle;     }      .course-tab {         width: 100%;         background: #fff;         margin-bottom: 30px;         box-shadow: 0 2px 4px 0 #f0f0f0;      }      .course-tab .tab-list {         width: 1200px;         margin: auto;         color: #4a4a4a;         overflow: hidden;     }      .tab-list li {         float: left;         margin-right: 15px;         padding: 26px 20px 16px;         font-size: 17px;         cursor: pointer;     }      .tab-list .active {         color: #ffc210;         border-bottom: 2px solid #ffc210;     }      .tab-list .free {         color: #fb7c55;     }      .course-content {         width: 1200px;         margin: 0 auto;         background: #FAFAFA;         overflow: hidden;         padding-bottom: 40px;     }      .course-tab-list {         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;     }      .tab-item {         width: 880px;         background: #fff;         padding-bottom: 20px;         box-shadow: 0 2px 4px 0 #f0f0f0;     }      .tab-item-title {         justify-content: space-between;         padding: 25px 20px 11px;         border-radius: 4px;         margin-bottom: 20px;         border-bottom: 1px solid #333;         border-bottom-color: rgba(51, 51, 51, .05);         overflow: hidden;     }      .chapter {         font-size: 17px;         color: #4a4a4a;         float: left;     }      .chapter-length {         float: right;         font-size: 14px;         color: #9b9b9b;         letter-spacing: .19px;     }      .chapter-title {         font-size: 16px;         color: #4a4a4a;         letter-spacing: .26px;         padding: 12px;         background: #eee;         border-radius: 2px;         display: -ms-flexbox;         display: flex;         -ms-flex-align: center;         align-items: center;     }      .chapter-title img {         width: 18px;         height: 18px;         margin-right: 7px;         vertical-align: middle;     }      .section-list {         padding: 0 20px;     }      .section-list .section-item {         padding: 15px 20px 15px 36px;         cursor: pointer;         justify-content: space-between;         position: relative;         overflow: hidden;     }      .section-item .name {         font-size: 14px;         color: #666;         float: left;     }      .section-item .index {         margin-right: 5px;     }      .section-item .free {         font-size: 12px;         color: #fff;         letter-spacing: .19px;         background: #ffc210;         border-radius: 100px;         padding: 1px 9px;         margin-left: 10px;     }      .section-item .time {         font-size: 14px;         color: #666;         letter-spacing: .23px;         opacity: 1;         transition: all .15s ease-in-out;         float: right;     }      .section-item .time img {         width: 18px;         height: 18px;         margin-left: 15px;         vertical-align: text-bottom;     }      .section-item .try {         width: 86px;         height: 28px;         background: #ffc210;         border-radius: 4px;         font-size: 14px;         color: #fff;         position: absolute;         right: 20px;         top: 10px;         opacity: 0;         transition: all .2s ease-in-out;         cursor: pointer;         outline: none;         border: none;     }      .section-item:hover {         background: #fcf7ef;         box-shadow: 0 0 0 0 #f3f3f3;     }      .section-item:hover .name {         color: #333;     }      .section-item:hover .try {         opacity: 1;     }      .course-side {         width: 300px;         height: auto;         margin-left: 20px;         float: right;     }      .teacher-info {         background: #fff;         margin-bottom: 20px;         box-shadow: 0 2px 4px 0 #f0f0f0;     }      .side-title {         font-weight: normal;         font-size: 17px;         color: #4a4a4a;         padding: 18px 14px;         border-bottom: 1px solid #333;         border-bottom-color: rgba(51, 51, 51, .05);     }      .side-title span {         display: inline-block;         border-left: 2px solid #ffc210;         padding-left: 12px;     }      .teacher-content {         padding: 30px 20px;         box-sizing: border-box;     }      .teacher-content .cont1 {         margin-bottom: 12px;         overflow: hidden;     }      .teacher-content .cont1 img {         width: 54px;         height: 54px;         margin-right: 12px;         float: left;     }      .teacher-content .cont1 .name {         float: right;     }      .teacher-content .cont1 .teacher-name {         width: 188px;         font-size: 16px;         color: #4a4a4a;         padding-bottom: 4px;     }      .teacher-content .cont1 .teacher-title {         width: 188px;         font-size: 13px;         color: #9b9b9b;         white-space: nowrap;     }      .teacher-content .narrative {         font-size: 14px;         color: #666;         line-height: 24px;     } </style>
View Code

路由注册

router/index.js

import Vue from 'vue' import VueRouter from 'vue-router'  import HomeView from '../views/HomeView.vue' import Login from @/components/Login.vue; import Register from @/components/Register.vue;  // 导入login页面组件 import ActualCourse from @/views/ActualCourse;  // 导入实战课页面 import FreeCourse from @/views/FreeCourse;  // 导入免费课页面 import LightCourse from @/views/LightCourse;  // 导入轻课页面 import CourseDetail from @/views/CourseDetail;  // 导入课程详情  Vue.use(VueRouter)  // 具体路径 const routes = [     {         path: '/',         name: 'home',         component: HomeView     },     {         path: '/login',         name: 'login',         component: Login     },     {         path: '/register',         name: 'register',         component: Register     },     {         path: '/actual-course',         name: 'ActualCourse',         component: ActualCourse     },     {         path: '/free-course',         name: 'FreeCourse',         component: FreeCourse     },     {         path: '/light-course',         name: 'LightCourse',         component: LightCourse     },     {         path: '/actual-detail/:id',  // 类似于转换器         name: 'CourseDetail',         component: CourseDetail     }, ]   // 生成路由 const router = new VueRouter({     mode: 'history',     base: process.env.BASE_URL,     routes  // 具体路径 })  export default router
View Code

视频播放组件:vue-core-video-player的使用

# 第一步:安装  npm install --save vue-core-video-player  # 第二步:main.js 引入 import VueCoreVideoPlayer from 'vue-core-video-player' Vue.use(VueCoreVideoPlayer)  # 默认是英文的 # 第二步(补充)国际化 Vue.use(VueCoreVideoPlayer, {   lang: 'zh-CN' })  # 第三步:在组件的html中使用 <div id=app>   <div class=player-container>     <vue-core-video-player src=http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4></vue-core-video-player>   </div> </div>  https://www.cnblogs.com/liuqingzheng/p/16204851.html  # 组件详细说明 https://github.com/core-player/vue-core-video-player-examples  

4、课程详情接口

# 课程列表(所有课程:分页,排序。。。),再加一个课程详情 # 基于原来的课程列表接口,只需要再加入继承一个类:RetrieveModelMixin class CourseView(GenericViewSet, ListModelMixin,RetrieveModelMixin):     queryset = Course.objects.all().filter(is_delete=False, is_show=True).order_by('orders')     serializer_class = CourseSerializer

5、课程章节接口

思路
# 查询所有章节---》章节跟课程有关联,根据课程id号,过滤章节 # 比如:查询课程id为1的所有章节 # 本质就是查询所有章节,带过滤功能

路由

router.register('chapter', CourseChapterView, 'chapter')

课程章节接口

class CourseChapterView(GenericViewSet, ListModelMixin):     queryset = CourseChapter.objects.all().filter(is_show=True,is_delete=False).order_by('orders')     serializer_class =CourseChapterSerializer     filter_backends = [DjangoFilterBackend]     # 加入过滤---》按照课程id过滤--》第三方django-filter     filter_fields = ['course_id']

序列化类

class CourseSectionSerializer(serializers.ModelSerializer):     class Meta:         model = CourseSection         fields = ['name', 'orders', 'section_link', 'duration', 'free_trail']   class CourseChapterSerializer(serializers.ModelSerializer):     # 使用子序列化     coursesections = CourseSectionSerializer(many=True)      class Meta:         model = CourseChapter         # CourseChapter表中隐藏了一个coursesections字段,对象.coursesections         fields = ['id', 'name', 'chapter', 'summary', 'coursesections']  # 返回该章节下的课时

6、搜索功能接口

思路

# 搜索功能--》配合搜索接口 # 类比:淘宝,京东,抖音,微博,百度---》这个接口本应该是最复杂,最有技术含量的接口     -不同的人搜出来的结果都不一样,因为有个性化推荐   -淘宝搜 [连体衣]--->【电饭煲】 199以下 # 咱们是最简单的实现,后期学完es:分布式全文检索引擎,基于es,写个百度 # 正常来讲:搜索可以搜索所有类型课程---》免费课,实战课,轻课--》咱们只写了实战课,只能搜出实战课  # 咱么如果只有实战课,实际上可以和查询所有实战课课程接口放一起---》为了方便后期的扩展,我们要单独写 # 咱么你的可以按照课程名字模糊匹配   # 速度慢的问题---》总共课程就不多--》mysql完全顶得住 # 课程名字和id 在redis中缓存 # 只要用搜索,对数量比较大,就上专业的搜索引擎---》es ---》倒排索引   # http://127.0.0.1:8000/api/v1/course/search/?search=%E5%85%A5%E9%97%A8
View Code

视图类

# 只针对于实战课的搜索接口 class CourseSearchView(GenericViewSet, ListModelMixin):     queryset = Course.objects.all().filter(is_delete=False, is_show=True).order_by('orders')     serializer_class = CourseSerializer     filter_backends = [SearchFilter]     search_fields = ['name', ]     # 加入分页类     pagination_class = CommonPageNumberPagination      # 方便后期扩展     # def list(self, request, *args, **kwargs):     #     # 这个查的是实战课     #     res=super().list(request, *args, **kwargs)     #     # res2=查询免费课     #     # res3=查询轻课     #     #     # 上面全是取数据库查询     #     # 后期改成取es中查询,     #     return APIResponse(result={'free_course':'字典','actual_course':res.data})
View Code

7、搜索功能前段

思路

# 头部组件中的搜索框 # 搜索结果展示页面

搜索结果展示页面 header.vue

<template>     <div class=header>         <div class=slogan>             <p>老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</p>         </div>         <div class=nav>             <ul class=left-part>                 <li class=logo>                     <router-link to=/>                         <img src=../assets/img/head-logo.svg alt=>                     </router-link>                 </li>                 <li class=ele>                     <span @click=goPage('/free-course') :class={active: url_path === '/free-course'}>免费课</span>                 </li>                 <li class=ele>                     <span @click=goPage('/actual-course') :class={active: url_path === '/actual-course'}>实战课</span>                 </li>                 <li class=ele>                     <span @click=goPage('/light-course') :class={active: url_path === '/light-course'}>轻课</span>                 </li>             </ul>              <div class=right-part>                 <div v-if=username>                     <span style=margin-right: 10px><img :src=icon alt= width=35px height=35px></span>                     <span>{{username}}</span>                     <span class=line>|</span>                     <span @click=handleLogout>退出</span>                 </div>                 <div v-else>                     <span @click=put_login>登录</span>                     <span class=line>|</span>                     <span @click=put_register>注册</span>                 </div>             </div>             <Login v-if=is_login @close=close_login @go=put_register/>             <Register v-if=is_register @close=close_register @go=put_login/>             <form class=search>                 <div class=tips v-if=is_search_tip>                     <span @click=search_action('Python')>Python</span>                     <span @click=search_action('Linux')>Linux</span>                 </div>                 <input type=text :placeholder=search_placeholder @focus=on_search @blur=off_search                        v-model=search_word>                 <button type=button class=glyphicon glyphicon-search @click=search_action(search_word)></button>             </form>          </div>     </div>  </template>  <script>     import Login from @/components/Login;     import Register from @/components/Register;      export default {         name: Header,         data() {             return {                 url_path: sessionStorage.url_path || '/',                 is_login: false,                 is_register: false,                 username: '',                 icon: '',                 is_search_tip: true,                 search_placeholder: '',                 search_word: ''              }         },         methods: {             goPage(url_path) {                 // 已经是当前路由就没有必要重新跳转                 if (this.url_path !== url_path) {                     this.$router.push(url_path);                 }                 sessionStorage.url_path = url_path;             },             close_login() {                 this.is_login = false                 // 登陆了,从cookie去取出username,                 this.username = this.$cookies.get('username')                 this.icon = this.$cookies.get('icon')             },             close_register() {                 this.is_register = false             },             put_register() {                 this.is_register = true                 this.is_login = false             },             put_login() {                 this.is_register = false                 this.is_login = true             },             handleLogout() {                 // cookie中的数据删除就退出了                 this.$cookies.set('username', '')                 this.$cookies.set('token', '')                 this.$cookies.set('icon', '')                 this.username = ''                 this.icon = ''             },             search_action(search_word) {                 if (!search_word) {                     this.$message('请输入要搜索的内容');                     return                 }                 // this.$route当前路径,query ?name=lqz  -->this.$route.query.name 拿到lqz                 if (search_word !== this.$route.query.word) {                     this.$router.push(`/course/search?word=${search_word}`);                 }                 this.search_word = '';             },             on_search() {                 this.search_placeholder = '请输入想搜索的课程';                 this.is_search_tip = false;             },             off_search() {                 this.search_placeholder = '';                 this.is_search_tip = true;             },          },         created() {             sessionStorage.url_path = this.$route.path;             this.url_path = this.$route.path;             // 登陆了,从cookie去取出username,             this.username = this.$cookies.get('username')             this.icon = this.$cookies.get('icon')         },         components: {             Login, Register         }     } </script>  <style scoped>     .header {         background-color: white;         box-shadow: 0 0 5px 0 #aaa;     }      .header:after {         content: ;         display: block;         clear: both;     }      .slogan {         background-color: #eee;         height: 40px;     }      .slogan p {         width: 1200px;         margin: 0 auto;         color: #aaa;         font-size: 13px;         line-height: 40px;     }      .nav {         background-color: white;         user-select: none;         width: 1200px;         margin: 0 auto;      }      .nav ul {         padding: 15px 0;         float: left;     }      .nav ul:after {         clear: both;         content: '';         display: block;     }      .nav ul li {         float: left;     }      .logo {         margin-right: 20px;     }      .ele {         margin: 0 20px;     }      .ele span {         display: block;         font: 15px/36px '微软雅黑';         border-bottom: 2px solid transparent;         cursor: pointer;     }      .ele span:hover {         border-bottom-color: orange;     }      .ele span.active {         color: orange;         border-bottom-color: orange;     }      .right-part {         float: right;     }      .right-part .line {         margin: 0 10px;     }      .right-part span {         line-height: 68px;         cursor: pointer;     }      .search {         float: right;         position: relative;         margin-top: 22px;         margin-right: 10px;     }      .search input, .search button {         border: none;         outline: none;         background-color: white;     }      .search input {         border-bottom: 1px solid #eeeeee;     }      .search input:focus {         border-bottom-color: orange;     }      .search input:focus + button {         color: orange;     }      .search .tips {         position: absolute;         bottom: 3px;         left: 0;     }      .search .tips span {         border-radius: 11px;         background-color: #eee;         line-height: 22px;         display: inline-block;         padding: 0 7px;         margin-right: 3px;         cursor: pointer;         color: #aaa;         font-size: 14px;      }      .search .tips span:hover {         color: orange;     } </style>
View Code

8、视频托管

什么是视频托管

# 为什么会用到视频托管平台     图片,视频,一般不放在自己项目中,因为只要访问一次图片,就向后端发一次请求,会消耗服务器资源 所以,我们通常把静态类型文件,托管到第三方平台,这个操作会花钱,它使用的是第三方平台的服务器资源和带宽 # 视频托管到云平台了     -阿里 oss     -七牛云存储。。。  # 公司里托管视频的多种方案     1.托管到云平台:阿里 oss, 七牛云存储        2.公司自己搭建文件服务器         -fastdfs:搭建笔记   搭建完毕用python上传(上传比较麻烦):https://zhuanlan.zhihu.com/p/372286804          -Minio:自己搭建Minio,阿里云上--》搭建笔记--》上传下载比较简单         -图形化界面后台,登陆上看到文件         -ceph:大型文件服务器,搭建比较码放      3.如果文件小可以直接放到项目中       # 七牛云---》国内go语言推崇者--》项目基本都是go开发的---》许式伟     我们的项目使用七牛云托管视频,七牛云是国内go语言推崇者,项目基本都是go开发的,掌门许式伟     go的国内镜像:goproxy.cn 就是七牛云的     -豆瓣python---》python写的  # 七牛云上传     -手动上传---》手动配置视频地址--(不采取)     -前端上传,返回连接地址,提交到咱们自己的后端,存到数据库     -前端把视频传到咱们后端,咱们后端通过python传到七牛云,生成连接,存到数据库:https://developer.qiniu.com/kodo/1242/python
View Code

七牛云快速使用

左上角—点击展开产品浮层—对象存储kodo—空间管理—新建空间

存储空间名称(不允许重复)—存储区域—访问控制

空间创建成功—绑定域名/立即绑定自定义域名(使用立即绑定自定义域名,平台会分配一个30天的试用域名)

文件管理—上传文件—选择文件

空间设置—可以选择访问控制为共有/私有

生成文件链接

可以把文件链接放入数据库中

七牛云上传文件

from qiniu import Auth, put_file, etag q = Auth('--', '-')  # 个人中心——秘钥管理 #要上传的空间 bucket_name = 'lqz' #上传后保存的文件名 key = '致命诱惑.png' #生成上传 Token,可以指定过期时间等 token = q.upload_token(bucket_name, key, 3600) #要上传文件的本地路径 localfile = './default.png' ret, info = put_file(token, key, localfile, version='v2') print(info)