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




    一、讲师列表页

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    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'
            })
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3、页面

    <div>
      
      <section class="no-data-wrap" v-if="data.total == 0">
        <em class="icon30 no-data-ico"> em>
        <span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理 中...span>
      section>
      
      <article 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述

    4、展示

    在这里插入图片描述


    1.3、分页功能

    1、js

    methods: {
      //分页切换方法
      //参数是页码数
      gotoPage(page) {
        teacherAPI.getPageList(page, 4).then((resp) => {
          this.data = resp.data.data;
        });
      },
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、页面

    
    <div>
      <div class="paging">
        
        <a :class="{ undisable: !data.hasPrevious }" href="#" title="首页" @click.prevent="gotoPage(1)">首页a>
        <a :class="{ undisable: !data.hasPrevious }" href="#" title="前一页" @click.prevent="gotoPage(data.current - 1)"><a>
        <a v-for="page in data.pages" :key="page" :class="{ current: data.current == page, undisable: data.current == page,}" :title="'' + page + ''" href="#" @click.prevent="gotoPage(page)">{{ page }}a>
        <a :class="{ undisable: !data.hasNext }" href="#" title="后一页" @click.prevent="gotoPage(data.current + 1)">>a>
        <a :class="{ undisable: !data.hasNext }" href="#" title="末页" @click.prevent="gotoPage(data.pages)">末页a>
        <div class="clear" />
      div>
    div>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    二、讲师详情页

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述


    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`
            })
        },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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
          })
        }
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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}} 
                    {{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)"> a>
                section>
              header>
              
              <section class="no-data-wrap" v-if="courseList.length==0">
                <em class="icon30 no-data-ico"> 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    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;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    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'
            })
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148

    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">1i>/
                  <i class="c-666 f-fM">1i>
                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()">价格 
                      <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"> 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">
                
                <a :class="{ undisable: !data.hasPrevious }" href="#" title="首页" @click.prevent="gotoPage(1)">a>
                <a :class="{ undisable: !data.hasPrevious }" href="#" title="前一页" @click.prevent="gotoPage(data.current - 1)"><a>
                <a v-for="page in data.pages" :key="page" :class="{ current: data.current == page, undisable: data.current == page,}" :title="'' + page + ''" href="#" @click.prevent="gotoPage(page)">{{ page }}a>
                <a :class="{ undisable: !data.hasNext }" href="#" title="后一页" @click.prevent="gotoPage(data.current + 1)">>a>
                <a :class="{ undisable: !data.hasNext }" href="#" title="末页" @click.prevent="gotoPage(data.pages)">a>
                <div class="clear" />
              div>
            div>
            
          section>
        section>
        
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

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


    四、讲师详情页

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3、业务层

    @Override
    public CourseWebVo getBaseCourseInfo(String courseId) {
        return baseMapper.getBaseCourseInfo(courseId);
    }
    
    • 1
    • 2
    • 3
    • 4

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    4.2、前端展示

    按课程id进行跳转查询

    在这里插入图片描述

    1、api

    //根据课程id查询
    getFrontCourseInfo(courseId){
        return request({
            url: `/eduservice/courseFront/getFrontCourseInfo/${courseId}`,
            method: 'get'
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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 }}   span>
                section>
                <section class="c-attr-mt of">
                  <span class="ml10 vam">
                    <em class="icon18 scIcon">em>
                    <a class="c-fff vam" title="收藏" href="#">收藏a>
                  span>
                section>
                <section class="c-attr-mt">
                  <a href="#" title="立即观看" class="comm-btn c-btn-3">立 即 观 看a>
                section>
              section>
            aside>
            
            <aside class="thr-attr-box">
              <ol class="thr-attr-ol clearfix">
                <li>
                  <p> p>
                  <aside>
                    <span class="c-fff f-fM">销量span>
                    <br />
                    <h6 class="c-fff f-fM mt10">{{ course.buyCount }}h6>
                  aside>
                li>
                <li>
                  <p> p>
                  <aside>
                    <span class="c-fff f-fM">课时数span>
                    <br />
                    <h6 class="c-fff f-fM mt10">{{ course.lessonNum }}h6>
                  aside>
                li>
                <li>
                  <p> 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"> 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

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


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

    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, "获取视频凭证失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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, "获取视频凭证失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    //初始化类
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述


    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    5、vod.js

    import request from '@/utils/request'
    
    export default {
        //根据视频id,获取视频凭证
        getPlayAuthById(vid) {
            return request({
                url: `/eduvod/video/getPlayAuth/${vid}`,
                method: 'get'
            })
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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" />
        
        <div id="J_prismPlayer" class="prism-player" />
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述


    六、课程评论功能

    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');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3、edu模块

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

    @Component
    @FeignClient(name = "service-ucenter", fallback = UcenterClientImpl.class)
    public interface UcenterClient {
    
        @PostMapping("/ucenter/member/getMemberInfoById/{memberId}")
        UcenterMemberVo getMemberInfoById(@PathVariable String memberId);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    熔断器

    @Component
    public class UcenterClientImpl implements UcenterClient {
        @Override
        public UcenterMemberVo getMemberInfoById(String memberId) {
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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");//根据数据库哪张表生成,有多张表就加逗号继续填写
    
    • 1
    • 2
    • 3
    • 4
    • 5

    EduComment填充@TableField注解

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间")
    private Date gmtCreate;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(value = "更新时间")
    private Date gmtModified;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    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
            })
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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">
          
          <a :class="{ undisable: !data.hasPrevious }" href="#" title="首页" @click.prevent="gotoPage(1)">a>
          <a :class="{ undisable: !data.hasPrevious }" href="#" title="前一页" @click.prevent="gotoPage(data.current - 1)"><a>
          <a v-for="page in data.pages" :key="page" :class="{current: data.current == page,undisable: data.current == page,}" :title="'' + page + ''" href="#" @click.prevent="gotoPage(page)">{{ page }}a>
          <a :class="{ undisable: !data.hasNext }" href="#" title="后一页" @click.prevent="gotoPage(data.current + 1)">>a>
          <a :class="{ undisable: !data.hasNext }" href="#" title="末页" @click.prevent="gotoPage(data.pages)">a>
          <div class="clear" />
        div>
        
      div>
    div>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

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


  • 相关阅读:
    Coreldraw2020最新64位电脑完整版本下载教程
    Nginx 本地部署服务
    简单聊聊如何更优雅的判断空值:Optional、三元运算符及其他方法
    UI 自动化测试实战(二)| 测试数据的数据驱动
    内网IP可以申请SSL证书吗
    [0CTF 2016]piapiapia
    [问题记录]Qt使用QPainter在QImage、QBitmap、QPixmap上面绘图时出现杂色
    网站监测的原理和应用
    mongodb 索引实操
    Vue3.2中,CSS与数据的双向绑定
  • 原文地址:https://blog.csdn.net/apple_53947466/article/details/125482670