Nuxt 使用Tips

320 阅读3分钟

全局样式

// nuxt.config.js
export default {
  css: ['~/assets/stylesheets/reset.css']
}

使用less scss全局变量的配置

npm i @nuxtjs/style-resources --save
// nuxt.config.js
export default {
  styleResources: {
    scss: ['./assets/style/main.scss']
  },
}

使用Element-UI

  • plugins文件夹新建element-ui.js
// plugins/element-ui.js
// 全局引入 
import Vue from 'vue';
import ElementUI from 'element-ui';
Vue.use(ElementUI);

// 按需引入
import { Button, Loading, MessageBox } from 'element-ui';
Vue.use(Button);
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;

// nuxt.config.js
export default {
  css: [
    'element-ui/lib/theme-chalk/index.css',
    'theme/element-reset.scss'
  ],
  plugins: [
    '@/plugins/element-ui.js'
  ],
}

Vuex

nuxt自己集成了vuex,所以不需要安装,在/store目录下新建index.js即可使用

// store/index.js
export const state = () => ({
  userInfo: null,
});

export const mutations = {
  setUserInfo(state, userInfo) {
    state.userInfo = userInfo;
  }
};

// 使用 pages/home.vue
this.$store.commit('setUserInfo', userInfo);

插件 plugins

  • 在/plugins/目录下创建js文件

axios 使用

使用@nuxtjs/axios、@nuxtjs/proxy

export default {
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/proxy'
  ],
  proxy: {
    '/api/': {
      target: process.env.BASE_URL,
      pathRewrite: {
        '^/api': ''
      }
    },
  },
}

全局处理axios

www.axios-js.com/zh-cn/docs/…

  • 在plugins下新建axios.js文件
import { getAccessToken } from './utils';
import { Message } from 'element-ui';
import qs from 'qs';

export default function({ $axios, next }) {
  // 401错误,返回登陆页
  const handle401 = (message) => {
    Message({
      message: message || '请登录后查看',
      type: 'error'
    });
    window.localStorage.removeItem('user');
    next('/login?path=' + encodeURIComponent(window.location.href));
  };

  // 是否显示错误信息
  let isShowMessage = true;
  // 是否需要token
  let hasToken = true;
  $axios.onRequest(config => {
    isShowMessage = config.isShowMessage === undefined ? true : config.isShowMessage;
    hasToken = config.hasToken === undefined ? true : config.hasToken;
    config.headers['authorization'] = hasToken ? getAccessToken() : '';
    if (config.method === 'get') {
      if (!config.params) {
        config.params = {};
      }
      config.params.t = new Date().getTime();
    }
    config.paramsSerializer = (params) => {
      return qs.stringify(params, { arrayFormat: 'repeat' });
    };
    return config;
  }, error => {
    return Promise.reject(error);
  });

  $axios.onResponse(resp => {
    const res = resp.data;
    if (res.code !== 200 && res.code < 1000) {
      if (res.code === 401) {
        handle401();
        return;
      } 
      if (isShowMessage) {
        Message({
          message: res.msg,
          type: 'error'
        });
        return;
      } 
      return Promise.reject(res.msg);
    } 
    return resp;
    
  });
  
  $axios.onError(error => {
    const err = error.response?.data;
    Message({
      message: err?.msg || err?.code || '请求失败',
      type: 'error'
    });
    return Promise.reject(error);
  });

}
  • 在nuxt.config.js注入
export default {
  plugins: [
    '@/plugins/axios'
  ]
}
  • 直接使用axios
this.$axios.$get('/api/xxx', { params: { id } })
.then(res => {
  if (res.code == 200) {
        
  }
});
  • 使用asyncData多次请求
//很少直接使用“响应对象”
export default {
    async asyncData( {$axios} ) {
        //发送多次请求
        let [响应1,响应2] = await Promise.all([请求1, 请求2])
        //返回数据
        return {
            变量1: 响应1,
            变量2: 响应2
        }
    }
}
  • 单独封装
// 1. 新建 /api/index.js
const modulesFiles = require.context('./apiModules', true, /\.js$/);
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1');
  const value = modulesFiles(modulePath);
  modules[moduleName] = value.default || value;
  return modules;
}, {});

export default modules;

// 2. 新建 /api/apiModules/user.js
export default axios => ({
  postApi(data) {
    return axios.post('/api/xxx', data);
  },
  getApi(params) {
    return axios.get('/api/xxx', { params });
  },
});

// 3. 新建 /plugins/api-plugin.js 注入全局$api
import apis from '@/api/index';
export default (ctx, inject) => {
  let apiObject = {};
  for (let i in apis) {
    apiObject[i] = apis[i](ctx.$axios);
  }
  inject('api', apiObject);
};

