课程条件查询带分页功能
后端接口
创建 vo 对象封装条件数据
在 service_edu 模块中的 entity 包下创建 frontvo 包,然后创建类:
@ApiModel(value = "课程查询对象", description = "课程查询对象封装")
@Data
public class CourseFrontVo {
@ApiModelProperty(value = "课程名称")
private String title;
@ApiModelProperty(value = "讲师id")
private String teacherId;
@ApiModelProperty(value = "一级类别id")
private String subjectParentId;
@ApiModelProperty(value = "二级类别id")
private String subjectId;
@ApiModelProperty(value = "销量排序")
private String buyCountSort;
@ApiModelProperty(value = "最新时间排序")
private String gmtCreateSort;
@ApiModelProperty(value = "价格排序")
private String priceSort;
}
controller 层
在 controller/front 包下,创建类:
@RestController
@RequestMapping("/eduservice/coursefront")
@CrossOrigin
public class CourseFrontController {
@Autowired
private EduCourseService courseService;
@ApiOperation("课程条件查询带分页")
@PostMapping("getFrontCourseList/{page}/{limit}")
public R getFrontCourseList(
@PathVariable("page") long page,
@PathVariable("limit") long limit,
@RequestBody(required = false) CourseFrontVo courseFrontVo) {
Page<EduCourse> pageCourse = new Page<>(page, limit);
Map<String, Object> map = courseService.getCourseFrontList(pageCourse, courseFrontVo);
return R.ok().data(map);
}
}
service 层
EduCourseService 增加方法:
// 课程条件查询带分页
Map<String, Object> getCourseFrontList(Page<EduCourse> pageCourse, CourseFrontVo courseFrontVo);
实现类:
// 课程条件查询带分页
@Override
public Map<String, Object> getCourseFrontList(Page<EduCourse> pageCourse, CourseFrontVo courseFrontVo) {
// 根据讲师 id 查询所讲课程
QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
// 判断条件值是否为空,不为空拼接
if (!StringUtils.isEmpty(courseFrontVo.getSubjectParentId())) { // 一级分类
wrapper.eq("subject_parent_id", courseFrontVo.getSubjectParentId());
}
if (!StringUtils.isEmpty(courseFrontVo.getSubjectId())) { // 二级分类
wrapper.eq("subject_id", courseFrontVo.getSubjectId());
}
if (!StringUtils.isEmpty(courseFrontVo.getBuyCountSort())) { // 销量
wrapper.orderByDesc("buy_count");
}
if (!StringUtils.isEmpty(courseFrontVo.getGmtCreateSort())) { // 更新时间
wrapper.orderByDesc("gmt_create");
}
if (!StringUtils.isEmpty(courseFrontVo.getPriceSort())) { // 价格
wrapper.orderByDesc("price");
}
baseMapper.selectPage(pageCourse, wrapper);
List<EduCourse> records = pageCourse.getRecords();
long current = pageCourse.getCurrent();
long pages = pageCourse.getPages();
long size = pageCourse.getSize();
long total = pageCourse.getTotal();
boolean hasNext = pageCourse.hasNext();
boolean hasPrevious = pageCourse.hasPrevious();
// 把分页数据获取出来放到 map
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;
}
前端实现
js 中定义方法
在 api 目录下创建 course.js
import request from '@/utils/request'
export default{
// 条件分页查询课程信息
getCourseList(page, limit, searchObj) {
return request({
url: `/eduservice/coursefront/getFrontCourseList/${page}/${limit}`,
method: 'post',
data: searchObj
})
},
// 查询所有分类的方法
getAllSubject(teacherId) {
return request({
url: '/eduservice/subject/getAllSubject',
method: 'get'
})
}
}
Vue 页面中调用(简化了条件查询,又加上了价格和评论数)
在 pages/course/index.vue 中:
<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 :class="{ active: allIndex }">
<a title="全部" href="#" @click="allCourse()">全部</a>
</li>
<li v-for="(item, index) in subjectNestedList" :key="item.id" :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="item.id" :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>
<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': countSort === 1 }">
<a title="销量" href="javascript:void(0);" @click="searchBuyCount()">销量
<span :class="{ hide: countSort !== 1 }">↓</span>
</a>
</li>
<li :class="{ 'current bg-orange': countSort === 2 }">
<a title="最新" href="javascript:void(0);" @click="searchGmtCreate()">最新
<span :class="{ hide: countSort != 2 }">↓</span>
</a>
</li>
<li :class="{ 'current bg-orange': countSort === 3 }">
<a title="价格" href="javascript:void(0);" @click="searchPrice()">价格
<span :class="{ hide: countSort !== 3 }">↓</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 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" style="width:267.5px; height:180px">
<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 v-if="Number(item.price) === 0" class="fr jgTag bg-green">
<i class="c-fff fsize12 f-fA">免费</i>
</span>
<span class="fr jgTag bg-green" v-else style="background-color: red;">
<i class="c-fff fsize18 f-fA">¥{{ item.price }}</i>
</span>
<span class="fl jgAttr c-ccc f-fA">
<i class="c-999 f-fA">{{ item.viewCount }}人学习</i>
|
<i class="c-999 f-fA">{{ item.commentCount }}人评论</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>
<script>
import courseApi from '@/api/course'
export default {
data() {
return {
page: 1, // 当前页
data: {}, // 课程列表
subjectNestedList: [], // 一级分类列表
subSubjectList: [], // 二级分类列表
searchObj: {}, // 查询表单对象
allIndex: true, // 是否选中 全部
oneIndex: -1, // 是否选中 一级分类
twoIndex: -1, // 是否选中 二级分类
countSort: '', // 是否选中 排序规则
}
},
created() {
//课程第一次查询
this.allCourse()
//一级分类显示
this.initSubject()
},
methods: {
//1 分页查询
gotoPage(page) {
courseApi.getCourseList(page, 4, this.searchObj).then(response => {
this.data = response.data.data
})
},
//2 查询全部课程
allCourse() {
this.allIndex = true
//清空
this.oneIndex = -1
this.searchObj.subjectParentId = ''
this.searchObj.subjectId = ''
this.subSubjectList = []
this.gotoPage(1);
},
//3 查询所有一级分类
initSubject() {
courseApi.getAllSubject()
.then(response => {
this.subjectNestedList = response.data.data.list
})
},
//4 点击某个一级分类,查询对应二级分类
searchOne(subjectParentId, index) {
//把传递index值赋值给oneIndex,为了active样式生效
this.oneIndex = index
//清空
this.allIndex = false
this.twoIndex = -1
this.searchObj.subjectId = ''
this.subSubjectList = []
//把点击一级分类id值,赋值给searchObj
this.searchObj.subjectParentId = subjectParentId
//点击某个一级分类进行条件查询
this.gotoPage(1)
//根据当前一级分类索引,获取二级分类
this.subSubjectList = this.subjectNestedList[index].children
},
//5 点击某个二级分类实现查询
searchTwo(subjectId, index) {
//把index赋值,为了样式生效
this.twoIndex = index
//把二级分类点击id值,赋值给searchObj
this.searchObj.subjectId = subjectId
//点击某个二级分类进行条件查询
this.gotoPage(1)
},
//6 销量排序
searchBuyCount() {
//设置对应变量值,为了样式生效
this.countSort = 1
//把值赋值到searchObj
this.searchObj.buyCountSort = this.countSort
this.searchObj.gmtCreateSort = ''
this.searchObj.priceSort = ''
//调用方法查询
this.gotoPage(1)
},
//7 最新排序
searchGmtCreate() {
//设置对应变量值,为了样式生效
this.countSort = 2
//把值赋值到searchObj
this.searchObj.buyCountSort = ''
this.searchObj.gmtCreateSort = this.countSort
this.searchObj.priceSort = ''
//调用方法查询
this.gotoPage(1)
},
//8 价格排序
searchPrice() {
//设置对应变量值,为了样式生效
this.countSort = 3
//把值赋值到searchObj
this.searchObj.buyCountSort = ''
this.searchObj.gmtCreateSort = ''
this.searchObj.priceSort = this.countSort
//调用方法查询
this.gotoPage(1)
}
}
};
</script>
<style scoped>
.active {
background: #68cb9b;
}
.hide {
display: none;
}
.show {
display: block;
}
</style>
课程详情功能(包含评论功能)
后端接口(课程详情)
vo 实体类
@Data
@ApiModel(value = "课程信息", description = "网站课程详情页需要的相关字段")
public class CourseWebVo implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
@ApiModelProperty(value = "课程标题")
private String title;
@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
private BigDecimal price;
@ApiModelProperty(value = "总课时")
private Integer lessonNum;
@ApiModelProperty(value = "课程封面图片路径")
private String cover;
@ApiModelProperty(value = "销售数量")
private Long buyCount;
@ApiModelProperty(value = "浏览数量")
private Long viewCount;
@ApiModelProperty(value = "课程简介")
private String description;
@ApiModelProperty(value = "讲师ID")
private String teacherId;
@ApiModelProperty(value = "讲师姓名")
private String teacherName;
@ApiModelProperty(value = "讲师资历")
private String intro;
@ApiModelProperty(value = "讲师头像")
private String avatar;
@ApiModelProperty(value = "课程类别ID")
private String subjectLevelOneId;
@ApiModelProperty(value = "类别名称")
private String subjectLevelOne;
@ApiModelProperty(value = "课程类别ID")
private String subjectLevelTwoId;
@ApiModelProperty(value = "类别名称")
private String subjectLevelTwo;
}
controller 层
CourseFrontController 添加方法:
@Autowired
private EduChapterService chapterService;
@ApiOperation("查询课程详情")
@GetMapping("getFrontCourseInfo/{courseId}")
public R getFrontCourseInfo(@PathVariable("courseId") String courseId) {
// 根据课程id,编写sql语句查询课程信息
CourseWebVo courseWebVo = courseService.getBaseCourseInfo(courseId);
// 根据课程ID,查询章节和小节
List<ChapterVo> chapterVideoList = chapterService.getChapterVideoByCourseId(courseId);
return R.ok().data("courseWebVo", courseWebVo).data("chapterVideoList", chapterVideoList);
}
service 层
EduCourseService 添加:
// 根据课程id,编写sql语句查询课程信息
CourseWebVo getBaseCourseInfo(String courseId);
实现类:
// 根据课程id,编写sql语句查询课程信息
@Override
public CourseWebVo getBaseCourseInfo(String courseId) {
return baseMapper.getBaseCourseInfo(courseId);
}
mapper 层
EduCourseMapper 添加
// 根据课程id,编写sql语句查询课程信息
CourseWebVo getBaseCourseInfo(String courseId);
对应的 XML 文件添加:
<!--sql语句,根据课程id查询课程详情信息-->
<select id="getBaseCourseInfo" resultType="org.jyunkai.eduservice.entity.frontvo.CourseWebVo">
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 JOIN edu_course_description ecd ON ec.id = ecd.id
LEFT JOIN edu_teacher et ON ec.teacher_id = et.id
LEFT JOIN edu_subject es1 ON ec.subject_parent_id = es1.id
LEFT JOIN edu_subject es2 ON ec.subject_id = es2.id
WHERE
ec.id = #{courseId}
</select>
注意:application.properties 中加入:
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/atguigu/eduservice/mapper/xml/*.xml
后端接口(评论)
实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="EduComment对象", description="评论")
public class EduComment implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "评论ID")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
@ApiModelProperty(value = "课程id")
private String courseId;
@ApiModelProperty(value = "会员id")
private String memberId;
@ApiModelProperty(value = "会员昵称")
private String nickname;
@ApiModelProperty(value = "会员头像")
private String avatar;
@ApiModelProperty(value = "评论内容")
private String content;
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
@TableLogic(value = "0",delval = "1")
private Integer isDeleted;
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}
controller层
@RestController
@RequestMapping("/eduservice/comment")
public class EduCommentController {
@Autowired
private EduCommentService eduCommentService;
@Autowired
private EduCourseService eduCourseService;
//根据课程id查询评论
@GetMapping("findAll/{id}")
public R findAllComment(@PathVariable String id) {
QueryWrapper<EduComment> wrapper = new QueryWrapper<>();
wrapper.eq("course_id", id).orderByDesc("gmt_create");
List<EduComment> commentList = eduCommentService.list(wrapper);
return R.ok().data("commentList", commentList);
}
//添加评论
@PostMapping("addComment")
public R addComment(@RequestBody EduComment eduComment) {
//添加评论到评论表
eduCommentService.save(eduComment);
//查询评论数
QueryWrapper<EduComment> wrapper = new QueryWrapper<>();
wrapper.eq("course_id", eduComment.getCourseId());
int count = eduCommentService.count(wrapper);
//更新课程表中的评论数
EduCourse course = eduCourseService.getById(eduComment.getCourseId());
course.setCommentCount(count);
eduCourseService.updateById(course);
return R.ok();
}
}
service层(无)
前端实现
api/course.js 课程详情
// 查询课程详情
getCourseInfo(courseId) {
return request({
url: `/eduservice/coursefront/getFrontCourseInfo/${courseId}`,
method: 'get'
})
}
api/comment.js 课程评论
//查询课程评论
getCommentList(id) {
return request({
url: `/eduservice/comment/findAll/${id}`,
method: 'get'
})
},
//添加评论
addComment(eduComment) {
return request({
url: `/eduservice/comment/addComment`,
method: 'post',
data: eduComment
})
}
pages/course/_id.vue
<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">{{ courseWebVo.subjectLevelOne }}</a>
\
<span class="c-333 fsize14">{{ courseWebVo.subjectLevelTwo }}</span>
</section>
<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"
style=" width: 620px; height: 357px">
</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">价格:</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 v-if="isBuy" class="c-attr-mt">
<a href="javascript:void(0)" title="已购买" class="comm-btn c-btn-3">已购买</a>
</section>
<section v-if="Number(courseWebVo.price) === 0" class="c-attr-mt">
<a href="javascript:void(0)" title="立即观看" class="comm-btn c-btn-3">立即观看</a>
</section>
<section v-if="!isBuy && Number(courseWebVo.price) !== 0" class="c-attr-mt">
<a href="#" @click="createOrders" title="立即购买" class="comm-btn c-btn-3">立即购买</a>
</section>
</section>
</aside>
<aside class="thr-attr-box">
<ol class="thr-attr-ol">
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">购买数</span>
<br>
<h6 class="c-fff f-fM mt10">{{ courseWebVo.buyCount }}</h6>
</aside>
</li>
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">课时数</span>
<br>
<h6 class="c-fff f-fM mt10">{{ courseWebVo.lessonNum }}</h6>
</aside>
</li>
<li>
<p> </p>
<aside>
<span class="c-fff f-fM">浏览数</span>
<br>
<h6 class="c-fff f-fM mt10">{{ courseWebVo.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 class="mt50">
<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="courseWebVo.description">{{courseWebVo.description}}</p>
</section>
</div>
</div>
<!-- /课程介绍 -->
<div>
<section id="c-i-tabTitle" class="c-infor-tabTitle c-tab-title">
<a name="c-i" :class="{ current: active === 1 }" @click="courseInfo" title="课程详情"
href="javascript:void(0)">课程目录</a>
<a name="c-i" :class="{ current: active === 2 }" @click="courseComment" title="课程评论"
href="javascript:void(0)">课程评论</a>
</section>
</div>
<article class="ml10 mr10">
<!-- 课程目录 -->
<div :class="{ hide: active === 2 }">
<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" target="_blank">
<span class="fr">
<i class="free-icon vam mr10">免费试听</i>
</span>
<em class="lh-menu-i-2 icon16 mr5"> </em>{{ video.title }}
</a>
</li>
</ol>
</li>
</ul>
</menu>
</div>
</section>
</div>
<!-- 评论 -->
<div class="mt50 commentHtml">
<div :class="{ hide: active === 1 }">
<section class="lh-bj-list pr mt20 replyhtml">
<ul>
<li class="unBr">
<aside class="noter-pic">
<img width="50" height="50" class="picImg" src="~/assets/img/avatar-boy.gif">
</aside>
<div class="of">
<section class="n-reply-wrap">
<fieldset>
<textarea name="" v-model="comment.content" placeholder="输入您要评论的文字"
id="commentContent"></textarea>
</fieldset>
<p class="of mt5 tar pl10 pr10">
<input type="button" @click="saveComment()" value="回复" class="lh-reply-btn"
style="width:70px;height:30px">
</p>
</section>
</div>
</li>
</ul>
</section>
<section class="">
<section class="question-list lh-bj-list pr">
<ul class="pr10">
<li v-for="comment in commentList" v-bind:key="comment.id">
<aside class="noter-pic">
<img width="50" height="50" class="picImg" :src="comment.avatar">
</aside>
<div class="of">
<span class="fl">
<font class="fsize12 c-blue">
{{ comment.nickname }}</font>
<font class="fsize12 c-999 ml5">评论:</font>
</span>
</div>
<div class="noter-txt mt5">
<p>{{ comment.content }}</p>
</div>
<div class="of mt5">
<span class="fr">
<font class="fsize12 c-999 ml5">{{ comment.gmtCreate }}</font>
</span>
</div>
</li>
</ul>
</section>
</section>
<!-- 公共分页 开始 -->
<!-- <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>
</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="#">
<img :src="courseWebVo.avatar" width="50" height="50" :alt="courseWebVo.teacherName">
</a>
</div>
<section class="hLh30 txtOf">
<a class="c-333 fsize16 fl" href="#">{{ courseWebVo.teacherName }}</a>
</section>
<section class="hLh20 txtOf">
<span class="c-999">{{ courseWebVo.intro }}</span>
</section>
</li>
</ul>
</section>
</div>
</div>
</aside>
<div class="clear"></div>
</div>
</section>
<!-- /课程详情 结束 -->
</div>
</template>
<script>
import courseApi from '@/api/course'
import commentApi from '@/api/comment'
import ordersApi from '@/api/orders'
import cookie from 'js-cookie'
export default {
data() {
return {
courseId: '', //课程id
courseWebVo: '', //课程详情
chapterVideoList: '', //课程目录
isBuy: false, //是否购买课程
commentList: {}, //课程评论列表
comment: {}, //要发布的评论
loginInfo: {}, //用户信息
active: 1, //判断选中样式
}
},
created() {
this.courseId = this.$route.params.id
this.comment.courseId = this.courseId
//初始化课程详情
this.courseInfo(this.courseId)
},
methods: {
//查询课程详情
courseInfo() {
courseApi.getCourseInfo(this.courseId)
.then(response => {
this.active = 1
this.courseWebVo = response.data.data.courseWebVo
this.chapterVideoList = response.data.data.chapterVideoList
this.isBuy = response.data.data.isBuy
})
},
//查看课程评论
courseComment() {
commentApi.getCommentList(this.courseId)
.then(response => {
this.active = 2
this.commentList = response.data.data.commentList
})
},
//发布评论
saveComment() {
var userStr = cookie.get('guli_ucenter')
//判断用户是否登录
if (userStr) {
// 把json格式字符串 转换 json对象(js对象)
this.loginInfo = JSON.parse(userStr)
this.comment.memberId = this.loginInfo.id
this.comment.nickname = this.loginInfo.nickname
this.comment.avatar = this.loginInfo.avatar
commentApi.addComment(this.comment)
.then(() => {
//提示
this.$message({
type: 'success',
message: '发布评论成功!'
});
this.courseComment()
this.comment = {}
})
}else{
alert("请先登录")
this.comment = {}
}
},
//生成订单
createOrders() {
ordersApi.createOrders(this.courseId)
.then(response => {
//获取返回订单号
//生成订单之后,跳转订单显示页面
this.$router.push({ path: '/orders/' + response.data.data.orderId })
})
}
}
};
</script>
<style scoped>
.hide {
display: none;
}
</style>
整合阿里云视频点播
后端接口
controller 层
在 service_vod 模块中,VodController 添加方法:
@ApiOperation("获取视频播放凭证")
@GetMapping("getPlayAuth/{id}")
public R getPlayAuth(@PathVariable("id") String id) {
DefaultAcsClient client = InitVodClient.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
// 创建获取凭证 request 对象
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
// 向 request 设置视频ID
request.setVideoId(id);
// 调用方法得到凭证
GetVideoPlayAuthResponse response = null;
try {
response = client.getAcsResponse(request);
} catch (ClientException e) {
e.printStackTrace();
throw new GuliException(20001, "获取凭证失败");
}
String playAuth = response.getPlayAuth();
return R.ok().data("playAuth", playAuth);
}
entity/vo/VideoVo 添加属性
@Data
public class VideoVo {
private String id;
private String title;
private String videoSourceId;
}
前端实现
修改超链接
修改 pages/course/_id.vue 中的视频超链接
创建 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>
创建 api/vod.js
import request from '@/utils/request'
export default{
// 获取视频播放凭证
getPlayAuth(vid) {
return request({
url: `eduvod/video/getPlayAuth/${vid}`,
method: 'get'
})
}
}
创建 pages/player/_vid.vue
<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>
<script>
import vod from '@/api/vod'
export default {
layout: 'video', // 应用 video 布局
asyncData({ params, error }) {
return vod.getPlayAuth(params.vid).then(response => {
// console.log(response.data.data)
return {
vid: params.vid,
playAuth: response.data.data.playAuth
}
})
},
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('播放器创建成功')
})
}
}
</script>
播放器其它组件
详见官网:https://player.alicdn.com/aliplayer/presentation/index.html?type=cover