(十三)在线教育网站搭建-前台页面搭建NUXT

NUXT

1. 服务端渲染技术NUXT

Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。

可以做到服务端拼接好html后直接返回,首屏可以做到无需发起ajax请求

https://zh.nuxtjs.org/

2. 下载demo

https://zh.nuxtjs.org/guide/installation

1
$ npx create-nuxt-app <项目名> 

配置

1
2
3
4
5
6
7
8
9
10
11
12
create-nuxt-app v2.14.0
✨ Generating Nuxt.js project in
? Project name
? Project description
? Author name
? Choose the package manager
? Choose UI framework
? Choose custom server framework
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose test framework
? Choose rendering mode

Choose custom server framework(选择服务端框架),选择Express

  • 选择默认的Nuxt服务器,不会生成server文件夹,所有服务端渲染的操作都是Nuxt帮你完成,开发体验更接近Vue项目,缺点是无法做一些服务端定制化的操作。
  • Express,会生成server文件夹,帮你搭建一个基本的Node服务端环境,可以在里面做一些node端的操作。

还有Choose Nuxt.js modules(选择nuxt.js的模块),可以选axiosPWA,如果选了axios,则会帮你在nuxt实例下注册$axios,让你可以在.vue文件中直接this.$axios发起请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
🎉  Successfully created project edu-online-nuxt

To get started:

cd edu-online-nuxt
npm run dev

To build & start for production:

cd edu-online-nuxt
npm run build
npm run start

To test:

cd edu-online-nuxt
npm run test

3. 目录结构

  • 资源目录 assets

    用于组织未编译的静态资源如 LESS、SASS 或 JavaScript。

  • 组件目录 components

    用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像页面组件那样有 asyncData 方法的特性。

  • 布局目录 layouts

    用于组织应用的布局组件。

  • 页面目录 pages

    用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。

  • 插件目录 plugins

    用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件。

  • nuxt.config.js 文件

    nuxt.config.js 文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。

4. 安装幻灯片

1
npm install vue-awesome-swiper

4.1. 配置插件 - plugin/nuxt-swiper-plugin.js

1
2
3
4
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr'

Vue.use(VueAwesomeSwiper)

4.2. 配置插件 - nuxt.config.js

1
2
3
4
5
6
7
css: [
'swiper/dist/css/swiper.css'
],

plugins: [
{ src: '~/plugins/nuxt-swiper-plugin.js', ssr: false }
]

5. 搭建前台

5.1. 修改布局 - layouts/default.vue

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<!-- 页头 -logo -->

<router-link to="/course" tag="li" active-class="current">

<!-- 内容区域 -->
<nuxt />

<!-- 页尾 - copyright -->
</div>
</template>

5.2. /course -> page/course/index.vue

5.2.1. 页面渲染之前获取所有数据 - asyncData

调用axios中get方法得到方法返回json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<div v-for="(teacher,index) in items" :key="index">
<a :href="'teacher/'+teacher.id"> {{teacher.name}}</a>
</div>
</div>
</template>

<script>
import axios from 'axios'
export default {
//页面渲染之前获取所有数据
asyncData({param,error}){
return axios.get('http://localhost:9500/eduservice/edu-teacher/listTeachers')
.then(response=>{
///console.log(response)
return {items: response.data.data.items}
})
}
}
</script>

5.3. ‘teacher/‘+teacher.id -> /teacher/_id.vue

每个讲师链接id,动态路由需要建立 _键.vue 文件

5.3.1. _id.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
{{teacher.name}}
</div>
</template>

<script>
import axios from 'axios'
export default {
asyncData({params,error}){
return axios.get('http://localhost:9500/eduservice/edu-teacher/selectTeacher/'+params.id)
.then(response => {
return {teacher: response.data.data.items}
})
}

}
</script>

6. 按页查询讲师顺序

6.1. 后台查询讲师数据 - FrontEduTeacherController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.online.edu.eduservice.controller.front;

