公司某个nuxt项目的经验总结

最近花了四个月完成了一个nuxt项目的开发,组件库为iview。现在对项目中累积的经验做一个总结和记录。

1.开工前准备

本项目功能众多且复杂,需要先理顺整个业务逻辑。
先花了一整天的时间画出了思维导图,确定开发模块的顺序。
然后找出需要做的共用组件,用工厂模式开发组件。

2.nuxt的配置

已经不是第一次用nuxt了,现在对于nuxt的配置已经很娴熟。现把本次项目用到的配置和含义记录如下:

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
const isProdMode = Object.is(process.env.NODE_ENV, 'production');
export default {
mode: 'universal', //默认是universal,如果不需要服务端渲染则设置spa,但不需要服务端渲染又何必用nuxt,所以这一项配置基本不用关心
head: {
title: '', //网站标签页的标题
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'keywords', name: 'keywords', content: '' },
{ hid: 'description', name: 'description', content: '' }
], //常用的meta设置,方便SEO
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } //favicon放置在static文件夹里
],
script: [
{ src: '' }, //引入需要的脚本文件,如百度统计文件
]
},
loading: { color: '#fff' }, //每个页面加载的进度条的颜色
css: [
{ src:'@/static/css/reset.scss', lang:'scss'}, //公用初始化设置css文件
],
plugins: [
{ src: '~/plugins/iview', ssr: true }, //引入的plugin,如组件库,iconfont,axios等,ssr为true的时候表示服务端渲染,建议涉及到window和document的部分ssr设置为false
],
modules: ["@nuxtjs/axios", "@nuxtjs/proxy"], //需要引入的模块,前者是网络请求axios必须,后者为开发模式下代理请求的必须
axios: { prefix: '/api/', proxy: true }, //请求的前缀
proxy: [
[
"/api/",
{
target: "http://xxx", // 后端服务地址
changeOrigin: true,
pathRewrite: { "^/api/": "/" }
}
]
],
dev: isProdMode,
env:{
BASE_API: '/api' //请求前缀,可根据开发环境和生产环境的不同做不同的设置
},
build: {
extend(config, ctx) {
config.resolve.extensions.push('.less') //对less类型的文件做处理
}
}
}

3.iview的定制主题

很多组件库的UI并不符合我们的设计稿,所以需要对iview的样式做全局修改。
iview的github地址可以看到iview的全局样式设置,可以看到是less格式的,所以项目必须支持less的解析。
使用npm install less-loader安装less-loader,如果解析失败,可能是less版本问题导致,详细参考本篇文章
在plugins下建立ui文件夹,目录如下:

1
2
3
ui
├── index.js # 入口文件
├── theme.less # 自定义覆盖变量

然后在nuxt.config.js文件的plugins里引入ui。

4.如何在nuxt项目中添加iconfont

本质上和正常的项目添加一样,主要是需要把css文件里的路径调整一致。
详情可以参考Nuxt使用iconfont矢量图标

5.scss做计算的方法

假定定义变量$a = ‘20px’,若要使用calc,直接calc(100% - a)这么用是不行的,需要按如下使用方法:  calc(100% - #{a})

6.做两个互相关联的datepicker

iview本身的datepicker如果要选择日期范围(即type为daterange),但是设计图稿要求用两个datepicker框来表示,这时候就需要用到data里绑定的属性值来做计算。
因为要用到this,所以disabledDate要用箭头函数表示。

1
2
3
4
<DatePicker type="date" v-model="startDate" :options="startOption" format="yyyy-MM-dd" style="width: 160px"></DatePicker>
<span style="margin:0 3px"></span>
<DatePicker type="date" v-model="endDate" :options="endOption" format="yyyy-MM-dd" style="width: 160px"></DatePicker>
</div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default {
    data(){
        return{
startDate:'',
endDate:'',
startOption:{
disabledDate: date => {
return date && date.valueOf() > Date.now() || date.valueOf()>this.endDate;
}
},
endOption:{
disabledDate: date => {
return date && date.valueOf() > Date.now() || date.valueOf()<this.startDate;
}
}
}
}
}

7.封装axios的注意事项

大部分请求都需要token,但对于不涉及用户权限和登录的接口是无需token的,那么在封装axios的时候就需要区别对待。
这次采取的方法是创建白名单,在白名单里的url就不从Cookie里拿token。

刷新token如何做?
比如token的有效期是1小时,用户有长达50分钟的时间内没有进行任何操作,此时用户进行刷新页面等操作需要刷新token来重新计算session时间。
后端接口会返回code为501,告知我需要刷新token进行更新。
对于刷新接口的操作采用“防抖”机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 是否正在刷新的标记
let isRefreshing = false;
if(response.data.code=="501"){
if (!isRefreshing) {
isRefreshing = true
return refreshToken().then(res => {
if (res.code === 200) {
Cookies.set('token', res.data.token);
}
}).finally(() => {
isRefreshing = false
})
}
}

8.组件全局注册

抽象出的组件可能会在许多其他组件里使用,每次使用都引入注册很麻烦,索性全局注册。
全局注册的文件放在plugins目录下,需要特别注意的是在nuxt.config.js里引入的时候ssr必须为true,否则服务端渲染和客户端渲染不一致 会导致组件的生命周期被执行两次。

9.组件网络请求放在mounted里

网络请求应该出现在mounted的生命周期里, 在created中的话, 因为还没有渲染出来, 会导致一个非常奇怪的ERROR connect ECONNREFUSED 127.0.0.1:80 的网络错误。
开发模式下没什么问题, 但是一旦打包 就会有很大的错误。

10.防止document和window对象的报错

防止报document or window is not defined的方法是做一个if(process.client)的判断:

1
2
3
if(process.client){
//写涉及到document和window相关的代码
}

Snapline wechat
扫码关注我的公众号“约翰柠檬的唱片店”
Buy me a cup of Coffee