框架入門經典項目TodoMVC


一、項目介紹

①地址: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>
index.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);
app.js


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM