Guli College 160,000-word notes + 1,600 pictures (16)——Lecturer display, course display

Project source code and required information
Link: https://pan.baidu.com/s/1azwRyyFwXz5elhQL0BhkCA?pwd=8z59
Extraction code: 8z59

Article directory

demo16- Lecturer display, course display

1. Lecturer page query backend

1.1 Control layer

Create the controller TeacherFrontController under the front package of the service_edu module

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

    @Autowired
    private EduTeacherService teacherService;

    //1.分页查询讲师
    @PostMapping("getTeacherFrontList/{page}/{limit}")
    public R getTeacherFrontList(@PathVariable long page, @PathVariable long limit) {
    
    
        Page<EduTeacher> pageTeacher = new Page<>(page, limit);
        Map<String, Object> map = teacherService.getTeacherFrontList(pageTeacher);

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

insert image description here

  • When doing the page query of lecturers, the front end is implemented by element-ui, which is relatively simple, and only needs to pass some parameters, so the page query written in "7.3 Control Layer Writing Method" of "demo03-Background Lecturer Management Module" The method only needs to return total (the total number of records) and rows (the list collection of data on this page). Here we don't use element-ui (of course, it's not that it can't be used, but another approach), we use a method that is closer to the bottom layer, which requires us to return all the data of the page (the current page, the number of records per page , total pages, total records...)
  • The way we return all paging data to the front end is: do paging at the service layer, put the data into the map collection and return it to the control layer, and the control layer returns all the paging data to the front end

1.2 Business layer interface

Define abstract methods in the business layer interface EduTeacherService

//1.分页查询讲师
Map<String, Object> getTeacherFrontList(Page<EduTeacher> pageTeacher);

insert image description here

1.3 Business layer implementation class

Implement the abstract method defined in the previous step in the business layer implementation class EduTeacherServiceImpl

//1.分页查询讲师
@Override
public Map<String, Object> getTeacherFrontList(Page<EduTeacher> pageParam) {
    
    
    QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
    wrapper.orderByDesc("id");
    baseMapper.selectPage(pageParam, wrapper); //执行后会将分页数据封装到pageParam

    //获取分页所有数据
    List<EduTeacher> records = pageParam.getRecords(); //该页数据的list集合
    long current = pageParam.getCurrent(); //当前页
    long pages = pageParam.getPages(); //总页数
    long size = pageParam.getSize(); //每页记录数
    long total = pageParam.getTotal(); //总记录数
    boolean hasNext = pageParam.hasNext(); //是否有下一页
    boolean hasPrevious = pageParam.hasPrevious(); //是否有上一页

    //把分页数据放到map集合中
    HashMap<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;
}

insert image description here

1.4 Test

Restart the service and use swagger for testing

insert image description here

2. Lecturer page query front end

2.1 Define methods in api

Create a teacher.js file in the api directory, and define a method to call the interface of the back-end paging query lecturer

import request from '@/utils/request'
export default {
    
    
  //分页查询讲师
  getTeacherList(page, limit) {
    
    
    return request({
    
    
      url: `/eduservice/teacherfront/getTeacherFrontList/${
      
      page}/${
      
      limit}`,
      method: 'post'
    })
  }
}

insert image description here

2.2 Call the method in the api

1. Introduce the teacher.js file on the index.vue page

import teacherApi from '@/api/teacher'

insert image description here

2. Our previous approach is: data() {return {...}}define the data model in , methods: {...}define the method in , and created() {...}perform initialization rendering in .

Here we change the way, we export default {...}write the following code in teacher->index.vue:

//异步调用
asyncData({
     
      params, error }) {
    
    
  return teacherApi.getTeacherList(1, 8)
    .then(response => {
    
    
      //得到后端返回的map集合
      return {
    
     data: response.data.data }
    })
}

insert image description here

  • The asyncData method on line 199 in the screenshot indicates an asynchronous call: the method written in the asyncData method will not be called immediately when we visit the index.vue page, but will be called after the visit.
    • The difference between created and asyncData: the code in created will be executed as soon as we load the page; the code in asyncData will not be executed as soon as we load the page, but will be executed after the page is loaded, which is called asynchronous call
    • Features of asynchronous calls: the code in the asyncData method will only be executed once
  • The second parameter error on line 199 in the screenshot is the error message during the call; the first parameter params is equivalent to the previous this.$route.params, that is, if we want to get the id in the route, we need to use this .$route.params.id, now we can use params.id (of course, it is also possible to use this.$route.params.id, but we definitely like to use simplified params.id)
  • Why is the 200th line in the screenshot only querying 8 pieces of data: In order to make the page look beautiful, we fixedly query 8 lecturer records
  • Line 203 in the screenshot: When assigning values, we used to use data() { return { data: {} }}and this.data = response.data.data, but we use here return { data: response.data.data }. The bottom layer of this method is actually to define a data model data first, and then assign values ​​to it. Both ways are possible, the second is more simplified
  • One pitfall: the 200th line in the screenshot return teacherApi.getTeacherList(1, 8)must be written on the same line, do not write in one line return, and then write in the next line teacherApi.getTeacherList(1, 8), otherwise there will be problems

2.3 Lecturer List Page Rendering

1. When no piece of data is queried from the database, the page will display "There is no relevant data, and the editor is working hard to sort it out..."

Add code in index.vue

v-if="data.total==0"

insert image description here

v-if="data.total>0"

insert image description here

2. Delete the part circled by the red frame in the figure below

insert image description here

3. Copy and paste the following code to the location just deleted

<li v-for="teacher in data.items" :key="teacher.id">
  <section class="i-teach-wrap">
    <div class="i-teach-pic">
      <a href="/teacher/1" :title="teacher.name" target="_blank">
        <img :src="teacher.avatar" :alt="teacher.name">
      </a>
    </div>
    <div class="mt10 hLh30 txtOf tac">
      <a href="/teacher/1" :title="teacher.name" target="_blank" class="fsize18 c-666">{
   
   {teacher.name}}</a>
    </div>
    <div class="hLh30 txtOf tac">
      <span class="fsize14 c-999">{
   
   {teacher.intro}}</span>
    </div>
    <div class="mt15 i-q-txt">
      <p class="c-999 f-fA">{
   
   {teacher.career}}</p>
    </div>
  </section>
</li>

insert image description here

4. Click the "Famous Teacher" menu on the homepage to view the effect

insert image description here

2.4 Add paging function

2.4.1 Define the pagination method

1. As we said in the second step of "2.2 Calling the method in the api", the code in the asyncData asynchronous call will only be executed once, that is to say, it will only call the back-end interface once to query the lecturer data once. If you want to click on the page The paging numbers (2, 3, 4...) cannot be realized by looking at other lecturer data, that is to say, paging switching cannot be realized

2. So we still need to methods: {...}define methods in to achieve paging

methods: {
    
    
  //分页切换
  gotoPage(page) {
    
     //参数是页码数
    teacherApi.getTeacherList(page, 8)
      .then(response => {
    
    
        this.data = response.data.data
      })
  }
}

insert image description here

Line 83 in the screenshot: As we said in the second step of "2.2 Calling the method in the api", return { data: response.data.data }it is to define the data model data and assign it a value, which means that there is a data data model in the page at this time, so it is not here Then you need to define the data model data, this.data = response.data.datajust use it directly for assignment

2.4.2 Paging page rendering

1. Delete the part circled by the red frame in the figure below

insert image description here

2. Copy and paste the pagination code below to the location just deleted

The code was provided to us by the teacher. When we use it later, we can copy this code and modify it according to our needs.

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

insert image description here

  • Line 53 in the screenshot :class="{undisable: !data.hasPrevious}"uses css style. When data.hasPrevious is judged to be false at this time, that is, when it is on the first page, the "Home" button cannot be clicked.
  • Line 56 in the screenshot @click.prevent="gotoPage(1)", as we said before, is to prevent the default behavior of the label, because the "homepage" is in the a label, so the page jump should occur after clicking the "homepage", but this time it will not A jump occurs but the method is executedgotoPage(1)

2.4.3 Testing

After saving the modification, test it yourself, mine is fine

3. Lecturer details backend

3.1 What does the backend need to check from the database

  • Query the basic information of the lecturer according to the lecturer id
  • Query the courses taught by the lecturer according to the lecturer id

Because the curriculum is used, you need to inject EduCourseService in the controller TeacherFrontController first

@Autowired
private EduCourseService courseService;

insert image description here

3.2 Control layer

Write the code in the controller TeacherFrontController

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

insert image description here

4. Lecturer details front end

4.1 Modify the page

On the index.vue page of the teacher directory, change the two places circled by the box in the figure below to:href="'/teacher/'+teacher.id"

insert image description here

4.2 Define methods in api

Define the interface in teacher.js to call the backend to query the details of the lecturer

//讲师详情
getTeacherInfo(id) {
    
    
  return request({
    
    
    url: `/eduservice/teacherfront/getTeacherFrontInfo/${
      
      id}`,
    method: 'get'
  })
}

insert image description here

4.3 Call the method in the api

1. As we said in "4.2 Dynamic Routing" of "demo13-Building the front-end environment and home page data display", the course details are dynamic routing, and the files created by dynamic routing are vue files starting with an underscore, and the parameter name is the file after the underscore name , eg _id.vue.

_id.vueSo write the code to write the code in the file under the teacher directory

2. Introduce the teacher.js file in _id.vue

import teacherApi from '@/api/teacher'

insert image description here

export default {...}3. Write code in the _id.vue page

//异步调用
asyncData({
     
      params, error }) {
    
    
  return teacherApi.getTeacherInfo(params.id)
    .then(response => {
    
    
      return {
    
    
        teacher: response.data.data.teacher,
        courseList: response.data.data.courseList
      }
    })
}

insert image description here

Line 117 in the screenshot params.id: As we said in the second step of "2.2 Calling the method in the api", params.idit is used to obtain the id value in the route, which is equivalent to this.$route.params.id, whichever one can be used. Pay attention when using params.id, because the file name is _id.vue, so use it params.id; if the file name is _vvvvid.vue, then useparams.vvvvid

4.4 Lecturer details page rendering

1. Delete the part circled by the red frame in the figure below on the _id.vue page

insert image description here

2. Copy and paste the following code to the location deleted in the previous step

<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="#" title="开始学习" target="_blank" class="comm-btn c-btn-1">开始学习</a>
      </div>
    </section>
    <h3 class="hLh30 txtOf mt10">
      <a href="#" :title="course.title" target="_blank" class="course-title fsize18 c-333">{
   
   {course.title}}</a>
    </h3>
  </div>
</li>

insert image description here

3. Modify the code in _id.vue according to the figure below

insert image description here

4.5 Test

Save the modification for testing, mine is fine

insert image description here

5. Course List Backend

5.1 Create vo class

We want to implement on the course list page: display courses according to the conditions of first-level classification, second-level classification, sales volume, latest time, price, so we need to create vo class to encapsulate these conditional data

Create a package frontvo under the entity package of the service_edu module to put some front-end vo classes, and then create a vo class CourseFrontVo under the frontvo package

@Data
public class CourseFrontVo {
    
    
    @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;
}

insert image description here

5.2 Control layer

Create the controller CourseFrontController under the front package

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

    @Autowired
    private EduCourseService courseService;

    //1.课程条件查询带分页
    @PostMapping("getFrontCourseList/{page}/{limit}")
    public R getFrontCourseList(@PathVariable long page,
                                @PathVariable long limit,
                                @RequestBody(required = false) CourseFrontVo courseFrontVo) {
    
    
        Page<EduCourse> pageCourse = new Page<>(page, limit);
        Map<String, Object> map = courseService.getCourseFrontList(pageCourse, courseFrontVo);
        return R.ok().data(map);
    }
}

