当前位置:   article > 正文

【Vue 快速入门系列】todoList案例小总结_使用todolist得心得

使用todolist得心得

一、案例效果

如下图所示,制作一个这样的记事本,可以使用这个记事本进行数据的存储以及管理,样式是天禹老师写好的我们直接使用就好了,主要在这个小案例中体会一下在Vue中如何维护页面展示的数据。在读懂这篇博客之前,需要有一定的前置知识前置知识传送门
最初版本:
在这里插入图片描述
各项完善之后
在这里插入图片描述

二、项目介绍

这个小小案例主要页面主要分为三大部分头、身体、脚部。这三个部分的组件为兄弟组件均归App组件进行管理
通过bootstrap.css渲染实现上面GIF的效果。

三、版本更新迭代

在写这个小案例时,可以有以下几个版本的更新迭代,建议从最基础的功能实现做起,由于篇幅有限这里只放基础功能源码及其他版本的核心代码,如果有什么疑问的话欢迎评论区留言。
在进行写代码的时候遵循以下步骤:

  1. 搭建起网页框架
  2. 使用css装饰
  3. 写入静态数据
  4. 测试JavaScript事件可行性
  5. 静态数据替换
  6. 大工告成

TodoList案例各个版本应实现的功能

  • v1、实现基础功能
    • 整体页面布局
    • 使用props属性编程技巧实现父子间通信(父组件传递props数据,子组件调用传进来的函数以实现修改父组件属性)
    • 使用javascript中的列表操作方法及ES6语法规范
    • 使用模板语法渲染页面
  • v2、实现本地存储
    • 添加浏览器本地存储功能
    • 添加浏览器本地存储读取功能
    • 添加浏览器本地存储清空功能
  • v3、使用自定义事件绑定
    • 删除props属性传递函数的通信方式
    • 增加父组件自定义事件绑定并在子组件触发的通信方式
  • v4、使用全局事件总线
    • 将数据源从App组件移入MyList组件
    • 删除原有的自定义事件绑定
    • 使用全局事件总线进行兄弟间组件通信
  • v5、增加编辑功能
    • 使用nextTick小技巧实现编辑聚焦
    • @blur="handBlur(todo,$event)失焦回调,获取模板对应的数据及编辑框的dom元素本身

基础功能版本页面的整体布局及代码如下:
在这里插入图片描述

main.js 存放基础的配置

import App from "./App.vue"
import Vue from "vue"
new Vue({
    el:"#App",
    render:h=>h(App)
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

App.vue 管理其余组件,提供页面的布局

<template>
<div id="root">
  <div class="todo-container">
    <div class="todo-wrap">
        <MyHead :lss="lss" :addobj="addtodo"></MyHead>
        <MyList :lss="lss" :alertDone="alertDone" :deltodo="deltodo"></MyList>
        <MyFoot :lss="lss" :alertall="alertall" :deld="deldoneall"></MyFoot>
    </div>
  </div>
</div>
</template>

<script>
import MyHead from "./components/MyHead.vue"
import MyFoot from "./components/MyFoot.vue"
import MyList from "./components/MyList.vue"
import {nanoid} from "nanoid"
export default {
    name:"App",
    data(){
      return {
        lss:[
          {id:1,title:"吃饭",done:false},
          {id:2,title:"喝水",done:true},
          {id:3,title:"睡觉",done:false}
        ]
      }
    },
    components:{
        MyFoot,
        MyHead,
        MyList
    },
    methods:{
      alertDone(id){
        this.lss.forEach((todo)=>{
          // alert("done")
          if (todo.id===id){todo.done=!todo.done}
        })
      },
      deltodo(id){
          this.lss=this.lss.filter((todo)=>{
              return todo.id!==id
          })
      },
      addtodo(titname){
          if (titname===""){
            return false;
          }
          var index = this.lss.findIndex(item => item.title === titname)
          console.log(index)
          if (index!==-1){
            return false
          }
          console.log(index)
          this.lss.unshift({"id":nanoid(),"title":titname,"done":false})
          return true
      },
      alertall(e){
        this.lss.forEach((todo)=>{
          todo.done=e
        })
      },
      deldoneall(){
        this.lss=this.lss.filter((todo)=>{
          return !todo.done
        })
      }
    }
}
</script>

<style>
body {
    background: #fff;
  }
  
  .btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }
  
  .btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
  }
  
  .btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
  }
  
  .btn:focus {
    outline: none;
  }
  
  .todo-container {
    width: 600px;
    margin: 0 auto;
  }
  .todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115

MyList.vue 作为MyItem的父组件,也就是页面的中间部分

<template>
    <ul class="todo-main">
       <!-- eslint-disable-next-line vue/valid-v-for -->
      <MyItem v-for="todo in lss" :todo="todo" :aleDone="alertDone" :deltodo="deltodo"></MyItem>
    </ul>
</template>

<script>
import MyItem from "./MyItem.vue"
export default {
    name:"MyList",
    components:{
      MyItem
    },
    props:["lss","alertDone","deltodo"]
}
</script>

<style scoped>
  .todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
  }
  
  .todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

