[TOC]

前台系统

搭建NUXT框架作为前台系统

服务端渲染技术NUXT

什么是服务端渲染

服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容,而不是在客户端通过AJAX获取数据。

服务器端渲染(SSR)的优势主要在于:更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。

如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再进行页面内容的抓取。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染(SSR)解决此问题。

另外,使用服务器端渲染,我们可以获得更快的内容到达时间(time-to-content),无需等待所有的 JavaScript 都完成下载并执行,产生更好的用户体验,对于那些「内容到达时间(time-to-content)与转化率直接相关」的应用程序而言,服务器端渲染(SSR)至关重要。

什么是NUXT

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

官网网站:

https://zh.nuxtjs.org/

NUXT环境初始化

下载压缩包

https://github.com/nuxt-community/starter-template/archive/master.zip

解压

将template中的内容复制到 guli

安装ESLint

将guli-admin项目下的.eslintrc.js配置文件复制到当前项目下

修改package.json

name、description、author(必须修改这里,否则项目无法安装)

 "name": "guli",
 "version": "1.0.0",
 "description": "谷粒学院前台网站",
 "author": "Helen <55317332@qq.com>",

修改nuxt.config.js

修改title: ‘{{ name }}’、content: ‘{{escape description }}’

这里的设置最后会显示在页面标题栏和meta数据中

head: {
    title: '谷粒学院 - Java视频|HTML5视频|前端视频|Python视频|大数据视频-自学拿1万+月薪的IT在线视频课程,谷粉力挺,老学员为你推荐',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'keywords', name: 'keywords', content: '谷粒学院,IT在线视频教程,Java视频,HTML5视频,前端视频,Python视频,大数据视频' },
      { hid: 'description', name: 'description', content: '谷粒学院是国内领先的IT在线视频学习平台、职业教育平台。截止目前,谷粒学院线上、线下学习人次数以万计!会同上百个知名开发团队联合制定的Java、HTML5前端、大数据、Python等视频课程,被广大学习者及IT工程师誉为:业界最适合自学、代码量最大、案例最多、实战性最强、技术最前沿的IT系列视频课程!' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
},

安装依赖

npm install

NUXT目录结构

(1)资源目录 assets

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

(2)组件目录 components

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

(3)布局目录 layouts

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

(4)页面目录 pages

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

(5)插件目录 plugins

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

(6)nuxt.config.js 文件

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

幻灯片插件

安装插件

npm install vue-awesome-swiper@3.1.3

配置插件

在 plugins 文件夹下新建文件 nuxt-swiper-plugin.js,内容是

import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr'
Vue.use(VueAwesomeSwiper)

在 nuxt.config.js 文件中配置插件

将 plugins 和 css节点 复制到 module.exports节点下

module.exports = {
  // some nuxt config...
  plugins: [
    { src: '~/plugins/nuxt-swiper-plugin.js', ssr: false }
  ],
  css: [
    'swiper/dist/css/swiper.css'
  ]
}

NUXT路由

固定路由

使用router-link构建路由,地址是/course

image.png

在page目录创建文件夹course ,在course目录创建index.vue

<template>
  <div>
    课程列表
  </div>
</template>

动态路由

创建方式

如果我们需要根据id查询一条记录,就需要使用动态路由。NUXT的动态路由是以下划线开头的vue文件,参数名为下划线后边的文件名

在pages下的course目录下创建_id.vue

<template>
  <div>
    讲师详情
  </div>
</template>

封装axios

我们可以参考guli-admin将axios操作封装起来,下载axios ,使用命令 npm install axios,创建utils文件夹,utils下创建request.js

import axios from 'axios'
// 创建axios实例
const service = axios.create({
  baseURL: 'http://localhost:8201', // api的base_url
  timeout: 20000 // 请求超时时间
})
export default service

Banner图的显示

首页Banner图

