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的模块),可以选axios
和PWA
,如果选了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); 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"> </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}} <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)"> </a> </section> </header> <!-- /无数据提示 开始--> <section class="no-data-wrap" v-if="courseList.id===''"> <em class="icon30 no-data-ico"> </em> <span class="c-666 fsize14 ml10 vam">没有相关数据,小编正在努力整理中...</span> </section> <!-- /无数据提示 结束--> <article class="comm-course-list"> <ul class="of"> <li v-for="course in courseList" :key="course.id"> <div class="cc-l-wrap"> <section class="course-img"> <img 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); 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"> </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 cleft outer join edu_course_description cd on c.id= cd.idleft outer join edu_teacher et on c.teacher_id= et.idleft outer join edu_subject es on c.subject_id = es.idwhere 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}} </span> </section> <section class="c-attr-mt of"> <span class="ml10 vam"> <em class="icon18 scIcon"></em> <a class="c-fff vam" title="收藏" href="#" >收藏</a> </span> </section> <section class="c-attr-mt"> <a href="#" title="立即观看" class="comm-btn c-btn-3">立即观看</a> </section> </section> </aside> <aside class="thr-attr-box"> <ol class="thr-attr-ol clearfix"> <li> <p> </p> <aside> <span class="c-fff f-fM">购买数</span> <br> <h6 class="c-fff f-fM mt10">{{courseObj.buyCount}}</h6> </aside> </li> <li> <p> </p> <aside> <span class="c-fff f-fM">课时数</span> <br> <h6 class="c-fff f-fM mt10">{{courseObj.lessonNum}}</h6> </aside> </li> <li> <p> </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"> </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) { QueryWrapper<EduVideo> queryWrapper = new QueryWrapper <>(); queryWrapper.eq("video_source_id" ,videoSourceId); EduVideo video = baseMapper.selectOne(queryWrapper); String courseId = video.getCourseId(); EduCourse course = courseService.getById(courseId); 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视频就可以标清版播放