MyItem.vue 展示一条代办事项信息

<template>
    <li>
        <label>
          <!-- 
            这里如果使用双向绑定的话。会修改掉props传过来的数据,虽然没有报错,但是一般不可以这么做。
          -->
            <input type="checkbox" :checked="todo.done" @change="alertdone(todo.id)"/>
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="delthistodo(todo.id)">删除</button>
    </li>
</template>

<script>
export default {
    name:"MyItem",
    props:["todo","aleDone","deltodo"],
    computed:{
    },
    
    methods:{
      alertdone(id){
        if (id){
        // alert(id)
          this.aleDone(id)
        }
      },
      delthistodo(id){
        this.deltodo(id)
      }
    }
}
</script>

<style scoped>
    li {
      list-style: none;
      height: 36px;
      line-height: 36px;
      padding: 0 5px;
      border-bottom: 1px solid #ddd;
    }
    
    li label {
      /* float: left; */
      cursor: pointer;
    }
    
    li label li input {
      vertical-align: middle;
      margin-right: 6px;
      position: relative;
      top: -1px;
    }
    
    li button {
      float: right;
      display: none;
      margin-top: 3px;
    }
    
    li:before {
      content: initial;
    }
    
    li:last-child {
      border-bottom: none;
    }
    li:hover{
      background-color: #ddd;
    }
    li:hover button{
      display:block;
    }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

MyHead.vue 页面的头部

<template>
    <div class="todo-header">
        <input v-model="temp" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="adddata"/>
    </div>
</template>

<script>
export default {
    name:"MyHead",
    props:["lss","addobj"],
    data(){
      return {
        temp:""
      }
    },
    methods:{
      adddata(){
        if (this.addobj(this.temp)){
          alert("添加成功!")
        }else{
          alert("添加失败!")
        }
        this.temp=""
      }
    }
}
</script>

<style scoped>
  .todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
  }
  
  .todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

MyFoot.vue 页面的脚部

<template>
        <div class="todo-footer" v-if="lss.length">
        <label>
          <input type="checkbox" v-model="alerall"/>
        </label>
        <span>
          <span>已完成{{donenum}}</span> / 全部{{lss.length}}
        </span>
        <button class="btn btn-danger" @click="deldoneall">清除已完成任务</button>
      </div>
</template>

<script>
export default {
    name:"MyFoot",
    props:["lss","alertall","deld"],
    computed:{
      donenum(){
        // 进行计数统计常用的方法。函数调用过程为第一个参数为回调函数,第二参数为初始值
        // 回调函数中第一个参数是函数调用过程中的返回值,第二个为对象
        return this.lss.reduce((pre,todo)=>pre+(todo.done?1:0),0)
      },
      alerall:{
        get(){
          return (this.lss.length===this.donenum)&&this.lss.length!==0
        },
        set(e){
          // 这里如果是勾选的话就传进来true,否则传进来false
          // console.log(e)
          this.alertall(e)
        }
      }
    },
    methods:{
      deldoneall(){
        this.deld()
      }
    }
}
</script>

<style scoped>
  .todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
  }
  
  .todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
  }
  
  .todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
  }
  
  .todo-footer button {
    float: right;
    margin-top: 5px;
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

末、项目素材

可以先尝试将以下代码拆分为最初的v1版本,然后逐步向高版本迭代。

1.css样式

/*base*/
body {
    background: #fff;
  }
  
  .btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }
  
  .btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
  }
  
  .btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
  }
  
  .btn:focus {
    outline: none;
  }
  
  .todo-container {
    width: 600px;
    margin: 0 auto;
  }
  .todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
  }
  
  /*header*/
  .todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
  }
  
  .todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
  }
  
  /*main*/
  .todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
  }
  
  .todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
  }
  /*item*/
  li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
  }
  
  li label {
    float: left;
    cursor: pointer;
  }
  
  li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
  }
  
  li button {
    float: right;
    display: none;
    margin-top: 3px;
  }
  
  li:before {
    content: initial;
  }
  
  li:last-child {
    border-bottom: none;
  }
  
  /*footer*/
  .todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
  }
  
  .todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
  }
  
  .todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
  }
  
  .todo-footer button {
    float: right;
    margin-top: 5px;
  }
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136

2.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>React App</title>

  <link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
  <div class="todo-container">
    <div class="todo-wrap">
      <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
      </div>
      <ul class="todo-main">
        <li>
          <label>
            <input type="checkbox"/>
            <span>xxxxx</span>
          </label>
          <button class="btn btn-danger" style="display:none">删除</button>
        </li>
        <li>
          <label>
            <input type="checkbox"/>
            <span>yyyy</span>
          </label>
          <button class="btn btn-danger" style="display:none">删除</button>
        </li>
      </ul>
      <div class="todo-footer">
        <label>
          <input type="checkbox"/>
        </label>
        <span>
          <span>已完成0</span> / 全部2
        </span>
        <button class="btn btn-danger">清除已完成任务</button>
      </div>
    </div>
  </div>
</div>

</body>
</html>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/223852
推荐阅读
  

闽ICP备14008679号