import com.online.edu.common.R;
import com.online.edu.eduservice.entity.EduTeacher;
import com.online.edu.eduservice.service.EduTeacherService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@Api("前端讲师控制器")
@RestController
@CrossOrigin
@RequestMapping("/eduservice/front/edu-teacher")
public class FrontEduTeacherController {

@Autowired
EduTeacherService eduTeacherService;

@ApiOperation("分页查询所有讲师")
@GetMapping("/listTeachers/{current}/{size}")
public R listTeachers(@PathVariable String current,
@PathVariable String size){

Map<String,Object> lists = eduTeacherService.frontListPageTeacher(current,size);
return R.ok().data("items",lists);
}
}

6.2. 后台查询讲师数据 - EduTeacherServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public Map<String, Object> frontListPageTeacher(String current, String size) {
Long currentPage = Long.parseLong(current);
Long pageSize = Long.parseLong(size);

Page<EduTeacher> page = new Page<>(currentPage,pageSize);
//结果 按sort降序,相同时按id升序
QueryWrapper<EduTeacher> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("sort");
queryWrapper.orderByAsc("id");

Page<EduTeacher> eduTeacherPage = baseMapper.selectPage(page, queryWrapper);

//存入所有记录
Map<String,Object> map = new HashMap<>();
map.put("total",eduTeacherPage.getTotal());
map.put("current",eduTeacherPage.getCurrent());
map.put("size",eduTeacherPage.getSize());
map.put("hasNext",eduTeacherPage.hasNext());
map.put("hasPrevious",eduTeacherPage.hasPrevious());
map.put("pages",eduTeacherPage.getPages());
map.put("items",eduTeacherPage.getRecords());

return map;
}

6.3. 后台查询讲师数据 - 测试swagger-ui

6.4. 前台-封装axios - 同vue的request.js

1
2
3
4
5
6
7
8
import axios from 'axios'

const service = axios.create({
baseURL:'http://locahost:9500',
timeout:20000
})

export default service

6.5. 前台-封装请求方法 - 同vue的api/teacher.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import request from '@/utils/request'

const base = '/eduservice/front/edu-teacher'

export default{

listTeachers(current,size){
return request({
url:`${base}/listTeachers/${current}/${size}`,
method:'get'
})
}
}

6.6. 前台-调用请求方法 - 同vue的views/index.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<template>
<div>
<div v-for="(teacher,index) in teacherList" :key="index">
<a :href="'teacher/'+teacher.id"> {{teacher.name}}</a>
</div>

<!-- 分页 -->
<el-pagination
:current-page="current"
:page-size="size"
:total="total"
background
style="padding: 30px 0; text-align: center;"
layout="total, prev, pager, next, jumper"
@current-change="getTeacherList"/>
</div>
</template>

<script>
import teacher from '@/api/teacher'
export default {
data(){
return{
current: 1,
size: 8,
total: 0,
teacherList: []
}
},
//页面渲染之前获取所有数据
created(){
this.getTeacherList()
},
methods:{
//调用路径请求
getTeacherList(current=1){
this.current = current
teacher.listTeachers(this.current,this.size).then(response=>{
this.teacherList = response.data.data.items.items
this.total = response.data.data.items.total
})
}
}
}
</script>

6.7. http://localhost:3000/teacher

6.8. 添加样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- 讲师列表 开始 -->
<section class="container">
<header class="comm-title all-teacher-title">
<h2 class="fl tac">
<span class="c-333">全部讲师</span>
</h2>
<section class="c-tab-title">
<a id="subjectAll" title="全部" href="#">全部</a>
</section>
</header>

<section class="c-sort-box unBr">
<div>
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="total===0">
<em class="icon30 no-data-ico">&nbsp;</em>
<span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理中...</span>
</section>
<!-- /无数据提示 结束-->
<article class="i-teacher-list">
<ul class="of">
<li v-for="teacher in teacherList" :key="teacher.id">
<section class="i-teach-wrap">
<div class="i-teach-pic">
<a :href="`/teacher/`+teacher.id" :title="teacher.name" target="_blank">
<img v-if="teacher.avatar!==''" :src="teacher.avatar" alt>
<img v-else src="~/assets/img/avatar-boy.gif"/>
</a>
</div>
<div class="mt10 hLh30 txtOf tac">
<a :href="`/teacher/`+teacher.id" :title="teacher.name" target="_blank" class="fsize18 c-666">{{teacher.name}}</a>
</div>
<div class="hLh30 txtOf tac">
<span class="fsize14 c-999">
{{teacher.career}}
</span>
</div>
<div class="mt15 i-q-txt">
<p class="c-999 f-fA">{{teacher.intro}}</p>
</div>
</section>
</li>
</ul>
<div class="clear"></div>
</article>
</div>
</section>
</section>

