記第一個Vue專案臺前幕後的經歷
背景:
部門有個需要前端展示的頁面,0前端開發經驗,初次接觸Vue,實現從後端到前端,從入門,開發、打包到部署,完整的歷程。
官方文件入門
ES6基礎知識瞭解,看了阮一峰的ES6入門
然後粗略擼了一遍Vue官方文件,動手用webpack搭建了一個簡單的demo。
看了Echarts的官方demo,瞭解了幾種資料圖表的資料結構。因為我要做的專案就是要將後端介面的資料拿到,然後圖形化的形式展示出來。
下面是整個專案過程中的關鍵點以及一些坑、
後端介面開發
後端採用Golang web框架beego實現RESTFul介面,參考了官方文件,然後自己寫了一個小demo 。比較容易上手,一週就完成後端介面。
Vue對接後端,進行axios二次開發
在構建應用時需要訪問一個 API 並展示其資料,調研Vue的多種方式後選擇了官方推薦的axiox。
從ajax到fetch、axios
前端是個發展迅速的領域,前端請求自然也發展迅速,從原生的XHR到jquery ajax,再到現在的axios和fetch。
jquery ajax
$.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function() {}, error: function() {} }) 複製程式碼
它是對原生XHR的封裝,還支援JSONP,非常方便;真的是用過的都說好。但是隨著react,vue等前端框架的興起,jquery早已不復當年之勇。很多情況下我們只需要使用ajax,但是卻需要引入整個jquery,這非常的不合理,於是便有了fetch的解決方案。
fetch
fetch號稱是ajax的替代品,它的API是基於Promise設計的,舊版本的瀏覽器不支援Promise,需要使用polyfill es6-promise
舉個例子:
// 原生XHR var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText)// 從伺服器獲取資料 } } xhr.send() // fetch fetch(url) .then(response => { if (response.ok) { response.json() } }) .then(data => console.log(data)) .catch(err => console.log(err)) 複製程式碼
看起來好像是方便點,then鏈就像之前熟悉的callback。
在MDN上,講到它跟jquery ajax的區別,這也是fetch很奇怪的地方:
當接收到一個代表錯誤的 HTTP 狀態碼時,從 fetch()返回的 Promise 不會被標記為 reject, 即使該 HTTP 響應的狀態碼是 404 或 500。相反,它會將 Promise 狀態標記為 resolve (但是會將 resolve 的返回值的 ok 屬性設定為 false ), 僅當網路故障時或請求被阻止時,才會標記為 reject。 預設情況下, fetch 不會從服務端傳送或接收任何 cookies, 如果站點依賴於使用者 session,則會導致未經認證的請求(要傳送 cookies,必須設定 credentials 選項).
突然感覺這還不如jquery ajax好用呢?別急,再搭配上async/await將會讓我們的非同步程式碼更加優雅:
async function test() { let response = await fetch(url); let data = await response.json(); console.log(data) } 複製程式碼
看起來是不是像同步程式碼一樣?簡直完美!好吧,其實並不完美,async/await是ES7的API,目前還在試驗階段,還需要我們使用babel進行轉譯成ES5程式碼。
還要提一下的是,fetch是比較底層的API,很多情況下都需要我們再次封裝。 比如:
// jquery ajax $.post(url, {name: 'test'}) // fetch fetch(url, { method: 'POST', body: Object.keys({name: 'test'}).map((key) => { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); }).join('&') }) 複製程式碼
由於fetch是比較底層的API,所以需要我們手動將引數拼接成'name=test'的格式,而jquery ajax已經封裝好了。所以fetch並不是開箱即用的。
另外,fetch還不支援超時控制。
哎呀,感覺fetch好垃圾啊,,還需要繼續成長。。
axios
axios是尤雨溪大神推薦使用的,它也是對原生XHR的封裝。它有以下幾大特性:
- 可以在node.js中使用
- 提供了併發請求的介面
- 支援Promise API
簡單使用
axios({ method: 'GET', url: url, }) .then(res => {console.log(res)}) .catch(err => {console.log(err)}) 複製程式碼
併發請求,官方的併發例子:
function getUserAccount() { return axios.get('/user/12345'); } function getUserPermissions() { return axios.get('/user/12345/permissions'); } axios.all([getUserAccount(), getUserPermissions()]) .then(axios.spread(function (acct, perms) { // Both requests are now complete })); 複製程式碼
axios體積比較小,也沒有上面fetch的各種問題,我認為是當前最好的請求方式
詳情參考官方文件
二次封裝axios
為了方便開發,將axios的方法結合後端介面,進行封裝,使用起來會比較方便。
首先建立一個request.js,內容如下:
import axios from 'axios'; import Qs from 'qs'; function checkStatus(err) { let msg = "", level = "error"; switch (err.response.status) { case 401: msg = "您還沒有登陸"; break; case 403: msg = "您沒有該項許可權"; break; case 404: msg = "資源不存在"; break; case 500: msg = "伺服器發生了點意外"; break; } try { msg = res.data.msg; } catch (err) { } finally { if (msg !== "" && msg !== undefined && msg !== null) { store.dispatch('showSnackBar', {text: msg, level: level}); } } return err.response; } function checkCode(res) { if ((res.status >= 200 && res.status < 400) && (res.data.status >= 200 && res.data.status < 400)) { let msg = "", level = "success"; switch (res.data.status) { case 201: msg = "建立成功"; break; case 204: msg = "刪除成功"; break; } try { msg = res.data.success; } catch (err) { } finally { } return res; } return res; } //這裡封裝axios的get,post,put,delete等方法 export default { get(url, params) { return axios.get( url, params, ).then(checkCode).catch((error)=>{console.log(error)}); }, post(url, data) { return axios.post( url, Qs.stringify(data), ).then(checkCode).catch(checkStatus); }, put(url, data) { return axios.put( url, Qs.stringify(data), ).then(checkCode).catch(checkStatus); }, delete(url, data) { return axios.delete( url, {data: Qs.stringify(data)}, ).then(checkCode).catch(checkStatus); }, patch(url, data) { return axios.patch( url, data, ).then(checkCode).catch(checkStatus); }, }; 複製程式碼
建立一個api.js,存放後端的介面:
//匯入上面的request模組 import request from './request'; //聲明後端介面 export const urlUserPrefix = '/v1/users'; export const urlProductPrefix = '/v1/products'; //使用前面封裝好的方法,呼叫後端介面 export const getUserslInfoLast = data => request.get(`${urlUserPrefix}`, data); export const getProductsInfo = data => request.get(`${urlProductPrefix}`, data); 複製程式碼
在.vue檔案中使用定義的方法,獲取後端介面的資料:
export default{ components: { chart: ECharts, }, store, name: 'ResourceTypeLine', data: () =>({ seconds: -1, //define dataset apiResponse:{}, initOptions: { renderer: options.renderer || 'canvas' }, mounted:function() { this.fTimeArray = this.getFormatTime() //呼叫method裡面的方法 this.getUserInfo() }, methods: { //非同步方式呼叫後端介面 async getUserInfo() { const resThis = await urlUserPrefix({ params: { //get的引數在這裡新增 beginTime: this.fTimeArray[0], endTime: this.fTimeArray[1], } }); this.apiResponseThisMonth = resThis.data try { } catch (err) { console.log(err); } }, 複製程式碼
開發環境配置跨域
為了更方便地與後臺聯調,需要在用vue腳手架建立地專案中,在config目錄地index.js設定proxytable來實現跨域請求,具體程式碼如下:
module.exports = { build: { env: require('./prod.env'), index: path.resolve(__dirname, '../dist/index.html'), assetsRoot: path.resolve(__dirname, '../dist'), assetsSubDirectory: 'static', assetsPublicPath: '.', productionSourceMap: false, // Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false, productionGzipExtensions: ['js', 'css'], // Run the build command with an extra argument to // View the bundle analyzer report after build finishes: // `npm run build --report` // Set to `true` or `false` to always turn it on or off bundleAnalyzerReport: process.env.npm_config_report }, dev: { env: require('./dev.env'), port: 8080, // hosts:"0.0.0.0", autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', //配置跨域請求,注意配置完之後需要重啟編譯該專案 proxyTable: { //請求名字變數可以自己定義 '/api': { target: 'http://test.com', // 請求的介面域名或IP地址,開頭是http或https // secure: false,// 如果是https介面,需要配置這個引數 changeOrigin: true,// 是否跨域,如果介面跨域,需要進行這個引數配置 pathRewrite: { '^/api':""//表示需要rewrite重寫路徑 } } }, // CSS Sourcemaps off by default because relative paths are "buggy" // with this option, according to the CSS-Loader README // (https://github.com/webpack/css-loader#sourcemaps) // In our experience, they generally work as expected, // just be aware of this issue when enabling this option. cssSourceMap: false } } 複製程式碼
vue 專案打包部署,通過nginx 解決跨域問題
由於開發環境中配置的跨域在將專案打包為靜態檔案時是沒有用的 ,就想到了用 nginx 通過反向代理的方式解決這個問題,但是其中有一個巨大的坑,後面會講到。
前提條件
liunx 下 nginx 安裝配置(將不做多的闡述,請自行百度)
配置nginx
- 在配置檔案中新增一個server
# 新增的服務 # 新增的服務 server { listen8086; # 監聽的埠 location / { root /var/www;# vue 打包後靜態檔案存放的地址 index index.html; # 預設主頁地址 } location /v1 { proxy_pass http://47.106.184.89:9010/v1; # 代理介面地址 } location /testApi { proxy_pass http://40.106.197.89:9086/testApi; # 代理介面地址 } error_page500 502 503 504/50x.html; location = /50x.html { roothtml; } } 複製程式碼
- 解釋說明
/var/www是我當前將vue 檔案打包後存放在 liunx下的路徑 ,
當我們啟動 nginx 後 就可以通過http://ip地址:8086/訪問到vue 打包的靜態檔案。
2.location /v1 指攔截以
v1開頭的請求,http請求格式為
http://ip地址:8086/v1/***
,這裡有一個坑!一定要按照上面的配置檔案**:proxy_pass http://47.106.184.89:9010/v1;如果你像我一開始寫的:proxy_pass http://47.106.184.89:9010/;,你永遠也匹配不到對應的介面!意思是你的介面地址以
v1開頭,location的匹配也要以
v1`開頭。
proxy_passhttp://47.106.197.89:9093/v1;` 當攔截到需要處理的請求時,將攔截請求代理到介面地址。
webpack打包
下面是config/index.js配置檔案
// see http://vuejs-templates.github.io/webpack for documentation. var path = require('path') module.exports = { build: { env: require('./prod.env'), index: path.resolve(__dirname, '../dist/index.html'), assetsRoot: path.resolve(__dirname, '../dist'), assetsSubDirectory: 'static', assetsPublicPath: '.', productionSourceMap: false, // Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false, productionGzipExtensions: ['js', 'css'], // Run the build command with an extra argument to // View the bundle analyzer report after build finishes: // `npm run build --report` // Set to `true` or `false` to always turn it on or off bundleAnalyzerReport: process.env.npm_config_report }, dev: { env: require('./dev.env'), port: 8080, // hosts:"0.0.0.0", autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', //配置跨域請求,注意配置完之後需要重啟編譯該專案 proxyTable: { //請求名字變數可以自己定義 '/api': { target: 'http://billing.hybrid.cloud.ctripcorp.com', // 請求的介面域名或IP地址,開頭是http或https // secure: false,// 如果是https介面,需要配置這個引數 changeOrigin: true,// 是否跨域,如果介面跨域,需要進行這個引數配置 pathRewrite: { '^/api':""//表示需要rewrite重寫路徑 } } }, // CSS Sourcemaps off by default because relative paths are "buggy" // with this option, according to the CSS-Loader README // (https://github.com/webpack/css-loader#sourcemaps) // In our experience, they generally work as expected, // just be aware of this issue when enabling this option. cssSourceMap: false } } 複製程式碼
Dockerfile
打包Docker映象
FROM Nginx:base MAINTAINER hantmac <[email protected]> WORKDIR /opt/workDir RUN mkdir /var/log/workDir COPY dist /var/www ADDnginx/default.conf /etc/nginx/conf.d/default.conf ENTRYPOINT nginx -g "daemon off;" 複製程式碼