项目3
课程详情页
pip install django-ckeditor
INSTALLED_APPS = [ ... 'ckeditor', # 富文本编辑器 'ckeditor_uploader', # 富文本编辑器上传图片模块 ... ]

# 富文本编辑器ckeditor配置 CKEDITOR_CONFIGS = { 'default': { 'toolbar': 'full', # 工具条功能 'height': 300, # 编辑器高度 # 'width': 300, # 编辑器宽 }, } CKEDITOR_UPLOAD_PATH = '' # 上传图片保存路径,留空则调用django的文件上传功能
path(r'^ckeditor/', include('ckeditor_uploader.urls')),

from ckeditor_uploader.fields import RichTextUploadingField class Course(models.Model): """ 专题课程 """ ... brief = RichTextUploadingField(max_length=2048, verbose_name="课程概述", null=True, blank=True)
效果:
详情页Detail.vue组件代码:

<template> <div class="detail"> <Header/> <div class="main"> <div class="course-info"> <div class="wrap-left"> </div> <div class="wrap-right"> <h3 class="course-name">Linux系统基础5周入门精讲</h3> <p class="data">23475人在学 课程总时长:148课时/180小时 难度:初级</p> <div class="sale-time"> <p class="sale-type">限时免费</p> <p class="expire">距离结束:仅剩 01天 04小时 33分 <span class="second">08</span> 秒</p> </div> <p class="course-price"> <span>活动价</span> <span class="discount">¥0.00</span> <span class="original">¥29.00</span> </p> <div class="buy"> <div class="buy-btn"> <button class="buy-now">立即购买</button> <button class="free">免费试学</button> </div> <div class="add-cart"><img src="/static/image/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">用户评论 (42)</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"> <p><img alt="" src="https://hcdn1.luffycity.com/static/frontend/course/5/21天01_1547098127.6672518.jpeg" width="840"></p> <p><img alt="" src="https://hcdn1.luffycity.com/static/frontend/course/5/21天01_1547098127.6672518.jpeg" width="840"></p> <p><img alt="" src="https://hcdn1.luffycity.com/static/frontend/course/5/21天01_1547098127.6672518.jpeg" width="840"></p> </div> <div class="tab-item" v-if="tabIndex==2"> <div class="tab-item-title"> <p class="chapter">课程章节</p> <p class="chapter-length">共11章 147个课时</p> </div> <div class="chapter-item"> <p class="chapter-title"><img src="/static/image/1.svg" alt="">第1章·Linux硬件基础</p> <ul class="lesson-list"> <li class="lesson-item"> <p class="name"><span class="index">1-1</span> 课程介绍-学习流程<span class="free">免费</span></p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即试学</button> </li> <li class="lesson-item"> <p class="name"><span class="index">1-2</span> 服务器硬件-详解<span class="free">免费</span></p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即试学</button> </li> </ul> </div> <div class="chapter-item"> <p class="chapter-title"><img src="/static/image/1.svg" alt="">第2章·Linux发展过程</p> <ul class="lesson-list"> <li class="lesson-item"> <p class="name"><span class="index">2-1</span> 操作系统组成-Linux发展过程</p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即购买</button> </li> <li class="lesson-item"> <p class="name"><span class="index">2-2</span> 自由软件-GNU-GPL核心讲解</p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即购买</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="/static/image/马金聚pc2x_1560859575.8268683.png"> <div class="name"> <p class="teacher-name">李泳谊</p> <p class="teacher-title">老男孩LInux学科带头人</p> </div> </div> <p class="narrative" >Linux运维技术专家,老男孩Linux金牌讲师,讲课风趣幽默、深入浅出、声音洪亮到爆炸</p> </div> </div> </div> </div> </div> <Footer/> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Detail", data(){ return { tabIndex:2, // 当前选项卡显示的下标 } }, methods: { }, 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; } .lesson-list{ padding:0 20px; } .lesson-list .lesson-item{ padding: 15px 20px 15px 36px; cursor: pointer; justify-content: space-between; position: relative; overflow: hidden; } .lesson-item .name{ font-size: 14px; color: #666; float: left; } .lesson-item .index{ margin-right: 5px; } .lesson-item .free{ font-size: 12px; color: #fff; letter-spacing: .19px; background: #ffc210; border-radius: 100px; padding: 1px 9px; margin-left: 10px; } .lesson-item .time{ font-size: 14px; color: #666; letter-spacing: .23px; opacity: 1; transition: all .15s ease-in-out; float: right; } .lesson-item .time img{ width: 18px; height: 18px; margin-left: 15px; vertical-align: text-bottom; } .lesson-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; } .lesson-item:hover{ background: #fcf7ef; box-shadow: 0 0 0 0 #f3f3f3; } .lesson-item:hover .name{ color: #333; } .lesson-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>
路由显示,代码:

import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) // @ 表示src目录 import Home from "@/components/Home" import Login from "@/components/Login" import Register from "@/components/Register" import Course from "@/components/Course" import Detail from "@/components/Detail" # import AliPlayer from "@/components/AliPlayer" export default new Router({ mode:"history", routes: [ // ...., { path: '/courses/:id', //在路由的路径中传递参数/courses/4 //查询字符串 courses/detail?id=4 name: 'Detail', component: Detail, }, ] })
Course.vue课程列表的组件中, 打通点击前往详情页的链接地址:

<h3><router-link :to="`/courses/${course.id}`">{{course.name}}</router-link> <span><img src="/static/image/avatar1.svg" alt="">{{course.students}}人已加入学习</span></h3>
安装依赖
npm install vue-video-player --save
在main.js中注册加载组件
require('video.js/dist/video-js.css'); require('vue-video-player/src/custom-theme.css'); import VideoPlayer from 'vue-video-player' Vue.use(VideoPlayer);
Detail.vue组件中内置vue-video播放器,代码:
分四个步骤:
-
通过import 导入videoPlayer组件
-
在vue组件中的components,注册播放器组件
-
在html中调用videoPlayer
-
在data中配置播放器的相关参数

<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)" /> </div> <div class="wrap-right"> <h3 class="course-name">Linux系统基础5周入门精讲</h3> <p class="data">23475人在学 课程总时长:148课时/180小时 难度:初级</p> <div class="sale-time"> <p class="sale-type">限时免费</p> <p class="expire">距离结束:仅剩 01天 04小时 33分 <span class="second">08</span> 秒</p> </div> <p class="course-price"> <span>活动价</span> <span class="discount">¥0.00</span> <span class="original">¥29.00</span> </p> <div class="buy"> <div class="buy-btn"> <button class="buy-now">立即购买</button> <button class="free">免费试学</button> </div> <div class="add-cart"><img src="/static/image/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">用户评论 (42)</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"> <p><img alt="" src="https://hcdn1.luffycity.com/static/frontend/course/5/21天01_1547098127.6672518.jpeg" width="840"></p> <p><img alt="" src="https://hcdn1.luffycity.com/static/frontend/course/5/21天01_1547098127.6672518.jpeg" width="840"></p> <p><img alt="" src="https://hcdn1.luffycity.com/static/frontend/course/5/21天01_1547098127.6672518.jpeg" width="840"></p> </div> <div class="tab-item" v-if="tabIndex==2"> <div class="tab-item-title"> <p class="chapter">课程章节</p> <p class="chapter-length">共11章 147个课时</p> </div> <div class="chapter-item"> <p class="chapter-title"><img src="/static/image/1.svg" alt="">第1章·Linux硬件基础</p> <ul class="lesson-list"> <li class="lesson-item"> <p class="name"><span class="index">1-1</span> 课程介绍-学习流程<span class="free">免费</span></p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即试学</button> </li> <li class="lesson-item"> <p class="name"><span class="index">1-2</span> 服务器硬件-详解<span class="free">免费</span></p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即试学</button> </li> </ul> </div> <div class="chapter-item"> <p class="chapter-title"><img src="/static/image/1.svg" alt="">第2章·Linux发展过程</p> <ul class="lesson-list"> <li class="lesson-item"> <p class="name"><span class="index">2-1</span> 操作系统组成-Linux发展过程</p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即购买</button> </li> <li class="lesson-item"> <p class="name"><span class="index">2-2</span> 自由软件-GNU-GPL核心讲解</p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即购买</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="/static/image/马金聚pc2x_1560859575.8268683.png"> <div class="name"> <p class="teacher-name">李泳谊</p> <p class="teacher-title">老男孩LInux学科带头人</p> </div> </div> <p class="narrative" >Linux运维技术专家,老男孩Linux金牌讲师,讲课风趣幽默、深入浅出、声音洪亮到爆炸</p> </div> </div> </div> </div> </div> <Footer/> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" // 1.加载播放器组件 import {videoPlayer} from 'vue-video-player'; export default { name: "Detail", data(){ return { tabIndex:2, // 当前选项卡显示的下标 playerOptions:{ playbackRates: [0.7, 1.0, 1.5, 2.0], // 播放速度 autoplay: false, //如果true,则自动播放 muted: false, // 默认情况下将会消除任何音频。 loop: false, // 循环播放 preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) language: 'zh-CN', aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 sources: [{ // 播放资源和资源格式 type: "video/mp4", src: "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4" //你的视频地址(必填) }], poster: "../static/image/course-cover.jpeg", //视频封面图 width: document.documentElement.clientWidth, // 默认视频全屏时的最大宽度 notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。 } } }, created() { // 接受地址路径参数 // console.log( '路径参数: id=',this.$route.params.id ); // 接受地址的查询字符串参数 // console.log( '查询字符串: uid=', this.$route.query.uid ); }, methods: { onPlayerPlay(){ alert("开始播放视频,关闭广告"); }, onPlayerPause(){ alert("暂停播放广告"); } }, 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; } .lesson-list{ padding:0 20px; } .lesson-list .lesson-item{ padding: 15px 20px 15px 36px; cursor: pointer; justify-content: space-between; position: relative; overflow: hidden; } .lesson-item .name{ font-size: 14px; color: #666; float: left; } .lesson-item .index{ margin-right: 5px; } .lesson-item .free{ font-size: 12px; color: #fff; letter-spacing: .19px; background: #ffc210; border-radius: 100px; padding: 1px 9px; margin-left: 10px; } .lesson-item .time{ font-size: 14px; color: #666; letter-spacing: .23px; opacity: 1; transition: all .15s ease-in-out; float: right; } .lesson-item .time img{ width: 18px; height: 18px; margin-left: 15px; vertical-align: text-bottom; } .lesson-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; } .lesson-item:hover{ background: #fcf7ef; box-shadow: 0 0 0 0 #f3f3f3; } .lesson-item:hover .name{ color: #333; } .lesson-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>
模型代码:
class Course(BaseModel): """ 实战课程 """ # .... level_choices = ( (0, '初级'), (1, '中级'), (2, '高级'), ) # 省略。。。 @property def level_name(self): '''难度等级的文本格式''' return self.level_choices[self.level][1]
序列化器代码:
# 把原来的讲师序列化器增加几个字段 class TeacherModelSerializer(serializers.ModelSerializer): """讲师导师序列化器""" class Meta: model = Teacher fields = ["id","image","brief","name","role_name","title","signature"] class CourseRetrieveModelSerializer(serializers.ModelSerializer): teacher = TeacherModelSerializer() class Meta: model = Course fields = ["id","name","course_img","course_video","students","lessons","pub_lessons","price","teacher","brief","level_name"]
视图代码:
from rest_framework.generics import RetrieveAPIView from .serializers import CourseRetrieveModelSerializer class CourseRetrieveAPIView(RetrieveAPIView): queryset = Course.objects.filter(is_delete=False, is_show=True) serializer_class = CourseRetrieveModelSerializer
路由代码:
re_path("(?P<pk>\d+)/", views.CourseRetrieveAPIView.as_view()),