<!--
<div v-for="(teacher,index) in teacherList" :key="index">
<a :href="'teacher/'+teacher.id"> {{teacher.name}}</a>
</div> -->

<!-- 分页 -->
<el-pagination
:current-page="current"
:page-size="size"
:total="total"
background
style="padding: 30px 0; text-align: center;"
layout="total, prev, pager, next, jumper"
@current-change="getTeacherList"/>
</div>
</template>

7. 单个讲师详情页- 讲师+所讲课程

7.1. 后台查询讲师数据 - FrontEduTeacherController

1
2
3
4
5
6
7
8
9
10
@ApiOperation("单个讲师信息查询+所讲课程")
@GetMapping("/getTeacherById/{id}")
public R getTeacherById(@PathVariable String id){
//讲师
EduTeacher teacher = eduTeacherService.getById(id);
//课程
List<EduCourse> courses = eduTeacherService.getTeacherCourse(id);

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

7.2. 后台查询讲师数据 - EduTeacherServiceImpl

1
2
3
4
5
6
7
8
9
//前端讲师对应课程查询
@Override
public List<EduCourse> getTeacherCourse(String id) {

QueryWrapper<EduCourse> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("teacher_id",id);
List<EduCourse> list = eduCourseService.list(queryWrapper);
return list;
}

7.3. 后台查询讲师数据 - 测试swagger-ui

7.4. 前台-封装请求方法 - api/teacher.js

1
2
3
4
5
6
7
getTeacherById(id){
return request({
url: `${base}/getTeacherById/`+id,
method: 'get'
})

}

7.5. 前台-调用请求方法 - teacher/_id.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div>
{{teacherObj.name}}
{{teacherObj.intro}}
<ul>
<li v-for="course in courseList" :key="course.id">
{{course.title}}
</li>
</ul>
</div>
</template>

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

data(){
return{
teacherObj:{},
courseList:[]
}
},
created(){
this.getTeacherById(this.$route.params.id)

},
methods:{
getTeacherById(id){
teacher.getTeacherById(id).then(response=>{
//console.log(response)
this.teacherObj = response.data.data.teacher
this.courseList = response.data.data.courses
})
}
}
}
</script>

7.6. http://localhost:3000/teacher/11

7.7. 添加样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- 讲师介绍 开始 -->
<section class="container">
<header class="comm-title">
<h2 class="fl tac">
<span class="c-333">讲师介绍</span>
</h2>
</header>
<div class="t-infor-wrap">
<!-- 讲师基本信息 -->
<section class="fl t-infor-box c-desc-content">
<div class="mt20 ml20">
<section class="t-infor-pic">
<img v-if="teacherObj.avatar!==''" :src="teacherObj.avatar" style="width:270px;height:270px;"/>

<img v-else src="~/assets/img/avatar-boy.gif" style="width:270px;height:270px;"/>
</section>
<h3 class="hLh30">
<span class="fsize24 c-333">{{teacherObj.name}}&nbsp;
<span v-if="teacherObj.level===1">高级讲师</span>
<span v-else>首席讲师</span>
</span>
</h3>
<section class="mt10">
<span class="t-tag-bg">{{teacherObj.intro}}</span>
</section>
<section class="t-infor-txt">
<p class="mt20">{{teacherObj.career}}</p>
</section>
<div class="clear"></div>
</div>
</section>
<div class="clear"></div>
</div>
<section class="mt30">
<div>
<header class="comm-title all-teacher-title c-course-content">
<h2 class="fl tac">
<span class="c-333">主讲课程</span>
</h2>
<section class="c-tab-title">
<a href="javascript: void(0)">&nbsp;</a>
</section>
</header>
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="courseList.id===''">
<em class="icon30 no-data-ico">&nbsp;</em>
<span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理中...</span>
</section>
<!-- /无数据提示 结束-->
<article class="comm-course-list">
<ul class="of">
<li v-for="course in courseList" :key="course.id">
<div class="cc-l-wrap">
<section class="course-img">
<img v-if="course.cover!==null" :src="course.cover" class="img-responsive" >
<img v-else src="~/assets/img/video.jpg" 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>
</ul>
<div class="clear"></div>
</article>
</div>
</section>
</section>
<!-- {{teacherObj.name}}
{{teacherObj.intro}}
<ul>
<li v-for="course in courseList" :key="course.id">
{{course.title}}
</li>
</ul> -->
</div>
</template>

