这个教程从零开始一步一步说明搭建一个完整实用vue项目的所有流程,vue-cli + webpack + vue-router + element + axios,本人学习时间也不长,所以偏向使用级,如果有哪里不对不详,或者大家在项目过程中遇到了其他问题,都可留言给我,我也希望尽可能完善它。
在用Vue.js构建大型应用的时候推荐使用NPM安装方法,需要的东西:
去node官网 https://nodejs.org/en/ 下载,一路下一步即可。一般我选择更多人用的稳定版:
vue-cli官方文档对Node 版本也有要求,必须参考:
Vue CLI 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。
安装完成后,打开命令行(快捷键window+R,输入cmd回车),输入指令node -v
查看node的版本,若出现相应的版本号,说明安装成功:
其实npm包管理器是集成在node中的,在Node.js安装时就顺带装好了。可用命令行npm -v
查看版本:
但注意这未必是最新的版本,更新npm版本指令(三选一):
npm -g install [email protected] (安装固定某个版本,举个例子,不用跟着这个版本号)
npm install npm -g (g即全局;install是安装,可缩写成i,so,这个命令也可以写作 npm i npm -g)
npm update -g
由于有些npm资源被屏蔽或者是国外资源,经常会导致npm安装依赖包失败,所以我们还需要npm的国内镜像——cnpm。
npm 默认的 registry 是 https://registry.npmjs.org ,下载 npm 包时是从国外的服务器下载,在国内很慢,一般将它设置为淘宝镜像: https://registry.npm.taobao.org。
使用cnpm的两种方式:
npm install -g cnpm –registry=https://registry.npm.taobao.org
cnpm -v
查看cnpm版本,检测是否安装成功:npm config set registry http://registry.npm.taobao.org/
npm get registry
或者 npm config list
查看当前npm的镜像地址发生变化,如下图(原本应该是:https://registry.npmjs.org/):npm config set registry https://registry.npmjs.org/
vue-cli官网: https://cli.vuejs.org/zh
vue-cli用于快速搭建大型单页应用,可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目。
在官方文档中有说明:
关于旧版本
Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。 如果你已经全局安装了旧版本的 vue-cli(1.x 或 2.x),你需要先通过 npm uninstall vue-cli -g 或 yarn global remove vue-cli 卸载它。(此截取文档注明编辑时间为 2018-8-12 10:20:44)
安装命令: npm install -g @vue/cli
检查其版本是否正确: vue --version
注意 : 在安装vue-cli时,已经自带安装webpack
两种途径:
输入命令行 vue init webpack my_test
回车(名字自定义)。
之后程序会问一堆问题,仔细看下每个选项。进行下一个按回车,如果不认可默认内容就直接输入自己的内容;或者输入y/n;或向上向下切换选项。设置项如下:
首先要命令行cd进入这个目录下,然后 npm run dev
项目就开始跑:
等待出现项目正在运行在8080端口的提示,项目就算运行成功了:
浏览器会自动打开这个地址 localhost:8080(如果没有自动打开网页,就在浏览器手动输入该网址)就能看到下面这个类似“hello world”的网页了:
注意
上面说到项目已经成功运行,我使用的IDE是webstorm,用webstorm打开项目,在IDE里也有操作命令行的地方,这里直接是你当前项目的目录下,不用再cd进入,很方便,所以一般我都是在这里写命令行:
在IDE里打开这个项目,很清楚地看到项目目录,这是刚构建好的最原始的状态,只有原生的vue和vue-router,其余地像element-ui、axios都还木有(如下图,这里是有nodo_modules目录的,只是我设置将其隐藏了):
下面了解下在这个自动构建的项目中如何写页面、配置路由、写样式、请求协议。这里就不讲过多原理,使用级,一步一步让它变成你希望的样子(本人也是个使用级菜鸟,大家可以一起学习下es6、webpack、node)。
为什么是这个服务地址来运行这个项目?是在config/index.js文件(如下图)里进行配置的,这是项目配置文件:
run就是运行,dev意指开发环境,为什么是dev不是def、development?这个‘dev’定义在在package.json文件的scrips对象中:
(貌似是在node还是wepack下,run的时候就直接找这个package.json文件的这里)。实质上是跑的dev对应的后面这段很长的命令。你这里定义的是dev就是npm run dev,你这里写的是def就是npm run def。
这个很长的命令是执行bulid/webpack.dev.conf.js文件的配置,看这个文件的代码就会知道就是在这规定main.js是入口文件,生成后的文件注入index.html文件中。
index.html这个传统的html文件就是入口文件.如果是单页面项目,那么你的项目里也就这么一个html文件,meta头、编码、title、css外链和js文件调用都在这。
当然既然在webpack的项目下,我们所有的js、css都以模块的形式引入(怎么引入下面说),而不像传统的那样在head或者body底部引入。
这个文件里什么都没有,只有个id=app的div块,这个#app块就是关键,记做①(如下图),我们的页面就在这个div块里。
此时我打开控制台,dom树里也有个#app的div结构,记做②如下图:
那是不是①的#app就是②的#app呢?……并不是,页面上的②的#app在哪?(见4)又是怎样被放在①中的?(见5)下面我们一个一个说明。
②的#app就在src/App.vue里,如下图(同样用②标记), 改变红圈中的id名,页面查看元素中dom树的id名也会跟着改变,而且很明显,这个.vue文件里有页面上的logo图片,还有路由入口。
上面我们说项目里只有一个html页面,其他的所有页面都是.vue文件,都看做组件,这个App.vue就是根组件 。
清楚了页面在根组件App.vue里,那么根组件是怎么放进index.html的①中的?就靠main.js来牵线搭桥。src/main.js是入口js文件,看看它做了什么:
在项目目录下输入命令进行打包:npm run build
这个‘build’和dev一样是定义在在package.json文件的scrips对象中,实质是执行build/build.js文件:
主要的操作是
打包在dist目录下后生成一个html文件和一个static文件夹,staic目录下存放整个项目的所有资源,包括css、js、图片、字体等。
双击index.html文件根本无法访问页面,我们这里借助http-server方法。
npm install http-server -g
http-server
,回车就会启动服务(如下图)来运行这个目录下的index.html,在浏览器输入其中任意一个url,就能运行这个打包好的项目了src文件夹,是码源目录、开发目录,基本上绝大多数工作都是在这里开展的。初始状态下,这个目录只有
App.vue、main.js两个文件和assets、components、router三个文件夹。
示例页面HelloWorld.vue是放在components文件夹下面的,但我会将之后开发过程中自己写真正的组件放在 vue-router官方文档:https://router.vuejs.org/zh/ 上面说main.js里引入过vue-router的配置文件(注意,不是vue-router插件本身),这个文件在src/router/index.js。此文件内做了路由相关的配置:注册路由、定义路由映射、创建路由实例、设置路由拦截等。 理解完示例后来说我们自己的路由,写自己的路由配置之前,注意下,我们要构建类似这样有header,有侧导航栏aside,有主页面main的项目,点击侧导航栏主页面main部分进行切换,所以aside和header不变,main是嵌套在这的(如下图所示)。所以我们这里要做嵌套路由。 首先和实例一样,在App.vue里光溜溜放个 其次要有个layout.vue来放公用的header和aside,以及给不通的main页面来留下路由出口 而router/index.js我们这样配置:(注意图中的注释和圈出的部分) 但一般情况下,我们点击‘项目管理’,也希望能展示他下面第一个子项‘项目一’(或者某个特定子项)的页面: 或者 上面我们说名路由配置、路由出口主要是用直接输入url的跳转方式说明,但是项目中大多应该是点击跳转,比如点击侧导航跳转,那么这个怎么实现?怎么传参? 很简单,也有两个方式(这里回忆下上面说路由对象它起码这么几个属性:path路由地址,component路由对应组件,还有个name路由名称,所以一下的两种方式都有根据path跳转和根据name两种): 这里 当用 当用 关于路由的基本应用就差不多了,以后再增加页面,一定要在router/index.js里进行注册。做过小程序的人,应该有这种习惯。 这是构建项目之后我们之后不论是用less还是用element-ui等等都要安装插件, 推荐使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用。正式安装之前,我们必须先介绍下package.json这个文件。 构建项目最后我们提到一定要npm install,这个命令的工作是什么?就是根据package.json这个文件里的dependencies的内容进行安装依赖,这些依赖会被下载到node_modules目录下。 我们想安装依赖,可以在package.json的这里写好模块名和 版本号,npm install之后就安装好了,还有一种方法就是直接运行npm install模块名,这样之后这个某块名会自动出现在package.json里。 在webpack的项目中你会看到各种loader,当你系统学习webpack官方文档就会看到加载器这个大章节,这是中文文档地址:https://www.html.cn/doc/webpack2/loaders/ 。 webpack 可以使用 loader 来预处理文件。也就是我们用了less就必须有less-loader(sass同样也需要sass-loader),这个loader我现在还是没特别明白,建议之后自己去系统学下webpack的配置,等我学会了应该也会整理成笔记。 像这样: 上面几种问题,有部分情况是因为无法使用less语法,但也有一部分是其他问题,总结下使用时的注意事项(在less-loader正常工作的条件下): assets 资源目录,一般存放开发过程中自己写的静态资源(image, css, js等),上面已经说了我的css放在了自建的style目录下,所以assets里我主要放图片。 这里的资源会被wabpack构建,这句话怎么理解? 以图片来说,在 *.vue 组件中,所有模板和CSS都会被 vue-html-loader 及 css-loader 解析,并查找资源URL,图片不是 JavaScript,当被视为模块依赖时,需要使用 url-loader 和 file-loader处理它,处理的具体方法配置在build/webpack.base.conf.js里: static也是资源目录,网上说里面放一些第三方库的文件,但是我并不是很懂,对于什么情况用哪个目录也很模糊,我还是说下图片放在这个目录下的引用和打包,大家从两者的区别中自行体会两者的应用场景。 我们在开发时这样相对路径、绝对路径引用,都不会有任何问题: Element官方文档:http://element-cn.eleme.io/#/zh-CN/component/installation “Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库” 写前端会用很多插件,但是ui又有自己的设计,所以经常对已有的组件的样式重写覆盖。这里我们不可避免地对用到的element-ui的组件会有些样式修改。 很多组件的api文档提供了一些样式修改的入口,比如导航菜单组件就有: 看元素(下图所示). 推荐这么做,局部生效的就写在局部,不要去掉scoped让组件样式污染全局: 文档地址 : http://www.axios-js.com/zh-cn/docs/ Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。 简单说,axios就相当于vue中的ajax异步请求,之前我用的是vue-resource,但是vue-resource不再继续维护了,就转战axios。 进入项目目录下: 如上面注释所说,我们在api/index.js里baseURL的地方改成’/api’(如下图),其他调用的地方照旧。但是注意,因为改了config里的代码,所以要重启(npm run dev)才可以。 现在虽然功能上跑通了,但代码却很别扭,因为开发时(npm run dev),api/index.js里的baseURL由于跨域反向代理,要写成‘/api/’;而部署生产环境时(npm run build),baseURL又要改成测试服务器地址。 在 这里留一嘴,开发环境和生产环境区分开了,但是实际工作中,生产环境也分测试环境、正式环境,或者同一套代码套用在不通的项目上,这样url和一些title都不一样,都需要区分配置。这个怎么实现?之后我整理好了再发 其实我也说不清token干嘛的,只知道服务器在登录协议会返个token,之后每个协议都要在header带上token,服务器会校验,如果token过期了需要重新登录。 拦截器 token 要说qs以及为什么要用qs,就要先说说headers。 HTTP消息头包括: 补充:POST请求有请求体,故与GET请求相比,请求头中多了Content-Length和Content-Type属性。 默认情况下,axios将JavaScript对象序列化为JSON。 要以应用程序/ x-www-form-urlencoded格式发送数据。也就是我们最常用的请求协议data发送一个json对象: 如何使我们的请求数据(js对象形式)转换成form-data的格式,下面有两种方法,其中一种就用到qs。 可以使用qs库的 在我的项目里,除了/login的协议,其他的post协议都是json参数,粗暴地把post方法参数用qs转化,那其他的协议不就没法用这个post方法了么?所以最终,post方法我没动,而是新加了个postForm方法来满足需要form-data参数的协议: 之后会学习整理下vuex
这,比如弹窗、日历选择器、进度条。而页面文件,我会在src目录下新建个views文件夹来存放。
特别注意:在.vue文件的中只能包含一个
只能有一个儿子,很多很多孙子,但不能有两个儿子,不然会报错!
六、改造-路由vue-router
页面有了,我们如何实现页面访问、跳转、传参呢,有请vue-router。1、理解helloWorld示例的路由应用
路由被挂载到根实例上,根实例又挂载在App.vue的#app上,所以,在#app里放置
(如下)。
这个标签是vue-router的内部组件,是路由出口,路由匹配到的组件将渲染在这里。示例中的路由组件只有helloworld,而App.vue中,路由出口在logo图片下,也就是说helloworld组件中的内容会渲染在logo图下面:
2、路由规划(嵌套路由)
顶层出口。(那个logo图就不要了)
,这个文件同样放在views里。
之所以不直接在App.vue里直接写header、aside这些,是因为你的项目除了上图中这种布局的页面之外,肯定会有不带header、aside的其他布局的页面,比如登陆、错误页。
相应的我们访问页面:
但是/pro1的component是project,并且还有他的父级component:
所以可以这样理解,当你的path匹配到路由对象:
那么这个对象的component会被渲染在相应的
,(如果它有父级,即它是children数组的一员)他的父级component也会渲染在相应的
,因为儿子只有一个父亲,知道儿子就能找到父亲。
但是反过来,如果匹配到的这个路由对象,它有children,children的路由不会被渲染,因为一个爸爸可以有很多个儿子,只知道父亲并不能确定要找的是他哪个儿子。
这时有两个方法可以达成:
router.push({ path: '/pro1' })
<router-link to="/pro1">项目管理router-link>
3、页面跳转传参
to
的值是对象,那么属于vue的特性插值情况,用 v-bind
指令,有冒号。path
和name
的值要和router/index.js配置里的值保持一致。path
来跳转时:name
路由名称来跳转时:4、页面注册
七、package.json
dependencies
就是你程序跑起来(包括开发和生产环境)需要的模块,没有这个模块你程序就会报错。因为在初始化的时候我们值安装了vue和vue-router,所以这里就只有这两个:
npm install node_module –save
自动更新dependencies字段值devDependencies
见命知意了,开发程序的时候需要的模块了,像一些进行单元测试之类的包。
npm install node_module –save-dev
自动更新devDependencies字段八、style
写前端都知道,一个项目会有些公用css,比如reset.css或者项目中可复用的页面。我再src目录下创建style目录来存放这些css文件:
全局的公用css就在app.vue里引入(将css当做一个模块引入,用的是相对路径),如下。而一些复用页面的公共样式就在各个页面的style部分以同样的方法引入即可。
公用的css文件提出来,每个页面特有的自己的样式写在.vue文件最下面的标签内即可。这时我们会发现style标签内有个scoped,这是用来干嘛的?
scoped 表明这里写的css 样式只适用于该组件(这个.vue文件),可以限定样式的作用域。
比如在在整个项目里你有无数个input,但你只希望在project1.vue里的input的高度为80px,那么在有scoped的情况下,就这个样式只会在这个文件里生效,对于其他组件(.vue文件)不会有任何影响。九、Less
1、less-loader
2、使用less-loader
在项目目录下输入命令:npm install --save-dev less-loader less
进行安装,之后就会看到在package.json的"devDependencies"里增加了两个模块:
{
test: /\.less$/,
loader: "style-loader!css-loader!less-loader",
}
好了,这样两步操作之后,保证我们在项目中正常使用less语法了。3、注意事项
标签处没有
lang="less"
时引入.less文件,.less文件里至少要有一句less语法,不然报错。标签处要加
lang="less"
,不然less文件里的less语法的样式不生效。里写less样式,
标签也要加
lang="less"
,不然报错,有
lang="less"
,但是实际上里面没有less语法的样式, 不会有任何问题十、assets和static
1、assets
这段代码表示build打包时,png、jpg等文件用url-loader进行处理,给出了10000B的限制,即10k,小于10k的图片转成base64码形式,大于10k的文件,加上hash值重命名,不管之前这个图片存在哪里,打包后全都存在存在dist/static/img文件夹路径下。要特别说明的是,这种处理只针对于src目录下的图片文件。结合下面的例子来说明这种处理机制。
当图片放在assets里,引用时,在css里用相对路径:
在开发时,这张图我放在了src/assets里,用相对路径(貌似不能绝对路径),但是build打包后,这张图根据上面说的处理方法变成这样:
在打包后,图片都在static/img里,所以css文件里引用也响应地做了改变(如下),这些都是loader做好了工作,我们不用管。
而因为数据绑定,我们也可以在js里引用图片,这时,可以使用相对路径,也可以使用绝对路径
@/assets/login/login-logo.png
,这种写法是利用了webpack的resolve.alias特性,执行时会把路径引用中的@符号,转换为相对应的路径,这里指代src目录。具体配置在build/webpack.base.conf.js文件(如下)。里但是这种@/assets
的绝对路径在css里不能使用,@是用js来解析的。
2、static
static目录下的文件打包的时候不会被编译,只是纯拷贝到了dist下面的static文件夹,直接引用。
但打包后发现页面的图片都出不来,引用路径错误。(如果你发现没这个问题,请往下看)
通过webpack+vuecli默认打包的css、js等资源,路径都是绝对的。因为我们的图片路径都是经历过文件夹的,在本地引用图片是绝对路径,但打包后因为把配置的static文件夹当成了根路径,所以很多图片找不到都不显示。
解决方法很简单,在config/index.js,把下图中的assetsPublicPath: '/'
改成assetsPublicPath: './',
特别注意:在写这个博客的时候我又试了一次,很神奇,在我没有改之前,打包好的文件跑起来也没有任何问题,图片引用正常,这是为什么呢?
因为vue-cli改了这个‘bug’,所以你什么都不用做,你的配置里还是assetsPublicPath: '/'
,打包后依然正常。2018年还需要操心这个问题,但2019年3月这次做这个项目就没有这个问题了。具体哪个版本就没问题了我也不太清楚,有知道的可以说一下。十一、改造-ElementUI
七、八、九、十都是为这里做准备而说明的。现在页面有了,路由也有了,布局也有了,我们来看样式,现在我所知道的最流行的vue样式组件库就是element-ui,它的官网这样自我介绍:
1、安装
npm i element-ui -S
安装完后,就会在package.json里看到element,表示安装成功:
在main.js文件里写入一下代码(注意第二条,一个都不能少):import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
element-ui/lib/theme-chalk/index.css
这个element-ui的样式一定要在引入App.vue前,确保App.vue里引用的公用cssindex.css
会覆盖ElementUI样式,像这样:
这样你就可以在自己的view里使用element-ui的标签元素、组件了,在这里我用了它的布局:2、如果你想改变element-ui的样式,很坑
但未必能满足我们全部的需求。举个例子,可能不够贴切,但可以说明问题。还是这个导航菜单组件,这里为了清楚地说明,我在每个标签元素上都定义了自己class name:‘my-**’,如下:
现在,我们希望每一个菜单的高度为80px,并且有个红色的border,我们这样写:
但我们看效果未能达到预期,“导航一”这一栏并没有变化:
我们看上面代码,‘导航一’
是个有子项的一级菜单,不同于‘导航二’
的结构,这一栏是一个标签,看过vue的知道这是个虚拟的容器。
my-sbumenu
是包括‘选项1、2、3’
的导航一
的所有内容,但并不是‘导航一’
这一栏自己的,所以想在这个类下来定义‘导航一’这栏的样式也是无效的,在‘导航一’
的命名的
.my-item
并没有被写在下图的任何一个class里:
‘导航一’
这一栏实际上是被包在一个div.el-submenu_title
里,而这个div和这个class都是我们的view里没有出现的,是element-ui通过底层js创建的,我们似乎无法給这个div添加一个我们自己的class名:
不过我们既然能知道这个div,那就直接通过.el-submenu__title
来找到它,修改样式好了:
很遗憾,这个也是无效的, 不是你的样式优先级不够高(加!important
也没用),你会发现在下图右边根本没有你的样式:
这就是我说的坑。element-ui修改默认原生样式修改时,一部分元素你的class名是能够到达你想让它去的位置,如上面生效的那部分.my-item
。
但有部分元素明显是底层js创建插入dom中的,你无法命名,只能查看到它原生的class名,这种元素的样式修改,就需要在全局修改,也就是说上面样式不生效的原因在于scope
这个作用域的限制。如果想让样式生效,就去掉scoped
即可。
但是不推荐这么做,如果在每个页面因为修改原生css而去掉作用域限制,也就是这里所有的css都会变成全局的,会污染全局样式。3、如何修改element-ui原生的样式
将对底层js生成元素样式修改的这部分代码写在全局的样式文件
里,比如common.css(或者index.css,全局公共样式的引入上面有讲),而且这种原生样式的修改如果你只希望在特定某个组件文件里生效而不是全局改变,可以用它的祖先选择器做限制,比如我这个只在侧边栏.aside
里,就用.aside
限制,这样就既不会污染全局,也可以特定修改:
!!这里要注意,全局样式之所以能全局生效,不是因为它写在一个单独的css文件里,也不是因为它的名字是common.css或者index.css,而是引入这个css的标签没有
scoped
。如果你写了个全局的公共样式文件,然后在引入的地方傻傻地也加了scoped
(像下图这样),辣么就只能傻眼了(自己理解不深刻时干过这种蠢事……粘样式的时候把scoped一块带过来了,就一直找不到问题出在哪)十二、改造-axios
请求协议是不可或缺的,这里我们用axios,这是文档对它的介绍:
特性
从浏览器中创建 XMLHttpRequests
从 node.js 创建 http 请求
支持 Promise API
拦截请求和响应
转换请求数据和响应数据
取消请求
自动转换 JSON 数据
客户端支持防御 XSRF1、安装
npm install axios
安装成功,package.json会出现axios
2、使用
在src下新建api目录,在这里放和服务器请求相关的文件,在api下新建index.js封装axios底层请求:引入aixos,创建axios实例,然后在这个实例上添加请求拦截器和响应拦截器(可有可无)、封装post请求和get请求。
api/index.js代码如下:import axios from 'axios'
//创建axios实例
const instance = axios.create({
baseURL: 'http://xxx.xxx.x.xxx:8080', // api的base_url
timeout: 10000, // 请求超时时间
})
//请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
//响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
//post请求
const post = function (url, params) {
return new Promise((resolve, reject) => {
instance({
url: url,
method: 'post',
params: params
}).then(res=> {
resolve(res);
}).catch(error => {reject(error);
})
});
}
//get请求
const get = function (url, params) {
return new Promise((resolve, reject) => {
instance({
url: url,
method: 'get',
params: params
}).then(res=> {
resolve(res);
}).catch(error => {
reject(error);
})
});
}
//暴露post、get方法
export default {post,get}
然后,在main.js里,引入上面的index.js(即暴露的两个方法),再将api方法绑定到vue全局import api from './api/index';
Vue.prototype.$api = api;
/login
这样的,而在api/index.js里创建axios时配置的baseURL
(像http://xxx.xxx.x.xxx:8080
这样)会加到url
的前面,拼成完整的请求地址http://xxx.xxx.x.xxx:8080/login
我上面的login请求的全地址应该是http://xxx.xxx.x.xxx:8080/login。但是在搭建过程中我查看控制台实际的请求地址却变成http://localhost:8080/login,似乎我创建axios实例时配置的baseURL没有生效(如下图):
仔细检查才发现是个低级错误。如下图所示,我在上面这个地方创建了名为instance
实例,baseURL
也是在instance
这个实例上配置的,但是我下面的post时却是调用axios原生对象上的方法axios.post
(如左边圈出的错误用法,因为当时是直接将api文档上post方法示例代码复制过来,正确应该是instance.post
(右边)。
3、跨域问题(反向代理)
请求url正确后,发现有有新问题,报错405 Not Allowed
:
看控制台log,显然,本地前端页面请求服务器协议会有跨域问题:
跨域问题经常会遇到,服务器那边可以解决,但我们前端不能只指望服务器,万一人家就是让你这边自己弄,我们也得有解决方法,毕竟跨域只会在开发中存在,而部署正式就不存在跨域。
这里我们用反向代理解决无法跨域请求问题,打开config/index.js文件,proxyTable原本是个空对象(如图所示):
我们在这里设置反向代理,增加这段代码,把baseURL移到这里:proxyTable: {
'/api': {
target: 'http://xxx.xxx.x.xxx:8080', //axiso实例里的baseURL的值
changeOrigin: true,
pathRewrite: {
//这里理解成用‘/api’代替target里面的地址,组件中我们调接口时直接用/api代替
// 比如我要调用'http://xxx.xxx.x.xxx:8080/login',直接写‘/api/user/add’即可 代理后地址栏显示/
'^/api': '/'
}
}
},
下面看到login请求成功,而且得到了服务器响应,反向代理跨域请求成功。这里url虽然显示的是localhost,但实际是config里的那个http:xxx.xxx.x.xxx:8080。
跨域请求是只有在开发阶段(npm run dev)才有的问题,因为前后端分离,前端本地不跑服务器,在开发时都是请求后端开发人员的服务器。当开发完成打包(npm run build)后,将打包文件部署正式或者测试服务器时,服务器代码和前端代码会部署在统一台电脑上(不了解服务器,至今接触过的项目都是部署在一起的),不存在跨域问题。4、优化
这样api/index.js里的baseURL的值手动改来改去很不智能,也很容易出错,很不好。我们的目标是webpack实现开发、生产环境的打包切换。/config/
目录下有prod.env.js
、dev.env.js
分别是生产环境、开发环境配置。
在任何文件里都能简单的用process.env
获取到当前的环境配置(不用任何文件引入):
所以api/index.js
里的baseURL
在开发环境和生产环境的值,分别放在各自配置文件的的BASE_URL里:
而api/index.js
里的baseURL
则换成当前环境变量的process.env.BASE_URL
:
这样配置后,当npm run dev
时:
当npm run build
后,再请求,url里没有‘/api/’:
上面这张图是我把打包好的文件扔到测试服务器,用测试地址
打的log。
但如果是http-server
本地运行打包文件,在控制台headers里看到的url是htttp://localhost:8080/login这种,不过这时url里只要没有/api/
,能够请求到数据,就说明配置达到了预期效果。5、关于token和拦截器
拦截器有请求拦截器和响应拦截器。会在每一个请求或响应被 then 或 catch 处理前拦截它们,并在这里做一些统一处理。在上面的api/index.js的代码中能看到拦截器。
我的token就在拦截器中处理。处理逻辑是:
Vue.use(ElementUI)
,所以可以在引入Vue的条件下用Vue.prototype.$message()
调用提示框。import Vue from 'vue'
import router from '../router/index'
//响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
const res = response.data;
//token存sessionStorage
if(res.hasOwnProperty('token')){
sessionStorage.setItem('token',JSON.stringify({data:res.token}));
}
//我的协议这类错误提示为 msg: "token不存在或者无效"
if(!res.success && res.msg == 'token不存在或者无效'){
Vue.prototype.$message({
message: "token失效,请重新登录!",
type: 'warning',
})
router.push({path: '/'})
}else{
//如果没问题一定要返回
return res;
}
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
// request拦截器
instance.interceptors.request.use(
config => {
// 根据各自情况加入token-安全携带,我这每一个都要token,所以都是true
if (true) {
// 让每个请求携带token
config.headers['token'] = JSON.parse(sessionStorage.getItem('token'));
}
//一定要返回
return config;
},
error => {
// 请求错误处理
Promise.reject(error);
}
)
十三、改造-qs
1、HTTP消息头
我参考了参考:https://blog.csdn.net/j080624/article/details/56006705 这个博文,下面说说要用到的(标记加粗的一定要看),其他的自己找资料深入了解吧。
即能用于请求消息中,也能用于响应信息中,但与被传输的实体内容没有关系的信息头
key
含义
Request URL
表示请求的地址(网络资源位置)
Request Method
表情请求的方法类型get , post
Status Code
响应状态码:
200 - OK 成功返回响应 ;
301 - 资源(网页等)被永久转移到其它URL;
404 - 请求的资源(网页等)不存在;
500 - 内部服务器错误
Remote Address
表示远程服务器地址,就是你本地的地址
Referrer Policy
当从一个链接跳到另一个链接,另一个链接的referer就记录了是从哪个链接跳来的。
Referrer Policy就是管理这个来源信息的机制。
key
含义
content-length
响应体的长度(响应内容的字节数)
content-type
返回的内容类型,服务端发送的类型及采用的编码方式。
返回的响应MIME类型与编码–告诉浏览器它发送的数据属于什么文件类型。
date
原始服务器消息发出的时间客户端请求服务端的时间
expires
响应过期的日期和时间
Pragma
Pragma Pragma:no-cache ——服务端禁止客户端缓存页面数据
key
含义
Accept
客户端能接收的MIME 类型。也可以称为媒体类型和内容类型。
application/json, text/plain, */*
斜杠前面的是 type(类型),斜杠后面的是 subtype(子类型)。
type 指定包含的时何种数据,subtype 标识数据的特定类型,即大类中的小类。
例如,image/jpeg,类型是image,子类型是jpeg。
已经定义了8个顶级类型:
text/:用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的;
image/:表示图片;
model/:表示3D模型,如VRML文件;
audio/:表示声音;
video/:表示视频,可能包括声音;
message/:表示协议特定的信封,如email消息和HTTP响应;
application/:用于传输应用程序数据或者二进制数据;
multipart/:表示多个文档和资源的容器;
Accept-Language
Accept-Language表示浏览器所支持的语言类型。zh-cn表示简体中文;zh 表示中文;
Connection
表示是否需要持久连接
Content-Length
请求体的长度(响应内容的字节数)
Content-Type
服务端发送的类型及采用的编码方式
Cookie
客户端暂存服务端的信息
User-Agent
用户代理
简称 UA告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本它是一个特殊字符串头
使得服务器能够识别客户端使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
实体头有以下几种:
Form Data 请求头部的 Content-Type: application/x-www-form-urlencoded
Request Payload 请求头部的 Content-Type: application/json,并且请求正文是一个 json 格式的字符串
2、为什么要用qs
data:{
user:'admin',
password:123456
}
但是我这个登录协议服务器要求以form-data的格式,如果我们像上面那样是请求不到对的数据3、qs(全称Query String)
Qs.stringify()
将 json 对象序列化为请求参数字符串 (URL的形式,如 {name:'dahuang',age: 11}
转化为 name=dahuang&age=11
)。
在目录下,命令:npm install qs --save
!!!!先别安装,往下看!!!
这个步骤是我参考网上的一些教程的,但是同时我也看到一种说法:Qs是axios里面自带的,可以直接引入。
然后我没有像上面npm安装,我的package.json
里也没有qs的依赖,试了下直接引用,发现是完全ok的。所以按照我实际的流程,是没有这一步的。
在src/api/index.js
里引入qs
然后在我要用的post方法请求参数处使用stringify()
方法,如下图:
之后,我的请求参数确实变成了form data
的形式:
并且也从服务器获得成功的相应:
'Content-Type'
为'application/x-www-form-urlencoded'
有人要问了,上一步不是就成功获得数据了吗,为什么还有一步?
这一步也是我从网上的教程看到的,并且上面说过,我这个/login协议示例中header
的Content-Type
值需要是application/x-www-form-urlencoded
,而之前是axios默认的application/json
。那么貌似确实需要这一步设置,保持一致。
但事实上,在第二步完成后还没有设置header
的Content-Type
之前,看了下我的请求头,它就已经是:
从理论上说,qs只转换数据格式,没有做其他的工作。
但从现象上看,当请求正文(参数)被转换成form-data
的形式,请求头的Content-Type
也跟着变成相应的application/x-www-form-urlencoded
。
这是我没弄明白的地方,有知道的还请不吝赐教。所以实际上这一步我也没有做。4、优化点
十四、预留vuex