文章目录
day02Vue常用特性表单基本操作表单修饰符自定义指令Vue.directive 注册全局指令Vue.directive 注册全局指令 带参数自定义指令局部指令计算属性 computed侦听器 watch侦听器案例过滤器过滤器中传递参数带参数的过滤器案例生命周期常用的钩子函数数组变异方法替换数组动态数组响应式数据图书列表案例1、 提供的静态数据2、 把提供好的数据渲染到页面上3、 添加图书4 修改图书-上5 修改图书-下6 删除图书完整代码常用特性应用场景1 过滤器2 自定义指令3 计算属性生命周期每日作业-Vue第02天TODOS案例题目描述训练目标训练提示操作步骤每日作业-Vue第02天参考答案todos案例1 提供的数据和 HTML结构2 把数据渲染到页面上3 把类名是 new-todo 按回车键的时候 把输入框中的数据展示到页面上4. 实现全选功能5 实现删除功能6 实现编辑功能7 Clear completed**8** number item left完整代码基础版增强版base.cssindex.cssapp.jsday02
Vue常用特性
表单基本操作
获取单选框中的值
通过v-model
<!-- 1、 两个单选框需要同时通过v-model 双向绑定 一个值 2、 每一个单选框必须要有value属性 且value 值不能一样 3、 当某一个单选框选中的时候 v-model 会将当前的 value值 改变 data 中的 数据gender 的值就是选中的值,我们只需要实时监控他的值就可以了--><input type="radio" id="male" value="1" v-model='gender'><label for="male">男</label><input type="radio" id="female" value="2" v-model='gender'><label for="female">女</label><script>new Vue({data: {// 默认会让当前的 value 值为 2 的单选框选中gender: 2, },})</script>
获取复选框中的值
通过v-model和获取单选框中的值一样复选框checkbox
这种的组合时 data 中的 hobby 我们要定义成数组 否则无法实现多选
<!-- 1、 复选框需要同时通过v-model 双向绑定 一个值 2、 每一个复选框必须要有value属性 且value 值不能一样 3、 当某一个单选框选中的时候 v-model 会将当前的 value值 改变 data 中的 数据hobby 的值就是选中的值,我们只需要实时监控他的值就可以了--><div><span>爱好:</span><input type="checkbox" id="ball" value="1" v-model='hobby'><label for="ball">篮球</label><input type="checkbox" id="sing" value="2" v-model='hobby'><label for="sing">唱歌</label><input type="checkbox" id="code" value="3" v-model='hobby'><label for="code">写代码</label></div><script>new Vue({data: {// 默认会让当前的 value 值为 2 和 3 的复选框选中hobby: ['2', '3'],},})</script>
获取下拉框和文本框中的值
通过v-model
<div><span>职业:</span><!--1、 需要给select 通过v-model 双向绑定 一个值 2、 每一个option 必须要有value属性 且value 值不能一样 3、 当某一个option选中的时候 v-model 会将当前的 value值 改变 data 中的 数据occupation 的值就是选中的值,我们只需要实时监控他的值就可以了--><!-- multiple 多选 --><select v-model='occupation' multiple><option value="0">请选择职业...</option><option value="1">教师</option><option value="2">软件工程师</option><option value="3">律师</option></select><!-- textarea 是 一个双标签 不需要绑定value 属性的 --><textarea v-model='desc'></textarea></div><script>new Vue({data: {// 默认会让当前的 value 值为 2 和 3 的下拉框选中occupation: ['2', '3'],desc: 'nihao'},})</script>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title><style type="text/css">form div {height: 40px;line-height: 40px;}form div:nth-child(4) {height: auto;}form div span:first-child {display: inline-block;width: 100px;}</style></head><body><div id="app"><form action=""><div><span>姓名:</span><span><input type="text" v-model='uname'></span></div><div><span>性别:</span><span><input type="radio" id="male" value="1" v-model='gender'><label for="male">男</label><input type="radio" id="female" value="2" v-model='gender'><label for="female">女</label></span></div><div><span>爱好:</span><input type="checkbox" id="ball" value="1" v-model='hobby'><label for="ball">篮球</label><input type="checkbox" id="sing" value="2" v-model='hobby'><label for="sing">唱歌</label><input type="checkbox" id="code" value="3" v-model='hobby'><label for="code">写代码</label></div><div><span>职业:</span><select v-model='occupation' multiple><option value="0">请选择职业...</option><option value="1">教师</option><option value="2">软件工程师</option><option value="3">律师</option></select></div><div><span>个人简介:</span><textarea v-model='desc'></textarea></div><div><input type="submit" value="提交" @click.prevent='handle'></div></form></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*表单基本操作*/var vm = new Vue({el: '#app',data: {uname: 'lisi',gender: 2,hobby: ['2','3'],// occupation: 3occupation: ['2','3'],desc: 'nihao'},methods: {handle: function(){// console.log(this.uname)// console.log(this.gender)// console.log(this.hobby.toString())// console.log(this.occupation)console.log(this.desc)}}});</script></body></html>
表单修饰符
.number 转换为数值
注意点:当开始输入非数字的字符串时,因为Vue无法将字符串转换成数值所以属性值将实时更新成相同的字符串。即使后面输入数字,也将被视作字符串。
.trim 自动过滤用户输入的首尾空白字符
只能去掉首尾的 不能去除中间的空格
.lazy 将input事件切换成change事件
.lazy 修饰符延迟了同步更新属性值的时机。即将原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上
在失去焦点 或者 按下回车键时才更新
<!-- 自动将用户的输入值转为数值类型 --><input v-model.number="age" type="number"><!--自动过滤用户输入的首尾空白字符 --><input v-model.trim="msg"><!-- 在“change”时而非“input”时更新 --><input v-model.lazy="msg" >
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><div id="app"><input type="text" v-model.number='age'><input type="text" v-model.trim='info'><input type="text" v-model.lazy='msg'><div>{{msg}}</div><button @click='handle'>点击</button></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*表单域修饰符*/var vm = new Vue({el: '#app',data: {age: '',info: '',msg: ''},methods: {handle: function(){// console.log(this.age + 13)// console.log(this.info.length)}}});</script></body></html>
自定义指令
内置指令不能满足我们特殊的需求Vue允许我们自定义指令Vue.directive 注册全局指令
<!-- 使用自定义的指令,只需在对用的元素中,加上'v-'的前缀形成类似于内部指令'v-if','v-text'的形式。 --><input type="text" v-focus><script>// 注意点: // 1、 在自定义指令中 如果以驼峰命名的方式定义 如 Vue.directive('focusA',function(){}) // 2、 在HTML中使用的时候 只能通过 v-focus-a 来使用 // 注册一个全局自定义指令 v-focusVue.directive('focus', {// 当绑定元素插入到 DOM 中。 其中 el为dom元素inserted: function (el) {// 聚焦元素el.focus();}});new Vue({el:'#app'});</script>
Vue.directive 注册全局指令 带参数
<input type="text" v-color='msg'><script type="text/javascript">/*自定义指令-带参数bind - 只调用一次,在指令第一次绑定到元素上时候调用*/Vue.directive('color', {// bind声明周期, 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置// el 为当前自定义指令的DOM元素 // binding 为自定义的函数形参 通过自定义属性传递过来的值 存在 binding.value 里面bind: function(el, binding){// 根据指令的参数设置背景色// console.log(binding.value.color)el.style.backgroundColor = binding.value.color;}});var vm = new Vue({el: '#app',data: {msg: {color: 'blue'}}});</script>
自定义指令局部指令
局部指令,需要定义在 directives 的选项 用法和全局用法一样局部指令只能在当前组件里面使用当全局指令和局部指令同名时以局部指令为准<input type="text" v-color='msg'><input type="text" v-focus><script type="text/javascript">/*自定义指令-局部指令*/var vm = new Vue({el: '#app',data: {msg: {color: 'red'}},//局部指令,需要定义在 directives 的选项directives: {color: {bind: function(el, binding){el.style.backgroundColor = binding.value.color;}},focus: {inserted: function(el) {el.focus();}}}});</script>
计算属性 computed
模板中放入太多的逻辑会让模板过重且难以维护 使用计算属性可以让模板更加的简洁计算属性是基于它们的响应式依赖进行缓存的computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化<div id="app"><!-- 当多次调用 reverseString 的时候 只要里面的 num 值不改变 他会把第一次计算的结果直接返回直到data 中的num值改变 计算属性才会重新发生计算--><div>{{reverseString}}</div><div>{{reverseString}}</div><!-- 调用methods中的方法的时候 他每次会重新调用 --><div>{{reverseMessage()}}</div><div>{{reverseMessage()}}</div></div><script type="text/javascript">/*计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存*/var vm = new Vue({el: '#app',data: {msg: 'Nihao',num: 100},methods: {reverseMessage: function(){console.log('methods')return this.msg.split('').reverse().join('');}},//computed 属性 定义 和 data 已经 methods 平级 computed: {// reverseString 这个是我们自己定义的名字 reverseString: function(){console.log('computed')var total = 0;// 当data 中的 num 的值改变的时候 reverseString 会自动发生计算 for(var i=0;i<=this.num;i++){total += i;}// 这里一定要有return 否则 调用 reverseString 的 时候无法拿到结果 return total;}}});</script>
侦听器 watch
使用watch来响应数据的变化一般用于异步或者开销较大的操作watch 中的属性 一定是data 中 已经存在的数据当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听<div id="app"><div><span>名:</span><span><input type="text" v-model='firstName'></span></div><div><span>姓:</span><span><input type="text" v-model='lastName'></span></div><div>{{fullName}}</div></div><script type="text/javascript">/*侦听器*/var vm = new Vue({el: '#app',data: {firstName: 'Jim',lastName: 'Green',// fullName: 'Jim Green'},//watch 属性 定义 和 data 已经 methods 平级 watch: {// 注意: 这里firstName 对应着data 中的 firstName // 当 firstName 值 改变的时候 会自动触发 watchfirstName: function(val) {this.fullName = val + ' ' + this.lastName;},// 注意: 这里 lastName 对应着data 中的 lastName lastName: function(val) {this.fullName = this.firstName + ' ' + val;}}});</script>
侦听器案例
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><div id="app"><div><span>用户名:</span><span><input type="text" v-model.lazy='uname'></span><span>{{tip}}</span></div></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*侦听器1、采用侦听器监听用户名的变化2、调用后台接口进行验证3、根据验证的结果调整提示信息*/var vm = new Vue({el: '#app',data: {uname: '',tip: ''},methods: {checkName: function(uname) {// 调用接口,但是可以使用定时任务的方式模拟接口调用var that = this;setTimeout(function(){// 模拟接口调用if(uname == 'admin') {that.tip = '用户名已经存在,请更换一个';}else{that.tip = '用户名可以使用';}}, 2000);}},watch: {uname: function(val){// 调用后台接口验证用户名的合法性this.checkName(val);// 修改提示信息this.tip = '正在验证...';}}});</script></body></html>
过滤器
Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和v-bind表达式。过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示支持级联操作过滤器不改变真正的data
,而只是改变渲染的结果,并返回过滤后的版本全局注册时是filter,没有s的。而局部过滤器是filters,是有s的<div id="app"><input type="text" v-model='msg'><!-- upper 被定义为接收单个参数的过滤器函数,表达式 msg 的值将作为参数传入到函数中 --><div>{{msg | upper}}</div><!-- 支持级联操作upper 被定义为接收单个参数的过滤器函数,表达式msg 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器 lower ,将upper 的结果传递到lower中--><div>{{msg | upper | lower}}</div><div :abc='msg | upper'>测试数据</div></div><script type="text/javascript">// lower 为全局过滤器/*过滤器1、可以用与插值表达式和属性绑定2、支持级联操作*/// Vue.filter('upper', function(val) {// return val.charAt(0).toUpperCase() + val.slice(1);// });Vue.filter('lower', function(val) {return val.charAt(0).toLowerCase() + val.slice(1);});var vm = new Vue({el: '#app',data: {msg: ''},//filters 属性 定义 和 data 已经 methods 平级 // 定义filters 中的过滤器为局部过滤器 filters: {// upper 自定义的过滤器名字 // upper 被定义为接收单个参数的过滤器函数,表达式 msg 的值将作为参数传入到函数中upper: function(val) {// 过滤器中一定要有返回值 这样外界使用过滤器的时候才能拿到结果return val.charAt(0).toUpperCase() + val.slice(1);}}});</script>
过滤器中传递参数
<div id="box"><!--filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。-->{{ message | filterA('arg1', 'arg2') }}</div><script>// 在过滤器中 第一个参数 对应的是 管道符前面的数据 n 此时对应 message// 第2个参数 a 对应 实参 arg1 字符串// 第3个参数 b 对应 实参 arg2 字符串Vue.filter('filterA',function(n,a,b){if(n<10){return n+a;}else{return n+b;}});new Vue({el:"#box",data:{message: "哈哈哈"}})</script>
带参数的过滤器案例
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><div id="app"><div>{{date | format('yyyy-MM-dd hh:mm:ss')}}</div></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*过滤器案例:格式化日期*/// Vue.filter('format', function(value, arg) {// if(arg == 'yyyy-MM-dd') {//var ret = '';//ret += value.getFullYear() + '-' + (value.getMonth() + 1) + '-' + value.getDate();//return ret;// }// return value;// })Vue.filter('format', function(value, arg) {function dateFormat(date, format) {if (typeof date === "string") {var mts = date.match(/(\/Date\((\d+)\)\/)/);if (mts && mts.length >= 3) {date = parseInt(mts[2]);}}date = new Date(date);if (!date || date.toUTCString() == "Invalid Date") {return "";}var map = {"M": date.getMonth() + 1, //月份 "d": date.getDate(), //日 "h": date.getHours(), //小时 "m": date.getMinutes(), //分 "s": date.getSeconds(), //秒 "q": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 };format = format.replace(/([yMdhmsqS])+/g, function(all, t) {var v = map[t];if (v !== undefined) {if (all.length > 1) {v = '0' + v;v = v.substr(v.length - 2);}return v;} else if (t === 'y') {return (date.getFullYear() + '').substr(4 - all.length);}return all;});return format;}return dateFormat(value, arg);})var vm = new Vue({el: '#app',data: {date: new Date()}});</script></body></html>
生命周期
事物从出生到死亡的过程Vue实例从创建 到销毁的过程 ,这些过程中会伴随着一些函数的自调用。我们称这些函数为钩子函数常用的钩子函数
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><div id="app"><div>{{msg}}</div><button @click='update'>更新</button><button @click='destroy'>销毁</button></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*Vue实例的生命周期*/var vm = new Vue({el: '#app',data: {msg: '生命周期'},methods: {update: function(){this.msg = 'hello';},destroy: function(){this.$destroy();}},beforeCreate: function(){console.log('beforeCreate');},created: function(){console.log('created');},beforeMount: function(){console.log('beforeMount');},mounted: function(){console.log('mounted');},beforeUpdate: function(){console.log('beforeUpdate');},updated: function(){console.log('updated');},beforeDestroy: function(){console.log('beforeDestroy');},destroyed: function(){console.log('destroyed');}});</script></body></html>
数组变异方法
在 Vue 中,直接修改对象属性的值无法触发响应式。当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变变异数组方法即保持数组方法原有功能不变的前提下对其进行功能拓展替换数组
不会改变原始数组,但总是返回一个新数组<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><div id="app"><div><span><input type="text" v-model='fname'><button @click='add'>添加</button><button @click='del'>删除</button><button @click='change'>替换</button></span></div><ul><li :key='index' v-for='(item,index) in list'>{{item}}</li></ul></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*Vue数组操作1、变异方法:会影响数组的原始数据的变化。2、替换数组:不会影响原始的数组数据,而是形成一个新的数组。*/var vm = new Vue({el: '#app',data: {fname: '',list: ['apple','orange','banana']},methods: {add: function(){this.list.push(this.fname);},del: function(){this.list.pop();},change: function(){this.list = this.list.slice(0,2);}}});</script></body></html>
动态数组响应式数据
Vue.set(a,b,c) 让 触发视图重新更新一遍,数据动态起来a是要更改的数据 、 b是数据的第几项、 c是更改后的数据<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><div id="app"><ul><li v-for='item in list'>{{item}}</li></ul><div><div>{{info.name}}</div><div>{{info.age}}</div><div>{{info.gender}}</div></div></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*动态处理响应式数据*/var vm = new Vue({el: '#app',data: {list: ['apple', 'orange', 'banana'],info: {name: 'lisi',age: 12}},});// vm.list[1] = 'lemon';// Vue.set(vm.list, 2, 'lemon');vm.$set(vm.list, 1, 'lemon');// vm.info.gender = 'male';vm.$set(vm.info, 'gender', 'female');</script></body></html>
图书列表案例
静态列表效果基于数据实现模板效果处理每行的操作按钮1、 提供的静态数据
数据存放在vue 中 data 属性中var vm = new Vue({el: '#app',data: {books: [{id: 1,name: '三国演义',date: ''},{id: 2,name: '水浒传',date: ''},{id: 3,name: '红楼梦',date: ''},{id: 4,name: '西游记',date: ''}]}}); var vm = new Vue({el: '#app',data: {books: [{id: 1,name: '三国演义',date: ''},{id: 2,name: '水浒传',date: ''},{id: 3,name: '红楼梦',date: ''},{id: 4,name: '西游记',date: ''}]}});
2、 把提供好的数据渲染到页面上
利用 v-for循环 遍历 books 将每一项数据渲染到对应的数据中<tbody><tr :key='item.id' v-for='item in books'><!-- 对应的id 渲染到页面上 --><td>{{item.id}}</td><!-- 对应的name 渲染到页面上 --><td>{{item.name}}</td><td>{{item.date}}</td><td><!-- 阻止 a 标签的默认跳转 --><a href="" @click.prevent>修改</a><span>|</span><a href="" @click.prevent>删除</a></td></tr></tbody>
3、 添加图书
通过双向绑定获取到输入框中的输入内容给按钮添加点击事件把输入框中的数据存储到 data 中的 books 里面<div><h1>图书管理</h1><div class="book"><div><label for="id">编号:</label><!-- 3.1、通过双向绑定获取到输入框中的输入的 id --><input type="text" id="id" v-model='id'><label for="name">名称:</label><!-- 3.2、通过双向绑定获取到输入框中的输入的 name --><input type="text" id="name" v-model='name'><!-- 3.3、给按钮添加点击事件 --><button @click='handle'>提交</button></div></div></div><script type="text/javascript">/*图书管理-添加图书*/var vm = new Vue({el: '#app',data: {id: '',name: '',books: [{id: 1,name: '三国演义',date: ''},{id: 2,name: '水浒传',date: ''},{id: 3,name: '红楼梦',date: ''},{id: 4,name: '西游记',date: ''}]},methods: {handle: function(){// 3.4 定义一个新的对象book 存储 获取到输入框中 书 的id和名字 var book = {};book.id = this.id;book.name = this.name;book.date = '';// 3.5 把book 通过数组的变异方法 push 放到 books 里面this.books.push(book);//3.6 清空输入框this.id = '';this.name = '';}}});</script>
4 修改图书-上
点击修改按钮的时候 获取到要修改的书籍名单 4.1 给修改按钮添加点击事件, 需要把当前的图书的id 传递过去 这样才知道需要修改的是哪一本书籍 把需要修改的书籍名单填充到表单里面 4.2 根据传递过来的id 查出books 中 对应书籍的详细信息4.3 把获取到的信息填充到表单<div id="app"><div class="grid"><div><h1>图书管理</h1><div class="book"><div><label for="id">编号:</label><input type="text" id="id" v-model='id' :disabled="flag"><label for="name">名称:</label><input type="text" id="name" v-model='name'><button @click='handle'>提交</button></div></div></div><table><thead><tr><th>编号</th><th>名称</th><th>时间</th><th>操作</th></tr></thead><tbody><tr :key='item.id' v-for='item in books'><td>{{item.id}}</td><td>{{item.name}}</td><td>{{item.date}}</td><td><!--- 4.1 给修改按钮添加点击事件, 需要把当前的图书的id 传递过去 这样才知道需要修改的是哪一本书籍---> <a href="" @click.prevent='toEdit(item.id)'>修改</a><span>|</span><a href="" @click.prevent>删除</a></td></tr></tbody></table></div></div><script type="text/javascript">/*图书管理-添加图书*/var vm = new Vue({el: '#app',data: {flag: false,id: '',name: '',books: [{id: 1,name: '三国演义',date: ''},{id: 2,name: '水浒传',date: ''},{id: 3,name: '红楼梦',date: ''},{id: 4,name: '西游记',date: ''}]},methods: {handle: function(){// 3.4 定义一个新的对象book 存储 获取到输入框中 书 的id和名字 var book = {};book.id = this.id;book.name = this.name;book.date = '';// 3.5 把book 通过数组的变异方法 push 放到 books 里面this.books.push(book);//3.6 清空输入框this.id = '';this.name = '';},toEdit: function(id){console.log(id)//4.2 根据传递过来的id 查出books 中 对应书籍的详细信息var book = this.books.filter(function(item){return item.id == id;});console.log(book)//4.3 把获取到的信息填充到表单// this.id 和 this.name 通过双向绑定 绑定到了表单中 一旦数据改变视图自动更新this.id = book[0].id;this.name = book[0].name;}}});</script>
5 修改图书-下
5.1 定义一个标识符, 主要是控制 编辑状态下当前编辑书籍的id 不能被修改 即 处于编辑状态下 当前控制书籍编号的输入框禁用5.2 通过属性绑定给书籍编号的 绑定 disabled 的属性 flag 为 true 即为禁用5.3 flag 默认值为false 处于编辑状态 要把 flag 改为true 即当前表单为禁用5.4 复用添加方法 用户点击提交的时候依然执行 handle 中的逻辑如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据<div id="app"><div class="grid"><div><h1>图书管理</h1><div class="book"><div><label for="id">编号:</label><!-- 5.2 通过属性绑定 绑定 disabled 的属性 flag 为 true 即为禁用--><input type="text" id="id" v-model='id' :disabled="flag"><label for="name">名称:</label><input type="text" id="name" v-model='name'><button @click='handle'>提交</button></div></div></div><table><thead><tr><th>编号</th><th>名称</th><th>时间</th><th>操作</th></tr></thead><tbody><tr :key='item.id' v-for='item in books'><td>{{item.id}}</td><td>{{item.name}}</td><td>{{item.date}}</td><td><a href="" @click.prevent='toEdit(item.id)'>修改</a><span>|</span><a href="" @click.prevent>删除</a></td></tr></tbody></table></div></div> <script type="text/javascript">/*图书管理-添加图书*/var vm = new Vue({el: '#app',data: {// 5.1 定义一个标识符, 主要是控制 编辑状态下当前编辑书籍的id 不能被修改 // 即 处于编辑状态下 当前控制书籍编号的输入框禁用 flag: false,id: '',name: '',},methods: {handle: function() {/*5.4 复用添加方法 用户点击提交的时候依然执行 handle 中的逻辑如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据*/ if (this.flag) {// 编辑图书// 5.5 根据当前的ID去更新数组中对应的数据 this.books.some((item) => {if (item.id == this.id) {// 箭头函数中 this 指向父级作用域的this item.name = this.name;// 完成更新操作之后,需要终止循环return true;}});// 5.6 编辑完数据后表单要处以可以输入的状态this.flag = false;// 5.7 如果 flag为false 表单处于输入状态 此时执行的用户添加数据 } else {var book = {};book.id = this.id;book.name = this.name;book.date = '';this.books.push(book);// 清空表单this.id = '';this.name = '';}// 清空表单this.id = '';this.name = '';},toEdit: function(id) {/*5.3 flag 默认值为false 处于编辑状态 要把 flag 改为true 即当前表单为禁 用 */ this.flag = true;console.log(id)var book = this.books.filter(function(item) {return item.id == id;});console.log(book)this.id = book[0].id;this.name = book[0].name;}}});</script>
6 删除图书
6.1 给删除按钮添加事件 把当前需要删除的书籍id 传递过来6.2 根据id从数组中查找元素的索引6.3 根据索引删除数组元素<tbody><tr :key='item.id' v-for='item in books'><td>{{item.id}}</td><td>{{item.name}}</td><td>{{item.date}}</td><td><a href="" @click.prevent='toEdit(item.id)'>修改</a><span>|</span><!-- 6.1 给删除按钮添加事件 把当前需要删除的书籍id 传递过来 --> <a href="" @click.prevent='deleteBook(item.id)'>删除</a></td></tr></tbody><script type="text/javascript">/*图书管理-添加图书*/var vm = new Vue({methods: {deleteBook: function(id){// 删除图书#// 6.2 根据id从数组中查找元素的索引// var index = this.books.findIndex(function(item){// return item.id == id;// });#// 6.3 根据索引删除数组元素// this.books.splice(index, 1);// -------------------------#// 方法二:通过filter方法进行删除# 6.4 根据filter 方法 过滤出来id 不是要删除书籍的id # 因为 filter 是替换数组不会修改原始数据 所以需要 把 不是要删除书籍的id 赋值给 books this.books = this.books.filter(function(item){return item.id != id;});}}});</script>
完整代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title><style type="text/css">.grid {margin: auto;width: 530px;text-align: center;}.grid table {border-top: 1px solid #C2D89A;width: 100%;border-collapse: collapse;}.grid th,td {padding: 10;border: 1px dashed #F3DCAB;height: 35px;line-height: 35px;}.grid th {background-color: #F3DCAB;}.grid .book {padding-bottom: 10px;padding-top: 5px;background-color: #F3DCAB;}.grid .total {height: 30px;line-height: 30px;background-color: #F3DCAB;border-top: 1px solid #C2D89A;}</style></head><body><div id="app"><div class="grid"><div><h1>图书管理</h1><div class="book"><div><label for="id">编号:</label><input type="text" id="id" v-model='id' :disabled="flag" v-focus><label for="name">名称:</label><input type="text" id="name" v-model='name'><button @click='handle' :disabled="submitFlag">提交</button></div></div></div><div class="total"><span>图书总数:</span><span>{{total}}</span></div><table><thead><tr><th>编号</th><th>名称</th><th>时间</th><th>操作</th></tr></thead><tbody><tr :key='item.id' v-for='item in books'><td>{{item.id}}</td><td>{{item.name}}</td><td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td><td><a href="" @click.prevent='toEdit(item.id)'>修改</a><span>|</span><a href="" @click.prevent='deleteBook(item.id)'>删除</a></td></tr></tbody></table></div></div><script type="text/javascript" src="js/vue.js"></script><script type="text/javascript">/*图书管理-添加图书*/Vue.directive('focus', {inserted: function (el) {el.focus();}});Vue.filter('format', function(value, arg) {function dateFormat(date, format) {if (typeof date === "string") {var mts = date.match(/(\/Date\((\d+)\)\/)/);if (mts && mts.length >= 3) {date = parseInt(mts[2]);}}date = new Date(date);if (!date || date.toUTCString() == "Invalid Date") {return "";}var map = {"M": date.getMonth() + 1, //月份 "d": date.getDate(), //日 "h": date.getHours(), //小时 "m": date.getMinutes(), //分 "s": date.getSeconds(), //秒 "q": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 };format = format.replace(/([yMdhmsqS])+/g, function(all, t) {var v = map[t];if (v !== undefined) {if (all.length > 1) {v = '0' + v;v = v.substr(v.length - 2);}return v;} else if (t === 'y') {return (date.getFullYear() + '').substr(4 - all.length);}return all;});return format;}return dateFormat(value, arg);})var vm = new Vue({el: '#app',data: {flag: false,submitFlag: false,id: '',name: '',books: []},methods: {handle: function(){if(this.flag) {// 编辑图书// 就是根据当前的ID去更新数组中对应的数据this.books.some((item) => {if(item.id == this.id) {item.name = this.name;// 完成更新操作之后,需要终止循环return true;}});this.flag = false;}else{// 添加图书var book = {};book.id = this.id;book.name = this.name;book.date = 2525609975000;this.books.push(book);// 清空表单this.id = '';this.name = '';}// 清空表单this.id = '';this.name = '';},toEdit: function(id){// 禁止修改IDthis.flag = true;console.log(id)// 根据ID查询出要编辑的数据var book = this.books.filter(function(item){return item.id == id;});console.log(book)// 把获取到的信息填充到表单this.id = book[0].id;this.name = book[0].name;},deleteBook: function(id){// 删除图书// 根据id从数组中查找元素的索引// var index = this.books.findIndex(function(item){// return item.id == id;// });// 根据索引删除数组元素// this.books.splice(index, 1);// -------------------------// 方法二:通过filter方法进行删除this.books = this.books.filter(function(item){return item.id != id;});}},computed: {total: function(){// 计算图书的总数return this.books.length;}},watch: {name: function(val) {// 验证图书名称是否已经存在var flag = this.books.some(function(item){return item.name == val;});if(flag) {// 图书名称存在this.submitFlag = true;}else{// 图书名称不存在this.submitFlag = false;}}},mounted: function(){// 该生命周期钩子函数被触发的时候,模板已经可以使用// 一般此时用于获取后台数据,然后把数据填充到模板var data = [{id: 1,name: '三国演义',date: 2525609975000},{id: 2,name: '水浒传',date: 2525609975000},{id: 3,name: '红楼梦',date: 2525609975000},{id: 4,name: '西游记',date: 2525609975000}];this.books = data;}});</script></body></html>
常用特性应用场景
1 过滤器
Vue.filter 定义一个全局过滤器<tr :key='item.id' v-for='item in books'><td>{{item.id}}</td><td>{{item.name}}</td><!-- 1.3 调用过滤器 --><td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td><td><a href="" @click.prevent='toEdit(item.id)'>修改</a><span>|</span><a href="" @click.prevent='deleteBook(item.id)'>删除</a></td></tr><script>#1.1 Vue.filter 定义一个全局过滤器Vue.filter('format', function(value, arg) {function dateFormat(date, format) {if (typeof date === "string") {var mts = date.match(/(\/Date\((\d+)\)\/)/);if (mts && mts.length >= 3) {date = parseInt(mts[2]);}}date = new Date(date);if (!date || date.toUTCString() == "Invalid Date") {return "";}var map = {"M": date.getMonth() + 1, //月份 "d": date.getDate(), //日 "h": date.getHours(), //小时 "m": date.getMinutes(), //分 "s": date.getSeconds(), //秒 "q": Math.floor((date.getMonth() + 3) / 3), //季度 "S": date.getMilliseconds() //毫秒 };format = format.replace(/([yMdhmsqS])+/g, function(all, t) {var v = map[t];if (v !== undefined) {if (all.length > 1) {v = '0' + v;v = v.substr(v.length - 2);}return v;} else if (t === 'y') {return (date.getFullYear() + '').substr(4 - all.length);}return all;});return format;}return dateFormat(value, arg);})#1.2 提供的数据 包含一个时间戳 为毫秒数[{id: 1,name: '三国演义',date: 2525609975000},{id: 2,name: '水浒传',date: 2525609975000},{id: 3,name: '红楼梦',date: 2525609975000},{id: 4,name: '西游记',date: 2525609975000}];</script>
2 自定义指令
让表单自动获取焦点通过Vue.directive 自定义指定<!-- 2.2 通过v-自定义属性名 调用自定义指令 --><input type="text" id="id" v-model='id' :disabled="flag" v-focus><script># 2.1 通过Vue.directive 自定义指定Vue.directive('focus', {inserted: function (el) {el.focus();}});</script>
3 计算属性
通过计算属性计算图书的总数 图书的总数就是计算数组的长度<div class="total"><span>图书总数:</span><!-- 3.2 在页面上 展示出来 --><span>{{total}}</span></div><script type="text/javascript">/*计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存*/var vm = new Vue({data: {flag: false,submitFlag: false,id: '',name: '',books: []},computed: {total: function(){// 3.1 计算图书的总数return this.books.length;}},});</script>
生命周期
每日作业-Vue第02天
TODOS案例
题目描述
当在输入框中输入完内容的按回车键 当前内容展示到页面上点击三角 实现全选和全不选功能点击叉号实现删除功能双击标题实现编辑功能如 点击 SECTION 1 则 内容区域显示 对应 SECTION 1 的内容 同时当前 SECTION的字体颜色变成蓝色训练目标
能够理解vue 中的数据渲染能够理解 v-for, v-if , v-bind , v-click 的使用能够理解 vue 中自定义指令、计算属性、数组变异方法训练提示
提供的数据如下
1、引入todos的CSS样式 (HTML和 CSS 已经写好 我们需要改成Vue动态渲染的
<!-- HTML --><section id="todoapp" class="todoapp"><header class="header"><h1>todos</h1><input placeholder="What needs to be done?" class="new-todo"></header><section class="main"><input id="toggle-all" type="checkbox" class="toggle-all"> <label for="toggle-all">Mark all as complete</label><ul class="todo-list"><li class=""><div class="view"><input type="checkbox" class="toggle"> <label>吃饭</label> <button class="destroy"></button></div> <input class="edit"></li><li class=""><div class="view"><input type="checkbox" class="toggle"><label>睡觉</label> <button class="destroy"></button></div> <input class="edit"></li><li class="completed"><div class="view"><input type="checkbox" class="toggle"> <label>打豆豆</label> <button class="destroy"></button></div> <input class="edit"></li></ul></section><footer class="footer"><span class="todo-count"><strong>2</strong> item left</span><ul class="filters"><li><a href="#/" class="selected">All</a></li><li><a href="#/active">Active</a></li><li><a href="#/completed">Completed</a></li></ul> <button class="clear-completed">Clear completed</button></footer></section><!--- 2CSS --><link rel="stylesheet" href="css/base.css"><link rel="stylesheet" href="css/index.css"><!--- 3、 提供的数据 --><script>new Vue({el: "#todoapp",data: {todos: [{id: 1,title: '吃饭',completed: false}, {id: 2,title: '睡觉',completed: false}, {id: 3,title: '打豆豆',completed: true}]}})</script>
操作步骤
1、 把数据渲染到页面上 根据completeed 的状态动态给li 绑定类名 未完成状态:不需要样式 完成状态: 类名为 completed 编辑状态:类名为 editing如果completed 为 true 则给当前li 添加 completed 2、把类名是 new-todo 按回车键的时候 把输入框中的数据展示到页面上 获取文本框中用户输入的数据 判断数据是否非空 如果是空的,则什么都不做 如果不是空的,则添加到数组中 添加到数组中 清空文本框 3、 实现全选功能 3.1 当点击三角即类名为 toggle-all 的复选框的时候 如果当前三角高亮 即 复选框为选中状态 让当前所有的li 为 完成状态 通过双向绑定获取当前复选框的选中状态 否则为未完成状态 3.2 当点击单个li 里面的复选框的时候 如果当前复选框选中 则当前的状态为完成状态 通过双向绑定获取当前复选框选中状态 通过选中状态动态改变 completed 的值 3.3 如果当前所有的li 都处于完成状态 即 复选框都选中 则上面的 toggle-all 复选框选中 有一个没有选中则当前toggle-all 复选框 处于未选中状态 4、实现删除功能 给类名是 destroy 的按钮添加点击事件点击当前按钮 删除当前按钮所在的 li 5 实现编辑功能 5.1 双击标题的时候 当前li 的类名添加 editing 5.1.1 给当前标题添加双击事件5.1.2 给当前li 添加editing 添加editing后 当前隐藏的输入框会显示出来 5.2 输入框的默认值为当前标题5.3 当用户没有编辑 的时候 按esc退出的时候 数据不发生变化5.4 当用户输入内容按回车键的时候 把标题更新5.5当用户失去焦点的 时候 把输入框中的标题更新 6 Clear completed 点击Clear completed 的时候删除所有的 已完成项 7 number item left 通过计算属性检测当前complete未完成的状态
每日作业-Vue第02天参考答案
todos案例
1 提供的数据和 HTML结构
引入todos的CSS样式 (HTML和 CSS 已经写好 我们需要改成Vue动态渲染的)<!-- HTML --><section id="todoapp" class="todoapp"><header class="header"><h1>todos</h1><input placeholder="What needs to be done?" class="new-todo"></header><section class="main"><input id="toggle-all" type="checkbox" class="toggle-all"> <label for="toggle-all">Mark all as complete</label><ul class="todo-list"><li class=""><div class="view"><input type="checkbox" class="toggle"> <label>吃饭</label> <button class="destroy"></button></div> <input class="edit"></li><li class=""><div class="view"><input type="checkbox" class="toggle"><label>睡觉</label> <button class="destroy"></button></div> <input class="edit"></li><li class="completed"><div class="view"><input type="checkbox" class="toggle"> <label>打豆豆</label> <button class="destroy"></button></div> <input class="edit"></li></ul></section><footer class="footer"><span class="todo-count"><strong>2</strong> item left</span><ul class="filters"><li><a href="#/" class="selected">All</a></li><li><a href="#/active">Active</a></li><li><a href="#/completed">Completed</a></li></ul> <button class="clear-completed">Clear completed</button></footer></section><!--- 2CSS --><link rel="stylesheet" href="css/base.css"><link rel="stylesheet" href="css/index.css"><!--- 3、 提供的数据 --><script>new Vue({el: "#todoapp",data: {todos: [{id: 1,title: '吃饭',completed: false}, {id: 2,title: '睡觉',completed: false}, {id: 3,title: '打豆豆',completed: true}]}})</script>
2 把数据渲染到页面上
根据completeed 的状态动态给li 绑定类名 未完成状态:不需要样式 完成状态: 类名为 completed 编辑状态:类名为 editing如果completed 为 true 则给当前li 添加 completed<li v-for="(item, index) in todos"v-bind:class="{completed: pleted}"><div class="view"><input type="checkbox" class="toggle"> <label>{{item.title}}</label> <button class="destroy"></button></div> <input class="edit"></li>
3 把类名是 new-todo 按回车键的时候 把输入框中的数据展示到页面上
获取文本框中用户输入的数据 判断数据是否非空 如果是空的,则什么都不做 如果不是空的,则添加到数组中 添加到数组中 清空文本框<header class="header"><h1>todos</h1><!--@keydown="addTodo" 麻烦,还需要自己来判断 keyCode@keyup.13="addTodo" 按键修饰符@keyup.enter="addTodo" 修饰符别名,推荐用法--><input class="new-todo" placeholder="What needs to be done?" @keyup.enter="addTodo" ></header><script>new Vue({el: "#todoapp",data: {todos: [{id: 1,title: '吃饭',completed: false}, {id: 2,title: '睡觉',completed: false}, {id: 3,title: '打豆豆',completed: true}]},methods:{addTodo(event) {// 1. 获取文本框中用户输入的数据var todoText = event.target.value.trim()// 2. 判断数据是否非空if (!todoText.length) {return}// 3. 添加到数组中const lastTodo = this.todos[this.todos.length - 1]const id = lastTodo ? lastTodo.id + 1 : 1// 3.1 当数组发生变化,则绑定渲染该数组的视图也会得到更新this.todos.push({id,title: todoText,completed: false})// 4. 清空文本框event.target.value = ''},}}})</script>
4. 实现全选功能
4.1 当点击三角即类名为 toggle-all 的复选框的时候 如果当前三角高亮 即 复选框为选中状态 让当前所有的li 为 完成状态 通过双向绑定获取当前复选框的选中状态 否则为未完成状态 4.2 当点击单个li 里面的复选框的时候 如果当前复选框选中 则当前的状态为完成状态 通过双向绑定获取当前复选框选中状态 通过选中状态动态改变 completed 的值 4.3 如果当前所有的li 都处于完成状态 即 复选框都选中 则上面的 toggle-all 复选框选中 有一个没有选中则当前toggle-all 复选框 处于未选中状态<section class="main"><!-- 4.1 通过双向绑定获取当前复选框的选中状态 ---><input v-model="toggleStat"id="toggle-all" type="checkbox" class="toggle-all"><ul class="todo-list"><li v-for="(item, index) in todos" v-bind:class="{completed: pleted}"><div class="view"><!-- 4.2 当点击单个li 里面的复选框的时候 如果当前复选框选中 则当前的状态为完成状态4.2.1 通过双向绑定获取当前复选框选中状态 通过选中状态动态改变 completed 的值 ---><input type="checkbox" class="toggle" v-model="pleted"><label>{{item.title}}</label><button class="destroy"></button></div><input class="edit"></li></ul></section> <script>new Vue({el: "#todoapp",methods: {addTodo(event) {},// 删除任务项removeTodo(delIndex, event) {this.todos.splice(delIndex, 1)},},computed: {toggleStat: {/*当读取一个变量的时候会触发该变量的getter当修改该变量时候会触发他的setter.*/get() {// 4.3 4.3 如果当前所有的li 都处于完成状态 即 复选框都选中 则上面的 toggle-all 复选框选中 有一个没有选中则当前toggle-all 复选框 处于未选中状态return this.todos.every(item => pleted)},// 当复选框为选中的时候当前 传入的为 true 没有选中传入的为false set(val) {this.todos.forEach(todo => pleted = val)}}}})</script>
5 实现删除功能
给类名是 destroy 的按钮添加点击事件点击当前按钮 删除当前按钮所在的 li<section class="main"><input v-model="toggleStat" id="toggle-all" type="checkbox" class="toggle-all"><label for="toggle-all">Mark all as complete</label><ul class="todo-list"><li v-for="(item, index) in todos" v-bind:class="{completed: pleted}"><div class="view"><input type="checkbox" class="toggle" v-model="pleted"><label>{{item.title}}</label><!-- 5.1 给按钮按钮添加点击事件 --><button class="destroy" @click="removeTodo(index)"></button></div><input class="edit"></li></ul></section><script>new Vue({el: "#todoapp",methods: {addTodo(event) {},// 删除任务项removeTodo(delIndex) {this.todos.splice(delIndex, 1)},},})</script>
6 实现编辑功能
6.1 双击标题的时候 当前li 的类名添加 editing
6.1.1 给当前标题添加双击事件6.1.2 给当前li 添加editing 添加editing后 当前隐藏的输入框会显示出来
6.2 输入框的默认值为当前标题
6.3 当用户没有编辑 的时候 按esc退出的时候 数据不发生变化
6.4 当用户输入内容按回车键的时候 把标题更新
5当用户失去焦点的 时候 把输入框中的标题更新
<section class="main"><input v-model="toggleStat" id="toggle-all" type="checkbox" class="toggle-all"><label for="toggle-all">Mark all as complete</label><ul class="todo-list"><!-- 6.1.1 点击的时候 通过一个标识符 来控制是否给当前li 添加 类名--><li v-for="(item, index) in todos" v-bind:class="{completed: pleted, editing: item === currentEditing}"><div class="view"><input type="checkbox" class="toggle" v-model="pleted"><!-- 6.1.1 给当前标题添加双击事件 点击的时候 通过一个标识符 来控制是否给当前li 添加 类名--><label@dblclick="currentEditing = item">{{item.title}}</label><button class="destroy" @click="removeTodo(index)"></button></div><!-- 6.2 输入框的默认值为当前标题 6.3 当用户没有编辑 的时候 按esc退出的时候 数据不发生变化6.4 当用户输入内容按回车键的时候 把标题更新6.5当用户失去焦点的 时候 把输入框中的标题更新 --><input class="edit" :value="item.title"@keyup.esc="currentEditing = null"@keyup.enter="saveEdit(item, index, $event)"@blur="saveEdit(item, index, $event)"></li></ul></section><script>new Vue({el: "#todoapp",data: {// 6.1.2 标识符默认的为空即一开始加载的时候类名 editing 不加载currentEditing: null,},methods:{// 保存编辑项saveEdit(item, index, event) {// 1. 拿到文本框中的数据// 非空校验// 如果为空,则直接删除这个 item// 如果不为空,则修改任务项的 title 数据var editText = event.target.value.trim()// 程序员要具有工匠精神:优化简写// !editText.length ?// this.todos.splice(index, 1) :// item.title = editTextif (!editText.length) {// 将元素直接从数组中移除return this.todos.splice(index, 1)}// 2. 将数据设置到任务项中item.title = editText// 3. 去除 editing 样式this.currentEditing = null},}})</script>
7 Clear completed
点击Clear completed 的时候删除所有的 已完成项<footer class="footer"><buttonclass="clear-completed"@click="removeAllDone">Clear completed</button></footer><script>new Vue({el: "#todoapp",data: {// 6.1.2 标识符默认的为空即一开始加载的时候类名 editing 不加载currentEditing: null,},methods:{// 删除所有已完成任务项removeAllDone() {// 找到所有已完成的任务项,把其删除。错误的写法// this.todos.forEach((item, index) => {// if (pleted) {//// 已完成//console.log(item.title)//this.todos.splice(index, 1)// }// })// 把所有需要保留的数据过滤出来,然后重新赋值给 todosthis.todos = this.todos.filter(item => !pleted)// 如果想要就在遍历的过程去删除,则可以使用 for 循环// 没删除一个,我们可以控制让索引 --// for (let i = 0; i < this.todos.length; i++) {// if (this.todos[i].completed) {//this.todos.splice(i, 1)//i--// }// }},}})</script>
8number item left
通过计算属性检测当前complete未完成的状态<span class="todo-count"><strong>{{leftCount}}</strong> item left</span><script>new Vue({computed: {leftCount: function() {return this.todos.filter(item => !pleted).length}}})</script>
完整代码
基础版
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title><link rel="stylesheet" href="css/base.css"><link rel="stylesheet" href="css/index.css"></head><body><section id="todoapp" class="todoapp"><header class="header"><h1>todos</h1><input placeholder="What needs to be done?" @keyup.enter="addTodo" class="new-todo"></header><section class="main"><input v-model="toggleStat" id="toggle-all" type="checkbox" class="toggle-all"><label for="toggle-all">Mark all as complete</label><ul class="todo-list"><li v-for="(item, index) in todos" v-bind:class="{completed: pleted, editing: item === currentEditing}"><div class="view"><input type="checkbox" class="toggle" v-model="pleted"><label @dblclick="currentEditing = item">{{item.title}}</label><button class="destroy" @click="removeTodo(index, $event)"></button></div><input class="edit" @keyup.enter="saveEdit(item, index, $event)" :value="item.title" @keyup.esc="currentEditing = null"></li></ul></section><footer class="footer"><span class="todo-count"><strong>{{leftCount}}</strong> item left</span><ul class="filters"><li><a href="#/" class="selected">All</a></li><li><a href="#/active" class="">Active</a></li><li><a href="#/completed" class="">Completed</a></li></ul> <button class="clear-completed">Clear completed</button></footer></section><script src="js/vue.js"></script><script>new Vue({el: "#todoapp",data: {currentEditing: null,todos: [{id: 1,title: '吃饭',completed: false}, {id: 2,title: '睡觉',completed: false}, {id: 3,title: '打豆豆',completed: true}]},methods: {addTodo(event) {// 1. 获取文本框中用户输入的数据// 2. 判断数据是否非空// 如果是空的,则什么都不做// 如果不是空的,则添加到数组中// 3. 添加到数组中// 4. 清空文本框var todoText = event.target.value.trim()if (!todoText.length) {return}const lastTodo = this.todos[this.todos.length - 1]const id = lastTodo ? lastTodo.id + 1 : 1// 当数组发生变化,则绑定渲染该数组的视图也会得到更新this.todos.push({id,title: todoText,completed: false})// 清空文本框event.target.value = ''},// 删除任务项removeTodo(delIndex) {this.todos.splice(delIndex, 1)},// 保存编辑项saveEdit(item, index, event) {// 1. 拿到文本框中的数据// 非空校验// 如果为空,则直接删除这个 item// 如果不为空,则修改任务项的 title 数据var editText = event.target.value.trim()// 程序员要具有工匠精神:优化简写// !editText.length ?// this.todos.splice(index, 1) :// item.title = editTextif (!editText.length) {// 将元素直接从数组中移除return this.todos.splice(index, 1)}// 2. 将数据设置到任务项中item.title = editText// 3. 去除 editing 样式this.currentEditing = null},},computed: {toggleStat: {get() {console.log(2345)return this.todos.every(item => pleted)},set(val) {console.log(678)console.log(val)this.todos.forEach(todo => pleted = val)}},leftCount: function() {return this.todos.filter(item => !pleted).length},}})</script></body></html>
增强版
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Template • TodoMVC</title><link rel="stylesheet" href="css/base.css"><link rel="stylesheet" href="css/index.css"><!-- CSS overrides - remove if you don't need it --><link rel="stylesheet" href="css/app.css"></head><body><section class="todoapp" id="todoapp"><header class="header"><h1>todos</h1><!--@keydown="addTodo" 麻烦,还需要自己来判断 keyCode@keyup.13="addTodo" 按键修饰符@keyup.enter="addTodo" 修饰符别名,推荐用法--><inputclass="new-todo"placeholder="What needs to be done?"@keyup.enter="addTodo"v-focus></header><!--当需要按条件控制渲染多个元素的时候,可以把他们都放到 template 这个特殊的标签中。Vue 会识别的它,在渲染的结果中不会包含 template 这个节点--><template v-if="todos.length"><!-- This section should be hidden by default and shown when there are todos --><section class="main"><inputid="toggle-all"class="toggle-all"type="checkbox"v-model="toggleStat"><label for="toggle-all">Mark all as complete</label><ul class="todo-list"><!-- These are here just to show the structure of the list items --><!-- List items should get the class `editing` when editing and `completed` when marked as completed --><!--未完成状态:不需要样式完成状态:completed编辑状态:editingv-bind:class="{类名: 布尔值}"当布尔值为 true 的时候,则作用这个类名当布尔值为 false 的时候,则去除这个类名任务项双击获得 editing 样式:这里使用一个中间变量,默认为 null ,也就是所有任务项都没有 editing 样式那 editing 的样式取决于:currentEditing 中间变量是否等价于当前任务项所以,当我双击的时候,我就手动把 currentEditing = 当前我双击的任务项那这个时候,样式判定条件 item === currentEditing 就满足了,满足就作用了这个样式。--><liv-bind:class="{completed: pleted, editing: item === currentEditing}"v-for="(item, index) of filterTodos"><div class="view"><inputclass="toggle"type="checkbox"v-model="pleted"><label@dblclick="currentEditing = item">{{ item.title }}</label><buttonclass="destroy"@click="removeTodo(index, $event)"></button></div><inputclass="edit":value="item.title"@keyup.esc="currentEditing = null"@keyup.enter="saveEdit(item, index, $event)"@blur="saveEdit(item, index, $event)"v-editing-focus="item === currentEditing"></li></ul></section><!-- This footer should hidden by default and shown when there are todos --><footer class="footer"><!-- This should be `0 items left` by default --><!--在模板绑定中也可以调用函数,函数的返回值将被渲染到这里。同样的,如果函数中依赖的 data 成员一旦发生变化,则函数会重新计算得到最新结果。也就是说这也是响应式的。严谨一点的说法:绑定函数所在的视图模板如果更新,则该函数也会重新执行。计算属性和方法(返回结果用于模板绑定):都可以达到同样的效果。- 如果是方法,则一旦方法所在的视图发生变化,则方法一定会重新执行- 方法没有缓存,也就是说多次使用该方法,则会重复执行多次- 计算属性是真正的依赖于内部 data 中的数据,如果数据没变,则计算属性不会重新执行- 所以相比来说,计算属性要比方法更为高效一些。--><span class="todo-count"><strong>{{ leftCount }}</strong> item left</span><!-- Remove this if you don't implement routing --><ul class="filters" v-auto-active><li><a :class="{selected: filterStat === 'all'}" href="#/">All</a></li><li><a :class="{selected: filterStat === 'active'}" href="#/active">Active</a></li><li><a :class="{selected: filterStat === 'completed'}" href="#/completed">Completed</a></li></ul><!-- Hidden if no completed items are left ↓ --><buttonclass="clear-completed"@click="removeAllDone">Clear completed</button></footer></template></section><footer class="info"><p>Double-click to edit a todo</p><!-- Remove the below line ↓ --><p>Template by <a href="">Sindre Sorhus</a></p><!-- Change this out with your name and url ↓ --><p>Created by <a href="">you</a></p><p>Part of <a href="">TodoMVC</a></p></footer><!-- Scripts here. Don't remove ↓ --><!-- <script src="node_modules/todomvc-common/base.js"></script> --><script src="js/vue.js"></script><script src="js/app.js"></script></body></html>
base.css
hr {margin: 20px 0;border: 0;border-top: 1px dashed #c5c5c5;border-bottom: 1px dashed #f7f7f7;}.learn a {font-weight: normal;text-decoration: none;color: #b83f45;}.learn a:hover {text-decoration: underline;color: #787e7e;}.learn h3,.learn h4,.learn h5 {margin: 10px 0;font-weight: 500;line-height: 1.2;color: #000;}.learn h3 {font-size: 24px;}.learn h4 {font-size: 18px;}.learn h5 {margin-bottom: 0;font-size: 14px;}.learn ul {padding: 0;margin: 0 0 30px 25px;}.learn li {line-height: 20px;}.learn p {font-size: 15px;font-weight: 300;line-height: 1.3;margin-top: 0;margin-bottom: 0;}#issue-count {display: none;}.quote {border: none;margin: 20px 0 60px 0;}.quote p {font-style: italic;}.quote p:before {content: '“';font-size: 50px;opacity: .15;position: absolute;top: -20px;left: 3px;}.quote p:after {content: '”';font-size: 50px;opacity: .15;position: absolute;bottom: -42px;right: 3px;}.quote footer {position: absolute;bottom: -40px;right: 0;}.quote footer img {border-radius: 3px;}.quote footer a {margin-left: 5px;vertical-align: middle;}.speech-bubble {position: relative;padding: 10px;background: rgba(0, 0, 0, .04);border-radius: 5px;}.speech-bubble:after {content: '';position: absolute;top: 100%;right: 30px;border: 13px solid transparent;border-top-color: rgba(0, 0, 0, .04);}.learn-bar > .learn {position: absolute;width: 272px;top: 8px;left: -300px;padding: 10px;border-radius: 5px;background-color: rgba(255, 255, 255, .6);transition-property: left;transition-duration: 500ms;}@media (min-width: 899px) {.learn-bar {width: auto;padding-left: 300px;}.learn-bar > .learn {left: 8px;}}
index.css
html,body {margin: 0;padding: 0;}button {margin: 0;padding: 0;border: 0;background: none;font-size: 100%;vertical-align: baseline;font-family: inherit;font-weight: inherit;color: inherit;-webkit-appearance: none;appearance: none;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}body {font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;line-height: 1.4em;background: #f5f5f5;color: #4d4d4d;min-width: 230px;max-width: 550px;margin: 0 auto;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;font-weight: 300;}:focus {outline: 0;}.hidden {display: none;}.todoapp {background: #fff;margin: 130px 0 40px 0;position: relative;box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),0 25px 50px 0 rgba(0, 0, 0, 0.1);}.todoapp input::-webkit-input-placeholder {font-style: italic;font-weight: 300;color: #e6e6e6;}.todoapp input::-moz-placeholder {font-style: italic;font-weight: 300;color: #e6e6e6;}.todoapp input::input-placeholder {font-style: italic;font-weight: 300;color: #e6e6e6;}.todoapp h1 {position: absolute;top: -155px;width: 100%;font-size: 100px;font-weight: 100;text-align: center;color: rgba(175, 47, 47, 0.15);-webkit-text-rendering: optimizeLegibility;-moz-text-rendering: optimizeLegibility;text-rendering: optimizeLegibility;}.new-todo,.edit {position: relative;margin: 0;width: 100%;font-size: 24px;font-family: inherit;font-weight: inherit;line-height: 1.4em;border: 0;color: inherit;padding: 6px;border: 1px solid #999;box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);box-sizing: border-box;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}.new-todo {padding: 16px 16px 16px 60px;border: none;background: rgba(0, 0, 0, 0.003);box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);}.main {position: relative;z-index: 2;border-top: 1px solid #e6e6e6;}.toggle-all {text-align: center;border: none; /* Mobile Safari */opacity: 0;position: absolute;}.toggle-all + label {width: 60px;height: 34px;font-size: 0;position: absolute;top: -52px;left: -13px;-webkit-transform: rotate(90deg);transform: rotate(90deg);}.toggle-all + label:before {content: '❯';font-size: 22px;color: #e6e6e6;padding: 10px 27px 10px 27px;}.toggle-all:checked + label:before {color: #737373;}.todo-list {margin: 0;padding: 0;list-style: none;}.todo-list li {position: relative;font-size: 24px;border-bottom: 1px solid #ededed;}.todo-list li:last-child {border-bottom: none;}.todo-list li.editing {border-bottom: none;padding: 0;}.todo-list li.editing .edit {display: block;width: 506px;padding: 12px 16px;margin: 0 0 0 43px;}.todo-list li.editing .view {display: none;}.todo-list li .toggle {text-align: center;width: 40px;/* auto, since non-WebKit browsers doesn't support input styling */height: auto;position: absolute;top: 0;bottom: 0;margin: auto 0;border: none; /* Mobile Safari */-webkit-appearance: none;appearance: none;}.todo-list li .toggle {opacity: 0;}.todo-list li .toggle + label {/*Firefox requires `#` to be escaped - /show_bug.cgi?id=922433IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - /en-us/microsoft-edge/platform/issues/7157459/*/background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A///2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%0%5%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');background-repeat: no-repeat;background-position: center left;}.todo-list li .toggle:checked + label {background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A///2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%0%5%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%%%2034-52z%22/%3E%3C/svg%3E');}.todo-list li label {word-break: break-all;padding: 15px 15px 15px 60px;display: block;line-height: 1.2;transition: color 0.4s;}.todo-list pleted label {color: #d9d9d9;text-decoration: line-through;}.todo-list li .destroy {display: none;position: absolute;top: 0;right: 10px;bottom: 0;width: 40px;height: 40px;margin: auto 0;font-size: 30px;color: #cc9a9a;margin-bottom: 11px;transition: color 0.2s ease-out;}.todo-list li .destroy:hover {color: #af5b5e;}.todo-list li .destroy:after {content: '×';}.todo-list li:hover .destroy {display: block;}.todo-list li .edit {display: none;}.todo-list li.editing:last-child {margin-bottom: -1px;}.footer {color: #777;padding: 10px 15px;height: 20px;text-align: center;border-top: 1px solid #e6e6e6;}.footer:before {content: '';position: absolute;right: 0;bottom: 0;left: 0;height: 50px;overflow: hidden;box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),0 8px 0 -3px #f6f6f6,0 9px 1px -3px rgba(0, 0, 0, 0.2),0 16px 0 -6px #f6f6f6,0 17px 2px -6px rgba(0, 0, 0, 0.2);}.todo-count {float: left;text-align: left;}.todo-count strong {font-weight: 300;}.filters {margin: 0;padding: 0;list-style: none;position: absolute;right: 0;left: 0;}.filters li {display: inline;}.filters li a {color: inherit;margin: 3px;padding: 3px 7px;text-decoration: none;border: 1px solid transparent;border-radius: 3px;}.filters li a:hover {border-color: rgba(175, 47, 47, 0.1);}.filters li a.selected {border-color: rgba(175, 47, 47, 0.2);}.clear-completed,html .clear-completed:active {float: right;position: relative;line-height: 20px;text-decoration: none;cursor: pointer;}.clear-completed:hover {text-decoration: underline;}.info {margin: 65px auto 0;color: #bfbfbf;font-size: 10px;text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);text-align: center;}.info p {line-height: 1;}.info a {color: inherit;text-decoration: none;font-weight: 400;}.info a:hover {text-decoration: underline;}/*Hack to remove background from Mobile Safari.Can't use it globally since it destroys checkboxes in Firefox*/@media screen and (-webkit-min-device-pixel-ratio:0) {.toggle-all,.todo-list li .toggle {background: none;}.todo-list li .toggle {height: 40px;}}@media (max-width: 430px) {.footer {height: 50px;}.filters {bottom: 10px;}}
app.js
/*** Vue* MVVM 框架* 这个思想的哲学就是:数据驱动视图* 把需要改变视图的数据都初始化到 Vue 中* 然后我们就可以通过修改 Vue 中的数据,从而实现对视图的更新*/;((Vue) => {const todos = [{id: 1,title: '吃饭',completed: false},{id: 2,title: '睡觉',completed: false},{id: 3,title: '打豆豆',completed: true}]// 什么时候需要封装自定义指令:// 当你需要进行一些底层 DOM 操作的时候,使用自定义指令更为方便// 注册一个全局自定义指令 `v-focus`// 第一个参数:指令名称,focus// 使用的时候,必须加上 v- 前缀来使用// 后面会解释为什么一个简简单单的自动获得焦点都需要封装一个自定义指令// Vue 带来便利的同时也会有一些让人头疼的问题,所以在软件开发中,有这么一句话:没有银弹Vue.directive('focus', {// 当被绑定的元素插入到 DOM 中时……inserted: function (el) {// 聚焦元素// el.focus()// el 就是作用了 v-focus 的 DOM 元素console.log(el)el.focus()}})// 在 Vue 中,不要出现这样的代码// document.getElementById('xx')Vue.directive('auto-active', {// 当被绑定的元素插入到 DOM 中时……// 这里就是纯原生 DOM 操作inserted: function (el) {// 聚焦元素// el.focus()// el 就是作用了 v-focus 的 DOM 元素// ul a// 给 a 注册点击事件var links = el.getElementsByTagName('a')Array.from(links).forEach(function (link) {link.onclick = function () {console.log(this)}})}})window.app = new Vue({el: '#todoapp',data: {// EcmaScript 6 对象属性简写方式// 当 key 和 值的名字一样的时候,可以省略简写// 下面的情况等价于 todos: todostodos,filterStat: 'all',currentEditing: null,toggleAllStat: true,// 这里不要使用 this 因为 this 指向的是 window// 所以这里无法实现你想要的结果// leftCount: this.todos.filter(item => !pleted).length},computed: {// 该属性比较特殊,从代码来看是一个方法,但是只能当做属性来使用// 也就是说,在模板中不能调用它,只能当做属性来使用它leftCount: function () {return this.todos.filter(item => !pleted).length},filterTodos: function () {switch (this.filterStat) {case 'active':return this.todos.filter(item => !pleted)breakcase 'completed':return this.todos.filter(item => pleted)breakdefault:return this.todosbreak}},// 使用计算属性的方式处理全选的联动效果toggleStat: {get() {return this.todos.every(item => pleted)},set (val) {this.todos.forEach(todo => pleted = val)}}},methods: {// EcmaScript 6 对象属性函数的简写方式// 等价于:addTodo: function () {}// 它没有任何特殊的特性,仅仅是简写了而已addTodo(event) {// 1. 获取文本框中用户输入的数据// 2. 判断数据是否非空// 如果是空的,则什么都不做// 如果不是空的,则添加到数组中// 3. 添加到数组中// 4. 清空文本框var todoText = event.target.value.trim()if (!todoText.length) {return}const lastTodo = this.todos[this.todos.length - 1]const id = lastTodo ? lastTodo.id + 1 : 1// 当数组发生变化,则绑定渲染该数组的视图也会得到更新this.todos.push({id,title: todoText,completed: false})// 清空文本框event.target.value = ''},toggleAll(event) {// 获取点击的 checkbox 的选中状态var checked = event.target.checked// 遍历数组中的所有元素,把每个元素的 completed 都设置为当前点击的 checkbox 的状态// 由于我们使用 v-model 为每一个任务项的完成状态都绑定了 completed// 所以当数据发生变化的时候,任务项的完成状态也会跟着变化this.todos.forEach(todo => pleted = checked)},// 删除任务项removeTodo(delIndex, event) {this.todos.splice(delIndex, 1)},// 删除所有已完成任务项removeAllDone() {// 找到所有已完成的任务项,把其删除。错误的写法// this.todos.forEach((item, index) => {// if (pleted) {//// 已完成//console.log(item.title)//this.todos.splice(index, 1)// }// })// 把所有需要保留的数据过滤出来,然后重新赋值给 todosthis.todos = this.todos.filter(item => !pleted)// 如果想要就在遍历的过程去删除,则可以使用 for 循环// 没删除一个,我们可以控制让索引 --// for (let i = 0; i < this.todos.length; i++) {// if (this.todos[i].completed) {//this.todos.splice(i, 1)//i--// }// }},// 方法也可以用于模板绑定// 在模板中调用方法,方法的返回值将会渲染到绑定的位置getLeftCount() {console.log('方法被调用了')return this.todos.filter(item => !pleted).length},// 保存编辑项saveEdit(item, index, event) {// 1. 拿到文本框中的数据// 非空校验// 如果为空,则直接删除这个 item// 如果不为空,则修改任务项的 title 数据var editText = event.target.value.trim()// 程序员要具有工匠精神:优化简写// !editText.length ?// this.todos.splice(index, 1) :// item.title = editTextif (!editText.length) {// 将元素直接从数组中移除return this.todos.splice(index, 1)}// 2. 将数据设置到任务项中item.title = editText// 3. 去除 editing 样式this.currentEditing = null},// 状态切换(先不要使用)// toggle (item, event) {// // console.log(pleted) // false// // 之前这里没有这个问题,应该 Vue 更新了新的机制了// // 声明周期、模板更新的问题// // 这里的解决方案就是把你的代码放到 Vue.nextTick() 的回调函数中// Vue.nextTick(() => {//this.toggleAllStat = this.todos.every(item => pleted)// })// }},// 配置局部自定义指令directives: {// 当作用了该指令的元素所在模板发生更新的时候,则这个 update 钩子会自动调用editingFocus: {// 在指令钩子中,函数内部的 this 是 windowupdate(el, binding) {if (binding.value) {el.focus()}}}}})window.onhashchange = function () {// 得到点击的路由 hashvar hash = window.location.hash.substr(2) || 'all'// 设置到程序中的过滤状态// 过滤状态一旦改变,则计算属性会感知到这个 filterStat 变了// 当它感知到 filterStat 变了之后,就会重新计算执行// 当 filterTodos 重新计算执行之后,数据得到了更新,则自动同步更新到视图中window.app.filterStat = hash}// 页面第一次进来,执行一次window.onhashchange()})(Vue)
【15】Vue:02-Vue表单基本操作 表单修饰符 自定义指令 计算属性computed 侦听器watch 过滤器 生命周期 数组变异方法 替换数组 动态数组响应数据 图书列表案例 TODOS案例