效果:

7.7.1. 有数据

7.7.2. 无数据

8.  课程列表

8.1. 请求分页查询 - FrontEduCourseController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.online.edu.eduservice.controller.front;

import com.online.edu.common.R;
import com.online.edu.eduservice.entity.EduCourse;
import com.online.edu.eduservice.entity.front.query.FrontCourseInfo;
import com.online.edu.eduservice.service.EduCourseService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@CrossOrigin
@RequestMapping("/eduservice/front/edu-course")
public class FrontEduCourseController {

@Autowired
EduCourseService eduCourseService;

@ApiOperation("课程分页查询")
@GetMapping("/listCourses/{current}/{size}")
public R listCourses(@PathVariable String current,
@PathVariable String size){
Map<String,Object> courses = eduCourseService.frontSelectPage(current,size);
return R.ok().data("items",courses);
}
}

8.2. 查询实现 - EduCourseServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//前台分页查询课程信息
@Override
public Map<String,Object> frontSelectPage(String current, String size) {

Long currentPage = Long.parseLong(current);
Long pageSize = Long.parseLong(size);

Page<EduCourse> page = new Page<>(currentPage,pageSize);
// QueryWrapper<EduCourse> queryWrapper = new QueryWrapper<>();
// queryWrapper.orderByAsc("id");

Page<EduCourse> eduCoursePage = baseMapper.selectPage(page,null);

Map<String,Object> map = new HashMap<>();
map.put("total",eduCoursePage.getTotal());
map.put("courses",eduCoursePage.getRecords());

return map;
}

8.3. 测试swagger-ui

8.4. 前台封装请求方法-api/course.js

1
2
3
4
5
6
7
8
9
10
11
12
import request from '@/utils/request'

const base = '/eduservice/front/edu-course'

export default{
listCourses(current,size){
return request({
url:`${base}/listCourses/${current}/${size}`,
method:'get'
})
}
}

8.5. 调用请求 - pages/course/index.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<div>
<ul>
<li v-for="course in courseList" :key="course.id">
{{course.title}}
</li>
</ul>
<el-pagination
:current-page="current"
:page-size="size"
:total="total"
background
style="padding: 30px 0; text-align: center;"
layout="total, prev, pager, next, jumper"
@current-change="getCourseList"/>
</div>
</template>

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

export default {
data(){
return{
courseList: [],
current: 1,
size: 3,
total: 0
}
},
created(){
this.getCourseList()

},
methods:{
getCourseList(current=1){
this.current = current
course.listCourses(this.current,this.size).then(response=>{
this.courseList = response.data.data.items.courses
this.total = response.data.data.items.total
})
}

}

}
</script>

8.6. 实现页面 - 得到数据

