或者

小言_互联网的博客

Vuejs学习笔记(全)

302人阅读  评论(0)

欢迎访问我的个人博客:guqing’s blog

1.起步

创建一个html,然后通过如下方式引入 Vue:

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

或者

<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

1.1HelloWorld:

<div id="app">
	<p>{
   {
    msg }}</p><!-- {
   {
   }}插值表达式-->
</div>
<script type="text/javascript">
	var vm = new Vue({
   
		el:'#app',
		data:{
   
			msg:'Hello Vue!'
		}
	})
</script>

以上就成功创建了第一个Vue应用!Vue将数据和DOM之间建立了关联,所有东西都是响应式的。打开控制台修改msg的值,你将看到上例相应地更新。

除了文本插值,我们还可以像这样来绑定元素特性:

<div id="app-2">
	<span v-bind:title="message">
		鼠标悬停几秒钟查看此处动态绑定的提示信息!
	</span>
</div>
<script type="text/javascript">
	var app2 = new Vue({
    
		el: '#app-2',
		data: {
    
			message: '页面加载于 ' + new Date().toLocaleString()
		}
	})
</script>

v-bind特性被称为指令。指令带有前缀 v-以表示它们是 Vue 提供的特殊特性。该指令的意思是:“将这个元素节点的 title 特性和 Vue 实例的 message属性保持一致”。

v-text与v-cloak:

<style>
    [v-cloak]{
   
        display: none;
    }
</style>
<div id="app">
    <p v-cloak>{
  { msg }}</p>
</div>
var vm = new Vue({
   
    el:'#app',
    data:{
   
        msg:'Hello World'
    }
})

使用v-cloak时当网速较慢时不会将插值表达式{ { msg }}显示出来,只有当加载完毕时才会渲染到页面上,且使用v-cloak时可以在插值表达式两侧写其他内容并且会显示到页面上

<div id="app">
    <p v-cloak></p>
</div>

结果为:

左边-------- Hello World 右边+++++++++++

但是如果使用v-text则不需要写样式,直接绑定即可解决问题,但是标签中不能写任何内容,否则会被覆盖

<div id="app">
    <p v-text="msg"></p>
    <p v-text="msg">Hello World</p>
</div>

如上其中第一个p标签和第二个p标签显示的内容是一样的Hello World并不会被显示出来

v-text插值表达式共同点是:

其内容都会被当做文本来解析,即使内容是html格式

如果想把内容当作html来解析则使用v-html,同样v-html标签体中的内容也不会显示

<div id="app">
	<div v-html="msg">哈哈哈哈哈哈</div>
</div>

var vm = new Vue({
   
        el:'#app',
        data:{
   
        msg:'<h1>这是一个大大的H1</h1>'
    }
})

1.2条件循环v-if与v-for:

条件:

控制元素是否显示:

<div id="app-3">
	<p v-if="seen">现在你看到我了</p>
</div>

<script type="text/javascript">
	var app3 = new Vue({
    
		el: '#app-3',
		data: {
    
			seen: true
		}
	})
</script>

seentrue时会将p标签显示出来,为false时之前显示的消息消失了。

v-showv-if功能一样可以控制隐藏与显示,但是不同点是v-if每次都会重新删除或者创建元素,而v-show则不会每次都进行DOM的删除和创建操作只是添加了display:none样式

  • v-if有较高的切换性能消耗
  • v-show有较高的初始化渲染消耗

如果元素涉及到频繁的切换,最好不要使用v-if,如果元素可能永远也不会被显示出来被用户看到则推荐使用v-if

循环:

v-for 指令可以绑定数组的数据来渲染一个项目列表:

<div id="app-4">
  <ol>
    <li v-for="todo in todos">
      {
  { todo.text }}
    </li>
      <!-- 也可以获得序号-->
       <li v-for="(todo,i) in todos">
      	索引值:{
  {i}}--->元素:{
  { todo.text }}
    </li>
  </ol>
</div>

var app4 = new Vue({
   
  el: '#app-4',
  data: {
   
    todos: [
      {
    text: '学习 JavaScript' },
      {
    text: '学习 Vue' },
      {
    text: '整个牛项目' }
    ]
  }
})

可以看到:

1. 学习 JavaScript
2. 学习 Vue
3. 整个牛项目

在控制台里,输入 app4.todos.push({ text: '新项目' }),你会发现列表最后添加了一个新项目。

v-for遍历对象

<div id="app-4">
    <p v-for="(value,key) in user">
        {
  {key}}:{
  {value}}
    </p>
</div>
<script type="text/javascript">
    var app4 = new Vue({
    
        el: '#app-4',
        data: {
    
            user:{
    
                id:'1',
                name:'zhangsan',
                sex:'男',
                age:'18'
            }
        }
    })
</script>

运行结果:

id:1

name:zhangsan

sex:男

age:18

在遍历对象的键值对时除了 value ,key在第三个位置还有一个索引

v-for="(value,key,index) in user"

v-for迭代数字

<div id="app-4">
    <p v-for="count in 10">
        这是第 {
  {count}} 次循环
    </p>
</div>

count迭代从1开始

v-for中key的使用

使用key来强制数据关联

<div id="app-4">
    <label>id:<input type="text" v-model="id"/></label>
    <label>name:<input type="text"v-model="name"/></label>
    <button type="button" @click="add">添加</button>

    <!-- 注意:v-for循环的时候,key属性只能使用number获取string-->
    <!-- 注意:key在使用的时候,必须使用v-bind属性绑定的形式指定key的值 -->
    <p v-for="item in list" :key="item.id">
        <input type="checkbox"/>{
  {item.id}}-{
  {item.name}}
    </p>
</div>
		
<script type="text/javascript">
    var app4 = new Vue({
    
        el: '#app-4',
        data: {
    
            list:[
                {
    id:1, name:'赵高'},
                {
    id:2, name:'嬴政'},
                {
    id:3, name:'李斯'},
                {
    id:4, name:'荀子'}
            ]
        },
        methods:{
    

            add: function(){
    //添加方法
                this.list.push({
    id:this.id, name: this.name});
            }
        }
    })
</script>

1.3数据绑定v-bind

<div id="app-2">
    <span v-bind:title="message">
        鼠标悬停几秒钟查看此处动态绑定的提示信息!
    </span>
</div>

var app2 = new Vue({
   
	el: '#app-2',
    data: {
   
		message: '页面加载于 ' + new Date().toLocaleString()
	}
})

v-bind是vue中提供的用于绑定属性的指令,v-bind可以简写为:要绑定的属性v-bind中还可以写合法js表达式如:

<div id="app-2">
    <span v-bind:title="message + 'hellod world'">
        鼠标悬停几秒钟查看此处动态绑定的提示信息!
    </span>
</div>

1.4事件绑定v-on

为了让用户和你的应用进行交互,我们可以用 v-on 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法:

<div id="app-5">
  <p>{
  { message }}</p>
  <button v-on:click="reverseMessage">逆转消息</button>
</div>

var app5 = new Vue({
   
  el: '#app-5',
  data: {
   
    message: 'Hello Vue.js!'
  },
  methods: {
   
    reverseMessage: function () {
   
      this.message = this.message.split('').reverse().join('')
    }
  }
})

注意reverseMessage方法中,我们不用直接操作DOM即可更新状态

除了v-on:click,v-on事件可以绑定:

mouseenter

mouseleave

等事件

v-on事件缩写:@事件名

事件修饰符:

  • .stop阻止冒泡
  • .prevent阻止默认事件
  • .capture添加事件侦听器使用事件捕获模式
  • .self只当事件在该事件本身(比如不是子元素)触发时触发回调
  • .once事件只触发一次

1.5事件修饰符案例:

<div id="app">
    <div class="innder" @click="divhandler">
        <button type="button" @click="btnhandler">戳他</button>
    </div>
</div>

//创建vue实例得到ViewModel
var vm = new Vue({
   
    el:'#app',
    data:{
   

    },
    methods:{
   
        divhandler:function(){
   
            console.log('这是触发了inner div的点击事件')
    	 },
        btnhandler:function(){
   
            console.log('这是触发了 btn 按钮的点击事件')
        }
    }
})