insert image description here

Line 25 in the screenshot required = false: We said in "7.5 Improvement" of "demo03-Background Lecturer Management Module" that the condition object can be empty, so here we need to add attributes to the @RequestBody annotation required = false. If this attribute is not added, an error will be reported when the condition object is empty

5.3 Business layer interface

Define abstract methods in the business layer interface EduCourseService

//课程条件查询带分页
Map<String, Object> getCourseFrontList(Page<EduCourse> pageCourse, CourseFrontVo courseFrontVo);

insert image description here

5.4 Business layer implementation class

Implement the abstract method defined in the previous step in the business layer implementation class EduCourseServiceImpl

//课程条件查询带分页
@Override
public Map<String, Object> getCourseFrontList(Page<EduCourse> pageParam, CourseFrontVo courseFrontVo) {
    
    
    QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
    //判断条件值是否为空,不为空就拼接条件
    if (!StringUtils.isEmpty(courseFrontVo.getSubjectParentId())) {
    
     //一级分类
        wrapper.eq("subject_parent_id", courseFrontVo.getSubjectParentId());
    }
    if (!StringUtils.isEmpty(courseFrontVo.getSubjectId())) {
    
     //二级分类
        wrapper.eq("subject_id", courseFrontVo.getSubjectId());
    }
    if (!StringUtils.isEmpty(courseFrontVo.getBuyCountSort())) {
    
     //关注度排序
        wrapper.orderByDesc("buy_count");
    }
    if (!StringUtils.isEmpty(courseFrontVo.getGmtCreateSort())) {
    
     //最新时间排序
        wrapper.orderByDesc("gmt_create");
    }
    if (!StringUtils.isEmpty(courseFrontVo.getPriceSort())) {
    
     //价格排序
        wrapper.orderByDesc("price");
    }

    baseMapper.selectPage(pageParam, wrapper);
    //获取分页所有数据
    List<EduCourse> records = pageParam.getRecords(); //该页数据的list集合
    long current = pageParam.getCurrent(); //当前页
    long pages = pageParam.getPages(); //总页数
    long size = pageParam.getSize(); //每页记录数
    long total = pageParam.getTotal(); //总记录数
    boolean hasNext = pageParam.hasNext(); //是否有下一页
    boolean hasPrevious = pageParam.hasPrevious(); //是否有上一页

    //把分页数据放到map集合中
    HashMap<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;
}

