前端课程页面、课程主页表分析创建、课程页面数据录入、课程分类接口、课程列表接口、课程页面前后端连通、课程详情页面

今日内容概要

  • 课程页面复制
  • 课程主页表分析
  • 课程页面数据录入
  • 课程分类接口
  • 课程列表接口
  • 课程页面前后端调通
  • 课程详情接口
  • 课程详情页面

内容详细

1、课程页面

# 打开前端项目 luffycity:  # 把前端代码复制到ActualCourse中即可 	http://liuqingzheng.top/python/%E8%B7%AF%E9%A3%9E%E9%A1%B9%E7%9B%AE/6-%E8%AF%BE%E7%A8%8B%E6%A8%A1%E5%9D%97/1-%E8%AF%BE%E7%A8%8B%E9%A1%B5%E9%A1%B5%E9%9D%A2/ 

src/views/FreeCourse.vue:

<template>   <div>     <Header></Header>     <img src=https://tva1.sinaimg.cn/large/e6c9d24egy1h1g112oiclj224l0u0jxl.jpg alt= height=500px          width=100%>     <Footer></Footer>   </div> </template>  <script>  import Header from @/components/Header import Footer from @/components/Footer  export default {   name: ActualCourse,   components: {     Header,     Footer,   } } </script>  <style scoped>  </style> 

src/views/ActualCourse.vue:

<template>   <div class=course>     <Header></Header>     <div class=main>       <!-- 筛选条件 -->       <div class=condition>         <ul class=cate-list>           <li class=title>课程分类:</li>           <li class=this>全部</li>           <li>Python</li>           <li>Linux运维</li>           <li>Python进阶</li>           <li>开发工具</li>           <li>Go语言</li>           <li>机器学习</li>           <li>技术生涯</li>         </ul>          <div class=ordering>           <ul>             <li class=title>筛&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</li>             <li class=default this>默认</li>             <li class=hot>人气</li>             <li class=price>价格</li>           </ul>           <p class=condition-result>共21个课程</p>         </div>        </div>       <!-- 课程列表 -->       <div class=course-list>         <div class=course-item>           <div class=course-image>             <img src=@/assets/img/course-cover.jpeg alt=>           </div>           <div class=course-info>             <h3>Python开发21天入门 <span><img src=@/assets/img/avatar1.svg alt=>100人已加入学习</span></h3>             <p class=teather-info>Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>             <ul class=lesson-list>               <li><span class=lesson-title>01 | 第1节:初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span></li>             </ul>             <div class=pay-box>               <span class=discount-type>限时免费</span>               <span class=discount-price>¥0.00元</span>               <span class=original-price>原价:9.00元</span>               <span class=buy-now>立即购买</span>             </div>           </div>         </div>         <div class=course-item>           <div class=course-image>             <img src=@/assets/img/course-cover.jpeg alt=>           </div>           <div class=course-info>             <h3>Python开发21天入门 <span><img src=@/assets/img/avatar1.svg alt=>100人已加入学习</span></h3>             <p class=teather-info>Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>             <ul class=lesson-list>               <li><span class=lesson-title>01 | 第1节:初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span></li>             </ul>             <div class=pay-box>               <span class=discount-type>限时免费</span>               <span class=discount-price>¥0.00元</span>               <span class=original-price>原价:9.00元</span>               <span class=buy-now>立即购买</span>             </div>           </div>         </div>         <div class=course-item>           <div class=course-image>             <img src=@/assets/img/course-cover.jpeg alt=>           </div>           <div class=course-info>             <h3>Python开发21天入门 <span><img src=@/assets/img/avatar1.svg alt=>100人已加入学习</span></h3>             <p class=teather-info>Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>             <ul class=lesson-list>               <li><span class=lesson-title>01 | 第1节:初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span></li>             </ul>             <div class=pay-box>               <span class=discount-type>限时免费</span>               <span class=discount-price>¥0.00元</span>               <span class=original-price>原价:9.00元</span>               <span class=buy-now>立即购买</span>             </div>           </div>         </div>         <div class=course-item>           <div class=course-image>             <img src=@/assets/img/course-cover.jpeg alt=>           </div>           <div class=course-info>             <h3>Python开发21天入门 <span><img src=@/assets/img/avatar1.svg alt=>100人已加入学习</span></h3>             <p class=teather-info>Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>             <ul class=lesson-list>               <li><span class=lesson-title>01 | 第1节:初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span></li>             </ul>             <div class=pay-box>               <span class=discount-type>限时免费</span>               <span class=discount-price>¥0.00元</span>               <span class=original-price>原价:9.00元</span>               <span class=buy-now>立即购买</span>             </div>           </div>         </div>       </div>     </div>     <!--<Footer></Footer>-->   </div> </template>  <script> import Header from @/components/Header // import Footer from @/components/Footer  export default {   name: Course,   data() {     return {       category: 0,     }   },   components: {     Header,     // Footer,   } } </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 .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 {   width: 100%; }  .course .course-item .course-info {   float: left;   width: 596px; }  .course-item .course-info h3 {   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 .lesson-list::after {   content: ;   display: block;   clear: both; }  .course-item .lesson-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 .lesson-list li .lesson-title {   /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */   text-overflow: ellipsis;   overflow: hidden;   white-space: nowrap;   display: inline-block;   max-width: 200px; }  .course-item .lesson-list li:hover {   background-image: url(/src/assets/img/play-icon-yellow.svg);   color: #ffc210; }  .course-item .lesson-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 .lesson-list li:hover .free {   color: #ffc210;   border-color: #ffc210; }  .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; }  .course-item .pay-box .buy-now:hover {   color: #fff;   background: #ffc210;   border: 1px solid #ffc210; } </style> 

