目录

1.初始课程详情页面

2.视频播放组件

3.课程详情页面后端接口实现

4.课程详情页面-前端

5.CKEditor富文本编辑器

6.课程章节和课时显示-后端接口

7.课程章节和课时显示-前端

1.初始课程详情页面

1.Detail.vue

<!-- 课程详情页面初始页面 -->
<template>
<div class="detail">
<Vheader/>
<div class="main">
<div class="course-info">
<div class="wrap-left"> </div>
<div class="wrap-right">
<h3 class="course-name">flask</h3>
<p class="data">111人在学&nbsp;&nbsp;&nbsp;&nbsp;课程总时长:111课时/12小时&nbsp;&nbsp;&nbsp;&nbsp;难度:</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">¥1111</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/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">用户评论 (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 class="course-brief" v-html=""></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/img/1.png" 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/img/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/img/chapter-player.svg"></p>
<button class="try">立即试学</button>
</li>
</ul>
</div>
<div class="chapter-item">
<p class="chapter-title"><img src="/static/img/1.png" 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/img/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/img/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="">
<div class="name">
<p class="teacher-name">xxx</p>
<p class="teacher-title">ssss</p>
</div>
</div>
<p class="narrative" >kkkk</p>
</div>
</div>
</div>
</div>
</div>
<Footer/>
</div>
</template> <script>
import Vheader from "./common/Vheader"
import Footer from "./common/Footer" export default {
name: "Detail",
data(){
return {
tabIndex:1,
}
},
created(){ },
methods: { },
components:{
Vheader,
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>

初始课程详情页面

2.index.js注册组件

import Detail from "@/components/Detail"
{
path:'/course/detail/:id', // 前端页面动态路由匹配
component:Detail
} // :id ===> this.$route.params.id // course/detail/1

3.course.vue

实现:在课程列表页面点击不同的课程可以进入到不同的课程详情页面

<h3><router-link :to="'/course/detail/'+course.id+'/'">django基础知识</router-link> <span><img src="/static/img/avatar1.svg" alt="">5000人已加入学习</span></h3>
           

此时 点击可进入课程详情页面

2.视频播放组件

1.安装

npm install vue-video-player --save

2.main.js注册组件

// 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);

3.Detail.vue引入

HTML部分

<!-- html -->
<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>

JS部分

// js
import {VideoPlayer} from 'vue-video-player'
data(){
return{
...
playerOptions: {
playbackRates: [0.7, 1.0, 1.5, 2.0], // 播放速度
autoplay: false, // 如果true,则自动播放
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 循环播放
preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [{ // 播放资源和资源格式
type: "video/mp4",
src: "" // 你的视频地址(必填)
}],
poster: "", // 视频封面图
width: document.documentElement.clientWidth, // 默认视频全屏时的最大宽度
notSupportedMessage: '此视频暂无法播放,请稍后再试', // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
} } } method:{
...
// 视频播放时触发此函数
onPlayerPlay:{
...
}
// 视频暂停时触发此函数
onPlayerPause:{
...
}
}
components:{
...
videoplayer // 挂载一下视频播放组件
}

4.在Xadmin上传视频

注意:课程详情页的那些视频其实要存到数据库里的,但是数据库并没有课程视频这个字段

所以需要在course表中添加一个course_video字段

# 将上传的视频保存在本地的video文件夹中
course_video = models.FileField(upload_to='video',verbose_name='封面视频',blank=True,null=True,max_length=255)

执行数据库迁移指令

python3 manage.py makemigrations
python3 manage.py migrate

在xadmin上传视频,即可在前端页面看到自己上传的视频

3.课程详情页面后端接口实现

接下来做的事情:在课程表里已经有了我们视频数据了,现在我们要想办法写一个后端接口将真实的课程详情数据返回到页面上,把真实的视频播放路径给前端,让前端展示出来。把真实的图片路径或者视频路径给前端,前端加载的时候会往后端发请求获取地址对应的视频数据,然后进行播放就可以了

urls.py

urlpatterns = [
......
re_path(r'detail/(?P<pk>\d+)/', views.CourseDetailView.as_view(),), ]

views.py

class CourseDetailView(RetrieveAPIView):
queryset = models.Course.objects.filter(is_deleted=False,is_show=True)
serializer_class = CourseDetailModelSerializer

models.py

class Course(BaseModel):
......
level_choices = (
(0, '初级'),
(1, '中级'),
(2, '高级'),
)
......
...... level = models.SmallIntegerField(choices=level_choices, default=1, verbose_name="难度等级") ...... def level_name(self):
'''level字段默认显示的是数字,通过返回get_字段_display可以返回数字对应的名字'''
return self.get_level_display()

serializers.py

class CourseDetailModelSerializer(serializers.ModelSerializer):
# 序列化器嵌套
teacher = TeacherModelSerializer() # 将外键关联的属性指定为关联表的序列化器对象,就能拿到关联表序列化出来的所有数据,还需要在fields中指定一下,注意,名称必须和外键属性名称相同 class Meta:
model = models.Course
fields = ["id", "name", "course_img", "students", "lessons", "pub_lessons", "price", "teacher",
"level_name", "course_video"]

后端接口测试

drf后端接口测试 /course/detail/1 可得到course=1所需要的所有数据

4.课程详情页面-前端

1.注意点

在课程列表页面,我们只展示了四个课时。但是在课程详情页我们要展示所有课时,所以不能用之前那个序列化器了。

现在我们是需要所有章节和所有课时信息、老师信息和课程信息。

如果将所有的信息都定义到一个序列化器的字段中,数据量有些太大。

我们可以利用axios可以发送异步请求的这个特点,分成两次请求来获取数据

将章节信息和课时信息放在一个序列化器中

其它的放在另一个序列化器中

我们先去请求除了章节信息和课时信息的其他信息

现在后端数据已经准备好了,接下来就是前端发送axios请求获取数据了

2.Detail.vue

<!-- html -->
<div class="wrap-right">
<h3 class="course-name">{{ course_data.name }}</h3>
<p class="data">{{course_data.students}}人在学&nbsp;&nbsp;&nbsp;&nbsp;课程总时长:{{course_data.lessons}}&nbsp;&nbsp;&nbsp;&nbsp;难度:{{course_data.level_name}}</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_data.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/img/cart-yellow.svg" alt="">加入购物车</div>
</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="">
<div class="name">
<p class="teacher-name">{{course_data.teacher.name}}</p>
<p class="teacher-title">{{course_data.teacher.title}}</p>
</div>
</div>
<p class="narrative" >{{course_data.teacher.signature}}</p>
</div>
</div>
</div> <!-- 视频播放 -->
<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>

前端将从后段获取的数据展示出来-HTML

// js

<script>

export default {
name: "Detail",
data(){
return {
......
course_id:0,
course_data:{
teacher:{}
},
playerOptions: {
......
sources: [{ // 播放资源和资源格式
type: "video/mp4",
src: "" // 你的视频地址(必填)
}],
poster: "", // 视频封面图
...... },
created(){
this.get_course_id();
this.get_course_data();
},
methods: { // 获取课程id,用处是请求不同的课程详情页面数据时带上不同的url参数来请求不同的课程详情数据
get_course_id(){
this.course_id = this.$route.params.id;
// 可以判断course_id的合法性 todo
}, get_course_data(){
this.$axios.get(`${this.$settings.Host}/course/detail/${this.course_id}/`)
.then((res)=>{
this.course_data = res.data; // 获取课程详情页数据
this.playerOptions.sources[0].src = res.data.course_video; // 获取视频数据
this.playerOptions.poster = res.data.course_img // 获取视频封面数据 })
}, }, }
</script>

前端发送axios请求获取后端数据-JS

5.CKEditor富文本编辑器

1.安装

pip install django-ckeditor

2.settings/dev.py INSTALLAPP配置

INSTALLED_APPS = [
...
'ckeditor', # 富文本编辑器
'ckeditor_uploader', # 富文本编辑器上传图片模块
...
]

3.setting/dev.py 配置

# 富文本编辑器ckeditor配置
CKEDITOR_CONFIGS = {
'default': {
'toolbar': 'full', # 工具条功能,full表示全部,Basic表示基本功能,功能少很多,还有个Custom自定义功能选项
'height': 300, # 编辑器高度
# 'width': 300, # 编辑器宽
},
}
CKEDITOR_UPLOAD_PATH = '' # 上传图片保存路径,留空则调用django的文件上传功能 # 也可以自定义配置
CKEDITOR_CONFIGS = {
'default': {
'toolbar': 'Custom',
'toolbar_Custom': [
['Bold', 'Italic', 'Underline','Image'], # 通过浏览器f12来查看每个功能的标签,就看到了类值cke_button_工具名称[注意使用驼峰式来写]
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
['Link', 'Unlink'],
['RemoveFormat', 'Source']
]
}
}

4.在总路由lyapi/urls.py添加路由

path(r'^ckeditor/', include('ckeditor_uploader.urls')),

5.将brief字段升级

# course/models.py
from ckeditor_uploader.fields import RichTextUploadingField
class Course(models.Model): # 课程概述变为富文本编辑器显示
brief = RichTextUploadingField(max_length=2048, verbose_name="课程概述", null=True, blank=True)

6.brief图片路径转化问题

相对路径转化为绝对路径

在brief中,存放的都是一些各种标签组成的字符串,而用户在使用富文本编辑器时,有可能会使用上传图片的功能。而图片上传后,默认都存在了后端的media文件夹中。但是前端并不会将我们的后端地址识别出来。它会默认被存放到前端:www.lycity.com/media中,所以需要我们手动更改一下上传图片存储的路径。这样用户上传的图片才能显示出来。

models.py

# course/models.py
class Course:
...
def new_brief(self):
data = self.brief
server_addr = contains.SERVER_ADDR
data = data.replace('/media',f'{server_addr}/media')
return data

settings/constants.py

SERVER_ADDR = 'http://www.lyapi.com:8001'

serializers.py

class CourseDetailModelSerializer:
...
teacher = TeacherModelSerializer()
class Meta:
model = model.Course
fields = [.........,teacher,level_name,new_brief] # 将new_brief添加到字段中

7.表情和图片应用不同的CSS样式

class Course:
...
def new_brief(self):
data = self.brief
server_addr = contains.SERVER_ADDR
'''
做了两件事:
1.将用户上传图片的相对路径改成了绝对路径
2.让图片和表情应用不同的CSS样式
'''
data = data.replace('src="/media',f'class="img_xx" src="{server_addr}/media')
return data

6.课程章节和课时显示-后端接口

urls.py

re_path(r'chapter/', views.ChapterView.as_view(),),

views.py

from django_filters.rest_framework import DjangoFilterBackend
class ChapterView(ListAPIView):
queryset = models.CourseChapter.objects.filter(is_deleted=False,is_show=True)
serializer_class = CourseChapterModelSerializer
filter_backends = [DjangoFilterBackend,]
filter_fields = ('course',)

serializers.py

class CourseLessonModelSerializer:
class Meta:
model = models.CourseLesson
fields = ['name','section_link','duration','free_trail','lesson'] class CourseChapterModelSerialzer: '''在一的序列化器嵌套多的序列化器,切记要加参数many=True'''
coursesection = CourseLessonModelSerializer(many=True) # 1201 class Meta:
model = models.CourseChapter
fields = ['chapter','name']

drf测试接口:course/chapter/?course=1

7.课程章节和课时显示-前端

<div class="tab-item" v-if="tabIndex==2">
<div class="tab-item-title">
<p class="chapter">课程章节</p>
<p class="chapter-length">共{{chapter_data.length}}章 </p>
</div>
<div class="chapter-item" v-for="(chapter,chapterindex) in chapter_data">
<p class="chapter-title"><img src="/static/img/1.png" alt="">第{{chapter.chapter}}章·{{chapter.name}}</p>
<ul class="lesson-list">
<li class="lesson-item" v-for="(lesson,lesson_index) in chapter.coursesections">
<p class="name"><span class="index">{{chapter.chapter}}-{{lesson.lesson}}</span> 课程介绍-{{lesson.name}}<span v-show="lesson.free_trail" class="free">免费</span></p>
<p class="time">{{lesson.duration}} <img src="/static/img/chapter-player.svg"></p>
<button class="try" v-if="lesson.free_trail">立即试学</button>
<button class="try" v-else>立即buy</button>
</li> </ul>
</div> </div>

课程章节和课时显示-HTML

// js
export default {
name: "Detail",
data(){
return { chapter_data:{}, } },
created(){ this.get_chapter_data();
},
methods: { get_chapter_data(){
this.$axios.get(`${this.$settings.Host}/course/chapter/`,{
params:{
course:this.course_id,
}
}).then((res)=>{
console.log(res.data);
this.chapter_data = res.data
})
}, }

课程章节和课时显示-JS

day82:luffy:课程详情页面显示&章节和课时显示&视频播放组件&CKEditor富文本编辑器的更多相关文章

  1. 项目页面集成ckeditor富文本编辑器

    步骤一.引入ckeditor.js (注:本实例以ThinkPHP3.2框架为载体,不熟悉ThinkPHP的朋友请自行补习,ckeditor文件代码内容也请去ckeditor官网自行下载) 作为程序员 ...

  2. day81:luffy:课程分类页面&课程信息页面&指定分类显示课程信息&分页显示课程信息

    目录 1.构建课程前端初始页面 2.course后端的准备工作 3.后端实现课程分类列表接口 4.前端发送请求-获取课程分类信息 5.后端实现课程列表信息的接口 6.前端显示列表课程信息 7.按照指定 ...

  3. 前端Vue项目——课程详情页面实现

    一.详情页面路由跳转 应用 Vue Router 编程式导航通过 this.$router.push() 来实现路由跳转. 1.绑定查看详情事件 修改 src/components/Course/Co ...

  4. Django day 33 vue中使用element-ui的使用,课程的相关介绍,vue绑定图片,课程列表接口,课程详情页面

    一:vue中使用element-ui的使用, 二:课程的相关介绍, 三:vue绑定图片, 四:课程列表接口, 五:课程详情页面

  5. Layui之动态循环遍历出的富文本编辑器显示

    这篇记得是工作中的例子 描述: 平常的富文本显示都是根据静态的html获取id来显示,比如: <textarea class="layui-textarea" id=&quo ...

  6. 「newbee-mall新蜂商城开源啦」 页面优化,最新版 wangEditor 富文本编辑器整合案例

    大家比较关心的新蜂商城 Vue3 版本目前已经开发了大部分内容,相信很快就能够开源出来让大家尝鲜了,先让大家看看当前的开发进度: 开源仓库地址为 https://github.com/newbee-l ...

  7. Ueditor富文本编辑器--Ctrl V 粘贴后原有图片显示错误

    最近负责将公司官网从静态网站改版成动态网站,方便公司推广营销人员修改增加文案,避免官网文案维护过于依赖技术人员.在做后台管理系统时用到了富文本编辑器Ueditor,因为公司有一个阿里云文件资源服务器, ...

  8. PHP UEditor富文本编辑器 显示 后端配置项没有正常加载,上传插件不能正常使用

    UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具有轻量,可定制,注重用户体验等特点,开源基于MIT协议,允许自由使用和修改代码... 问题描述 我的编辑器在本地测试的时候没问 ...

  9. MVC中提交包含HTML代码的页面处理方法(尤其是在使用kindeditor富文本编辑器的时候)

    针对文本框中有HTML代码提交时,mvc的action默认会阻止提交,主要是出于安全考虑.如果有时候需求是要将HTML代码同表单一起提交,那么这时候我们可以采取以下两种办法实现: 1.给Control ...

随机推荐

  1. Python-joypy和 R-ggridges 峰峦图制作

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 以下文章来源于DataCharm,作者 宁海涛 转载地址 https://www ...

  2. sqlserver和oracle修改表结构

    sqlserver和oracle修改表结构常用SQL Server:1.增加列  ALTER TABLE users ADD address varchar(30);2.删除列  ALTER TABL ...

  3. C++ “string”: 未声明的标识符

    转载:https://blog.csdn.net/kkkmmmjjjj/article/details/53780549 解决方案: 要添加using namespace std;语句在宏定义后面. ...

  4. matlab中polyfit

    来源:https://ww2.mathworks.cn/help/matlab/ref/polyfit.html?searchHighlight=polyfit&s_tid=doc_srcht ...

  5. P3660 [USACO17FEB]Why Did the Cow Cross the Road III G

    Link 题意: 给定长度为 \(2N\) 的序列,\(1~N\) 各处现过 \(2\) 次,i第一次出现位置记为\(ai\),第二次记为\(bi\),求满足\(ai<aj<bi<b ...

  6. 多测师讲解selenium ——切换窗口——打印句柄_高级讲师肖sir

    (一)同一个窗口打开两个浏览器 from selenium import webdriverfrom time import sleepdrvier=webdriver.Chrome()url='ht ...

  7. 题解:洛谷P1357 花园

    题解:洛谷P1357 花园 Description 小 L 有一座环形花园,沿花园的顺时针方向,他把各个花圃编号为 \(1∼n\).花园 \(1\) 和 \(n\) 是相邻的. 他的环形花园每天都会换 ...

  8. MeteoInfoLab脚本示例:多坐标系

    绘图的时候首先要有坐标系(Axes),可以用axes命令创建,如果没有创建在绘图时会自动创建一个.参数里的position是用来置顶坐标系的图形(figure)中的位置的,通过位置置顶,可以将多个坐标 ...

  9. centos7搭建docker环境

    Docker简介 Docker是一种虚拟化技术,用来将你的应用程序及其依赖的环境一起打包成一个镜像发布,使得在任何地方都能获得相同的运行环境. Docker 是一个开源项目,诞生于 2013 年初,最 ...

  10. 如何获取前端提交来得json格式数据

    composer.json { "require": { "guzzlehttp/guzzle": "~6.0" } } composer ...