insert image description here

The sales sorting, latest time sorting, and price sorting in the conditional query are different from the first-level classification and the second-level classification: the first-level classification and the second-level classification are the first-level and second-level classifications after the front-end clicks on a certain category The id is passed to the backend, and you can use this category id to search in the database; but the sales order, the latest time order, and the price order obviously cannot do this. Our method is: click "attention" or "latest" or "on the page" When the price is ", the front end will pass a value to the back end. It doesn't matter how much this value is. It mainly uses this value to let the back end judge whether it needs to sort by sales, latest time, or price.

6. Course list front end

6.1 Define methods in api

1. After clicking the first-level classification on the page, the corresponding second-level classification will be obtained according to the first-level classification id. The specific implementation method is described in the third step of "4.13.3 Define change event method" in "demo08-course management" I have already encountered it: first query all the first-level and second-level classifications from the backend, and then perform a traversal operation on the front-end according to which first-level classification is clicked on the page, so as to display all the second-level classifications under the first-level classification . Query to get all the interfaces of the first-level classification. We have written it in the "7.3 Control Layer" of "demo07-Course Classification Management" (the getAllSubject method in the controller EduSubjectController), just use it directly

insert image description here

2. Create a course.js file in the api directory

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'
    })
  }
}

insert image description here

6.2 Call the method in the api

1. Import the course.js file into the index.vue file in the course directory

