2022年最新《谷粒学院开发教程》:9 - 前台课程模块

资料
资料地址
后台管理系统目录前台展示系统目录
1 - 构建工程篇7 - 渲染前台篇
2 - 前后交互篇8 - 前台登录篇
3 - 文件上传篇9 - 前台课程篇
4 - 课程管理篇10 - 前台支付篇
5 - 章节管理篇11 - 统计分析篇
6 - 微服务治理12 - 项目完结篇


一、讲师列表页

1.1、后端接口

1、控制层

@RestController
@CrossOrigin
@RequestMapping("eduservice/teacherFront")
public class TeacherFrontController {

    @Autowired
    EduTeacherService eduTeacherService;

    @RequestMapping("/getTeacherFrontPageList/{page}/{limit}")
    public R getTeacherFrontPageList(@PathVariable Long limit, @PathVariable Long page) {
        Page<EduTeacher> teacherPage = new Page<>(page, limit);

        Map<String, Object> map = eduTeacherService.getTeacherFrontPageList(teacherPage);

        return R.ok().data(map);
    }
}

2、业务层

//前台系统分页查询讲师的方法
@Override
public Map<String, Object> getTeacherFrontPageList(Page<EduTeacher> teacherPage) {
    QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
    wrapper.orderByAsc("gmt_create");
    //把分页数据封装到pageTeacher对象中
    baseMapper.selectPage(teacherPage, wrapper);

    //把分页的数据获取出来返回一个map集合
    HashMap<String, Object> map = new HashMap<>();

    //总记录数
    long total = teacherPage.getTotal();
    //当前页
    long current = teacherPage.getCurrent();
    //每页记录数
    long size = teacherPage.getSize();
    //查询到的对象
    List<EduTeacher> teacherList = teacherPage.getRecords();
    //总页数
    long pages = teacherPage.getPages();
    //是否有上一页
    boolean hasPrevious = teacherPage.hasPrevious();
    //是否有下一页
    boolean hasNext = teacherPage.hasNext();

    //将数据封装到map中返回
    map.put("total", total);
    map.put("current", current);
    map.put("size", size);
    map.put("list", teacherList);
    map.put("hasPrevious", hasPrevious);
    map.put("hasNext", hasNext);
    map.put("pages", pages);

    return map;
}

1.2、前端展示

1、api/teacher.js

import request from '@/utils/request'

export default{
    getPageList(page, limit){
        return request({
            url:`/eduservice/teacherFront/getTeacherFrontPageList/${page}/${limit}`,
            method: 'get'
        })
    }
}

2、teacher/index.vue

<script>
import teacherAPI from "@/api/teacher";

export default {
  //异步调用,进页面调用,且只会调一次
  //params:相当于之前的 this.$route.params.id 等价 params.id
  //error:错误信息
  asyncData({ params, error }) {
    return teacherAPI.getPageList(1, 8).then((response) => {
      //this.data = response.data.data
      return { data: response.data.data };
    });
  },
  data() {
    return {};
  },
};
</script>

3、页面

<div>
  <!-- /无数据提示 开始-->
  <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 class="i-teacher-list" v-if="data.total > 0">
    <ul class="of">
      <li v-for="item in data.list" :key="item.id">
        <section class="i-teach-wrap">
          <div class="i-teach-pic">
            <a :href="'/teacher/'+item.id" :title="item.name" target="_blank">
              <img :src="item.avatar" :alt="item.name" />
            </a>
          </div>
          <div class="mt10 hLh30 txtOf tac">
            <a :href="'/teacher/'+item.id" :title="item.name" target="_blank" class="fsize18 c-666">{{ item.name }}</a>
          </div>
          <div class="hLh30 txtOf tac">
            <span class="fsize14 c-999">{{item.intro}}</span>
          </div>
          <div class="mt15 i-q-txt">
            <p class="c-999 f-fA">{{item.career}}</p>
          </div>
        </section>
      </li>
    </ul>
    <div class="clear"></div>
  </article>
</div>

在这里插入图片描述

4、展示

在这里插入图片描述


1.3、分页功能

1、js

methods: {
  //分页切换方法
  //参数是页码数
  gotoPage(page) {
    teacherAPI.getPageList(page, 4).then((resp) => {
      this.data = resp.data.data;
    });
  },
},

2、页面

<!-- 公共分页 开始 -->
<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>
<!-- 公共分页 结束 -->

二、讲师详情页

2.1、后端接口

编写接口:

  • 根据讲师id查询讲师信息
  • 根据讲师id查询讲师所讲课程
/**
 * 根据id查询讲师信息
 */
@GetMapping("/getTeacherInfo/{id}")
public R getTeacherInfo(@PathVariable String id) {
    EduTeacher teacher = eduTeacherService.getById(id);

    QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
    wrapper.eq("teacher_id", id);
    List<EduCourse> courseList = eduCourseService.list(wrapper);

    return R.ok().data("teacher", teacher).data("courseList", courseList);
}

在这里插入图片描述


2.2、前端展示

1、api/teacher.js

