谷粒学院(十七)讲师列表页 | 讲师详情 | 课程列表页 | 课程详情 | 整合阿里云视频点播


一、讲师列表页 - 前后端

1、Controller类

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

    @Autowired
    private EduTeacherService teacherService;

    @Autowired
    private EduCourseService courseService;

    //1.分页查询讲师的方法
    @GetMapping("getTeacherFrontList/{page}/{limit}")
    public R getTeacherFrontList(@PathVariable long page,@PathVariable long limit){
        Page <EduTeacher> teacherPage = new Page <>(page,limit);
        Map<String,Object> map=  teacherService.getTeacherFrontList(teacherPage);
        return R.ok().data(map);
    }

}

2、Service类

//查询讲师列表 带分页
@Override
public Map <String, Object> getTeacherFrontList(Page <EduTeacher> page) {
    QueryWrapper <EduTeacher> wrapper = new QueryWrapper <>();
    wrapper.orderByAsc("sort");
    this.baseMapper.selectPage(page, wrapper);

    List <EduTeacher> records = page.getRecords();
    long current = page.getCurrent();
    long pages = page.getPages();
    long size = page.getSize();
    long total = page.getTotal();
    boolean hasNext = page.hasNext();
    boolean hasPrevious = page.hasPrevious();

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

3、使用Swagger测试

http://localhost:8001/swagger-ui.html

4、创建 api

创建文件夹api,api下创建teacher.js,用于封装讲师模块的请求

import request from '@/utils/request'

export default {
    //分页查询讲师的方法
    getTeacheList(page,limit){
        return request({
            url:`/eduservice/teacherfront/getTeacherFrontList/${page}/${limit}`,
            mehtod:'get'
        })
    }
}

5、讲师列表组件中调用api

pages/teacher/index.vue

<script>
    import teacherApi from '@/api/teacher'
	export default {
        //  data() {
        //    return {
        //      data:[]
        //    }
        //  },
        //异步调用(仅仅执行一次) 
        //params:相当于之前 this.$route.params =>> this.$route.params.id==params.id
        asyncData({ params, error}) {
            return teacherApi.getTeacheList(1,8).then(response=>{
                //this.data = response.data.data 和下面这行等价.
                return {data:response.data.data}
            })
        },
        methods: {
            gotoPage(page){
                teacherApi.getTeacheList(page,8).then(response=>{
                    this.data = response.data.data
                })
            }
        },
};
</script>

6、页面渲染

<template>
  <div id="aCoursesList" class="bg-fa of">
    <!-- 讲师列表 开始 -->
    <section class="container">
      <header class="comm-title all-teacher-title">
        <h2 class="fl tac">
          <span class="c-333">全部讲师</span>
        </h2>
        <section class="c-tab-title">
          <a id="subjectAll" title="全部" href="#">全部</a>
          <!-- <c:forEach var="subject" items="${subjectList }">
                            <a id="${subject.subjectId}" title="${subject.subjectName }" href="javascript:void(0)" οnclick="submitForm(${subject.subjectId})">${subject.subjectName }</a>
          </c:forEach> -->
        </section>
      </header>
      <section class="c-sort-box unBr">
        <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.items" :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>
        <!-- 公共分页 开始 -->
        <div>
          <div class="paging">
            <!-- undisable这个class是否存在,取决于数据属性hasPrevious -->
            <a
              :class="{undisable: !data.hasPrevious}"
              href="#"
              title="首页"
              @click.prevent="gotoPage(1)"
              disabled
              >首页</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>

7、页面效果展示

image-20220315090521630

image-20220315090552223

二、讲师详情页 - 前后端

1、Controller 类

//2.讲师详情的功能
@GetMapping("getTeacherFrontInfo/{teacherId}")
public R getTeacherFrontInfo(@PathVariable String teacherId){

    //1.根据讲师id查询讲师基本信息
    EduTeacher eduTeacher = teacherService.getById(teacherId);
    //2.根据讲师id查询讲师的所有课程
    QueryWrapper <EduCourse> wrapper = new QueryWrapper <>();
    wrapper.eq("teacher_id", teacherId);
    List <EduCourse> courseList = courseService.list(wrapper);
    return R.ok().data("teacher",eduTeacher).data("courseList",courseList);
}

2、swagger测试

3、teacher api

api/teacher.js

//根据id获取讲师
getTeacherInfo(teacherId){
    return request({
        url:`/eduservice/teacherfront/getTeacherFrontInfo/${teacherId}`,
        mehtod:'get'
    })
}

4、讲师详情页中调用api

pages/teacher/_id.vue

<script>
import teacherApi from '@/api/teacher'
export default {

    asyncData({ params, error}) {
        //查询讲师详情信息  params.id==this.$route.params.id
        return teacherApi.getTeacherInfo(params.id).then(response=>{
            return{
                teacher:response.data.data.teacher,
                courseList:response.data.data.courseList
            }
        })
    }
};
</script>

5、页面渲染

(1)讲师基本信息模板

<!-- 讲师基本信息 -->
        <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" v-if="teacher.level==1">{{teacher.name}}&nbsp;高级讲师</span>
              <span class="fsize24 c-333" v-if="teacher.level==2">{{teacher.name}}&nbsp;特级讲师</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>

(2)无数据提示

<!-- /无数据提示 开始-->
<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>

(3)当前讲师课程列表

<article class="comm-course-list" v-if="courseList.length>0">
         <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>

6、页面效果展示

点击讲师列表页面的任意一个讲师,可以进入讲师详情页面

image-20220315090752280

三、课程列表页 - 前后端

1、Controller类

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

    @Autowired
    private EduCourseService courseService;

    @Autowired
    private EduChapterService chapterService;

    //1.条件查询带分页查询课程
    @PostMapping("getFrontCourseList/{page}/{limit}")
    public R getFrontCourseList(@PathVariable long page, @PathVariable long limit,
                                @RequestBody(required = false)CourseQueryVo courseQueryVo){
        Page <EduCourse> coursePage = new Page <>(page, limit);
        Map <String,Object> map = courseService.getCourseFrontInfo(coursePage,courseQueryVo);
        //返回分页所有数据
        return R.ok().data(map);
    }
}