8.7. 添加样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- /课程列表 开始 -->
<section class="container">
<header class="comm-title">
<h2 class="fl tac">
<span class="c-333">全部课程</span>
</h2>
</header>
<div class="mt40">
<!-- /无数据提示 开始-->
<section class="no-data-wrap" v-if="courseList.length===0">
<em class="icon30 no-data-ico">&nbsp;</em>
<span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理中...</span>
</section>
<!-- /无数据提示 结束-->
<article class="comm-course-list">
<ul class="of" id="bna">
<li v-for="course in courseList" :key="course.id">
<div class="cc-l-wrap">
<section class="course-img">
<img v-if="course.cover!==null&&course.cover!==''" :src="course.cover" class="img-responsive" >
<img v-else src="~/assets/img/video.jpg" class="img-responsive">
<div class="cc-mask">
<a :href="`/course/`+course.id" title="开始学习" class="comm-btn c-btn-1">开始学习</a>
</div>
</section>
<h3 class="hLh30 txtOf mt10">
<a :href="`/course/`+course.id" :title="course.title" class="course-title fsize18 c-333">
{{course.title}}
</a>
</h3>
<section class="mt10 hLh20 of">
<span class="fr jgTag bg-green">
<i class="c-fff fsize12 f-fA" v-if="Number(course.price)===0">免费</i>
<i class="c-fff fsize12 f-fA" v-else>收费</i>
</span>
<span class="fl jgAttr c-ccc f-fA">
<i class="c-999 f-fA">{{course.viewCount}}人学习</i>
<!-- |
<i class="c-999 f-fA">123评论</i> -->
</span>
</section>
</div>
</li>
</ul>
<div class="clear"></div>
</article>
</div>
</section>


<!-- <ul>
<li v-for="course in courseList" :key="course.id">
{{course.title}}
</li>
</ul> -->
<el-pagination
:current-page="current"
:page-size="size"
:total="total"
background
style="padding: 30px 0; text-align: center;"
layout="total, prev, pager, next, jumper"
@current-change="getCourseList"/>
</div>
</template>

8.8. 页面

9.  单个课程页面

9.1. 查询课程表+描述表+讲师表+科目表 - 组合显示

1
2
3
4
5
6
7
8
9
select c.id,c.title,c.price,c.cover,c.view_count,c.lesson_num,c.buy_count,
cd.description,
et.id teacherId,et.name teacherName,et.avatar,et.intro,
es.id subjecId
from edu_course c
left outer join edu_course_description cd on c.id=cd.id
left outer join edu_teacher et on c.teacher_id=et.id
left outer join edu_subject es on c.subject_id = es.id
where c.id = '1235822849831206914';

9.2. FrontCourseInfo - 查询组合对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.online.edu.eduservice.entity.front.query;

import com.online.edu.eduservice.entity.query.QueryChapter;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiParam;
import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

@Data
public class FrontCourseInfo {

@ApiModelProperty("id")
private String id;

@ApiModelProperty("课程标题")
private String title;

@ApiModelProperty("课程封面")
private String cover;

@ApiModelProperty("课程总价")
private BigDecimal price;

@ApiModelProperty("总课时")
private int lessonNum;

@ApiModelProperty(value = "销售数量")
private Long buyCount;

@ApiModelProperty(value = "浏览数量")
private Long viewCount;

@ApiModelProperty("课程描述")
private String description;

@ApiModelProperty("讲师ID")
private String teacherId;

@ApiModelProperty("讲师姓名")
private String teacherName;

@ApiModelProperty("讲师头像")
private String avatar;

@ApiModelProperty("讲师资历")
private String intro;

@ApiModelProperty("科目类别")
private String subjectName;

@ApiModelProperty("科目ID")
private String subjectId;

@ApiModelProperty("章节+小节")
private List<QueryChapter> chapters;

}

9.3. EduTeacherMapper.xml - 查询语句

1
2
3
4
5
6
7
8
9
10
11
<select id="frontGetAllCourseInfo" resultType="com.online.edu.eduservice.entity.front.query.FrontCourseInfo">
select c.id,c.title,c.price,c.cover,c.view_count,c.lesson_num,c.buy_count,
cd.description,
et.id teacherId,et.name teacherName,et.avatar,et.intro,
es.id subjectId
from edu_course c
left outer join edu_course_description cd on c.id=cd.id
left outer join edu_teacher et on c.teacher_id=et.id
left outer join edu_subject es on c.subject_id = es.id
where c.id = #{id}
</select>

9.4. EduCourseMapper - 定义查询方法

1
2
3
4
5
6
7
public interface EduCourseMapper extends BaseMapper<EduCourse> {

CourseInfo getAllCourseInfo(String id);

FrontCourseInfo frontGetAllCourseInfo(String id);

}

