nuxtjs开发(一)

Nuxt.js 是一个基于 Vue.js 的通用应用框架。
通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI 渲染。
服务端渲染(通过 SSR)功能,本文将记录nuxt开发历程。

安装

使用官方脚手架工具create-nuxt-app,创建项目pc-index

1
npx create-nuxt-app pc-index

1
yarn create nuxt-app pc-index

我选择的是: EsLint 、Axios 、 Element UI,其他都为默认选项。

配置

nuxt.config.js - serverMiddleware(服务器端渲染中间件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 方式一(官方默认) 
# https://github.com/senchalabs/connect
# https://zh.nuxtjs.org/api/configuration-servermiddleware
serverMiddleware: [
#W ill register redirect-ssl npm package
'redirect-ssl',
# Will register file from project api directory to handle /api/* requires
{ path: '/api/ready', handler: '~/api/ready.js' },
# We can create custom instances too
{ path: '/static2', handler: serveStatic(`${__dirname}/static2`) }
],

# 方式二(推荐)
# https://github.com/nuxt-community/express-template/blob/master/template/api/index.js
serverMiddleware: { '/api': '~/api' }, # 见 本文“服务中间件”
...

nuxt.config.js - head nuxt.config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dev: false, // !!! 强制设为false 防止脚本报错跳转到错误页面 !!!,生产环境
head: {
title: process.env.npm_package_name || '',
meta: [ // 配置meta属性
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
],
link: [ // 静态文件,存放在static中
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'stylesheet', type: 'text/css', href: '/public.css' },
{ rel: 'stylesheet', type: 'text/css', href: '/iconfont.css' }
]
},

nuxt.config.js - 样式变量variables.scss

1
2
3
4
5
6
7
modules: [
'@nuxtjs/style-resources'
],
styleResources: {
scss: './assets/variables.scss'
// less: './assets/**/*.less',
},

layouts/default.vue - head Vue Meta

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
head: {
htmlAttrs: {
lang: 'zh-CN',
amp: true
},
bodyAttrs: {
class: ['default-mode', 'yx-body']
},
title: 'yt优学',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Home page description default' }
]
}
}
</script>

axios - 拦截器

1
2
3
4
5
6
7
8
# nuxt.config.js
plugins: [
'~/plugins/axios'
],
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
}
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
# plugins/axios.js
const props = require('../props')

/**
* client & server
* 获取cookie
* */
function _getCookie (cookie, cname) {
const name = cname + '='
const ca = cookie.split(';')
for (let i = 0; i < ca.length; i++) {
let c = ca[i]
while (c.charAt(0) === ' ') {
c = c.substring(1)
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length)
}
}
return ''
}

/**
* client
* 设置cookie
* */
function _setCookie (cname, cvalue, exdays) {
const d = new Date()
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000))
const expires = 'expires=' + d.toGMTString()
document.cookie = cname + '=' + cvalue + '; ' + expires
}

/**
* client
* 跳转登录
* */
function _toLogin () {
const _domain = (/(dev.|test.)?yitong.com(:\d+)?/.exec(location.host) || [])[0]
if (_domain) {
location.href = '/vc3/wx/login.html?back=' + location.href
} else {
location.href = 'login.html?back=' + location.href
}
}

/**
* client
* start: IE9时response.data是undefined,因此需要使用response.request.responseText
* */
// function _ie9Response (res) {
// let data
// if (res.data === undefined) {
// data = res.request.responseText
// } else {
// data = res.data
// }
// // 判断data不是Object时,解析成Object
// if (!(data instanceof Object)) {
// data = JSON.parse(data)
// }
// res.data = data
// return res
// }

export default function ({ $axios }) {
$axios.onRequest((config) => {
if (process.client) {
// 客户端请求走https
$axios.defaults.baseURL = `https:${props.apiBase}`
} else {
// 内网请求走http
$axios.defaults.baseURL = `http:${props.apiBase}`
}
const cookieStr = (process.server ? config.headers.common.cookie : document.cookie) || ''
const token = _getCookie(cookieStr, 'token')
config.headers = Object.assign(config.headers, {
'Content-Type': 'application/json;charset=utf-8',
'X-YT-AppKey': props.appKey || '',
Authorization: 'Bearer ' + token
})
})

$axios.onResponse((res) => {
// if (process.client) {
// res = _ie9Response(res)
// }
const code = res.data.code
console.log('response------->', res.data)
if (code === 1) {
// do something
}
if (code === 100) {
console.log('请重新登录', res.data)
if (process.client) {
_setCookie('token', '', -1000)
localStorage.clear()
// _toLogin()

// 该cookie调试用:
_setCookie('token', '******', 1)
}
} else {
console.log('服务端其他错误:', res.data)
}
})

$axios.onError((error) => {
const code = parseInt(error.response && error.response.status)
switch (code) {
case 400:
// page400()
break
case 500:
error.message = '服务器内部错误'
// page500()
break

case 501:
error.message = '服务未实现'
// page500()
break

case 502:
error.message = '网关错误'
// page500()
break

case 503:
error.message = '服务不可用'
// page500()
break

case 504:
error.message = '网关超时'
// page500()
break

case 505:
error.message = 'HTTP版本不受支持'
// page500()
break

default:
// page500()
break
}
console.log('$axios onError ------> ', error)
})

$axios.onRequestError(() => {
// console.log('$axios onRequestError ------> ', error)
})

$axios.onResponseError(() => {
// console.log('$axios onResponseError ------> ', error)
})
}

