无接口纯本地操作
记事本分为三个组件组成:
- 头部是一个输入框和全选框,用户回车可以创建一个待办事项
- 身体是循环渲染的代办事项列表,有单选框与删除按钮,单选框控制该代办事项是否完成
- 尾部是代办事项的总数、任务状态切换(完成、未完成、已完成)、清除已完成事项
// 父组件中的代码
<script setup lang="ts">
import "./styles/base.css"
import "./styles/index.css"
import TodoHeader from "./components/TodoHeader.vue"
import TodoMain from "./components/TodoMain.vue"
import TodoFooter from "./components/TodoFooter.vue"
import { computed, ref, watch } from "vue"
// todo.d.ts 文件引入
import { List } from "./todo"
// 本地持久化
const taskList = ref<List[] | undefined>(JSON.parse(localStorage.getItem('list') || '[]'))
// 监听子组件回车事件的回调函数
const hAddList = (val:string) => {
taskList.value!.push({
id: (+new Date()).toString(),
name: val,
isDone: false
})
}
// 待办事项的类型
const type = ref(1)
// 使用计算属性判断当前任务状态 1:全部,2:未完成,3:已完成
const newList = computed({
get():List[]|undefined{
if(type.value===1){
return taskList.value
}else if(type.value===2){
// 未完成
return taskList.value!.filter(item => !item.isDone)
}else if(type.value===3){
return taskList.value!.filter(item => item.isDone)
}
},
set(){
}
})
const hType = (val: number) =>{
type.value = val
}
const upDataCheckbox = (id: string) => {
taskList.value!.forEach(item => {
if(item.id === id){
item.isDone = !item.isDone
}
})
}
// 使用计算属性控制小选框和全选框是否勾选
const isAll = computed({
get(){
return newList.value!.every(item => item.isDone)
},
set(val){
// isAll 值被修改时执行的方法,val 是修改后的值
newList.value!.map(item => item.isDone = val)
}
})
// 监听子组件修改全选框是否选中事件的回调
const upDataIsAll = (val: boolean) => {
isAll.value = val
}
// 监听子组件删除事件的回调
const delList = (id:string) => {
taskList.value = taskList.value!.filter(item => item.id !== id)
}
// 监听子组件清除已完成事件的回调
const delListDone = () => {
taskList.value = taskList.value!.filter(item => !item.isDone)
}
// 侦听数组的变化,变化的数组重新存入本地
watch(()=> taskList, () => {
console.log(taskList.value)
localStorage.setItem('list',JSON.stringify(taskList.value))
}, {deep: true})
</script>
<template>
<section class="todoapp">
<TodoHeader @upDataIsAll="upDataIsAll" :isAll="isAll" @addList="hAddList"></TodoHeader>
<TodoMain @delList="delList" @upDataCheckbox="upDataCheckbox" :taskList="newList"></TodoMain>
<TodoFooter @delListDone="delListDone" :taskList="taskList" :type="type" @upDataType="hType"></TodoFooter>
</section>
</template>
类型声明文件:todo.d.ts
type List = {
id: string;
isDone: boolean;
name: string;
}
export {
List
}
头部子组件
<template>
<header class="header">
<h1>todos</h1>
<input
id="toggle-all"
:checked="isAll"
@click="upDataIsAll"
class="toggle-all"
type="checkbox" >
<label for="toggle-all"></label>
<input
class="new-todo"
v-model.trim="todoInp"
placeholder="输入任务名称-回车确认"
autofocus
@keydown.enter="haddEnter"
/>
</header>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const prop = defineProps<{ isAll: boolean }>()
const emit = defineEmits<{
(e: 'addList', todoInp: string):void,
(e: 'upDataIsAll', boo: boolean):void
}>()
// 输入的内容
const todoInp = ref<string>('')
const haddEnter = () => {
// 传递到父组件添加数据
emit('addList', todoInp.value)
// 清空input
todoInp.value = ''
}
const upDataIsAll = () => {
emit('upDataIsAll', !prop.isAll)
}
</script>
身体
<script setup lang="ts">
import { List } from '../todo'
const emit = defineEmits<{
(e: 'upDataCheckbox', id: string):void
(e: 'delList', id: string):void
}>()
defineProps<{taskList: List[]|undefined}>()
// 点击时让当前这一项的 isDone 取反, 也就是通知父组件修改当前项的完成状态
const checkIsDone = (id: string)=>{
emit('upDataCheckbox', id)
}
// 通知父组件删除当前项
const delList = (id: string) => {
emit('delList', id)
}
</script>
<template>
<ul class="todo-list">
<!-- completed: 完成的类名 -->
<li :class="{'completed': item.isDone}" v-for="item in taskList" :key="item!.id">
<div class="view">
<input @click="checkIsDone(item.id)" :checked="item.isDone" class="toggle" type="checkbox"/>
<label>{{item.name}}</label>
<button @click="delList(item.id)" class="destroy"></button>
</div>
</li>
</ul>
</template>
底部组件
<script setup lang="ts">
import { List } from '../todo';
// 使用计算属性,判断点击的是哪个按钮,给父组件传递对应的数据,父组件中计算属性返回对应的数组
const emit = defineEmits<{
(e:'upDataType', num: number):void
(e:'delListDone'):void
}>()
// 通知父组件修改当前点击的底部完成状态
const hisDone = (num:number) => {
emit('upDataType', num)
}
defineProps<{type:number,taskList: List[] | undefined }>()
const delList = () => {
// 通知父组件清除已完成的数据
emit('delListDone')
}
</script>
<template>
<footer class="footer">
<span class="todo-count">剩余<strong>{{taskList!.length}}</strong></span>
<ul class="filters">
<li>
<a @click="hisDone(1)" :class="{'selected': type===1}" href="javascript:;">全部</a>
</li>
<li>
<a @click="hisDone(2)" :class="{'selected': type===2}" href="javascript:;">未完成</a>
</li>
<li>
<a @click="hisDone(3)" :class="{'selected': type===3}" href="javascript:;">已完成</a>
</li>
</ul>
<button @click="delList" class="clear-completed" >清除已完成</button>
</footer>
</template>