jackieli123723 / jackieli123723.github.io

✅lilidong 个人博客
9 stars 0 forks source link

vue代理和请求统一处理 #26

Open jackieli123723 opened 6 years ago

jackieli123723 commented 6 years ago

记录一些使用 vue-cli 脚手架创建项目时,遇到的一些问题以及解决方案

设置代理与跨域

开发时设置

如果你的后端 API 服务是 Express 提供或者是 Thinkjs 再或者是 koa2 等等,当你请求数据时就会面临着跨域请求问题

执行 npm run dev,你会发现会报一个错误:vue-resource.common.js?e289:1071 POST http://localhost:8080/api/use... 404 (Not Found)。这是由于直接访问 8080 端口,是访问不到的,所以这里需要设置一下代理转发映射.

项目根目录下的 config 文件夹中有一个 proxyTable 参数,用来设置地址映射表,可以添加到开发时配置(dev)中

|-- config
  |-- dev.env.js
  |-- index.js
  |-- prod.env.js

config/index.js

dev: {
  // ...
  proxyTable: {
    '/api': {
      target: 'http://127.0.0.1:3000/api/',
      changeOrigin: true,
      pathRewrite: {
          '^/api': ''
      }
    }
  },
  // ...
}

添加以上代码之后,请求 /api 时就代表 http://127.0.0.1:3000/api/(这里要写 ip,不要写 localhost), changeOrigin 参数接收一个布尔值,如果为 true,这样就不会有跨域问题了。

更多接口参数配置,请参考 https://github.com/chimurai/http-proxy-middleware#options

webpack 接口配置文档 https://webpack.js.org/configuration/dev-server/#devserver-proxy

正式上线时设置

|-- src
  |-- axios
    |-- index.js
  |-- config.js

正式上线时,不推荐使用上一个方案,这里推荐使用 axios 进行转发

src/config.js

export default {
  serverUrl: 'http://127.0.0.1:3000/'
}

src/axios/index.js

import axios from 'axios'
import config from '@/config'

// 设置全局 axio s默认值
axios.defaults.baseURL = config.serverUrl
axios.defaults.timeout = 5000 // 5000的超时验证
axios.defaults.headers.post['Content-Type'] = 'application/jsoncharset=UTF-8'

// 创建一个 axios 实例
const instance = axios.create()
instance.defaults.headers.post['Content-Type'] = 'application/jsoncharset=UTF-8'

axios.interceptors.request.use = instance.interceptors.request.use

export async function postDate (username, password) {
  try {
    const response = await fetch.post('/postDate', {
      username,
      password
    })
    return response.data
  } catch (err) {
    console.log('message', err)
    if (err.response) {
      throw Error(err.response.data.message)
    }
    throw err
  }
}

父子组件

|-- src
  |-- components
    |-- HerderBar.vue
    |-- FooterBar.vue
  |-- pages
    |-- Home.vue

假如你的 components 目录下有 HerderBar.vue 和 FooterBar.vue 这两个子组件,而 Home.vue 要引用这两组件,那么下面这种写法可以完成该需求

src/pages/Home.vue

<template>
<div>
  <header-bar></header-bar>
  <!-- ... ... -->
  <footer-bar></footer-bar>
</div>
</template>

<script>
import HeaderBar from '@/components/HeaderBar'
import FooterBar from '@/components/FooterBar'

export default {
  name: 'Home',
  components: {
    HeaderBar,
    FooterBar
  }
}
</script>

图标库

市面上用的比较广泛的图标库有两个,一是阿里巴巴矢量图标库,其有上百万图标共程序员选择,自定义比较强;二是Font Awesome,该图标库虽没有上百万图标,但也受到大部门程序员喜爱。

很多人在写 Vue 项目时,前端 UI 框架都喜欢使用 Element UI,但是该 UI 框架默认提供的图标库实现是少之又少,但是该 UI 框架允许我们引入第三方图标库

iconfont

这个引入就非常简单了,在 iconfont 网站上有提供离线版和在线版,看自己的意愿,然后在 index.html 里使用 style 标签引入即可。

fontawesome

参考代码element-font-awesome

使用 less 时,别忘了安装 npm 依赖

npm install -S less less-loader

目录结构

|-- src
  |-- font.less
  |-- main.js

src/main.js

import './font.less'

src/font.less

[class^="el-icon-fa"], [class*=" el-icon-fa"] {
  display: inline-block;
  font: normal normal normal 14px/1 FontAwesome!important;
  font-size: inherit;
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
};

@import url("../node_modules/font-awesome/less/font-awesome");
@fa-css-prefix: el-icon-fa;

sass

对于 css 的预编译器,个人比较喜欢 sass 的,在使用 sass 时仍然需要添加 npm 依赖

npm install --save node-sass sass-lodaer

目录结构

|-- src
  |-- assets
    |-- scss
      |-- _public.scss
      |-- index.scss
  |-- App.vue

src/App.vue

<style lang="scss">
@import './assets/scss/index';
</style>

<template>
<div id="app">
</div>
</template>