import request from '@/utils/request'

export default{
    // 根据ID查询讲师本身信息+课程信息
    getTeacherInfoByid(id){
        return request({
            url: `/eduservice/teacherFront/getTeacherInfo/${id}`,
            method: `get`
        })
    },
}

2、js

import teacherApi from '@/api/teacher'
export default {
  created() {
    this.teacherId = this.$route.params.id
    this.getByid()
  },
  data() {
    return {
      teacher: {
        name: '',
        intro: '',
        career: '',
        level: '',
      },
      courseList: [],
      teacherId: '',

    }
  },
  methods: {
    getByid() {
      teacherApi.getTeacherInfoByid(this.teacherId).then(resp => {
        this.teacher = resp.data.data.teacher
        this.courseList = resp.data.data.courseList
      })
    }
  },
};

3、页面

<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>
      <div class="t-infor-wrap">
        <!-- 讲师基本信息 -->
        <section class="fl t-infor-box c-desc-content">
          <div class="mt20 ml20">
            <section class="t-infor-pic">
              <img :src="teacher.avatar" />
            </section>
            <h3 class="hLh30">
              <span class="fsize24 c-333">{{teacher.name}}&nbsp;
                {{teacher.level ===1?'高级讲师':'首席讲师'}}
              </span>
            </h3>
            <section class="mt10">
              <span class="t-tag-bg">{{teacher.career}}</span>
            </section>
            <section class="t-infor-txt">
              <p class="mt20">
                {{teacher.intro}}
              </p>
            </section>
            <div class="clear"></div>
          </div>
        </section>
        <div class="clear"></div>
      </div>
      <section class="mt30">

        <div>
          <header class="comm-title all-teacher-title c-course-content">
            <h2 class="fl tac">
              <span class="c-333">主讲课程</span>
            </h2>
            <section class="c-tab-title">
              <a href="javascript: void(0)">&nbsp;</a>
            </section>
          </header>
          <!-- /无数据提示 开始-->
          <section class="no-data-wrap" v-if="courseList.length==0">
            <em class="icon30 no-data-ico">&nbsp;</em>
            <span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理 中...</span>
          </section>
          <!-- /无数据提示 结束-->
          <article class="comm-course-list">
            <ul class="of">
              <li v-for="course in courseList" :key="course.id">
                <div class="cc-l-wrap">
                  <section class="course-img">
                    <img :src="course.cover" class="img-responsive" />
                    <div class="cc-mask">
                      <a :href="'/course/'+course.id" title="开始学习" target="_blank" class="comm- btn c-btn-1">开始学习</a>
                    </div>
                  </section>
                  <h3 class="hLh30 txtOf mt10">
                    <a :href="'/course/'+course.id" :title="course.title" target="_blank" class="course-title fsize18 c-333">{{course.title}}</a>
                  </h3>
                </div>
              </li>

            </ul>
            <div class="clear"></div>
          </article>
        </div>
      </section>
    </section>
    <!-- /讲师介绍 结束 -->
  </div>
</template>

4、展示

在这里插入图片描述


三、课程列表页

3.1、后端接口

1、创建vo进行模糊查询

package com.laptoy.eduservice.entity.frontVo;

@Data
public class CourseFrontVo implements Serializable {

    private static final long serialVersionUID = 1L;

    @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;

}

2、控制层

@RestController
@CrossOrigin
@RequestMapping("eduservice/courseFront")
public class CourseFrontController {

    @Autowired
    EduCourseService courseService;

    @PostMapping("/getFrontCourseList/{page}/{limit}")
    public R getFrontCourseList(@PathVariable Long limit, @PathVariable Long page,
                                @RequestBody CourseFrontVo courseFrontVo) {

        Page<EduCourse> coursePage = new Page<>(page, limit);
        Map<String, Object> map = courseService.getFrontCourseList(coursePage, courseFrontVo);

        return R.ok().data(map);
    }

}

3、业务层

