谷粒学院——第十六章、课程前台管理

课程条件查询带分页功能

后端接口

创建 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()">价格&nbsp;
                  <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">&nbsp;</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)">&lt;</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)">&gt;</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 }}&nbsp;&nbsp;&nbsp;</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>&nbsp;</p>
              <aside>
                <span class="c-fff f-fM">购买数</span>
                <br>
                <h6 class="c-fff f-fM mt10">{{ courseWebVo.buyCount }}</h6>
              </aside>
            </li>
            <li>
              <p>&nbsp;</p>
              <aside>
                <span class="c-fff f-fM">课时数</span>
                <br>
                <h6 class="c-fff f-fM mt10">{{ courseWebVo.lessonNum }}</h6>
              </aside>
            </li>
            <li>
              <p>&nbsp;</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">&nbsp;</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)">&lt;</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)">&gt;</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 中的视频超链接
image.png

创建 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肉丝不切片

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值