Cap1 前言
目前使用Nuxt.js
框架做了几个项目,不得不说对比与Vue-CLI
,Nuxt.js
实在是懒人必备框架,什么东西都帮你配置好了。所以多少对它有点了解,所以有了这个笔记。希望未来再做类似的地方有个能够参考的笔记。
Cap2 目录结构
/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
:放置静态资源目录,可以启动后直接访问资源。/store
:Vuex
状态树管理配置文件。nuxt.config.js
:用于Nuxt.js
配置,覆盖默认设置。
除了上面默认的文件夹,我们可以自己额外添加文件:
/api
:存放项目接口文件。/utils
:项目工具类。/test
:单元测试类。
Cap3 单应用和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)?
主要博主做的项目大部分都是后端界面,所以对此也不是太熟,故这里就不再描述了。
Cap4 样式预处理器
配置样式预处理器也很简单,Nuxt.js
有自己封装的包:Nuxt Style Resources,支持sass
、less
、stylus
。现在项目用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。
Cap5 接口管理
如果项目默认安装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
组件里使用。
Cap6 中间件的使用
其实中间件这个说法不是太复杂,例如有个功能用的到,就是路由鉴权,就是在用户登录和游客的时候哪些页面能进或者不能进的。直接上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 中间件
Cap7 使用插件
Vue组件:
一般来说使用Vue.js
插件偏多,例如使用element-ui
和Vue-BootStrap
插件,分别新建element-ui.js
和bootstrap.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();
},
}
这样可以在页面动态插入脚本,注意的是在插入前检查下脚本函数是否已经加载,要不然重其它页面进入会一直执行插入函数。
Cap8 页面布局
如果在项目在不同的视图页面使用不同的布局的话,也很简单,在/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>
根据项目需求修改内容就行了,然后添加或者修改自己想要东西就行了。
Cap9 视图页面和路由
Nuxt.js
视图页面在/pages
文件夹下面,会根据该目录结构生成vue-router
路由配置。这边内容直接看Nuxt.js 路由文档就行了,上面说明写得还算是详细,理解没啥问题。
Cap10 静态资源
项目静态目录是在/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>