@Override
public Map<String, Object> getFrontCourseList(Page<EduCourse> pageCourse, CourseFrontVo courseFrontVo) {
    String title = null;
    String subjectId = null;
    String subjectParentId = null;
    String gmtCreateSort = null;
    String buyCountSort = null;
    String priceSort = null;
    String teacherId = null;

    if (!StringUtils.isEmpty(courseFrontVo)) {
        title = courseFrontVo.getTitle();
        subjectId = courseFrontVo.getSubjectId();
        subjectParentId = courseFrontVo.getSubjectParentId();
        gmtCreateSort = courseFrontVo.getGmtCreateSort();
        buyCountSort = courseFrontVo.getBuyCountSort();
        priceSort = courseFrontVo.getPriceSort();
        teacherId = courseFrontVo.getTeacherId();
    }


    QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
    //判断条件值是否为空,不为空拼接条件
    if (!StringUtils.isEmpty(subjectParentId)) {//一级分类
        wrapper.eq("subject_parent_id", subjectParentId);
    }
    if (!StringUtils.isEmpty(subjectId)) {//二级分类
        wrapper.eq("subject_id", subjectId);
    }
    if (!StringUtils.isEmpty(buyCountSort)) {//关注度
        wrapper.orderByDesc("buy_count");
    }
    if (!StringUtils.isEmpty(priceSort)) {//价格
        wrapper.orderByDesc("price");
    }
    if (!StringUtils.isEmpty(gmtCreateSort)) {//最新,创建时间
        wrapper.orderByDesc("gmt_create");
    }


    baseMapper.selectPage(pageCourse, wrapper);

    long total = pageCourse.getTotal();//总记录数
    List<EduCourse> courseList = pageCourse.getRecords();//数据集合
    long size = pageCourse.getSize();//每页记录数
    long current = pageCourse.getCurrent();//当前页
    long pages = pageCourse.getPages();//总页数
    boolean hasPrevious = pageCourse.hasPrevious();//是否有上一页
    boolean hasNext = pageCourse.hasNext();//是否有下一页

    HashMap<String, Object> map = new HashMap<>();
    map.put("total", total);
    map.put("list", courseList);
    map.put("size", size);
    map.put("current", current);
    map.put("pages", pages);
    map.put("hasPrevious", hasPrevious);
    map.put("hasNext", hasNext);

    return map;
}

3.2、前端展示

1、api/course.js

import request from '@/utils/request'

export default {
    //前台多条件分页查询
    getConditionPage(page, limit, searchObj) {
        return request({
            url: `/eduservice/courseFront/getFrontCourseList/${page}/${limit}`,
            method: 'post',
            data: searchObj
        })
    },
    //查询所有分类(一级分类、二级分类)的方法
    getAllSubject() {
        return request({
            url: `/eduservice/subject/getAllSubject`,
            method: 'get'
        })
    }
}

2、js

<script>
import courseApi from "@/api/course";
export default {
  data() {
    return {
      page: 1, //当前页
      data: {}, //课程列表
      oneSubjects: [], // 一级分类列表
      subSubjectList: [], // 二级分类列表
      searchObj: {}, // 查询表单对象
      oneIndex: -1,
      twoIndex: -1,
      buyCountSort: "",
      gmtCreateSort: "",
      priceSort: "",
      index: 0
    };
  },
  methods: {
    // 1.1、初始化课程
    initCourseFirst() {
      courseApi.getConditionPage(1, 4, this.searchObj).then((resp) => {
        this.data = resp.data.data;
      });
    },
    // 1.2、初始化一级分类
    initSubject() {
      courseApi.getAllSubject().then((resp) => {
        this.oneSubjects = resp.data.data.data;
      });
    },

    // 2、分页切换方法
    gotoPage(page) {
      courseApi.getConditionPage(page, 4, this.searchObj).then((resp) => {
        this.data = resp.data.data;
      });
    },

    // 3.1、点击某个一级分类,查询对应的二级分类
    seacherOne(subjectParentId, index) {
      this.oneIndex = index;
      this.twoIndex = -1;

      // 1)把一级分类点击的id值,赋值给searchObj,以便于进行模糊查询
      this.searchObj.subjectParentId = subjectParentId;
      // 2)点击某个一级分类进行条件查询,此时给查询条件赋值了一级分类id
      this.gotoPage(1);

      // 3)查出所有一级分类id并与传入的id作比较,如果相同则查出该id对应的所有二级分类
      for (let i = 0; i < this.oneSubjects.length; i++) {
        var oneSubject = this.oneSubjects[i];
        if (oneSubject.id == subjectParentId) {
          this.subSubjectList = oneSubject.children;
        }
      }

      // 4)查询完毕清空一级分类id
      this.searchObj.subjectParentId = "";
    },

    // 3.2、点击某个二级分类实现查询
    searchTwo(subjectId, index) {
      // 把index赋值,为了样式生效
      this.twoIndex = index;
      
      // 1)把二级分类点击id值,赋给searchObj
      this.searchObj.subjectId = subjectId;
      // 2)点击某个二级分类进行调节查询
      this.gotoPage(1);

      // 查询完毕清空二级分类id
      this.searchObj.subjectId = "";
    },

    // 4.1、根据价格进行排序
    searchPrice() {
      //设置对应变量值,为了样式生效
      this.buyCountSort = ''
      this.gmtCreateSort = ''
      this.priceSort = '1'

      //把值赋值到searchObj中
      this.searchObj.buyCountSort = this.buyCountSort
      this.searchObj.priceSort = this.priceSort
      this.searchObj.gmtCreateSort = this.gmtCreateSort

      //调用方法查询
      this.gotoPage(1)
    },
    // 4.2、根据最新进行排序
    searchGmtCreate() {
      //设置对应变量值,为了样式生效
      this.buyCountSort = ''
      this.gmtCreateSort = '1'
      this.priceSort = ''

      //把值赋值到searchObj中
      this.searchObj.buyCountSort = this.buyCountSort
      this.searchObj.priceSort = this.priceSort
      this.searchObj.gmtCreateSort = this.gmtCreateSort

      //调用方法查询
      this.gotoPage(1)
    },
    // 4.3、根据销量排序
    searchBuyCount() {
      //设置对应变量值,为了样式生效
      this.buyCountSort = '1'
      this.gmtCreateSort = ''
      this.priceSort = ''

      //把值赋值到searchObj中
      this.searchObj.buyCountSort = this.buyCountSort
      this.searchObj.priceSort = this.priceSort
      this.searchObj.gmtCreateSort = this.gmtCreateSort

      //调用方法查询
      this.gotoPage(1)

    },

    // 5、显示全部
    showAll(){
      this.subSubjectList = [];
      this.gotoPage(1)
    }

  },
  created() {
    // 查询所有课程
    this.initCourseFirst();
    // 查询所有一级分类
    this.initSubject();
  },
};
</script>
<style scoped>
.active {
  background: rgb(111, 199, 111);
}
.hide {
  display: none;
}
.show {
  display: block;
}
</style>