运行结果:(冒泡机制限制性当前的事件再往外冒)

这是触发了 btn 按钮的点击事件
事件修饰符.html:29 这是触发了inner div的点击事件

如果想要阻止冒泡则在@click上加.stop

<div id="app">
    <div class="innder" @click="divhandler">
        <button type="button" @click.stop="btnhandler">戳他</button>
    </div>
</div>

执行结果(阻止了事件向外冒泡):

这是触发了 btn 按钮的点击事件

阻止默认行为.prevent

<div id="app-2">
	<a href="www.baidu.com" @click="linkClick">点我到百度</a>
</div>

如上点击会跳转到百度,如果想阻止a标签的默认跳转事件则:

//创建vue实例得到ViewModel
var vm = new Vue({
   
	el:'#app-2',
	data:{
   

	},
	methods:{
   
		linkClick:function(){
   
			alert("弹出");
		}
	}
})

capture

.capture机制即捕获机制,与冒泡机制相反,事件从往里执行

self

.self机制只触发本身的事件,既不事件冒泡也不捕获,只会阻止自己身上冒泡行为的触发而不会影响其他元素的事件

once

使用.once,事件只触发一次

1.6简单案例:跑马灯效果

<div id="app">
	<input type="button" value="浪起来" @click="lang"/>
	<input type="button" value="低调"@click="stop"/>
		
	<h4>{
   {
    message }}</h4>
</div>
	
<script type="text/javascript">
	var vm = new Vue({
   
		el:'#app',
		data:{
   
			message:'猥琐发育,别浪!',
			intervalId:null//在data上定义定时器id
		},
		methods:{
   
			lang: function() {
   
				//注意在vm实例中想要获取data上的数据,或者想要调用methods中的方法
				//必须要使用this来进行访问
				var that = this;//解决内部访问问题
				
				//在开启之前先将之前开启的定时器关闭,避免重复开启
				clearInterval(this.intervalId)
				that.intervalId = setInterval(function(){
   
					//截取第一个字符串
					var start = that.message.substring(0,1);
					//截取剩下的字符串
					var end = that.message.substring(1);
					//拼接剩下的字符串
					that.message = end + start;
				},400)
				//vue会监听自己身上data中所有数据的改变,只要数据发生变化,就会自动更新DOM
			},
			stop:function() {
   
				clearInterval(this.intervalId)
			}
		}
	})
	/*
		分析:
		1.给【浪起来按钮】绑定一个点击事件:v-on
		2.在按钮的事件处理函数中,写相关的业务逻辑代码:拿到message字符,然后
		调用字符串的substring来进行字符串截取操作,把第一个字符截取出来,放到最后
		一个位置即可
		3.为了实现点击一下按钮自动截取的功能需要2步骤中的代码,放到一个定时器中
	*/
		
</script>

1.7v-model指令:

v-bind只能实现数据的单项绑定,从M自动绑定到V,无法实现数据的双向绑定

v-model它能轻松实现表单输入和应用状态之间的双向绑定。

<div id="app-6">
  <p>{
  { message }}</p>
  <input v-model="message">
</div>

var app6 = new Vue({
   
  el: '#app-6',
  data: {
   
    message: 'Hello Vue!'
  }
})

注意v-model只能运用在表单元素中

input(radio,text,address,email…)

select

checkbox

textarea

1.8v-model简易计算器

<div id="app">
    <input type="text" v-model="n1" />

    <select v-model="opration">
        <option value ="+">+</option>
        <option value ="-">-</option>
        <option value ="*">*</option>
        <option value ="/">/</option>
    </select>

    <input type="text" v-model="n2" />

    <input type="button" value="=" @click="sulotion"/>

    <input type="text" v-model="result" />
</div>
<script type="text/javascript">
    var vm = new Vue({
    
        el:'#app',
        data:{
    
            n1:0,
            n2:0,
            result:0,
            opration:'+'
        },
        methods:{
    
            sulotion:function(){
    //计算器计算方法
                //逻辑
               sulotion:function(){
    //计算器计算方法
					var codeStr = 'parseInt(this.n1)'+ this.opration +'parseInt(this.n2)'
					this.result = eval(codeStr);
				}
            }
        }
    })
</script>

1.9在Vue中使用样式

1.数组方式:class="['thin','red','i']"

<style type="text/css">
    .red{
    
        color: red;
    }
    .thin{
    
        font-weight: 200;
    }
    .i{
    
        font-style: italic;
    }
    .active{
    
        letter-spacing: 0.5em;
    }
</style>

<div id="app">
	<h1 :class="['thin','red','i']">这是一个h1,测试vue中使用样式</h1>
</div>
<script type="text/javascript">
	var vm = new Vue({
    
		el:'#app',
		data:{
    

		},
		method:{
    

		}
	})
</script>

2.在数组中使用三元表达式:class="['thin','red',flag?'active':'']"

<div id="app">
	<h1 :class="['thin','red',flag?'active':'']">这是一个h1,测试vue中使用样式</h1>
</div>

其中flag是data中定义的变量

3.在数组中使用对象代替三元表达式提高可读性{'active':flag}

<h1 :class="['thin','red',{
    'active':flag}]">这是一个h1,测试vue中使用样式</h1>

4.直接使用对象:

:class="{'thin':true,'red':true,'active':true}"

<h1 :class="{
    'thin':true,'red':true,'active':true}">这是一个h1,测试vue中使用样式</h1>

5.间接绑定:

<div id="app">
	<h1 :class="classObj">这是一个h1,测试vue中使用样式</h1></div>

var vm = new Vue({
   
    el:'#app',
    data:{
   
        classObj:{
   'thin':true,'red':true,'active':true}
    }
})

6.使用style

也可以间接绑定

<div id="app">
	<h1 :style="{
      color:'red', font-weight:'200' }">这是一个h1,测试vue中使用样式</h1></div>

2. Vue在chrome浏览器的调试工具Vue-Devtools

作用

vue-devtools是一款基于chrome游览器的插件,用于调试vue应用,这可以极大地提高我们的调试效率。接下来我们就介绍一下vue-devtools的安装。

安装地址:

https://github.com/vuejs/vue-devtools

或者

https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd

2.1 vue过滤器

2.1.1 系统过滤器

  • 关于系统过滤器的使用请参考文档:https://v1.vuejs.org/api/#Filters
  • 注意:系统过滤器是Vue1.0中存在的,在vue2.0中已经删除了

2.1.2 自定义过滤器

2.1.2.1 私有过滤器
table {
   
    border-collapse: collapse;
    color: black;
}
th{
   
    color: white;
    background-color: #42B983;
}
table,
tr,
td,
th {
   
    border: 1px solid #FF0000;
}

<div id="app">
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>username</th>
                <th>password</th>
                <th>date</th>
            </tr>
        </thead>
        <tr v-for="user in list">
            <td>{
  { user.id }}</td>
            <td>{
  { user.username }}</td>
            <td>{
  { user.password }}</td>
            <td>{
  { user.date | dateFormate}}</td>
        </tr>
    </table>
</div>

var vm = new Vue({
   
    el: '#app',
    data: {
   
        list: [{
   
            id: 1,
            username: 'guqing',
            password: '12345',
            date: new Date()
        }]
    },
    //在某一个vue对象内部定义的过滤器称为私有过滤器
    //这种过滤器旨在当前vue对象el指定的监管的区域内游泳
    filters: {
   
        //input是自定义过滤器的默认参数,input的值永远都是取决于 | 左边的内容
        dateFormate: function(input) {
   
            console.log(input)
            //过滤器的逻辑,将input的值格式化成yyyy-MM-dd字符输出
            var year = input.getFullYear();
            var month = input.getMonth() + 1;
            var day = input.getDay();
            input = year + '-' + month + '-' + day
            return input;
        }
    }
})

以上为创建一个私有日期格式化过滤器,使用方式与系统过滤器一样