src/views/ActualLightCourse.vue:

<template>   <div class=course>     <Header></Header>     <div class=main>       <!-- 筛选条件 -->       <div class=condition>         <ul class=cate-list>           <li class=title>课程分类:</li>           <li class=this>全部</li>           <li>轻课1</li>           <li>轻课2</li>           <li>致命诱惑</li>         </ul>          <div class=ordering>           <ul>             <li class=title>筛&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;选:</li>             <li class=default this>默认</li>             <li class=hot>人气</li>             <li class=price>价格</li>           </ul>           <p class=condition-result>共21个课程</p>         </div>        </div>       <!-- 课程列表 -->       <div class=course-list>         <div class=course-item>           <div class=course-image>             <img src=@/assets/img/course-cover.jpeg alt=>           </div>           <div class=course-info>             <h3>Python开发21天入门 <span><img src=@/assets/img/avatar1.svg alt=>100人已加入学习</span></h3>             <p class=teather-info>Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>             <ul class=lesson-list>               <li><span class=lesson-title>01 | 第1节:初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span></li>             </ul>             <div class=pay-box>               <span class=discount-type>限时免费</span>               <span class=discount-price>¥0.00元</span>               <span class=original-price>原价:9.00元</span>               <span class=buy-now>立即购买</span>             </div>           </div>         </div>         <div class=course-item>           <div class=course-image>             <img src=@/assets/img/course-cover.jpeg alt=>           </div>           <div class=course-info>             <h3>Python开发21天入门 <span><img src=@/assets/img/avatar1.svg alt=>100人已加入学习</span></h3>             <p class=teather-info>Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p>             <ul class=lesson-list>               <li><span class=lesson-title>01 | 第1节:初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span> <span class=free>免费</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码</span></li>               <li><span class=lesson-title>01 | 第1节:初识编码初识编码</span></li>             </ul>             <div class=pay-box>               <span class=discount-type>限时免费</span>               <span class=discount-price>¥0.00元</span>               <span class=original-price>原价:9.00元</span>               <span class=buy-now>立即购买</span>             </div>           </div>         </div>        </div>     </div>     <Footer></Footer>   </div> </template>  <script> import Header from @/components/Header import Footer from @/components/Footer  export default {   name: Course,   data() {     return {       category: 0,     }   },   components: {     Header,     Footer,   } } </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 .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 {   width: 100%; }  .course .course-item .course-info {   float: left;   width: 596px; }  .course-item .course-info h3 {   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 .lesson-list::after {   content: ;   display: block;   clear: both; }  .course-item .lesson-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 .lesson-list li .lesson-title {   /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */   text-overflow: ellipsis;   overflow: hidden;   white-space: nowrap;   display: inline-block;   max-width: 200px; }  .course-item .lesson-list li:hover {   background-image: url(/src/assets/img/play-icon-yellow.svg);   color: #ffc210; }  .course-item .lesson-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 .lesson-list li:hover .free {   color: #ffc210;   border-color: #ffc210; }  .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; }  .course-item .pay-box .buy-now:hover {   color: #fff;   background: #ffc210;   border: 1px solid #ffc210; } </style> 

src/router/index.js中:

import ActualCourse from @/views/ActualCourse; import FreeCourse from @/views/FreeCourse; import LightCourse from @/views/LightCourse; import CourseDetail from @/views/CourseDetail;   const routes = [     {         path: '/actual-course',         name: 'ActualCourse',         component: ActualCourse     },     {         path: '/free-course',         name: 'FreeCourse',         component: FreeCourse     },     {         path: '/light-course',         name: 'LightCourse',         component: LightCourse     },  ] 

image

2、课程主页表分析

# 三类课程---》表用同一个还是分开 	由于三种课程字段不一样,我们把每个课程单独创建一个表     # 以实战课为例来写 	课程分类表      	实战课表  ---》跟课程分类是一对多---》Python7天入门---》第一章:环境搭建 5个视频   第二章:面向对象 15个视频         	课程章节表---》跟实战课表是一对多的关系      	课时表---》跟章节表一对多      	老师表--》一对多---》一个老师讲多门课程    	评论表(没写)            # 打开后端项目 建立5张表 # 先打开后端luffy_api项目: # 注册课程app 注意切换到apps路径下 PS E:\Django\luffy_api\luffy_api\apps> python ../../manage.py startapp course  # 到luffy_api/luffy_api/urls.py 添加课程路由:     path('api/v1/course/', include('course.urls')),  # http://127.0.0.1:8000/api/v1/course/      # 复制urls.py文件到course目录下:  # 到配置文件中注册 写在最下面即可:	 INSTALLED_APPS = [ 'course', ]     # ForeignKey 属性on_delete可以选择如下: 	CASCADE   级联删除,比如删除老师,老师关联的所有课程都删除---》危险系数太高 作者和作者详情,就可以使用级联删除 	DO_NOTHING  什么都不做 	SET_DEFAULT 删了老师,课程这个字段设置成默认,配合default 	SET_NULL    删了老师,课程中老师这个字段设置为空 null=True 	SET(值/函数)  删除老师,执行函数,课程中老师这个字段设置为SET的值或函数的执行结果        # ForeignKey 属性 db_constraint 	ForeignKey是外键---》实际上在数据库会有外键关系      	实际上外键关系有好处: 		做约束---》插入数据时,脏数据插入不进去 	 	外键关系也有坏处: 		插入速度慢---》插入的时候要校验约束          	实际编码中,公司里,基本不用外键约束----》这些操作统统由程序员和程序约束--》提高速度      	db_constraint 不建外键约束---》可以基于对象的跨表查询--》基于双下划线的连表查---》一点不受影响       # ForeignKey 属性 related_name 	反向操作时,使用的字段名,用于代替原反向查询时的’表名_set’ 	反向操作:通过课程查询所有章节:course.表名小写_set.all() 	如果写了related_name---》course.coursechapters.all()      # ForeignKey 属性 related_query_name 	反向查询操作时,使用的连接前缀,用于替换表名。 	原来 __链表查询,使用表名小写,写了它后,直接使用这个字段 

数据准备

创建表 course/models.py

from django.db import models from utils.model import BaseModel   # 课程分类表 class CourseCategory(BaseModel):     分类     name = models.CharField(max_length=64, unique=True, verbose_name=分类名称)      class Meta:         db_table = luffy_course_category         verbose_name = 分类         verbose_name_plural = verbose_name      def __str__(self):         return %s % self.name   # 实战课表 class Course(BaseModel):     # choice     course_type = (         (0, '付费'),         (1, '超级VIP专享'),      )     level_choices = (         (0, '初级'),         (1, '中级'),         (2, '高级'),         (3, '特高级'),         (4, '超神'),     )     status_choices = (         (0, '上线'),         (1, '下线'),         (2, '预上线'),     )     # 课程名     name = models.CharField(max_length=128, verbose_name=课程名称)     # 课程图片  null:数据库可以为空,blank:后台管理录入的时候可以不填,     course_img = models.ImageField(upload_to=courses, max_length=255, verbose_name=封面图片, blank=True, null=True)     # 付费类型     course_type = models.SmallIntegerField(choices=course_type, default=0, verbose_name=付费类型)     # 详情介绍--》课程详情页面---》TextField---》bbs项目的文章详情,html内容     brief = models.TextField(max_length=2048, verbose_name=详情介绍, null=True, blank=True)     # 难度等级     level = models.SmallIntegerField(choices=level_choices, default=0, verbose_name=难度等级)     # 发布日期  课程录入一个时间---》没有发布---》发布是在网站上可以看到了     pub_date = models.DateField(verbose_name=发布日期, auto_now_add=True)     # 建议学习周期     period = models.IntegerField(verbose_name=建议学习周期(day), default=7)     # 课件路径--》课程有课件  ppt,png,md---》压缩成zip     attachment_path = models.FileField(upload_to=attachment, max_length=128, verbose_name=课件路径, blank=True,                                        null=True)     # 课程状态     status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name=课程状态)     # 学习人数 ---》优化字段,正常课程跟用户是有关系的,不需要关联查询统计用户个数了     students = models.IntegerField(verbose_name=学习人数, default=0)     # 总课时数量---3个章节20课时的内容     sections = models.IntegerField(verbose_name=总课时数量, default=0)     # 课时更新数量  ---》3个章节20课时的内容现在只更新了10个     pub_sections = models.IntegerField(verbose_name=课时更新数量, default=0)     # 课程原价     price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name=课程原价, default=0)      ### 关联字段---》老师---》一个老师有多门课程,关联字段写在多的一方,写在课程中     teacher = models.ForeignKey(Teacher, on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name=授课老师)     ###  关联字段---》课程分类--->一个分类下有多个课程,关联字段写在多的一方     course_category = models.ForeignKey(CourseCategory, on_delete=models.SET_NULL, db_constraint=False, null=True,                                         blank=True,                                         verbose_name=课程分类)      class Meta:         db_table = luffy_course         verbose_name = 课程         verbose_name_plural = 课程      def __str__(self):         return %s % self.name   # 课程章节 class CourseChapter(BaseModel):     # 一对多,写在多的一方     course = models.ForeignKey(Course, related_name='coursechapters', on_delete=models.CASCADE, verbose_name=课程名称)     # 章节数字--->第几章     chapter = models.SmallIntegerField(verbose_name=第几章, default=1)     # 章节标题     name = models.CharField(max_length=128, verbose_name=章节标题)     # 章节介绍     summary = models.TextField(verbose_name=章节介绍, blank=True, null=True)     # 发布日期     pub_date = models.DateField(verbose_name=发布日期, auto_now_add=True)      class Meta:         db_table = luffy_course_chapter         verbose_name = 章节         verbose_name_plural = verbose_name      def __str__(self):         return %s:(第%s章)%s % (self.course, self.chapter, self.name)   # 课时表 class CourseSection(BaseModel):     课时     section_type_choices = (         (0, '文档'),         (1, '练习'),         (2, '视频')     )     # 跟章节一对多,关联字段写在多的一方     chapter = models.ForeignKey(CourseChapter, related_name='coursesections', on_delete=models.CASCADE,                                 verbose_name=课程章节)      # 课时名     name = models.CharField(max_length=128, verbose_name=课时标题)     # 重写字段     orders = models.PositiveSmallIntegerField(verbose_name=课时排序)     # 课时种类:视频,文档,练习     section_type = models.SmallIntegerField(default=2, choices=section_type_choices, verbose_name=课时种类)     # 课时链接:视频地址,文档地址     section_link = models.CharField(max_length=255, blank=True, null=True, verbose_name=课时链接,                                     help_text=若是video,填vid,若是文档,填link)     # 视频时长 ,仅在前端展示使用     duration = models.CharField(verbose_name=视频时长, blank=True, null=True, max_length=32)     # 发布时间     pub_date = models.DateTimeField(verbose_name=发布时间, auto_now_add=True)     # 是否可试看  允许免费看几个视频     free_trail = models.BooleanField(verbose_name=是否可试看, default=False)      class Meta:         db_table = luffy_course_section         verbose_name = 课时         verbose_name_plural = verbose_name      def __str__(self):         return %s-%s % (self.chapter, self.name)   # 老师表  class Teacher(BaseModel):     导师     role_choices = (         (0, '讲师'),         (1, '导师'),         (2, '班主任'),     )     # 老师名     name = models.CharField(max_length=32, verbose_name=导师名)     # 老师身份---》讲师,导师,班主任     role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name=导师身份)     # 职位、职称     title = models.CharField(max_length=64, verbose_name=职位、职称)     # 导师签名     signature = models.CharField(max_length=255, verbose_name=导师签名, help_text=导师签名, blank=True, null=True)     # 老师图片     image = models.ImageField(upload_to=teacher, null=True, verbose_name=导师封面)     # 导师描述-->很详细-->html     brief = models.TextField(max_length=1024, verbose_name=导师描述)      class Meta:         db_table = luffy_teacher         verbose_name = 导师         verbose_name_plural = verbose_name      def __str__(self):         return %s % self.name 

迁移数据:

# 迁移数据库: 	python manage.py makemigrations 	python manage.py migrate       # course/admin.py中写入: from .models import *  admin.site.register(Teacher) admin.site.register(CourseCategory) admin.site.register(Course) admin.site.register(CourseChapter) admin.site.register(CourseSection) 

image

3、课程页面数据录入

3.1 老师表数据

INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (1, 1, 1, 0, '2019-07-14 13:44:19.661327', '2019-07-14 13:46:54.246271', 'Alex', 1, '老男孩Python教学总监', '金角大王', 'teacher/alex_icon.png', '老男孩教育CTO & CO-FOUNDER 国内知名PYTHON语言推广者 51CTO学院2016\2017年度最受学员喜爱10大讲师之一 多款开源软件作者 曾任职公安部、飞信、中金公司、NOKIA中国研究院、华尔街英语、ADVENT、汽车之家等公司');  INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (2, 2, 1, 0, '2019-07-14 13:45:25.092902', '2019-07-14 13:45:25.092936', 'Mjj', 0, '前美团前端项目组架构师', NULL, 'teacher/mjj_icon.png', '是马JJ老师, 一个集美貌与才华于一身的男人,搞过几年IOS,又转了前端开发几年,曾就职于美团网任高级前端开发,后来因为不同意王兴(美团老板)的战略布局而出家做老师去了,有丰富的教学经验,开起车来也毫不含糊。一直专注在前端的前沿技术领域。同时,爱好抽烟、喝酒、烫头(锡纸烫)。 我的最爱是前端,因为前端妹子多。');  INSERT INTO luffy_teacher(id, orders, is_show, is_delete, created_time, updated_time, name, role, title, signature, image, brief) VALUES (3, 3, 1, 0, '2019-07-14 13:46:21.997846', '2019-07-14 13:46:21.997880', 'Lyy', 0, '老男孩Linux学科带头人', NULL, 'teacher/lyy_icon.png', 'Linux运维技术专家,老男孩Linux金牌讲师,讲课风趣幽默、深入浅出、声音洪亮到爆炸'); 

image

3.2 分类表数据

INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (1, 1, 1, 0, '2019-07-14 13:40:58.690413', '2019-07-14 13:40:58.690477', 'Python');  INSERT INTO luffy_course_category(id, orders, is_show, is_delete, created_time, updated_time, name) VALUES (2, 2, 1, 0, '2019-07-14 13:41:08.249735', '2019-07-14 13:41:08.249817', 'Linux'); 

3.3 课程表数据

INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (1, 1, 1, 0, '2019-07-14 13:54:33.095201', '2019-07-14 13:54:33.095238', 'Python开发21天入门', 'courses/alex_python.png', 0, 'Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土&&&Python从入门到入土', 0, '2019-07-14', 21, '', 0, 231, 120, 120, 0.00, 1, 1);  INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (2, 2, 1, 0, '2019-07-14 13:56:05.051103', '2019-07-14 13:56:05.051142', 'Python项目实战', 'courses/mjj_python.png', 0, '', 1, '2019-07-14', 30, '', 0, 340, 120, 120, 99.00, 1, 2);  INSERT INTO luffy_course(id, orders, is_show, is_delete, created_time, updated_time, name, course_img, course_type, brief, level, pub_date, period, attachment_path, status, students, sections, pub_sections, price, course_category_id, teacher_id) VALUES (3, 3, 1, 0, '2019-07-14 13:57:21.190053', '2019-07-14 13:57:21.190095', 'Linux系统基础5周入门精讲', 'courses/lyy_linux.png', 0, '', 0, '2019-07-14', 25, '', 0, 219, 100, 100, 39.00, 2, 3); 

3.4 章节表数据

INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (1, 1, 1, 0, '2019-07-14 13:58:34.867005', '2019-07-14 14:00:58.276541', 1, '计算机原理', '', '2019-07-14', 1);  INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (2, 2, 1, 0, '2019-07-14 13:58:48.051543', '2019-07-14 14:01:22.024206', 2, '环境搭建', '', '2019-07-14', 1);  INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (3, 3, 1, 0, '2019-07-14 13:59:09.878183', '2019-07-14 14:01:40.048608', 1, '项目创建', '', '2019-07-14', 2);  INSERT INTO luffy_course_chapter(id, orders, is_show, is_delete, created_time, updated_time, chapter, name, summary, pub_date, course_id) VALUES (4, 4, 1, 0, '2019-07-14 13:59:37.448626', '2019-07-14 14:01:58.709652', 1, 'Linux环境创建', '', '2019-07-14', 3); 

3.5 课时表数据

INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (1, 1, 0, '2019-07-14 14:02:33.779098', '2019-07-14 14:02:33.779135', '计算机原理上', 1, 2, NULL, NULL, '2019-07-14 14:02:33.779193', 1, 1);  INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (2, 1, 0, '2019-07-14 14:02:56.657134', '2019-07-14 14:02:56.657173', '计算机原理下', 2, 2, NULL, NULL, '2019-07-14 14:02:56.657227', 1, 1);  INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (3, 1, 0, '2019-07-14 14:03:20.493324', '2019-07-14 14:03:52.329394', '环境搭建上', 1, 2, NULL, NULL, '2019-07-14 14:03:20.493420', 0, 2);  INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (4, 1, 0, '2019-07-14 14:03:36.472742', '2019-07-14 14:03:36.472779', '环境搭建下', 2, 2, NULL, NULL, '2019-07-14 14:03:36.472831', 0, 2);  INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (5, 1, 0, '2019-07-14 14:04:19.338153', '2019-07-14 14:04:19.338192', 'web项目的创建', 1, 2, NULL, NULL, '2019-07-14 14:04:19.338252', 1, 3);  INSERT INTO luffy_course_Section(id, is_show, is_delete, created_time, updated_time, name, orders, section_type, section_link, duration, pub_date, free_trail, chapter_id) VALUES (6, 1, 0, '2019-07-14 14:04:52.895855', '2019-07-14 14:04:52.895890', 'Linux的环境搭建', 1, 2, NULL, NULL, '2019-07-14 14:04:52.895942', 1, 4); 

4、课程分类接口

创建序列化类:course/serializer.py

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

视图类course/views.py中写:

from .models import CourseCategory, Course 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 

路由:course/urls.py中写入:

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

image

5、课程列表接口

路由:course/urls.py中添加:

from .views import CourseCategoryView, CourseView   # http://127.0.0.1:8000/api/v1/course/actual/ router.register('actual', CourseView, 'actual') 

在视图类 course/views.py中添加:

from .pagination import CommonPageNumberPagination from rest_framework.filters import OrderingFilter # 安装第三方模块 pip install django-filter 并到配置文件app中注册 from django_filters.rest_framework import DjangoFilterBackend   class CourseView(GenericViewSet, ListModelMixin):     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'] 

在序列化类中course/serializer.py:

from rest_framework import serializers from .models import CourseCategory, Course, Teacher   class CourseCategorySerializer(serializers.ModelSerializer):     class Meta:         model = CourseCategory         fields = ['id', 'name']   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表中没有---》重写:序列类写,表模型中写         ] 

在 course/models.py中补充:

# 实战课表 class Course(BaseModel):     ......          def status_name(self):         return self.get_status_display()      @property     def section_list(self):         sections = []          # 如果课时小于等于四条,返回总课时,如果大于4条,最多返回4条         # 第一步:通过课程拿到所有章节         # course_chapter_list=self.coursechapter_set.all()  # 不需要         course_chapter_list = self.coursechapters.all()          # 第二步:循环所有章节         for course_chapter in course_chapter_list:              # 第三步:通过章节,拿到该章节的所有课时             course_section_list = course_chapter.coursesections.all()              # 第四步:循环取出所有章节,追加到一个列表中,准备返回             for course_section in course_section_list:                 sections.append({                     'name': course_section.name,                     'section_link': course_section.section_link,                     'duration': course_section.duration,                     'free_trail': course_section.free_trail,                 })                 if len(sections) == 4:                     return sections         # 在for循环外层         return sections                # 老师表 class Teacher(BaseModel):     导师     .......          def role_name(self):         return self.get_role_display() 

新建分页类 course/pagination.py:

from rest_framework.pagination import PageNumberPagination   class CommonPageNumberPagination(PageNumberPagination):     page_size = 2     page_query_param = 'page'     page_size_query_param = 'page_size'     max_page_size = 5 

image

6、课程页面前后端调通

以实战课程为例 ActualCourse.vue:

<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='/free/detail/'+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> 

image

7、课程详情页面(点击课程进入详情页面)

#课程详情页取出课程id号 this.course_id=this.$route.params.id // console.log(this.$route) // 当前路径对象 // console.log(this.$router)// 路由对象   # 当前页面需要视频播放视频---》视频播放组件 npm install vue-video-player  # main.js中配置 // vue-video播放器 require('video.js/dist/video-js.css'); require('vue-video-player/src/custom-theme.css'); import VideoPlayer from 'vue-video-player' Vue.use(VideoPlayer);  # 在index.js中 添加路由:      {         path: '/actual/detail/:id',         name: 'CourseDetail',         component: CourseDetail     }, 

修改views/ActualCourse.vue:

<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/'+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> 

新建详情页views/CourseDetail.vue:

<template>   <div class=detail>     <Header/>     <div class=main>       <div class=course-info>         <div class=wrap-left>           <!--                    <videoPlayer class=video-player vjs-custom-skin-->           <!--                                 ref=videoPlayer-->           <!--                                 :playsinline=true-->           <!--                                 :options=playerOptions-->           <!--                                 @play=onPlayerPlay($event)-->           <!--                                 @pause=onPlayerPause($event)>-->           <!--                    </videoPlayer>-->         </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_text></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  // 加载组件 // import {videoPlayer} from 'vue-video-player';  export default {   name: Detail,   data() {     return {       tabIndex: 2,   // 当前选项卡显示的下标       course_id: 0, // 当前课程信息的ID       course_info: {         teacher: {},       }, // 课程信息       course_chapters: [], // 课程的章节课时列表       playerOptions: {         aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如16:9或4:3)         sources: [{ // 播放资源和资源格式           type: video/mp4,           src: http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4 //你的视频地址(必填)         }],       }     }   },   created() {     this.get_course_id();     this.get_course_data();     this.get_chapter();   },   methods: {     // onPlayerPlay() {     //     // 当视频播放时,执行的方法     //     console.log('视频开始播放')     // },     // onPlayerPause() {     //     // 当视频暂停播放时,执行的方法     //     console.log('视频暂停,可以打开广告了')     // },     get_course_id() {       // 获取地址栏上面的课程ID       this.course_id = this.$route.params.pk || this.$route.query.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/free/${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/chapters/`, {         params: {           course: this.course_id,         }       }).then(response => {         this.course_chapters = response.data;       }).catch(error => {         window.console.log(error.response);       })     },   },   components: {     Header,     Footer,     // videoPlayer, // 注册组件   } } </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> 

image