2、Service类

//条件查询带分页
@Override
public Map <String, Object> getCourseFrontInfo(Page <EduCourse> coursePage, CourseQueryVo courseQueryVo) {
    QueryWrapper <EduCourse> wrapper = new QueryWrapper <>();
    String buyCountSort = courseQueryVo.getBuyCountSort();
    String gmtCreateSort = courseQueryVo.getGmtCreateSort();
    String priceSort = courseQueryVo.getPriceSort();
    String subjectParentId = courseQueryVo.getSubjectParentId();
    String subjectId = courseQueryVo.getSubjectId();
    if(!StringUtils.isEmpty(buyCountSort)){
        if(buyCountSort.equals("1")){//1是降序
            wrapper.orderByDesc("buy_count");
        }else if(buyCountSort.equals("2")){//2是升序
            wrapper.orderByAsc("buy_count");
        }
    }
    if(!StringUtils.isEmpty(gmtCreateSort)){
        if(gmtCreateSort.equals("1")){//1是降序
            wrapper.orderByDesc("gmt_create");
        }else if(gmtCreateSort.equals("2")){//2是升序
            wrapper.orderByAsc("gmt_create");
        }
    }
    if(!StringUtils.isEmpty(priceSort)){
        if(priceSort.equals("1")){//1是降序
            wrapper.orderByDesc("price");
        }else if(priceSort.equals("2")){//2是升序
            wrapper.orderByAsc("price");
        }
    }
    if(!StringUtils.isEmpty(subjectParentId)){
        wrapper.eq("subject_parent_id", subjectParentId);
    }
    if(!StringUtils.isEmpty(subjectId)){
        wrapper.eq("subject_id", subjectId);
    }

    this.baseMapper.selectPage(coursePage, wrapper);

    List <EduCourse> records = coursePage.getRecords();
    long current = coursePage.getCurrent();
    long pages = coursePage.getPages();
    long size = coursePage.getSize();
    long total = coursePage.getTotal();
    boolean hasNext = coursePage.hasNext();
    boolean hasPrevious = coursePage.hasPrevious();

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

3、使用Swagger测试

4、编写course api

import request from '@/utils/request'

export default {
    //1.条件查询带分页查询课程
    getCourseList(page,limit,searchObj){
        return request({
            url:`/eduservice/coursefront/getFrontCourseList/${page}/${limit}`,
            method:'post',
            data:searchObj
        })
    },
    //2.查询所有课程分类
    getAllSubject(){
        return request({
            url:`/eduservice/subject/getAllSubject`,
            method:'get'
        })
    },
}

5、课程列表页面中调用api

<script>
import courseApi from '@/api/course'
export default {
  data() {
    return {
      page:1,
      data:{},
      subjectNestedList: [], // 一级分类列表
      subSubjectList: [], // 二级分类列表
      searchObj: {}, // 查询表单对象
      oneIndex:-1,
      twoIndex:-1,
      buyCountSort:"",  //1是降序 2是升序  不选则不排序
      gmtCreateSort:"",
      priceSort:""
    }
  },
  created() {
    this.initCourseList()
    this.initSubject()
  },
  methods: {
    //1.查询第一页数据
    initCourseList(){
      courseApi.getCourseList(1,8,this.searchObj).then(response=>{
        this.data = response.data.data
      })
    },
    //2.查询所有一级分类 和所有二级分类
    initSubject(){
      courseApi.getAllSubject().then(response=>{
        this.subjectNestedList = response.data.data.list
        this.subSubjectList = []//先清空所有的二级分类
        // this.subSubjectList=this.subjectNestedList[0].children

        for(var i = 0;i < this.subjectNestedList.length; i++){
          this.subSubjectList.push(...this.subjectNestedList[i].children)
        }
      })
    },
    //3.分页跳转
    gotoPage(page){
      courseApi.getCourseList(page,8,this.searchObj).then(response=>{
        this.data = response.data.data
      })
    },
    //4.单击一级分类,显示二级分类
    searchOne(subjectParentId,index){
      this.oneIndex = index
      //相关值的初始化
      this.twoIndex = -1
      this.searchObj.subjectId = ""
      this.subSubjectList = []//清空二级分类
      //把一级分类点击id,赋值给searchObj
      this.searchObj.subjectParentId = subjectParentId
      //点击单个一级分类进行条件查询
      this.gotoPage(1)
      for(var i = 0;i < this.subjectNestedList.length; i++){
        var oneSubject = this.subjectNestedList[i]
        if(subjectParentId == oneSubject.id){
          //从一级分类里面获取对应的二级分类
          this.subSubjectList = oneSubject.children
        }
      }
    },
    //5.单击二级分类,进行查询
    searchTwo(subjectId,index){
      this.twoIndex = index
      // this.searchObj.subjectParentId = ""//一级分类搜索清空 这里不需要情况,因为
      this.searchObj.subjectId = subjectId
      this.gotoPage(1)
    },
    //6.点击全部分类,查询全部
    searchAll(index){
      this.oneIndex  = index
      this.twoIndex = -1
      //进行查询全部
      this.searchObj = {}
      this.gotoPage(1)
      this.subSubjectList  = []//清空二级,填充所有二级分类
      for(var i = 0;i < this.subjectNestedList.length; i++){
          this.subSubjectList.push(...this.subjectNestedList[i].children)
        }
    },
    //7.根据销量排序
    searchBuyCount(){
      //设置对应变量值,为了样式生效
      if(this.buyCountSort==""){
        this.buyCountSort="1"
      }else if(this.buyCountSort=="1"){
        this.buyCountSort="2"
      }else if(this.buyCountSort=="2"){
        this.buyCountSort = "1"
      }
      console.log("this.buyCountSort:"+this.buyCountSort);
      this.gmtCreateSort = ""
      this.priceSort = ""
      //把值赋值到searchObj
      this.searchObj.buyCountSort = this.buyCountSort
      this.searchObj.gmtCreateSort = this.gmtCreateSort
      this.searchObj.priceSort = this.priceSort
      //调用方法 进行搜索
      this.gotoPage(1)
    },
    searchGmtCreate(){
      //设置对应变量值,为了样式生效
      if(this.gmtCreateSort==""){
        this.gmtCreateSort="1"
      }else if(this.gmtCreateSort=="1"){
        this.gmtCreateSort="2"
      }else if(this.gmtCreateSort=="2"){
        this.gmtCreateSort = "1"
      }
      console.log("this.gmtCreateSort:"+this.gmtCreateSort);
      this.buyCountSort = ""
      this.priceSort = ""
      //把值赋值到searchObj
      this.searchObj.buyCountSort = this.buyCountSort
      this.searchObj.gmtCreateSort = this.gmtCreateSort
      this.searchObj.priceSort = this.priceSort
      //调用方法 进行搜索
      this.gotoPage(1)
    },
    searchPrice(){
       //设置对应变量值,为了样式生效
       if(this.priceSort==""){
        this.priceSort="1"
      }else if(this.priceSort=="1"){
        this.priceSort="2"
      }else if(this.priceSort=="2"){
        this.priceSort = "1"
      }
      console.log("this.priceSort:"+this.priceSort);
      this.buyCountSort = ""
      this.gmtCreateSort = ""
      //把值赋值到searchObj
      this.searchObj.buyCountSort = this.buyCountSort
      this.searchObj.gmtCreateSort = this.gmtCreateSort
      this.searchObj.priceSort = this.priceSort
      //调用方法 进行搜索
      this.gotoPage(1)
    }
    
  },
};
</script>

</script>
<style scoped>
  .active {
    background: #68CB9B;
  }
  .active2 {
    background: #00cc7e;
  }
  .hide {
    display: none;
  }
  .show {
    display: block;
  }
</style>

6、页面渲染

分类渲染+分类搜索功能+课程分页渲染

<template>
  <div id="aCoursesList" class="bg-fa of">
    <!-- /课程列表 开始 -->
    <section class="container">
      <header class="comm-title">
        <h2 class="fl tac">
          <span class="c-333">全部课程</span>
        </h2>
      </header>
      <section class="c-sort-box">
        <section class="c-s-dl">
          <dl>
            <dt>
              <span class="c-999 fsize14">课程类别</span>
            </dt>
            <dd class="c-s-dl-li">
              <ul class="clearfix">
                <li>
                  <a title="全部" href="#" @click="searchAll(-1)" :class="{active:oneIndex==-1}">全部</a>
                </li>
                <li v-for="(item,index) in subjectNestedList" :key="item.id" @click="searchOne(item.id,index)" :class="{active:oneIndex==index}">
                  <a :title="item.title" href="#">{{item.title}}</a>
                </li>
               
              </ul>
            </dd>
          </dl>
          <dl>
            <dt>
              <span class="c-999 fsize14"></span>
            </dt>
            <dd class="c-s-dl-li">
              <ul class="clearfix">
                <li v-for="(item,index) in subSubjectList" :key="index" @click="searchTwo(item.id,index)"
                :class="{active2:twoIndex==index}">
                  <a :title="item.title" href="#" >{{item.title}}</a>
                </li>
              </ul>
            </dd>
          </dl>
          <div class="clear"></div>
        </section>
        <div class="js-wrap">
          <section class="fr">
            <span class="c-ccc">
              <i class="c-master f-fM">1</i>/
              <i class="c-666 f-fM">1</i>
            </span>
          </section>
          <section class="fl">
            <ol class="js-tap clearfix">
              <li :class="{'current bg-orange':buyCountSort!=''}">
                <a title="销量" href="javascript:void(0);" @click="searchBuyCount()">销量
                <span :class="{hide:buyCountSort=='1'}" v-if="buyCountSort!=''"></span>
                <span :class="{hide:buyCountSort=='2'}" v-if="buyCountSort!=''"></span>
                </a>
              </li>
              <li :class="{'current bg-orange':gmtCreateSort!=''}">
                <a title="最新" href="javascript:void(0);" @click="searchGmtCreate()">最新
                <span :class="{hide:gmtCreateSort=='1'}" v-if="gmtCreateSort!=''"></span>
                <span :class="{hide:gmtCreateSort=='2'}" v-if="gmtCreateSort!=''"></span>
                
                </a>
              </li>
              <li :class="{'current bg-orange':priceSort!=''}">
                <a title="价格" href="javascript:void(0);" @click="searchPrice()">价格&nbsp;
                  <span :class="{hide:priceSort=='1'}" v-if="priceSort!=''"></span>
                  <span :class="{hide:priceSort=='2'}" v-if="priceSort!=''"></span>
                </a>
              </li>
            </ol>
          </section>
        </div>
        <div class="mt40">
          <!-- /无数据提示 开始-->
          <section class="no-data-wrap" v-if="data.total == 0">
            <em class="icon30 no-data-ico">&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.items" :key="item.id">
                <div class="cc-l-wrap">
                  <section class="course-img">
                    <img :src="item.cover" class="img-responsive" :alt="item.title">
                    <div class="cc-mask">
                      <a :href="'/course/'+item.id" title="开始学习" class="comm-btn c-btn-1">开始学习</a>
                    </div>
                  </section>
                  <h3 class="hLh30 txtOf mt10">
                    <a :href="'/course/'+item.id" :title="item.title" class="course-title fsize18 c-333">{{item.title}}</a>
                  </h3>
                  <section class="mt10 hLh20 of">

                     <span class="fr jgTag bg-green" v-if="Number(item.price) === 0">
                          <i class="c-fff fsize12 f-fA">免费</i>
                        </span>
                        <span class="fr jgTag bg-green" v-else>
                           <i class="c-fff fsize12 f-fA">{{item.price}}</i>
                        </span>

                   
                    <span class="fl jgAttr c-ccc f-fA">
                      <i class="c-999 f-fA">{{item.buyCount}}人学习</i>
                      |
                      <i class="c-999 f-fA">9634评论</i>
                    </span>
                  </section>
                </div>
              </li>
              
            </ul>
            <div class="clear"></div>
          </article>
        </div>
        <!-- 公共分页 开始 -->
        <div>
          <div class="paging">
            <!-- undisable这个class是否存在,取决于数据属性hasPrevious -->
            <a
              :class="{undisable: !data.hasPrevious}"
              href="#"
              title="首页"
              @click.prevent="gotoPage(1)"></a>
            <a
              :class="{undisable: !data.hasPrevious}"
              href="#"
              title="前一页"
              @click.prevent="gotoPage(data.current-1)">&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>

7、页面效果展示

image-20220315092745329

四、课程详情页 - 前后端

1、Controller类

//2.课程详情的方法
@GetMapping("getFrontCourseInfo/{courseId}")
public R getFrontCourseInfo(@PathVariable String courseId){
    //根据课程id,编写sql语句查询课程信息
    CourseWebVo courseWebVo = courseService.getBaseCourseInfo(courseId);

    //根据课程id,查询章节和小节
    List <ChapterVo> chapterVideoList = chapterService.getChapterVideo(courseId);
    return R.ok().data("course",courseWebVo).data("chapterVideoList",chapterVideoList);
}

2、Service类

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

3、Mapper类

public CourseWebVo getBaseCourseInfo(String courseId);
<select id="getBaseCourseInfo" resultType="com.rg.eduservice.entity.frontvo.CourseWebVo" parameterType="string">
    SELECT ec.id,ec.`title`,ec.`price`,ec.`lesson_num` AS lessonNum,ec.`cover`,ec.`buy_count` buyCount ,ec.`view_count` viewCount,
    ecd.`description`,
    et.`id` teacherId,
    et.`name` teacherName,
    et.`intro`,
    et.`avatar`,
    es1.`id` AS subjectLevelOneId,
    es1.`title` AS subjectLevelOne,
    es2.`id` AS subjectLevelTwoId,
    es2.`title` AS subjectLevelTwo
    FROM  edu_course ec LEFT OUTER JOIN edu_subject es1 ON ec.`subject_parent_id`= es1.`id`
    LEFT OUTER JOIN edu_subject es2 ON ec.`subject_id` = es2.`id`
    LEFT OUTER JOIN edu_teacher et ON ec.`teacher_id` = et.`id`
    LEFT OUTER JOIN edu_course_description ecd ON ec.`id` = ecd.`id`
    WHERE ec.id=#{id}
</select>

4、使用Swagger测试

5、编写course api

 //3.课程课程基本详情的方法
getCourseInfo(courseId){
    return request({
        url:`/eduservice/coursefront/getFrontCourseInfo/${courseId}`,
        method:'get'
    })
}

6、课程详情页面中调用API

<script>
import courseApi from '@/api/course'
export default {

  asyncData({ params, error}) {
    //查询课程详情信息
    return courseApi.getCourseInfo(params.id).then(response=>{
      return{
        course:response.data.data.course,
        chapterVideoList:response.data.data.chapterVideoList
      }
    })
  }

};
</script>

7、页面渲染

<template>
  <div id="aCoursesList" class="bg-fa of">
    <!-- /课程详情 开始 -->
    <section class="container">
      <section class="path-wrap txtOf hLh30">
        <a href="#" title class="c-999 fsize14">首页</a>
        \
        <a href="#" title class="c-999 fsize14">{{course.subjectLevelOne}}</a>
        \
        <span class="c-333 fsize14">{{course.subjectLevelTwo}}</span>
      </section>
      <div>
        <article class="c-v-pic-wrap" style="height: 357px;">
          <section class="p-h-video-box" id="videoPlay">
            <img :src="course.cover" :alt="course.title" class="dis c-v-pic">
          </section>
        </article>
        <aside class="c-attr-wrap">
          <section class="ml20 mr15">
            <h2 class="hLh30 txtOf mt15">
              <span class="c-fff fsize24">{{course.title}}</span>
            </h2>
            <section class="c-attr-jg">
              <span class="c-fff">价格:</span>
              <b class="c-yellow" style="font-size:24px;">¥{{course.price}}</b>
            </section>
            <section class="c-attr-mt c-attr-undis">
              <span class="c-fff fsize14">主讲: {{course.teacherName}}&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 chapterVideoList" :key="chapter.id">
                            <a href="javascript: void(0)" :title="chapter.title" class="current-1">
                              <em class="lh-menu-i-1 icon18 mr10"></em>{{chapter.title}}
                            </a>
                            <ol class="lh-menu-ol" style="display: block;">
                              <li class="lh-menu-second ml30" v-for="video in chapter.children" :key="video.id">
                                <a :href="'/player/'+video.videoSourceId" title target="_blank">
                                  <span class="fr">
                                    <i class="free-icon vam mr10" v-if="video.isFree === true">免费试听</i>
                                  </span>
                                  <em class="lh-menu-i-2 icon16 mr5">&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">
                      <!-- "'/course/'+item.id" -->
                      <a :href="'/teacher/'+course.teacherId">
                        <img :src="course.avatar" width="50" height="50" :alt="course.teacherName">
                      </a>
                    </div>
                    <section class="hLh30 txtOf">
                      <a class="c-333 fsize16 fl" href="#">{{course.teacherName}}</a>
                    </section>
                    <section class="hLh20 txtOf">
                      <span class="c-999">{{course.intro}}</span>
                    </section>
                  </li>
                </ul>
              </section>
            </div>
          </div>
        </aside>
        <div class="clear"></div>
      </div>
    </section>
    <!-- /课程详情 结束 -->
  </div>
</template>

8、页面效果展示

image-20220315093716055

image-20220315093728057

五、视频点播后端获取播放凭证

1、VideoController

service_vod微服务中 VodController.java 中创建 getPlayAuth 接口方法

//根据视频id获取视频凭证
@GetMapping("getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id){
    try {
        DefaultAcsClient client = InitVodClient.initVodClient(ConstantVodUtils.ACCESS_KEY_ID, ConstantVodUtils.ACCESS_KEY_SECRET);
        GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
        request.setVideoId(id);
        GetVideoPlayAuthResponse response  = client.getAcsResponse(request);
        String playAuth = response.getPlayAuth();
        return R.ok().data("playAuth", playAuth);
    }catch (Exception e){
        throw new GuLiException(20001, "获取凭证失败");
    }
}

2、在service_edu中VideoVo类中添加属性

private String videoSourceId;//视频id

3、Swagger测试

六、前端播放器整合

1、点击播放超链接

course/_id.vue 中修改课时目录超链接

image-20220315094429274

image-20220315094631412

2、layout

因为播放器的布局和其他页面的基本布局不一致,因此创建新的布局容器 layouts/video.vue

<template>
  <div class="guli-player">
    <div class="head">
      <a href="#" title="谷粒学院">
        <img class="logo" src="~/assets/img/logo.png" lt="谷粒学院">
    </a></div>
    <div class="body">
      <div class="content"><nuxt/></div>
    </div>
  </div>
</template>
<script>
export default {}
</script>

<style>
html,body{
  height:100%;
}
</style>

<style scoped>
.head {
  height: 50px;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
}

.head .logo{
  height: 50px;
  margin-left: 10px;
}

.body {
  position: absolute;
  top: 50px;
  left: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;
}
</style>

3、api

创建api模块 api/vod.js,从后端获取播放凭证

import request from '@/utils/request'

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

4、播放组件相关文档

集成文档:https://help.aliyun.com/document_detail/51991.html?spm=a2c4g.11186623.2.39.478e192b8VSdEn
在线配置:https://player.alicdn.com/aliplayer/setting/setting.html
功能展示:https://player.alicdn.com/aliplayer/presentation/index.html

5、创建播放页面

创建 pages/player/_vid.vue

(1)引入播放器js库和css样式

<template>
  <div>

    <!-- 阿里云视频播放器样式 -->
    <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.8.1/skins/default/aliplayer-min.css" >
    <!-- 阿里云视频播放器脚本 -->
    <script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.8.1/aliplayer-min.js" />

    <!-- 定义播放器dom -->
    <div id="J_prismPlayer" class="prism-player" />
  </div>
</template>

(2)获取播放凭证

<script>
import vod from '@/api/vod'

export default{
    layout:'video',//应用video布局
    asyncData({params, error}) {
        //注意 这里的params.vid 是写页面的名字,如果页面写的是_id,则要写params.id
        return vod.getPlayAuth(params.vid).then(response=>{
            return {
                vid:params.vid,
                playAuth:response.data.data.playAuth
            }
        })
    },
}
</script>

(3)创建播放器

/**
     * 页面渲染完成时:此时js脚本已加载,Aliplayer已定义,可以使用
     * 如果在created生命周期函数中使用,Aliplayer is not defined错误
     */
    // 因为刚进入页面时,this.vid和this.playAuth都还没有,发送完异步请求后才有.所以视频播放的相关配置应该放在mounted,等页面加载完成后执行
    mounted() {
        new Aliplayer({
        id: 'J_prismPlayer',
        vid: this.vid, // 视频id
        playauth: this.playAuth, // 播放凭证
        encryptType: '1', // 如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
        width: '100%',
        height: '500px',
        // 以下可选设置
        cover: 'http://guli.shop/photo/banner/1525939573202.jpg', // 封面
        qualitySort: 'asc', // 清晰度排序

        mediaType: 'video', // 返回音频还是视频
        autoplay: false, // 自动播放
        isLive: false, // 直播
        rePlay: false, // 循环播放
        preload: true,
        controlBarVisibility: 'hover', // 控制条的显示方式:鼠标悬停
        useH5Prism: true, // 播放器类型:html5
        }, function(player) {
            console.log('播放器创建成功')
        })
    }

6、加入播放组件

功能展示:https://player.alicdn.com/aliplayer/presentation/index.html

7、页面效果展示

点击免费播放,进入视频播放页面

image-20220315095817689


如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱编程的大李子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值