对于这一段时间vue3基础的练习,实现todoList
- APP.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<Header
@addTodo="addTodo"
/>
<List
:todoList="todoList"
:changeChecked="changeChecked"
:deleteTodo="deleteTodo"
/>
<Footer
:todoList="todoList"
@changeAllTodo="changeAllTodo"
@delFinished="delFinished"
:key="footerKey"
/>
</div>
</div>
</template>
<script lang="ts" >
import {defineComponent} from "vue";
import Header from "@/components/Header/index.vue";
import List from "@/components/List/index.vue";
import Footer from "@/components/Footer/index.vue";
export default defineComponent({
name:'App',
components:{
Header,
List,
Footer
}
})
</script>
<script setup lang="ts">
//引入nanoid 用来生成id
import {nanoid} from "nanoid";
import {todoListType} from '@/types/todoList'
import {watch, ref, onMounted} from "vue";
import {getTodoList, saveTodoList} from '@/utils/localStorage'
//初始定义任务数据todoList
// const todoList = ref<todoListType>([
// {id:nanoid(),thing:'吃饭',done:false},
// {id:nanoid(),thing:'睡觉',done:true},
// {id:nanoid(),thing:'打豆豆',done:false},
// ])
//定义数据,并在页面初始化后通过本地存储获取数据
const todoList = ref<todoListType>([])
//初始化函数(挂载完成)
onMounted(()=>{
getTodoList()
})
//改变数据是否被选中状态函数
const changeChecked = (id:string,done:boolean)=>{
todoList.value.forEach(item =>{
item.id === id ? item.done = done : '';
})
}
//添加数据的自定义事件(子传父)
const addTodo = (thing:string)=>todoList.value.push({id:nanoid(),thing,done:false})
//点击删除按钮事件函数
const deleteTodo = (id:string)=> {
//一定要重新赋值
todoList.value = todoList.value.filter(item => item.id != id)
}
//全选/全不选自定义事件
const changeAllTodo = (done:boolean)=> todoList.value.forEach(item=>item.done=done)
//定义footer组件key值(根据dff算法,用来改变key值进行组件重新加载)
let footerKey = ref<number>(0)
//清楚已完成任务自定义事件
const delFinished = () =>{
todoList.value = todoList.value.filter(item => !item.done)
//用来改变key值进行组件重新加载,更新已完成任务的数据显示
footerKey.value++
}
//监听数据变化,进行数据永久化存储
//watch 三个参数 1.监听的数据可以是数据,也可以写个函数,return出一个数据
// 2.回调函数 3.配置对象
//第一个参数 监听的是.value时,只会监听内部数据的状态变化
//第一个参数 监听的不是.value时,只会监听数据的数量变化
//所以最好的方法就是 监听原始数据 + deep:true 的配置
// watch(todoList,(newVal,oldVal)=>{
// saveTodoList(newVal)
// },{deep:true})
//简写:
watch(todoList,saveTodoList,{deep:true})
</script>
<style scoped>
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
- /src/components/Header/index.vue
<template>
<div class="todo-header">
<!--简写: @keydown.enter="$emit('addTodo',($event.target as HTMLInputElement).value)" -->
<!-- keydown.enter vue3中取消了键盘事件的数字,改为固定的键盘英文 -->
<input
type="text"
placeholder="请输入你的任务名称,按回车键确认"
@keydown.enter="add"
/>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
export default defineComponent({
name: "Header"
})
</script>
<script lang="ts" setup>
//1.自定义事件直接接收(已经接收到了),可直接在模板中$emit使用,因为模板中是插值语法,存在组件实例,能找到$emit
// defineEmits<{
// (emit:'addTodo',thing:string):void
// }>()
//defineEmits接收后得到一个emit函数,可以以函数接收并调用emit函数书写额外逻辑
const emit = defineEmits<{(emit:'addTodo',thing:string):void}>()
// 2.自定义事件接收后书写逻辑
const add = (e:Event)=>{
emit('addTodo',(e.target as HTMLInputElement).value);
//清空显示的添加数据
// 对 e.target 目标元素进行断言 书写类型为 HTMLInputElement
(e.target as HTMLInputElement).value = ''
}
</script>
<style scoped>
.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>
- /src/components/List/index.vue
<template>
<ul class="todo-main">
<!-- :todo="todo" 数据批量传递给子组件 -->
<Item
v-for="todo in todoList"
:key="todo.id"
:todo="todo"
:changeChecked="changeChecked"
:deleteTodo="deleteTodo"
/>
</ul>
</template>
<script lang="ts">
import {defineComponent} from "vue";
import Item from "@/components/List/Item/index.vue";
export default defineComponent({
name: "List",
components:{Item}
})
</script>
<script setup lang="ts">
import {todoListType} from '@/types/todoList'
//接收props传参定义类型(事件函数一般都没有返回值,返回值类型写void)
interface propsType{
todoList:todoListType,
changeChecked:(id:string,done:boolean)=>void,
deleteTodo:(id:string)=>void,
}
//接收props传参 todoList数据
defineProps<propsType>()
</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>
- /src/components/List/Item/index.vue
<template>
<li @mousemove="isEnter = true"
@mouseleave="isEnter = false"
:class="isEnter ? 'active' : ''"
>
<label>
<!-- $event.target 进行类型断言 HTMLInputElement-->
<!-- 数据操控试图变化-->
<input
:checked="todo.done"
@change="changeChecked(todo.id,($event.target as HTMLInputElement).checked)"
type="checkbox"
/>
<span>{{ todo.thing }}</span>
</label>
<button class="btn btn-danger" @click="deleteTodo(todo.id)">删除</button>
</li>
</template>
<script lang="ts">
import {defineComponent,ref} from 'vue';
export default defineComponent({
name: "Item"
})
</script>
<script lang="ts" setup>
import {todoType} from '@/types/todoList'
//接收props传参定义类型(事件函数一般都没有返回值,返回值类型写void)
interface propsType{
todo:todoType,
changeChecked:(id:string,done:boolean)=>void,
deleteTodo:(id:string)=>void,
}
// defineProps返回一个Proxy对象,包含所有props ,页面中使用要 Proxy对象.数据
// Proxy(Object) {todo: Proxy(Object), changeChecked: ƒ, deleteTodo: ƒ}
defineProps<propsType>()
// 定义变量,来区分移入移出情况
let isEnter = ref<boolean>(false)
</script>
<style scoped>
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
li.active {
background-color: #ccc;
}
li.active button {
display: block;
}
</style>
- /src/components/Footer/index.vue
<template>
<div class="todo-footer">
<label>
<input type="checkbox" v-model="allChange"/>
<span>全选/全不选</span>
</label>
<span> <span>已完成 {{finished}}</span> / 全部 {{todoList.length}} </span>
<!-- $emit('delFinished') 自定义事件简写,模板中使用插值语法,存在组件实例,上面有$emit-->
<button class="btn btn-danger" @click="$emit('delFinished')">清除已完成任务</button>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
export default defineComponent({
name: "Footer"
})
</script>
<script setup lang="ts">
import {todoListType, todoType} from '@/types/todoList'
import {computed} from "vue";
//直接从defineProps返回的proxy对象中解构出通过props传递的todoList数据
const {todoList} = defineProps<{
todoList:todoListType
}>()
const emit = defineEmits<{
(emit:'changeAllTodo',done:boolean):void,
(emit:'delFinished'):void
}>()
//计算属性计算已完成
const finished = computed(()=>{
return todoList.filter((item:todoType)=>item.done===true).length
})
//全选按钮实现全选和全不选
const allChange = computed({
get(){
return todoList.every((item:todoType)=>item.done)
},
set(newVal:boolean){
//newVal是最新的 get函数得到的值
//emit 函数 第一参数 是函数名,第二参数是传参
emit('changeAllTodo',newVal);
}
})
</script>
<style scoped>
.todo-footer {
display: flex;
justify-content: space-between;
line-height: 40px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
background-color: #ccc;
}
</style>
- /src/types/todoList.ts
//接口定义每条数据类型
export interface todoType{
id:string,
thing:string,
done:boolean,
}
//定义todoList的类型
export type todoListType = todoType[];
- /src/utils/localStorage.ts
import {todoListType} from "@/types/todoList";
//存储todoList数据到浏览器本地存储
export function saveTodoList(todoList:todoListType){
localStorage.setItem('todoList', JSON.stringify(todoList))
}
//通过浏览器本地存储获取todoList数据
export function getTodoList(){
//断言 localStorage.getItem('todoList' ) 一定是一个字符串
return JSON.parse(localStorage.getItem('todoList' ) as string) || '[]'
}
/* 仓库地址*/
https://gitee.com/jiangyiqian/todo-list.git