服务端&客户端请求

pages/*.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
export default Vue.extend({
async asyncData ({ $axios }) {
// 服务端渲染demo:
// const { result } = await $axios.$post('/auth/v3/user/getUser', {})
// return { user: result }
},

data () {
return {
user: null,
meta: [
// hid is used as unique identifier. Do not use `vmid` for it as it will not work
{
hid: 'description',
name: 'description',
content: 'page index'
}
]
}
},

async mounted () {
// 客户端请求demo:
// const { result } = await this.$axios.$post('/auth/v3/user/getUser', {})
// window.localStorage.setItem('user/user', result ? JSON.stringify(result) : '')
}
})

服务中间件 serverMiddleware

/api/index.js

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
'use strict'
const Koa = require('koa')
const router = require('koa-router')()
const logger = require('koa-logger')
const cors = require('kcors')
const bodyParser = require('koa-bodyparser')
const api = require('./api')
// const props = require('../props.json')

const app = new Koa()
app.use(logger())
app.use(cors())
app.use(bodyParser())
app.use(async (ctx, next) => {
try {
await next()
} catch (e) {
console.log(e)
ctx.status = 200
ctx.body = api.transformError(e)
}
})
app.on('error', function (e, ctx) {
console.log(e, ctx)
})

app.use(async (ctx, next) => {
ctx.headerx = api.headerx(ctx.headers)
await next()
})

app.use(require('./demo.js').routes())

// Koa https://stackoverflow.com/questions/58282278/using-koa-as-nuxt-server-middleware
app.listen()

export default {
path: '/api',
handler: app.callback()
}

// Express版本
/**
const express = require('express')
const bodyParser = require('body-parser')

// Create express instance
const app = express()

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
// 设置跨域访问
app.all('*', function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Credentials', 'true')
res.header('Access-Control-Allow-Methods', '*')
res.header('Access-Control-Allow-Headers', 'Content-Type,Access-Token')
res.header('Access-Control-Expose-Headers', '*')
res.header('Content-Type', 'application/json;charset=utf-8')
if (req.method === 'OPTIONS') {
res.end()
} else {
next()
}
})

// Require API routes
const ai = require('./routes/ai')
const build = require('./routes/build')

// Import API Routes
app.use(ai)
app.use(build)

// Export express app
module.exports = app

// Start standalone server if directly running
if (require.main === module) {
const port = process.env.PORT || 3001
app.listen(port, () => {
console.log(`API server listening on port ${port}`)
})
}
*/

/api/api.js

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
const _ = require('lodash')
const axios = require('axios')

class ApiError extends Error {
constructor(message, code) {
super(message)
this.code = code
this.timestamp = Date.now()
}

toJSON() {
return {
timestamp: this.timestamp,
code: this.code,
message: this.message
}
}
}

const http = axios.create({
method: 'POST',
headers: {
'content-type': 'application/json;charset=utf-8'
},
timeout: 10 * 60 * 1000,
maxContentLength: 100 * 1024 * 1024
})
http.interceptors.response.use(function (response) {
if (response.data) {
if (response.data.code === 1) {
return response.data.result
}
}
return Promise.reject(response)
})

const excludes = new Set([
'host',
'connection',
'content-length',
'content-type',
'accept-encoding'
])

function headerx(headers) {
return _.pickBy(headers, (value, key) => {
return !excludes.has(key)
})
};

function ok(result, message) {
return {
timestamp: Date.now(),
code: 1,
message: message || '请求成功',
result
}
}

function error(e) {
console.log('--------------->',e)
return new ApiError(e.message || '出错了', e.code || 9)
}

function transformError(e) {
if (e.request && e.status && e.data) {
e = e.data
}
if (e.timestamp && e.code && e.message) {
return e
} else {
return error()
}
}

function validate(b, message) {
if (!b) {
throw error({
message: message || '参数错误',
code: 10
})
}
}

function equal(value, expected, message) {
if (value != expected) {
throw error({
message: message || '参数错误',
code: 10
})
}
}

function maxlength(value, expected, message) {
if (value) {
validate(value.length <= expected, message)
}
}

function gt(value, expected, message) {
validate(value > expected, message)
}

function notEmpty(value, message) {
validate(value, message)
}

module.exports = {
http,
headerx,
ok,
error,
transformError,
validator: {
gt,
notEmpty,
equal,
maxlength
}
}

/api/demo.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const router = require('koa-router')()
const api = require('./api')
router.get('/ready', (ctx) => {
// if (false) {
// ctx.body = api.error({
// code: 9,
// message: '出错啦',
// result: null
// })
// return
// }
ctx.body = api.ok({ code: 1, message: '启动成功', result: 'ok' }, '')
})
module.exports = router

参考资料

填坑日记


nuxtjs开发(一)
http://example.com/20200804-nuxtjs/
作者
csorz
发布于
2020年8月4日
许可协议