import courseApi from '@/api/course'

insert image description here

2. export default {...}Write code in call method in api

data() {
    
    
  return {
    
    
    page:1, //当前页
    data:{
    
    }, //课程列表
    subjectNestedList: [], // 一级分类列表
    subSubjectList: [], // 二级分类列表
    searchObj: {
    
    }, // 查询表单对象
    oneIndex:-1,
    twoIndex:-1,
    buyCountSort:"",
    gmtCreateSort:"",
    priceSort:""
  }
},
created() {
    
    
  //查询第一页数据
  this.initCourseFirst()
  //一级分类显示
  this.initSubject()
},
methods: {
    
    
  //1.查询第一页数据
  initCourseFirst() {
    
    
    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
      })
  },
  //3.分页切换的方法
  gotoPage(page) {
    
     //参数是页码数
    courseApi.getCourseList(page, 8, this.searchObj)
      .then(response => {
    
    
        this.data = response.data.data
      })
  }
}

insert image description here

  • Line 322 in the screenshot data:{}: The teacher said that the data model data that encapsulates the course list should be a list instead of an object (because the backend returns a list), that is to say, it should not be but should be, but then the teacher data:{}said data:[], It’s okay to use data:{}, say that after encapsulating it as an object, we can also turn it into an array later
  • The function of line 326 in the screenshot oneIndex:-1: After selecting a certain first-level classification, the style of this first-level classification will be changed. In the same way, twoIndex:-1the function is to change the style of the secondary classification after selecting a certain secondary classification
  • The buyCountSort, gmtCreateSort, and priceSort in lines 328, 329, and 330 in the screenshot also have the function of changing the style, and there is another function that we have said in "5.4 Business Layer Implementation Class": pass values ​​to the backend, and the backend judges whether Pass the value to determine whether to sort and what to sort

6.3 Level 1 Classification Rendering

1. Delete the part circled by the red frame below in the index.vue page

insert image description here

2. Copy and paste the following code to the location just deleted

<li v-for="(item,index) in subjectNestedList" :key="index">
  <a :title="item.title" href="#">{
   
   {item.title}}</a>
</li>

insert image description here

The teacher said that because clicking on the first-level classification will change the style, we need to use the index index here for convenience (the specific role of this index is in the following "6.8.3 Change the style of the first-level classification clicked")

6.4 Secondary Classification Rendering

1. Delete the part circled by the red frame below in the index.vue page

insert image description here

2. Copy and paste the following code to the location just deleted

<li v-for="(item,index) in subSubjectList" :key="index">
  <a :title="item.title" href="#">{
   
   {item.title}}</a>
</li>

insert image description here

6.5 Course List Rendering

1. Add the following code to the index.vue page

v-if="data.total==0"
v-if="data.total>0"

insert image description here

2. Delete the part circled by the red frame below in the index.vue page

insert image description here

3. Copy and paste the following code to the location just deleted

<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/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 v-if="Number(item.price) === 0" class="fr jgTag bg-green">
        <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>

insert image description here

Line 85 in the screenshot Number(item.price) === 0: Because the price field in the database is of decimal type, it needs Number(item.price)to be converted to a number before it can be judged with 0

6.6 Test

