背景:
部门有个需要前端展示的页面, 0 前端开发经验, 初次接触 Vue, 实现从后端到前端, 从入门, 开发, 打包到部署, 完整的历程.
官方文档入门
ES6 基础知识了解, 看了阮一峰的 ES6 入门 http://es6.ruanyifeng.com/#docs/number
然后粗略撸了一遍 Vue 官方文档 https://cn.vuejs.org/ , 动手用 webpack 搭建了一个简单的 demo.
看了 Echarts 的官方 demo https://echarts.baidu.com/examples/ , 了解了几种数据图表的数据结构. 因为我要做的项目就是要将后端接口的数据拿到, 然后图形化的形式展示出来.
下面是整个项目过程中的关键点以及一些坑,
后端接口开发
后端采用 Golang Web 框架 beego 实现 RESTFul 接口, 参考了官方文档 https://beego.me/docs/intro/ , 然后自己写了一个小 https://github.com/hantmac/beego_api_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 {
- listen 8086; # 监听的端口
- 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_page 500 502 503 504 /50x.HTML;
- location = /50x.HTML {
- root HTML;
- }
- }
解释说明
/var/www 是我当前将 vue 文件打包后存放在 liunx 下的路径 ,
当我们启动 nginx 后 就可以通过 http://ip 地址: 8086 / 访问到 vue 打包的静态文件.
2.location /v1 指拦截以 v1 开头的请求, http 请求格式为 http://ip 地址: 8086/v1/*** http://xn--ip-im8ckc:8086/v1/*** , 这里有一个坑! 一定要按照上面的配置文件 **:proxy_pass http://47.106.184.89:9010/v1; 如果你像我一开始写的: proxy_pass http://47.106.184.89:9010/;, 你永远也匹配不到对应的接口! 意思是你的接口地址以 v1 开头, location 的匹配也要以 v1` 开头.
proxy_pass http://47.106.197.89:9093/v1;` http://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 <hantmac@outlook.com>
- WORKDIR /opt/workDir
- RUN mkdir /var/log/workDir
- COPY dist /var/www
- ADD nginx/default.conf /etc/nginx/conf.d/default.conf
- ENTRYPOINT nginx -g "daemon off;"
后记
这是首次接触前端的第一个项目, 期间经历了从后端接口开发, 前端框架选型 (一度想要用 react, 后来还是放弃), 熟悉 Vue, 到组件开发, webpack 构建, Nginx 部署, Docker 发布的完整过程. 虽然页面比较简单, 但是期间的采坑无数. Vue 确实是对小白比较友好, 广大后端 er 可以玩耍起来了!
来源: https://juejin.im/post/5c4f1f0de51d4552d573b784