1.什么是vue
是一套前端框架,简化前端大量的jquery,原生js的重复DOM操作代码。用来简化开发。
是一套数据驱动视图的前端框架,基于MVVM架构,M:model数据,V:view视图,vm:viewModel视图数据监听器,当数据改变,修改视图;当视图发生变化,修改model数据。是一种数据双向绑定监听的实现。
是国人尤雨溪开发。
示例:
在HBuilder中新建vue项目,起名day19_vue
先用之前的方式写一个例子:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <h1 id="mytitle">mytitle</h1> <input type="button" value="测试" οnclick="changeTitle()" /> </body> <script> function changeTitle(){ document.getElementById("mytitle").innerHTML = "测试标题"; } </script> </html>
这是命令式的js代码,先从dom结构找到元素,再找元素属性,再确定要赋的值,最后把内容显示到页面中。最后一步叫页面渲染render,不是我们控制的。
如果编写页面效果,比如改变标题等等,要改变的内容比较多时,以上过程会一遍一遍的走。
再看以下代码:
<script> var myobj={}; myobj.a="abc"; console.log(myobj.a); //abc //赋值操作是包含在Object里的一个方法 Object.defineProperty(myobj,"b",{ //js给对象定义属性,第三个参数是描述信息desc,用json设置扩展对象时的相关参数,最关键的一个就是value value:"jack" }) console.log(myobj.b); //jack </script>
在这里也可进行数据的读写,get和set方法。
<script> var myobj={}; Object.defineProperty(myobj,"b",{ get:function(){ console.log("尝试获取b的值"); }, set:function(){ //给b赋值时该函数会触发 console.log("尝试设置b的值"); } }) console.log(myobj.b); //undefined myobj.b = 10; </script>
用一个公共变量去写:
<script> var myobj={}; var temp; Object.defineProperty(myobj,"b",{ get:function(){ console.log("尝试获取b的值"); return temp; }, set:function(newVal){ console.log("尝试设置b的值 改成"+newVal); temp = newVal; } }) myobj.b = 10; console.log(myobj.b); </script>
结果,控制台输出:
尝试设置b的值 改成10
尝试获取b的值
10
用它来写上面的按钮的例子:
<body> <h1 id="mytitle">mytitle</h1> <input type="button" value="测试" οnclick="changeTitle()" /> </body> <script> var myobj={}; var temp; Object.defineProperty(myobj,"b",{ //相当于一套工具,重写get,set方法 get:function(){ console.log("尝试获取b的值"); return temp; }, set:function(newVal){ console.log("尝试设置b的值 改成"+newVal); temp = newVal; //设置值 document.getElementById("mytitle").innerHTML = temp; //渲染页面 } }) function changeTitle(){ myobj.b = "jack"; } </script>
这就是MVVM模式,把核心要干的事想明白。这里核心的事是js里改数据,页面里元素展现的效果要变。将他们写成公共代码,只需改变数据,自动在页面中变成相应的效果。
以上就是vue底层的一部分工作。
前端框架还会使用虚拟dom,用于提升页面渲染效果。
<script> function changeTitle(){ document.getElementById("mytitle").innerHTML = "测试标题1"; document.getElementById("mytitle").innerHTML = "测试标题2"; document.getElementById("mytitle").innerHTML = "测试标题3"; document.getElementById("mytitle").innerHTML = "测试标题4"; } </script>
触发按钮后,最终页面显示的是测试标题4,实际上做了四次页面渲染。
可以用变量去改进:
<script> function changeTitle(){ var temp temp = "测试标题1"; temp = "测试标题2"; temp = "测试标题3"; temp = "测试标题4"; document.getElementById("mytitle").innerHTML = temp; } </script>
这样,只会渲染一次。
实际上是有一套标准:
<script> function changeTitle(){ var temp temp = "测试标题1"; temp = "测试标题2"; temp = "测试标题3"; temp = "测试标题4"; //document.getElementById("mytitle").innerHTML = temp; console.log(document); //document对象就是dom树对象 } </script>
最终调取渲染页面的代码时不让自己调取,由底层自己找到dom树,由我们告知操作哪个元素,剩下的事底层做。
以上就是虚拟dom 的底层原理,由虚拟dom过渡底层的修改,最终只做一次渲染。
总结:使用MVVM模式,只关注数据;不直接操作dom树,页面渲染效率提升。
前端框架有三个:vue,react,angular。他们的语法完全不同,但思想都是接近的,都是MVVM模式和虚拟dom。
2.vue的使用
2.1vue写法
在项目下新建vue介绍.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/v2.6.10/vue.js" type="text/javascript" charset="UTF-8"></script> <!-- 引入vue --> </head> <body> <div id="mydiv"> 测试数据{{myval}} </div> </body> <script> new Vue({ //创建vue对象,参数是json格式 //要告知指定要监控的页面数据的分标签,然后帮助做自动化的响应式数据的流程。 响应式数据就是数据一遍,页面元素自动一遍。 el:"#mydiv", data:{ myval:"abc" } }) </script> </html>
运行,结果页面输出:测试数据abc
两个大括号(叫差值表达式)就是vue将变量值输出到页面的指令。
vue对象在使用时经常会读取:
<script> var myvue = new Vue({ el:"#mydiv", data:{ myval:"abc" } }) console.log(myvue); </script>
在控制台:
在vue对象的data部分,设置了myval值,自动生成了get,set方法。也能看到配置的el对应的属性。data里的数据myval其实直接放在了第一层,在data前两行也可看到myval。
vue里配置属性都是以$符号加对应属性名进行的。该符号在jquery里表示jquery对象,故使用vue时不能使用jquery。
可读取配置的属性:
读根标签:
console.log(myvue.$el);
控制台输出:
<div id="mydiv">测试数据abc</div>
读数据:
console.log(myvue.$data);
改数据:
console.log(myvue.$data.myval="jack");
操作模板里的键值对时,$data可省略:(仅仅是对data数据的优化,因为data的value放在了第一层)
console.log(myvue.myval="jack");
从现在开始会写一些HTML(ES6)之后的新语法。
①.之前定义变量会用var,ES6之后多了两个。
先看let:
<script> var temp1 = 10; let temp2 = 10; console.log(temp1); //10 console.log(temp2); //10 for(var i=0;i<10;i++){ console.log(i); } console.log(i); //10 循环外读i,会读到10。10是循环最后走完不满足判断条件之后出来的。从循环外读循环内使用的变量,从语法上并不合理。 for(let j=0;i<10;i++){ //let多了函数块内的作用域 console.log(j); } console.log(j); //此时报错,在循环外读j,会告知没有定义。let语法上会更严格,有块级作用域。 </script>
js里变量的作用域有:全局,函数内。let多加了一个块级作用域,指代码块,如上循环的代码就是一个典型的代码块。它使变量只在块内生效。
const:
<script> const temp3 = 10; //用来定义常量 temp3 = 11; //报错,告知是常量,不可写操作 </script>
②.ES6之后提供的拼接的语法
console.log("我的字符串"+temp3+"!!!!"); //我的字符串10!!!! 之前用+拼接 console.log(`我的字符串${temp3}!!!!`); //我的字符串10!!!!
这和vue没关系,是js里的功能。
③.定义匿名函数,ES6之后也可简化:
Object.defineProperty(myobj,"b",{ get:function(){ console.log("尝试获取b的值"); return temp; }, set:function(newVal){ console.log("尝试设置b的值 改成"+newVal); temp = newVal; document.getElementById("mytitle").innerHTML = temp; } }) //简化: Object.defineProperty(myobj,"b",{ get(){ console.log("尝试获取b的值"); return temp; }, set(newVal){ console.log("尝试设置b的值 改成"+newVal); temp = newVal; document.getElementById("mytitle").innerHTML = temp; } })
2.2vue常用指令
1.差值表达式{{}}
作用就是把变量显示在页面中。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/v2.6.10/vue.js" type="text/javascript" charset="UTF-8"></script> </head> <body> <div id="mydiv"> {{myval}} </div> </body> <script> var option = { el:"#mydiv", data:{ myval:"测试" } }; new Vue(option); </script> </html>
结果:页面输出测试
2.v-html
可解析html标签。和差值表达式的主要区别是里面可以加标签。
<body> <div id="mydiv"> {{myval}} <div v-html="myhtml"> </div> </div> </body> <script> var option = { el:"#mydiv", data:{ myval:"测试", myhtml:"<h1>测试</h1>" } }; new Vue(option); </script>
使用的不多,可用以下代码代替:
<h1>{{myval}}</h1>
3.v-bind
给元素的属性绑定变量
之前:
<input type="text" value="abc" />
如果想让变量和输入框里的值有关联:
<body> <div id="mydiv"> <input type="text" v-bind:value="mytest" /> </div> </body> <script> var option = { el:"#mydiv", data:{ mytest:"abc123" } }; let myvue = new Vue(option); myvue.mytest = "jack"; </script>
结果:页面文本框默认值变为jack
class属性也可绑定,但较特殊:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/v2.6.10/vue.js" type="text/javascript" charset="UTF-8"></script> <style> .cls1{ background-color:lightcoral; } .cls2{ border:1px solid black; } .cls3{ width:100px; height:100px; } </style> </head> <body> <div id="mydiv"> <div v-bind:class="mycls">mydiv</div> </div> </body> <script> var option = { el:"#mydiv", data:{ mycls:"cls1" } }; let myvue = new Vue(option); myvue.mycls = "jack"; </script> </html>
但是class属性允许多值:
<script> var option = { el:"#mydiv", data:{ mycls:"cls1 cls2" } }; let myvue = new Vue(option); </script>
此时就是两个样式。
但样式切换比较麻烦:
<body> <div id="mydiv"> <div v-bind:class="{'cls1':mycls1,'cls2':mycls2,'cls3':mycls3}">mydiv</div> </div> </body> <script> var option = { el:"#mydiv", data:{ mycls1:true, mycls2:true, mycls3:true } }; let myvue = new Vue(option); </script>
此时三个样式全部生效,不想让哪个生效,改成false即可。
想用一个搞定也行:
<body> <div id="mydiv"> <div v-bind:class="{'cls1':mycls1,'cls2':mycls1,'cls3':mycls1}">mydiv</div> </div> </body> <script> var option = { el:"#mydiv", data:{ mycls1:true } }; let myvue = new Vue(option); </script>
还可以使用多个class属性,需要控制的样式用v-bind控制,不需要控制的不用加v-bind
<head> <meta charset="utf-8"> <title></title> <script src="js/v2.6.10/vue.js" type="text/javascript" charset="UTF-8"></script> <style> .cls1{ background-color:lightcoral; } .cls2{ border:1px solid black; } .cls3{ width:100px; height:100px; } .cls4{ background-color:lightgreen; } </style> </head> <body> <div id="mydiv"> <div class="cls4" v-bind:class="{'cls1':mycls1,'cls2':mycls2,'cls3':mycls3}">mydiv</div> </div> </body> <script> var option = { el:"#mydiv", data:{ mycls1:false, mycls2:true, mycls3:true } }; let myvue = new Vue(option); </script>
属性绑定时也可简化,不写v-bind,只保留冒号。
4.v-if
控制页面元素是否可见
<body> <div id="mydiv"> <div v-if="divshow"> mydiv2 </div> </div> </body> <script> var option = { el:"#mydiv", data:{ divshow:false } }; let myvue = new Vue(option); </script>
此时页面不显示mydiv2
底层是用注释标签<!---->替换掉元素标签。
v-show也是控制元素是否显示:
主要区别是底层不改结构,而是给标签加样式
<div style="display:none;">mydiv2</div>
总结:v-if控制页面元素是否存在(<!-- -->);v-show控制页面元素是否隐藏(display)
除了v-if还有v-else-if和v-else:
<body> <div id="mydiv"> <div v-if="divshow==1"> <!--通过v-if后面的逻辑条件返回布尔值 --> 测试1 </div> <div v-else-if="divshow==2"> 测试2 </div> <div v-else> 测试3 </div> </div> </body> <script> var option = { el:"#mydiv", data:{ divshow:2 } }; let myvue = new Vue(option); </script>
三个里走一个,如果v-if匹配成功,则页面显示测试1;如果v-else-if匹配成功,则页面显示测试2;如果前两个都不匹配,则走v-else,页面输出测试3。
5.v-on
绑定事件
<body> <div id="mydiv"> <input type="button" value="测试按钮" v-on:click="myFun()" /> </div> </body> <script> var option = { el:"#mydiv", methods:{ myFun:function(){ console.log("按钮被点击了"); } } }; let myvue = new Vue(option); </script>
也有简写:
<body> <div id="mydiv"> <input type="button" value="测试按钮" @click="myFun()" /> </div> </body> <script> var option = { el:"#mydiv", methods:{ myFun(){ console.log("按钮被点击了"); } } }; let myvue = new Vue(option); </script>
多个函数间是逗号隔开。
vue报错的几种常见原因:
1.格式错误(比如多个函数没用逗号隔开)
2.根标签里写的所有变量(key)一定要和下面对应,即上面使用的东西下面必须有定义。但下面定义出来上面没有用是可以的。
一个简单的示例:点击按钮,改变标题文本:
<body> <div id="mydiv"> <h1>{{myval}}</h1> <input type="button" value="测试按钮" @click="myFun()" @mouseover="myFun2()" /> </div> </body> <script> new Vue({ el:"#mydiv", data:{ myval:"测试" }, methods:{ myFun(){ console.log("按钮被点击了"); this.myval = "新测试"; //this指代生成这些函数的对象,即vue对象 }, myFun2(){ console.log("鼠标过来了"); } } }); </script>
6.v-model
双向绑定
点击按钮,改变文本框的值,并且鼠标移过去读文本框的值。
<body> <div id="mydiv"> <h1>{{myval}}</h1> <input type="text" :value="mytest" /> <input type="button" value="测试按钮" @click="myFun()" @mouseover="myFun2()" /> </div> </body> <script> new Vue({ el:"#mydiv", data:{ myval:"测试", mytest:"abc123" }, methods:{ myFun(){ console.log("按钮被点击了"); this.mytest = "新测试"; }, myFun2(){ console.log("鼠标过来了"); console.log(this.mytest); } } }); </script>
换一种做法:
自己在页面的文本框输入abc123456,鼠标飘到测试按钮,却并不是abc123。原因是做属性绑定做的是单向的绑定,即可以给元素赋值,但元素里的内容发生改变,而vue对象里mytest:"abc123"却并未发生改变。
这里用双向绑定:
<body> <div id="mydiv"> <h1>{{myval}}</h1> <input type="text" v-model="mytest" /> <input type="button" value="测试按钮" @click="myFun()" @mouseover="myFun2()" /> </div> </body>
双向绑定在不同的表单元素上用法不太一样。上面是文本框的使用。
以下是单选,多选,下拉列表的使用:
<body> <div id="mydiv"> <input type="radio" value="1" v-model="gender" />男 <input type="radio" value="2" v-model="gender" />女 {{gender}} <br /> <input type="checkbox" :value="1" v-model="hobby" />爬山 <!--value不加冒号,最后展现的是字符串 --> <input type="checkbox" :value="2" v-model="hobby" />游泳 <input type="checkbox" :value="3" v-model="hobby" />开车 {{hobby}} <br /> <select v-model="area" @change="getVal()"> <option value="110">北京</option> <option value="120">上海</option> <option value="130">深圳</option> </select> </div> </body> <script> new Vue({ el:"#mydiv", data:{ gender:1 , //通过这个值设置哪个按钮选中 hobby:[1,3], area:"120" }, methods:{ getVal(){ console.log(this.area); //当选项改变时,将对应的value打印到控制台 } } }); </script>
7.v-for
哪个元素需要重复生成,就在哪个元素上加v-for。
<body> <div id="mydiv"> <ul> <li v-for="mynews in newlist" >{{mynews.content}}</li> </ul> </div> </body> <script> new Vue({ el:"#mydiv", data:{ newlist:[{"content":"新闻1"},{"content":"新闻2"},{"content":"新闻3"}] } }); </script>
页面输出:
-
新闻1
-
新闻2
-
新闻3
如果有定值要写:
<li v-for="mynews in newlist" >劲爆!!!{{mynews.content}}</li>
页面输出:
-
劲爆!!!新闻1
-
劲爆!!!新闻2
-
劲爆!!!新闻3
其他几种遍历:
<body> <div id="mydiv"> <table border="1"> <tr> <th>用户编号</th> <th>用户名</th> <th>手机号</th> </tr> <tbody> <tr v-for="user in userlist"> <td>{{user.userId}}</td> <td>{{user.userName}}</td> <td>{{user.userPhone}}</td> </tr> </tbody> </table> </div> </body> <script> new Vue({ el:"#mydiv", data:{ userlist:[{"userId":"001","userName":"小明","userPhone":"13838383838"}, {"userId":"002","userName":"小强","userPhone":"13838383838"}, {"userId":"003","userName":"小黄","userPhone":"13838383838"}] } }); </script>
页面显示:
用户编号 | 用户名 | 手机号 |
---|---|---|
001 | 小明 | 13838383838 |
002 | 小强 | 13838383838 |
003 | 小黄 | 13838383838 |
<body> <div id="mydiv"> <select v-model="myarea" > <option value="999">----请选择----</option> <option v-for="area in arealist" :value="area.areaId" >{{area.areaName}}</option> </select> {{myarea}} </div> </body> <script> new Vue({ el:"#mydiv", data:{ arealist:[{"areaId":"001","areaName":"北京"}, {"areaId":"002","areaName":"上海"}, {"areaId":"003","areaName":"深圳"}], myarea:"999" } }); </script>
增加option选项,并且下拉列表最开始显示----请选择----
3.使用axios发送ajax请求 跨区处理
3.1使用axios发送ajax请求
页面加载结束后,想使用一些功能,vue提供了一些入口,这些入口称为钩子函数。
类似javaEE中的监听器。
vue允许在这些钩子函数里写代码。
vue中提供了一些钩子函数
-
vue对象创建 create
vue挂载 mount
vue属性改变 update
vue销毁 destory
如果页面加载结束做一些初始化设计,用mount比较合适。
示例:
新建vue钩子函数
<body> <div id="mydiv"> {{myval}} </div> </body> <script> new Vue({ el:"#mydiv", data:{ myval:"", }, methods:{ }, mounted(){ console.log("页面加载后执行代码"); //页面加载结束后 发ajax请求 取后台数据 this.myval = "jack"; //页面加载结束后赋初始值 }, created(){ console.log("vue对象创建出来了"); } }) </script>
经常配合页面发ajax请求去后台取数据,取回数据,可直接给页面的元素赋值。
发ajax请求不使用jquery,而使用axios。
1.axios介绍
Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js
promise 对象是一个异步调用对象,ajax请求本身就是异步请求,需要配回调函数。
Axios 基于promise 对象封装ajax请求后,可以使用.then(表示成功的回调)和.catch(表示失败的回调)
在axios中文文档有详细的说明。
目前只需记住简化的方法:
// 向给定ID的用户发起请求 axios.get('/user?ID=12345') .then(function (response) { // 处理成功情况 console.log(response); }) .catch(function (error) { // 处理错误情况 console.log(error); }) .then(function () { // 总是会执行 });
axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
打开BootCDN,在这里引入axios(也可下载它的js)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/v2.6.10/vue.js" type="text/javascript" charset="UTF-8"></script> <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.js"></script> </head> <body> <div id="mydiv"> {{myval}} </div> </body> <script> new Vue({ el:"#mydiv", data:{ myval:"", }, methods:{ }, mounted(){ console.log("页面加载后执行代码"); this.myval = "jack"; console.log(axios); axios.get("/xxx").then(function(ret){ console.log(ret); }).catch(function(err){ console.log(err); }) }, created(){ console.log("vue对象创建出来了"); } }) </script> </html>
运行,在开发者工具中打开network,可看到xxx对应的Type是xhr,表示的就是ajax的异步请求。
以上就发出了ajax异步请求。
接下来就是后台的处理:
新建项目day9_vueDemo
新建src.com.javasm.controller.AjaxServlet
@WebServlet("/myajax") public class AjaxServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("请求进来了"); String myVal = req.getParameter("myVal"); myVal += "my dear!!!"; resp.setContentType("application/json;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print("{\"returnMsg\":\""+myVal+"\"}"); writer.flush(); writer.close(); } }
启动服务器,访问http://localhost:8080/day9/myajax?myVal=jack
页面显示:{"returnMsg":"jackmy dear!!!"}
服务接口没问题。
将HBuilder中的vue钩子函数和day19_vue中的js复制到web包下:
现在使用的vue版本主要是v2.6.10,因为v3.2.8版本还没有全面铺开。
将vue钩子函数的地址xxx修改:(get方式,参数直接在后面拼)
/day9/myajax?myVal=rose
重新部署,运行,访问:http://localhost:8080/day9/vue钩子函数.html
前端代码里,发送ajax成功后,回调函数拿到后端返回的数据,然后把数据打印了出来。(console.log(ret);)
会发现,这里返回的数据和以前有些不同:
真正的数据其实在返回的json对象里的data,故应该用返回的数据再点data,才是返回的真正的数据,其他都是数据传输的一些相关参数。响应的写法上:
mounted() { console.log("页面加载后执行代码"); console.log(axios); axios.get("/day9/myajax?myVal=rose").then(function(ret){ console.log(ret.data); }).catch(function(err){ console.log(err); }) }
get方式的参数直接拼在请求地址后面即可。post方式的参数有些不同:
mounted() { console.log("页面加载后执行代码"); console.log(axios); axios.post("/day9/myajax","myVal=rose").then(function(retdata){ console.log(retdata.data); }).catch(function (err) { console.log(err); }) }
3.2跨区处理
前面是HBUdiler写好页面,复制到idea。idea的web端发ajax请求,idea的服务端再接收请求,发送响应。
能否直接从HBUdiler直接发Ajax请求,不用复制到idea去做?
在HBUdiler,将请求地址改为:/day9/myajax?myVal=rose,跳出的页面的控制台输出:
这是Hbuilder的404页面,在其network的Hearders可看到访问路径
访问地址不对。我们使用相对根路径是在当前服务器跳转,当前是HBUilder内嵌的一个小服务器,想访问的是Tomcat的服务器。
将HBUdiler请求地址改为:http://localhost:8080/day9/myajax?myVal=jack。因为之前页面输入该地址就能访问到Tomcat服务器。结果弹出的页面报错。
执行了一个不符合要求的跨域请求,即被CORS policy规则阻挡。
CORS policy叫同源策略,通过js发请求和在浏览器里发请求不同。使用js发请求要求同源,即ip,协议,端口一致。即同一个服务器随便取数据,不同服务器不能随便取人家的数据,人家不同意就取不到。出现跨域额访问,同源策略会阻止这种请求。
什么是跨域,http:// (协议) localhost:(地址) 8080(端口) 。这三个地方有一个不同就叫跨域。
刚才就是跨了端口。
是否允许跨域是由请求和响应报文体现的。每次发请求都会知道请求的来源地址(Origin)和
请求的目的地址(Host)。
处理是处理响应报文:
@WebServlet("/myajax") public class AjaxServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /* 允许跨域的主机地址 */ resp.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8848"); /* 允许跨域的请求方法GET, POST, HEAD 等 */ resp.setHeader("Access-Control-Allow-Methods", "*"); /* 重新预检验跨域的缓存时间 (s) */ resp.setHeader("Access-Control-Max-Age", "3600"); /* 允许跨域的请求头 */ resp.setHeader("Access-Control-Allow-Headers", "*"); /* 是否携带cookie */ resp.setHeader("Access-Control-Allow-Credentials", "true"); System.out.println("请求进来了"); String myVal = req.getParameter("myVal"); myVal += "my dear!!!"; resp.setContentType("application/json;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print("{\"returnMsg\":\""+myVal+"\"}"); writer.flush(); writer.close(); } }
允许跨域的主机地址从报错信息拿或者network的请求报文拿。
此时再用HBUilder访问就可以访问通了。
当你发送请求时,浏览器会自动发送一个method=option的请求,检测服务器返回的报文中是否包含允许跨域访问的响应头(域检请求)。在重写后台的方法时,是可以看到的:
@Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doOptions(req, resp); }
这个不要在服务端重写,浏览器会通过它发送域检请求。
总结:跨域请求何时产生?当前页面是服务器A,要访问的数据是服务器B时。
4.省市县级联
后端的代码不变。day5的AreaServlet,先配置项目。
然后在重写的service方法里加跨域访问的请求头:
/* 允许跨域的主机地址 */ resp.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8848"); /* 允许跨域的请求方法GET, POST, HEAD 等 */ resp.setHeader("Access-Control-Allow-Methods", "*"); /* 重新预检验跨域的缓存时间 (s) */ resp.setHeader("Access-Control-Max-Age", "3600"); /* 允许跨域的请求头 */ resp.setHeader("Access-Control-Allow-Headers", "*"); /* 是否携带cookie */ resp.setHeader("Access-Control-Allow-Credentials", "true");
在HBUilder里新建 省市县级联菜单.html:
①.从后台拿数据
引入vue,axios和qs对应的js
根标签还是id=mydiv的div标签。
在body标签加入省市县的下拉列表,放在根标签里。
在script标签新建vue对象。
先测试,在mounted()的axios里用post方法,并将参数设置,看是否能取到数据。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/v2.6.10/vue.min.js" type="text/javascript" charset="utf-8"></script> <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.0/axios.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.3/qs.js"></script> </head> <body> <div id="mydiv"> <select id="prov"> <option disabled selected>------------请选择-------------</option> </select>省 <select id="city"> <option disabled selected>------------请选择-------------</option> </select>市 <select id="coun"> <option disabled selected>------------请选择-------------</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ }, methods:{ }, mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=00101").then(function(ret){ console.log(ret.data); }).catch(function(err){ console.log(err); }) } }) </script> </html>
结果控制台输出:
页面加载后取省数据 并输出相应的数据。
②.将拿到的数据填到页面
就是在select标签里添option标签。
请选择所在option保留,不去管,在下面增加option。
返回的数据是省数组,要遍历,用v-for。遍历省数组也要在data里写明,初始值给一个空数组。测试数据一般先用假数据。将返回的数据填到空数组中。
请求先不发送,测试一下假数据是否好用。
<body> <div id="mydiv"> <select id="prov"> <option disabled selected>------------请选择-------------</option> <option v-for="prov in provlist" :value="prov.areaId">{{prov.areaName}}</option> </select>省 <select id="city"> <option disabled selected>------------请选择-------------</option> </select>市 <select id="coun"> <option disabled selected>------------请选择-------------</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ provlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}] }, methods:{ }, mounted() { console.log("页面加载后取省数据"); /* axios.post("http://localhost:8080/day5/getArea","areaid=00101").then(function(ret){ console.log(ret.data); }).catch(function(err){ console.log(err); }) */ } }) </script>
省的下拉列表出现假数据,没问题。下一步填真数据。
真数据在retData:
mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=00101").then(function(ret){ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(function(err){ console.log(err); })
控制台打印出了真数据,但下拉列表没生效。
原因:这里this的指向改变了。axios对象和vue对象没关系,axios里的this指向window对象,指当前整个页面。
可以在外面建一个this,就可以在axios里使用了。
mounted() { console.log("页面加载后取省数据"); let that = this; axios.post("http://localhost:8080/day5/getArea","areaid=00101").then(function(ret){ console.log(ret.data.retData); that.provlist = ret.data.retData; }).catch(function(err){ console.log(err); })
运行后真实数据出现在了省的下拉列表中。
不过有更好的处理方式,直接写成箭头函数,替代匿名函数使用(类似之前的lomdar表达式):
使用箭头函数的好处:不是匿名函数,不改变箭头的指向
mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=00101").then(ret=>{ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); })
给省做双向绑定。
当省改变,要知道当前选中的是哪个省,故在data里给出省编号的数据。省编号给到请选择的身上。
<body> <div id="mydiv"> <select v-model="provCode" @change="changeCity()"> <option :value="999" disabled selected>------------请选择-------------</option> <option v-for="prov in provlist" :value="prov.areaId">{{prov.areaName}}</option> </select>省 <select > <option disabled selected>------------请选择-------------</option> </select>市 <select > <option disabled selected>------------请选择-------------</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ provCode:"999", provlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}] }, methods:{ changeCity(){ console.log(this.provCode); } }, mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=0").then(ret=>{ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }) </script>
接下来还是发请求取数据,还是先在data里建假数据。在市对应的select里加option。测试假数据:
<body> <div id="mydiv"> <select v-model="provCode" @change="changeCity()"> <option :value="999" disabled selected>------------请选择-------------</option> <option v-for="prov in provlist" :value="prov.areaId">{{prov.areaName}}</option> </select>省 <select > <option disabled selected>------------请选择-------------</option> <option v-for="city in citylist" :value="city.areaId">{{city.areaName}}</option> </select>市 <select > <option disabled selected>------------请选择-------------</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ provCode:"999", provlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}], citylist:[{areaId: "001",areaName: "金水区2"},{areaId: "002",areaName: "黑背2"}] }, methods:{ changeCity(){ console.log(this.provCode); } }, mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=0").then(ret=>{ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }) </script>
然后将真数据填入市的下拉列表:
<script> new Vue({ el:"#mydiv", data:{ provCode:"999", provlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}], citylist:[] }, methods:{ changeCity(){ console.log(this.provCode); axios.post("http://localhost:8080/day5/getArea","areaid="+this.provCode).then(ret=>{ console.log(ret.data.retData); this.citylist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }, mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=0").then(ret=>{ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }) </script>
换了省之后,市的下拉列表应该换成请选择。
<body> <div id="mydiv"> <select v-model="provCode" @change="changeCity()"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="prov in provlist" :value="prov.areaId">{{prov.areaName}}</option> </select>省 <select v-model="cityCode"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="city in citylist" :value="city.areaId">{{city.areaName}}</option> </select>市 <select > <option disabled selected>------------请选择-------------</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ provCode:"999", cityCode:"999", provlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}], citylist:[] }, methods:{ changeCity(){ console.log(this.provCode); this.cityCode = "999"; //省选项发生改变,市的下拉列表主动回到请选择 axios.post("http://localhost:8080/day5/getArea","areaid="+this.provCode).then(ret=>{ console.log(ret.data.retData); this.citylist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }, mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=0").then(ret=>{ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }) </script>
至此省市的级联就做完了。
③.和县区的级联
同样先拿假数据测试:
<body> <div id="mydiv"> <select v-model="provCode" @change="changeCity()"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="prov in provlist" :value="prov.areaId">{{prov.areaName}}</option> </select>省 <select v-model="cityCode"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="city in citylist" :value="city.areaId">{{city.areaName}}</option> </select>市 <select > <option disabled selected>------------请选择-------------</option> <option v-for="coun in counlist" :value="coun.areaId">{{coun.areaName}}</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ provCode:"999", cityCode:"999", provlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}], citylist:[], counlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}] }, methods:{ changeCity(){ console.log(this.provCode); this.cityCode = "999"; //省选项发生改变,市的下拉列表主动回到请选择 axios.post("http://localhost:8080/day5/getArea","areaid="+this.provCode).then(ret=>{ console.log(ret.data.retData); this.citylist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }, mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=0").then(ret=>{ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }) </script>
假数据没问题,清空假数据。当市改变时,发请求拿县区的真实数据,在city所在select加change事件:
<body> <div id="mydiv"> <select v-model="provCode" @change="changeProv()"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="prov in provlist" :value="prov.areaId">{{prov.areaName}}</option> </select>省 <select v-model="cityCode" @change="changeCity"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="city in citylist" :value="city.areaId">{{city.areaName}}</option> </select>市 <select > <option disabled selected>------------请选择-------------</option> <option v-for="coun in counlist" :value="coun.areaId">{{coun.areaName}}</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ provCode:"999", cityCode:"999", provlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}], citylist:[], counlist:[] }, methods:{ changeProv(){ console.log(this.provCode); this.cityCode = "999"; //省选项发生改变,市的下拉列表主动回到请选择 axios.post("http://localhost:8080/day5/getArea","areaid="+this.provCode).then(ret=>{ console.log(ret.data.retData); this.citylist = ret.data.retData; }).catch(err=>{ console.log(err); }) }, changeCity(){ console.log(this.cityCode); axios.post("http://localhost:8080/day5/getArea","areaid="+this.cityCode).then(ret=>{ console.log(ret.data.retData); this.counlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }, mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=0").then(ret=>{ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }) </script>
当市的选项发生改变时,县区的选项变为请选择:
<body> <div id="mydiv"> <select v-model="provCode" @change="changeProv()"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="prov in provlist" :value="prov.areaId">{{prov.areaName}}</option> </select>省 <select v-model="cityCode" @change="changeCity"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="city in citylist" :value="city.areaId">{{city.areaName}}</option> </select>市 <select v-model="counCode"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="coun in counlist" :value="coun.areaId">{{coun.areaName}}</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ provCode:"999", cityCode:"999", counCode:"999", provlist:[{areaId: "0010101",areaName: "金水区"},{areaId: "002",areaName: "黑背"}], citylist:[], counlist:[] }, methods:{ changeProv(){ console.log(this.provCode); this.cityCode = "999"; //省选项发生改变,市的下拉列表主动回到请选择 axios.post("http://localhost:8080/day5/getArea","areaid="+this.provCode).then(ret=>{ console.log(ret.data.retData); this.citylist = ret.data.retData; }).catch(err=>{ console.log(err); }) }, changeCity(){ console.log(this.cityCode); this.counCode = "999"; axios.post("http://localhost:8080/day5/getArea","areaid="+this.cityCode).then(ret=>{ console.log(ret.data.retData); this.counlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }, mounted() { console.log("页面加载后取省数据"); axios.post("http://localhost:8080/day5/getArea","areaid=0").then(ret=>{ console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }) </script>
当省选项改变,县区的选项也需要变为请选择,且选项也要清空。
changeProv(){ console.log(this.provCode); this.cityCode = "999"; //省选项发生改变,市的下拉列表主动回到请选择 this.counCode = "999"; //省选项发生改变,县区的下拉列表主动回到请选择 this.counlist = []; axios.post("http://localhost:8080/day5/getArea","areaid="+this.provCode).then(ret=>{ console.log(ret.data.retData); this.citylist = ret.data.retData; }).catch(err=>{ console.log(err); }) }
总结:使用vue要抛弃掉找元素,改属性,自己页面渲染的思想。这些vue框架都帮着做了,我们所要关注的就是数据怎么去变。
④.qs.js
上面发送参数时用的是比较原始的url拼接的格式:
axios.post("http://localhost:8080/day5/getArea","areaid=0")
以前用jquery时可用json格式:
axios.post("http://localhost:8080/day5/getArea",{"areaid":"0"})
这样去写,参数并没有传过去。原因:使用axios方式传数据,如果是json格式,会用json格式原封不动的传入后台。
后台不使用工具就解析不出来。
若后台不用工具,前台就想发json格式数据,就要用到qs,一个把json对象转成字符串拼接格式的工具。
axios.post("http://localhost:8080/day5/getArea",window.Qs.stringify({"areaid":"0"}))
最终的代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="js/v2.6.10/vue.min.js" type="text/javascript" charset="utf-8"></script> <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.0/axios.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.3/qs.js"></script> </head> <body> <div id="mydiv"> <select v-model="provCode" @change="changeProv()"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="prov in provlist" :value="prov.areaId" >{{prov.areaName}}</option> </select>省 <select v-model="cityCode" @change="changeCity"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="city in citylist" :value="city.areaId" >{{city.areaName}}</option> </select>市 <select v-model="counCode"> <option value="999" disabled selected>------------请选择-------------</option> <option v-for="coun in counlist" :value="coun.areaId" >{{coun.areaName}}</option> </select>县/区 </div> </body> <script> new Vue({ el:"#mydiv", data:{ provCode:"999", cityCode:"999", counCode:"999", provlist:[{areaId: '001', areaName: '河南2'},{areaId: '002', areaName: '河北2'}], citylist:[], counlist:[] }, methods:{ changeProv(){ console.log(this.provCode); this.cityCode = "999"; this.counCode = "999"; this.counlist = []; axios.post("http://localhost:8080/day5/getArea","areaid="+this.provCode) .then(ret=>{ //使用箭头函数 替代匿名函数 //箭头函数不改变this的指向 可以直接使用到vue对象 console.log(ret.data.retData); this.citylist = ret.data.retData; }).catch(err=>{ console.log(err); }) }, changeCity(){ console.log(this.cityCode); this.counCode = "999"; axios.post("http://localhost:8080/day5/getArea","areaid="+this.cityCode) .then(ret=>{ //使用箭头函数 替代匿名函数 //箭头函数不改变this的指向 可以直接使用到vue对象 console.log(ret.data.retData); this.counlist = ret.data.retData; }).catch(err=>{ console.log(err); }) } }, mounted() { console.log(window.Qs.stringify({"areaid":"0"})); console.log("页面加载后取省数据"); //console.log(this); //let that = this; axios.post("http://localhost:8080/day5/getArea",window.Qs.stringify({"areaid":"0"})) .then(ret=>{ //使用箭头函数 替代匿名函数 //箭头函数不改变this的指向 可以直接使用到vue对象 console.log(ret.data.retData); this.provlist = ret.data.retData; }).catch(err=>{ console.log(err); }) /* .then(function(ret){ console.log(ret.data.retData); console.log(this); that.provlist = ret.data.retData; }).catch(function(err){ console.log(err); }) */ } }) </script> </html>