小于博客 小于博客
首页
  • Java学习笔记
  • Docker专区
  • 实战教程
  • Shell
  • 内存数据库
  • Vue学习笔记
  • Nginx
  • Php
  • CentOS
  • Docker
  • Gitlab
  • GitHub
  • MySql
  • MongoDB
  • OpenVPN
  • 配置文件详解
  • Other
  • ELK
  • K8S
  • Nexus
  • Jenkins
  • 随写编年
  • 电影音乐
  • 效率工具
  • 博客相关
  • 最佳实践
  • 迎刃而解
  • 学习周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • 网站状态 (opens new window)
    • json2go (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)

小于博客

行者常至,为者常成
首页
  • Java学习笔记
  • Docker专区
  • 实战教程
  • Shell
  • 内存数据库
  • Vue学习笔记
  • Nginx
  • Php
  • CentOS
  • Docker
  • Gitlab
  • GitHub
  • MySql
  • MongoDB
  • OpenVPN
  • 配置文件详解
  • Other
  • ELK
  • K8S
  • Nexus
  • Jenkins
  • 随写编年
  • 电影音乐
  • 效率工具
  • 博客相关
  • 最佳实践
  • 迎刃而解
  • 学习周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • 网站状态 (opens new window)
    • json2go (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)
  • Java学习笔记

  • Docker专区

  • Shell编程

  • 实战教程

  • 内存数据库

  • Vue学习笔记

    • 基础知识

    • 脚手架工程化

      • src_分析脚手架
      • src_ref属性
      • src_props配置
      • src_mixin(混入)
      • src_插件
      • src_scoped样式
      • src_TodoList案例
      • 浏览器本地存储
      • src_TodoList_本地存储
      • src_组件自定义事件
      • src_TodoList_自定义事件
      • src_全局事件总线
      • src_TodoList_事件总线
      • src_消息订阅与发布
      • src_TodoList_pubsub
      • src_TodoList_nextTick
        • 代码
          • 代码路径
          • App.vue
          • MyFooter.vue
          • MyHeader.vue
          • MyItem.vue
          • MyList.vue
          • main.js
        • 笔记
      • src_过渡与动画
      • src_TodoList_动画
      • src_配置代理服务器
      • src_github搜索案例
      • 插槽
      • src_求和案例_纯Vue版本
    • 实战积累

  • 编程世界
  • Vue学习笔记
  • 脚手架工程化
小于博客
2022-08-17
目录

srcTodoListnextTick

# 代码

# 代码路径

$ tree -N
.
├── App.vue
├── components
│   ├── MyFooter.vue
│   ├── MyHeader.vue
│   ├── MyItem.vue
│   └── MyList.vue
└── main.js
1
2
3
4
5
6
7
8
9

# App.vue

<template>
  <div id="app">
    <div class="todo-container">
      <div class="todo-wrap">
        <!-- header -->
        <MyHeader @addTodo="addTodo"></MyHeader>
        <MyList :todos="todos"></MyList>
        <MyFooter
          :todos="todos"
          @checkAllTodo="checkAllTodo"
          @clearAllTodo="clearAllTodo">
        </MyFooter>
      </div>
    </div>
  </div>
</template>

<script>
    import MyFooter from './components/MyFooter.vue'
    import MyHeader from './components/MyHeader.vue'
    import MyList from './components/MyList.vue'

    import  pubsub  from "pubsub-js";
    export default {
        name:'App',
        components:{MyFooter,MyHeader,MyList},
        data() {
            return {
                todos:[
                    {id:'001',title:'抽烟',done:true},
                    {id:'002',title:'喝酒',done:false},
                    {id:'003',title:'开车',done:true}
                ]
            }
        },
        methods: {
          // 添加一个todo
          addTodo(todoObj){
            // console.log('我是App,我接收到了数据:',x);
            this.todos.unshift(todoObj)
          },
          // 勾选or取消勾选一个todo
          checkTodo(id){
            this.todos.forEach((todo)=>{
              if(todo.id === id){
                todo.done = !todo.done
              }
            })
          },
          // 删除一个todo
          deleteTodo(_,id){
            this.todos = this.todos.filter(todo => {
              return todo.id !== id
            })
          },
          // 全选or取消全选
          checkAllTodo(done){
            this.todos.forEach(todo => {
              todo.done = done
            });
          },
          // 清除所有已完成的todo
          clearAllTodo(){
            this.todos = this.todos.filter((todo) =>{
              return !todo.done
            })
          },
          // 更新一个todo
          updateTodo(id,title){
            this.todos.forEach((todo)=>{
              if(todo.id === id){
                todo.title = title
              }
            })
          }
        },
        mounted() {
          this.$bus.$on('checkTodo',this.checkTodo)
          this.$bus.$on('updateTodo',this.updateTodo)
          // this.$bus.$on('deleteTodo',this.deleteTodo)
          this.pid = pubsub.subscribe('deleteTodo',this.deleteTodo)
        },
        beforeDestroy(){
          this.$bus.$off('checkTodo')
          this.$bus.$off('updateTodo')
          // this.$bus.$off('deleteTodo')
          pubsub.unsubcribe(this.pid)
        }
    }
