Loading

框架入门经典项目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

posted @ 2018-07-26 01:00  澎湃_L  阅读(6774)  评论(0编辑  收藏  举报