<script>
export default {
  name: 'app'
}
</script>

src/assets/scss/index.scss

@import 'public';

src/assets/scss/public.scss

#app {
  font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

vuex

目录结构

|-- src
  |-- store
    |-- modules
      |-- ... ...
    |-- actions.js      # 根级别的 action =>
    |-- getters.js      # 根级别的 mutation =>
    |-- index.js        # 我们组装模块并导出 store 的地方
    |-- types.js        # 根级别的 type => 状态
  |-- main.js

src/main.js

import Vue from 'vue'
import App from './App'

import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  template: '<App/>',
  components: {
    App
  }
})

vue-router

目录结构

|-- router
  |-- axios
    |-- index.js
  |-- main.js

src/main.js

import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: {
    App
  }
})

HTML5 History 模式

下面这一代码片段是使用 vue-cli 下载的模板写法,但是这种写法会使你的 URL 变成 http://localhost:8080/#/

import Vue from 'vue'
import Router from 'vue-router'
// ... ...

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      // ... ...
    }
  ]
})

对于强迫症的人来说,这样的 URL 非常丑,这就需要开启 HTML5 History 模式,更具体的说明请看官方文档 vue-router HTML5 History 模式

router/index.js

import Vue from 'vue'
import Router from 'vue-router'
// ... ...

Vue.use(Router)

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/',
      // ... ...
    }
  ]
})

export default router

路由拦截

对于进入某些页面需要进行登录验证,那么就需要设置路由拦截,vue-router 官方文档称之为导航钩子,具体请看官方文档 vue-router 导航钩子

实际上在进行路由拦截时需要进行数据验证,当验证通过时方能允许其通过该路由,该验证数据通常会存储在 vuexstate 中,或者会存储在 Local Storage,再或者 Session Storage,无论存储在哪里,vue-router 配置文件能够正确访问到即可,当然验证程序就需要后端服务 API 提供了

router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import store from '@/store'
// ... ...

Vue.use(Router)

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/',
      // ... ...
      meta: {
        requireAuth: true // 添加该字段,表示进入这个路由是需要进行验证的
      }
    }
  ]
})

router.beforeEach((to, from, next) => {
  if (to.matched.some(r => r.meta.requireAuth)) { // 判断该路由是否需要登录权限
    if (store.state.token) { // 通过 vuex state 获取当前的 token 是否存在
      next()
    } else {
      next({
        path: '/login', // 验证失败,将会跳转到该路由
        query: {
          redirect: to.fullPath
        } // 将跳转的路由 path 作为参数,登录成功后跳转到该路由
      })
    }
  } else {
    next()
  }
})

export default router

axios

自从 Vue.js 更新至 2.x 版本之后,官方就不再使用 vue-resource,替而代之的是 axios

目录结构

|-- src
  |-- axios
    |-- index.js
  |-- pages
    |-- Home.vue
  |-- main.js

src/main.js

import Vue from 'vue'
import App from './App'

import api from './axios'
Vue.prototype.$api = api

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: {
    App
  }
})

axios 拦截

使用 vue-router 进行路由拦截是不够的,当然也是需要数据验证的,更加详细的说明以及例子请移步 【vue+axios】一个项目学会前端实现登录拦截

axios/index.js

import axios from 'axios'
import store from '../store'
import router from '../router'

// 设置全局axios默认值
axios.defaults.timeout = 5000 // 5000的超时验证
axios.defaults.headers.post['Content-Type'] = 'application/jsoncharset=UTF-8'

// 创建一个axios实例
const instance = axios.create()
instance.defaults.headers.post['Content-Type'] = 'application/jsoncharset=UTF-8'

axios.interceptors.request.use = instance.interceptors.request.use

// http request 拦截器
instance.interceptors.request.use(
  config => {
    if (store.state.token) {
      config.headers.Authorization = `token ${store.state.token}`.replace(/(^")|("$)/g, '')
    }
    return config
  },
  error => {
    return Promise.reject(error)
  })

// http response 拦截器
instance.interceptors.response.use(
  response => {
    return response
  },
  error => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          store.dispatch('UserLogout')
          router.replace({
            path: 'login',
            query: {
              redirect: router.currentRoute.fullPath
            }
          })
      }
    }
    return Promise.reject(error.response)
  })

export default {
  // POST
  PostData (data) {
    return instance.post('/api/postData', data)
  },
  // GET
  GetData () {
    return instance.get('/api/user/getData')
  }
}

在 Vue 组件内调用

src/pages/Home.vue

<template>
<div>
  <!-- ... ... -->
</div>
</template>

<script>

export default {
  name: 'Home',
  data: {
    return {
      fromData: [
        // ... ...
      ]
    }
  },
  created () {
    this.$api.GetData()
      .then(({data}) => {
        // ... ...
      })
      .catch((err) => {
        console.log(err)
      })
  },
  methods: {
    post () {
      const opt = this.fromData
      this.$api.PostData(opt)
        .then(({data}) => {
          // ... ...
        })
        .catch((err) => {
          console.log(err)
        })
    }
  }
}
</script>