9.5. EduCourseServiceImpl - 组合查询结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//得到前台课程信息 = 课程+讲师+描述表+科目表
@Override
public FrontCourseInfo frontGetAllCourseInfo(String id) {

FrontCourseInfo courseInfo = baseMapper.frontGetAllCourseInfo(id);
//科目路径
String subjectName = eduSubjectService.getSubjectPath(courseInfo.getSubjectId());
courseInfo.setSubjectName(subjectName);

//章节
List<QueryChapter> chapters = eduChapterService.selectAllInfo(id);
courseInfo.setChapters(chapters);


return courseInfo;
}

9.6. FrontEduCourseController

1
2
3
4
5
6
7
@ApiOperation("单课程信息:课程,讲师,章节,视频")
@GetMapping("/getCourseById/{id}")
public R getCourseIdById(@PathVariable String id){

FrontCourseInfo courseInfo = eduCourseService.frontGetAllCourseInfo(id);
return R.ok().data("course",courseInfo);
}

9.7. 测试swagger-ui

9.8. 前台定义路由 - course.js

1
2
3
4
5
6
getCourseById(id){
return request({
url:`${base}/getCourseById/`+id,
method: 'get'
})
}

9.9. 单个课程页面 - course/_id.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<div>
{{courseObj.title}}
<h3>{{courseObj.teacherName}}</h3>
<h3>{{courseObj.subjectName}}</h3>
<ul>
<li v-for="chapter in chapterList" :key="chapter.id">
- {{chapter.title}}
<ul>
<li v-for="course in chapter.children" :key="course.id">
-- {{course.title}}
</li>
</ul>
</li>
</ul>

</div>
</template>
<script>
import course from '@/api/course'
export default {
data(){
return{
courseObj:{},
chapterList: []

}
}
,created(){
this.getCourseById(this.$route.params.id)
}
,methods:{
getCourseById(id){
course.getCourseById(id).then(response=>{
this.courseObj = response.data.data.course
this.chapterList = response.data.data.course.chapters
})
}

}
}
</script>

9.10. 测试页面展示数据

9.11. 添加样式

描述的显示 v-html 除去html部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<template>
<div id="aCoursesList" class="bg-fa of">
<!-- /课程详情 开始 -->
<section class="container">
<section class="path-wrap txtOf hLh30">
<a href="#" title class="c-999 fsize14">首页</a>
\
<a :href="`/course`" title class="c-999 fsize14">课程列表</a>

<span class="c-333 fsize14"
v-for="(name,index) in courseObj.subjectName" :key="index">
\ {{name}}
</span>
</section>