3、页面

<template>
  <div id="aCoursesList" class="bg-fa of">
    <!-- /课程列表 开始 -->
    <section class="container">
      <!-- 导航头 开始 -->
      <header class="comm-title">
        <h2 class="fl tac">
          <span class="c-333">全部课程</span>
        </h2>
      </header>
      <!-- 导航头 结束 -->
      <section class="c-sort-box">
        <!-- 课程类别导航 开始-->
        <section class="c-s-dl">
          <!-- 一级分类导航 -->
          <dl>
            <dt>
              <span class="c-999 fsize14">课程类别</span>
            </dt>
            <dd class="c-s-dl-li">
              <ul class="clearfix">
                <li>
                  <a title="全部" href="#" @click="showAll">全部</a>
                </li>
                <li v-for="oneSubject in oneSubjects" :key="oneSubject.id" :class="{active:oneIndex==index}">
                  <a :title="oneSubject.title" @click="seacherOne(oneSubject.id, index)" href="#">{{ oneSubject.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="subject in subSubjectList" :key="subject.id" :class="{active:twoIndex==index}">
                  <a :title="subject.title" @click="searchTwo(subject.id, index)" href="#">{{ subject.title }}</a>
                </li>
              </ul>
            </dd>
          </dl>
          <div class="clear"></div>
        </section>
        <!-- 课程类别导航 结束-->

        <!-- 销量导航 开始-->
        <div class="js-wrap">
          <section class="fr">
            <span class="c-ccc">
              <i class="c-master f-fM">1</i>/
              <i class="c-666 f-fM">1</i>
            </span>
          </section>
          <section class="fl">
            <ol class="js-tap clearfix">
              <li :class="{ 'current bg-orange': buyCountSort != '' }">
                <a title="销量" href="javascript:void(0);" @click="searchBuyCount()">销量
                  <span :class="{ hide: buyCountSort == '' }"></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="价格" href="javascript:void(0);" @click="searchPrice()">价格&nbsp;
                  <span :class="{ hide: priceSort == '' }"></span>
                </a>
              </li>
            </ol>
          </section>
        </div>
        <!-- 销量导航 结束-->

        <!-- 内容数据 开始-->
        <div class="mt40">
          <!-- /无数据提示 开始-->
          <section class="no-data-wrap" v-if="data.total == 0">
            <em class="icon30 no-data-ico">&nbsp;</em>
            <span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理 中...</span>
          </section>
          <!-- /无数据提示 结束-->

          <!-- 遍历课程数据 开始 -->
          <article class="comm-course-list" v-if="data.total > 0">
            <ul class="of" id="bna">
              <li v-for="item in data.list" :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/1" title="开始学习" class="comm-btn c- btn-1">开始学习</a>
                    </div>
                  </section>
                  <h3 class="hLh30 txtOf mt10">
                    <a href="/course/1" :title="item.title" class="course-title fsize18 c-333">{{ item.title }}</a>
                  </h3>
                  <section class="mt10 hLh20 of">
                    <span class="fr jgTag bg-green" v-if="Number(item.price) === 0">
                      <i class="c-fff fsize12 f-fA">免费</i>
                    </span>
                    <span class="fl jgAttr c-ccc f-fA">
                      <i class="c-999 f-fA">9634人学习</i> | <i class="c-999 f-fA">9634评论</i>
                    </span>
                  </section>
                </div>
              </li>
            </ul>
            <div class="clear"></div>
          </article>
          <!-- 遍历课程数据 结束 -->
        </div>
        <!-- 内容数据 结束-->

        <!-- 公共分页 开始 -->
        <div>
          <div class="paging">
            <!-- undisable这个class是否存在,取决于数据属性hasPrevious -->
            <a :class="{ undisable: !data.hasPrevious }" href="#" title="首页" @click.prevent="gotoPage(1)"></a>
            <a :class="{ undisable: !data.hasPrevious }" href="#" title="前一页" @click.prevent="gotoPage(data.current - 1)">&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>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


四、讲师详情页

4.1、后端接口

1、编写sql语句,根据课程id查询课程信息

  • 课程基本信息
  • 课程分类
  • 课程描述
  • 所属讲师

2、根据课程id查询章节和小节


1、courseWebVo

@Data
public class CourseWebVo {

    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;
}

2、控制层

// 2、课程详情页
@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);
}

