01-課程頁(yè)面靜態(tài)效果整合
一、列表頁(yè)面
創(chuàng)建 pages/course/index.vue
二、詳情頁(yè)面
創(chuàng)建 pages/course/_id.vue
02-課程列表頁(yè)面
一、課程后端接口
1、課程列表
(1)課程列表vo類(條件查詢需要的對(duì)象)
@ApiModel(value = "課程查詢對(duì)象", description = "課程查詢對(duì)象封裝")
@Data
public class CourseFrontVo {
? ? @ApiModelProperty(value = "課程名稱")
? ? private String title;
? ? @ApiModelProperty(value = "講師id")
? ? private String teacherId;
? ? @ApiModelProperty(value = "一級(jí)類別id")
? ? private String subjectParentId;
? ? @ApiModelProperty(value = "二級(jí)類別id")
? ? private String subjectId;
? ? @ApiModelProperty(value = "銷量排序")
? ? private String buyCountSort;
? ? @ApiModelProperty(value = "最新時(shí)間排序")
? ? private String gmtCreateSort;
? ? @ApiModelProperty(value = "價(jià)格排序")
? ? private String priceSort;
}
(2)課程列表controller
@RestController
@CrossOrigin
@RequestMapping("/eduservice/coursefront")
public class CourseFrontController {
? ? @Autowired
? ? private EduCourseService courseService;
? ? //1 條件查詢帶分頁(yè)查詢課程
? ? @ApiOperation(value = "分頁(yè)課程列表")
? ? @PostMapping("pageCourseCondition/{current}/{limit}")
? ? public R pageCourseCondition(@PathVariable long limit,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @PathVariable long current,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @RequestBody(required = false) CourseFrontVo courseFrontVo){
? ? ? ? //@RequestBody(required = false)使用RequestBody必須是post提交,不添加required = false必須要有值,添加后表示可以沒(méi)有值
? ? ? ? Page<EduCourse> pageParam = new Page<EduCourse>(current,limit);
? ? ? ? Map<String, Object> map = courseService.pageListWeb(pageParam, courseFrontVo);
? ? ? ? return R.ok().data(map);
? ? }
}
//@RequestBody(required = false)使用RequestBody必須是post提交,不添加required = false必須要有值,添加后可以沒(méi)有值
(3)課程列表service
//1 條件查詢帶分頁(yè)查詢課程
? ? @Override
? ? public Map<String, Object> pageListWeb(Page<EduCourse> pageParam, CourseFrontVo courseFrontVo) {
? ? ? ? QueryWrapper<EduCourse> queryWrapper = new QueryWrapper<>();
? ? ? ? //判斷條件值是否為空,不為空拼接
? ? ? ? if (!StringUtils.isEmpty(courseFrontVo.getSubjectParentId())){//一級(jí)分類
? ? ? ? ? ? queryWrapper.eq("subject_parent_id",courseFrontVo.getSubjectParentId());
? ? ? ? }
? ? ? ? if (!StringUtils.isEmpty(courseFrontVo.getSubjectId())){//二級(jí)分類
? ? ? ? ? ? queryWrapper.eq("subject_id",courseFrontVo.getSubjectId());
? ? ? ? }
? ? ? ? if (!StringUtils.isEmpty(courseFrontVo.getBuyCountSort())){//銷售數(shù)量
? ? ? ? ? ? queryWrapper.orderByDesc("buy_count",courseFrontVo.getBuyCountSort());
? ? ? ? }
? ? ? ? if (!StringUtils.isEmpty(courseFrontVo.getGmtCreateSort())){//創(chuàng)建時(shí)間
? ? ? ? ? ? queryWrapper.orderByDesc("gmt_create",courseFrontVo.getGmtCreateSort());
? ? ? ? }
? ? ? ? if (!StringUtils.isEmpty(courseFrontVo.getPriceSort())){//價(jià)格
? ? ? ? ? ? queryWrapper.orderByDesc("price",courseFrontVo.getPriceSort());
? ? ? ? }
? ? ? ? baseMapper.selectPage(pageParam, queryWrapper);
? ? ? ? List<EduCourse> records = pageParam.getRecords();
? ? ? ? long pages = pageParam.getPages();
? ? ? ? long current = pageParam.getCurrent();
? ? ? ? long total = pageParam.getTotal();
? ? ? ? long size = pageParam.getSize();
? ? ? ? boolean hasNext = pageParam.hasNext(); //下一頁(yè)
? ? ? ? boolean hasPrevious = pageParam.hasPrevious(); //上一頁(yè)
? ? ? ? Map<String,Object> map = new HashMap<String,Object>();
? ? ? ? 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;
? ? }
二、課程列表前端
1、定義api
api/course.js
import?request?from?'@/utils/request'
export?default?{
??//1?條件查詢帶分頁(yè)查詢課程
??getCourseList(current,limit,searchObj)?{
????return?request({
??????url:?`/eduservice/coursefront/pageCourseCondition/${current}/${limit}`,
??????method:?'post',
??????data:searchObj
????})
??},
??//查詢所有分類的方法
??getAllSubject(){
????return?request({
????????url:?`/eduservice/edu-subject/getAllSubject`,
????????method:?'get'
??????})
??}
}
2、頁(yè)面調(diào)用接口
pages/course/index.vue
<script>
import?courseApi?from?'@/api/course'
export?default?{
??data(){
????return{
??????page:1,
??????data:{},
??????subjectNestedList:?[],?//?一級(jí)分類列表
??????subSubjectList:?[],?//?二級(jí)分類列表
??????searchObj:?{},?//?查詢表單對(duì)象
??????oneIndex:-1,
??????twoIndex:-1,
??????buyCountSort:"",
??????gmtCreateSort:"",
??????priceSort:""
????}
??},
??created(){
????//獲取課程列表
????this.initCourseFirst()
????//獲取分類
????this.initSubject()
??},
??methods:{
????//1?查詢第一頁(yè)數(shù)據(jù)
????initCourseFirst(){
??????courseApi.getCourseList(1,8,this.searchObj).then(response=>{
????????this.data?=?response.data.data
??????})
????},
????//2?查詢所有一級(jí)分類
????initSubject(){
??????courseApi.getAllSubject().then(response=>{
????????this.subjectNestedList?=?response.data.data.list
??????})
????},
????//分頁(yè)切換方法
????gotoPage(page){
???????courseApi.getCourseList(page,8,this.searchObj).then(response=>{
????????this.data?=?response.data.data
??????})
????},
????//4?點(diǎn)擊某個(gè)一級(jí)分類,查詢對(duì)應(yīng)的二級(jí)分類
????searchOne(subjectParentId,index){
??????//把傳遞index值賦值給oneIndex,為了active樣式生效
??????this.oneIndex?=?index
??????//查詢一級(jí)分類,二級(jí)分類不需要值
??????this.twoIndex?=?-1
??????this.searchObj.subjectId?=?"";
??????this.subSubjectList?=?[];
??????//把一級(jí)分類點(diǎn)擊id值,賦值給searchObj
??????this.searchObj.subjectParentId?=?subjectParentId
??????//點(diǎn)擊某個(gè)一級(jí)分類進(jìn)行條件查詢
??????this.gotoPage(1)?//或者gotoPage(this.page)??this.page的默認(rèn)值為1
??????//拿著點(diǎn)擊一級(jí)分類id和所有一級(jí)分類id進(jìn)行比較,
??????//如果id相同,從一級(jí)分類里面獲取對(duì)應(yīng)的二級(jí)分類
??????for(let?i?=0;i<this.subjectNestedList.length;i++){
??????????//獲取每個(gè)一級(jí)分類
??????????var?oneSubject?=?this.subjectNestedList[i]
??????????//如果id值相同
??????????if(subjectParentId?==?oneSubject.id){
??????????????this.subSubjectList?=?oneSubject.children
??????????}
??????}
????},
??}
};
</script>
完善當(dāng)點(diǎn)擊一級(jí)分類時(shí),頁(yè)面中顯示的是該一級(jí)分類下的所有課程:

完善點(diǎn)擊添一級(jí)分類顯示樣式:
<style?scoped>
??.active?{
????background:?#bdbdbd;
??}
??.hide?{
????display:?none;
??}
??.show?{
????display:?block;
??}
</style>
如果oneIndex==index則添加樣式:




結(jié)果:

完善點(diǎn)擊某個(gè)二級(jí)分類實(shí)現(xiàn)查詢


結(jié)果:

完善排序方式顯示
<section?class="fl">
????????????<ol?class="js-tap?clearfix">
??????????????<li?:class="{'current?bg-orange':buyCountSort!=''}">
??????????????<!--?:class="{'current?bg-orange':buyCountSort!=''}"表示如果有值樣式生效?-->
????????????????<a?title="銷量"?href="javascript:void(0);"?@click="searchBuyCount()">銷量
??????????????????<span?:class="{hide:buyCountSort==''}">↓</span>
????????????????</a>
??????????????</li>
??????????????<li?:class="{'current?bg-orange':gmtCreateSort!=''}">
????????????????<a?title="最新"?href="javascript:void(0);"?@click="searchGmtCreate()">最新
??????????????????<span?:class="{hide:gmtCreateSort==''}">↓</span>
????????????????</a>
??????????????</li>
??????????????<li?:class="{'current?bg-orange':priceSort!=''}">
????????????????<a?title="價(jià)格"?href="javascript:void(0);"?@click="searchPrice()">價(jià)格
??????????????????<span?:class="{hide:priceSort==''}">↓</span>
????????????????</a>
??????????????</li>
????????????</ol>
??????????</section>

????//6?根據(jù)銷量進(jìn)行排序
????searchBuyCount(){
??????//設(shè)置對(duì)應(yīng)變量值,為了樣式生效
??????this.buyCountSort?=?"1"
??????this.gmtCreateSort?=?""
??????this.priceSort?=?""
??????//把值賦值到search0bj
??????this.searchObj.buyCountSort?=?this.buyCountSort
??????this.searchObj.gmtCreateSort?=?this.gmtCreateSort
??????this.searchObj.priceSort?=?this.priceSort
??????//調(diào)用方法查詢
??????this.gotoPage(1)
????},
????//7?最新排序
????searchGmtCreate(){
??????//設(shè)置對(duì)應(yīng)變量值,為了樣式生效
??????this.buyCountSort?=?""
??????this.gmtCreateSort?=?"1"
??????this.priceSort?=?""
??????//把值賦值到search0bj
??????this.searchObj.buyCountSort?=?this.buyCountSort
??????this.searchObj.gmtCreateSort?=?this.gmtCreateSort
??????this.searchObj.priceSort?=?this.priceSort
??????//調(diào)用方法查詢
??????this.gotoPage(1)
????},
????//8?價(jià)格排序
????searchPrice(){
??????//設(shè)置對(duì)應(yīng)變量值,為了樣式生效
??????this.buyCountSort?=?""
??????this.gmtCreateSort?=?""
??????this.priceSort?=?"1"
??????//把值賦值到search0bj
??????this.searchObj.buyCountSort?=?this.buyCountSort
??????this.searchObj.gmtCreateSort?=?this.gmtCreateSort
??????this.searchObj.priceSort?=?this.priceSort
??????//調(diào)用方法查詢
??????this.gotoPage(1)
????}

