• vue之TodoMVC项目实战


    一、初始化项目

    1、下载模板

    进入github中https://github.com/tastejs/todomvc-app-template,并且在命令行将其clone下来

    git clone https://github.com/tastejs/todomvc-app-template.git

    2、安装依赖

    进入项目目录中安装依赖

    npm install

    3、引入vue.js文件

    首先在命令行中安装vue

    npm install vue

    然后再index.html中引入

    然后再app.js文件中写入相应的逻辑代码

    二、实现功能

    1、数据列表渲染功能

    1.1 功能分析

    • 有数据
    每一个数据对象:
    {
      id:1,
     content:'',
    complated:''  #表示是否已经完成的任务true(false)  
    
    }
    并且每一个数据对象有三个状态:
                未完成(没有样式)
                已完成(.completed )
                编辑中( .editing )
    • 无数据

    输入框下面的部分应该隐藏起来

    1.2 实现

    • 创建一个数据源
                        items:[
                        {id:1,content:'dddd',completed:false},
                        {id:2,content:'aaaa',completed:false},
                        {id:3,content:'bbbb',completed:false},
                        {id:4,content:'cccc',completed:false},
                    ]

    • 处理有数据的情况
    <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 -->
                        <!-- 根据不同状态获取相应的样式,三种状态  -->
                <li v-for="(item,index) in items" :class="{completed:item.completed}">
                    <div class="view">
                        <!-- v-model进行双向绑定,checkbox是否选中  -->
                        <input class="toggle" type="checkbox" v-model="item.completed">
                        <!-- 获取对应对象的内容  -->
                        <label>{{item.content}}</label>
                        <!-- 将id传入用于删除对应的数据  -->
                        <button class="destroy" :value="item.id"></button>
                    </div>
                    <input class="edit" value="Create a TodoMVC template">
                </li>
    
            </ul>
    • 处理无数据的情况

    利用v-show指令判断数组长度是否为0,也就是是否为false

    <footer class="footer" v-show="items.length">
            <!-- This should be `0 items left` by default -->
            <span class="todo-count"><strong>0</strong> item left</span>
            <!-- Remove this if you don't implement routing -->
            <ul class="filters">
                <li>
                    <a class="selected" href="#/">All</a>
                </li>
                <li>
                    <a href="#/active">Active</a>
                </li>
                <li>
                    <a href="#/completed">Completed</a>
                </li>
            </ul>
            <!-- Hidden if no completed items are left ↓ -->
            <button class="clear-completed">Clear completed</button>
        </footer>

     2、添加任务功能

    2.1 功能分析

    •  将输入的内容添加到任务列表中
    • 如果输入为空,不做任何事情
    • 按enter键添加到任务列表,并清空输入框

    2.2 实现

    <header class="header">
            <h1>todoapp</h1>
            <!--绑定键盘事件添加数据-->
            <input @keyup.enter="addItem" class="new-todo" placeholder="What needs to be done?" autofocus>
        </header>

    在app.js文件的methods参数写入方法

                    addItem(event){
                        //获取文本框中的值
                        const newValue=event.target.value.trim();
                        //判断是否为空,如果为空什么也不做
                        if(!newValue.length){
                            return
                        }
                        //如果不为空将新值添加到数组中
                        newObject={
                            id:this.items.length+1, //生成一个新的id
                            content:newValue,
                            completed:false
                        };
                        this.items.push(newObject);
                        //将文本框置空
                        event.target.value=''
                    }

    3、显示所有未完成任务数功能

    3.1 功能分析

    • 当数组发生变化时计算内部对象的个数,可通过计算属性
    • 利用filter函数筛选出未完成任务的个数
    • 任务数量为单数时为item,为复数时为items

    3.2 实现

        <!-- 返回所有未完成任务的数量,并且如果为单数就为item,否则为items -->
            <span class="todo-count"><strong>{{ incomplete }}</strong> item{{ incomplete===1? '' : 's' }} left</span>

    在app.js文件的computed参数中写入对应的方法

        incomplete(){
                        //箭头函数返回未完成任务的个数
                        return this.items.filter(item=>!item.completed).length
                        // this.items.filter(function (item) {
                        //     return !item.completed
                        // }).length
                    }

     4、切换所有任务状态

    4.1 功能分析

    • 点击输入框前面的复选框后,将所有任务标记为与复选框相同的状态

         (1)使用计算属性中的set方法,此时需要v-model进行数据的双向绑定,通过监听数据属性,获取新的checkbox的值

         (2)将获取的值赋给每一个任务项

    • 当 选中/取消 某个任务后,复选框 也应同步更新状态

         (1)使用计算属性的get方法,判断所有incomplete是否为 0 ,

         (2)绑定了 incomplete,当 incomplete发生变化后, 自动更新复选框状态(如果为0说明任务已经全部完成,复选框会自动选中,反之不选中

    4.2 实现

    index.html

    <input id="toggle-all" v-model="isSelectAll" class="toggle-all" type="checkbox">

    app.js

        isSelectAll:{
                        //循环数据源中的每一个对象,并且将通过v-model双向绑定获取的值赋给每一个item中的状态,从而根据input checkbox的状态去顶任务的状态
                        set:function (newState) {
                            this.items.forEach(function (item) {
                                item.completed=newState;
                            })
                        },
                        
                        //根据任务完成的状态完成绑定v-model的input checkbox框的状态获取
                        get:function () {
    
                            return this.incomplete===0
                        }
    
                    }

    5、删除任务项

    5.1 功能分析

    • 悬停在某个任务项上显示 X 移除按钮,可点击移除当前任务项

            (1) 移除按钮处添加点击事件

            (2)通过数组函数 splice 移除任务

    5.2 实现

     index.html

    <button class="destroy" :value="item.id" @click="removeItem(index)"></button>

    app.js

    //移除对象 splice(),传入移除对象的索引,以及从此处开始完后删掉的数量
                    removeItem(index){
                        this.items.splice(index,1)
    
                    },

    6、编辑任务项

    6.1 功能分析

    • 双击 <label> (某个任务项)进入编辑状态(在 <li> 上通过 .editing 进行切换状态)
    • 进入编辑状态后输入框显示原内容,并会自动获取编辑焦点
    • 输入状态按 Esc 取消编辑,editing 样式应该被移除
    • 按 Enter 键 或 失去焦点时 保存改变数据,移除 editing 样式

    6.2 实现

    • 双击 <label> (某个任务项)进入编辑状态(在 <li> 上通过 .editing 进行切换状态)
    <!-- 获取对应对象的内容,在内容的标签上绑定双击事件--> 
      <label @dblclick="toEdit(item)">{{item.content}}</label>
                    //双击进入编辑模式,也就是加入.editing样式
                    toEdit(item){
                        this.currentItem=item
                    },
    • 进入编辑状态后输入框显示原内容,并会自动获取编辑焦点
        <!-- 显示点击编辑后的默认值:value="item.content"  -->
                    <input class="edit" v-todo-focus="item===currentItem"  :value="item.content"  >
        //自定义局部指令,用于聚焦编辑框修改内容,当进入编辑模式的对象与传入的对象是同一个时聚焦,防止聚焦到别处
                directives:{
                    "todo-focus":{
                        //当指令的值更新后会调用此方法
                        update(el,binding){
                            //el表示作用的元素
                            //binding表示指令后输入的内容
                            if(binding.value){
                                el.focus()
                            }
    
                        }
    
                    }
                },
    • 输入状态按 Esc 取消编辑,editing 样式应该被移除
        <!-- 显示点击编辑后的默认值:value="item.content"  -->
                    <input class="edit" @keyup.esc="cancelEdit"
                           v-todo-focus="item===currentItem"
                           :value="item.content"  >
                    //点击键盘的esc取消编辑
                    cancelEdit(){
                        this.currentItem=null
                    },
    • 按 Enter 键 或 失去焦点时 保存改变数据,移除 editing 样式
        <!-- 显示点击编辑后的默认值:value="item.content"  -->
                    <input class="edit" @keyup.esc="cancelEdit"
                           @keyup.enter="saveData(item,index,$event)"
                           @blur="saveData(item,index,$event)"
                           v-todo-focus="item===currentItem"
                           :value="item.content"  >
        //通过enter以及blur事件,保存数据,只有当获取焦点才会触发该事件
                    saveData(item,index,event){
                        //获取对应文本框中去除空格后的内容
                        const content=event.target.value.trim();
                        //判断内容是否为空,如果为空,删除任务项
                        if(!content){
                            //重用removeItem函数删除
                            this.removeItem(index)
                        }
                        //否则对数据进行更新
                        item.content=content;
                        //更新后移除编辑样式,.editing
                        this.currentItem=null
    
                    },

    7、清除所有任务项

     7.1 功能分析

    • 单击右下角 Clear completed 按钮时,移除所有已完成任务
    • 当列表中没有已完成的任务时,应该隐藏 Clear completed 按钮

    7.2 实现

    • 单击右下角 Clear completed 按钮时,移除所有已完成任务

    在index.html添加点击事件,然后再app.js中通过filter函数锅炉出所有未完成任务,并且赋给items

    index.html

        <!-- Hidden if no completed items are left ↓ -->
    <!--在对应的地方添加点击事件-->
            <button @click="removeAllCompleted" class="clear-completed">Clear completed</button>

    app.js

                    //过滤出所有未完成的任务项,并且将过滤后的数据赋值给items
                    removeAllCompleted(){
                        this.items= this.items.filter((item)=>!item.completed)
                    },
    • 当列表中没有已完成的任务时,应该隐藏 Clear completed 按钮

    判断总的任务数与没有完成任务数的大小,如果当总任务数 ( items.length ) > 未完成数 ( incomplete) ,说明列表中还有已完成数据,则是显示按钮;反之不显示。

    index.html

            <button @click="removeAllCompleted" class="clear-completed" v-show="items.length > incomplete">Clear completed</button>

    app.js

    //计算属性
    incomplete(){
                        //箭头函数返回未完成任务的个数
                        return this.items.filter(item=>!item.completed).length
                        // this.items.filter(function (item) {
                        //     return !item.completed
                        // }).length
                    },

    8、 过滤出不同状态 的数据

    8.1 功能分析

    • 根据点击的状态不同,获取不同状态下的数据
    • 改变不同状态下的样式

    8.2 实现

    • 根据点击的状态不同,获取不同状态下的数据

          (1)在 data 中定义接收状态变化的值filterStatus 

          (2)通过 window.onhashchange 获取点击的路由 hash (# 开头的),来获取对应的那个状态值,并将状态值赋值给 filterStatus 

          (3)定义一个计算属性 filterItems 用于过滤出目标数据, 用于感知 filterStatus 的状态值变化,当变化后,通过 switch-case + filter 过滤出目标数据。

    app.js

    //1、定义变量
    data:{
            filterState:'all',
    
                },
    
    //2、获取路由hash值,并且截取需要的路由,当截取的为空时返回‘all’
            window.onhashchange=function () {
            // window.location.hash  获取的是这样的数据 #/active
                const hash=window.location.hash.substr(2) || 'all';
                //将状态值赋值给vm实例中的filterState
                vm.filterState = hash
            };
    
        //第一次访问生效,手动调用一次
            window.onhashchange()

    //3、定义计算属性filterItems //过滤出不同状态下的数据,以this.filterState为过滤条件 filterItems(){ switch (this.filterState) { case "active": return this.items.filter(item=>!item.completed); break case "completed": return this.items.filter(item=>item.completed); break default: return this.items; break } },

    index.html

    <!--将v-for循环的items替换为filterItems-->
    <li v-for="(item,index) in filterItems" :class="{completed:item.completed,editing:item===currentItem}">
    • 改变不同状态下的样式
            <ul class="filters">
                <li>
                    <a class="selected" href="#/">All</a>
                </li>
                <li>
                    <a href="#/active">Active</a>
                </li>
                <li>
                    <a href="#/completed">Completed</a>
                </li>
            </ul>

    将上述被选中的样式切换为:

    class="selected"

    如下:

            <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>

     9、数据持久化(localStorage

    9.1 功能分析

    目前数据只是单纯的放在内存中,自己定义的数组:

    data:{
                    items:[
                        {id:1,content:'dddd',completed:false},
                        {id:2,content:'aaaa',completed:false},
                        {id:3,content:'bbbb',completed:false},
                        {id:4,content:'cccc',completed:false},
                    ],
                    currentItem:null,
                    filterState:'all',
    
                },

    如果需要保存在本地,可以使用localStorage ,它主要是用于本地存储数据。语法如下:

    //保存数据语法:
    localStorage.setItem("key", "value");
    
    //读取数据语法:
    var data= localStorage.getItem("key");
    
    //删除数据语法:
    localStorage.removeItem("key");

    本项目中使用的步骤如下:

    • 定义 itemStorage 数据存储对象,内部自定义 fetch 获取本地数据 ,save 存数据到本地。
    • 修改 Vue 实例中 data 选项的 items 属性,通过 itemStorage.fetch() 方法初始化数据
    • Vue 实例中增加一个 watch 选项,用于监听 items 的变化,一旦变化通过 itemStorage.save() 重新保存数据到本地

    9.2 实现

    • 定义 itemStorage 数据存储对象,里面自定义 fetch 获取本地数据 ,save 存数据到本地。
    var STOREGE_KEY = "todo-items";
    
        //定义localstorege对象,注意是在Vue实例外面定义的
        const itemStorage = {
            //获取本地数据的方法
            fetch:function () {
                //获取数据并且数据反序列化,变成数组对象,如果为空,则是空数组
                return JSON.parse(localStorage.getItem(STOREGE_KEY) || '[]')
            },
            //保存数据到本地,items就是需要保存的数据源,并且以JSON字符串的格式存储
            save:function (items) {
                localStorage.setItem(STOREGE_KEY,JSON.stringify(items))
            }
    
        };
    • 修改 Vue 实例中 data 选项的 items 属性,通过 itemStorage.fetch() 方法初始化数据
    var vm = new Vue({
                el:'#todoapp',
                data:{
                    // items:[
                    //     {id:1,content:'dddd',completed:false},
                    //     {id:2,content:'aaaa',completed:false},
                    //     {id:3,content:'bbbb',completed:false},
                    //     {id:4,content:'cccc',completed:false},
                    // ],
                    //从本地获取数据
                    items:itemStorage.fetch(),
    
                    currentItem:null,
                    filterState:'all',
    
                },
    • Vue 实例中增加一个 watch 选项,用于监听 items 的变化,一旦变化通过 itemStorage.save() 重新保存数据到本地
    //监听器,用于本地化数据的存储,一旦数组对象有变化,立即存储
                watch:{
                //监听items,一旦items发生变化就会执行
                items:{
                    deep:true,//需要监听数组对象内部的变化,需要指定deep:true
                    handler(newitems,olditems){
                        // newitems:新的数组对象
                        // olditems:之前的数组对象
                        itemStorage.save(newitems)
                    }
                }
                },

     

    项目地址:https://github.com/ShenJianPing0307/todo-demo

  • 相关阅读:
    Asp.Net MVC 路由
    Http 请求处理流程
    Http Module 介绍
    彻底屏蔽鼠标右键、另存为、查看源文件
    使用TransactionScope实现单数据库连接事务操作
    Asp.Net MVC(创建一个任务列表应用程序) Part.1
    Http Handler 介绍
    jQuery.API源码深入剖析以及应用实现(1) - 核心函数篇
    安装MSSQL2008出现的问题记录
    SQL – 8.Union
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11271828.html
Copyright © 2020-2023  润新知