2.1.2.2 全局过滤器

html代码同私有过滤器

js如下:

//定义一个名称为dateFormate的全局过滤器,两个参数,一个参数为过滤器名称,第二个参数为逻辑代码
Vue.filter('dateFormate',function(input) {
   
    //将input的值格式化成yyyy-MM-dd字符输出
    var year = input.getFullYear();
    var month = input.getMonth() + 1;
    var day = input.getDay();
    input = year + '-' + month + '-' + day
    return input;
})

var vm = new Vue({
   
    el: '#app',
    data: {
   
        list: [{
   
            id: 1,
            username: 'guqing',
            password: '12345',
            date: new Date()
        }]
    }
})

全局过滤器在全局共有,在不能的Vue对象中都可以使用

3. v-on按键修饰符

3.1 作用说明

文档地址:https://cn.vuejs.org/v2/guide/events.html

在监听键盘事件时,我们经常需要监听常见的键值,vue允许v-on在监听键盘事件时添加按键修饰符:
- enter
- table
- delete (捕获‘删除’和‘退格’键)
- esc
- space
- up
- down
- left
- right
1.0.8+支持单字母按键别名

3.2 案例:

#app-4{
   
    width: 600px;
    margin: 0 auto;
}
table {
   
    width:100%;
    border-collapse: collapse;
    color: black;
    margin-top: 15px;
}

th {
   
    color: white;
    background-color: dodgerblue;
}

table,
tr,
td,
th {
   
    text-align: center;
    border: 1px solid #FF0000;
}

<div id="app-4">
    <label>id:<input type="text" v-model="id" /></label>
    <!-- #### 重点看这里:-->
    <!-- 注册keydown事件,不加修饰符时只要按键改变就会触发,所以要加按键修饰符 -->
    <label>姓名:<input type="text" v-model="name" @keydown.enter="add"/></label>
    <button type="button" @click="add">添加</button>

    <table>
        <thead>
            <tr>
                <th>选择</th>
                <th>ID</th>
                <th>姓名</th>
                <th>操作</th>
            </tr>
        </thead>
        <tr v-show="list.length ==0">
            <td colspan="4">当前列表没有任何数据</td>
        </tr>
        <tr v-for="user in list" :key="user.id">
            <td><input type="checkbox" /></td>
            <td>{
  { user.id }}</td>
            <td>{
  { user.name }}</td>
            <td><a href="#" @click="deleteEle">删除</a></td>
        </tr>
    </table>
</div>

var app4 = new Vue({
   
    el: '#app-4',
    data: {
   
        list: [{
   
            id: 1,
            name: '赵高'
        },
               {
   
                   id: 2,
                   name: '嬴政'
               },
               {
   
                   id: 3,
                   name: '李斯'
               },
               {
   
                   id: 4,
                   name: '荀子'
               }
              ],
        id: 0,
        name: null
    },
    methods: {
   
        add: function() {
    //添加方法
            this.list.push({
   
                id: this.id,
                name: this.name
            });
        },
        deleteEle: function(id) {
   
            var index = this.list.findIndex(function(item) {
   
                //根据item中的id属性来判断这个item是否是上面id中
                //对应的数据,如果是返回一个true ,否返回false,继续下面的一条数据的遍历,以此类推
                return item.id == id; //如果返回true,那么findIndex方法会将这个item对应的id返回到外面接受
            });
            //删除
            this.list.splice(index, 1);
        }
    }
})

3.3 可以自定义按键别名

Vue.config.keyCodes.f2 = 113;//f2自定义按键名称,113对应的键盘码
console.log(Vue.config.keyCodes)

3.4 自定义指令

在vue2.0中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通DOM元素进行底层操作,这时候就会用到自定义指令。

自定义指令最重要的两个部分就是钩子函数和钩子函数对应的参数。

3.4.1 钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

**bind:**只调用一次,指令第一次绑定到元素时调用,在这里可以进行一次性的初始化设置。

**inserted:**被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。

**update:**所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

**componentUpdated:**指令所在组件的 VNode 及其子 VNode 全部更新后调用。

**unbind:**只调用一次,指令与元素解绑时调用。

3.4.2钩子函数参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding:一个对象,包含以下属性:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1"中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用。

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。

3.4.3 自定义全局属性指令用法

//vue1.0中提供了一个Vue.directive(自定义指令名称,回调函数)来供程序员自定义指令
//注意:在定义指令时不需要加v-前缀但是使用时必须加v-前缀
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
   
    // 当被绑定的元素插入到 DOM 中时……
    inserted: function(el) {
   
        // 聚焦元素
        el.focus()
    }
})

//定义一个v-color指令
Vue.directive('color', {
   
    // 当被绑定的元素被绑定时
    bind: function(el,binding) {
   
        el.style.color = binding.value;
    }
})

使用自定义指令

<input type="text" v-focus v-color="customColor" placeholder="请输入筛选条件" v-model="searchtext" />

3.4.4 自定义私有指令