<div>
<article class="c-v-pic-wrap" style="height: 357px;">
<section class="p-h-video-box" id="videoPlay">
<img v-if="courseObj.cover!==null&&courseObj.cover!==''" :src="courseObj.cover" class="img-responsive" style="width: 650px;height: 358px;">
<img v-else src="~/assets/img/video.jpg" class="img-responsive" style="width: 650px;height: 358px;">
</section>
</article>
<aside class="c-attr-wrap">
<section class="ml20 mr15">
<h2 class="hLh30 txtOf mt15">
<span class="c-fff fsize24">{{courseObj.title}}</span>
</h2>
<section class="c-attr-jg">
<span class="c-fff">价格:</span>
<b class="c-yellow" style="font-size:24px;">¥{{courseObj.price}}</b>
</section>
<section class="c-attr-mt c-attr-undis">
<span class="c-fff fsize14">
主讲: {{courseObj.teacherName}}&nbsp;&nbsp;&nbsp;
</span>
</section>
<section class="c-attr-mt of">
<span class="ml10 vam">
<em class="icon18 scIcon"></em>
<a class="c-fff vam" title="收藏" href="#" >收藏</a>
</span>
</section>
<section class="c-attr-mt">
<a href="#" title="立即观看" class="comm-btn c-btn-3">立即观看</a>
</section>
</section>
</aside>
<aside class="thr-attr-box">
<ol class="thr-attr-ol clearfix">
<li>
<p>&nbsp;</p>
<aside>
<span class="c-fff f-fM">购买数</span>
<br>
<h6 class="c-fff f-fM mt10">{{courseObj.buyCount}}</h6>
</aside>
</li>
<li>
<p>&nbsp;</p>
<aside>
<span class="c-fff f-fM">课时数</span>
<br>
<h6 class="c-fff f-fM mt10">{{courseObj.lessonNum}}</h6>
</aside>
</li>
<li>
<p>&nbsp;</p>
<aside>
<span class="c-fff f-fM">浏览数</span>
<br>
<h6 class="c-fff f-fM mt10">{{courseObj.viewCount}}</h6>
</aside>
</li>
</ol>
</aside>
<div class="clear"></div>
</div>
<!-- /课程封面介绍 -->
<div class="mt20 c-infor-box">
<article class="fl col-7">
<section class="mr30">
<div class="i-box">
<div>
<section id="c-i-tabTitle" class="c-infor-tabTitle c-tab-title">
<a name="c-i" class="current" title="课程详情">课程详情</a>
</section>
</div>
<article class="ml10 mr10 pt20">
<div>
<h6 class="c-i-content c-infor-title">
<span>课程介绍</span>
</h6>
<div class="course-txt-body-wrap">
<section class="course-txt-body">
<p v-html="courseObj.description">{{courseObj.description}}</p>
</section>
</div>
</div>
<!-- /课程介绍 -->
<div class="mt50">
<h6 class="c-g-content c-infor-title">
<span>课程大纲</span>
</h6>
<section class="mt20">
<div class="lh-menu-wrap">
<menu id="lh-menu" class="lh-menu mt10 mr10">
<ul>
<!-- 文件目录 -->
<li class="lh-menu-stair" v-for="chapter in chapterList" :key="chapter.id">
<a href="javascript: void(0)" :title="chapter.title" class="current-1">
<em class="lh-menu-i-1 icon18 mr10"></em>{{chapter.title}}
</a>
<ol class="lh-menu-ol" style="display: block;">
<li class="lh-menu-second ml30" v-for="video in chapter.children" :key="video.id">
<a href="#" title>
<span class="fr">
<i class="free-icon vam mr10" v-if="!video.isFree">免费试听</i>
</span>
<em class="lh-menu-i-2 icon16 mr5">&nbsp;</em>{{video.title}}
</a>
</li>
</ol>
</li>
</ul>
</menu>
</div>
</section>
</div>
<!-- /课程大纲 -->
</article>
</div>
</section>
</article>
<aside class="fl col-3">
<div class="i-box">
<div>
<section class="c-infor-tabTitle c-tab-title">
<a title href="javascript:void(0)">主讲讲师</a>
</section>
<section class="stud-act-list">
<ul style="height: auto;">
<li>
<div class="u-face">
<a :href="`/teacher/`+courseObj.teacherId">
<img v-if="courseObj.avatar!==''&&courseObj.avatar!==null" :src="courseObj.avatar" width="50" height="50" alt/>
<img v-else src="~/assets/img/avatar-boy.gif" width="50" height="50" alt/>
</a>
</div>
<section class="hLh30 txtOf">
<a class="c-333 fsize16 fl" :href="`/teacher/`+courseObj.teacherId">{{courseObj.teacherName}}</a>
</section>
<section class="hLh20 txtOf">
<span class="c-999">{{courseObj.intro}}</span>
</section>
</li>
</ul>
</section>
</div>
</div>
</aside>
<div class="clear"></div>
</div>
</section>
<!-- {{courseObj.title}}
<h3>{{courseObj.teacherName}}</h3>
<h3>{{courseObj.subjectName}}</h3>
<ul>
<li v-for="chapter in chapterList" :key="chapter.id">
- {{chapter.title}}
<ul>
<li v-for="course in chapter.children" :key="course.id">
-- {{course.title}}
</li>
</ul>
</li>
</ul> -->
</div>
</template>
<script>
import course from '@/api/course'
export default {
data(){
return{
courseObj:{},
chapterList: []

}
}
,created(){
this.getCourseById(this.$route.params.id)

}
,methods:{
getCourseById(id){
course.getCourseById(id).then(response=>{
this.courseObj = response.data.data.course
this.chapterList = response.data.data.course.chapters
})
}

}
}
</script>

9.12. 展示

10. 立即观看-添加锚点

跳转至章节部分

1
2
3
4
5
<a href="#playvideos" title="立即观看" class="comm-btn c-btn-3">立即观看</a>

