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、页面效果展示
点击免费播放,进入视频播放页面