后端

  • 创建service_sms工程,逆向工程生成项目结构
  • 启动类

    @SpringBootApplication
    @MapperScan("com.atguigu.cmsservice.mapper")
    @EnableCaching                // 开启缓存
    @ComponentScan("com.atguigu") //指定扫描位置
    public class CmsApplication {
     public static void main(String[] args) {
         SpringApplication.run(CmsApplication.class, args);
     }
    }
    
  • 配置文件

    # 服务端口
    server:
    port: 8004
    spring:
    application:
     name: service-cms  # 服务名
    profiles:
     active: dev          # 设置为开发环境
    datasource:            # 配置数据源
     driver-class-name: com.mysql.cj.jdbc.Driver
     url: jdbc:mysql://localhost:3306/guli_edu?characterEncoding=utf-8&serverTimezone=GMT%2B8
     username: root
     password: root
    jackson: # 配置json全局时间
     date-format: yyyy-MM-dd HH:mm:ss  # 配置返回json的时间格式
     time-zone: GMT+8                  # json是格林尼治时间,和我们相差8小时,需要加上8
    cloud:
     nacos:
       discovery:
         server-addr: 127.0.0.1:8848   # nacos服务地址
    redis:
     host: 192.168.241.130             # ip地址
     port: 6379                        # 端口号
     database: 0
     timeout: 1800000                  # 超时时间
     lettuce:
       pool:
         max-active: 20
         max-wait: -1                  # 最大阻塞等待时间(负数表示没限制)
         max-idle: 5
         min-idle: 0
    mybatis-plus:                         # mybatis-plus日志
    configuration:
     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    mapper-locations: classpath:com/atguigu/eduservice/mapper/xml/*.xm
    
  • controller
    ```java @RestController @RequestMapping(“/cmsservice/crm-banner-front”) @CrossOrigin public class CrmBannerFrontController {

    @Autowired private CrmBannerService crmBannerService;

    @GetMapping(“getAllBanner”) public ResultEntity getAllBanner(){

     List<CrmBanner> crmBannerList = crmBannerService.selectBannerList();
     return ResultEntity.ok().data("crmBannerList" , crmBannerList);
    

    }

}


-  业务逻辑实现接口,使用注解[@Cacheable(value ](/Cacheable(value ) = "banner" , key = "'selectBannerList'"),将banner图信息存入redis   
```java
@Service
public class CrmBannerServiceImpl extends ServiceImpl<CrmBannerMapper, CrmBanner> implements CrmBannerService {

    @Override
    @Cacheable(value = "banner" , key = "'selectBannerList'")
    public List<CrmBanner> selectBannerList() {
        QueryWrapper<CrmBanner> crmBannerQueryWrapper = new QueryWrapper<>();
        crmBannerQueryWrapper.orderByDesc("id");
        // 拼接sql查询2条记录
        crmBannerQueryWrapper.last("limit 2");
        List<CrmBanner> crmBannerList = baseMapper.selectList(crmBannerQueryWrapper);
        return crmBannerList;
    }
}

前端

  • api定义接口地址

    import request from '@/utils/request'
    export default {
    getList() {
     return request({
       url: `/cmsservice/crm-banner-front/getAllBanner`,
       method: 'get'
     })
    }
    }
    
  • js调用方法
    ```javascript

    
    -  banner图显示  
    ```vue
       <!-- 幻灯片 开始 -->
        <div v-swiper:mySwiper="swiperOption">
          <div class="swiper-wrapper">
            <div
              v-for="banner in bannerList"
              :key="banner.id"
              class="swiper-slide"
              style="background: #040b1b"
            >
              <a target="_blank" :href="banner.linkUrl">
                <img width="100%" :src="banner.imageUrl" :alt="banner.title" />
              </a>
            </div>
          </div>
          <div class="swiper-pagination swiper-pagination-white"></div>
          <div
            class="swiper-button-prev swiper-button-white"
            slot="button-prev"
          ></div>
          <div
            class="swiper-button-next swiper-button-white"
            slot="button-next"
          ></div>
        </div>
        <!-- 幻灯片 结束 -->
    

    讲师和课程的前台显示

    后端

    • service_edu工程中创建controller,同时将课程和讲师信息存入redis @Cacheable(key = “‘index’” , value = “teacherAndCourseIndexInfo”)
      ```java @RestController @CrossOrigin @RequestMapping(“/eduservice/index”) public class IndexController {

      @Autowired private EduCourseService eduCourseService;

      @Autowired private EduTeacherService eduTeacherService;

      @GetMapping(“index”) @Cacheable(key = “‘index’” , value = “teacherAndCourseIndexInfo”) public ResultEntity index() {

       // 根据课程id查询前8个
       QueryWrapper<EduCourse> eduCourseQueryWrapper = new QueryWrapper<>();
       eduCourseQueryWrapper.orderByDesc("id");
       eduCourseQueryWrapper.last("limit 8");
       List<EduCourse> eduCourseList = eduCourseService.list(eduCourseQueryWrapper);
      
       // 查询前4条名师
       QueryWrapper<EduTeacher> teacherQueryWrapper = new QueryWrapper<>();
       eduCourseQueryWrapper.orderByDesc("id");
       eduCourseQueryWrapper.last("limit 4");
       List<EduTeacher> eduTeacherList = eduTeacherService.list(teacherQueryWrapper);
      
       return ResultEntity.ok().data("eduList", eduCourseList).data("teacherList", eduTeacherList);
      
    }
    

    }

    
    -  edu模块中配置redis的信息  
    ```yaml
    spring:
        redis:
          host: 192.168.241.130             # ip地址
          port: 6379                        # 端口号
          database: 0
          timeout: 1800000                  # 超时时间
          lettuce:
            pool:
              max-active: 20
              max-wait: -1                  # 最大阻塞等待时间(负数表示没限制)
              max-idle: 5
              min-idle: 0
    

    前端

    • api定义接口地址

      import request from '@/utils/request'
      export default {
       // 查询显示在首页的课程和讲师
       getList() {
           return request({
               url: `/eduservice/index/index`,
               method: 'get'
           })
       }
      }
      
    • 引入index.js文件

      import index from "@/api/index/index.js";
      
    • data中定义讲师和课程数据

      teacherList: {},
      courseList: {},
      
    • 调用方法

      initDataTeacherAndCourse() {
         index.getList().then((response) => {
           this.teacherList = response.data.data.teacherList;
           this.courseList = response.data.data.eduList;
         });
       },
      
    • 显示课程和讲师

      <!-- 网校课程 开始 -->
         <div>
           <section class="container">
             <header class="comm-title">
               <h2 class="tac">
                 <span class="c-333">热门课程</span>
               </h2>
             </header>
             <div>
               <article class="comm-course-list">
                 <ul class="of" id="bna">
                   <li v-for="(course, index) in courseList" v-bind:key="index">
                     <div class="cc-l-wrap">
                       <section class="course-img">
                         <!-- ~/assets/photo/course/01.jpg -->
                         <img
                           :src="course.cover"
                           class="img-responsive"
                           :alt="course.title"
                         />
                         <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="#"
                           :title="course.title"
                           class="course-title fsize18 c-333"
                           >{{ course.title }}</a
                         >
                       </h3>
                       <section class="mt10 hLh20 of">
                         <span
                           class="fr jgTag bg-green"
                           v-if="Number(course.price) === 0"
                         >
                           <i class="c-fff fsize12 f-fA">免费</i>
                         </span>
                         <span class="fr jgTag bg-green" v-else>
                           <i class="c-fff fsize12 f-fA"> ¥{{ course.price }}</i>
                         </span>
                         <span class="fl jgAttr c-ccc f-fA">
                           <i class="c-999 f-fA">{{ course.buyCount }} 人学习</i>
                           |
                           <i class="c-999 f-fA">{{ course.viewCount }} 人浏览</i>
                         </span>
                       </section>
                     </div>
                   </li>
                 </ul>
                 <div class="clear"></div>
               </article>
               <section class="tac pt20">
                 <a href="#" title="全部课程" class="comm-btn c-btn-2">全部课程</a>
               </section>
             </div>
           </section>
         </div>
         <!-- /网校课程 结束 -->
         <!-- 网校名师 开始 -->
         <div>
           <section class="container">
             <header class="comm-title">
               <h2 class="tac">
                 <span class="c-333">名师大咖</span>
               </h2>
             </header>
             <div>
               <article class="i-teacher-list">
                 <ul class="of">
                   <li v-for="(teacher, index) in teacherList" v-bind:key="index">
                     <section class="i-teach-wrap">
                       <div class="i-teach-pic">
                         <a :href="'/teacher/' + teacher.id" :title="teacher.name">
                           <img :alt="teacher.name" :src="teacher.avatar" />
                         </a>
                       </div>
                       <div class="mt10 hLh30 txtOf tac">
                         <a
                           :href="'/teacher/' + teacher.id"
                           :title="teacher.name"
                           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>
                 </ul>
                 <div class="clear"></div>
               </article>
               <section class="tac pt20">
                 <a href="#" title="全部讲师" class="comm-btn c-btn-2">全部讲师</a>
               </section>
             </div>
           </section>
         </div>