var app4 = new Vue({
   
    el: '#app-4',
    data: {
   },
	directives: {
   
        focus: {
   
            // 指令的定义
            inserted: function(el) {
   
                el.focus()
         }
     }
}

使用自定义元素

<input type="text" v-focus placeholder="请输入筛选条件" v-model="searchtext" />

3.4.5 函数简写

在很多时候,你可能想在 bindupdate 时触发相同行为,而不关心其它的钩子。比如这样写:

Vue.directive('color-swatch', function (el, binding) {
   
  el.style.backgroundColor = binding.value
})

使用同上

3.4.6 对象字面量

如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。

<div v-demo="{ color: 'white', text: 'hello!' }"></div>

Vue.directive('demo', function (el, binding) {
   
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text)  // => "hello!"
})

4. Vue的Ajax请求

4.1 Vue-resource插件介绍

下载地址:

https://github.com/pagekit/vue-resource/blob/develop/docs/http.md

$.ajax能做的事情,vue-resource插件一样也能做到,而且vue-resource的API更为简洁。另外,vue-resource还提供了非常有用的inteceptor功能,使用inteceptor可以在请求前和请求后附加一些行为,比如使用inteceptor在ajax请求时显示loading界面。

vue-resource的特点:

1. 体积小

vue-resource非常小巧,在压缩以后只有大约12KB,服务端启用gzip压缩后只有4.5KB大小,这远比jQuery的体积要小得多。

2. 支持主流的浏览器

和Vue.js一样,vue-resource除了不支持IE 9以下的浏览器,其他主流的浏览器都支持。

3. 支持Promise API和URI Templates

Promise是ES6的特性,Promise的中文含义为“先知”,Promise对象用于异步计算。
URI Templates表示URI模板,有些类似于ASP.NET MVC的路由模板。

4. 支持拦截器

拦截器是全局的,拦截器可以在请求发送前和发送请求后做一些处理。
拦截器在一些场景下会非常有用,比如请求发送前在headers中设置access_token,或者在请求失败时,提供共通的处理方式。

4.1.1 Methods

  • get(url, [config])
  • head(url, [config])
  • delete(url, [config])
  • jsonp(url, [config])
  • post(url, [body], [config])
  • put(url, [body], [config])
  • patch(url, [body], [config])

4.1.2 Config

Parameter Type Description
url string URL to which the request is sent
body Object, FormData, string Data to be sent as the request body
headers Object Headers object to be sent as HTTP request headers
params Object Parameters object to be sent as URL parameters
method string HTTP method (e.g. GET, POST, …)
responseType string Type of the response body (e.g. text, blob, json, …)
timeout number Request timeout in milliseconds (0 means no timeout)
credentials boolean Indicates whether or not cross-site Access-Control requests should be made using credentials
emulateHTTP boolean Send PUT, PATCH and DELETE requests with a HTTP POST and set the X-HTTP-Method-Override header
emulateJSON boolean Send request body as application/x-www-form-urlencoded content type
before function(request) Callback function to modify the request object before it is sent
uploadProgress function(event) Callback function to handle the ProgressEvent of uploads
downloadProgress function(event) Callback function to handle the ProgressEvent of downloads

4.1.3 Response

A request resolves to a response object with the following properties and methods:

Property Type Description
url string Response URL origin
body Object, Blob, string Response body
headers Header Response Headers object
ok boolean HTTP status code between 200 and 299
status number HTTP status code of the response
statusText string HTTP status text of the response
Method Type Description
text() Promise Resolves the body as string
json() Promise Resolves the body as parsed JSON object
blob() Promise Resolves the body as Blob object

4.1.4 实例

get请求

<!-- 导入vue.js和vue-resource.js,且按顺序导入 -->
<script src="../js/vue2.5.22.js" type="text/javascript" charset="utf-8"></script>
<script src="../js/vue-resource1.5.1.js" type="text/javascript" charset="utf-8"></script>

<div id="app">
    {
  { userList }}
    <button type="button" @click="getdata">get请求</button>
</div>

new Vue({
   
    el:'#app',
    data:{
   
        userList:null
    },
    methods:{
   
        getdata:function(){
   
            //请求的url
            var url = 'https://www.layui.com/demo/table/user/';
            //利用vue-resource发出Ajax请求
            this.$http.get(url)//发出请求
                .then(function(response){
   //获取服务器返回的数据
                this.userList = response.body;//获取当前url响应回来的数据
            });
        }
    }
})

post请求

new Vue({
   
    el:'#app',
    methods:{
   
        postdata:function(){
   
            //请求的url
            var url = 'https://www.layui.com/demo/table/user/';
            //利用vue-resource发出Ajax请求
            //post有三个参数:post(url,传入服务器的请求报文体数据,{emulateJson:true})
            this.$http.post(url,{
   name:'张三'},{
   emulateJson:true})//发出post请求
                .then(function(response){
   //获取服务器返回的数据
                console.log(response.body);//获取当前url响应回来的数据
            });
        }
    }
})

jsonp请求

new Vue({
   
    el:'#app',
    methods:{
   
        jsonpdata:function(){
   
            //请求的url
            var url = 'https://www.layui.com/demo/table/user/';
            //利用vue-resource发出Ajax请求
            //url后不需要跟callback参数
            this.$http.jsonp(url)
                .then(function(response){
   //获取服务器返回的数据
                console.log(response.body);//获取当前url响应回来的数据
            });
        }
    }
})

5. Vue生命周期

所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同,this.fetchTodos 的行为未定义。

5.1 生命周期图示

5.2 生命周期方法

beforeCreate

  • 类型Function

  • 详细

    在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

created

  • 类型Function

  • 详细

    在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

beforeMount

  • 类型Function

  • 详细

    在挂载开始之前被调用:相关的 render 函数首次被调用。

    该钩子在服务器端渲染期间不被调用。

mounted

  • 类型Function

  • 详细

    el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。

    注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted

mounted: function () {
   
  this.$nextTick(function () {
   
    // Code that will run only after the
    // entire view has been rendered
  })
}

该钩子在服务器端渲染期间不被调用。

beforeUpdate

  • 类型Function

  • 详细

    数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。

    该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。

updated

  • 类型Function

  • 详细

    由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

    当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性watcher 取而代之。

    注意 updated 不会承诺所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以用 vm.$nextTick 替换掉 updated

updated: function () {
   
  this.$nextTick(function () {
   
    // Code that will run only after the
    // entire view has been re-rendered
  })
}

activated

beforeDestroy

  • 类型Function

  • 详细

    实例销毁之前调用。在这一步,实例仍然完全可用。

    该钩子在服务器端渲染期间不被调用。

destroyed

  • 类型Function

  • 详细

    Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

    该钩子在服务器端渲染期间不被调用。

errorCaptured

2.5.0+ 新增

  • 类型(err: Error, vm: Component, info: string) => ?boolean

  • 详细

    当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false以阻止该错误继续向上传播。

    你可以在此钩子中修改组件的状态。因此在模板或渲染函数中设置其它内容的短路条件非常重要,它可以防止当一个错误被捕获时该组件进入一个无限的渲染循环。
    
    

错误传播规则

  • 默认情况下,如果全局的 config.errorHandler 被定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报。
  • 如果一个组件的继承或父级从属链路中存在多个 errorCaptured 钩子,则它们将会被相同的错误逐个唤起。
  • 如果此 errorCaptured 钩子自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的 config.errorHandler
  • 一个 errorCaptured 钩子能够返回 false 以阻止错误继续向上传播。本质上是说“这个错误已经被搞定了且应该被忽略”。它会阻止其它任何会被这个错误唤起的 errorCaptured 钩子和全局的 config.errorHandler

5.3 如何使用声明周期方法

new Vue({
   
    el:'#app',
    data:{
   
        userList:null
    },
    //Vue对象实例创建成功以后就会自定调用这个方法
    created:function(){
   
        this.getdata();
    },
    methods:{
   
        getdata:function(){
   
            //请求的url
            var url = 'https://www.layui.com/demo/table/user/';
            //利用vue-resource发出Ajax请求
            this.$http.get(url)//发出请求
                .then(function(response){
   //获取服务器返回的数据
                this.userList = response.body;//获取当前url响应回来的数据
            });
        }
    }
})

注意:data中定义的变量只有在created及以后方法才能获取到。

6 进入/离开 & 列表过渡

6.1 概述

Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。
包括以下工具:

  • 在 CSS 过渡和动画中自动应用 class
  • 可以配合使用第三方 CSS 动画库,如 Animate.css
  • 在过渡钩子函数中使用 JavaScript 直接操作 DOM
  • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

6.2 transition过渡

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点
.fade-enter-active,
.fade-leave-active {
   
    transition: opacity .5s;
}

.fade-enter,
.fade-leave-to
/* .fade-leave-active below version 2.1.8 */
{
   
    opacity: 0;
}

<div id="demo">
    <button v-on:click="show = !show">
        Toggle
    </button>
    <transition name="fade">
        <p v-if="show">hello</p>
    </transition>
</div>

new Vue({
   
    el: '#demo',
    data: {
   
        show: true
    }
})

当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:

  1. 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
  2. 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
  3. 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同)

过渡的类名

对需要进行动画控制的元素使用transition标签包裹有一个name属性值自定义如name=“fade”,但是使用css控制时的前缀必须与name值一致如fade-enter 。

在进入/离开的过渡中,会有 6 个 class 切换,以下v表示name值。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
  4. v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter 会替换为 my-transition-enter

v-enter-activev-leave-active 可以控制进入/离开过渡的不同的缓和曲线。

6.3 使用animate.css实现动画过渡

下载地址:

https://daneden.github.io/animate.css/
<div id="app">
    <button type="button" @click="show = !show">隐藏/显示</button>

    <transition enter-active-class='fadeInRight' leave-active-class='fadeOutRight'>
        <p v-if="show" class="animated">Hello Animate css</p>
    </transition>
</div>

new Vue({
   
    el:'#app',
    data:{
   
        show:true
    }
})

enter-active-class和leave-active-class是animate.css中定义好的动画类

6.4 利用钩子函数进行一半动画的控制

.show{
   
    transition: all 0.4s ease;
}

<div id="app">
    <button type="button" @click="show = !show">隐藏/显示</button>

    <transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
        <p v-if="show" class="show">Hello Animate css</p>
    </transition>
</div>

new Vue({
   
    el:'#app',
    data:{
   
        show:true
    },
    methods:{
   
        //这里主要通过js定义当前实现动画的初始位置,el表示当前操作的元素
        beforeEnter:function(el){
   
            el.style.transform = "translate(100px,0)";
        },
        //通过js定义动画的结束位置
        enter:function(el,done){
   
            //设置刷新状态
            el.offsetWidth;
			
            //设置动画的结束位置
            el.style.transform = "translate(0px,0)";

            //手动调用done,保证动画即时结束
            done();
        },
        //动画的状态复原设置
        afterEnter:function(el){
   
            this.show = !this.show;
        }
    }
})

7 组件注册