三、課程列表渲染
1、課程類別顯示
????<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="#">全部</a>
????????????????</li>
????????????????<li?v-for="(item,index)?in?subjectNestedList"?:key="index"?:class="{active:oneIndex==index}">
??????????????????<a?:title="item.title"?href="#"?@click="searchOne(item.id,?index)">{{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"??:class="{active:twoIndex==index}">
??????????????????<a?:title="item.title"?href="#"?@click="searchTwo(item.id,?index)">{{item.title}}</a>
????????????????</li>
??????????????</ul>
????????????</dd>
??????????</dl>
??????????<div?class="clear"></div>
????????</section>
2、無(wú)數(shù)據(jù)提示
添加:v-if="data.total==0"
?????????<!--?/無(wú)數(shù)據(jù)提示?開(kāi)始-->
??????????<section?class="no-data-wrap"?v-if="data.total==0">
????????????<em?class="icon30?no-data-ico"> </em>
????????????<span?class="c-666?fsize14?ml10?vam">沒(méi)有相關(guān)數(shù)據(jù),小編正在努力整理中...</span>
??????????</section>
??????????<!--?/無(wú)數(shù)據(jù)提示?結(jié)束-->
3、列表
????<article?v-if="data.total>0"?class="comm-course-list">
????????????<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="開(kāi)始學(xué)習(xí)"?class="comm-btn?c-btn-1">開(kāi)始學(xué)習(xí)</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?v-if="Number(item.price)?===?0"??class="fr?jgTag?bg-green">
??????????????????????<i?class="c-fff?fsize12?f-fA">免費(fèi)</i>
????????????????????</span>
????????????????????<span?class="fl?jgAttr?c-ccc?f-fA">
??????????????????????<i?class="c-999?f-fA">{{?item.viewCount?}}人學(xué)習(xí)</i>
??????????????????????|
??????????????????????<i?class="c-999?f-fA">9634評(píng)論</i>
????????????????????</span>
??????????????????</section>
????????????????</div>
??????????????</li>
????????????</ul>
????????????<div?class="clear"></div>
??????????</article>
4、分頁(yè)頁(yè)面渲染
????<!--?公共分頁(yè)?開(kāi)始?-->
??????????<div>?????
????????????<div?class="paging">
??????????????<!--?undisable這個(gè)class是否存在,取決于數(shù)據(jù)屬性hasPrevious?-->
??????????????<a
????????????????:class="{undisable:?!data.hasPrevious}"
????????????????href="#"
????????????????title="首頁(yè)"
????????????????@click.prevent="gotoPage(1)">首頁(yè)</a>
??????????????<a
????????????????:class="{undisable:?!data.hasPrevious}"
????????????????href="#"
????????????????title="前一頁(yè)"
????????????????@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+'頁(yè)'"
????????????????href="#"
????????????????@click.prevent="gotoPage(page)">{{?page?}}</a>
??????????????<a
????????????????:class="{undisable:?!data.hasNext}"
????????????????href="#"
????????????????title="后一頁(yè)"
????????????????@click.prevent="gotoPage(data.current+1)">></a>
??????????????<a
????????????????:class="{undisable:?!data.hasNext}"
????????????????href="#"
????????????????title="末頁(yè)"
????????????????@click.prevent="gotoPage(data.pages)">末頁(yè)</a>
??????????????<div?class="clear"/>
????????????</div>
??????????</div>
????????<!--?公共分頁(yè)?結(jié)束?-->


03-課程詳情頁(yè)
一、vo對(duì)象的定義
在項(xiàng)目中很多時(shí)候需要把model轉(zhuǎn)換成dto用于網(wǎng)站信息的展示,按前端的需要傳遞對(duì)象的數(shù)據(jù),保證model對(duì)外是隱私的,例如密碼之類的屬性能很好地避免暴露在外,同時(shí)也會(huì)減小數(shù)據(jù)傳輸?shù)捏w積。
CourseWebInfoVo.java(用于封裝課程的詳情信息)
@ApiModel(value="課程信息", description="網(wǎng)站課程詳情頁(yè)需要的相關(guān)字段")
@Data
public class CourseWebInfoVo {
? ? private String id;
? ? @ApiModelProperty(value = "課程標(biāo)題")
? ? private String title;
? ? @ApiModelProperty(value = "課程銷售價(jià)格,設(shè)置為0則可免費(fèi)觀看")
? ? private BigDecimal price;
? ? @ApiModelProperty(value = "總課時(shí)")
? ? private Integer lessonNum;
? ? @ApiModelProperty(value = "課程封面圖片路徑")
? ? private String cover;
? ? @ApiModelProperty(value = "銷售數(shù)量")
? ? private Long buyCount;
? ? @ApiModelProperty(value = "瀏覽數(shù)量")
? ? private Long viewCount;
? ? @ApiModelProperty(value = "課程簡(jiǎn)介")
? ? private String description;
? ? @ApiModelProperty(value = "講師ID")
? ? private String teacherId;
? ? @ApiModelProperty(value = "講師姓名")
? ? private String teacherName;
? ? @ApiModelProperty(value = "講師資歷,一句話說(shuō)明講師")
? ? private String intro;
? ? @ApiModelProperty(value = "講師頭像")
? ? private String avatar;
? ? @ApiModelProperty(value = "課程一級(jí)類別ID")
? ? private String subjectLevelOneId;
? ? @ApiModelProperty(value = "類別一級(jí)名稱")
? ? private String subjectLevelOne;
? ? @ApiModelProperty(value = "課程二級(jí)類別ID")
? ? private String subjectLevelTwoId;
? ? @ApiModelProperty(value = "類別二級(jí)名稱")
? ? private String subjectLevelTwo;
}
二、課程和講師信息的獲取
1、CourseFrontController
? ?? //根據(jù)課程id,編寫(xiě)sq1語(yǔ)句查詢課程信息
????@ApiOperation(value = "根據(jù)ID查詢課程")
? ? @GetMapping("getFrontCourseInfo/{courseId}")
? ? public R getFrontCourseInfo(@PathVariable String courseId){
? ? ? ? //根據(jù)課程id,編寫(xiě)sql語(yǔ)句查詢課程信息
? ? ? ? CourseWebInfoVo courseWebInfoVo = courseService.getBaseCourseInfo(courseId);
? ? ? ? //根據(jù)課程id查詢章節(jié)和小節(jié)
? ? ? ? List<ChapterVo> chapterVideoByCourseId = chapterService.getChapterVideoByCourseId(courseId);
? ? ? ? return R.ok().data("courseWebVo",courseWebInfoVo).data("chapterVideoList",chapterVideoByCourseId);
? ? }
2、EduCourseService
? ? //根據(jù)課程id,編寫(xiě)sq1語(yǔ)句查詢課程信息
? ? CourseWebInfoVo getBaseCourseInfo(String courseId);
3、EduCourseServiceImpl
????//根據(jù)課程id,編寫(xiě)sq1語(yǔ)句查詢課程信息
? ? @Override
? ? public CourseWebInfoVo getBaseCourseInfo(String courseId) {
? ? ? ? return baseMapper.getBaseCourseInfo(courseId);
? ? }
4、Mapper中關(guān)聯(lián)查詢課程和講師信息
EduCourseMapper.java
????//根據(jù)課程id,編寫(xiě)sq1語(yǔ)句查詢課程信息
? ? CourseWebInfoVo getBaseCourseInfo(String courseId);
EduCourseMapper.xml
????<!-- sql語(yǔ)句:根據(jù)課程id,編寫(xiě)sq1語(yǔ)句查詢課程信息 -->
? ? <select id="getBaseCourseInfo" resultType="com.atguigu.eduservice.entity.frontvo.CourseWebInfoVo">
? ? ? ? SELECT ec.id,ec.title,ec.price,ec.lesson_num AS lessonNum,ec.cover,
? ? ? ? ? ? ? ec.buy_count AS buyCount,ec.view_count AS viewCount,
? ? ? ? ? ? ? ecd.description,
? ? ? ? ? ? ? et.id AS teacherId,et.name AS 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_course_description ecd ON ec.id=ecd.id
? ? ? ? ? ? ? ? ? ? ? ? LEFT OUTER JOIN edu_teacher et ON ec.teacher_id=et.id
? ? ? ? ? ? ? ? ? ? ? ? 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
? ? ? ? ? ? WHERE ec.id=#{courseId}
? ? </select>
因?yàn)楝F(xiàn)在方法中只有一個(gè)參數(shù),當(dāng)只有一個(gè)參數(shù)時(shí),WHEREec.id=#{courseId}中courseId可以隨便寫(xiě)。
默認(rèn)xml文件不加載問(wèn)題:


三、前端js
1、api/course.js
? //?課程詳情的方法
??getCourseInfo(courseId){
????return?request({
????????url:?`/eduservice/coursefront/getFrontCourseInfo/${courseId}`,
????????method:?'get'
????})
??}
2、pages/course/_id.vue
<script>
import?courseApi?from?'@/api/course'
export?default?{
??asyncData({?params,?error?})?{
????return?courseApi.getCourseInfo(params.id).then(response=>{
??????return?{
????????courseWebVo:response.data.data.courseWebVo,
????????chapterVideoList:response.data.data.chapterVideoList
??????}
????})
??}
};
</script>
四、頁(yè)面模板
pages/course/_id.vue
1、課程所屬分類
???<!--?課程所屬分類?開(kāi)始?-->
????<section?class="container">
??????<section?class="path-wrap?txtOf?hLh30">
????????<a?href="#"?title?class="c-999?fsize14">首頁(yè)</a>
????????\
????????<a?href="#"?title?class="c-999?fsize14">課程列表</a>
????????\
????????<span?class="c-333?fsize14">{{courseWebVo.subjectLevelOne}}</span>
????????\
????????<span?class="c-333?fsize14">{{courseWebVo.subjectLevelTwo}}</span>
??????</section>
??????<!--?/課程所屬分類?結(jié)束?-->
2、課程基本信息
??????<!--?課程基本信息?開(kāi)始?-->
??????<div>
????????<article?class="c-v-pic-wrap"?style="height:?357px;">
??????????<section?class="p-h-video-box"?id="videoPlay">
????????????<img?:src="courseWebVo.cover"?:alt="courseWebVo.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">{{courseWebVo.title}}</span>
????????????</h2>
????????????<section?class="c-attr-jg">
??????????????<span?class="c-fff">價(jià)格:</span>
??????????????<b?class="c-yellow"?style="font-size:24px;">¥{{courseWebVo.price}}</b>
????????????</section>
????????????<section?class="c-attr-mt?c-attr-undis">
??????????????<span?class="c-fff?fsize14">主講:?{{courseWebVo.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">購(gòu)買數(shù)</span>
????????????????<br>
????????????????<h6?class="c-fff?f-fM?mt10">{{courseWebVo.buyCount}}</h6>
??????????????</aside>
????????????</li>
????????????<li>
??????????????<p> </p>
??????????????<aside>
????????????????<span?class="c-fff?f-fM">課時(shí)數(shù)</span>
????????????????<br>
????????????????<h6?class="c-fff?f-fM?mt10">{{courseWebVo.lessonNum}}</h6>
??????????????</aside>
????????????</li>
????????????<li>
??????????????<p> </p>
??????????????<aside>
????????????????<span?class="c-fff?f-fM">瀏覽數(shù)</span>
????????????????<br>
????????????????<h6?class="c-fff?f-fM?mt10">{{courseWebVo.viewCount}}</h6>
??????????????</aside>
????????????</li>
??????????</ol>
????????</aside>
????????<div?class="clear"></div>
??????</div>
??????<!--?/課程基本信息?結(jié)束?-->
3、課程詳情介紹
在后端添加課程簡(jiǎn)介時(shí)可以添加樣式,但是?{{courseWebVo.description}}只是進(jìn)行了原樣輸出。

????????????????????<!--?將內(nèi)容中的html翻譯過(guò)來(lái)?-->
??????????????????????<p?v-html="courseWebVo.description">
????????????????????????{{courseWebVo.description}}
??????????????????????</p>

4、課程大綱
????????????<!--?課程大綱?開(kāi)始-->
????????????????<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="#"?title>
??????????????????????????????????<span?class="fr">
????????????????????????????????????<i?class="free-icon?vam?mr10"?v-if="video.free?===?true">免費(fèi)試聽(tīng)</i>
??????????????????????????????????</span>
??????????????????????????????????<em?class="lh-menu-i-2?icon16?mr5"> </em>{{video.title}}
????????????????????????????????</a>
??????????????????????????????</li>
????????????????????????????</ol>
??????????????????????????</li>
????????????????????????</ul>
??????????????????????</menu>
????????????????????</div>
??????????????????</section>
????????????????</div>
????????????????<!--?/課程大綱?結(jié)束?-->
5、主講講師
???<!--?主講講師?開(kāi)始-->
????????????<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/'+courseWebVo.teacherId">
????????????????????????<img?:src="courseWebVo.avatar"?width="50"?height="50"?alt>
??????????????????????</a>
????????????????????</div>
????????????????????<section?class="hLh30?txtOf">
?????????????????????? <a?class="c-333?fsize16?fl"?:href="'/teacher/'+courseWebVo.teacherId"> {{courseWebVo.teacherName}}</a>
????????????????????</section>
????????????????????<section?class="hLh20?txtOf">
??????????????????????<span?class="c-999">{{courseWebVo.intro}}</span>
????????????????????</section>
??????????????????</li>
????????????????</ul>
??????????????</section>
????????????</div>
????????????<!--?/主講講師?結(jié)束?-->
最終顯示結(jié)果:

04-視頻播放測(cè)試
一、獲取播放地址播放
獲取播放地址
參考文檔:https://help.aliyun.com/document_detail/61064.html
前面的?03-使用服務(wù)端SDK?介紹了如何獲取非加密視頻的播放地址。直接使用03節(jié)的例子獲取加密視頻播放地址會(huì)返回如下錯(cuò)誤信息
Currently only the AliyunVoDEncryption stream exists, you must use the Aliyun player to play or set the value of ResultType to Multiple.
目前只有AliyunVoDEncryption流存在,您必須使用Aliyun player來(lái)播放或?qū)esultType的值設(shè)置為Multiple。
因此在testGetPlayInfo測(cè)試方法中添加?ResultType?參數(shù),并設(shè)置為true
privateParams.put("ResultType","Multiple");
此種方式獲取的視頻文件不能直接播放,必須使用阿里云播放器播放
二、視頻播放器
參考文檔:https://help.aliyun.com/document_detail/61109.html
1、視頻播放器介紹
阿里云播放器SDK(ApsaraVideo Player SDK)是阿里視頻服務(wù)的重要一環(huán),除了支持點(diǎn)播和直播的基礎(chǔ)播放功能外,深度融合視頻云業(yè)務(wù),如支持視頻的加密播放、安全下載、清晰度切換、直播答題等業(yè)務(wù)場(chǎng)景,為用戶提供簡(jiǎn)單、快速、安全、穩(wěn)定的視頻播放服務(wù)。
2、集成視頻播放器
參考文檔:https://help.aliyun.com/document_detail/51991.html
參考 【播放器簡(jiǎn)單使用說(shuō)明】一節(jié)
引入腳本文件和css文件
?????<link?rel="stylesheet"??/>
????<script?charset="utf-8"?type="text/javascript"?src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"></script>
初始化視頻播放器
<body>
????<div??class="prism-player"?id="J_prismPlayer"></div>
????????<script>
????????????var?player?=?new?Aliplayer({
????????????????id:?'J_prismPlayer',
????????????????width:?'100%',
????????????????autoplay:?false,
????????????????cover:?'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
????????????????//播放配置
????????????},function(player){
????????????????console.log('播放器創(chuàng)建好了。')
????????????});
????????</script>
</body>
3、播放地址播放
在Aliplayer的配置參數(shù)中添加如下屬性
????????????????//播放方式一:支持播放地址播放,此播放優(yōu)先級(jí)最高,此種方式不能播放加密視頻
????????????????source?:?'你的視頻播放地址',
測(cè)試:創(chuàng)建html文件(01播放地址播放.html)
<!DOCTYPE?html>
<html?lang="en">
<head>
????<meta?charset="UTF-8">
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0">
????<title>Document</title>
????<link?rel="stylesheet"??/>
????<script?charset="utf-8"?type="text/javascript"?src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"></script>
</head>
<body>
????<div??class="prism-player"?id="J_prismPlayer"></div>
????????<script>
????????????var?player?=?new?Aliplayer({
????????????????id:?'J_prismPlayer',
????????????????width:?'100%',
????????????????autoplay:?false,
????????????????cover:?'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
????????????????//播放配置
????????????????//播放方式一:支持播放地址播放,此播放優(yōu)先級(jí)最高,此種方式不能播放加密視頻
????????????????source?:?'https://outin-b2b9512bc4dc11ea89cf00163e1c60dc.oss-cn-shanghai.aliyuncs.com/sv/25884263-1734c67e05f/25884263-1734c67e05f.mp4?Expires=1595476184&OSSAccessKeyId=LTAIxSaOfEzCnBOj&Signature=wj2Q5wH1m2snNTmPyDSo6cD4YTU%3D',
????????????},function(player){
????????????????console.log('播放器創(chuàng)建好了。')
????????????});
????????</script>
</body>
</html>
啟動(dòng)瀏覽器運(yùn)行,測(cè)試視頻的播放:

4、播放憑證播放(推薦)
阿里云播放器支持通過(guò)播放憑證自動(dòng)換取播放地址進(jìn)行播放,接入方式更為簡(jiǎn)單,且安全性更高。播放憑證默認(rèn)時(shí)效為100秒(最大為3000秒),只能用于獲取指定視頻的播放地址,不能混用或重復(fù)使用。如果憑證過(guò)期則無(wú)法獲取播放地址,需要重新獲取憑證。
encryptType:'1',//如果播放加密視頻,則需設(shè)置encryptType=1,非加密視頻無(wú)需設(shè)置此項(xiàng)
vid:'視頻id',
playauth:'視頻授權(quán)碼',
注意:播放憑證有過(guò)期時(shí)間,默認(rèn)值:100秒?。取值范圍:100~3000。
設(shè)置播放憑證的有效期
在獲取播放憑證的測(cè)試用例中添加如下代碼
request.setAuthInfoTimeout(200L);
在線配置參考:https://player.alicdn.com/aliplayer/setting/setting.html
測(cè)試:創(chuàng)建html文件(02播放憑證播放.html)
playauth是根據(jù)視頻id獲取。


<!DOCTYPE?html>
<html?lang="en">
<head>
????<meta?charset="UTF-8">
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0">
????<title>Document</title>
????<link?rel="stylesheet"??/>
????<script?charset="utf-8"?type="text/javascript"?src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js"></script>
</head>
<body>
????<div??class="prism-player"?id="J_prismPlayer"></div>
????????<script>
????????????var?player?=?new?Aliplayer({
????????????????id:?'J_prismPlayer',
????????????????width:?'100%',
????????????????autoplay:?false,
????????????????cover:?'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
????????????????//播放配置
????????????????//播放憑證播放
????????????????encryptType:'1',//如果播放加密視頻,則需設(shè)置encryptType=1,非加密視頻無(wú)需設(shè)置此項(xiàng)
????????????????vid?:?'07d8aac8a3b045c69cd91b84bdbedc19',
????????????????playauth?:?'eyJTZWN1cml0eVRva2VuIjoiQ0FJUzN3SjFxNkZ0NUIyeWZTaklyNWZrQ01uUnZMbEc0UFNETkdEZ3FITVVhc2RobjUvZ2pUejJJSGhKZVhOdkJPMGV0ZjQrbVdCWTdQY1lsck1xRXM0VUhST1lQSklwc01VSXJsUDRKcExGc3QySjZyOEpqc1V6aE5vMTFGaXBzdlhKYXNEVkVma3VFNVhFTWlJNS8wMGU2TC8rY2lyWVhEN0JHSmFWaUpsaFE4MEtWdzJqRjFSdkQ4dFhJUTBRazYxOUszemRaOW1nTGlidWkzdnhDa1J2MkhCaWptOHR4cW1qL015UTV4MzFpMXYweStCM3dZSHRPY3FjYThCOU1ZMVdUc3Uxdm9oemFyR1Q2Q3BaK2psTStxQVU2cWxZNG1YcnM5cUhFa0ZOd0JpWFNaMjJsT2RpTndoa2ZLTTNOcmRacGZ6bjc1MUN0L2ZVaXA3OHhtUW1YNGdYY1Z5R0dOLzZuNU9aUXJ6emI0WmhKZWVsQVJtWGpJRFRiS3VTbWhnL2ZIY1dPRGxOZjljY01YSnFBWFF1TUdxQ2QvTDlwdzJYT2x6NUd2WFZnUHRuaTRBSjVsSHA3TWVNR1YrRGVMeVF5aDBFSWFVN2EwNDQvNWVUWWFwazFNVWFnQUU1cXBMc2ZOQVB4S2lmQWRGbXZySjQyaWNaQVYyd3A4T05OODlVR1Q2NHEySkJnSE9NYlV3aHRYQUJvUmhBSXZLbVJtbVdIQnBwQ0hmRHB3cUxoNkRtUGw3bTQ0QUtrb0J0R0w0YmR2eUFMbGtBbWI0MWdWcXM2UERlVkUybTRHQ1BvRDNZNkFIUGJpYlpjWEJCQzNnaFpXOGhTa1dqMkJieFFKWHJEK09ZSFE9PSIsIkF1dGhJbmZvIjoie1wiQ0lcIjpcIkdCU2NldzJNQ0J4eHQ1d2MydXgyang0VTZzTFRESWtYMC9jMGpONnhiclFJUjR5MldxTHdLdzE4clJnRVZlTVJcXHJcXG5cIixcIkNhbGxlclwiOlwiYWovb2tGOC9CTFZOR0p2VEJlWHZWS3VYbE9oU1I3QldOVGRPRGVwVlB6az1cXHJcXG5cIixcIkV4cGlyZVRpbWVcIjpcIjIwMjAtMDctMjNUMDM6MTE6MThaXCIsXCJNZWRpYUlkXCI6XCIwN2Q4YWFjOGEzYjA0NWM2OWNkOTFiODRiZGJlZGMxOVwiLFwiU2lnbmF0dXJlXCI6XCJqcWRBRHp5OXliNkZJZmxLQlBDTHNyT3V4eVU9XCJ9IiwiVmlkZW9NZXRhIjp7IlN0YXR1cyI6Ik5vcm1hbCIsIlZpZGVvSWQiOiIwN2Q4YWFjOGEzYjA0NWM2OWNkOTFiODRiZGJlZGMxOSIsIlRpdGxlIjoiNiAtIFdoYXQgSWYgSSBXYW50IHRvIE1vdmUgRmFzdGVyIiwiQ292ZXJVUkwiOiJodHRwOi8vb3V0aW4tYjJiOTUxMmJjNGRjMTFlYTg5Y2YwMDE2M2UxYzYwZGMub3NzLWNuLXNoYW5naGFpLmFsaXl1bmNzLmNvbS8wN2Q4YWFjOGEzYjA0NWM2OWNkOTFiODRiZGJlZGMxOS9zbmFwc2hvdHMvNTkxZDRiYjM1MGQ1NDM4M2IzYWM4ZGVjYzI0ZGM0NGEtMDAwMDEuanBnP0V4cGlyZXM9MTU5NTQ3NzM3OCZPU1NBY2Nlc3NLZXlJZD1MVEFJeFNhT2ZFekNuQk9qJlNpZ25hdHVyZT13d0NieXh2R0gxeW1xM0g0d3RzMHlRTFMyelUlM0QiLCJEdXJhdGlvbiI6MTYuMjc2N30sIkFjY2Vzc0tleUlkIjoiU1RTLk5UUUNzZVFmZ1c2aDZGUUxzQWZITXBaS28iLCJBY2Nlc3NLZXlTZWNyZXQiOiJwNlNQdGZoOUhXS1FRMzZESERuOEg4QWlDTHRwM2tqODQ5V2hOeDRIUXZuIiwiUmVnaW9uIjoiY24tc2hhbmdoYWkiLCJDdXN0b21lcklkIjoxMTk5MDc0OTExNjk3NzgxfQ',????????????????
????????????},function(player){
????????????????console.log('播放器創(chuàng)建好了。')
????????????});
????????</script>
</body>
</html>
啟動(dòng)瀏覽器運(yùn)行,測(cè)試視頻的播放:

05-整合阿里云視頻播放器
一、后端獲取播放憑證
1、VodController
service-vod微服務(wù)中創(chuàng)建 VodController.java
controller中創(chuàng)建 getPlayAuth接口方法
????//根據(jù)視頻id獲取視頻憑證
? ? @GetMapping("getPlayAuth/{id}")
? ? public R getPlayAuth(@PathVariable String id){
? ? ? ? try {
? ? ? ? ? ? //創(chuàng)建初始化對(duì)象
? ? ? ? ? ? DefaultAcsClient client = InitObjectV.initVodClient(ConstantPropertiesUtil.ACCESS_KEY_ID,ConstantPropertiesUtil.ACCESS_KEY_SECRET);
? ? ? ? ? ? //創(chuàng)建獲取憑證request和response對(duì)象
? ? ? ? ? ? GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
? ? ? ? ? ? //向request設(shè)置視頻id
? ? ? ? ? ? request.setVideoId(id);
? ? ? ? ? ? //調(diào)用方法得到憑證
? ? ? ? ? ? GetVideoPlayAuthResponse response = client.getAcsResponse(request);
? ? ? ? ? ? String playAuth = response.getPlayAuth();
? ? ? ? ? ? return R.ok().data("playAuth",playAuth);
? ? ? ? } catch (ClientException e) {
? ? ? ? ? ? throw new GuliException(20001,"獲取憑證失敗");
? ? ? ? }
? ? }
2、Swagger測(cè)試

二、前端播放器整合

1、點(diǎn)擊播放超鏈接
course/_id.vue
修改課時(shí)目錄超鏈接

2、layout
因?yàn)椴シ牌鞯牟季趾推渌?yè)面的基本布局不一致,因此創(chuàng)建新的布局容器 layouts/video.vue
<template>
??<div?class="guli-player">
????<div?class="head">
??????<a?href="#"?title="谷粒學(xué)院">
????????<img?class="logo"?src="~/assets/img/logo.png"?lt="谷粒學(xué)院">
??????</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
創(chuàng)建api模塊 api/vod.js,從后端獲取播放憑證
import?request?from?'@/utils/request'
export?default?{
????//根據(jù)視頻id獲取視頻憑證
????getPlayAuth(vid)?{
????????return?request({
????????????url:?`/eduvod/video/getPlayAuth/${vid}`,
????????????method:?'get'
????????})
????}
}
4、播放組件相關(guān)文檔
集成文檔: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、創(chuàng)建播放頁(yè)面
創(chuàng)建pages/player/_vid.vue
(1)引入播放器js庫(kù)和css樣式
<template>
??<div>
????<!--?阿里云視頻播放器樣式?-->
????<link?rel="stylesheet"??>
????<!--?阿里云視頻播放器腳本?-->
????<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)獲取播放憑證