Save the modification and go to the page to see the effect (don't forget to restart the backend)

insert image description here

6.7 Paging page rendering

1. Delete the part circled by the red frame below in the index.vue page

insert image description here

2. Copy and paste the following code to the location just deleted

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

insert image description here

6.8 First-level and second-level classification linkage

6.8.1 Binding events to the first-level category

Add the following code

@click="searchOne(item.id,index)"

insert image description here

The second parameter index passed to the searchOne method is currently not used, and will be used later when changing the style of the first-level category clicked (in the following "6.8.3 Change the style of the first-level category clicked")

6.8.2 Methods for defining primary classifications

//4.点击某个一级分类,查询对应二级分类
searchOne(subjectParentId, index) {
    
    
  //1.点击某个一级分类,就需要进行查询
  //1.1将一级分类的id赋值给searchObj
  this.searchObj.subjectParentId = subjectParentId
  //1.1从后端查询数据
  this.gotoPage(1)

  //2.拿着页面中点击的一级分类的id与所有一级分类id进行比较
  //如果两者id相同,就从该一级分类中获取到所有二级分类
  for (let i=0;i<this.subjectNestedList.length;i++) {
    
    
    //2.1获取每个一级分类
    var oneSubject = this.subjectNestedList[i]
    //2.2比较id是否相同
    if (subjectParentId == oneSubject.id) {
    
    
      //2.3从该一级分类中获取所有二级分类
      this.subSubjectList = oneSubject.children
    }
  }
}

insert image description here

6.8.3 Change the style of the clicked primary category

1. Copy and paste the following code into index.vue

<style scoped>
  .active {
    background: #bdbdbd;
  }
  .hide {
    display: none;
  }
  .show {
    display: block;
  }
</style>

insert image description here

2. Add the following code in index.vue to change the style of the first-level classification after clicking on a certain level of classification

:class="{active:oneIndex==index}"

insert image description here

The function of this line of code is: when the oneIndex and index values ​​are equal, the background color will be changed using the active style

3. So we need to add code in the searchOne method so that the values ​​of oneIndex and index can be equal

//把传递过来的index值赋值给数据模型oneIndex,使得可以改变点击的一级分类的样式
this.oneIndex = index

//为了避免bug.我们清空一些值
this.twoIndex = -1
this.searchObj.subjectId = ""
this.subSubjectList = []

insert image description here

6.8.4 Binding events and adding styles to the secondary category

Add the following code to the two places in the figure

:class="{active:twoIndex==index}"
@click="searchTwo(item.id,index)"

insert image description here

6.8.5 Methods for Defining Secondary Classifications

//5.点击某个二级分类,进行相应的查询
searchTwo(subjectId, index) {
    
    
  //1.给数据模型twoIndex赋值使得可以显示样式
  this.twoIndex = index
  //2.点击某个二级分类,就需要进行查询
  //2.1将二级分类的id赋值给searchObj
  this.searchObj.subjectId = subjectId
  //2.2从后端查询数据
  this.gotoPage(1)
}

insert image description here

6.9 Sort by Sales or Latest or Price

1. Delete the part circled by the red frame below in the index.vue page

insert image description here

2. Copy and paste the following code to the location just deleted

<li :class="{'current bg-orange':buyCountSort!=''}">
  <a title="销量" href="javascript:void(0);" @click="searchBuyCount()">销量
  <span :class="{hide:buyCountSort==''}">↓</span>
  </a>
</li>
<li :class="{'current bg-orange':gmtCreateSort!=''}">
  <a title="最新" href="javascript:void(0);" @click="searchGmtCreate()">最新
  <span :class="{hide:gmtCreateSort==''}">↓</span>
  </a>
</li>
<li :class="{'current bg-orange':priceSort!=''}">
  <a title="价格" href="javascript:void(0);" @click="searchPrice()">价格&nbsp;
    <span :class="{hide:priceSort==''}">↓</span>
  </a>
</li>

insert image description here

Line 50 in the screenshot :class="{'current bg-orange':buyCountSort!=''}": Determine whether buyCountSort has a value, and if so, let the style take effect

3. Define the method

//6.根据销量排序
searchBuyCount() {
    
    
  //1.给数据模型buyCountSort设值,其余两个数据模型设为空
  this.buyCountSort = "1"
  this.gmtCreateSort = ""
  this.priceSort = ""
  //2.把值赋给searchObj
  this.searchObj.buyCountSort = this.buyCountSort
  this.searchObj.gmtCreateSort = this.gmtCreateSort
  this.searchObj.priceSort = this.priceSort
  //3.调用方法来查询
  this.gotoPage(1)
},
//7.根据最新排序
searchGmtCreate() {
    
    
  //1.给数据模型gmtCreateSort设值,其余两个数据模型设为空
  this.buyCountSort = ""
  this.gmtCreateSort = "1"
  this.priceSort = ""
  //2.把值赋给searchObj
  this.searchObj.buyCountSort = this.buyCountSort
  this.searchObj.gmtCreateSort = this.gmtCreateSort
  this.searchObj.priceSort = this.priceSort
  //3.调用方法来查询
  this.gotoPage(1)
},
//8.根据价格排序
searchPrice() {
    
    
  //1.给数据模型priceSort设值,其余两个数据模型设为空
  this.buyCountSort = ""
  this.gmtCreateSort = ""
  this.priceSort = "1"
  //2.把值赋给searchObj
  this.searchObj.buyCountSort = this.buyCountSort
  this.searchObj.gmtCreateSort = this.gmtCreateSort
  this.searchObj.priceSort = this.priceSort
  //3.调用方法来查询
  this.gotoPage(1)
}

insert image description here

Why does the 230th line in the screenshot assign a value of 1 to the data model buyCountSort? As we said in "5.4 Business Layer Implementation Class", it doesn't matter how much this value is. As long as there is a value, the backend will make the corresponding query

7. Course details backend

7.1 Analysis

1. To query course details, you need to query the basic information of the course, the corresponding lecturer, and the course introduction. This situation is the same as "1.1 Analysis" of "demo10-Course Management", and there are too many data tables involved. It is recommended that we write SQL statements by hand. accomplish

2. The backend needs two interfaces:

  • Write sql statement to query course information according to course id
    • Basic Course Information
    • course sorts
    • Course Introduction
    • Lecturer
  • Query chapters and subsections according to the course id (we have already written this method in the business layer implementation class EduChapterServiceImpl in "1.3.2 Business Layer Implementation Class" of "demo09-Course Management", so there is no need to write it repeatedly)

insert image description here

7.2 Create vo class

Create the vo class CourseWebVo under the frontvo package

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

insert image description here

7.3 Control layer

1. Because we need to use the getChapterVideoByCourseId method of the EduChapterService interface, we need to inject EduChapterService into the controller CourseFrontController

@Autowired
private EduChapterService chapterService;

insert image description here

2. Write the method in the controller CourseFrontController

//2.查询课程详情
@GetMapping("getFrontCourseInfo/{courseId}")
public R getFrontCourseInfo(@PathVariable 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);
}

insert image description here

7.4 Business layer interface

Define abstract methods in the business layer interface EduCourseService

//根据课程id查询课程信息(手写sql语句来实现)
CourseWebVo getBaseCourseInfo(String courseId);