3、业务层

@Override
public CourseWebVo getBaseCourseInfo(String courseId) {
    return baseMapper.getBaseCourseInfo(courseId);
}

4、dao

<!--前台根据课程id,查询课程基础信息-->
<select id="getBaseCourseInfo" resultType="com.laptoy.eduservice.entity.frontVo.CourseWebVo">
    SELECT ec.id,
           ec.title,
           ec.cover,
           ec.lesson_num as lessonNum,
           ec.price,
           ec.cover,
           ec.buy_count  as buyCount,
           ec.view_count as viewCount,
           esd.description,
           s1.title      as subjectLevelOne,
           s2.title      AS subjectLevelTwo,
           s1.id         as subjectLevelOneId,
           s2.id         as subjectLevelTwoId,
           t.name        AS teacherName,
           t.id          as teacherId,
           t.avatar,
           t.intro
    FROM edu_course ec
             LEFT JOIN edu_teacher t ON ec.teacher_id = t.id
             LEFT JOIN edu_course_description esd on ec.id = esd.id
             LEFT JOIN edu_subject s1 ON ec.subject_parent_id = s1.id
             LEFT JOIN edu_subject s2 ON ec.subject_id = s2.id
    WHERE ec.id = #{id}
</select>

4.2、前端展示

按课程id进行跳转查询

在这里插入图片描述

1、api

//根据课程id查询
getFrontCourseInfo(courseId){
    return request({
        url: `/eduservice/courseFront/getFrontCourseInfo/${courseId}`,
        method: 'get'
    })
}

2、js - course/_id.vue

<script>
import courseApi from "@/api/course";
export default {
  data() {
    return {
      chapterList: [],
      course: {
        courseId: "",
      },
    };
  },
  created() {
    this.course.courseId = this.$route.params.id;
    this.getCourseInfo();
  },
  methods: {
    // 获取课程详细信息
    getCourseInfo() {
      courseApi.getFrontCourseInfo(this.course.courseId).then((resp) => {
        this.chapterList = resp.data.data.chapterVideoList;
        this.course = resp.data.data.courseWebVo;
      });
    },
  },

};
</script>

3、页面

<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="/course" title class="c-999 fsize14">{{course.subjectLevelOne}}</a>\
        <span class="c-333 fsize14">{{ course.subjectLevelTwo }}</span>
      </section>
      <!-- 回退导航栏 结束 -->
      <div>
        <!-- 课程大图左侧 封面 -->
        <article class="c-v-pic-wrap" style="height: 357px">
          <section class="p-h-video-box" id="videoPlay">
            <img :src="course.cover" :alt="course.title" class="dis c-v-pic" height="355px" width="630px" />
          </section>
        </article>
        <!-- 课程大图中侧 细节 -->
        <aside class="c-attr-wrap">
          <section class="ml20 mr15">
            <h2 class="hLh30 txtOf mt15">
              <span class="c-fff fsize24">{{ course.title }}</span>
            </h2>
            <section class="c-attr-jg">
              <span class="c-fff">价格:</span>
              <b class="c-yellow" style="font-size: 24px">¥{{ course.price }}</b>
            </section>
            <section class="c-attr-mt c-attr-undis">
              <span class="c-fff fsize14">主讲: {{ course.teacherName }}&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 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>&nbsp;</p>
              <aside>
                <span class="c-fff f-fM">销量</span>
                <br />
                <h6 class="c-fff f-fM mt10">{{ course.buyCount }}</h6>
              </aside>
            </li>
            <li>
              <p>&nbsp;</p>
              <aside>
                <span class="c-fff f-fM">课时数</span>
                <br />
                <h6 class="c-fff f-fM mt10">{{ course.lessonNum }}</h6>
              </aside>
            </li>
            <li>
              <p>&nbsp;</p>
              <aside>
                <span class="c-fff f-fM">浏览数</span>
                <br />
                <h6 class="c-fff f-fM mt10">{{ course.viewCount }}</h6>
              </aside>
            </li>
          </ol>
        </aside>
        <div class="clear"></div>
      </div>
      <!-- 课程描述 大纲 讲师 -->
      <div class="mt20 c-infor-box">
        <article class="fl col-7">
          <section class="mr30">
            <div class="i-box">
              <div>
                <section id="c-i-tabTitle" class="c-infor-tabTitle c-tab- title">
                  <a name="c-i" class="current" title="课程详情">课 程 详 情</a>
                </section>
              </div> 
              <!-- 课程描述及大纲 -->
              <article class="ml10 mr10 pt20">
                <!-- 课程描述 -->
                <div>
                  <h6 class="c-i-content c-infor-title">
                    <span>课程介绍</span>
                  </h6>
                  <div class="course-txt-body-wrap">
                    <section class="course-txt-body">
                      <p v-html="course.description">
                        {{ course.description }}
                      </p>
                    </section>
                  </div>
                </div>
                <!-- 课程大纲 章节小节 -->
                <div class="mt50">
                  <h6 class="c-g-content c-infor-title">
                    <span>课程大纲</span>
                  </h6>
                  <section class="mt20">
                    <div class="lh-menu-wrap">
                      <menu id="lh-menu" class="lh-menu mt10 mr10">
                        <ul>
                          <!-- 文件目录 -->
                          <li class="lh-menu-stair" v-for="chapter in chapterList" :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" v-if="video.free=== true">
                                    <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>
                <!-- /课程大纲 -->
              </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="course.avatar" width="50" height="50" alt />
                      </a>
                    </div>
                    <section class="hLh30 txtOf">
                      <a class="c-333 fsize16 fl" href="#">{{course.teacherName}}</a>
                    </section>
                    <section class="hLh20 txtOf">
                      <span class="c-999">{{course.intro}}</span>
                    </section>
                  </li>
                </ul>
              </section>
            </div>
          </div>
        </aside>
        <!-- 课程讲师 结束 -->
        <div class="clear"></div>
      </div>
    </section>
    <!-- /课程详情 结束 -->
  </div>
