一、讲师列表页 - 前后端
1、Controller类
@RestController
@CrossOrigin
@RequestMapping("/eduservice/teacherfront")
public class TeacherFrontController {
@Autowired
private EduTeacherService teacherService;
@Autowired
private EduCourseService courseService;
//1.分页查询讲师的方法
@GetMapping("getTeacherFrontList/{page}/{limit}")
public R getTeacherFrontList(@PathVariable long page,@PathVariable long limit){
Page <EduTeacher> teacherPage = new Page <>(page,limit);
Map<String,Object> map= teacherService.getTeacherFrontList(teacherPage);
return R.ok().data(map);
}
}
2、Service类
//查询讲师列表 带分页
@Override
public Map <String, Object> getTeacherFrontList(Page <EduTeacher> page) {
QueryWrapper <EduTeacher> wrapper = new QueryWrapper <>();
wrapper.orderByAsc("sort");
this.baseMapper.selectPage(page, wrapper);
List <EduTeacher> records = page.getRecords();
long current = page.getCurrent();
long pages = page.getPages();
long size = page.getSize();
long total = page.getTotal();
boolean hasNext = page.hasNext();
boolean hasPrevious = page.hasPrevious();
Map <String, Object> map = new HashMap <>();
map.put("items", records);
map.put("current", current);
map.put("pages", pages);
map.put("size", size);
map.put("total", total);
map.put("hasNext", hasNext);
map.put("hasPrevious", hasPrevious);
return map;
}
3、使用Swagger测试
http://localhost:8001/swagger-ui.html
4、创建 api
创建文件夹api,api下创建teacher.js,用于封装讲师模块的请求
import request from '@/utils/request'
export default {
//分页查询讲师的方法
getTeacheList(page,limit){
return request({
url:`/eduservice/teacherfront/getTeacherFrontList/${page}/${limit}`,
mehtod:'get'
})
}
}
5、讲师列表组件中调用api
pages/teacher/index.vue
<script>
import teacherApi from '@/api/teacher'
export default {
// data() {
// return {
// data:[]
// }
// },
//异步调用(仅仅执行一次)
//params:相当于之前 this.$route.params =>> this.$route.params.id==params.id
asyncData({ params, error}) {
return teacherApi.getTeacheList(1,8).then(response=>{
//this.data = response.data.data 和下面这行等价.
return {data:response.data.data}
})
},
methods: {
gotoPage(page){
teacherApi.getTeacheList(page,8).then(response=>{
this.data = response.data.data
})
}
},
};
</script>
6、页面渲染
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- 讲师列表 开始 -->
<section class="container">
<header class="comm-title all-teacher-title">
<h2 class="fl tac">
<span class="c-333">全部讲师</span>
</h2>
<section class="c-tab-title">
<a id="subjectAll" title="全部" href="#">全部</a>
<!-- <c:forEach var="subject" items="${subjectList }">
<a id="${subject.subjectId}" title="${subject.subjectName }" href="javascript:void(0)" οnclick="submitForm(${subject.subjectId})">${subject.subjectName }</a>
</c:forEach> -->
</section>
</header>
<section class="c-sort-box unBr">
<div>
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="data.total==0">
<em class="icon30 no-data-ico"> </em>
<span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理中...</span>
</section>
<!-- /无数据提示 结束-->
<article class="i-teacher-list" v-if="data.total > 0">
<ul class="of">
<li v-for="item in data.items" :key="item.id">
<section class="i-teach-wrap">
<div class="i-teach-pic">
<a :href="'/teacher/'+item.id" :title="item.name" target="_blank">
<img :src="item.avatar" :alt="item.name">
</a>
</div>
<div class="mt10 hLh30 txtOf tac">
<a :href="'/teacher/'+item.id" :title="item.name" target="_blank" class="fsize18 c-666">{{item.name}}</a>
</div>
<div class="hLh30 txtOf tac">
<span class="fsize14 c-999">{{item.intro}}</span>
</div>
<div class="mt15 i-q-txt">
<p class="c-999 f-fA">{{item.career}}</p>
</div>
</section>
</li>
</ul>
<div class="clear"></div>
</article>
</div>
<!-- 公共分页 开始 -->
<div>
<div class="paging">
<!-- undisable这个class是否存在,取决于数据属性hasPrevious -->
<a
:class="{undisable: !data.hasPrevious}"
href="#"
title="首页"
@click.prevent="gotoPage(1)"
disabled
>首页</a>
<a
:class="{undisable: !data.hasPrevious}"
href="#"
title="前一页"
@click.prevent="gotoPage(data.current-1)"><</a>
<a
v-for="page in data.pages"
:key="page"
:class="{current: data.current == page, undisable: data.current == page}"
:title="'第'+page+'页'"
href="#"
@click.prevent="gotoPage(page)">{{ page }}</a>
<a
:class="{undisable: !data.hasNext}"
href="#"
title="后一页"
@click.prevent="gotoPage(data.current+1)">></a>
<a
:class="{undisable: !data.hasNext}"
href="#"
title="末页"
@click.prevent="gotoPage(data.pages)">尾页</a>
<div class="clear"/>
</div>
</div>
<!-- 公共分页 结束 -->
</section>
</section>
<!-- /讲师列表 结束 -->
</div>
</template>
7、页面效果展示
二、讲师详情页 - 前后端
1、Controller 类
//2.讲师详情的功能
@GetMapping("getTeacherFrontInfo/{teacherId}")
public R getTeacherFrontInfo(@PathVariable String teacherId){
//1.根据讲师id查询讲师基本信息
EduTeacher eduTeacher = teacherService.getById(teacherId);
//2.根据讲师id查询讲师的所有课程
QueryWrapper <EduCourse> wrapper = new QueryWrapper <>();
wrapper.eq("teacher_id", teacherId);
List <EduCourse> courseList = courseService.list(wrapper);
return R.ok().data("teacher",eduTeacher).data("courseList",courseList);
}
2、swagger测试
3、teacher api
api/teacher.js
//根据id获取讲师
getTeacherInfo(teacherId){
return request({
url:`/eduservice/teacherfront/getTeacherFrontInfo/${teacherId}`,
mehtod:'get'
})
}
4、讲师详情页中调用api
pages/teacher/_id.vue
<script>
import teacherApi from '@/api/teacher'
export default {
asyncData({ params, error}) {
//查询讲师详情信息 params.id==this.$route.params.id
return teacherApi.getTeacherInfo(params.id).then(response=>{
return{
teacher:response.data.data.teacher,
courseList:response.data.data.courseList
}
})
}
};
</script>
5、页面渲染
(1)讲师基本信息模板
<!-- 讲师基本信息 -->
<section class="fl t-infor-box c-desc-content">
<div class="mt20 ml20">
<section class="t-infor-pic">
<img :src="teacher.avatar">
</section>
<h3 class="hLh30">
<span class="fsize24 c-333" v-if="teacher.level==1">{{teacher.name}} 高级讲师</span>
<span class="fsize24 c-333" v-if="teacher.level==2">{{teacher.name}} 特级讲师</span>
</h3>
<section class="mt10">
<span class="t-tag-bg">{{teacher.career}}</span>
</section>
<section class="t-infor-txt">
<p
class="mt20"
>{{teacher.intro}}</p>
</section>
<div class="clear"></div>
</div>
</section>
(2)无数据提示
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="courseList.length==0">
<em class="icon30 no-data-ico"> </em>
<span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理中...</span>
</section>
(3)当前讲师课程列表
<article class="comm-course-list" v-if="courseList.length>0">
<ul class="of">
<li v-for="course in courseList" :key="course.id">
<div class="cc-l-wrap">
<section class="course-img">
<img :src="course.cover" class="img-responsive" >
<div class="cc-mask">
<a :href="'/course/'+course.id" title="开始学习" target="_blank" class="comm-btn c-btn-1">开始学习</a>
</div>
</section>
<h3 class="hLh30 txtOf mt10">
<a :href="'/course/'+course.id" :title="course.title" target="_blank" class="course-title fsize18 c-333">{{course.title}}</a>
</h3>
</div>
</li>
</ul>
<div class="clear"></div>
</article>
6、页面效果展示
点击讲师列表页面的任意一个讲师,可以进入讲师详情页面
三、课程列表页 - 前后端
1、Controller类
@RestController
@CrossOrigin
@RequestMapping("/eduservice/coursefront")
public class CourseFrontController {
@Autowired
private EduCourseService courseService;
@Autowired
private EduChapterService chapterService;
//1.条件查询带分页查询课程
@PostMapping("getFrontCourseList/{page}/{limit}")
public R getFrontCourseList(@PathVariable long page, @PathVariable long limit,
@RequestBody(required = false)CourseQueryVo courseQueryVo){
Page <EduCourse> coursePage = new Page <>(page, limit);
Map <String,Object> map = courseService.getCourseFrontInfo(coursePage,courseQueryVo);
//返回分页所有数据
return R.ok().data(map);
}
}
2、Service类
//条件查询带分页
@Override
public Map <String, Object> getCourseFrontInfo(Page <EduCourse> coursePage, CourseQueryVo courseQueryVo) {
QueryWrapper <EduCourse> wrapper = new QueryWrapper <>();
String buyCountSort = courseQueryVo.getBuyCountSort();
String gmtCreateSort = courseQueryVo.getGmtCreateSort();
String priceSort = courseQueryVo.getPriceSort();
String subjectParentId = courseQueryVo.getSubjectParentId();
String subjectId = courseQueryVo.getSubjectId();
if(!StringUtils.isEmpty(buyCountSort)){
if(buyCountSort.equals("1")){//1是降序
wrapper.orderByDesc("buy_count");
}else if(buyCountSort.equals("2")){//2是升序
wrapper.orderByAsc("buy_count");
}
}
if(!StringUtils.isEmpty(gmtCreateSort)){
if(gmtCreateSort.equals("1")){//1是降序
wrapper.orderByDesc("gmt_create");
}else if(gmtCreateSort.equals("2")){//2是升序
wrapper.orderByAsc("gmt_create");
}
}
if(!StringUtils.isEmpty(priceSort)){
if(priceSort.equals("1")){//1是降序
wrapper.orderByDesc("price");
}else if(priceSort.equals("2")){//2是升序
wrapper.orderByAsc("price");
}
}
if(!StringUtils.isEmpty(subjectParentId)){
wrapper.eq("subject_parent_id", subjectParentId);
}
if(!StringUtils.isEmpty(subjectId)){
wrapper.eq("subject_id", subjectId);
}
this.baseMapper.selectPage(coursePage, wrapper);
List <EduCourse> records = coursePage.getRecords();
long current = coursePage.getCurrent();
long pages = coursePage.getPages();
long size = coursePage.getSize();
long total = coursePage.getTotal();
boolean hasNext = coursePage.hasNext();
boolean hasPrevious = coursePage.hasPrevious();
Map <String, Object> map = new HashMap <>();
map.put("items", records);
map.put("current", current);
map.put("pages", pages);
map.put("size", size);
map.put("total", total);
map.put("hasNext", hasNext);
map.put("hasPrevious", hasPrevious);
return map;
}
3、使用Swagger测试
4、编写course api
import request from '@/utils/request'
export default {
//1.条件查询带分页查询课程
getCourseList(page,limit,searchObj){
return request({
url:`/eduservice/coursefront/getFrontCourseList/${page}/${limit}`,
method:'post',
data:searchObj
})
},
//2.查询所有课程分类
getAllSubject(){
return request({
url:`/eduservice/subject/getAllSubject`,
method:'get'
})
},
}
5、课程列表页面中调用api
<script>
import courseApi from '@/api/course'
export default {
data() {
return {
page:1,
data:{},
subjectNestedList: [], // 一级分类列表
subSubjectList: [], // 二级分类列表
searchObj: {}, // 查询表单对象
oneIndex:-1,
twoIndex:-1,
buyCountSort:"", //1是降序 2是升序 不选则不排序
gmtCreateSort:"",
priceSort:""
}
},
created() {
this.initCourseList()
this.initSubject()
},
methods: {
//1.查询第一页数据
initCourseList(){
courseApi.getCourseList(1,8,this.searchObj).then(response=>{
this.data = response.data.data
})
},
//2.查询所有一级分类 和所有二级分类
initSubject(){
courseApi.getAllSubject().then(response=>{
this.subjectNestedList = response.data.data.list
this.subSubjectList = []//先清空所有的二级分类
// this.subSubjectList=this.subjectNestedList[0].children
for(var i = 0;i < this.subjectNestedList.length; i++){
this.subSubjectList.push(...this.subjectNestedList[i].children)
}
})
},
//3.分页跳转
gotoPage(page){
courseApi.getCourseList(page,8,this.searchObj).then(response=>{
this.data = response.data.data
})
},
//4.单击一级分类,显示二级分类
searchOne(subjectParentId,index){
this.oneIndex = index
//相关值的初始化
this.twoIndex = -1
this.searchObj.subjectId = ""
this.subSubjectList = []//清空二级分类
//把一级分类点击id,赋值给searchObj
this.searchObj.subjectParentId = subjectParentId
//点击单个一级分类进行条件查询
this.gotoPage(1)
for(var i = 0;i < this.subjectNestedList.length; i++){
var oneSubject = this.subjectNestedList[i]
if(subjectParentId == oneSubject.id){
//从一级分类里面获取对应的二级分类
this.subSubjectList = oneSubject.children
}
}
},
//5.单击二级分类,进行查询
searchTwo(subjectId,index){
this.twoIndex = index
// this.searchObj.subjectParentId = ""//一级分类搜索清空 这里不需要情况,因为
this.searchObj.subjectId = subjectId
this.gotoPage(1)
},
//6.点击全部分类,查询全部
searchAll(index){
this.oneIndex = index
this.twoIndex = -1
//进行查询全部
this.searchObj = {}
this.gotoPage(1)
this.subSubjectList = []//清空二级,填充所有二级分类
for(var i = 0;i < this.subjectNestedList.length; i++){
this.subSubjectList.push(...this.subjectNestedList[i].children)
}
},
//7.根据销量排序
searchBuyCount(){
//设置对应变量值,为了样式生效
if(this.buyCountSort==""){
this.buyCountSort="1"
}else if(this.buyCountSort=="1"){
this.buyCountSort="2"
}else if(this.buyCountSort=="2"){
this.buyCountSort = "1"
}
console.log("this.buyCountSort:"+this.buyCountSort);
this.gmtCreateSort = ""
this.priceSort = ""
//把值赋值到searchObj
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
this.searchObj.priceSort = this.priceSort
//调用方法 进行搜索
this.gotoPage(1)
},
searchGmtCreate(){
//设置对应变量值,为了样式生效
if(this.gmtCreateSort==""){
this.gmtCreateSort="1"
}else if(this.gmtCreateSort=="1"){
this.gmtCreateSort="2"
}else if(this.gmtCreateSort=="2"){
this.gmtCreateSort = "1"
}
console.log("this.gmtCreateSort:"+this.gmtCreateSort);
this.buyCountSort = ""
this.priceSort = ""
//把值赋值到searchObj
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
this.searchObj.priceSort = this.priceSort
//调用方法 进行搜索
this.gotoPage(1)
},
searchPrice(){
//设置对应变量值,为了样式生效
if(this.priceSort==""){
this.priceSort="1"
}else if(this.priceSort=="1"){
this.priceSort="2"
}else if(this.priceSort=="2"){
this.priceSort = "1"
}
console.log("this.priceSort:"+this.priceSort);
this.buyCountSort = ""
this.gmtCreateSort = ""
//把值赋值到searchObj
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
this.searchObj.priceSort = this.priceSort
//调用方法 进行搜索
this.gotoPage(1)
}
},
};
</script>
</script>
<style scoped>
.active {
background: #68CB9B;
}
.active2 {
background: #00cc7e;
}
.hide {
display: none;
}
.show {
display: block;
}
</style>
6、页面渲染
分类渲染+分类搜索功能+课程分页渲染
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- /课程列表 开始 -->
<section class="container">
<header class="comm-title">
<h2 class="fl tac">
<span class="c-333">全部课程</span>
</h2>
</header>
<section class="c-sort-box">
<section class="c-s-dl">
<dl>
<dt>
<span class="c-999 fsize14">课程类别</span>
</dt>
<dd class="c-s-dl-li">
<ul class="clearfix">
<li>
<a title="全部" href="#" @click="searchAll(-1)" :class="{active:oneIndex==-1}">全部</a>
</li>
<li v-for="(item,index) in subjectNestedList" :key="item.id" @click="searchOne(item.id,index)" :class="{active:oneIndex==index}">
<a :title="item.title" href="#">{{item.title}}</a>
</li>
</ul>
</dd>
</dl>
<dl>
<dt>
<span class="c-999 fsize14"></span>
</dt>
<dd class="c-s-dl-li">
<ul class="clearfix">
<li v-for="(item,index) in subSubjectList" :key="index" @click="searchTwo(item.id,index)"
:class="{active2:twoIndex==index}">
<a :title="item.title" href="#" >{{item.title}}</a>
</li>
</ul>
</dd>
</dl>
<div class="clear"></div>
</section>
<div class="js-wrap">
<section class="fr">
<span class="c-ccc">
<i class="c-master f-fM">1</i>/
<i class="c-666 f-fM">1</i>
</span>
</section>
<section class="fl">
<ol class="js-tap clearfix">
<li :class="{'current bg-orange':buyCountSort!=''}">
<a title="销量" href="javascript:void(0);" @click="searchBuyCount()">销量
<span :class="{hide:buyCountSort=='1'}" v-if="buyCountSort!=''">↑</span>
<span :class="{hide:buyCountSort=='2'}" v-if="buyCountSort!=''">↓</span>
</a>
</li>
<li :class="{'current bg-orange':gmtCreateSort!=''}">
<a title="最新" href="javascript:void(0);" @click="searchGmtCreate()">最新
<span :class="{hide:gmtCreateSort=='1'}" v-if="gmtCreateSort!=''">↑</span>
<span :class="{hide:gmtCreateSort=='2'}" v-if="gmtCreateSort!=''">↓</span>
</a>
</li>
<li :class="{'current bg-orange':priceSort!=''}">
<a title="价格" href="javascript:void(0);" @click="searchPrice()">价格
<span :class="{hide:priceSort=='1'}" v-if="priceSort!=''">↑</span>
<span :class="{hide:priceSort=='2'}" v-if="priceSort!=''">↓</span>
</a>
</li>
</ol>
</section>
</div>
<div class="mt40">
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="data.total == 0">
<em class="icon30 no-data-ico"> </em>
<span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理中...</span>
</section>
<!-- /无数据提示 结束-->
<article class="comm-course-list" v-if="data.total > 0">
<ul class="of" id="bna">
<li v-for="item in data.items" :key="item.id">
<div class="cc-l-wrap">
<section class="course-img">
<img :src="item.cover" class="img-responsive" :alt="item.title">
<div class="cc-mask">
<a :href="'/course/'+item.id" title="开始学习" class="comm-btn c-btn-1">开始学习</a>
</div>
</section>
<h3 class="hLh30 txtOf mt10">
<a :href="'/course/'+item.id" :title="item.title" class="course-title fsize18 c-333">{{item.title}}</a>
</h3>
<section class="mt10 hLh20 of">
<span class="fr jgTag bg-green" v-if="Number(item.price) === 0">
<i class="c-fff fsize12 f-fA">免费</i>
</span>
<span class="fr jgTag bg-green" v-else>
<i class="c-fff fsize12 f-fA"> ¥{{item.price}}</i>
</span>
<span class="fl jgAttr c-ccc f-fA">
<i class="c-999 f-fA">{{item.buyCount}}人学习</i>
|
<i class="c-999 f-fA">9634评论</i>
</span>
</section>
</div>
</li>
</ul>
<div class="clear"></div>
</article>
</div>
<!-- 公共分页 开始 -->
<div>
<div class="paging">
<!-- undisable这个class是否存在,取决于数据属性hasPrevious -->
<a
:class="{undisable: !data.hasPrevious}"
href="#"
title="首页"
@click.prevent="gotoPage(1)">首</a>
<a
:class="{undisable: !data.hasPrevious}"
href="#"
title="前一页"
@click.prevent="gotoPage(data.current-1)"><</a>
<a
v-for="page in data.pages"
:key="page"
:class="{current: data.current == page, undisable: data.current == page}"
:title="'第'+page+'页'"
href="#"
@click.prevent="gotoPage(page)">{{ page }}</a>
<a
:class="{undisable: !data.hasNext}"
href="#"
title="后一页"
@click.prevent="gotoPage(data.current+1)">></a>
<a
:class="{undisable: !data.hasNext}"
href="#"
title="末页"
@click.prevent="gotoPage(data.pages)">末</a>
<div class="clear"/>
</div>
</div>
<!-- 公共分页 结束 -->
</section>
</section>
<!-- /课程列表 结束 -->
</div>
</template>
7、页面效果展示
四、课程详情页 - 前后端
1、Controller类
//2.课程详情的方法
@GetMapping("getFrontCourseInfo/{courseId}")
public R getFrontCourseInfo(@PathVariable String courseId){
//根据课程id,编写sql语句查询课程信息
CourseWebVo courseWebVo = courseService.getBaseCourseInfo(courseId);
//根据课程id,查询章节和小节
List <ChapterVo> chapterVideoList = chapterService.getChapterVideo(courseId);
return R.ok().data("course",courseWebVo).data("chapterVideoList",chapterVideoList);
}
2、Service类
@Override
public CourseWebVo getBaseCourseInfo(String courseId) {
return this.baseMapper.getBaseCourseInfo(courseId);
}
3、Mapper类
public CourseWebVo getBaseCourseInfo(String courseId);
<select id="getBaseCourseInfo" resultType="com.rg.eduservice.entity.frontvo.CourseWebVo" parameterType="string">
SELECT ec.id,ec.`title`,ec.`price`,ec.`lesson_num` AS lessonNum,ec.`cover`,ec.`buy_count` buyCount ,ec.`view_count` viewCount,
ecd.`description`,
et.`id` teacherId,
et.`name` teacherName,
et.`intro`,
et.`avatar`,
es1.`id` AS subjectLevelOneId,
es1.`title` AS subjectLevelOne,
es2.`id` AS subjectLevelTwoId,
es2.`title` AS subjectLevelTwo
FROM edu_course ec LEFT OUTER JOIN edu_subject es1 ON ec.`subject_parent_id`= es1.`id`
LEFT OUTER JOIN edu_subject es2 ON ec.`subject_id` = es2.`id`
LEFT OUTER JOIN edu_teacher et ON ec.`teacher_id` = et.`id`
LEFT OUTER JOIN edu_course_description ecd ON ec.`id` = ecd.`id`
WHERE ec.id=#{id}
</select>
4、使用Swagger测试
5、编写course api
//3.课程课程基本详情的方法
getCourseInfo(courseId){
return request({
url:`/eduservice/coursefront/getFrontCourseInfo/${courseId}`,
method:'get'
})
}
6、课程详情页面中调用API
<script>
import courseApi from '@/api/course'
export default {
asyncData({ params, error}) {
//查询课程详情信息
return courseApi.getCourseInfo(params.id).then(response=>{
return{
course:response.data.data.course,
chapterVideoList:response.data.data.chapterVideoList
}
})
}
};
</script>
7、页面渲染
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- /课程详情 开始 -->
<section class="container">
<section class="path-wrap txtOf hLh30">
<a href="#" title class="c-999 fsize14">首页</a>
\
<a href="#" title class="c-999 fsize14">{{course.subjectLevelOne}}</a>
\
<span class="c-333 fsize14">{{course.subjectLevelTwo}}</span>
</section>
<div>
<article class="c-v-pic-wrap" style="height: 357px;">
<section class="p-h-video-box" id="videoPlay">
<img :src="course.cover" :alt="course.title" class="dis c-v-pic">
</section>
</article>
<aside class="c-attr-wrap">
<section class="ml20 mr15">
<h2 class="hLh30 txtOf mt15">
<span class="c-fff fsize24">{{course.title}}</span>
</h2>
<section class="c-attr-jg">
<span class="c-fff">价格:</span>
<b class="c-yellow" style="font-size:24px;">¥{{course.price}}</b>
</section>
<section class="c-attr-mt c-attr-undis">
<span class="c-fff fsize14">主讲: {{course.teacherName}} </span>
</section>
<section class="c-attr-mt of">
<span class="ml10 vam">
<em class="icon18 scIcon"></em>
<a class="c-fff vam" title="收藏" href="#" >收藏</a>
</span>
</section>
<section class="c-attr-mt">
<a href="#" title="立即观看" class="comm-btn c-btn-3">立即观看</a>
</section>
</section>
</aside>
<aside class="thr-attr-box">
<ol class="thr-attr-ol clearfix">
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">购买数</span>
<br>
<h6 class="c-fff f-fM mt10">{{course.buyCount}}</h6>
</aside>
</li>
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">课时数</span>
<br>
<h6 class="c-fff f-fM mt10">{{course.lessonNum}}</h6>
</aside>
</li>
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">浏览数</span>
<br>
<h6 class="c-fff f-fM mt10">{{course.viewCount}}</h6>
</aside>
</li>
</ol>
</aside>
<div class="clear"></div>
</div>
<!-- /课程封面介绍 -->
<div class="mt20 c-infor-box">
<article class="fl col-7">
<section class="mr30">
<div class="i-box">
<div>
<section id="c-i-tabTitle" class="c-infor-tabTitle c-tab-title">
<a name="c-i" class="current" title="课程详情">课程详情</a>
</section>
</div>
<article class="ml10 mr10 pt20">
<div>
<h6 class="c-i-content c-infor-title">
<span>课程介绍</span>
</h6>
<div class="course-txt-body-wrap">
<section class="course-txt-body">
<p v-html="course.description">
{{course.description}}
</p>
</section>
</div>
</div>
<!-- /课程介绍 -->
<div class="mt50">
<h6 class="c-g-content c-infor-title">
<span>课程大纲</span>
</h6>
<section class="mt20">
<div class="lh-menu-wrap">
<menu id="lh-menu" class="lh-menu mt10 mr10">
<ul>
<!-- 文件目录 -->
<li class="lh-menu-stair" v-for="chapter in chapterVideoList" :key="chapter.id">
<a href="javascript: void(0)" :title="chapter.title" class="current-1">
<em class="lh-menu-i-1 icon18 mr10"></em>{{chapter.title}}
</a>
<ol class="lh-menu-ol" style="display: block;">
<li class="lh-menu-second ml30" v-for="video in chapter.children" :key="video.id">
<a :href="'/player/'+video.videoSourceId" title target="_blank">
<span class="fr">
<i class="free-icon vam mr10" v-if="video.isFree === true">免费试听</i>
</span>
<em class="lh-menu-i-2 icon16 mr5"> </em>{{video.title}}
</a>
</li>
</ol>
</li>
</ul>
</menu>
</div>
</section>
</div>
<!-- /课程大纲 -->
</article>
</div>
</section>
</article>
<aside class="fl col-3">
<div class="i-box">
<div>
<section class="c-infor-tabTitle c-tab-title">
<a title href="javascript:void(0)">主讲讲师</a>
</section>
<section class="stud-act-list">
<ul style="height: auto;">
<li>
<div class="u-face">
<!-- "'/course/'+item.id" -->
<a :href="'/teacher/'+course.teacherId">
<img :src="course.avatar" width="50" height="50" :alt="course.teacherName">
</a>
</div>
<section class="hLh30 txtOf">
<a class="c-333 fsize16 fl" href="#">{{course.teacherName}}</a>
</section>
<section class="hLh20 txtOf">
<span class="c-999">{{course.intro}}</span>
</section>
</li>
</ul>
</section>
</div>
</div>
</aside>
<div class="clear"></div>
</div>
</section>
<!-- /课程详情 结束 -->
</div>
</template>
8、页面效果展示
五、视频点播后端获取播放凭证
1、VideoController
service_vod微服务中 VodController.java 中创建 getPlayAuth 接口方法
//根据视频id获取视频凭证
@GetMapping("getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id){
try {
DefaultAcsClient client = InitVodClient.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
request.setVideoId(id);
GetVideoPlayAuthResponse response = client.getAcsResponse(request);
String playAuth = response.getPlayAuth();
return R.ok().data("playAuth", playAuth);
}catch (Exception e){
throw new GuLiException(20001, "获取凭证失败");
}
}
2、在service_edu中VideoVo类中添加属性
private String videoSourceId;//视频id
3、Swagger测试
六、前端播放器整合
1、点击播放超链接
course/_id.vue 中修改课时目录超链接
2、layout
因为播放器的布局和其他页面的基本布局不一致,因此创建新的布局容器 layouts/video.vue
<template>
<div class="guli-player">
<div class="head">
<a href="#" title="谷粒学院">
<img class="logo" src="~/assets/img/logo.png" lt="谷粒学院">
</a></div>
<div class="body">
<div class="content"><nuxt/></div>
</div>
</div>
</template>
<script>
export default {}
</script>
<style>
html,body{
height:100%;
}
</style>
<style scoped>
.head {
height: 50px;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.head .logo{
height: 50px;
margin-left: 10px;
}
.body {
position: absolute;
top: 50px;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
</style>
3、api
创建api模块 api/vod.js,从后端获取播放凭证
import request from '@/utils/request'
export default {
//根据视频id获取视频凭证
getPlayAuth(vid){
return request({
url:`/vodService/video/getPlayAuth/${vid}`,
mehtod:'get'
})
},
}
4、播放组件相关文档
集成文档:https://help.aliyun.com/document_detail/51991.html?spm=a2c4g.11186623.2.39.478e192b8VSdEn
在线配置:https://player.alicdn.com/aliplayer/setting/setting.html
功能展示:https://player.alicdn.com/aliplayer/presentation/index.html
5、创建播放页面
创建 pages/player/_vid.vue
(1)引入播放器js库和css样式
<template>
<div>
<!-- 阿里云视频播放器样式 -->
<link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css" >
<!-- 阿里云视频播放器脚本 -->
<script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js" />
<!-- 定义播放器dom -->
<div id="J_prismPlayer" class="prism-player" />
</div>
</template>
(2)获取播放凭证
<script>
import vod from '@/api/vod'
export default{
layout:'video',//应用video布局
asyncData({params, error}) {
//注意 这里的params.vid 是写页面的名字,如果页面写的是_id,则要写params.id
return vod.getPlayAuth(params.vid).then(response=>{
return {
vid:params.vid,
playAuth:response.data.data.playAuth
}
})
},
}
</script>
(3)创建播放器
/**
* 页面渲染完成时:此时js脚本已加载,Aliplayer已定义,可以使用
* 如果在created生命周期函数中使用,Aliplayer is not defined错误
*/
// 因为刚进入页面时,this.vid和this.playAuth都还没有,发送完异步请求后才有.所以视频播放的相关配置应该放在mounted,等页面加载完成后执行
mounted() {
new Aliplayer({
id: 'J_prismPlayer',
vid: this.vid, // 视频id
playauth: this.playAuth, // 播放凭证
encryptType: '1', // 如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
width: '100%',
height: '500px',
// 以下可选设置
cover: 'http://guli.shop/photo/banner/1525939573202.jpg', // 封面
qualitySort: 'asc', // 清晰度排序
mediaType: 'video', // 返回音频还是视频
autoplay: false, // 自动播放
isLive: false, // 直播
rePlay: false, // 循环播放
preload: true,
controlBarVisibility: 'hover', // 控制条的显示方式:鼠标悬停
useH5Prism: true, // 播放器类型:html5
}, function(player) {
console.log('播放器创建成功')
})
}
6、加入播放组件
功能展示:https://player.alicdn.com/aliplayer/presentation/index.html
7、页面效果展示
点击免费播放,进入视频播放页面
如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客