谷粒学院(十七)讲师列表页 | 讲师详情 | 课程列表页 | 课程详情 | 整合阿里云视频点播
文章目录
-
- 一、讲师列表页 - 前后端
-
- 1、Controller类
- 2、Service类
- 3、使用Swagger测试
- 4、创建 api
- 5、讲师列表组件中调用api
- 6、页面渲染
- 7、页面效果展示
- 二、讲师详情页 - 前后端
-
- 1、Controller 类
- 2、swagger测试
- 3、teacher api
- 4、讲师详情页中调用api
- 5、页面渲染
- 6、页面效果展示
- 三、课程列表页 - 前后端
-
- 1、Controller类
- 2、Service类
- 3、使用Swagger测试
- 4、编写course api
- 5、课程列表页面中调用api
- 6、页面渲染
- 7、页面效果展示
- 四、课程详情页 - 前后端
-
- 1、Controller类
- 2、Service类
- 3、Mapper类
- 4、使用Swagger测试
- 5、编写course api
- 6、课程详情页面中调用API
- 7、页面渲染
- 8、页面效果展示
- 五、视频点播后端获取播放凭证
-
- 1、VideoController
- 2、在service_edu中VideoVo类中添加属性
- 3、Swagger测试
- 六、前端播放器整合
-
- 1、点击播放超链接
- 2、layout
- 3、api
- 4、播放组件相关文档
- 5、创建播放页面
- 6、加入播放组件
- 7、页面效果展示
一、讲师列表页 - 前后端
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类
//查询讲师列表 带分页@Overridepublic 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> <!-- ${subject.subjectName } --> </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"> <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)"><span class="token entity" title="<</a> <av-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类
//条件查询带分页@Overridepublic 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> <av-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类
@Overridepublic 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"> <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" /> <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、页面效果展示
点击免费播放,进入视频播放页面
如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客