</template>

在这里插入图片描述
在这里插入图片描述


五、整合阿里云视频播放器

5.1、后端接口

1、VodController

//根据视频id获取视频凭证
@GetMapping("/getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id) {
    try {
        String playAuth = vodService.getPlayAuth(id);
        return R.ok().data("playAuth", playAuth);
    } catch (Exception e) {
        e.printStackTrace();
        throw new LaptoyException(20001, "获取视频凭证失败");
    }
}

2、业务层

@Value("${aliyun.vod.file.keyid}")
private String accessKeyId;

@Value("${aliyun.vod.file.keysecret}")
private String accessKeySecret;
    
// 根据视频id获取视频凭证
@Override
public String getPlayAuth(String id) {
    String accesskeyId = accessKeyId;
    String accesskeySecret = accessKeySecret;

    try {
        //创建初始化对象
        DefaultAcsClient cl = InitObject.initVodClient(accesskeyId, accesskeySecret);
        //创建获取视频地址request对象和response对象
        GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
        //向request对象设置视频id值
        request.setVideoId(id);

        GetVideoPlayAuthResponse response = cl.getAcsResponse(request);

        //获取视频播放凭证
        return response.getPlayAuth();

    } catch (ClientException e) {
        e.printStackTrace();
        throw new LaptoyException(20001, "获取视频凭证失败");
    }
}
//初始化类
public class InitObject {
    public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {
        String regionId = "cn-shanghai";  // 点播服务接入区域
        DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        DefaultAcsClient client = new DefaultAcsClient(profile);
        return client;
    }
}

在这里插入图片描述


5.2、前端展示

1、修改超链接地址

在这里插入图片描述

2、在pages创建文件夹player,下级创建文件_vid.vue

3、videoVo添加

在这里插入图片描述

4、新建播放器布局 /layout/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>

5、vod.js

import request from '@/utils/request'

export default {
    //根据视频id,获取视频凭证
    getPlayAuthById(vid) {
        return request({
            url: `/eduvod/video/getPlayAuth/${vid}`,
            method: 'get'
        })
    }
}

6、页面 _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>

7、js

<script>
import vodApi from "@/api/vod";
export default {
  layout: "video", //应用video布局
  // 异步数据请求
  asyncData({ params }) {
    return vodApi.getPlayAuthById(params.vid).then((response) => {
      return {
        playAuth: response.data.data.playAuth,
        vid: params.vid,
      };
    });
  },

  mounted() {
    //页面渲染之后  created
    new Aliplayer(
      {
        id: "J_prismPlayer",
        vid: this.vid, // 视频id
        playauth: this.playAuth, // 播放凭证
        encryptType: "1", // 如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
        width: "100%",
        height: "500px",
        // 以下可选设置
        cover: "https://img-blog.csdnimg.cn/e3a9d8d7355f4e3db2407fbdd9fbe7df.png", // 封面
        qualitySort: "asc", // 清晰度排序
        mediaType: "video", // 返回音频还是视频
        autoplay: false, // 自动播放
        isLive: false, // 直播
        rePlay: false, // 循环播放
        preload: true,
        controlBarVisibility: "hover", // 控制条的显示方式:鼠标悬停
        useH5Prism: true, // 播放器类型:html5
      },
      function (player) {
        console.log("播放器创建成功");
      }
    );
  },
};
</script>

在这里插入图片描述


六、课程评论功能

6.1、数据库设计

