一、项目介绍
①地址:http://todomvc.com/
②GitHub下载模板
③通过npm下载模板的样式
④通过npm下载Vuejs
⑤项目文件,主要修改app.js和index.html两个文件
二、使用Vuejs需求实现(主体思路)
①列表渲染
- 有数据的时候展示出来:v-if 的使用
(function (Vue) { let todos=[ {id:1,title:'睡觉',completed:true}, {id:2,title:'美食',completed:false}, {id:3,title:'代码',completed:true} ] new Vue({ el:'#todoapp', data:{ todos:todos, }, })(Vue);
<li v-for="item of todos" v-bind:class='{completed:item.completed}'> <div class="view"> <input class="toggle" type="checkbox" v-model='item.completed'> <label>{{item.title}}</label> <button class="destroy"></button> </div> <input class="edit" value="Create a TodoMVC template"> </li>
- 没有数据的时候隐藏main部分:添加一个不会出现在页面的template模板,并且使用v-if,当todos没有数据的时候,长度为0
<template v-if='todos.length'> <!-- This section should be hidden by default and shown when there are todos --> <section class="main"> ..... </section> <!-- This footer should hidden by default and shown when there are todos --> <footer class="footer"> .... </footer> </template>
②添加任务
- 页面初始化获得焦点:自定义指令,注册一个全局自定义指令 `v-focus`,然后在input里直接使用
// 自定义指令,自动获取焦点 Vue.directive('focus', { inserted: function (el) { el.focus(); } });
<input class="new-todo" placeholder="What needs to be done?" @keyup.enter='addTodo' v-focus>
- 敲回车添加到任务列表:鼠标抬起注册addTodo事件,追加数据
- 不允许有非空数据:为空时,return返回
- 添加完成后清空文本框:令event.target.value= ' '
<header class="header"> <h1>todos</h1> <input class="new-todo" placeholder="What needs to be done?" @keyup.enter='addTodo'> </header>
methods:{ // 添加任务 addTodo(event){ let todoText=event.target.value.trim(); if(!todoText.length){ return } let id=this.todos[this.todos.length-1].id+1; this.todos.push({ id:id, title:todoText, completed:false, }); event.target.value=''; },
③标记所有任务完成或者未完成:点击的时候注册toggleAll事件处理函数
<input @click='toggleAll' id="toggle-all" class="toggle-all" type="checkbox">
toggleAll(event){ let checked=event.target.checked; this.todos.forEach(todo => todo.completed=checked); },
④任务项
- 切换任务完成状态:v-bind绑定一个class=“{类名:布尔值}”,当布尔值为true,作用这个类名,当布尔值为false,则去除这个类名
<li v-for="item of todos" v-bind:class='{completed:item.completed}'>
- 删除单个任务项:@click=‘ removeTodo(index,$event) ’ ,传入两个参数,删除的索引index和事件$event(传参以后,正常的event获取不到),然后处理函数利用数组方法splice操作
<button class="destroy" @click='removeTodo(index,$event)' ></button>
removeTodo(delIndex,event){ this.todos.splice(delIndex,1); },
- 双击label进入编辑模式:这里使用一个中间变量currentEditing,默认为null,也就是所有的任务项都没有editing样式,editing的样式取决于中间变量是否等价于当前任务项,当双击的时候,手动把中间量等于双击的当前任务项,这样editing样式就为true,也就是起作用了。
<li v-for="(item,index) of todos" v-bind:class='{completed:item.completed,editing:item===currentEditing}'>
<label @dblclick="currentEditing=item">{{item.title}}</label>
data:{ todos:todos, currentEditing:null,
⑤编辑任务项
- 编辑文本框自动获得焦点:局部自定义指令,自动获取焦点‘ editing-focus ’
<input class="edit" :value='item.title' @blur='saveEdit(item,index,$event)' @keyup.enter='saveEdit(item,index,$event)' @keyup.esc='currentEditing=null' v-editing-focus="item===currentEditing">
directives:{ // 局部自定义属性 editingFocus:{ update(el,binding){ if(binding.value){ el.focus(); } }, }, },
- 在编辑文本框敲回车后者失去焦点后,如果为空,则直接删除这个item,如果不为空,保存这个数据,并去除editing样式:saveEdit处理函数,传入参数
- 输入状态按下esc取消编辑:设置默认value属性是item的title,按下esc抬起的时候,令中间变量为null,去除editing样式
<input class="edit" :value='item.title' @blur='saveEdit(item,index,$event)' @keyup.enter='saveEdit(item,index,$event)' @keyup.esc='currentEditing=null'>
saveEdit(item,index,event){ var editText=event.target.value.trim(); // 如果为空,直接删除这个item if(!editText.length){ return this.todos.splice(index,1); } // 如果不为空,修改title的值,然后去除eiditing样式 item.title=editText; this.currentEditing=null; },
⑥其他(footer部分)
- 显示所有未完成任务数:@click=‘ removeAllDone ’ ,处理事件利用数组方法filter过滤未完成数据,然后重新赋值给数据列表
<button class="clear-completed" @click='removeAllDone'>Clear completed</button>
removeAllDone(){ this.todos=this.todos.filter((item,index)=>{ return !item.completed;//return true,即item.completed为false }); },
- 清除所有的已完成任务:利用计算属性computed的自定义方法leftCount(参考vue教程--计算属性),还有一种方法就是模板中调用处理函数,处理函数使用for循环来删除,但是删完需要把循环索引i--,但是这种方法没有缓存,每一次使用都要重新调用,推荐使用计算属性,效率更高。
<span class="todo-count"><strong>{{leftCount}}</strong> item left</span>
computed:{ leftCount:function(){ return this.todos.filter(item => !item.completed).length } },
- 将数据持久化到localStorage中(待完成):利用watch功能(配置deep,深度监视),计算属性用于需要在模板中绑定输出值,而watch观察者则用于根据需要数据的改变从而定制特殊功能业务
- 路由状态切换:data里添加属性filterState默认为‘all’;计算属性computed增加filtertodos方法,过滤不同状态的路由;同时修改列表渲染为遍历filterTodos;在window里添加路由改变事件onhashchange,并且每次页面进来需要执行一次保持上一次的状态;改变点击时的样式,添加属性selected当为true时作用,即filterState会等于路由的时候,样式生效。
data:{ todos:todos, currentEditing:null, filterState:'all', },
computed:{ leftCount:function(){ return this.todos.filter(item => !item.completed).length }, filterTodos:function(){ switch(this.filterState){ case 'active': return this.todos.filter(item=>!item.completed); break; case 'completed': return this.todos.filter(item=>item.completed); break; default: return this.todos; break; }; },
<li v-for="(item,index) of filterTodos" v-bind:class='{completed:item.completed,editing:item===currentEditing}'>
// 路由状态切换 window.onhashchange=function(){ var hash=window.location.hash.substr(2) || 'all'; window.app.filterState=hash; }; // 页面第一次进来,保持状态 window.onhashchange();
<ul class="filters"> <li> <a :class="{selected:filterState==='all'}" href="#/">All</a> </li> <li> <a :class="{selected:filterState==='active'}" href="#/active">Active</a> </li> <li> <a :class="{selected:filterState==='completed'}" href="#/completed">Completed</a> </li> </ul>
三、项目完整代码和效果展示
<!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="node_modules/todomvc-common/base.css"> <link rel="stylesheet" href="node_modules/todomvc-app-css/index.css"> <!-- CSS overrides - remove if you don't need it --> <link rel="stylesheet" href="css/app.css"> </head> <body> <!-- id="todoapp"vue管理模块入口 --> <section id="todoapp" class="todoapp"> <header class="header"> <h1>todos</h1> <input class="new-todo" placeholder="What needs to be done?" @keyup.enter='addTodo' v-focus> </header> <template v-if='todos.length'> <!-- This section should be hidden by default and shown when there are todos --> <section class="main"> <!-- @click='toggleAll'点击事件 --> <input @click='toggleAll' id="toggle-all" class="toggle-all" type="checkbox" v-bind:checked='toggleState'> <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 --> <!-- vue列表渲染 --> <li v-for="(item,index) of filterTodos" v-bind:class='{completed:item.completed,editing:item===currentEditing}'> <div class="view"> <input class="toggle" type="checkbox" v-model='item.completed'> <label @dblclick="currentEditing=item">{{item.title}}</label> <button class="destroy" @click='removeTodo(index,$event)' ></button> </div> <input class="edit" :value='item.title' @blur='saveEdit(item,index,$event)' @keyup.enter='saveEdit(item,index,$event)' @keyup.esc='currentEditing=null' 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 --> <span class="todo-count"><strong>{{leftCount}}</strong> item left</span> <!-- Remove this if you don't implement routing --> <ul class="filters"> <li> <a :class="{selected:filterState==='all'}" href="#/">All</a> </li> <li> <a :class="{selected:filterState==='active'}" href="#/active">Active</a> </li> <li> <a :class="{selected:filterState==='completed'}" href="#/completed">Completed</a> </li> </ul> <!-- Hidden if no completed items are left ↓ --> <button class="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="http://sindresorhus.com">Sindre Sorhus</a></p> <!-- Change this out with your name and url ↓ --> <p>Created by <a href="http://todomvc.com">you</a></p> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p> </footer> <!-- Scripts here. Don't remove ↓ --> <script src="node_modules/todomvc-common/base.js"></script> <script src="node_modules/vue/dist/vue.js"></script> <script src="js/app.js"></script> </body> </html>
(function (Vue) { // 数据 let todos=[ {id:1,title:'睡觉',completed:true}, {id:2,title:'美食',completed:false}, {id:3,title:'代码',completed:true} ]; // 全局自定义指令,自动获取焦点 Vue.directive('focus', { inserted: function (el) { el.focus(); } }); // vue实例 window.app=new Vue({ el:'#todoapp', data:{ todos:todos, currentEditing:null, filterState:'all', toggleAllstate:true, }, computed:{ leftCount:function(){ return this.todos.filter(item => !item.completed).length }, filterTodos:function(){ switch(this.filterState){ case 'active': return this.todos.filter(item=>!item.completed); break; case 'completed': return this.todos.filter(item=>item.completed); break; default: return this.todos; break; }; }, // 全选的联动效果 toggleState:function(){ return this.todos.every(item=>item.completed); }, }, methods:{ // 添加任务 addTodo(event){ let 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:id, title:todoText, completed:false, }); event.target.value=''; }, // 点击全部完成或者未完成 toggleAll(event){ let checked=event.target.checked; this.todos.forEach(todo => todo.completed=checked); }, // 删除单个任务项 removeTodo(delIndex,event){ this.todos.splice(delIndex,1); }, // 显示所有未完成任务数(删除所有已完成) removeAllDone(){ this.todos=this.todos.filter((item,index)=>{ return !item.completed;//return true,即item.completed为false }); }, // 保存编辑项 saveEdit(item,index,event){ var editText=event.target.value.trim(); // 如果为空,直接删除这个item if(!editText.length){ return this.todos.splice(index,1); } // 如果不为空,修改title的值,然后去除eiditing样式 item.title=editText; this.currentEditing=null; }, }, directives:{ // 局部自定义属性 editingFocus:{ update(el,binding){ if(binding.value){ el.focus(); } }, }, }, }); // 路由状态切换 window.onhashchange=function(){ var hash=window.location.hash.substr(2) || 'all'; window.app.filterState=hash; }; // 页面第一次进来,保持状态 window.onhashchange(); })(Vue);