<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)" /> </div> <div class="wrap-right"> <h3 class="course-name">{{course.name}}</h3> <p class="data">{{course.students}}人在学 课程总时长:{{course.lessons}}课时/{{course.lessons==course.pub_lessons?'更新完成':`已更新${course.pub_lessons}课时`}} 难度:初级</p> <div class="sale-time"> <p class="sale-type">限时免费</p> <p class="expire">距离结束:仅剩 01天 04小时 33分 <span class="second">08</span> 秒</p> </div> <p class="course-price"> <span>活动价</span> <span class="discount">¥0.00</span> <span class="original">¥{{course.price}}</span> </p> <div class="buy"> <div class="buy-btn"> <button class="buy-now">立即购买</button> <button class="free">免费试学</button> </div> <div class="add-cart"><img src="/static/image/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">用户评论 (42)</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 v-html="course.brief"></div> </div> <div class="tab-item" v-if="tabIndex==2"> <div class="tab-item-title"> <p class="chapter">课程章节</p> <p class="chapter-length">共11章 147个课时</p> </div> <div class="chapter-item"> <p class="chapter-title"><img src="/static/image/1.svg" alt="">第1章·Linux硬件基础</p> <ul class="lesson-list"> <li class="lesson-item"> <p class="name"><span class="index">1-1</span> 课程介绍-学习流程<span class="free">免费</span></p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即试学</button> </li> <li class="lesson-item"> <p class="name"><span class="index">1-2</span> 服务器硬件-详解<span class="free">免费</span></p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即试学</button> </li> </ul> </div> <div class="chapter-item"> <p class="chapter-title"><img src="/static/image/1.svg" alt="">第2章·Linux发展过程</p> <ul class="lesson-list"> <li class="lesson-item"> <p class="name"><span class="index">2-1</span> 操作系统组成-Linux发展过程</p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即购买</button> </li> <li class="lesson-item"> <p class="name"><span class="index">2-2</span> 自由软件-GNU-GPL核心讲解</p> <p class="time">07:30 <img src="/static/image/chapter-player.svg"></p> <button class="try">立即购买</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="/static/image/马金聚pc2x_1560859575.8268683.png"> <div class="name"> <p class="teacher-name">李泳谊</p> <p class="teacher-title">老男孩LInux学科带头人</p> </div> </div> <p class="narrative" >Linux运维技术专家,老男孩Linux金牌讲师,讲课风趣幽默、深入浅出、声音洪亮到爆炸</p> </div> </div> </div> </div> </div> <Footer/> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" // 1.加载播放器组件 import {videoPlayer} from 'vue-video-player'; export default { name: "Detail", data(){ return { course_id: 0, // 课程ID course: {}, // 课程信息 tabIndex:2, // 当前选项卡显示的下标 playerOptions:{ playbackRates: [0.7, 1.0, 1.5, 2.0], // 播放速度 autoplay: false, //如果true,则自动播放 muted: false, // 默认情况下将会消除任何音频。 loop: false, // 循环播放 preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) language: 'zh-CN', aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 sources: [{ // 播放资源和资源格式 type: "video/mp4", src: "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4" //你的视频地址(必填) }], poster: "../static/image/course-cover.jpeg", //视频封面图 width: document.documentElement.clientWidth, // 默认视频全屏时的最大宽度 notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。 } } }, created(){ // 接受地址路径参数 // console.log( '路径参数: id=',this.$route.params.id ); // 接受地址的查询字符串参数 // console.log( '查询字符串: uid=', this.$route.query.uid ); this.course_id = this.$route.params.id; this.get_course(); }, methods: { onPlayerPlay(){ alert("开始播放视频,关闭广告"); }, onPlayerPause(){ alert("暂停播放广告"); }, get_course(){ // 获取课程信息 this.$axios.get(`${this.$settings.Host}/courses/${this.course_id}/`).then(response=>{ this.course = response.data; }).catch(error=>{ let self = this; this.$alert("无法获取当前课程信息,请联系客服工作人员","路飞学城",{ callback(){ self.$router.back(); // 等同于 self.$router.go(-1); } }); }) } }, 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; } .lesson-list{ padding:0 20px; } .lesson-list .lesson-item{ padding: 15px 20px 15px 36px; cursor: pointer; justify-content: space-between; position: relative; overflow: hidden; } .lesson-item .name{ font-size: 14px; color: #666; float: left; } .lesson-item .index{ margin-right: 5px; } .lesson-item .free{ font-size: 12px; color: #fff; letter-spacing: .19px; background: #ffc210; border-radius: 100px; padding: 1px 9px; margin-left: 10px; } .lesson-item .time{ font-size: 14px; color: #666; letter-spacing: .23px; opacity: 1; transition: all .15s ease-in-out; float: right; } .lesson-item .time img{ width: 18px; height: 18px; margin-left: 15px; vertical-align: text-bottom; } .lesson-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; } .lesson-item:hover{ background: #fcf7ef; box-shadow: 0 0 0 0 #f3f3f3; } .lesson-item:hover .name{ color: #333; } .lesson-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>
上面的其他数据均可正常显示,但是在显示详情介绍的时候,无法显示图片
样式重置,static/css/reset.css,代码:
.course-brief img{ width: 100%; }
后端提供当前课程对应的章节和课时列表信息