组件名

在注册一个组件的时候,我们始终需要给它一个名字。比如在全局注册的时候我们已经看到了:

Vue.component('my-component-name', {
    /* ... */ })

该组件名就是 Vue.component 的第一个参数

你给予组件的名字可能依赖于你打算拿它来做什么。当直接在 DOM 中使用一个组件 (而不是在字符串模板或单文件组件) 的时候,我们强烈推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突

组件名大小写

定义组件名的方式有两种:

使用 kebab-case

Vue.component('my-component-name', {
    /* ... */ })

当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 <my-component-name>

使用 PascalCase

Vue.component('MyComponentName', {
    /* ... */ })

当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name><MyComponentName> 都是可接受的。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

7.1 注册全局组件

到目前为止,我们只用过 Vue.component 来创建组件:

Vue.component('my-component-name', {
   
  // ... 选项 ...
})

这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。比如:

Vue.component('component-a', {
    /* ... */ })
Vue.component('component-b', {
    /* ... */ })
Vue.component('component-c', {
    /* ... */ })

new Vue({
    el: '#app' })

使用组件

<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。

7.2 定义和组件的三种方式

第一种方式:

//方法1:定义组件
var login = Vue.extend({
   
    template:'<h1>登陆页面</h1>'
})
//注册组件
Vue.component('login',login);

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

第二种方式:

//方法2:定义一个全局组件
Vue.component('register',{
   
    template:'<h1>注册页面</h1>'
})

使用

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

第三种方式:

//方法3:定义一个账号组件,建议使用方法三
Vue.component('account',{
   
    template:'#account',
    data:function(){
   
        //与vue对象不同的是组件中的data是一个function,
        //所以要这么写
        return {
   
            message:'账户组件'
        }
    },
    methods:{
   
        login:function(){
   
            alert("Hello VueJs")
        }
    }
})

模板的事件写在对应的模板中

全局组件引用了一个在html中定义的组件模板account

<!--组件模板的定义 template和script定义都可以但是建议使用template-->
<template id="account">
    <div>
        {
  {message}}
        <a href="#" @click.prevent="login">登陆</a> |
        <a href="#">注册</a>
    </div>
</template>

也可以使用script定义但是建议使用template标签

 <script type="x-template" id="account">
     <div>
    	<a href="#">登陆</a> |
		<a href="#">注册</a>
	</div>
</script>

值得注意的是,所有子元素必须使用一个共同的根标签包围,如div

7.3 局部注册

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:

var ComponentA = {
    /* ... */ }
var ComponentB = {
    /* ... */ }
var ComponentC = {
    /* ... */ }

然后在 components 选项中定义你想要使用的组件:

new Vue({
   
  el: '#app',
  components: {
   
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

或者是在组件中定义子组件

//定义一个账号组件
Vue.component('account',{
   
    template:'<div><h1>账号组件</h1><login></login></div>',
    //在账号组件中定义一个登陆子组件
    components:{
   
        'login':{
   
            template:'<h2>登陆子组件</h2>'
        }
    }
})

在组件中定义的子组件必须在父组件中使用如上面的

<div><h1>账号组件</h1><login></login></div>

对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。

7.4 组件的动态切换

首先注册两个组件,login和regist

Vue.component('login',{
   
    template:'<h1>登陆页面</h1>'
})

Vue.component('regist',{
   
    template:'<h1>注册页面</h1>'
})		

<div id="app">
    <a href="#" @click.prevent="cname = 'login'">登陆</a> |
    <a href="#" @click.prevent="cname = 'regist'">注册</a>
    <!-- 利用component标签中的:is参数来进行组件的切换 -->
    <component :is="cname"></component>
</div>

组件的切换使用component标签的is属性

new Vue({
   
    el:'#app',
    data:{
   
        cname:'login'
    }
})

只要控制is的值即可控制组件的显示,如上通过点击事件改变cname的值来切换组件

7.5 父组件与子组件传值

父组件向子组件传值

  • 定义组件
<template id="subEle">
    <div>
        {
  {name}}
    </div>
</template>

new Vue({
   
    el:'#app',
    data:{
   
        name:'张三'
    },
    components:{
   
        'subelement':{
   
            template:'#subEle',
            props:['name']//负责接收父组件传入的值
        }
    }
})

在组件中有一个props属性在其中定义变量负责接收父组件传递过来的值

然后在组件模板中使用插值表达式接收{ {name}}

  • 父组件如何向子组件传递值?

通过将子组件中负责接收父组件传入值得变量name绑定到组件上传入值即可

<div id="app">
	<subelement :name="name"></subelement>
</div>

子组件向父组件传值

new Vue({
   
    el:'#app',
    data:{
   
        name:'张三'
    },
    methods:{
   
        getdata:function(data){
   
            alert(data)
        }
    },
    components:{
   
        'subelement':{
   
            template:'#subEle',
            //props:['name']//负责接收父组件传入的值
            methods:{
   
                sendData:function(){
   
                    //将hello传值给父组件
                    this.$emit('send','Hello VueJs')
                }
            }
        }
    }
})

如上所示:在子组件中写一个方法sendData用于向父组件发送值,内部使用this.$emit方法向父组件发送一个事件用于传递值,key是事件名称,值就是呀发送的数据。

当点击子组件中定义的按钮时触发sendData事件向父组件传值

<template id="subEle">
    <button type="button" @click="sendData">点击传值</button>
</template>

而这个父组件我们可以将Vue对象的实例视为一个根组件,将数据传递给Vue实例。

使用组件并传递值

<div id="app">
    <subelement v-on:send="getdata"></subelement>
</div>

当点击子组件的按钮时调用子组件中定义的方法sendData触发一个方法向父组件发送一个send事件,而send事件绑定了一个getdata方法用于获取子组件传递过来的值

methods:{
   
        getdata:function(data){
   
            alert(data)
        }
    }

8 获取dom对象和组件的对象

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:

<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>

<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>

关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!$refs 也不是响应式的,因此你不应该试图用它在模板中做数据绑定。

使用ref属性用于对象或组建获取

<div id="app">
    <button type="button" @click="getdom">获取dom对象</button>
    <div id="div1" ref="myDiv">Hello VueJs</div>

    <button type="button" @click="getComponents">获取组件对象</button>
    <login ref="mycomponents"></login>
</div>

new Vue({
   
    el:'#app',
    methods:{
   
        getdom:function(){
   
            //获取到id="div1"的div对象
            //有两种方式可以完成
            //1.通过传统的document方式
            console.log(document.getElementById("div1").innerHTML)

            //2.vuejs的写法,在元素上添加v-el
            console.log(this.$refs.myDiv)
        },
        getComponents:function(){
   
            //获取组件对象
            console.log(this.$refs.mycomponents)
            //获取组件中的值
            console.log(this.$refs.mycomponents.subname)
        }
    },
    //定义私有组件(子组件)
    components:{
   
        'login':{
   
            data:function(){
   
                return {
   subname:'这是一个组件名称'}
            },
            template:'<h1>这是一个子组件</h1>'
        }
    }
})

9 vue-router路由

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

9.1 路由写法和传参

引入vuejs和vue-router

<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

//准备组件
var Ap = Vue.extend({
   });

var login = Vue.extend({
   
    template:'<div><h1>登陆</h1></div>'
})

var regist = Vue.extend({
   
    template:'<div><h1>注册{
   {name}}</h1></div>',
    data:function(){
   
        return {
   
            name:''
        }
    },
    created:function() {
   //在页面渲染是将参数赋值给name
        this.name = this.$route.params.name
    }
})

//实例化路由规则对象
var router = new VueRouter({
   
    routes:[
        {
   path:'/',redirect:'/login'},//如果是/则自动重定向到login
        {
   path:'/login',component:login},
        {
   path:'/regist/:name',component:regist}//获取参数
    ]
});

//开启路由对象
new Vue({
   
    el:'#app',
    router:router//开启路由对象
})

<div id="app">
    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <router-link to="/login">登陆</router-link>
    
    <!--通过router-link指定参数 -->
    <router-link to="/regist/zhangsan">注册</router-link>

    <!-- 路由出口 -->
 	<!-- 路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
</div>

9.2 嵌套路由

//准备组件
//准备组件
var App = Vue.extend({
   });

var account = Vue.extend({
   
    template:'<div><h1>账号组件</h1></div>'
})

var login = Vue.extend({
   
    template:'<div><h1>登陆</h1></div>'
})

var regist = Vue.extend({
   
    template:'<div><h1>注册</h1></div>'
})

//实例化路由规则对象
var router = new VueRouter({
   
    routes:[
        {
   
            path:'/account',
            component:account,
            children:[
                {
   
                    path:'login',
                    component:login
                },
                {
   
                    path:'regist',
                    component:regist
                }
            ]
        }
    ]
});

//开启路由对象
new Vue({
   
    el:'#app',
    router:router//开启路由对象
})

10 watch和computed

computed计算属性

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

computed: {
   
  now: function () {
   
    return Date.now()
  }
}

侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<div id="demo">{
  { fullName }}</div>

var vm = new Vue({
   
  el: '#demo',
  data: {
   
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
   
    firstName: function (val) {
   
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
   
      this.fullName = this.firstName + ' ' + val
    }
  }
})

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

var vm = new Vue({
   
  el: '#demo',
  data: {
   
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
   
    fullName: function () {
   
      return this.firstName + ' ' + this.lastName
    }
  }
})

好得多了,不是吗?

计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :

computed: {
   
  fullName: {
   
    // getter
    get: function () {
   
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
   
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

现在再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也会相应地被更新。

11 安装webpack

安装webpack首先需要nodjs的环境

安装nodejs

1.下载地址

https://nodejs.org/en/

2.运行安装包,选择相关的路径,主要注意点如下选择add to path,因为这样会自动给你配置对应的环境变量,其余的都是直接下一步下一步然后install

3.检验是否安装成功,执行两个命令node -vnpm -v(这个主要高版本的nodejs会在安装的过程中自动帮你安装的,主要的作用是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西) 分别查看版本信息

Nodejs使用前的准备工作

1.在安装目录D:\Program Files\nodejs下创建两个文件夹node_globalnode_cache,主要防止执行其他安装命令时候将东西安装在C盘里面,希望将全模块所在路径和缓存路径放在我node.js安装的文件夹中。

2.新建文件后在执行命令行cmd,然后执行下面两个语句

npm config set prefix "D:\Program Files\nodejs\node_global"

npm config set cache "D:\Program Files\nodejs\node_cache"


3.接下来设置环境变量,关闭cmd窗口,“我的电脑”-右键-“属性”-“高级系统设置”-“高级”-“环境变量”,系统变量下新建NODE_PATH,填写好对应的路径

修改默认的用户变量D:\Program Files\nodejs\node_global

测试一下

配置完后,安装个module测试下,我们就安装最常用的express模块,打开cmd窗口,
输入如下命令进行模块的全局安装:

npm install express -g     # -g是全局安装的意思

安装nrm命令

npm install nrm -g  

设置镜像地址为taobao镜像

nrm ls #查看镜像地址
nrm use taobao  #切换到淘宝镜像

安装webpack1.14.0版本

npm install webpack@1.14.0 -g

Webpack常用指令和wabpack.config.js配置文件

  • webpack常用指令
webpack 入口文件.js 输出文件.js
webpack //最基本的启动webpack的方法,默认查找名为webpack.config.js文件
webpack --config webpack.config.js //指定配置文件

webpack -p //对打包后的文件进行压缩
webpack -d //提供source.map,方便调试代码

  • webpack配置文件

    webpack配置文件打包方式需要新建一个dist目录用于存放打包后的配置文件

src中存放需要打包的文件及入口js文件main.js,在入口文件中使用的文件会被打包

webpack.config.js文件

module.exports={
   
	entry:'./src/main.js',//指定打包的入口文件
	output:{
   //配置输出
		path:__dirname + '/dist',//注意:webpack1.14.0要求这个路径是一个绝对路径
		filename:'build.js'
	}
}

11.1 webpack相关配置

在使用loader之前需要在当前目录下打开cmd命令面板,输入:

nmp init

初始化一个package.json文件在存放相关的文件

11.1.1 打包css资源演示

wabpack中使用css-loader和style-loader这两个loader来处理css资源的打包工作,所以使用前必须在项目中安装这两个包:

npm i css-loader style-loader --save-dev

  • 在webpack.config.js中配置这两个loader

css-loader:只负责加载css模块,不会将加载的css样式应用到html
style-loader:负责将css-loader加载到的css样式动态的添加到html-head-style标签中

在webpack.config.js中添加处理css文件的loader配置:

module.exports={
   
	entry:'./src/main.js',//指定打包的入口文件
	output:{
   //配置输出
		path:__dirname + '/dist',//注意:webpack1.14.0要求这个路径是一个绝对路径
		filename:'build.js'
	},
	module:{
   
		loaders:[
			{
   
				test:/\.css$/,//以css结尾的文件
				loader:'style-loader!css-loader'//使用!作为分隔符
			}
		]
	}
}

在执行webpack命令,重新打包输出文件,打包好以后css就被打包到了build.js文件中,只要引入build.js文件css样式就会生效

11.2 打包less文件

安装lessless-loader

cnpm install less less-loader style-loader css-loader --save-dev

./statics/css/目录下新建一个less文件:site2.less

@color:green;
#res{
	border: 3px solid @color;
}

在入口文件main.js中导入less文件:

//导入less文件
require('../statics/css/site2.less')

使用webpack打包即可预览到效果

11.3webpack打包url()请求的资源

  • 需要安装的node包有:
url-loader:打包通过url方式请求的资源

file-loader:url-loader的依赖loader
在项目根目录下打开cmd输入:
npm install url-loader file-loader --save-dev

  • 在webpack.config.js文件中配置这两个loader
{
   
    test: /\.(png|jpe?g|gif|svg|ttf|bmp)(\?.*)?$/,
        loader: 'url-loader',
            options: {
   
                limit: 1000,
                    name: 'img/[name].[hash:7].[ext]'
            }
}

在css文件中使用图片资源

#bg{
   
	width: 300px;
	height: 300px;
	background: url(../img/AIMP.png);
}

在webpack打包即可

11.4 利用webpack-dev-server实现热刷新配置

我们在修改了代码以后需要不断的重新执行webpack命令重新打包然后回到浏览器刷新页面查看,这种开发效率低下,所以这里使用weboack-dev-server当代码更新的时候自动刷新浏览器。

需要安装的node包有:

  • webpack@1.14.0:webpack-dev-server依赖与webpack
  • webpack-dev-server@1.16.0 :webpack开发服务器
  • html-webpack-plugin : 结果webpack在内存中自动生成index.html的入口文件

安装(前提有node环境):

cnpm install webpack@1.14.0 webpack-dev-server@1.16.0 html-webpack-plugin --save-dev

在package.json文件中配置webpack-dev-server命令

"script": {
   
    "dev":"webpack-dev-server --inline --hot --open --port 4009"
}

参数说明:

  • –inline:自动刷新
  • –hot :热加载
  • –port : 指定监听端口号为4009
  • –open : 指定在默认浏览器中打开
  • –host : 可以指定服务器的ip,不指定默认为127.0.0.1(localhost)

配置html-wabpack-plugin组件

webpack-dev-server要实现浏览器自动刷新,必须要利用html-webpack-plugin在内存中生成index.html页面才能实现,html-webpack-plugin配置步骤:

  • 1.在webpack.config.js文件中加入如下代码:
//导入html-webpack-plugin包获取到插件对象
var htmlwp = require('html-webpack-plugin');

plugins:[
    new htmlwp({
   
        title:'首页',//生成的页面标题
        filename:'index.html',//webpack-dev-server在内存中生成的文件名称,自动将build.js注入到这个页面底部
        template:'index1.html'//更具index1.html这个模板生成(这个文件由程序员自己定义)
    })
]

运行

在控制台执行:

npm run dev

就会开启webpack-dev-server服务器来运行vue项目,这时候随便修改一个css样式,就会自动刷新看到效果。

webpack.config.js完整示例配置:

var htmlwp = require('html-webpack-plugin');

module.exports={
   
	entry:'./src/main.js',//指定打包的入口文件
	output:{
   //配置输出
		path:__dirname + '/dist',//注意:webpack1.14.0要求这个路径是一个绝对路径
		filename:'build.js'
	},
	module:{
   
		loaders:[
			{
   
				test:/\.css$/,//以css结尾的文件
				loader:'style-loader!css-loader'//使用!作为分隔符
			},
			{
   
				test:/\.less$/,//以less结尾的文件
				loader:'style-loader!css-loader!less-loader'
			},
			{
   
                test: /\.(png|jpe?g|gif|svg|ttf|bmp)(\?.*)?$/,
                loader: 'url-loader',
                options: {
   
                    limit: 1000,
                    name: 'img/[name].[hash:7].[ext]'
                }
            }
		]
	},
	plugins:[
		new htmlwp({
   
			title:'首页',//生成的页面标题
			filename:'index.html',//webpack-dev-server在内存中生成的文件名称,自动将build.js注入到这个页面底部
			template:'index1.html'//更具index1.html这个模板生成(这个文件由程序员自己定义)
		})
	]
}

11.5ECMAScript6语法转ECMAScript5语法

需要安装的node包有:

  • babel-core
  • babel-loader
  • babek-plugin-trabsform-runtime:这个包主要是打包.vue组件页面中的es6语法需要

在项目目录下打开cmd命令面板输入:

npm install babel-core babel-loader@6.4.1 babel-preset-es2015 babel-plugin-transform-runtime --save-dev

  • 在webpack-config.js中配置这两个loader
loaders:[{
   
    //将当前项目中所有的.js文件都要进行es6转es5操作,node_moudels除外
    test:/\.js$/,//表示当前打包的文件后缀的正则表达式
    //loader:'babel-loader?presets[]=es2015',//如果写到这里将来在打包.vue文件的时候会报错,表示先利用css-loader解析.css文件,在调用style-loader打包
    loader:'babel-loader',
    exclude:/node_modules/  //node_modules中的所有.js文件不去转换,提高打包性能
}],
babel:{
   
   presets:['es2015'],
   plugins:['transform-runtime']//这句代码就是为了解决打包.vue文件不报错
}

完整的配置为:

module.exports = {
   
	entry: './src/main.js', //指定打包的入口文件
	output: {
    //配置输出
		path: __dirname + '/dist', //注意:webpack1.14.0要求这个路径是一个绝对路径
		filename: 'build.js'
	},
	module: {
   
		loaders: [{
   
				test: /\.css$/, //以css结尾的文件
				loader: 'style-loader!css-loader' //使用!作为分隔符
			},
			{
   
				test: /\.less$/, //以less结尾的文件
				loader: 'style-loader!css-loader!less-loader'
			},
			{
   
				test: /\.(png|jpe?g|gif|svg|ttf|bmp)(\?.*)?$/,
				loader: 'url-loader',
				options: {
   
					limit: 1000,
					name: 'img/[name].[hash:7].[ext]'
				}
			},
			{
   
				//将当前项目中所有的.js文件都要进行es6转es5操作,node_moudels除外
				test: /\.js$/, //表示当前打包的文件后缀的正则表达式
				//loader:'babel-loader?presets[]=es2015',//如果写到这里将来在打包.vue文件的时候会报错,表示先利用css-loader解析.css文件,在调用style-loader打包
				loader: 'babel-loader',
				exclude: /node_modules/ //node_modules中的所有.js文件不去转换就是排除,提高打包性能
			}
		]
	},
	babel: {
   
		presets: ['es2015'],
		plugins: ['transform-runtime'] //这句代码就是为了解决打包.vue文件不报错
	},
	plugins: [
		new htmlwp({
   
			title: '首页', //生成的页面标题
			filename: 'index.html', //webpack-dev-server在内存中生成的文件名称,自动将build.js注入到这个页面底部
			template: 'index1.html' //更具index1.html这个模板生成(这个文件由程序员自己定义)
		})
	]
}

  • 在main.js中使用es6语法导入site.css
import '../statics/css/site.css'

各依赖版本:

"devDependencies": {
   
    "babel-core": "^6.26.3",
    "babel-loader": "^6.4.1",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-es2015": "^6.24.1",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.0.4",
    "less-loader": "^4.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "webpack": "^1.14.0",
    "webpack-dev-server": "^1.16.0"
  }

11.6利用webpack解析和打包.vue组件页面

Vue项目中的每个页面其实都是一个.vue的文件,这种文件,Vue称之为组件页面,必须借助于webpack的vue-loader才能使用

所以必须安装相关包:

vue : vuejs核心包

vue-loader : .vue文件编译loader

babel-plugin-transform-runtime : es6实时转成es5语法

1.在项目根目录下打开cmd命令面板,输入:

npm install vue-loader vue-template-compiler babel-plugin-transform-runtime --save-dev

npm install vue --save

2.在webpack.config.js中添加如下配置(只能在webpack1.0中使用):

babel:{
   
    presets:['es2015'],
    plugins:['transform-runtime']//这句话就是为了解决打包.vue文件不报错
}

在webpack2.0中在webpack.config.js文件中添加babel:{}是不认识的要改写如下方式:

在项目根目录下新建.babelrc文件,内容填写如下:

{
   
    presets:['es2015'],
    plugins:['transform-runtime']//这句话就是为了解决打包.vue文件不报错
}

3.在webpack.config.js的loader中增加

{
   
    //打包
    test:/\.vue$/,
    loader:'vue-loader'
}

12 搭建webpack1.14.0+Vue2.6.0的HelloWorld

App.vue文件

<!-- 以后项目的根组件 -->
<template>
	<!-- 主要放html页面结构 -->
	<div>
		<span v-text="message" class="red"></span>
	</div>
</template>

<script>
	// 负责到处.vue这个组件对象(它的本质是一个vue对象,所以vue中该定义的元素都可以使用)
	export default{
   
		data(){
   //等价于es5的data:function(){}c
			return {
   
				message:'Hello VueJs!!'
			}
		},
		methods: {
   
			
		},
		created(){
   
			
		}
	}
</script>

<style scoped>
	/* 当前页面的css样式写到这里,其中scoped表示这个里面的css代码只是在当前组件页面有效,不会去影响到其他组件页面 */
	.red{
   
		color:red;
	}
</style>

main.js文件

//导入vue核心包
import Vue from 'vue';

//导入App.vue的vue对象
import App from './App.vue';

//利用vue对象进行渲染
new Vue({
   
	el:'#app',
// 	render:function(create){
   
// 		create(App)
// 	}es5写法
	render:create=>create(App) //es6箭头函数写法也称goes to 语法,左边参数右边方法体
});

index.html模板index1.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title>首页</title>
	</head>
	<body>
		<div id="app">
			
		</div>
	</body>
</html>


web.config.js文件

var htmlwp = require('html-webpack-plugin');

module.exports = {
   
	entry: './src/main.js', //指定打包的入口文件
	output: {
    //配置输出
		path: __dirname + '/dist', //注意:webpack1.14.0要求这个路径是一个绝对路径
		filename: 'build.js'
	},
	module: {
   
		loaders: [{
   
				test: /\.css$/, //以css结尾的文件
				loader: 'style-loader!css-loader' //使用!作为分隔符
			},
			{
   
				test: /\.less$/, //以less结尾的文件
				loader: 'style-loader!css-loader!less-loader'
			},
			{
   
				test: /\.(png|jpe?g|gif|svg|ttf|bmp)(\?.*)?$/,
				loader: 'url-loader',
				options: {
   
					limit: 1000,
					name: 'img/[name].[hash:7].[ext]'
				}
			},
			{
   
				//将当前项目中所有的.js文件都要进行es6转es5操作,node_moudels除外
				test: /\.js$/, //表示当前打包的文件后缀的正则表达式
				//loader:'babel-loader?presets[]=es2015',//如果写到这里将来在打包.vue文件的时候会报错,表示先利用css-loader解析.css文件,在调用style-loader打包
				loader: 'babel-loader',
				exclude: /node_modules/ //node_modules中的所有.js文件不去转换就是排除,提高打包性能
			},
			{
   
				//打包
				test: /\.vue$/,
				loader: 'vue-loader'
			}
		]
	},
	babel: {
   
		presets: ['es2015'],
		plugins: ['transform-runtime'] //这句代码就是为了解决打包.vue文件不报错
	},
	plugins: [
		new htmlwp({
   
			title: '首页', //生成的页面标题
			filename: 'index.html', //webpack-dev-server在内存中生成的文件名称,自动将build.js注入到这个页面底部
			template: 'index1.html' //更具index1.html这个模板生成(这个文件由程序员自己定义)
		})
	]
}

package.json文件

{
   
  "name": "package",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "scripts": {
   
    "dev": "webpack-dev-server --inline --hot --open --port 4009"
  },
  "author": "guqing",
  "license": "ISC",
  "dependencies": {
   
    "vue": "^2.6.4"
  },
  "devDependencies": {
   
    "babel-core": "^6.26.3",
    "babel-loader": "^6.4.1",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-es2015": "^6.24.1",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.0.4",
    "less-loader": "^4.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "vue-loader": "^10.0.2",
    "vue-template-compiler": "^2.6.4",
    "webpack": "^1.14.0",
    "webpack-dev-server": "^1.16.0"
  }
}

13 项目中使用的ECMAScript6语法

  • 1.对象的写法
es5中对象:
{
   
    add:add,
    substrict:substrict
}

es6中对象:
{
   
    add,
    substrict
}
注意这种写法的属性名和值变量是同一个名称才可以简写,否则要写成es5那样

  • 2.在对象中的方法写法
es5中对象:
{
   
    add:function(){
   },
    substrict:function(){
   }
}

es6中对象:
{
   
    add(){
   },
    substrict(){
   }
}

3.对象的到出写法

es5两种形式:
module.exports = function(){
   }
exports.add = function(){
   }

es6中写法:
1.export defaut{
   
	add(){
   }
}
2.export function add(){
   }//相当于将add方法当作一个属性挂载到exports对象

4.对象的导入

es5:
var add = require('./calc.js');
es6:
如果导出的是:
export default{
   
    add(){
   }
}
那么可以通过
import obj from './calc.js'导入

如果到处的是:
export function add(){
   }
export function substrict(){
   }
export const PI=3.14
那么可以通过按需加载
import {
   add,substrict,PI} from './calc.js'

5.es6中的箭头函数的写法

箭头的演变过程:
[2,1,3].sort(function(x,y){
   return y-x;})

用箭头函数实现=>读 goes to
[2,1,3].sort((x,y)=>{
   return y-x});
[2,1,3].sort((x,y)=>{
   return x++;y++;y-x});
[2,1,3].forEach(x=>{
   console.log(x)});

14 webpack项目中集成vue-router步骤

1.安装vue-router:

npm install vue-router --save

2.在webpack打包入口js文件中(entry指定的哪个文件main.js)配置如下:

//1.导入vue核心包
import Vue from 'vue';

//2.导入App.vue的vue对象
import App from './App.vue';

//3.导入路由vue-router
import VueRouter from 'vue-router';

//4.将VueRouter绑定到Vue对象上
Vue.use(VueRouter);

//6.导入路由规则需要的组件对象
import login from './components/account/login.vue';
import regist from './components/account/regist.vue';

//5.定义路由规则
var router = new VueRouter({
   
	routes:[//重点在这里是routes而不是routers没有r,否则<router-view>标签是真的不会显示的
		{
   path:'/login',component:login},
		{
   path:'/regist',component:regist}
	]
})

//利用vue对象进行渲染
new Vue({
   
	el:'#app',
	//使用路由对象实例
	router,
// 	render:function(create){
   
// 		create(App)
// 	}es5写法
	render:create=>create(App) //es6箭头函数写法也称goes to 语法,左边参数右边方法体
});

App.vue文件

<!-- 以后项目的根组件 -->
<template>
	<!-- 主要放html页面结构 -->
	<div>
		<!-- <span v-text="message" class="red"></span> -->
		<router-link to="/login">登陆</router-link>
		<span>|</span>
		<router-link to="/regist">|注册</router-link>
		
		<!-- 路由占位符 -->
		<router-view></router-view>
	</div>
</template>

<script>
	// 负责到处.vue这个组件对象(它的本质是一个vue对象,所以vue中该定义的元素都可以使用)
	export default{
   
		data(){
   //等价于es5的data:function(){}c
			return {
   
				message:'Hello VueJs!!'
			}
		},
		methods: {
   
			show
		},
		created(){
   
			
		}
	}
	function show(){
   
		alert("你看见我了");
	}
</script>

<style scoped>
	/* 当前页面的css样式写到这里,其中scoped表示这个里面的css代码只是在当前组件页面有效,不会去影响到其他组件页面 */
	.red{
   
		color:red;
	}
</style>


在component/account/下写login.vue文件

<template>
	<div>
		<h1>登陆组件页面</h1>
	</div>
</template>

<script>
	export default{
   
		
	}
</script>

<style scoped>
	
</style>


在component/account/下写regist.vue文件

<template>
	<div>
		<h1>注册组件页面</h1>
	</div>
</template>

<script>
	export default{
   
		
	}
</script>

<style scoped="scoped">
	
</style>


15Vue移动组件mint-ui使用

vue拥有很多的第三方开发的PC端或者移动端UI组件,此项目中主要用到了Vue移动端组件:mint-ui

类似的移动端组件还有:

  • 1.vux
  • 2.淘宝团队开发的:SUI
  • 3.muse-ui

PC端组件:

  • 1.饿了么团队开发的:element
http://element-cn.eleme.io/#/zh-CN/component/collapse
  • 2.iView是一套基于Vue.js的开源UI组件库,主要用于服务PC界面中的后台产品

mint-ui资源介绍

下载地址:

https://github.com/ElemeFE/mint-ui

2.官网:

http://mint-ui.github.io/#!/zh-cn

3.文档地址:

http://mint-ui.github.io/docs/#/

安装mint-ui集成到项目中

1.安装:

cnpm install mint-ui --save #命令将mint-ui安装到项目中

2.在main.js中全局导入mint-ui和它的css后即可在任何组件中使用mint-ui组件了

//6导入mint-ui的css文件
import 'mint-ui/lib/style.min.css'

//7.导入mint-ui组件对象
import Mint from 'mint-ui'

//8在Vue中使用Mint
Vue.use(Mint)

16.MUI介绍

官网

http://dev.dcloud.net.cn/mui/

文档地址

http://dev.dcloud.net.cn/mui/ui/

github地址

https://github.com/dcloudio/mui

直接使用git克隆项目到本地然后查看

mui/examples/hello-mui

目录中的实例,复制代码使用,dist目录中是需要的js和css

使用时在入口文件中导入

import '../statics/mui/css/mui.css';

使用:例如使用mui的九宫格样式

<!-- 九宫格 -->
<div class="mui-content gridview">
	<ul class="mui-table-view mui-grid-view mui-grid-9">
		<li class="mui-table-view-cell mui-media mui-col-xs-2 mui-col-sm-5" v-for="item in gridviews" :key="item.id">
			<a href="#">
				<!-- <span class="mui-icon mui-icon-home"></span> -->
				<img class="gridIcon" :src="item.icon" width="34px" height="34px">
				<div class="mui-media-body">{
   {
   item.title}}</div>
			</a>
		</li>
	</ul>
</div>

Vue练手项目地址

https://github.com/guqing/creek

https://github.com/guqing/creek-ui

转载:https://blog.csdn.net/qq_22174779/article/details/87907036
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场