一、項目介紹
①地址: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);

