VueCLI核心知识综合案例TodoList
目录
1 拿到一个功能模块首先需要拆分组件:
2 使用组件实现静态页面的效果
3 分析数据保存在哪个组件
4 实现添加数据
5 实现复选框勾选
6 实现数据的删除
7 实现底部组件中数据的统计
8 实现勾选全部的小复选框来实现大复选框的勾选
9 实现勾选大复选框来实现所有的小复选框都被勾选
10 清空所有数据
11 实现案例中的数据存入本地存储
12 案例中使用自定义事件完成组件间的数据通信
13 案例中实现数据的编辑
14 实现数据进出的动画效果
【分析】组件化编码的流程
1. 实现静态组件:抽取组件,使用组件实现静态页面效果
2.展示动态数据:
2.1 数据的类型、名称是什么?
2.2 数据保存在哪个组件?
3.交互---从绑定事件监听开始
1 拿到一个功能模块首先需要拆分组件:
2 使用组件实现静态页面的效果
【main.js】
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({el: '#app',render: h => h(App)
})
【MyHeader】
<template><div class="todo-header"><input type="text"/></div>
</template><script>export default {name: 'MyHeader',}
</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>
【Item】
<template> <li><label><input type="checkbox"/><span v-for="todo in todos" :key="todo.id">{{todo.title}}</span></label><button class="btn btn-danger">删除</button></li>
</template><script>export default {name: 'Item',data() {return {todos: [{id: '001', title: '吃饭', done: true},{id: '002', title: '学习', done: false},{id: '003', title: '追剧', done: true},]}},}
</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: rgb(196, 195, 195);}li:hover button{display: block;}
</style>
【List】
<template><ul class="todo-main"><Item></Item><Item></Item></ul>
</template><script>import Item from './Item.vue'export default {name: 'List',components:{Item},}
</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>
【MyFooter】
<template><div class="todo-footer"><label><input type="checkbox"/></label><span><span>已完成 0</span> / 3</span><button class="btn btn-danger">清除已完成任务</button></div>
</template><script>export default {name: 'MyFooter',}
</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>
【App】
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader></MyHeader><List></List><MyFooter></MyFooter></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import List from './components/List.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,List,MyFooter} }
</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-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>
通过以上代码就可以实现静态页面的效果了!!!
3 分析数据保存在哪个组件
在上述代码中数据是保存在Item组件中的,但是如果想要在后续实现一系列交互效果:在MyHeader组件中需要添加数据,而MyHeader组件和Item组件没有直接的关系, 就当前学习阶段的知识而言,并不能实现这两个组件之间的通信(后续会有解决方案),同理MyFooter也一样。
【分析】因为App组件是所有组件的父组件,所以数据放在App组件中,再使用props配置,所有的子组件就都可以访问到。
【App】(同时需要将数据传递到Item组件中,在当前阶段只能通过props配置一层一层往下传,所以是 App-->List,List-->Item)
1. 实现 App-->List 传递todos数据
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader></MyHeader><List :todos="todos"></List><MyFooter></MyFooter></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import List from './components/List.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,List,MyFooter},data() {return {todos: [{id: '001', title: '吃饭', done: true},{id: '002', title: '学习', done: false},{id: '003', title: '追剧', done: true},]}},}
</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-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>
2. List接受todos数据
<template><ul class="todo-main"><Item v-for="todo in todos" :key="todo.id"></Item></ul>
</template><script>import Item from './Item.vue'export default {name: 'List',components:{Item},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>
通过上述的代码便可以根据数据的数量来渲染出几个Item了,但是此时Item里面是没有内容的,所以需要 List-->Item 再次传递每条数据
3. 实现 List-->Item 传递todo数据
<template><ul class="todo-main"><Item v-for="todo in todos" :key="todo.id" :todo="todo"></Item></ul>
</template><script>import Item from './Item.vue'export default {name: 'List',components:{Item},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>
4. Item接受todo数据
<template> <li><label><input type="checkbox"/><span>{{todo.title}}</span></label><button class="btn btn-danger">删除</button></li>
</template><script>export default {name: 'Item',// 声明接收todo对象props:['todo'],}
</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: rgb(196, 195, 195);}li:hover button{display: block;}
</style>
4 实现添加数据
【App】定义接收数据的回调函数
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"></MyHeader><List :todos="todos"></List><MyFooter></MyFooter></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import List from './components/List.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,List,MyFooter},data() {return {todos: [{id: '001', title: '吃饭', done: true},{id: '002', title: '学习', done: false},{id: '003', title: '追剧', done: true},]}},methods: {// 添加一个todoaddTodo(todoObj) {this.todos.unshift(todoObj)}}}
</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-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>
【MyHeader】实现添加数据的方法
<template><div class="todo-header"><!-- 绑定键盘回车事件 --><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add" v-model="title"/></div>
</template><script>import {nanoid} from 'nanoid' // 生成idexport default {name: 'MyHeader',data() {return {title: ''}},props: ['addTodo'], // 接收父组件传过来的addTodo函数methods: {add(e) {// 校验数据if (!this.title.trim()) return alert('输入不能为空')// 将用户的输入包装成为一个todo对象const todoObj = {id: nanoid(),/* title: e.target.value, */title: this.title,done: false}console.log(todoObj)// console.log(e.target.value)// console.log(this.title)// 通知App组件去添加一个todo对象this.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>
5 实现复选框勾选
【App】也是要逐层传递
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"></MyHeader><List :todos="todos" :changeTodo="changeTodo"></List><MyFooter></MyFooter></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import List from './components/List.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,List,MyFooter},data() {return {todos: [{id: '001', title: '吃饭', done: true},{id: '002', title: '学习', done: false},{id: '003', title: '追剧', done: true},]}},methods: {// 添加一个todoaddTodo(todoObj) {this.todos.unshift(todoObj)},// 勾选或者取消勾选一个todochangeTodo(id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done})},}}
</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-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>
【List】
<template><ul class="todo-main"><Item v-for="todo in todos" :key="todo.id" :todo="todo":changeTodo="changeTodo"></Item></ul>
</template><script>import Item from './Item.vue'export default {name: 'List',components:{Item},props: ['todos', 'changeTodo']}
</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>
【Item】
<template> <li><label><input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/><span>{{todo.title}}</span></label><button class="btn btn-danger">删除</button></li>
</template><script>export default {name: 'Item',// 声明接收todo对象props:['todo', 'changeTodo'],methods: {// 勾选 or 取消勾选handleCheck(id) {// 通知 App组件将对应的todo对象的状态改变this.changeTodo(id)}}}
</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: rgb(196, 195, 195);}li:hover button{display: block;}
</style>
6 实现数据的删除
【App】
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"></MyHeader><List :todos="todos" :changeTodo="changeTodo" :deleteTodo="deleteTodo"></List><MyFooter></MyFooter></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import List from './components/List.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,List,MyFooter},data() {return {todos: [{id: '001', title: '吃饭', done: true},{id: '002', title: '学习', done: false},{id: '003', title: '追剧', done: true},]}},methods: {// 添加一个todoaddTodo(todoObj) {this.todos.unshift(todoObj)},// 勾选或者取消勾选一个todochangeTodo(id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done})},// 删除一个tododeleteTodo(id) {this.todos = this.todos.filter((todo) => todo.id !== id)},}}
</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-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>
【List】
<template><ul class="todo-main"><Item v-for="todo in todos" :key="todo.id" :todo="todo":changeTodo="changeTodo":deleteTodo="deleteTodo"></Item></ul>
</template><script>import Item from './Item.vue'export default {name: 'List',components:{Item},props: ['todos', 'changeTodo', 'deleteTodo']}
</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>
【Item】
<template> <li><label><input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/><span>{{todo.title}}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button></li>
</template><script>export default {name: 'Item',// 声明接收todo对象props:['todo', 'changeTodo', 'deleteTodo'],methods: {// 勾选 or 取消勾选handleCheck(id) {// 通知 App组件将对应的todo对象的状态改变this.changeTodo(id)},// 删除操作handleDelete(id) {// console.log(id)if (confirm("确定删除吗?")) {// 通知App删除this.deleteTodo(id)}}}}
</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: rgb(196, 195, 195);}li:hover button{display: block;}
</style>
7 实现底部组件中数据的统计
【分析】如果想要统计数据的数量,就需要将数据传递到MyFooter组件中
【App】
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"></MyHeader><List :todos="todos" :changeTodo="changeTodo" :deleteTodo="deleteTodo"></List><MyFooter:todos="todos"></MyFooter></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import List from './components/List.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,List,MyFooter},data() {return {todos: [{id: '001', title: '吃饭', done: true},{id: '002', title: '学习', done: false},{id: '003', title: '追剧', done: true},]}},methods: {// 添加一个todoaddTodo(todoObj) {this.todos.unshift(todoObj)},// 勾选或者取消勾选一个todochangeTodo(id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done})},// 删除一个tododeleteTodo(id) {this.todos = this.todos.filter((todo) => todo.id !== id)},}}
</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-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>
【MyFooter】
在这个组件中可以使用计算属性实现数据的总长度和被勾选的数据的计算
<template><div class="todo-footer" v-if="todosLength"><label><input type="checkbox"/></label><span><span>已完成 {{doneTotal}}</span> / {{todosLength}}</span><button class="btn btn-danger">清除已完成任务</button></div>
</template><script>
export default {name: 'MyFooter',props: ['todos'],computed: {todosLength() {return this.todos.length},doneTotal() {return this.todos.filter(todo => todo.done).length// 也可以使用下面求和来实现// return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)}}
}
</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>
8 实现勾选全部的小复选框来实现大复选框的勾选
:checked="isAll"
isAll也是通过计算属性计算得来的
【MyFooter】
<template><div class="todo-footer" v-if="todosLength"><label><input type="checkbox" :checked="isAll"/></label><span><span>已完成 {{doneTotal}}</span> / {{todosLength}}</span><button class="btn btn-danger">清除已完成任务</button></div>
</template><script>
export default {name: 'MyFooter',props: ['todos'],computed: {todosLength() {return this.todos.length},doneTotal() {return this.todos.filter(todo => todo.done).length// 也可以使用下面求和来实现// return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)},isAll() {return this.doneTotal === this.todosLength && this.todosLength > 0}, }
}
</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>
9 实现勾选大复选框来实现所有的小复选框都被勾选
【App】
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"></MyHeader><List :todos="todos" :changeTodo="changeTodo" :deleteTodo="deleteTodo"></List><MyFooter:todos="todos":checkAllTodo="checkAllTodo"></MyFooter></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import List from './components/List.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,List,MyFooter},data() {return {todos: [{id: '001', title: '吃饭', done: true},{id: '002', title: '学习', done: false},{id: '003', title: '追剧', done: true},]}},methods: {// 添加一个todoaddTodo(todoObj) {this.todos.unshift(todoObj)},// 勾选或者取消勾选一个todochangeTodo(id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done})},// 删除一个tododeleteTodo(id) {this.todos = this.todos.filter((todo) => todo.id !== id)},// 全选or取消全选checkAllTodo(done) {this.todos.forEach((todo) => {todo.done = done})},}}
</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-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>
【MyFooter】
<template><div class="todo-footer" v-if="todosLength"><label><input type="checkbox" :checked="isAll" @change="checkAll"/></label><span><span>已完成 {{doneTotal}}</span> / {{todosLength}}</span><button class="btn btn-danger">清除已完成任务</button></div>
</template><script>
export default {name: 'MyFooter',props: ['todos', 'checkAllTodo'],computed: {todosLength() {return this.todos.length},doneTotal() {return this.todos.filter(todo => todo.done).length// 也可以使用下面求和来实现// return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)},isAll() {return this.doneTotal === this.todosLength && this.todosLength > 0}, },methods: {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>
10 清空所有数据
【App】
<template><div id="root"><div class="todo-container"><div class="todo-wrap"><MyHeader :addTodo="addTodo"></MyHeader><List :todos="todos" :changeTodo="changeTodo" :deleteTodo="deleteTodo"></List><MyFooter:todos="todos":checkAllTodo="checkAllTodo":clearAllTodo="clearAllTodo"></MyFooter></div></div></div>
</template><script>import MyHeader from './components/MyHeader.vue'import List from './components/List.vue'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,List,MyFooter},data() {return {todos: [{id: '001', title: '吃饭', done: true},{id: '002', title: '学习', done: false},{id: '003', title: '追剧', done: true},]}},methods: {// 添加一个todoaddTodo(todoObj) {this.todos.unshift(todoObj)},// 勾选或者取消勾选一个todochangeTodo(id) {this.todos.forEach((todo) => {if (todo.id === id) todo.done = !todo.done})},// 删除一个tododeleteTodo(id) {this.todos = this.todos.filter((todo) => todo.id !== id)},// 全选or取消全选checkAllTodo(done) {this.todos.forEach((todo) => {todo.done = done})},// 清空所有已经完成的todoclearAllTodo() {this.todos = this.todos.filter((todo) => !todo.done)}}}
</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-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>
【MyFooter】
<template><div class="todo-footer" v-if="todosLength"><label><input type="checkbox" :checked="isAll" @change="checkAll"/></label><span><span>已完成 {{doneTotal}}</span> / {{todosLength}}</span><button class="btn btn-danger" @click="clearTodo">清除已完成任务</button></div>
</template><script>
export default {name: 'MyFooter',props: ['todos', 'checkAllTodo', 'clearAllTodo'],computed: {todosLength() {return this.todos.length},doneTotal() {return this.todos.filter(todo => todo.done).length// 也可以使用下面求和来实现// return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)},isAll() {return this.doneTotal === this.todosLength && this.todosLength > 0}, },methods: {checkAll(e) {this.checkAllTodo(e.target.checked)},clearTodo() {this.clearAllTodo()}}
}
</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>
11 实现案例中的数据存入本地存储
【分析】首先我们要知道什么时候需要将数据存入本地存储?所以这就用到了watch监听,当todos的值发生变化时,将新的值存入本地存储。
又因为当我们勾选复选框时,我们发现本地存储中的 done 值并没有发生变化?这主要是因为监听默认只会监听第一层,如果想要监听对象中某个数据发生变化时,就需要深度监视了。
【App】
这里使用 || 运算可以防止一开始本地存储中没有数据而报错
12 案例中使用自定义事件完成组件间的数据通信
这边以添加数据为例
【App】
给发送数据的组件绑定自定义事件
<MyHeader @addTodo="addTodo"></MyHeader>...methods: {// 添加一个todoaddTodo(todoObj) {this.todos.unshift(todoObj)},
}
【MyHeader】
13 案例中实现数据的编辑
需求分析:当点击编辑按钮时,变成input表单修改数据,此时编辑按钮隐藏,当失去焦点时,编辑完成,显示编辑后的数据,同时编辑按钮显示。
这边使用全局事件总线来实现通信
【App】
methods: {...// 更改updateTodo(id,title) {this.todos.forEach((todo) => {if (todo.id === id) todo.title = title})},...},mounted() {this.$bus.$on('updateTodo', this.updateTodo)},beforeDestroy() {this.$bus.$off('updateTodo')}
【Item】
因为如果想要失去焦点时实现数据的修改,那么你必须提前获取焦点,但是由于Vue的执行机制,当Vue底层监视到数据发生改变时,它并不会立即去重新渲染模板,而是继续执行后面的代码,所以如果不加以处理的话,直接获取焦点,肯定会报错,因为页面中的元素还没有加载解析出,找不到获取焦点的input元素,所以可以通过以下的代码实现
this.$nextTick(function() { // 告诉Vue,DOM渲染完毕后,再执行focus()方法this.$refs.inputTiltle.focus()
})
14 实现数据进出的动画效果
【Item】
使用<transtion></transtion>标签包裹
相关文章:

VueCLI核心知识综合案例TodoList
目录 1 拿到一个功能模块首先需要拆分组件: 2 使用组件实现静态页面的效果 3 分析数据保存在哪个组件 4 实现添加数据 5 实现复选框勾选 6 实现数据的删除 7 实现底部组件中数据的统计 8 实现勾选全部的小复选框来实现大复选框的勾选 9 实现勾选大复选框来…...

关于cuda路径问题
问题:Could not load dynamic library ‘libcudart.so.11.0’ 原因:调用系统环境下的cuda但系统环境没有装cuda 解决: 1.在系统环境装cuda,但如果每权限就不好操作; 2.用虚拟环境装好的cuda路径丢给环境变量 暂时性&am…...

六、Spring/Spring Boot整合ActiveMQ
Spring/Spring Boot整合ActiveMQ 一、Spring整合ActiveMQ1.pom.xml2.Queue - 队列2.1 applicationContext.xml2.2 生产者2.3 消费者 3.Topic - 主题3.1 applicationContext.xml3.2 生产者3.3 消费者 4.消费者 - 监听器4.1 编写监听器类4.2 配置监听器4.3 生产者消费者一体 二、…...

树莓派4B(Raspberry Pi 4B)使用docker搭建springBoot/springCloud服务
树莓派4B(Raspberry Pi 4B)使用docker搭建springBoot/springCloud服务 前提:本文基于Ubuntu,Java8,SpringBoot 2.6.13讲解 准备工作 准备SpringBoot/SpringCloud项目jar包 用 maven 打包springBoot/springCloud项目&…...

数据库设计、JDBC、数据库连接池
数据库设计 数据库设计概念 数据库设计就是根据业务 系统的具体需求,结合我们所选用的DBMS,为这个业务系统构造出最优的数据存储模型。建立数据库中的表结构以及表与表之间的关联关系的过程。有哪些表?表里有哪些字段?表和表之间有什么关系? 数据库设计的步骤…...

SpringBoot实现OneDrive文件上传
SpringBoot实现OneDrive文件上传 源码 OneDriveUpload: SpringBoot实现OneDrive文件上传 获取accessToken步骤 参考文档:针对 OneDrive API 的 Microsoft 帐户授权 - OneDrive dev center | Microsoft Learn 1.访问Azure创建应用Microsoft Azure,使…...

C++初阶:容器适配器介绍、stack和queue常用接口详解及模拟实现
介绍完了list类的相关内容后:C初阶:适合新手的手撕list(模拟实现list) 接下来进入新的篇章,stack和queue的介绍以及模拟: 文章目录 1.stack的初步介绍2.stack的使用3.queue的初步介绍4.queue的使用5.容器适…...

GRUB and the Boot Process on UEFI-based x86 Systems
background info : BIOS and UEFI-CSDN博客 The UEFI-based platform reads the partition table on the system storage and mounts the EFI System Partition (ESP), a VFAT partition labeled with a particular globally unique identifier (GUID). The ESP contains EFI a…...

2.C语言——输入输出
1.字符输入输出函数 1.输入:getchar() 字面意思,接收单个字符,使用方法 char a; a getchar();实际上效果等同于char a; scanf("%c",&a);2.输出:putchar() 2.格式化输入输出函数 1.输入:scanf() 格式: scanf(“格式控制…...

MySQL篇之SQL优化
一、表的设计优化 表的设计优化(参考阿里开发手册《嵩山版》): 1. 比如设置合适的数值(tinyint int bigint),要根据实际情况选择。 2. 比如设置合适的字符串类型(char和varchar)…...

QGis —— 1、Windows10下载安装QGis及插件
QGis官网 QGIS(自由开源的地理信息系统)是一个专业的GIS应用程序,它建立在免费和开源软件(FOSS)之上,并为此而自豪。QGIS 是一个方便使用的开源地理信息系统 (GIS),根据 GNU 通用公共许可授权。…...

【打工日常】使用docker部署Dashdot工具箱
一、Dashdot介绍 dashdot是一个简洁清晰的服务器数据仪表板,基于React实现 ,主要是显示操作系统、进程、存储、内存、网络这五个的数据。 二、本次实践介绍 1. 本次实践简介 本次实践部署环境为个人测试环境 2. 本地环境规划 本次实践环境规划…...

使用client-only 解决组件不兼容SSR问题
目录 前言 一、解决方案 1.基于Nuxt 框架的SSR应用 2.基于vue2框架的应用 3.基于vue3框架的应用 二、总结 往期回顾 前言 最近在我的单页面SSR应用上开发JSON编辑器功能,在引入组件后直接客户端跳转OK,但是在直接加载服务端渲染的时候一直报这…...

基于Java SSM框架实现网上报名系统项目【项目源码+论文说明】
基于java的SSM框架实现网上报名系统演示 摘要 随着互联网时代的到来,同时计算机网络技术高速发展,网络管理运用也变得越来越广泛。因此,建立一个B/S结构的网上报名系统,会使网上报名系统工作系统化、规范化,也会提高网…...

7.1 Qt 中输入行与按钮
目录 前言: 技能: 内容: 参考: 前言: line edit 与pushbotton的一点联动 当输入行有内容时,按钮才能使用,并能读出输入行的内容 技能: pushButton->setEnabled(false) 按钮不…...

云计算基础-网络虚拟化
虚拟交换机 什么是虚拟交换机 虚拟交换机是一种运行在虚拟化环境中的网络设备,其运行在宿主机的内存中,通过软件方式在宿主机内部实现了部分物理交换机的功能,如 VLAN 划分、流量控制、QoS 支持和安全功能等网络管理特性 虚拟交换机在云平…...

166基于matlab的通过峭度指标与互相关系数筛选IMF进行SVD分解去噪
基于matlab的通过峭度指标与互相关系数筛选IMF进行SVD分解去噪,分辨虚假imf,提取最大峭度imf图。输出去噪前后时域及其包络谱结果。程序已调通,可直接运行。 166 matlab SVD去噪 IMF筛选 包络谱 (xiaohongshu.com)...

第六十三天 服务攻防-框架安全CVE复现DjangoFlaskNode.JSJQuery
第六十三天 服务攻防-框架安全&CVE复现&Django&Flask&Node.JS&JQuery 知识点: 中间件及框架列表: IIS,Apache,Nginx,Tomcat,Docker,K8s,Weblogic.JBoos,WebSphere, Jenkins,GlassFish,Jetty,Jira,Struts2,Laravel,Solr,Shiro,Thin…...

最大子序和+旅行问题——单调队列
一、最大子序和 输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。 注意: 子序列的长度至少是 1。 输入 第一行输入两个整数 n,m (1 ≤ n,m ≤ 300000)。 第二行输入 n 个数,代…...

Unity设备分级策略
Unity设备分级策略 前言 之前自己做的设备分级策略,在此做一个简单的记录和思路分享。希望能给大家带来帮助。 分级策略 根据拟定的评分标准,预生成部分已知机型的分级信息,且保存在包内;如果设备没有被评级过,则优…...

自己在开发AI应用的过程总结的 Prompt - 持续更新
自己在开发AI应用的过程总结的 Prompt - 持续更新 0. 引言1. 让模型以"中文"进行回复2. 控制模型仅输出"hi"3. 让模型"提供简单、清晰而具体的回答"4. 让模型"在最后说谢谢" 0. 引言 我想,我们多半有着相似的经历…...

STM32——OLED菜单
文章目录 一.补充二. 二级菜单代码 简介:首先在我的51 I2C里面有OLED详细讲解,本期代码从51OLED基础上移植过来的,可以先看完那篇文章,在看这个,然后按键我是用的定时器扫描不会堵塞程序,可以翻开我的文章有单独的定时…...

Open CASCADE学习|布尔运算后消除内部拓扑
在CAD建模中,布尔运算是一种逻辑运算方法,通过这种方法,可以创建、修改或组合几何对象。布尔运算主要包括并集(UNION)、交集(INTERSECT)和差集(SUBTRACT)三种运算。 并集…...

【数据仓库】主题域和数据域
数据域与主题域区别 https://www.cnblogs.com/datadance/p/16898254.html 数据域是自下而上,以业务数据视角来划分数据,一般进行完业务系统数据调研之后就可以进行数据域的划分。针对公共明细层(DWD)进行主题划分。主题域则自上而…...

C#,二分法(Bisection Method)求解方程的算法与源代码
1 二分法 二分法是一种分治算法,是一种数学思维。 对于区间[a,b]上连续不断且f(a)f(b)<0的函数yf(x),通过不断地把函数f(x)的零点所在的区间…...

Portainer安装/快速上手
前置: 管理docker容器的工具 Portainer: Container Management Software for Kubernetes and Docker https://docs.portainer.io/v/ce-2.9/start/install/server/docker/linux 官网安装教程 Install Portainer CE with Docker on Linux - Portainer Documentat…...

恢复被.target勒索病毒加密的数据文件:拒绝向.target勒索病毒支付赎金
引言: 在当今数字时代,勒索病毒已成为网络安全领域的一大威胁,而.target勒索病毒是其中引起广泛关注的一种变种。本文将深入探讨.target勒索病毒的特点以及被其加密的数据文件恢复方法。数据的重要性不容小觑,您可添加我们的技术…...

【Linux网络编程六】服务器守护进程化Daemon
【Linux网络编程六】服务器守护进程化Daemon 一.背景知识:前台与后台二.相关操作三.Linux的进程间关系四.自成会话五.守护进程四步骤六.服务器守护进程化 一.背景知识:前台与后台 核心知识就是一个用户在启动Linux时,都会给一个session会话&a…...

MySQL之json数据操作
1 MySQL之JSON数据 总所周知,mysql5.7以上提供了一种新的字段格式json,大概是mysql想把非关系型和关系型数据库一口通吃,所以推出了这种非常好用的格式,这样,我们的很多基于mongoDB的业务都可以用mysql去实现了。当然…...

【大厂AI课学习笔记】【2.1 人工智能项目开发规划与目标】(5)数据管理
今天学习了数据管理,以及数据管理和数据治理的区别和联系。 数据管理:利用计算机硬件和软件技术对数据进行有效的收集、存储、处理和应用的过程其目的在于充分有效地发挥数据的作用。 实现数据有效管理的关键是数据组织。 数据管理和数据治理的区别&am…...