vue todoList练习

  • Post author:
  • Post category:vue




vue todoList练习

运行效果

在这里插入图片描述

原理分析和实现

首先,还是先把布局写好,和引入vue,准备vue实例,这个不多说,代码如下

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            body{font-family: "微软雅黑";font-size: 14px;}
            input{font-size: 14px;}
            body,ul,div,html{padding: 0;margin: 0;}
            .hidden{display: none;}
            .main{width: 800px;margin: 0 auto;}
            li{list-style-type: none;line-height: 40px;position: relative;border: 1px solid transparent;padding: 0 20px;}
            li .type-span{display: block;width: 10px;height: 10px;background: #ccc;margin: 14px 10px 0 0 ;float: left;}
            li .close{position: absolute;color: #f00;font-size: 20px;line-height: 40px;height: 40px;right: 20px;cursor: pointer;display: none;top: 0;}
            li:hover{border: 1px solid #09f;}
            li:hover .close{display: block;}
            li .text-keyword{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
            .text-keyword{box-sizing: border-box;width: 100%;height: 40px;padding-left: 10px;outline: none;}
        </style>
    </head>
    <body>
        <div id="app" class="main">
            <h2>小目标列表</h2>
            <div class="list">
                <h3>添加小目标</h3>
                <input type="text" class="text-keyword" placeholder="输入小目标后,按回车确认"/>
                <p>共有N个目标</p>
                <p>
                    <input type="radio" name="chooseType" checked="true"/><label>所有目标</label>
                    <input type="radio" name="chooseType"/><label>已完成目标</label>
                    <input type="radio" name="chooseType"/><label>未完成目标</label>
                </p>
            </div>
            <ul>
                <li class="li1">
                    <div>
                        <span class="type-span"></span>
                        <span>html5</span>
                        <span class="close">X</span>
                    </div>
                </li>
                <li class="li1">
                    <div>
                        <span class="type-span"></span>
                        <span>css3</span>
                        <span class="close">X</span>
                    </div>
                </li>
            </ul>
        </div>
    </body>
    <script src="vue2.4.2.js"></script>
    <script type="text/javascript">
    new Vue({
        el: "#app",
        data: {
        },
        computed:{
            
        },
        methods:{
            
        }
    });
    </script>
</html>

布局有了,相当于一个骨架就有了,下面实现功能,一个一个来



步骤1

输入并回车,多一条记录。下面的记录文字也会改变

在这里插入图片描述

首先,大的输入框回车要添加纪录,那么输入框必须绑定一个值和一个添加纪录的方法。

代码如下:

然后,下面的记录也要改变,所以,下面的记录也要帮一个值,因为这个记录可能会有多个,这个值就是一个数组,也可以看到,记录除了名称,还有记录是否完成的状态,所以,绑定记录的这个值肯定是一个对象数组!代码如下

最后,记录文字

要改变。这个只是一个当前记录的长度即可!


html代码

<!--利用v-model把addText绑定到input-->
<input type="text" class="text-keyword" placeholder="输入小目标后,按回车确认" @keyup.13='addList' v-model="addText"/>
<p>共有{{prolist.length}}个目标</p>
<!--v-for遍历prolist-->
<li class="li1" v-for="list in prolist">
    <div>
        <span class="type-span"></span>
        <span>{{list.name}}</span>
        <span class="close">X</span>
    </div>
</li>


js代码

new Vue({
    el: "#app",
    data: {
        addText:'',
        //name-名称,status-完成状态
       prolist:[
               {name:"HTML5",status:false},
               {name:"CSS3",status:false},
               {name:"vue",status:false},
               {name:"react",status:false}
        ]
    },
    computed:{
        
    },
    methods:{
        addList(){
            //添加进来默认status=false,就是未完成状态
            this.prolist.push({
                name:this.addText,
                status:false
            });
            //添加后,清空addText
            this.addText="";
        }
    }
});

测试一下,没问题

在这里插入图片描述



步骤2

点击切换,下面记录会改变

在这里插入图片描述

看到三个选项,也很简单,无非就是三个选择,一个是所有的目标,一个是所有已经完成的目标,一个是所有没完成的目标。

首先.新建一个新的变量(newList),储存prolist。遍历的时候不再遍历prolist,而是遍历newList。改变也是改变newList。

然后.选择所有目标的时候,显示全部prolist,把prolist赋值给newList。

然后.选择所有已经完成目标的时候,只显示prolist中,status为true的目标,把prolist中,status为true的项赋值给newList,

最后.选择所有未完成目标的时候,只显示status为false的目标,把prolist中,status为false的项赋值给newList。

代码如下


html代码

 <ul>
    <li class="li1" v-for="list in newList">
        <div>
            <span class="status-span"></span>
            <span>{{list.name}}</span>
            <span class="close" @click='delectList(index)'>X</span>
        </div>
    </li>
</ul>


js代码

new Vue({
    el: "#app",
    data: {
        addText:'',
        //name-名称,status-完成状态
       prolist:[
               {name:"HTML5",status:false},
               {name:"CSS3",status:false},
               {name:"vue",status:false},
               {name:"react",status:false}
        ],
        newList:[]
    },
    computed:{
        noend:function(){
            return this.prolist.filter(function(item){
                return !item.status
            }).length;
        }
    },
    methods:{
        addList(){
            //添加进来默认status=false,就是未完成状态
            this.prolist.push({
                name:this.addText,
                status:false
            });
            //添加后,清空addText
            this.addText="";
        },
        chooseList(type){
            //type=1时,选择所有目标
            //type=2时,选择所有已完成目标
            //type=3时,选择所有未完成目标
            switch(type){
                case 1:this.newList=this.prolist;break;
                case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
            }
        },
        delectList(index){
            //根据索引,删除数组某一项
            this.prolist.splice(index,1);
            //更新newList  newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList  那么就要手动更新newList
            this.newList=this.prolist;
        },
    },
    mounted(){
        //初始化,把prolist赋值给newList。默认显示所有目标
        this.newList=this.prolist;
    }
});

运行结果

在这里插入图片描述



步骤3

红色关闭标识,点击会删除该记录。前面按钮点击会切换该记录完成状态,颜色也改变,记录文字也跟着改变

在这里插入图片描述

首先点击红色关闭标识,点击会删除该记录。这个应该没什么问题,就是删除prolist的一条记录!

然后前面按钮点击会切换该记录完成状态。这个也没什么,就是改变prolist的一条记录的status字段!

最后记录文字的改变,就是记录prolist中status为false的有多少条,prolist中status为true的有多少条而已


html代码

<!--如果noend等于0,就是全部完成了就显示‘全部完成了’,如果没有就是显示已完成多少条(prolist.length-noend)和未完成多少条(noend)-->
<p>共有{{prolist.length}}个目标,{{noend==0?"全部完成了":'已完成'+(prolist.length-noend)+',还有'+noend+'条未完成'}}</p>


<ul>
    <li class="li1" v-for="(list,index) in newList">
        <div>
            <span class="status-span" @click="list.status=!list.status" :class="{'status-end':list.status}"></span>
            <span>{{list.name}}</span>
            <span class="close" @click='delectList(index)'>X</span>
        </div>
    </li>
</ul>


js代码

new Vue({
    el: "#app",
    data: {
        addText:'',
        //name-名称,status-完成状态
       prolist:[
               {name:"HTML5",status:false},
               {name:"CSS3",status:false},
               {name:"vue",status:false},
               {name:"react",status:false}
        ],
        newList:[]
    },
    computed:{
        //计算属性,返回未完成目标的条数,就是数组里面status=false的条数
        noend:function(){
            return this.prolist.filter(function(item){
                return !item.status
            }).length;
        }
    },
    methods:{
        addList(){
            //添加进来默认status=false,就是未完成状态
            this.prolist.push({
                name:this.addText,
                status:false
            });
            //添加后,清空addText
            this.addText="";
        },
        chooseList(type){
            switch(type){
                case 1:this.newList=this.prolist;break;
                case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
            }
        },
        delectList(index){
            //根据索引,删除数组某一项
            this.prolist.splice(index,1);
            //更新newList  newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList  那么就要手动更新newList
            this.newList=this.prolist;
        },
    },
    mounted(){
        this.newList=this.prolist;
    }
});

运行结果

在这里插入图片描述



步骤4

文字双击会出现输入框,可输入文字,如果回车或者失去焦点,就改变文字,如果按下ESC就恢复原来的文字

在这里插入图片描述

首先.双击出现输入框,就是双击文字后,给当前的li设置一个类名(‘eidting’),然后写好样式。当li出现这个类名的时候,就出现输入框,并且隐藏其它内容。

然后.回车或者失去焦点,就改变文字这个只需要操作一个,就是把类名(‘eidting’)清除掉。然后输入框就会隐藏,其它内容显示!

最后.按下ESC就恢复原来的文字,就是出现输入框的时候,用一个变量(‘beforeEditText’)先保存当前的内容,然后按下了ESC,就把变量(‘beforeEditText’)赋值给当前操作的值!

代码如下:


html代码

<ul>
    <li class="li1" v-for="(list,index) in newList" :class="{'eidting':curIndex===index}">
        <div>
            <span class="status-span" @click="list.status=!list.status" :class="{'status-end':list.status}"></span>
            <span @dblclick="curIndex=index">{{list.name}}</span>
            <span class="close" @click='delectList(index)'>X</span>
        </div>
        <input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited'/>
    </li>
</ul>


css加上

li div{display: block;}
li.eidting div{display: none;}
li .text2{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
li.eidting .text2{display: block;}


js代码

methods:{
        addList(){
            //添加进来默认status=false,就是未完成状态
            this.prolist.push({
                name:this.addText,
                status:false
            });
            //添加后,清空addText
            this.addText="";
        },
        chooseList(type){
            //type=1时,选择所有目标
            //type=2时,选择所有已完成目标
            //type=3时,选择所有未完成目标
            switch(type){
                case 1:this.newList=this.prolist;break;
                case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
            }
        },
        delectList(index){
            //根据索引,删除数组某一项
            this.prolist.splice(index,1);
            //更新newList  newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList  那么就要手动更新newList
            this.newList=this.prolist;
        },
        //修改前
        editBefore(name){
            //先记录当前项(比如这一项,{name:"HTML5",status:false})
            //beforeEditText="HTML5"
            this.beforeEditText=name;
        },
        //修改完成后
        edited(){
            //修改完了,设置curIndex="",这样输入框就隐藏,其它元素就会显示。因为在li元素 写了::class="{'eidting':curIndex===index}"  当curIndex不等于index时,eidting类名就清除了!
            //输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步只是清除eidting类名,隐藏输入框而已
            //还有一个要注意的就是虽然li遍历的是newList,比如改了newList的这一项({name:"HTML5",status:false}),比如改成这样({name:"HTML",status:true})。实际上prolist的这一项({name:"HTML5",status:false}),也会被改成({name:"HTML",status:true})。因为这里是一个对象,而且公用一个堆栈!修改其中一个,另一个会被影响到
            this.curIndex="";
        },
        //取消修改
        cancelEdit(val){
            //上面说了输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步就是把之前保存的beforeEditText赋值给当前项的name属性,起到一个恢复原来值得作用!
            val.name=this.beforeEditText;
            this.curIndex="";
        }
 },

运行结果

在这里插入图片描述

还有一个小细节,大家可能注意到了,就是双击文字,出来输入框的时候,还要自己手动点击一下,才能获得焦点,我们想双击了,输入框出来的时候,自动获取焦点,怎么办?自定义指令就行了!

computed:{...},
methods:{...},
mounted(){...},
directives:{
    "focus":{
        update(el){
            el.focus();
        }
    }
}

然后html 调用指令

<input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited' v-focus/>



完整代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <style>
        body{font-family: "微软雅黑";font-size: 14px;}
        input{font-size: 14px;}
        body,ul,div,html{padding: 0;margin: 0;}
        .hidden{display: none;}
        .main{width: 800px;margin: 0 auto;}
        li{list-style-type: none;line-height: 40px;position: relative;border: 1px solid transparent;padding: 0 20px;}
        li .status-span{display: block;width: 10px;height: 10px;background: #ccc;margin: 14px 10px 0 0 ;float: left;}
        li .status-span.status-end{
            background: #09f;
        }
        li .close{position: absolute;color: #f00;font-size: 20px;line-height: 40px;height: 40px;right: 20px;cursor: pointer;display: none;top: 0;}
        li:hover{border: 1px solid #09f;}
        li:hover .close{display: block;}
        li div{display: block;}
        li.eidting div{display: none;}
        li .text2{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
        li.eidting .text2{display: block;}
        li .text-keyword{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
        .text-keyword{box-sizing: border-box;width: 100%;height: 40px;padding-left: 10px;outline: none;}
    </style>
</head>
<body>
<div id="app" class="main">
    <h2>小目标列表</h2>
    <div class="list">
        <h3>添加小目标</h3>
        <input type="text" class="text-keyword" placeholder="输入小目标后,按回车确认" @keyup.13='addList' v-model="addText"/>
        <!--如果noend等于0,就是全部完成了就显示‘全部完成了’,如果没有就是显示已完成多少条(prolist.length-noend)和未完成多少条(noend)-->
        <p>共有{{prolist.length}}个目标,{{noend==0?"全部完成了":'已完成'+(prolist.length-noend)+',还有'+noend+'条未完成'}}</p>
        <p>
            <input type="radio" name="chooseType" checked="true" @click='chooseList(1)'/><label>所有目标</label>
            <input type="radio" name="chooseType" @click='chooseList(2)'/><label>已完成目标</label>
            <input type="radio" name="chooseType" @click='chooseList(3)'/><label>未完成目标</label>
        </p>
    </div>
    <ul>
        <li class="li1" v-for="(list,index) in newList" :class="{'eidting':curIndex===index}">
            <div>
                <span class="status-span" @click="changeType(index)" :class="{'status-end':list.status}"></span>
                <span @dblclick="curIndex=index">{{list.name}}</span>
                <span class="close" @click='delectList(list)'>X</span>
            </div>
            <input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited' v-focus/>
        </li>
    </ul>
</div>
</body>
<script src="vue.min.js"></script>
<script type="text/javascript">
    new Vue({
        el: "#app",
        data: {
            addText:'',
            //name-名称,status-完成状态
            prolist:[
                {name:"HTML5",status:false},
                {name:"CSS3",status:false},
                {name:"vue",status:false},
                {name:"react",status:false}
            ],
            newList:[],
            curIndex:'',
            beforeEditText:"",
            curType:0
        },
        computed:{
            //计算属性,返回未完成目标的条数,就是数组里面status=false的条数
            noend:function(){
                return this.prolist.filter(function(item){
                    return !item.status
                }).length;
            }
        },
        methods:{
            addList(){
                //添加进来默认status=false,就是未完成状态
                this.prolist.push({
                    name:this.addText,
                    status:false
                });
                //添加后,清空addText
                this.addText="";
            },
            chooseList(type){
                //type=1时,选择所有目标
                //type=2时,选择所有已完成目标
                //type=3时,选择所有未完成目标
                this.curType=type;
                switch(type){
                    case 1:this.newList=this.prolist;break;
                    case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                    case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
                }
            },
            /*改变单条数据的完成状态*/
            changeType(index){
                this.newList[index].status=!this.newList[index].status;
                //更新数据
                this.chooseList(this.curType);
            },
            delectList(list){
                var index=this.prolist.indexOf(list);
                //根据索引,删除数组某一项
                this.prolist.splice(index,1);
                //更新newList  newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList  那么就要手动更新newList
                //this.newList=this.prolist;
                this.chooseList(this.curType);
            },
            //修改前
            editBefore(name){
                //先记录当前项(比如这一项,{name:"HTML5",status:false})
                //beforeEditText="HTML5"
                this.beforeEditText=name;
            },
            //修改完成后
            edited(){
                //修改完了,设置curIndex="",这样输入框就隐藏,其它元素就会显示。因为在li元素 写了::class="{'eidting':curIndex===index}"  当curIndex不等于index时,eidting类名就清除了!
                //输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步只是清除eidting类名,隐藏输入框而已
                //还有一个要注意的就是虽然li遍历的是newList,比如改了newList的这一项({name:"HTML5",status:false}),比如改成这样({name:"HTML",status:true})。实际上prolist的这一项({name:"HTML5",status:false}),也会被改成({name:"HTML",status:true})。因为这里是一个对象,而且公用一个堆栈!修改其中一个,另一个会被影响到
                this.curIndex="";
            },
            //取消修改
            cancelEdit(val){
                //上面说了输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步就是把之前保存的beforeEditText赋值给当前项的name属性,起到一个恢复原来值得作用!
                val.name=this.beforeEditText;
                this.curIndex="";
            }
        },
        mounted(){
            //初始化,把prolist赋值给newList。默认显示所有目标
            this.newList=this.prolist;
        },
        directives:{
            "focus":{
                update(el){
                    el.focus();
                }
            }
        }
    });
</script>
</html>