CREATE TABLE `edu_comment` (
  `id` char(19) NOT NULL COMMENT '讲师ID',
  `course_id` varchar(19) NOT NULL DEFAULT '' COMMENT '课程id',
  `teacher_id` char(19) NOT NULL DEFAULT '' COMMENT '讲师id',
  `member_id` varchar(19) NOT NULL DEFAULT '' COMMENT '会员id',
  `nickname` varchar(50) DEFAULT NULL COMMENT '会员昵称',
  `avatar` varchar(255) DEFAULT NULL COMMENT '会员头像',
  `content` varchar(500) DEFAULT NULL COMMENT '评论内容',
  `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
  `gmt_create` datetime NOT NULL COMMENT '创建时间',
  `gmt_modified` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_course_id` (`course_id`),
  KEY `idx_teacher_id` (`teacher_id`),
  KEY `idx_member_id` (`member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论';

#
# Data for table "edu_comment"
#

INSERT INTO `edu_comment` VALUES ('1194499162790211585','1192252213659774977','1189389726308478977','1','小三123','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','课程很好',0,'2019-11-13 14:16:08','2019-11-13 14:16:08'),('1194898406466420738','1192252213659774977','1189389726308478977','1','小三123','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','11',0,'2019-11-14 16:42:35','2019-11-14 16:42:35'),('1194898484388200450','1192252213659774977','1189389726308478977','1','小三123','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','111',0,'2019-11-14 16:42:53','2019-11-14 16:42:53'),('1195251020861317122','1192252213659774977','1189389726308478977','1',NULL,NULL,'2233',0,'2019-11-15 16:03:45','2019-11-15 16:03:45'),('1195251382720700418','1192252213659774977','1189389726308478977','1',NULL,NULL,'4455',0,'2019-11-15 16:05:11','2019-11-15 16:05:11'),('1195252819177570306','1192252213659774977','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','55',0,'2019-11-15 16:10:53','2019-11-15 16:10:53'),('1195252899448160258','1192252213659774977','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','55',0,'2019-11-15 16:11:13','2019-11-15 16:11:13'),('1195252920587452417','1192252213659774977','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','223344',0,'2019-11-15 16:11:18','2019-11-15 16:11:18'),('1195262128095559681','14','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','11',0,'2019-11-15 16:47:53','2019-11-15 16:47:53'),('1196264505170767874','1192252213659774977','1189389726308478977','1','小三1231','http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132','666666',0,'2019-11-18 11:10:58','2019-11-18 11:10:58');

6.2、远程调用接口

在service-ucenter模块,创建远程调用接口(识别用户信息),根据登录的用户进行发布评论并存储在edu模块的评论表中

在这里插入图片描述

1、创建vo存储信息 - com.laptoy.commonutils.vo.UcenterMemberVo

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "UcenterMember对象", description = "会员表")
public class UcenterMemberVo implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "会员id")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "微信openid")
    private String openid;

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "昵称")
    private String nickname;

    @ApiModelProperty(value = "性别 1 女,2 男")
    private Integer sex;

    @ApiModelProperty(value = "年龄")
    private Integer age;

    @ApiModelProperty(value = "用户头像")
    private String avatar;

    @ApiModelProperty(value = "用户签名")
    private String sign;

    @ApiModelProperty(value = "是否禁用 1(true)已禁用,  0(false)未禁用")
    private Boolean isDisabled;

    @ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
    private Boolean isDeleted;

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;
}

2、UcenterMemberController

//根据用户id查询用户信息
@PostMapping("/getMemberInfoById/{memberId}")
public UcenterMemberVo getMemberInfoById(@PathVariable String memberId) {
    Member member = memberService.getById(memberId);
    UcenterMemberVo memberVo = new UcenterMemberVo();
    BeanUtils.copyProperties(member, memberVo);

    return memberVo;
}

3、edu模块

远程调用接口(自行对应)

@Component
@FeignClient(name = "service-ucenter", fallback = UcenterClientImpl.class)
public interface UcenterClient {

    @PostMapping("/ucenter/member/getMemberInfoById/{memberId}")
    UcenterMemberVo getMemberInfoById(@PathVariable String memberId);

}

熔断器

@Component
public class UcenterClientImpl implements UcenterClient {
    @Override
    public UcenterMemberVo getMemberInfoById(String memberId) {
        return null;
    }
}

6.3、后端接口

1、使用代码生成器生成

gc.setOutputDir("D:\\MyCode\\IdeaCode\\project\\gulicollege\\guli_parent\\service\\service_edu" + "/src/main/java"); //输出目录

pc.setModuleName("eduservice"); //模块名

strategy.setInclude("edu_comment");//根据数据库哪张表生成,有多张表就加逗号继续填写

EduComment填充@TableField注解

@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;

@TableField(fill = FieldFill.INSERT_UPDATE)
@ApiModelProperty(value = "更新时间")
private Date gmtModified;

2、控制层

@CrossOrigin
@RestController
@RequestMapping("/eduservice/comment")
public class EduCommentController {
    @Autowired
    private EduCommentService commentService;

    //根据课程id_分页查询课程评论的方法
    @GetMapping("/getCommentPage/{page}/{limit}")
    public R getCommentPage(@PathVariable Long page, @PathVariable Long limit, String courseId) {
        Page<EduComment> commentPage = new Page<>(page, limit);
        
        Map<String,Object> map = commentService.getCommentPage(commentPage,limit,courseId);
        
        return R.ok().data(map);
    }

    //添加评论
    @PostMapping("/auth/addComment")
    public R addComment(HttpServletRequest request, @RequestBody EduComment eduComment) {
        commentService.addComment(request,eduComment);
        
        return R.ok();
    }
}

3、业务层

@Service
public class EduCommentServiceImpl extends ServiceImpl<EduCommentMapper, EduComment> implements EduCommentService {
    @Autowired
    private UcenterClient ucenterClient;

    @Override
    public void addComment(HttpServletRequest request, EduComment eduComment) {
        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        //判断用户是否登录
        if (StringUtils.isEmpty(memberId)) {
            throw new LaptoyException(20001, "请先登录");
        }
        eduComment.setMemberId(memberId);

        //远程调用ucenter根据用户id获取用户信息
        UcenterMemberVo memberVo = ucenterClient.getMemberInfoById(memberId);

        if (memberVo == null) {
            throw new LaptoyException(20001, "远程调用失败,请检测会员服务");
        }
        eduComment.setAvatar(memberVo.getAvatar());
        eduComment.setNickname(memberVo.getNickname());

        //保存评论
        this.save(eduComment);
    }

    @Override
    public Map<String, Object> getCommentPage(Page<EduComment> commentPage, Long limit, String courseId) {
        QueryWrapper<EduComment> wrapper = new QueryWrapper<>();

        //判断课程id是否为空
        if (!StringUtils.isEmpty(courseId)) {
            wrapper.eq("course_id", courseId);
        }

        //按最新排序
        wrapper.orderByDesc("gmt_create");

        //数据会被封装到commentPage中
        this.page(commentPage, wrapper);

        List<EduComment> commentList = commentPage.getRecords();
        long current = commentPage.getCurrent();//当前页
        long size = commentPage.getSize();//一页记录数
        long total = commentPage.getTotal();//总记录数
        long pages = commentPage.getPages();//总页数
        boolean hasPrevious = commentPage.hasPrevious();//是否有上页
        boolean hasNext = commentPage.hasNext();//是否有下页

        HashMap<String, Object> map = new HashMap<>();
        map.put("current", current);
        map.put("size", size);
        map.put("total", total);
        map.put("pages", pages);
        map.put("hasPrevious", hasPrevious);
        map.put("hasNext", hasNext);
        map.put("list", commentList);

        return map;
    }
}

6.4、前端展示

1、comment.js

import request from '@/utils/request'
export default {
    getPageList(page, limit, courseId) {
        return request({
            url: `/eduservice/comment/getCommentPage/${page}/${limit}`,
            method: 'get',
            params: courseId
        })
    },
    addComment(comment) {
        return request({
            url: `/eduservice/comment/auth/addComment`,
            method: 'post',
            data: comment
        })
    }
}

2、html - 放在course/_id.vue的下面

<!-- /课程评论 开始 -->
<div class="mt50 commentHtml">
  <div>
    <h6 class="c-c-content c-infor-title" id="i-art-comment">
      <span class="commentTitle">课程评论</span>
    </h6>
    <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">
                <span class="fl"><tt class="c-red commentContentmeg" style="display: none"></tt></span>
                <input type="button" @click="addComment()" value="回复" class="lh-reply-btn" />
              </p>
            </section>
          </div>
        </li>
      </ul>
    </section>
    <section class="">
      <section class="question-list lh-bj-list pr">
        <ul class="pr10">
          <li v-for="comment in data.list" :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-999ml5">{{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>
<!-- /课程评论 结束 -->

3、js

<script>
import courseApi from "@/api/course";
import comment from "@/api/comment";
export default {
  methods: {
    // 获取课程详细信息
    getCourseInfo() {
      courseApi.getFrontCourseInfo(this.course.courseId).then((resp) => {
        this.chapterList = resp.data.data.chapterVideoList;
        this.course = resp.data.data.courseWebVo;
        this.course.courseId = resp.data.data.courseWebVo.id;
      });
    },

    // 初始化评论
    initComment() {
      comment.getPageList(this.page, this.limit, this.course.courseId).then((response) => {
          this.data = response.data.data;
          console.log(response.data.data);
        });
    },
    addComment() {
      this.comment.courseId = this.course.courseId;
      this.comment.teacherId = this.course.teacherId;
      comment.addComment(this.comment).then((response) => {
        if (response.data.success) {
          this.$message({
            message: "评论成功",
            type: "success",
          });
          this.comment.content = "";
          this.initComment();
        }
      });
    },
    gotoPage(page) {
      comment.getPageList(page, this.limit, this.courseId).then((response) => {
        this.data = response.data.data;
      });
    },
  },
  data() {
    return {
      chapterList: [],
      course: {
        courseId: "",
      },
      data: {},
      page: 1,
      limit: 4,
      total: 10,
      comment: {
        content: "",
        courseId: "",
        teacherId: "",
      },
      isbuyCourse: false,
    };
  },
  created() {
    this.course.courseId = this.$route.params.id;
    this.getCourseInfo();
    this.initComment();
  },
};
</script>

在这里插入图片描述
在这里插入图片描述


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Laptoy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值