一、实现样式
二、文档结构
三、核心点
todos分为外面整体的
App.vue
,里面包含todoHeader.vue、List.vue>Item.vue、TodoFooter.vue
采用localStorage本地存储,对数据todos进行深度监视watch,只要数据一改变,就对localStorage本地存储数据进行修改
watch:{
todos:{
deep:true,
handler(value){
localStorage.setItem('todoList',JSON.stringify(value))
}
}
整体数据存放在
App.vue
里面,使用props
方法将数据对子组件进行传递,使用全局事件总线来实现子对父的通信,
// 安装事件总线
beforeCreate() {
Vue.prototype.$bus = this
}
安装
nanoid
,用来生成唯一标识id
// 生成一个对象
const todo = {id:nanoid(),title:this.$refs.todotext.value,done:false};
四、附录
4.1 main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el: '#root',
render: h => h(App),
// 安装事件总线
beforeCreate() {
Vue.prototype.$bus = this
}
})
4.2 App.vue
<template>
<div class="main">
<h1>TODOS</h1>
<TodoHeader/>
<List
:todos="todos"
:deletetodo="deletetodo"
/>
<TodoFooter
:todos="todos"
/>
</div>
</template>
<script>
import TodoHeader from './components/TodoHeader'
import List from './components/List.vue'
import TodoFooter from './components/TodoFooter.vue'
export default {
name:'App',
components:{
TodoHeader,
List,
TodoFooter,
},
data(){
return{
todos:JSON.parse(localStorage.getItem('todoList'))||[]
}
},
watch:{
todos:{
deep:true,
handler(value){
localStorage.setItem('todoList',JSON.stringify(value))
}
}
},
methods:{
// 添加数据到todos
addtodo(todo){
this.todos.unshift(todo)
},
// 删除todos
deletetodo(id){
// console.log(id);
//过滤器 过滤id
if(confirm('你确定删除吗?')){
this.todos = this.todos.filter(todo=>todo.id !==id)
}
},
// 全选 取消全选
checkAlltodo(done){
this.todos.forEach(todo=>{
todo.done = done
})
// console.log(done);
},
// 删除已完成的事情
clearDonetodo(){
this.todos = this.todos.filter(todo=>{
return !todo.done
})
},
// 编辑
edittodo(id,value){
// console.log(id,value);
this.todos.forEach(todo=>{
if(todo.id === id)
todo.title = value
})
}
},
mounted(){
this.$bus.$on('addtodo',this.addtodo)
this.$bus.$on('checkAlltodo',this.checkAlltodo)
this.$bus.$on('deletetodo',this.deletetodo)
this.$bus.$on('clearDonetodo',this.clearDonetodo)
this.$bus.$on('edittodo',this.edittodo)
},
beforeDestroy(){
this.$bus.$off('addtodo')
this.$bus.$off('checkAlltodo')
this.$bus.$off('deletetodo')
this.$bus.$off('clearDonetodo')
this.$bus.$off('edittodo')
}
}
</script>
<style>
.main{
/* text-align: center; */
width: 600px;
padding: 20px 8px;
border: 1px solid #DDDDDD;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.95);;
/* margin-top: 120px; */
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 5%);
}
*{
margin: 0 auto;
box-sizing: border-box;
}
body{
width: 100%;
height: 100vh; /* 重点一 */
margin: 0 auto;
background-image: url(./assets/back.jpg);
background-repeat: no-repeat;
background-size: cover; /* 重点二 */
overflow: auto;
padding-top: 120px;
}
h1{
text-align: center;
margin-bottom: 20px;
}
</style>
4.3 todoHeader.vue
<template>
<div class="todoHeader">
<input type="text" maxlength="30" ref="todotext" placeholder="请输入你的任务" @keydown.enter="inputtodo()">
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name: 'TodoHeader',
methods:{
// 输入框输入
inputtodo(){
// 判断是否为空
if(this.$refs.todotext.value.trim()){
// 生成一个对象
const todo = {id:nanoid(),title:this.$refs.todotext.value,done:false};
// this.addtodo(todo)
this.$bus.$emit('addtodo',todo)
// console.log(todo);
this.$refs.todotext.value = ''
}else{
alert('输入内容不能为空!!!')
this.$refs.todotext.value = ''
}
}
}
}
</script>
<style scoped>
.todoHeader input{
height: 40px;
width: 580px;
margin-bottom: 20px;
padding: 0 8px;
font-size: 16px;
border: 1px solid #ddd;
outline: none;
border-radius: 4px;
}
</style>
4.4 List.vue
<template>
<div class="todoList">
<ul>
<Item
v-for="(todo) in todos"
:key="todo.id"
:todo="todo"
/>
</ul>
</div>
</template>
<script>
import Item from './Item.vue'
export default {
name:'List',
components:{
Item
},
props:['todos']
}
</script>
<style>
ul{
padding: 0;
margin: 0;
}
.todoList{
margin-bottom: 30px;
}
</style>
4.5 Item.vue
<template>
<li class="todoItem" :class="{'tododone':todo.done}">
<input type="checkbox" v-model="todo.done">
<span v-show="!todo.isEdit">{{todo.title}}</span>
<input type="text" maxlength="30"
v-show="todo.isEdit"
:value="todo.title"
ref="inputtext"
@blur="inputblur"
class="editItem"
>
<a href="javascript:;" >
<img src="../assets/edit.png" @click="edittodo(todo)">
<img src="../assets/delete.png" @click="deletetodo(todo.id)">
</a>
</li>
</template>
<script>
export default {
name:'Item',
methods:{
// 删除
deletetodo(id){
this.$bus.$emit('deletetodo',id)
},
// 编辑
edittodo(todo){
if(todo.hasOwnProperty('isEdit')){
todo.isEdit = true;
}else{
this.$set(todo,'isEdit',true)
}
// 节点渲染完 获取焦点
this.$nextTick(function(){
this.$refs.inputtext.focus()
} )
},
//失去焦点
inputblur(todo){
this.todo.isEdit = false
// console.log(this.todo.id,this.$refs.inputtext.value);
if(this.$refs.inputtext.value.trim()){
this.$bus.$emit('edittodo',this.todo.id,this.$refs.inputtext.value)
}else{
alert('编辑内容不能为空!!!')
}
}
},
props:['todo']
}
</script>
<style scoped>
.todoItem{
list-style: none;
height: 48px;
width: 578px;
border: 1px solid #ddd;
line-height: 48px;
padding: 0 10px;
margin-bottom:-1px;
position: relative;
}
.tododone{
background-color: #eee;
text-decoration: line-through;
color: #ccc;
}
.todoItem a{
color: #ccc;
text-decoration: none;
position:absolute;
right:6px;
top: 4px;
display: none;
}
.todoItem img{
width: 20px;
height: 20px;
margin-right: 8px;
}
.todoItem input{
margin-right: 6px;
}
.editItem{
outline: none;
font-size: 16px;
border: none;
height: 30px;
width: 400px;
margin-left: -2px;
background-color:transparent;
}
.todoItem:hover{
background-color: #eee;
}
.todoItem:hover a{
display: inline;
}
</style>
4.6 todoFooter.vue
<template>
<div class="todoFooter" v-show="total>0">
<input type="checkbox" v-model="isAll">
<span>已完成{{donetotal}}/全部{{total}}</span>
<button @click="cleardone">清除已完成任务</button>
</div>
</template>
<script>
export default {
name:'TodoFooter',
data(){
return{
done:10,
all:15
}
},
computed:{
// 已完成
donetotal(){
let i = 0
this.todos.forEach(todo => {
if(todo.done){
i++
}
});
return i
},
// 总数
total(){
return this.todos.length
},
// 是否全选
isAll:{
get(){
return this.donetotal == this.total && this.donetotal>0
},
set(value){
// this.checkAlltodo(value)
this.$bus.$emit('checkAlltodo',value)
}
}
},
methods:{
cleardone(){
if(confirm('请确认是否删除所有已完成事项?'))
// this.clearDonetodo()
this.$bus.$emit('clearDonetodo')
}
},
props:['todos']
}
</script>
<style scoped>
.todoFooter{
margin-left: 12px;
position: relative;
height: 34px;
line-height: 34px;
}
.todoFooter input{
margin-right: 6px;
}
.todoFooter button{
width: 120px;
height: 34px;
font-size: 14px;
background-color: #1A73E8;
color: white;
border: none;
border-radius: 6px;
position: absolute;
right: 10px;
/* bottom: 0px; */
cursor: pointer;
}
</style>