最终效果
源码:
App.vue组件
<template>
<div class="todo-container">
<div class="todo-wrap">
<Header @addTodo="addTodo"></Header>
<List :todos="todos">
</List>
<Footer :todos="todos" @SelectAll="SelectAll" @deleteComplete="deleteComplete"></Footer>
</div>
</div>
</template>
<script>
import Header from "./components/Header";
import Footer from "./components/Footer";
import List from "./components/List";
// 导入PubSub
import Pubsub from 'pubsub-js'
export default {
name: "App",
components: {Footer, List, Header},
comments:{
Header,
Footer,
List
},
mounted(){
//发布消息
Pubsub.subscribe("deleteTodo",(msg,index)=>{
console.log(index)
this.deleteTodo(index)
})
// 这里也可以给某个元素绑定事件,
//譬如 挂载时直接给hander标签绑定事件, 这样会直接传递给子组件
// this.$refs.hander.$on('addTodo',this.addTodo)
},
data(){
return {
todos:[
{name:'吃饭',complete:true},
{name:'睡觉',complete:false},
{name:'codding',complete:false}
]
}
},
methods:{
addTodo(todo){
this.todos.unshift(todo)
},
deleteTodo(index){
this.todos.splice(index,1)
},
// 全选/ 全不选
SelectAll(flag){
this.todos.forEach(item=>{item.complete=flag})
},
// 删除已完成任务
deleteComplete(){
// filter 过滤已经完成的,取反,重新给数组赋值
this.todos=this.todos.filter((item)=>!item.complete)
}
},
}
</script>
<style scoped>
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
margin-top: 50px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
Header.vue 组件
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="addTodo" v-model.trim="title"></input>
</div>
</template>
<script>
export default {
name: "Header",
data(){
return{
title:'',
complete:false
}
},
methods:{
addTodo(){
if(this.title==='' || this.title===null){
alert('标题不能为空')
return
}
const todos={name:this.title,complete:this.complete}
// 调用父组件传入自定义事件,事件名要加 ''
this.$emit('addTodo',todos)
this.title=''
}
}
}
</script>
<style scoped>
.todo-header input {
width: 100%;
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>
List.Vue 组件
<template>
<div>
<ul class="todo-main">
<Item v-for="(todo,index) in todos" :key="index" :todo="todo" :index="index"></Item>
</ul>
</div>
</template>
<script>
import Item from "./Item";
export default {
name: "List",
components: {Item},
props:{
todos:{
type:Array,
required:false
}
}
}
</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>
Item.vue组件\
<template>
<div>
<li @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)" :style="{backgroundColor:color}">
<label>
<input type="checkbox" v-model="todo.complete">
<span>{{todo.name}}</span>
</label>
<button class="btn btn-danger" v-show="isshow" @click="deleteTodo(index)">删除</button>
</li>
</div>
</template>
<script>
export default {
name: "Item",
props:{
todo:{
type:Object,
required:false
},
index:{
type:Number,
required: true
}
},
data(){
return {
color:'white',
isshow:false
}
},
methods:{
handleEnter(flag){
if(flag){
this.color="gainsboro"
this.isshow=true
}else{
this.color="white"
this.isshow=false
}
},
deleteTodo(index){
const { todo } =this
// 第一个参数message订阅的事件名
if(window.confirm(`确定删除${todo.name}吗?`)){
PubSub.publish('deleteTodo',index)
}
}
}
}
</script>
<style scoped>
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
margin-top: 3px;
}
li:before {
content: initial;
}
</style>