1.创建工程和组件
①:初始化todo工程:
vue create todo-demo (将欢迎界面清理,重置app.vue)
②: 创建3个组件(布局样式略) :src-components-TodoHeader.vue+TodoMain.vue+TodoFooter.vue③:把styles的样式文件准备好:3.1放在src中3.2(app.vue)import "./styles/index.css"import "./styles/base.css"④: App.vue引入组件注册使用, 最外层容器类名todoapp :4.1组件引入:import TodoHeader from './components/TodoHeader'import TodoMain from './components/TodoMain'import TodoFooter from './components/TodoFooter'4.2组件注册:export default { // 1.2 组件注册 components: { TodoHeader, TodoMain, TodoFooter, }, }
4.3组件使用:
<template> <div class="todoapp"> <TodoHeader></TodoHeader> <TodoMain></TodoMain> <TodoFooter></TodoFooter> </div> </template>
2.循环展示任务
需求1: 把待办任务, 展示到页面TodoMain.vue组件上
需求2: 关联选中状态, 设置相关样式
2.1准备数据并传入TodoMain.vue中
app.vue:
<TodoMain :arr="list"></TodoMain>
data(){
return {
list: [
{id:100,name:"吃饭",isDone:true},
{id:201,name:"睡觉",isDone:false},
{id:103,name:"打豆豆",isDone:true},
]
}
}
TodoMain.vue:
export default {
// 2.1 定义props
props: ['arr'],
}
2.2v-for循环展示数据
TodoMain.vue:
v-model绑定复选框选中状态
<template>
<ul class="todo-list">
<!-- 2.2 循环任务-关联选中状态-铺设数据 -->
<!-- completed: 完成的类名 -->
<li class="{completed: obj.isDone}" v-for="obj in arr" :key='obj.id'>
<div class="view">
<input class="toggle" type="checkbox" v-model="obj.isDone"/>
<label>{{ obj.name }}</label>
<button class="destroy"></button>
</div>
</li>
</ul>
</template>
3.添加任务
需求: 输入任务敲击回车, 新增待办任务
①: TodoHeader.vue – 输入框 – 键盘事件 – 回车按键
②: 子传父, 把待办任务 – App.vue中 – 加入数组list里③: 原数组改变, 所有用到的地方都会更新④: 输入框为空, 提示用户必须输入内容
3.1.绑定键盘回车事件
TodoHeader.vue:
输入框 - v-model获取值
<template>
<header class="header">
<h1>todos</h1>
<input id="toggle-all" class="toggle-all" type="checkbox"/>
<label for="toggle-all"></label>
<!-- 3.0 键盘事件-回车按键
3.1 输入框 - v-model获取值
-->
<input
class="new-todo"
placeholder="输入任务名称-回车确认"
autofocus
@keydown.enter="downFn"
v-model="task"
/>
</header>
</template>
3.2将当前任务名加入list数组(子传父)
app.vue(父):
<TodoHeader @create="createFn"></TodoHeader>
TodoHeader.vue(子):
methods: {
downFn() {
// 3.2(重要) - 当前任务名字要加到list数组里
// 子传父技术
this.$emit("create", this.task);
//回车后输入框清空
this.task = "";
}}
app.vue(父):
methods: {
createFn(taskName) {
// 添加任务
// 3.3 push到数组里
let id =
this.list.length == 0 ? 100 : this.list[this.list.length - 1].id + 1;
this.list.push({
id: id,
name: taskName,
isDone: false,
});
},
}
4.删除功能
点击任务后的x, 删除当前这条任务
①: x标签 – 点击事件 – 传入id区分
②: 子传父, 把id传回– App.vue中 – 删除数组list里某个对应的对象③: 原数组改变, 所有用到的地方都会 更新
TodoMain.vue(子):
<!-- 4.0 注册点击事件 -->
<button class="destroy" @click="delFn(obj.id)"></button>
methods: {
delFn(id) {
// 4.1 子传父
this.$emit("del", id);
},
},
app.vue(父):
<TodoMain :arr="list" @del="deleteFn"></TodoMain>
deleteFn(theId) {
// 删除任务
let index = this.list.findIndex((obj) => obj.id === theId);
this.list.splice(index, 1);
},
5.底部统计
统计当前剩余任务条数
①: App.vue中 – 数组list – 传给TodoFooter.vue
②: 直接在标签上显示 / 定义计算属性用于显示都可以
③: 原数组只要改变, 所有用到此数组的地方都会更新
父传子:
app.vue(父):
<TodoFooter :farr="list" ></TodoFooter>
TodoFooter.vue(子):
<span class="todo-count">剩余<strong>{{ farr.length }}</strong></span>
以上方法可用计算属性替代:
<span class="todo-count">剩余<strong>{{ count }}</strong></span>
// 5.1 计算属性 - 任务数量
computed: {
count(){
return this.farr.length
}
},
6.底部数据切换
需求1:点击底部数据切换(加边框)
需求2:对应切换不同数据显示
①: TodoFooter.vue – 定义isSel – 值为all, yes, no其中一种
②: 多个class分别判断谁应该有类名selected③: 点击修改isSel的值④: 子传父, 把类型isSel传到App.vue⑤: 定义计算属性showArr, 决定从list里显示哪些数据给TodoMain.vue和TodoFooter.vue
6.1点击高亮
TodoFooter.vue:
// 6. 目标: 点谁谁亮
// 6.0 变量isSel
data(){
return {
isSel: 'all' // 全部:'all', 已完成'yes', 未完成'no'
}
},
<li>
<!-- 6.1 判断谁应该有高亮样式: 动态class
6.2 用户点击要切换isSel里保存的值
-->
<a :class="{selected: isSel === 'all'}" href="javascript:;" @click="isSel='all'">全部</a>
</li>
<li>
<a :class="{selected: isSel === 'no'}" href="javascript:;" @click="isSel='no'">未完成</a>
</li>
<li>
<a :class="{selected: isSel === 'yes'}" href="javascript:;" @click="isSel='yes'">已完成</a>
</li>
6.2将isSel传给app.vue
app.vue(父):
<TodoFooter
:farr="list"
@changeType="typeFn"
></TodoFooter>
TodoFooter.vue(子):
<ul class="filters" @click="fn">
<li>
<!-- 6.1 判断谁应该有高亮样式: 动态class
6.2 用户点击要切换isSel里保存的值
-->
<a :class="{selected: isSel === 'all'}" href="javascript:;" @click="isSel='all'">全部</a>
</li>
<li>
<a :class="{selected: isSel === 'no'}" href="javascript:;" @click="isSel='no'">未完成</a>
</li>
<li>
<a :class="{selected: isSel === 'yes'}" href="javascript:;" @click="isSel='yes'">已完成</a>
</li>
</ul>
methods: {
fn(){ // 切换筛选条件
// 6.3 子 -> 父 把类型字符串传给App.vue
this.$emit("changeType", this.isSel)
},
}
app.vue(父):
data() {
return {
// 6.4 先中转接收类型字符串
getSel: "all", // 默认显示全部
};
},
typeFn(str) {
// 'all' 'yes' 'no' // 修改类型
this.getSel = str;
},
// 6.5 定义showArr数组 - 通过list配合条件筛选而来
computed: {
showArr() {
if (this.getSel === "yes") {
// 显示已完成
return this.list.filter((obj) => obj.isDone === true);
} else if (this.getSel === "no") {
// 显示未完成
return this.list.filter((obj) => obj.isDone === false);
} else {
return this.list; // 全部显示
}
},
},
7.底部清空已完成
需求: 点击右下角链接标签, 清除已完成任务
①: 清空标签 – 点击事件
②: 子传父 – App.vue – 一个清空方法③: 过滤未完成的覆盖list数组 (不考虑恢复)
TodoFooter.vue(子):
<!-- 7. 目标: 清除已完成 -->
<!-- 7.0 点击事件 -->
<button class="clear-completed" @click="clearFn">清除已完成</button>
methods: {
clearFn(){ // 清空已完成任务
// 7.1 触发App.vue里事件对应clearFun方法
this.$emit('clear')
}
}
app.vue(父):
<TodoFooter
:farr="showArr"
@changeType="typeFn"
@clear="clearFun"
></TodoFooter>
clearFun() {
// 清除已完成
this.list = this.list.filter((obj) => obj.isDone == false);
},
8.数据缓存
需求: 无论如何变化 – 都保证刷新后数据还在
①: App.vue – 侦听list数组改变 – 深度
②: 覆盖式存入到本地 – 注意本地只能存入JSON字符串③: 刷新页面 – list应该默认从本地取值 – 要考虑无数据情况空数组
// 8. 目标: 数据缓存
watch: {
list: {
deep: true,
handler() {
// 8.0 只要list变化 - 覆盖式保存到localStorage里
localStorage.setItem("todoList", JSON.stringify(this.list));
},
},
},
data() {
return {
// 8.1 默认从本地取值
list: JSON.parse(localStorage.getItem("todoList")) || [],
// 6.4 先中转接收类型字符串
getSel: "all", // 默认显示全部
};
},
9.全选功能
需求1: 点击全选 – 小选框受到影响
①: TodoHeader.vue – 计算属性 - isAll②: App.vue – 传入数组list – 在isAll的set里影响小选框③: isAll的get里统计小选框最后状态, 影响isAll – 影响全选状态④: 考虑无数据情况空数组 – 全选不应该勾选
TodoHeader.vue:
<!-- 9. 目标: 全选状态
9.0 v-model关联全选状态
页面变化(勾选true, 未勾选false) -> v-model -> isAll变量
-->
<input id="toggle-all" class="toggle-all" type="checkbox" v-model="isAll" />
<label for="toggle-all"></label>
TodoHeader.vue(子):
// 9.1 定义计算属性
computed: {
isAll: {
set(checked) {
// 只有true / false
// 9.3 影响数组里每个小选框绑定的isDone属性
this.arr.forEach((obj) => (obj.isDone = checked));
},
get() {
// 9.4 小选框统计状态 -> 全选框
// 9.5 如果没有数据, 直接返回false-不要让全选勾选状态
return (
this.arr.length !== 0 && this.arr.every((obj) => obj.isDone === true)
);
},
},
},
// 9.2 父 -> 子 list数组
props: ["arr"],
app.vue(父):
<TodoHeader :arr="list" @create="createFn"></TodoHeader>