insert image description here

7.5 Business layer implementation class

Implement the abstract method defined in the previous step in the business layer implementation class EduCourseServiceImpl

//根据课程id查询课程信息(手写sql语句来实现)
@Override
public CourseWebVo getBaseCourseInfo(String courseId) {
    
    
    return baseMapper.getBaseCourseInfo(courseId);
}

insert image description here

7.6 Persistence layer

7.6.1 Define methods in mapper

Define the abstract method of "query course information based on course id" in EduCourseMapper

//根据课程id查询课程信息
CourseWebVo getBaseCourseInfo(String courseId);

insert image description here

7.6.2 Writing Mappings

Write the mapping of the abstract method just defined in EduCourseMapper.xml

<!--sql语句:根据课程id查询课程信息-->
<select id="getBaseCourseInfo" resultType="com.atguigu.eduservice.entity.frontvo.CourseWebVo">
    SELECT ec.id,ec.title,ec.cover,ec.lesson_num AS lessonNum,ec.price,
           ec.buy_count AS buyCount,ec.view_count AS viewCount,
           ecd.description,
           et.id AS teacherId,et.name AS teacherName,et.intro,et.avatar,
           es1.title AS subjectLevelOne,es1.id AS subjectLevelOneId,
           es2.title AS subjectLevelTwo,es2.id AS subjectLevelTwoId
    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=#{courseId}
</select>

insert image description here

8. Course details front end

8.1 Modify page

On the index.vue page of the course directory, change the two places circled by the box in the figure below to:href="'/course/'+item.id"

insert image description here

8.2 Define methods in api

Define the method in course.js to call the backend to query the interface of course details

//3.查询课程详情
getCourseInfo(id) {
    
    
  return request({
    
    
    url: `/eduservice/coursefront/getFrontCourseInfo/${
      
      id}`,
    method: 'get'
  })
}

insert image description here

8.3 Call the method in the api

1. Introduce the course.js file in _id.vue

import courseApi from '@/api/course'

insert image description here

export default {...}2. Write code in the _id.vue page

We can use the original way: data() {return {...}}define the data model in , methods: {...}define methods in , and created() {...}initialize rendering in . You can also use asynchronous calls. We use the asynchronous call method here, because if we use the original method, we need to created() {...}write code in to get the id value in the route, and if we use the asynchronous call method, we can directly use params.id to get the id value in the route

//异步调用
asyncData({
     
      params, error }) {
    
    
  return courseApi.getCourseInfo(params.id)
    .then(response => {
    
    
      return {
    
    
        courseWebVo: response.data.data.courseWebVo,
        chapterVideoList: response.data.data.chapterVideoList
      }
    })
}

insert image description here

8.4 Course details page rendering

1. Modify the code in _id.vue according to the figure below

insert image description here

2. Delete the part circled by the red frame in the figure below on the _id.vue page

insert image description here

3. Copy and paste the following code to the location deleted in the previous step

<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="#" title>
        <span class="fr">
          <i class="free-icon vam mr10">免费试听</i>
        </span>
        <em class="lh-menu-i-2 icon16 mr5">&nbsp;</em>{
   
   {video.title}}
      </a>
    </li>
  </ol>
</li>

insert image description here

4. The test results are as follows. It can be found that everything else is normal, but the course introduction has html tags

insert image description here

5. The solution is to add an attribute to the <p> tag so that the html tag can be translated

v-html="courseWebVo.description"

insert image description here

6. At this time, the course introduction can be displayed normally

insert image description here

9. Alibaba Cloud Video Player Test

There are two ways to play the video: play by play address and play by play certificate. It is recommended to use the playback certificate to play, because in actual development, our videos are encrypted and cannot be played using the playback address. Let's demonstrate the use of playback credentials to play:

1. Create a folder aliyunplayer under vue1010 in the workspace, and create a file 01.html under this folder

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <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"></script>
</head>
<body>
    <div  class="prism-player" id="J_prismPlayer"></div>
    <script>
        var player = new Aliplayer({
      
      
            id: 'J_prismPlayer',
            width: '100%',
            autoplay: false,
            cover: 'https://img-blog.csdnimg.cn/065732aef50d4927b21f80886772de7d.png#pic_center',  
            //根据播放凭证播放
            encryptType:'1',
            vid : '52f90c17ab8541169c4464c374200a79', //视频id
            playauth : 'eyJTZWN1cml0eVRva2VuIjoiQ0FJU2h3TjFxNkZ0NUIyeWZTaklyNWZETTR6OHArcFlockRTZUhiN3J6TU1OUFYyMm9PWXFEejJJSDFFZm5sb0FPa2FzUHd6bFd0UjZmd2Vsck1xRnNRZkh4ZVpQSk1odnN3UG9WbjRKcExGc3QySjZyOEpqc1ZtZzkxUTEwYXBzdlhKYXNEVkVmbDJFNVhFTWlJUi8wMGU2TC8rY2lyWXBUWEhWYlNDbFo5Z2FQa09Rd0M4ZGtBb0xkeEtKd3hrMnQxNFVtWFdPYVNDUHdMU2htUEJMVXhtdldnR2wyUnp1NHV5M3ZPZDVoZlpwMXI4eE80YXhlTDBQb1AyVjgxbExacGxlc3FwM0k0U2M3YmFnaFpVNGdscjhxbHg3c3BCNVN5Vmt0eVdHVWhKL3phTElvaXQ3TnBqZmlCMGVvUUFQb3BGcC9YNmp2QWF3UExVbTliWXhncGhCOFIrWGo3RFpZYXV4N0d6ZW9XVE84MCthS3p3TmxuVXo5bUxMZU9WaVE0L1ptOEJQdzQ0RUxoSWFGMElVRXh6Rm1xQ2QvWDRvZ3lRTzE3eUdwTG9pdjltamNCSHFIeno1c2VQS2xTMVJMR1U3RDBWSUpkVWJUbHphazVNalRTNEsvTllLMUFkS0FvNFhlcVBNYXgzYlFGRHI1M3ZzVGJiWHpaYjBtcHR1UG56ZDFRSUNGZk1sRWVVR29BQkI5LzhZb0xPVThYc2djVjVzamYvdDVzQ3J6d1FhMFNkWGE0c3hVNkFDb05Rbis2SUNyN0hOZVZkeEZRVmk5UEtwVERWZ2VRaDRldDB2RkxlYnVTRnlCdmlkaU5McWY1dmxwdmNrTkpLckxSUU5DU1VseVZSc25nM0dPbnRoNmZMUGF3SWpNK003TjlnVStubkpFMkJDS3pvNnVqb25adHBBYXZKck0zWlYrZz0iLCJBdXRoSW5mbyI6IntcIkNJXCI6XCIrekJ2RVBaT2E2T0cwZnpweGNWQTNqRWtBUHMzbWpUc3V1TlpBcm0zeTBSVHhIQTlHNHAwT1hFM2NJd1VGQWxHXCIsXCJDYWxsZXJcIjpcIjl4V3p5R2NXRXZoSldHWVh2VC9UUWdNcU5TZ08zcE1aSjdlOVE1U3pEUk09XCIsXCJFeHBpcmVUaW1lXCI6XCIyMDIyLTA5LTE2VDA3OjQ3OjU3WlwiLFwiTWVkaWFJZFwiOlwiNTJmOTBjMTdhYjg1NDExNjljNDQ2NGMzNzQyMDBhNzlcIixcIlNpZ25hdHVyZVwiOlwiK0VNZ05hYVdEVURVeGR1QzR6MGI5a0RENEdvPVwifSIsIlZpZGVvTWV0YSI6eyJTdGF0dXMiOiJOb3JtYWwiLCJWaWRlb0lkIjoiNTJmOTBjMTdhYjg1NDExNjljNDQ2NGMzNzQyMDBhNzkiLCJUaXRsZSI6IjYgLSBXaGF0IElmIEkgV2FudCB0byBNb3ZlIEZhc3RlciIsIkNvdmVyVVJMIjoiaHR0cDovL291dGluLTE2ZjU0MDI0MmI1MTExZWRiYTRjMDAxNjNlMWM4ZGJhLm9zcy1jbi1zaGFuZ2hhaS5hbGl5dW5jcy5jb20vNTJmOTBjMTdhYjg1NDExNjljNDQ2NGMzNzQyMDBhNzkvc25hcHNob3RzL2ExYWJlZTliYjhjMTQ4YTc4MmEzYzA3NGQ1MTU3YzA5LTAwMDAxLmpwZz9FeHBpcmVzPTE2NjMzMTc5NzcmT1NTQWNjZXNzS2V5SWQ9TFRBSTNEa3h0c2JVeU5ZViZTaWduYXR1cmU9Z2RHa1RucEFkcXZHbSUyQkFOeGswQjJiY1VydlElM0QiLCJEdXJhdGlvbiI6MTYuMjc2N30sIkFjY2Vzc0tleUlkIjoiU1RTLk5Udng2SEo1eTFyOXpQSkszWTh6WjVGM0oiLCJBY2Nlc3NLZXlTZWNyZXQiOiJCRVpnSkM3ajNTdXZmRUt3SmVnODE2QjJSY3ExNGRnWjRSb0dCSm4xODRYSCIsIlJlZ2lvbiI6ImNuLXNoYW5naGFpIiwiQ3VzdG9tZXJJZCI6MTUzMjIzNTkwOTgwMDgyMX0='
        },function(player){
      
      
            console.log('播放器创建好了。')
        });
    </script>
</body>
</html>

insert image description here

  • Lines 8 and 9 in the screenshot are for importing scripts and css files respectively. This step is essential when integrating Alibaba Cloud video player

  • Line 17 in the screenshot autoplay: falseindicates that the video will not be played automatically after entering the page

  • Line 18 in the screenshot cover: 'https://img-blog.csdnimg.cn/065732aef50d4927b21f80886772de7d.png#pic_center': If the setting is not to automatically play the video after entering the page, we choose a picture as the video cover at this time

  • Line 20 in the screenshot encryptType:'1': If you play an encrypted video, you must add this line of code. If you play a non-encrypted video, you can add this line of code or not

  • The playauth on line 22 in the screenshot is the video credentials. The method of obtaining the certificate according to the video id has been written in the test class TestVod of the service_vod module. We change the parameter of the setVideoId method to the video id and execute the test method to obtain the video certificate. After execution, the certificate of my video is eyJTZWN1cml0eVRva2VuI… I won’t write it because it’s too long

    insert image description here