<ul id="playvideos">
<!-- 文件目录 -->
</ul>

11. 播放页面

点击小节实现跳转

1
<a :href="`/player/`+video.videoSourceId" title>

11.1. 播放刷新观看人数-video.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import request from '@/utils/request'

const base = '/videoservice/front/video'

export default{
getVideoInfo(videoSourceId){
return request({
url: `${base}/getVideoInfo/`+videoSourceId,
method: 'get'
})
},
updateViewCount(videoSourceId){
return request({
url: '/eduservice/front/edu-video/updateViewCount/'+videoSourceId,
method:'get'
})
}
}

11.2. 后端 - controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.online.edu.eduservice.controller.front;

import com.online.edu.common.R;
import com.online.edu.eduservice.service.EduVideoService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@CrossOrigin
@RequestMapping("/eduservice/front/edu-video")
public class FrontEduVideoController {

@Autowired
EduVideoService eduVideoService;

@ApiOperation("更新观看人数")
@GetMapping("/updateViewCount/{videoSourceId}")
public R updateViewCount(@PathVariable String videoSourceId){
eduVideoService.updateViewCount(videoSourceId);
return R.ok();
}
}

11.3. 后端 - service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
public void updateViewCount(String videoSourceId) {

//根据video_source_id查询视频
QueryWrapper<EduVideo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("video_source_id",videoSourceId);
EduVideo video = baseMapper.selectOne(queryWrapper);

//得到视频得课程id
String courseId = video.getCourseId();

EduCourse course = courseService.getById(courseId);

//课程中的观看数+1
Long viewCount = course.getViewCount();
viewCount++;
course.setViewCount(viewCount);
courseService.updateById(course);

//更新视频信息
R result = vodClient.getAliyunVideoInfo(video.getVideoSourceId());
Map<String,Object> map = (Map<String,Object>)result.getData().get("videoInfo");
video.setDuration((Double)map.get("duration"));
video.setStatus((String)map.get("status"));

baseMapper.updateById(video);

}

12. 新建pages/player/_id.vue

播放器直接来自阿里云官网

集成文档:https://help.aliyun.com/document_detail/51991.html?spm=a2c4g.11186623.2.39.478e192b8VSdEn

在线配置:https://player.alicdn.com/aliplayer/setting/setting.html

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div>
<!-- 阿里云视频播放器样式 -->
<link rel="stylesheet" href="/css/aliplayer-min.css" />
<!-- 阿里云视频播放器脚本 -->
<script type="text/javascript" src="/js/aliplayer-min.js"></script>
<!-- <link rel="stylesheet" href="//g.alicdn.com/de/prismplayer/2.6.0/skins/default/aliplayer-min.css" />
<script type="text/javascript" src="//g.alicdn.com/de/prismplayer/2.6.0/aliplayer-min.js"></script> -->

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

</div>
</template>
<script>
import video from '@/api/video'
export default {

//layout:'video',
asyncData({params,error}){
return video.getVideoInfo(params.id).then(response=>{
//console.log(response)
return{
videoId: params.id,
playAuth: response.data.data.videoInfo.playAuth,
cover: response.data.data.videoInfo.coverUrl
}
})
},
created(){
//每次刷新页面,课程观看次数+1
video.updateViewCount(this.$route.params.id)
},

/**
* 页面渲染完成时:此时js脚本已加载,Aliplayer已定义,可以使用
* 如果在created生命周期函数中使用,Aliplayer is not defined错误
*/
mounted(){
var player = new Aliplayer({
id: "J_prismPlayer",
autoplay: true,
width:"720px",
height:"300px",
encryptType: '1',
vid: this.videoId,
playauth: this.playAuth,
cover: this.cover,
rePlay: false, // 循环播放,
preload: true,
controlBarVisibility: 'hover', // 控制条的显示方式:鼠标悬停
useH5Prism: true, // 播放器类型:html5
qualitySort: 'asc', // 清晰度排序,
format: 'mp4' // 播放格式

});
}
}
</script>

这里 chrome浏览器版本不支持 flv格式播放视频

将视频转码,这里会将视频转为mp4,m3u8格式

这样flv视频就可以标清版播放

本文结束  感谢您的阅读