</script>

<style>
/*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-edit {
  color: #fff;
  background-color: skyblue;
  border: 1px solid rgb(119, 181, 206);
  margin-right: 5px;
}

.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

# MyFooter.vue

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
            <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
        <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'MyFooter',
        props:['todos'],
        computed:{
            total(){
                return this.todos.length
            },
            doneTotal(){
                // 第一种:使用遍历的方式
                // let i = 0
                // this.todos.forEach(todo => {
                //     if(todo.done){
                //         i++
                //     }
                // });
                // return i

                // 第二种:使用 reduce 处理
                // return this.todos.reduce((pre, todo) => pre+(todo.done ? 1 : 0), 0);

                // 第三种:使用filter处理
                const a = this.todos.filter(todo => {
                   return todo.done
                });
                return a.length
            },
            isAll:{
                get(){
                    return this.total === this.doneTotal && this.total > 0
                },
                set(value){
                    // this.checkAllTodo(value)
                    this.$emit('checkAllTodo', value);
                }
            }
        },
        methods: {
            clearAll(){
                console.log('aaaa');

                // this.clearAllTodo()
                this.$emit('clearAllTodo')
            }
        //     checkAll(e){
        //         this.checkAllTodo(e.target.checked)
        //     }
        },
    }
</script>

<style scoped>
/*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;
}
</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

# MyHeader.vue

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

<script>
    import { nanoid } from "nanoid";
    export default {
        name:'MyHeader',
        // props:['addTodo'],
        data() {
            return {
                title:''
            }
        },
        methods: {
            add(){
                // 校验数据
                if(!this.title.trim()){
                    return alert('输入不能为空')
                }
                // 将用户的输入包装成一个todo对象
                const todoObj = {id:nanoid(),title:this.title,done:false}
                // 通知APP组件添加一个todo对象
                this.$emit('addTodo',todoObj)
                // 清空输入框
                this.title=''
            }
        },
    }
</script>

<style scoped>
/*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);
}
</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

# MyItem.vue

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @change="changeTodo(todo.id)"/>
            <span v-show="!todo.isEdit">{{todo.title}}</span>
            <input type="text"
              v-show="todo.isEdit"
              :value="todo.title"
              @blur="handleBlur(todo,$event)"
              ref="inputTitle"
            >
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
        <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
    </li>
</template>

<script>
    import pubsub from 'pubsub-js'
    export default {
        name:'MyItem',
        // 声明接收todo对象
        props:['todo'],
        methods: {
            // 勾选or取消勾选
            changeTodo(id){
                // 通知App组件将对应的todo对象的done值取反
                // this.checkTodo(id)
                this.$bus.$emit('checkTodo',id)
            },
            // 删除todo--通过事件总线的方式
            // handleDelete(id){
            //     if(confirm('确定删除吗?')){
            //         this.$bus.$emit('deleteTodo',id)
            //     }
            // }
            // 删除todo--通过订阅发布
            handleDelete(id){
              if(confirm('确定删除吗?')){
                pubsub.publish('deleteTodo',id)
              }
            },
            handleEdit(todo){
              if(todo.hasOwnProperty('isEdit')){
                todo.isEdit = true
              } else {
                this.$set(todo,'isEdit',true)
              }
              this.$nextTick(function(){
                this.$refs.inputTitle.focus()
              })
            },
            // 失去焦点回调 (真正执行修改逻辑)
            handleBlur(todo,e){
              todo.isEdit = false
              if(!e.target.value.trim()) return alert('内容不能为空')
              this.$bus.$emit('updateTodo',todo.id,e.target.value)
            }
        },
    }
</script>

<style scoped>
/*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;
}

li:hover{
    background-color: gray;
}
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
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

# MyList.vue

<template>
    <ul class="todo-main">
        <MyItem
            v-for="t in todos"
            :key="t.id"
            :todo="t"/>
    </ul>
</template>

<script>
    import  MyItem  from "./MyItem.vue";

    export default {
        name:'MyList',
        components:{MyItem},
        props:['todos'],
    }
</script>

<style scoped>
/*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;
}
</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

# main.js

import Vue from "vue"
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    el: '#app',
    components:{App},
    render: h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this
    },
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# 笔记

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick 所指定的回调函数中执行。
上次更新: 2024/02/28, 13:00:35

← src_TodoList_pubsub src_过渡与动画→

最近更新
01
SpringBoot 快速实现 api 加密!
03-21
02
SpringBoot整合SQLite
03-07
03
SpringBoot配置使用H2数据库的简单教程
02-21
更多文章>
Theme by Vdoing | Copyright © 2017-2024 | 点击查看十年之约 | 豫ICP备2022014539号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式