子组件MyHeader
组件结构
MyHeader组件结构
具体代码
<template>
<!-- todo头部输入框-->
<div class="todo-header">
<!-- 待办事项输入框-->
<!-- 显示提示信息,自动获取焦点,敲击回车后完成待办事项的添加,利用v-model实现双向绑定-->
<input
type="text"
placeholder="往我这填要干的事,填完以后按回车"
autofocus="autofocus"
v-model="title"
@keyup.enter="add"
/>
</div>
</template>
<script>
// 引入nanoid库,用于生成作为待办事项唯一id的uuid
import {nanoid} from 'nanoid'
export default {
name: 'MyHeader',
data () {
return {
title: '' // 待办事项内容初始值为空,利用v-model实现数据双向绑定后可根据用户输入内容实现动态更新
}
},
methods: {
// 添加待办
add () {
// 校验数据 删去输入数据前后空格 若输入为空则发出提醒
if (!this.title.trim()) return alert('输入不能为空')
// 将用户输入包装成一个todo对象,对象中包括id,输入内容和完成状态
const todoObj = {id: nanoid(), title: this.title, completed: false}
// 使用$emit让父组件App监听到addTodo事件,即去添加一个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>
子组件MyList
组件结构
MyList组件结构
具体代码
<template>
<!-- todo列表 -->
<ul class="todo-main">
<!-- 待办项动画,用transition-group包裹来控制多个事项的动画效果, -->
<!-- transition-group必须要设置key值,appear用于添加入场效果-->
<transition-group name="todo" appear>
<!-- todo列表中的todo事件 -->
<!-- 令每个todos对象数组中的todoObj对象的key值都与其id相同,并令todo为todoObj -->
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
/>
</transition-group>
</ul>
</template>
<script>
// 引入组件
import MyItem from './MyItem'
export default {
name: 'MyList',
// 注册组件
components: {MyItem},
// 声明接收App传递过来的数据
props: ['todos']
}
</script>
<!--scoped表示此样式仅作用于当下的模块-->
<style scoped>
/*main*/
/*todo列表主体样式*/
.todo-main {
margin-left: 0;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0;
overflow: hidden;
}
/*todo列表为空时样式*/
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*添加待办事项的动画过渡效果*/
.todo-enter-active{
animation: mergeDown 1s;
}
/*删除待办事项的动画过渡效果*/
.todo-leave-active{
animation: mergeDown 1s reverse;
}
/*使用@keyframes可以创建动画*/
/*此处创建下沉效果*/
@keyframes mergeDown {
from{
transform: translateY(-30%);
overflow: hidden;
}
to{
transform: translateY(0px);
}
}
</style>
子组件MyItem
组件结构
MyItem组件结构
具体代码
<template>
<li>
<!-- 标签 当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上 -->
<label>
<!-- 勾选框,根据待办事项完成状态改变勾选状态,状态改变时调用handleCheck方法 -->
<input
type="checkbox"
:checked="todo.completed"
@change="handleCheck(todo.id)"
/>
<!-- 如下代码也能实现功能,但是不太推荐,因为修改了props,有些违反原则-->
<!-- <input type="checkbox" v-model="todo.completed"/>-->
<!-- 待办事项 -->
<!--未进入编辑状态时,显示待办事项-->
<span v-show="!todo.isEdit">{{todo.title}}</span>
<!--在编辑状态中,显示包含原内容的输入框,失焦后调用handleBlur方法完成更改-->
<input
type="text"
:value="todo.title"
v-show="todo.isEdit"
@blur="handleBlur(todo,$event)"
ref="inputTitle"
>
</label>
<!-- 编辑按钮 -->
<!--点击按钮前未进入编辑状态,isEdit为假,按钮显示,点击后isEdit为真,按钮消失,进入编辑状态-->
<button
class="btn btn-edit"
v-show="!todo.isEdit"
@click="handleEdit(todo)"
>
编辑
</button>
<!-- 删除按钮,点击时调用handleDelete方法-->
<button
class="btn btn-danger"
@click="handleDelete(todo.id)"
>
删除
</button>
</li>
</template>
<script>
import pubsub from 'pubsub-js' // 引入pubsub
export default {
name: 'MyItem',
// 声明接收MyList里的todo对象
props: ['todo'],
methods: {
// 勾选 or 取消勾选
handleCheck (id) {
// 通知App组件将对应的todo对象的completed值取反
// this.checkTodo(id)
this.$bus.$emit('checkTodo', id) // 通过全局总线bus触发App中的checkTodo事件,对对应id的对象调用checkTodo方法
},
// 删除
handleDelete (id) {
if (confirm('Do you mean it???')) { // confirm() 弹出对话框,点击确认后继续
// this.deleteTodo(id)
// this.$bus.$emit('deleteTodo', id)
pubsub.publish('deleteTodo', id) // 发布deleteTodo消息并传递id数据
}
},
// 编辑
handleEdit (todo) {
if (todo.hasOwnProperty('isEdit')) { // 如果该todo已有isEdit属性(不管属性真假)
todo.isEdit = true // 令isEdit为真,将文字变为输入框
} else { // 如果todo还没有isEdit属性
this.$set(todo, 'isEdit', true) // 利用set函数将isEdit属性赋给该todo,且令初始值为真
}
this.$nextTick(function () { // $nextTick指定的回调函数会在DOM节点更新后执行
this.$refs.inputTitle.focus() // 先点击编辑按钮令文字变为输入框后,再令输入框获取焦点
})
},
// 失去焦点回调(真正执行修改逻辑)
handleBlur (todo, event) {
todo.isEdit = false // 令isEdit为假,失焦后输入框消失
if (!event.target.value.trim()) return alert('Input section is empty!') // 若去掉输入内容前后空格后,输入内容为空,弹出提示框
this.$bus.$emit('updateTodo', todo.id, event.target.value) // 若输入内容非空,触发全局事件总线中的updateTodo事件,并传递该todo的id与输入框中的值
}
}
}
</script>
<style scoped>
/*item*/
/*待办事项样式*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
/*列表中被label包裹内容的样式*/
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 5px;
position: relative;
top: -1px;
}
/*列表中所有按钮的样式*/
li button {
float: right;
display: none;
margin-top: 3px;
}
/*在每个li元素前插入内容的样式*/
li:before {
content: initial;
}
/*属于其父元素的最后一个子元素的li元素的样式*/
li:last-child {
border-bottom: none;
}
/*鼠标指针悬浮于li上时的样式*/
li:hover {
background-color: #dddddd;
font-weight: bold;
font-size: large;
}
li:hover button {
display: block;
}
/*删除按钮样式*/
.btn-danger {
color: #f5f5f5;
background-color: #da4f49;
border: 1px solid #bd362f;
margin-right: 5px;
}
/*鼠标指针移动到删除按钮上时的样式*/
.btn-danger:hover {
color: #f5f5f5;
background-color: #bd362f;
margin-right: 5px;
}
/*编辑按钮样式*/
.btn-edit {
color: #f5f5f5;
background-color: lightskyblue;
border: 1px solid cornflowerblue;
}
/*鼠标移动到编辑按钮上时的样式*/
.btn-edit:hover {
color: #f5f5f5;
background-color: cornflowerblue;
}
</style>
子组件MyFooter
组件结构
MyFooter组件结构
具体代码
<template>
<!-- todo底部-->
<div class="todo-footer" v-show="total">
<!-- 全选勾选框-->
<label>
<!-- v-model="isAll"等同于: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'], // 引入父组件App中的todos对象数组
computed: { // 计算属性,可用于处理复杂逻辑
// 总数
total () {
return this.todos.length // 返回todos对象数组的长度,即待办事项总数
},
// 已完成数
doneTotal () {
// reduce()方法按顺序对数组的每个元素回调函数,并按顺序从前一个元素的计算中传入返回值。最终计算为一个值。
// 判断todo对象是否完成,若完成则操作数为1,若未完成则操作数为0,返回pre+操作数,令pre的初始值为0
return this.todos.reduce((pre, todo) => pre + (todo.completed ? 1 : 0), 0) // 最终返回对象数组中所有已完成事项的总数
},
// 控制全选框
isAll: {
// 全选框是否勾选
get () {
return this.doneTotal === this.total && this.total > 0 // 若列表不为空且待办事项的已完成数和总数相同,返回真,全选框被勾选
},
// isAll被修改时set被调用
set (value) {
this.$emit('checkAllTodo', value) // 当isAll被修改时向App传递checkAllTodo事件,将数组内所有对象的完成状态更改为value
}
}
},
methods: {
// 勾选或取消一个待办事项的已完成状态需要对父组件App里的todos对象进行修改,且todos被多个组件使用,所以最好提交给父组件来改
// checkAll (todo) {
// this.checkAllTodo(todo.target.checked)
// }
clearAll () {
this.$emit('clearAllTodo')
}
}
}
</script>
<style scoped>
/*footer*/
/*页面底部样式*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 5px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 5px;
cursor: pointer;
}
/*页面底部勾选框样式*/
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 15px;
}
/*页面底部按钮样式*/
.todo-footer button {
float: right;
margin-top: 5px;
}
/*清除按钮样式*/
.btn-danger {
color: #f5f5f5;
background-color: #da4f49;
border: 1px solid #bd362f;
margin-right: 5px;
}
/*鼠标指针移动到清除按钮上时的样式*/
.btn-danger:hover {
color: #f5f5f5;
background-color: #bd362f;
margin-right: 5px;
}
</style>
网友评论