一、安装Nuxt
- 先安装Node、Npm(至少5.2.0版本),确保安装了npx(npx在NPM版本5.2.0默认安装了)
- 进入命令行,输入
npx create-nuxt-app <项目名>
,比如npx create-nuxt-app nuxt
-
相关选项
image.png
-
依赖安装完成
image.png
- 安装
scss
:cnpm i -D node-sass sass-loader
二、相关配置
0、配置端口
在package.json
里面新增
"config": {
"nuxt": {
"host": "0.0.0.0",
"port": "8012"
}
}
1、.eslintrc.js
默认:
module.exports = {
root: true,
env: {
browser: true,
node: true
},
parserOptions: {
parser: 'babel-eslint'
},
extends: ['@nuxtjs', 'plugin:nuxt/recommended'],
// add your custom rules here
rules: {}
}
只在rules
新增以下代码:
rules: {
'vue/max-attributes-per-line': [
2,
{
singleline: 10,
multiline: {
max: 1,
allowFirstLine: false
}
}
],
'vue/name-property-casing': ['error', 'PascalCase'],
'vue/script-indent': ['error', 4, { baseIndent: 1 }],
'vue/html-indent': ['error', 4, { baseIndent: 1 }],
'vue/singleline-html-element-content-newline': 0,
'accessor-pairs': 2,
'arrow-parens': 'off',
'arrow-spacing': [
2,
{
before: true,
after: true
}
],
'block-spacing': [2, 'always'],
'brace-style': [
2,
'1tbs',
{
allowSingleLine: true
}
],
camelcase: [
0,
{
properties: 'always'
}
],
'comma-dangle': [2, 'never'],
'comma-spacing': [
2,
{
before: false,
after: true
}
],
'comma-style': [2, 'last'],
'constructor-super': 2,
curly: [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
eqeqeq: [2, 'allow-null'],
'generator-star-spacing': [
2,
{
before: true,
after: true
}
],
'handle-callback-err': [2, '^(err|error)$'],
indent: [
0,
4,
{
SwitchCase: 1
}
],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [
2,
{
beforeColon: false,
afterColon: true
}
],
'keyword-spacing': [
2,
{
before: true,
after: true
}
],
'new-cap': [
2,
{
newIsCap: true,
capIsNew: false
}
],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 2,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [
2,
{
allowLoop: false,
allowSwitch: false
}
],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [
2,
{
max: 1
}
],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [
2,
{
defaultAssignment: false
}
],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [
2,
{
vars: 'all',
args: 'none'
}
],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [
2,
{
initialized: 'never'
}
],
'operator-linebreak': [
2,
'after',
{
overrides: {
'?': 'before',
':': 'before'
}
}
],
'padded-blocks': [2, 'never'],
quotes: [
2,
'single',
{
avoidEscape: true,
allowTemplateLiterals: true
}
],
semi: [2, 'never'],
'semi-spacing': [
2,
{
before: false,
after: true
}
],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [
2,
{
words: true,
nonwords: false
}
],
'spaced-comment': [
2,
'always',
{
markers: [
'global',
'globals',
'eslint',
'eslint-disable',
'*package',
'!',
','
]
}
],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
yoda: [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [
0,
'always',
{
objectsInObjects: false
}
],
'array-bracket-spacing': [2, 'never']
}
2、nuxt.config.js
(1) 引入外部JS
在head
里面新增script
对象,如引入:echarts.js
、clipboard.js
head:{
script: [
{
src:
'https://cdn.staticfile.org/echarts/4.2.1-rc1/echarts.min.js'
},
{
src:
'https://cdn.staticfile.org/clipboard.js/2.0.4/clipboard.min.js'
}
]
}
(2) 路由配置
- 新增
router
对象
router: {
// 路由中间件,存放在 `@/middleware/stats.js`
middleware: 'stats',
extendRoutes(routes) {
// 捕获未知路由,然后统一跳转到根路由
routes.push({
path: '*',
redirect: '/'
})
},
scrollBehavior() {
// 路由跳转,滚动条置顶
return { x: 0, y: 0 }
}
}
-
middleware/stats.js
说明
在中间件中访问
window
会报错,因为window
是客户端的,中间件将会在服务端、客户端都运行,因此window
是不一定存在的!
export default function(context) {
// 获取userAgent
context.userAgent = process.server
? context.req.headers['user-agent']
: navigator.userAgent
const ua = context.userAgent
// 是否为手机端
const mobile = /(Android|webOS|iPhone|iPad|iPod|tablet|BlackBerry|Mobile)/i.test(
ua
)
// 是否为微信浏览器
const wx = /MicroMessenger/i.test(ua)
// 将值赋给context
context.isMobile = mobile
context.isWx = wx
console.log('中间件,mobile:', mobile)
console.log('中间件,wx:', wx)
if (mobile) {
// 当为手机端,do something
// redirect:跳转到外部
context.redirect(302, 'https://m.baidu.com')
// $router.push:内部跳转
context.$router.push('/home')
}
}
(3) 全局SCSS引入
- 安装依赖
cnpm i -S @nuxtjs/style-resources
,cnpm i -D node-sass sass-loader
- 在
modules
下新增@nuxtjs/style-resources
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'@nuxtjs/eslint-module',
// 新增的 @nuxtjs/style-resources
'@nuxtjs/style-resources'
]
- 在
assets
下新建style
文件夹,然后再新增2个.scss
文件:app.scss
、flex.scss
- 在
nuxt.config.js
里新增styleResources
对象
styleResources: {
// @ 代表根目录,该值为数组,因此可以继续新增你的 .scss 文件
scss: ['@/assets/style/app.scss', '@/assets/style/flex.scss']
}
(4) 打包配置
- 打包相关的配置是写在
build
对象中的,在原有的配置上新增以下
build:{
// 提取css
extractCSS: true
}
(5) 环境变量
- 安装依赖
cnpm i -D cross-env
- 在
package.json
中的scripts
中新增
"scripts": {
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
"precommit": "npm run lint",
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
// 以下是新增的
"devprod": "cross-env PATH_TYPE=prod nuxt", // 以正式环境本地运行
"buildprod": "cross-env PATH_TYPE=prod nuxt build", // 以正式环境打包
"devprod2": "cross-env PATH_TYPE=prod PATH_HOST=2 nuxt", // 以正式环境、域名为2本地运行
"buildprod2": "cross-env PATH_TYPE=prod PATH_HOST=2 nuxt build" // 以正式环境、域名为2打包
}
- 在
nuxt.config.js
新增env
对象
env: {
// 环境变量:test、prod
PATH_TYPE: process.env.PATH_TYPE || 'test',
// 域名变量:1、2、3.....
PATH_HOST: process.env.PATH_HOST || '1'
}
(6) 配置完成的 nuxt.config.js
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: process.env.npm_package_name || '',
meta: [
{ charset: 'utf-8' },
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: process.env.npm_package_description || ''
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
script: [
{
src:
'https://cdn.staticfile.org/echarts/4.2.1-rc1/echarts.min.js'
},
{
src:
'https://cdn.staticfile.org/clipboard.js/2.0.4/clipboard.min.js'
}
]
},
/*
** router
*/
router: {
// 路由中间件
middleware: 'stats',
extendRoutes(routes) {
// 捕获未知路由,然后统一跳转到根路由
routes.push({
path: '*',
redirect: '/'
})
},
scrollBehavior() {
// 路由跳转,滚动条置顶
return { x: 0, y: 0 }
}
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: ['element-ui/lib/theme-chalk/index.css'],
/*
** Plugins to load before mounting the App
*/
plugins: ['@/plugins/element-ui'],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'@nuxtjs/eslint-module',
// 新增的 @nuxtjs/style-resources
'@nuxtjs/style-resources'
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {},
/*
** styleResources
*/
styleResources: {
scss: ['@/assets/style/app.scss']
},
/*
** Build configuration
*/
build: {
// 提取css
extractCSS: true,
transpile: [/^element-ui/],
/*
** You can extend webpack config here
*/
extend(config, ctx) {}
},
/*
** env
*/
env: {
PATH_TYPE: process.env.PATH_TYPE || 'test',
PATH_HOST: process.env.PATH_HOST || '1'
}
}
3、插件处理 plugins
(1) plugins/element-ui.js
原始的:
import Vue from './node_modules/vue'
import Element from './node_modules/element-ui'
import locale from './node_modules/element-ui/lib/locale/lang/en'
Vue.use(Element, { locale })
修改后:
import Vue from 'vue'
import Element from 'element-ui'
// 覆盖默认样式的,(可选)
import '@/assets/style/element-variables.scss'
Vue.use(Element)
assets/style/element-variables.scss
文件说明
/* 改变主题色变量 */
$--color-primary: #c22b46;
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import '~element-ui/packages/theme-chalk/src/index';
(2) 全局组件注册
- 先在
plugins
下新建global-components.js
import Vue from 'vue'
// 找到components文件夹下以.vue命名的文件
const RC = require.context('@/components/', true, /\.vue$/)
RC.keys().forEach(fileName => {
const componentConfig = RC(fileName)
// 因为得到的filename格式是: './baseButton.vue', 所以这里我们去掉头和尾,只保留真正的文件名
let componentName = fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')
const index = componentName.indexOf('/')
if (index !== -1) componentName = componentName.substr(index + 1)
Vue.component(componentName, componentConfig.default || componentConfig)
})
- 在
nuxt.config.js
中plugins
对象里新增
plugins: [
'@/plugins/element-ui',
// 以下统统为新增的
'@/plugins/global-components'
]
(3) 全局Vue方法、变量
- 先在
plugins
下新建combined-inject.js
import Vue from 'vue'
// 引入接口地址,这只是我个人习惯而已,可选操作
import apiUrl from '@/apiurl'
// ================全局 Vue变量================
// 全局api对象
Vue.prototype.$api = {}
// 全局user对象
Vue.prototype.$user = { name: 'hzq', age: 25 }
// ================全局 Vue方法================
// 深拷贝对象方法
Vue.prototype.$copy = obj => JSON.parse(JSON.stringify(obj))
// 是否为测试环境
const isTest = process.env.PATH_TYPE === 'test'
const Host = process.env.PATH_HOST === '1' ? '***.com' : '***.cn'
// 手机接口域名
const mbHost = isTest ? `http://m.${Host}.com` : `https://m.${Host}.cn`
// 该数组用于装自定义的 Vue.prototype 变量,要去掉 $
const arr = ['api', 'user', 'copy']
export default (c, inject) => {
// 接口访问地址
const apiurl = mbHost.replace('//m.', '//www.') + '/api'
// 封装处理axios
apiUrl.map(a => {
const methods = a.methods || 'post'
Vue.prototype.$api[a.name] = (params = {}, headers = {}) => {
if (methods === 'get') params = { params }
return c.$axios[methods](apiurl + a.url, params, {
headers
})
}
})
// 通过循环arr,可以同时注入在context,Vue实例中,并且系统会自动将$添加到方法名的前面
// 然后就可以这样访问:context.app.$user、this.$user
arr.map(val => inject(val, Vue.prototype['$' + val]))
}
- 在
nuxt.config.js
中plugins
对象里新增
plugins: [
'@/plugins/element-ui',
// 以下统统为新增的
'@/plugins/global-components',
'@/plugins/combined-inject'
]
4、公用CSS处理
可以在@/layouts/default.vue
里面的style
添加
default.vue
<template>
<div>
<nuxt />
</div>
</template>
<style>
/* 公共CSS */
* {
margin: 0;
padding: 0;
font-family: 'Microsoft YaHei', '微软雅黑', 'PingFang SC', Arial;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
body {
background-color: #fff;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: normal;
}
ul,
ol {
list-style: none;
}
a {
text-decoration: none;
}
a:link,
a:visited,
a:hover,
a:active {
color: #000;
}
</style>
5、添加Vuex
- 在
@/store
下新建index.js
export const state = () => ({
state1: 'state1',
user: { hzq: { age: 25 } }
})
export const mutations = {
// 这是一个万能的mutations,它支持vuex的数据流,能实时变化
// 使用方法如下
// 拿上面两个state举例:
// 改变state1:this.$store.commit('save',['state1','newState1'])
// 改变age:this.$store.commit('save',['user.hzq.age',24])
save(state, [key, data]) {
if (!key) throw new Error('mutations save need key!')
const keyPath = key.split('.')
const len = keyPath.length
const lastKey = keyPath.pop()
let needSave = state
for (let i = 0; i < len - 1; i++) {
needSave = needSave[keyPath[i]]
}
if (!needSave.hasOwnProperty(lastKey)) {
throw new Error(`【${key}】 Error Key: ${lastKey}`)
}
needSave[lastKey] = data
}
}
-
nuxt
会自动读取文件,然后生成vuex
,就可以在vue
里面this.$store.state.user.hzq.age
等调用
6、添加百度统计
1.在@/plugins
下新建baidu.js
export default ({ app: { router }, store }) => {
/* 每次路由变更时进行pv统计 */
router.afterEach((to, from) => {
try {
/* 告诉增加一个PV */
window._hmt = window._hmt || []
window._hmt.push(['_trackPageview', to.fullPath])
} catch (e) {}
})
}
- 在
nuxt.config.js
里的script
新增
script: [
***,
{
src: 'https://hm.baidu.com/hm.js?*******' // 百度统计js
}
]
- 在
nuxt.config.js
里的plugins
新增
plugins: [
'@/plugins/element-ui',
// =============以下统统为新增的=============
'@/plugins/global-components', // 全局Vue组件注册
'@/plugins/combined-inject', // 全局Vue变量
'@/plugins/baidu' // 百度统计
]
7、添加百度自动推送
1.在@/plugins
下新建baidu_js_push.js
export default ({ app: { router } }) => {
router.afterEach(() => {
try {
const bp = document.createElement('script')
bp.setAttribute('id', 'baidu_js_push')
const curProtocol = window.location.protocol.split(':')[0]
if (curProtocol === 'https') {
bp.src = 'https://*****/push.js'
} else {
bp.src = 'http://*****/push.js'
}
const scripts = document.getElementsByTagName('script')
const links = document.getElementsByTagName('link')
const s = links[0]
const curr = [...scripts].find(s => s.id === 'baidu_js_push')
if (curr) s.parentNode.removeChild(curr)
s.parentNode.insertBefore(bp, s)
} catch (e) {}
})
}
- 在
nuxt.config.js
里的plugins
新增
plugins: [
'@/plugins/element-ui',
// =============以下统统为新增的=============
'@/plugins/global-components', // 全局Vue组件注册
'@/plugins/combined-inject', // 全局Vue变量
'@/plugins/baidu', // 百度统计
'@/plugins/baidu_js_push', // 百度自动推送
]
8、添加360自动推送
1.在@/plugins
下新建360_js_push.js
export default ({ app: { router } }) => {
router.afterEach(() => {
try {
const isHttp = document.location.protocol === 'http:'
const scripts = document.getElementsByTagName('script')
const links = document.getElementsByTagName('link')
const s = links[0]
const newwrite = str => {
const qhres = document.createElement('script')
qhres.setAttribute('id', 'qhres')
qhres.setAttribute('charset', 'UTF-8')
qhres.src = str.split('src="')[1].split('"></script>')[0]
const curr = [...scripts].find(s => s.id === 'qhres')
if (curr) s.parentNode.removeChild(curr)
s.parentNode.insertBefore(qhres, s)
}
document.write = newwrite
const passport = document.createElement('script')
passport.setAttribute('id', 'sozz')
passport.setAttribute('charset', 'UTF-8')
passport.src = isHttp
? 'http://****.com/11.0.1.js?****'
: 'https://****.com/11.0.1.js?****'
const curr = [...scripts].find(s => s.id === 'sozz')
if (curr) s.parentNode.removeChild(curr)
s.parentNode.insertBefore(passport, s)
} catch (e) {}
})
}
- 在
nuxt.config.js
里的plugins
新增
plugins: [
'@/plugins/element-ui',
// =============以下统统为新增的=============
'@/plugins/global-components', // 全局Vue组件注册
'@/plugins/combined-inject', // 全局Vue变量
'@/plugins/baidu', // 百度统计
'@/plugins/baidu_js_push', // 百度自动推送
'@/plugins/360_js_push' // 360自动推送
]
三、一些坑点
1、关于Window对象
-
nuxt
的生命周期:nuxt生命周期
1、红色框:服务端运行
2、黄色框:服务端&&客户端 都运行
3、绿色框:客户端运行
在 1 中都是不存在window
,使用会报错
在 2 中可能存在,强行要用时,请用process.client
判断
created(){
if (process.client) {
window.title = "my nuxt template"
}
}
在 3 中存在window
2、组件中无法使用 asyncData、fetch 等
asyncData、fetch
等只能在页面级别的.vue
使用,即放在@/pages
里面的.vue
;无法在组件级别的.vue
使用,即放在@/components
里面的.vue
因此想我们的组件一开始就渲染好的话,可以先在上一个页面或其他页面先获取数据,然后放在vuex
里面,这样拿到的组件就是渲染完的。
3、关于asyncData
asyncData
方法会在组件(限于页面组件)每次加载之前被调用
在asyncData
中一些常用的东西(个人理解的)
async asyncData(c) {
// Vue全局变量
c.app.$api.Login()
c.app.$user.hzq.age
// Vue 路由 c.route 等价于 this.$route
c.route.path
// 页面跳转 c.redirect 等价于 this.$router.push
c.redirect('/home')
// Vuex c.store 等价于 this.$store
c.store.commit()
c.store.state.user
}
四、完结
- 这是根据以上配置完成的
nuxt
项目模板:template_nuxt_element - 这是我实际的
nuxt
项目:公司官网,官网项目所使用的技术都在这篇文章说明了,应该能满足nuxt
项目需求,若有不足希望大家指出。
参考文档:
nuxt官网
Nuxt开发经验分享,让你踩少点坑!
网友评论