前言

目前使用Nuxt.js框架做了几个项目,不得不说对比与Vue-CLINuxt.js实在是懒人必备框架,什么东西都帮你配置好了。所以多少对它有点了解,所以有了这个笔记。希望未来再做类似的地方有个能够参考的笔记。

目录结构

/assets           # 能被webpack处理的项目资源
/components       # 项目组件
/layouts          # 项目布局
/middleware       # 项目中间件
/pages            # 项目页面 页面路由自动生成
/plugins          # 项目插件
/static           # 不被webpack处理的静态资源
/store            # Vuex状态管理目录
nuxt.config.js    # Nuxt.js配置文件
package.json      # npm包管理器文件
  • /assets:这个目录可以存放字体,样式,图片等,这些资源将会被WebPack处理器编译最终形态文件。
  • /components:存放Vue.js组件目录,如果Nuxt.js特征方法是不能在该目录下使用的。
  • /layouts:存放项目布局组件,在不同的页面展示双栏或者单栏布局。
  • /middleware:放置项目中间件应用,例如路由鉴权等功能。
  • /pages:放置项目视图和路由。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。
  • /plugins:放置声明Vue插件或者一些JavaScript插件配置文件。
  • /static:放置静态资源目录,可以启动后直接访问资源。
  • /storeVuex状态树管理配置文件。
  • nuxt.config.js:用于Nuxt.js配置,覆盖默认设置。

除了上面默认的文件夹,我们可以自己额外添加文件:

  • /api:存放项目接口文件。
  • /utils:项目工具类。
  • /test:单元测试类。

单应用和seo优化

现在绝大项目都是前后端分离的项目,所以纯粹作为静态页面来编写,打开nuxt.config.js修改其中的mode: 'spa'即可。然后将package.json中的dev启动方式修改成"dev": "nuxt --spa"加快启动编译项目。

SPA:没有服务器端渲染(只有客户端路由导航等)

Universal :同构应用程序(服务器端呈现+客户端路由导航等)

Nuxt.js的SEO优化还算可以,需要注意header设置meta设置,Nuxt.js使用的是Vue-Meta,可以到它文档查看相关设置:

export default {
  /*
  ** Headers of the page
  */
  head: {
    htmlAttrs: {
      lang: 'zh-CN',
    },
    title: `淮城一只猫`,
    meta: [
      { charset: 'utf-8' },
      { name: 'apple-mobile-web-app-capable', content: 'yes' }, // iOS浏览器禁止缩放
      { name: 'viewport', content: 'width=device-width, initial-scale=1.0, shrink-to-fit=no, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no' },
      { hid: 'keywords', name: 'keywords', content: 'keywords' },
      { hid: 'description', name: 'description', content: 'description' },
      { name: 'renderer', content: 'webkit' }, // 强制让360浏览器使用Webkit内核
      { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' },
    ],
    script: [],
    link: []
  },
}

然后在pages每个页面设置:

export default {
  head() {
    return {
      title: "淮城一只猫 - 首页"
    };
  }
}

如果能力有条件的可以尝试Nuxt PWA,这里就不再详细描述了。

服务端渲染

如果在前端展示的时候,给搜索引擎爬虫抓取页面需要注意更好的SEO展示,那么就需要服务器端渲染了(SSR),具体内容可以查看官网文档:为什么使用服务器端渲染 (SSR)?

主要博主做的项目大部分都是后端界面,所以对此也不是太熟,故这里就不再描述了。

样式预处理器

配置样式预处理器也很简单,Nuxt.js有自己封装的包:Nuxt Style Resources,支持sasslessstylus。现在项目用sass处理器比较多,就以它为例:

yarn add sass-loader node-sass # 安装SASS预处理器
yarn add @nuxtjs/style-resources # 安装nuxt.js 样式模块

nuxt.config.js添加:

export default {
  modules: [
    '@nuxtjs/style-resources', // 导入模块
  ],
  /*
  ** Sass module
  */
  styleResources: {
   // your settings here
   sass: [
       './assets/style/variables.scss', // 导入样式
   ], // alternative: scss
  }
}

到这里就可以愉快的使用样式预处理器编写样式了。

其他处理器安装参考Nuxt.js styleResources

接口管理

如果项目默认安装Axios插件后,对axios进行一系列封装也很简单,只需要在/plugins新建个axios.js文件:

import md5 from 'js-md5'
import { getToken, removeToken } from '~/utils/auth'
 
import { Notification } from 'element-ui'
 
export default function ({ $axios, redirect }) {
  // 设置请求头信息
  $axios.setHeader('sign', md5('xxxxxxxxxxxxxxxxxxxxxxxxxxx');
  $axios.setHeader('apiversion', '1.0.0');
  $axios.setHeader('clientfrom', 'pc');
 
  // 当登录的时候 自动设置 请求头 Token
  if (getToken()) {
    $axios.setHeader('token', getToken()); // 设置请求头
  }
 
  /**
   * 请求
   */
  $axios.onRequest(config => {
    // console.log('请求地址:' + config.url)
  });
  /**
   * 响应
   */
  $axios.onResponse(response => {
    // token无效,强制登出
    if( response.data.msg.code === 10001 ) {
      const self = this;
      removeToken();
      Notification({
        title: "系统错误",
        message: "用户Token无效,请重新登陆!",
        type: "error"
      });
      self.$router.push("/login");
    }
  });
  /**
   * 错误
   */
  $axios.onError(error => {
    const code = parseInt(error.response && error.response.status);
    if (code === 400) {
      Notification.error({
        title: '接口错误!',
        message: '接口状态:400,请联系系统管理员!'
      });
      redirect('/400')
    }
    // 接口获取错误
    if (isNaN(code)) {
      Notification.error({
        title: '接口错误!',
        message: '接口状态:未知,请联系系统管理员!'
      });
      redirect('/error')
    }
    // 接口不存在
    if (code === 404) {
      Notification.error({
        title: '接口错误!',
        message: '接口状态:404,请联系系统管理员!'
      });
      redirect('/404')
    }
    // 接口传参错误
    if (code === 500) {
      Notification.error({
        title: '接口错误!',
        message: '接口状态:500,请联系系统管理员!'
      });
      redirect('/500')
    }
  })
}

根据自己项目情况增加或者修改。nuxt.js axios插件文档可以参考:Nuxt.js Axios Module。然后在nuxt.config.js导入:

export default {
  plugins: [
    '~/plugins/axios', // 导入axios配置文件
  ],
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
  ],
  axios: {
      // ... 根据项目调整参数
  }
}

开启接口反代:

如果调试后端接口发现跨域的问题,使用接口反代也很简单,根据文档直接在axios字段添加:

export default {
  /*
  ** Axios module configuration
  */
  axios: {
    // See https://github.com/nuxt-community/axios-module#options
    baseURL: 'http://api.example.com/xx',
    https: false,
    retry: true, // 请求失败重试(仅限3次)
    debug: false,
    proxy: 'http://api.example.com/xx', // 设置代理接口
  },
  // 接口反代
  proxy: {
    '/api/': { target: 'http://api.example.com/xx', pathRewrite: { '^/api/': '' } }
  },
}

如果项目是接口是/index访问的话,反代接口是/api/index就可以访问了。

可维护接口文件管理

为了方便后期维护接口管理,可以在项目根目录新建/api文件夹,目录下面可以新建一些接口文件:

/api
- `index.js` # 首页接口管理
- `user.js` # 用户接口管理
- `cart.js` # 购物车接口管理

然后我们在index.js进行接口编写:

import config from '../config'
 
// 根据环境生成接口前缀
const apiPrefix = process.env.NODE_ENV === 'development' ? '/api' : '';
/**
* 获取首页数据
* @param $axios 接口信息
* @returns {Promise<Object>} 返回数据
*/
export const indexApi = ({$axios}) => {
  return new Promise((resolve, reject) => {
    resolve($axios.$get(`${apiPrefix}/v1.index/index`));
  });
};
 
 
/**
* 获取首页数据 V2
* @param $axios 接口信息
* @param liveID 参数ID
* @returns {Promise<Object>} 返回数据
*/
export const indexV2Api = ({$axios}, liveID) => {
  return new Promise((resolve, reject) => {
    resolve($axios.$get(`${apiPrefix}/v2.index/index`, {
      params: {
        id: liveID
      }
    }));
  });
};
 
/**
* 获取首页数据 V3
* @param $axios 接口信息
* @param type 类型
* @param id 参数ID
* @returns {Promise<Object>} 返回数据
*/
export const indexV3Api = ({$axios}, type, id) => {
  return new Promise((resolve, reject) => {
    resolve($axios.$post(`${apiPrefix}/v3.index/index`, {
      type: type,
      id: id
    }));
  });
};

然后在项目页面直接导入:

import {indexV2Api} from "~/api/index";
 
export default {
  methods: {
     // 异步加载数据
     async initAxiosData() {
         const self = this;
         const {data} = await indexV2Api(self);
         console.log(data); // 返回接口信息
     }
  }
}

这边具体信息可以参考文档:asyncData 方法,但这个方法不能在components组件里使用。

中间件的使用

其实中间件这个说法不是太复杂,例如有个功能用的到,就是路由鉴权,就是在用户登录和游客的时候哪些页面能进或者不能进的。直接上demo吧,直接在/middleware文件夹新建permission.js文件:

import {getToken} from '~/utils/auth' // getToken from cookie
/**
* 路由鉴定
* 一些页面游客无权限访问,需要重新登陆
*/
export default async function ({next, route, store}) {
  const whiteList = ['/', '/login', '/future', '/history', '/future/item', '/history/item', '/login/reg', '/login/forget']; // 不重定向白名单
 
  // 用户登录的时候
  if (getToken()) {
    if (route.path === '/login' || route.path === '/login/reg' || route.path === '/login/forget') {
      // 返回首页
      next('/')
    } else {
      // 请求申请个人信息接口 => 当有Token的时候
      store.dispatch("GetUserInfo");
    }
  } else {
    // 未登录访问非白名单的时候
    if (whiteList.indexOf(route.path) === -1) {
      next('/login') // 否则全部重定向到登录页
    }
  }
}

如果需要全局配置直接在nuxt.config.js配置:

module.exports = {
  router: {
    middleware: 'permission'
  }
}

这样可以在路由加载之前执行permission.js里面的内容。

如果需要只在几个单独的页面执行的话也很简单,只需要打开某个/page/xxx.vue可视化文件配置:

export default {
  data() {},
  middleware: 'permission',
}

参考文档:Nuxt.js 中间件

使用插件

Vue组件:

一般来说使用Vue.js插件偏多,例如使用element-uiVue-BootStrap插件,分别新建element-ui.jsbootstrap.js文件:

// Element-ui.js
import Vue from 'vue'
import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/zh-CN'
 
export default () => {
  Vue.use(Element, { locale })
}
// Bootstrap.js
import Vue from 'vue';
import BootstrapVue from 'bootstrap-vue';
 
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
 
export default () => {
  Vue.use(BootstrapVue)
}

然后在nuxt.config.js文件引入即可:

export default {
  /*
  ** Plugins to load before mounting the App
  */
  plugins: [
    '~/plugins/bootstrap',
    '~/plugins/element-ui',
  ],
  /*
  ** Nuxt.js modules
  */
  modules: [
    // Doc: https://bootstrap-vue.js.org/docs/
    'bootstrap-vue/nuxt',
  ],
  /*
  ** BootStrap-Vue module configuration
  */
  bootstrapVue: {
    bootstrapCSS: false, // or `css`
    bootstrapVueCSS: false // or `bvCSS`
  },
  /*
  ** Build configuration
  */
  build: {
    transpile: [/^element-ui/],
  }
}

vue-bootstrap安装方法可以参考Vue Bootstrap nuxtjs-module文档,里面讲解比较详细。如果是其他UI或者Vue插件的话和上面方法差不多,参考各个文档安装方法就行了。

JavaScript插件:

如果Vue.js组件或者插件无法满足项目需求的话,就需要引入原生JavaScript插件文件来满足项目需求,例如我有个项目类型是直播系统,需要引入视频直播JavaScript插件(video.js视频插件库),可能觉得为什么不需要单独npm包导入?主要考虑到这几个文件只在一个页面使用到,作为npm导入会被WebPack编译成全局使用js文件,再加上占用空间很大,所以需要单独抽离出来做个工具包使用。

那么单独最简单的方法就是直接在页面插入JavaScript文件就行了,把插入的动作函数封装一个工具库:

// -'/utils/loadFile.js'
/**
* 动态插入动态脚本
* @param scripts 动态脚本资源路径,支持url资源路径
* @returns {Promise<array>}
*/
export async function loadScripts(scripts) {
  function get(src) {
    return new Promise(function (resolve, reject) {
      var el = document.createElement("script");
      el.type = "text/javascript";
      el.addEventListener("load", function () {
        resolve(src);
      }, false);
      el.addEventListener("error", function () {
        reject(src);
      }, false);
      el.src = src;
      (document.getElementsByTagName("body")[0] || document.getElementsByTagName("head")[0]).appendChild(el);
    });
  }
 
  const myPromises = scripts.map(async function (script, index) {
    return await get(script);
  });
 
  return await Promise.all(myPromises);
}
 
/**
* 动态插入样式表
* @param scripts 样式脚本资源路径,支持url资源路径
* @returns {Promise<array>}
*/
export async function loadStyles(scripts) {
  function get(src) {
    return new Promise(function (resolve, reject) {
      var el = document.createElement("link");
      el.type = "text/css";
      el.rel = "stylesheet";
      el.addEventListener("load", function () {
        resolve(src);
      }, false);
      el.addEventListener("error", function () {
        reject(src);
      }, false);
      el.href = src;
      document.getElementsByTagName("head")[0].appendChild(el);
    });
  }
 
  const myPromises = scripts.map(async function (script, index) {
    return await get(script);
  });
 
  return await Promise.all(myPromises);
}

上面封装的方法是异步动态载入资源文件,使用的方法也很简单,直接在视图页面引入这个文件载入函数即可:

// -'/pages/item.vue'
import {loadScripts, loadStyles} from "~/utils/loadFile";
export default {
  methods: {
    /**
     * 初始化视频加载资源
     */
    initVideo() {
      const self = this;
      // 检查页面是否存在方法函数
      if (typeof videojs === "undefined") {
        loadScripts(["/video/video.min.js"])
          .then(function () {
            return loadScripts([
              "/video/videojs-http-streaming.min.js",
              "/video/zh-CN.js"
            ]);
          })
          .then(function () {
            return loadStyles([
              "/video/video-js.min.css"
            ]);
          })
          .then(function () {
          // 初始化播放器
          self.livePlayObj = videojs(self.$refs.liveVideoRef, {
            language: "zh-CN"
          }); // 初始化播放器
          // 播放视频
          self.livePlayObj.src({
            src: "http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8",
            type: "application/x-mpegURL",
            withCredentials: true
          });
          self.livePlayObj.load();
          // 开始播放视频
          self.livePlayObj.play();
          });
      }
    },
  },
  mounted() {
    const self = this;
    self.initLiveData();
  },
}

这样可以在页面动态插入脚本,注意的是在插入前检查下脚本函数是否已经加载,要不然重其它页面进入会一直执行插入函数。

页面布局

如果在项目在不同的视图页面使用不同的布局的话,也很简单,在/layouts文件夹下声明不同的布局文件就行了。

default.vue:不可删除也不可修改名字,作为默认布局文件。

例如我新建个download.vue布局文件,在视图文件里直接声明布局文件名字即可:

export default {
  layout: "download",
}

页面模板

如果需要大改动页面布局的话,可以在项目根目录下新建app.html文件:

<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head {{ HEAD_ATTRS }}>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
</html>

根据项目需求修改内容就行了,然后添加或者修改自己想要东西就行了。

视图页面和路由

Nuxt.js视图页面在/pages文件夹下面,会根据该目录结构生成vue-router路由配置。这边内容直接看Nuxt.js 路由文档就行了,上面说明写得还算是详细,理解没啥问题。

静态资源

项目静态目录是在/static目录下,这个目录对应的是项目地址的/,这边没什么要值得主要的,就是在页面引入资源的时候要注意项目在二级目录设定问题:

<template>
  <div class="wscn-http404-container">
    <img class="pic-404__child right" :src="`${homeUrl}images/404_images/404_cloud.png`" alt="404">
  </div>
</template>
 
<script>
export default {
  data() {
    return {
      homeUrl: this.$router.options.base
    }
  },
}
</script>
 
<style rel="stylesheet/scss" lang="scss" scoped>
</style>