(3)創(chuàng)建播放器
?????/**
?????*?頁(yè)面渲染完成時(shí):此時(shí)js腳本已加載,Aliplayer已定義,可以使用
?????*?如果在created生命周期函數(shù)中使用,Aliplayer?is?not?defined錯(cuò)誤
?????*/
????mounted()?{
????????new?Aliplayer({
????????????id:?'J_prismPlayer',
????????????vid:?this.vid,?//?視頻id
????????????playauth:?this.playAuth,?//?播放憑證
????????????encryptType:?'1',?//?如果播放加密視頻,則需設(shè)置encryptType=1,非加密視頻無(wú)需設(shè)置此項(xiàng)
????????????width:?'100%',
????????????height:?'500px'
????????},?function(player)?{
????????????console.log('播放器創(chuàng)建成功')
????????})
????}

(4)其他常見(jiàn)的可選配置
????????//?以下可選設(shè)置
????????????cover:?'http://guli.shop/photo/banner/1525939573202.jpg',?//?封面
????????????qualitySort:?'asc',?//?清晰度排序
????????????mediaType:?'video',?//?返回音頻還是視頻
????????????autoplay:?false,?//?自動(dòng)播放
????????????isLive:?false,?//?直播
????????????rePlay:?false,?//?循環(huán)播放
????????????preload:?true,
????????????controlBarVisibility:?'hover',?//?控制條的顯示方式:鼠標(biāo)懸停
????????????useH5Prism:?true,?//?播放器類型:html5

6、加入播放組件
功能展示:https://player.alicdn.com/aliplayer/presentation/index.html