2. Right click in the blank space and select "Open with Live Server" to see the effect in the browser

insert image description here

3. It can be seen that it will not play automatically, and the displayed video cover is the one we set, and the video can be played normally (the problem I encountered during the test: it can be used for the first time after executing the test method to obtain the certificate, but after a while After that, it cannot be used, and the test method must be re-executed to obtain a new certificate to play)

insert image description here

insert image description here

10. Integrate the backend of Alibaba Cloud video player

Write the method in the controller VodController of the service_vod module

//根据视频id获取视频凭证
@GetMapping("getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id) {
    
    
    try {
    
    
        //1.创建初始化对象
        DefaultAcsClient client =InitVodClient.initVodClient(
                ConstantVodUtils.ACCESS_KEY_ID,
                ConstantVodUtils.ACCESS_KEY_SECRET);

        //2.创建获取视频凭证的request和response
        GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();

        //3.向request对象里面设置视频id(加密视频id、没有加密视频的id都是可以的)
        request.setVideoId(id);

        //4.调用初始化对象里面的方法,获取视频信息
        GetVideoPlayAuthResponse response = client.getAcsResponse(request);

        //5.从获取到的视频信息中取视频凭证的信息
        String playAuth = response.getPlayAuth();

        return R.ok().data("playAuth", playAuth);
    } catch (Exception e) {
    
    
        throw new GuliException(20001, "获取视频凭证失败");
    }
}

insert image description here

11. Integrate Alibaba Cloud video player front end

11.1 Create a playback page

1. We want to play the video on a new page after clicking a section of the video. Dynamic routing is used here, so we create the folder player in the pages directory, and then create the page _vid.vue in the player directory

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

insert image description here

2. Change the part circled by the red box in the figure below on the _id.vue page under the course directory to:href="'/player/'+video.videoSourceId"

insert image description here

3. In step 2, use video.videoSourceId to get the id of the video, so we need to add the attribute videoSourceId to the vo class VideoVo (in the service_edu module, entity–>chapter–>VideoVo)

insert image description here

11.2 Create a layout page

Because the layout of the player is inconsistent with the basic layout of other pages, we create the layout page video.vue in the layouts directory

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

insert image description here

11.3 Define methods in api

Create a vod.js file in the api directory, and call the interface of "obtaining credentials according to the video id" on the backend

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

insert image description here

11.4 Call the method in the api (obtain play credentials)

Write code in the _vid.vue page

<script>
import vod from '@/api/vod'
export default {
  layout: 'video', //应用video布局
  //异步调用
  asyncData({ params, error }) {
    return vod.getPlayAuth(params.vid)
        .then(response => {
            return {
                vid: params.vid,
                playAuth: response.data.data.playAuth
            }
        })
  }
}
</script>

insert image description here

  • Line 19 in the screenshot params.vid: Our page name is _vid.vue, so use params.vid to get the parameters in the route
  • Line 22 in the screenshot vid: params.vid: Because the video id is needed to play the video later, so here is the video id assigned to the data model vid

11.5 Create player

1. Because we use asynchronous calls to obtain video credentials, we said in step 2 of "2.2 Calling methods in api" that the code in asyncData will not be executed when we first enter the page, only after sending the request Only then will the code in asyncData be executed to obtain the video id and video credentials. These two data will be used when we create the player, so in order to ensure that these two data have been obtained when creating the player, we need to create the playback The code of the device is written in the mounted method (we said in "3.10 Instance Life Cycle" of "demo04-Front-end Technology", that the mounted method is executed after the page is rendered)

2. Add the following code in _vid.vue

mounted() {
    
     //页面渲染之后
  new Aliplayer({
    
    
      id: 'J_prismPlayer',
      vid: this.vid, // 视频id
      playauth: this.playAuth, // 播放凭证
      encryptType: '1', // 如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
      width: '100%',
      height: '500px'
  }, function(player) {
    
    
      console.log('播放器创建成功')
  })
}

insert image description here

Line 29 in the screenshot id: 'J_prismPlayer': The id here needs to correspond to the id in line 10 of the screenshot in step 1 of "11.1 Create Play Page"

11.6 Testing

1. Save the front-end modification and restart the back-end, the effect is as follows, you can see that the video can be played normally, and the current function has been realized

insert image description here

2. But it is played in the same tab page, if we want it to play in a new tab page, we need to add attributes to the a tag of the _id.vue page in the course directorytarget="_blank"

insert image description here

3. Save the modification, go to the browser again to see the effect, and you can see that it is playing in a new tab.

insert image description here

11.7 Other Common Optional Configurations

The code we wrote in "11.5 Creating a Player" is a mandatory configuration when creating a player. The following are some optional configurations. We also add them to see the effect

// 以下可选设置
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

insert image description here

insert image description here

12. Modify a small bug

In order to make the course cover size appropriate, we add the following code to the _id.vue page under the course directory

height="357px"

insert image description here

In this way, the size of the course cover will look much better.

insert image description here

Guess you like

Origin blog.csdn.net/maxiangyu_/article/details/127032856