// 4. nuxt.config.js引用插件
export default {
  plugins: [
    '@/plugins/api-plugin'
  ]
}

// 5. page里使用
this.$api.user.getApi(data).then(res => {
  if (res.data.code === 200) {

  }
});

布局 layout

Header、Footer等公共组件放哪?

nuxt默认布局放在layout/default.vue

// /项目名/layouts/default.vue
<template>
  <div class="">
    <HeaderVue />
    <nuxt class="" />
  </div>
</template>

<script>
import HeaderVue from '@/components/common/Header.vue';
export default {
  components: {
    HeaderVue
  },
};
</script>

自定义布局

// layouts/BaseLayout.vue
<template>
  <div>
    <Header></Header>
    <Nuxt></Nuxt>
  </div>
</template>
<script>
import Header from '../components/Header.vue';
export default {
  components: { Header }
};
// pages/home/index.vue
export default {
  layout: 'BaseLayout',
}

配置错误页面

layouts/error.vue 文件来定制化错误页面

中间件 middleware

中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前,中间件接收 context 作为第一个参数,可以直接解构使用。

例如:页面需要权限验证:判断登录状态

  • middleware目录下创建js文件
// auth.js
import { getAccessToken } from '../plugins/utils';
import { Message } from 'element-ui';
export default function({ store, route, redirect, params, query, req, res }) {
  const token = getAccessToken();
  if (!token) {
    Message({
      type: 'error',
      message: '您还没有登录,请先登录!'
    });
    redirect('/login?path=' + encodeURIComponent(window.location.href));
  }
}
  • 页面中引用中间件
// detail.vue
export default {
  middleware: ['auth'],
}
  • nuxt.config.js中引用中间件
router: {
    middleware: 'auth',
  }

生命周期

Nuxt扩展的asyncData, fetch, validate, middleware会在前后端内都执行 nuxtServerInit, serverMiddleware会在服务端执行 暴露给我们的redirect,error等方法前后端都可以使用, 但是诸如req,res之类的,前端则为undefined

image.png

生命周期流程图,红框内的是Nuxt的生命周期(运行在服务端),黄框内同时运行在服务端&&客户端上,绿框内则运行在客户端

配置cross-env

  • package.json 配置变量键值
  "scripts": {
    "dev": "cross-env NODE_ENV=development nuxt",
    "build:test": "cross-env NODE_ENV=development nuxt build",
    "build:prod": "cross-env NODE_ENV=production nuxt build",
  }
  • 新建env文件
module.exports = {
  development: {
    NODE_ENV: 'development',
    BASE_URL: 'https://xxx-test.com/api' // 测试服务器地址
  },
  production: {
    NODE_ENV: 'production',
    BASE_URL: 'https://xxx/api' // 正式服务器地址
  }
};
  • 在nuxt.config.js使用变量
const env = require('./config/env');
export default {
  env: {
    NODE_ENV: process.env.NODE_ENV || 'development',
    BASE_URL: env[process.env.NODE_ENV].BASE_URL || 'https://xxx.com/'
  },
  proxy: {
    '/api/': {
      target: process.env.BASE_URL || 'https://xxx-test.com/api',
      pathRewrite: {
        '^/api': ''
      }
    },
  },
}

配置端口号

// nuxt.config.js
export default {
  server: {
    port: 80, // default: 3000
    host: '0.0.0.0' // default: localhost
  }
}

自定义Loading页面

//nuxt.config.js
export default {
  loading: '~components/loading.vue'
}

配置网页tab栏ico图标 配置Meta标签

  • 默认标签
// nuxt.config.js
export default {
  head: {
    meta: [
      { charset: 'utf-8' },
      { title: 'xxx' },
      { 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' },// 读取在static文件下的ico图标
    ],
    script: [
    ]
  },
 }
  • 页面组件特有的Meta标签
// pages/home.vue
export default {
  head () {
    return {
      meta: [ { name: 'keywords', content: 'xxx' }]
    }
  }
}
  • 动态路由的Meta标签
export default {
  async asyncData ({ app, params }) {
    let data = await app.$axios.get(`/appinfo/${params.id}`);
    return { appname: data.appinfo.appname }
  },
  head () {
   return {
     meta: [ { name: 'keywords', content: `${this.appname},xxx` }]
   }
  }
}

打包部署

运行打包命令npm run build,会生成.nuxt文件夹 将.nuxt,package.json,nuxt.config.js,static,server(可能有),这几个文件上传到服务器,后端需要时也可添加node_modules。

后端执行下npm install, 然后在执行npm run dev

开启安全组接口,才能访问

为了管理node服务,安装pm2 

pm2 start node