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
- 2. Lecturer page query front end
- 3. Lecturer details backend
- 4. Lecturer details front end
- 5. Course List Backend
- 6. Course list front end
- 7. Course details backend
- 8. Course details front end
- 9. Alibaba Cloud Video Player Test
- 10. Integrate the backend of Alibaba Cloud video player
- 11. Integrate Alibaba Cloud video player front end
- 12. Modify a small bug
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);
}
}
- 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);
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;
}
1.4 Test
Restart the service and use swagger for testing
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'
})
}
}
2.2 Call the method in the api
1. Introduce the teacher.js file on the index.vue page
import teacherApi from '@/api/teacher'
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 }
})
}
- 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: {} }}
andthis.data = response.data.data
, but we use herereturn { 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 linereturn
, and then write in the next lineteacherApi.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"
v-if="data.total>0"
2. Delete the part circled by the red frame in the figure below
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>
4. Click the "Famous Teacher" menu on the homepage to view the effect
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
})
}
}
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.data
just use it directly for assignment
2.4.2 Paging page rendering
1. Delete the part circled by the red frame in the figure below
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)"><</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>
- 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;
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);
}
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"
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'
})
}
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.vue
So 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'
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
}
})
}
Line 117 in the screenshot params.id
: As we said in the second step of "2.2 Calling the method in the api", params.id
it 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
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>
3. Modify the code in _id.vue according to the figure below
4.5 Test
Save the modification for testing, mine is fine
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;
}
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);
}
}
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);
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;
}
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
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'
})
}
}
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'
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
})
}
}
- 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 teacherdata:{}
saiddata:[]
, It’s okay to usedata:{}
, 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:-1
the 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
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>
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
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>
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"
2. Delete the part circled by the red frame below in the index.vue page
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>
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)
6.7 Paging page rendering
1. Delete the part circled by the red frame below in the index.vue page
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)"><</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>
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)"
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
}
}
}
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>
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}"
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 = []
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)"
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)
}
6.9 Sort by Sales or Latest or Price
1. Delete the part circled by the red frame below in the index.vue page
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()">价格
<span :class="{hide:priceSort==''}">↓</span>
</a>
</li>
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)
}
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)
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;
}
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;
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);
}
7.4 Business layer interface
Define abstract methods in the business layer interface EduCourseService
//根据课程id查询课程信息(手写sql语句来实现)
CourseWebVo getBaseCourseInfo(String courseId);
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);
}
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);
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>
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"
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'
})
}
8.3 Call the method in the api
1. Introduce the course.js file in _id.vue
import courseApi from '@/api/course'
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
}
})
}
8.4 Course details page rendering
1. Modify the code in _id.vue according to the figure below
2. Delete the part circled by the red frame in the figure below on the _id.vue page
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"> </em>{
{video.title}}
</a>
</li>
</ol>
</li>
4. The test results are as follows. It can be found that everything else is normal, but the course introduction has html tags
5. The solution is to add an attribute to the <p> tag so that the html tag can be translated
v-html="courseWebVo.description"
6. At this time, the course introduction can be displayed normally
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>
-
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: false
indicates 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
2. Right click in the blank space and select "Open with Live Server" to see the effect in the browser
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)
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, "获取视频凭证失败");
}
}
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>
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"
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)
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>
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'
})
}
}
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>
- 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('播放器创建成功')
})
}
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
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"
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.
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
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"
In this way, the size of the course cover will look much better.