from .models import CourseChapter,CourseLesson class CourseLessonModelSerializer(serializers.ModelSerializer): class Meta: model = CourseLesson fields = ["id", "name", "free_trail","duration"] class CourseChapterModelSerializer(serializers.ModelSerializer): lessons = CourseLessonModelSerializer(many=True) class Meta: model = CourseChapter fields = ["chapter","name","summary","lessons"]
courses/views.py视图,代码:
from rest_framework.generics import ListAPIView from .models import CourseChapter from .serializers import CourseChapterModelSerializer class CourseChapterListAPIView(ListAPIView): queryset = CourseChapter.objects.filter(is_show=True, is_delete=False).order_by("chapter") serializer_class = CourseChapterModelSerializer filter_backends = [DjangoFilterBackend] filter_fields = ['course', ]
courses/urls.py路由,代码:
path("chapters/", views.CourseChapterListAPIView.as_view()),

<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)" /> </div> <div class="wrap-right"> <h3 class="course-name">{{course.name}}</h3> <p class="data">{{course.students}}人在学 课程总时长:{{course.lessons}}课时/{{course.lessons==course.pub_lessons?'更新完成':`已更新${course.pub_lessons}课时`}} 难度:初级</p> <div class="sale-time"> <p class="sale-type">限时免费</p> <p class="expire">距离结束:仅剩 01天 04小时 33分 <span class="second">08</span> 秒</p> </div> <p class="course-price"> <span>活动价</span> <span class="discount">¥0.00</span> <span class="original">¥{{course.price}}</span> </p> <div class="buy"> <div class="buy-btn"> <button class="buy-now">立即购买</button> <button class="free">免费试学</button> </div> <div class="add-cart"><img src="/static/image/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">用户评论 (42)</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 v-html="url_format(course.brief)"></div> </div> <div class="tab-item" v-if="tabIndex==2"> <div class="tab-item-title"> <p class="chapter">课程章节</p> <p class="chapter-length">共11章 147个课时</p> </div> <div class="chapter-item" v-for="chapter in chapter_list"> <p class="chapter-title"><img src="/static/image/1.svg" alt="">第{{chapter.chapter}}章·{{chapter.name}}</p> <ul class="lesson-list"> <li class="lesson-item" v-for="lesson in chapter.lessons"> <p class="name"><span class="index">{{chapter.chapter}}-{{lesson.id}}</span> {{lesson.name}}<span class="free" v-if="lesson.free_trail">免费</span></p> <p class="time">{{lesson.duration}} <img src="/static/image/chapter-player.svg"></p> <button class="try" v-if="lesson.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="teacher.image"> <div class="name"> <p class="teacher-name">{{teacher.name}}</p> <p class="teacher-title">{{teacher.title}} {{teacher.signature}}</p> </div> </div> <p class="narrative" >{{teacher.brief}}</p> </div> </div> </div> </div> </div> <Footer/> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" // 1.加载播放器组件 import {videoPlayer} from 'vue-video-player'; export default { name: "Detail", data(){ return { course_id: 0, // 课程ID course: {}, // 课程信息 teacher:'', chapter_list: [], // 章节列表 tabIndex:2, // 当前选项卡显示的下标 playerOptions:{ playbackRates: [0.7, 1.0, 1.5, 2.0], // 播放速度 autoplay: false, //如果true,则自动播放 muted: false, // 默认情况下将会消除任何音频。 loop: false, // 循环播放 preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) language: 'zh-CN', aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 sources: [{ // 播放资源和资源格式 type: "video/mp4", src: "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4" //你的视频地址(必填) }], poster: "../static/image/course-cover.jpeg", //视频封面图 width: document.documentElement.clientWidth, // 默认视频全屏时的最大宽度 notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。 } } }, created(){ // 接受地址路径参数 // console.log( '路径参数: id=',this.$route.params.id ); // 接受地址的查询字符串参数 // console.log( '查询字符串: uid=', this.$route.query.uid ); this.course_id = this.$route.params.id; this.get_course(); this.get_chapter(); }, methods: { onPlayerPlay(){ alert("开始播放视频,关闭广告"); }, onPlayerPause(){ alert("暂停播放广告"); }, get_course(){ // 获取课程信息 this.$axios.get(`${this.$settings.Host}/courses/${this.course_id}/`).then(response=>{ this.course = response.data; this.teacher=response.data.teacher }).catch(error=>{ let self = this; this.$alert("无法获取当前课程信息,请联系客服工作人员","路飞学城",{ callback(){ self.$router.back(); // 等同于 self.$router.go(-1); } }); }) }, get_chapter(){ // 获取课程相关的章节课时信息 this.$axios.get(`${this.$settings.Host}/courses/chapters/`,{ // params:{ // "course": this.course_id, // } }).then(response=>{ this.chapter_list = response.data; console.log(this.chapter_list); alert("hjsaiodfj") }).catch(error=>{ this.$message.info("没有获取到当前课程的章节信息"); }); }, url_format(data){ console.log(data); while( data.search('="/media') != -1 ){ data = data.replace('="/media',`="${this.$settings.Host}/media`); } return data; } }, 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; } .lesson-list{ padding:0 20px; } .lesson-list .lesson-item{ padding: 15px 20px 15px 36px; cursor: pointer; justify-content: space-between; position: relative; overflow: hidden; } .lesson-item .name{ font-size: 14px; color: #666; float: left; } .lesson-item .index{ margin-right: 5px; } .lesson-item .free{ font-size: 12px; color: #fff; letter-spacing: .19px; background: #ffc210; border-radius: 100px; padding: 1px 9px; margin-left: 10px; } .lesson-item .time{ font-size: 14px; color: #666; letter-spacing: .23px; opacity: 1; transition: all .15s ease-in-out; float: right; } .lesson-item .time img{ width: 18px; height: 18px; margin-left: 15px; vertical-align: text-bottom; } .lesson-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; } .lesson-item:hover{ background: #fcf7ef; box-shadow: 0 0 0 0 #f3f3f3; } .lesson-item:hover .name{ color: #333; } .lesson-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>