输出效果
总结
- 完成一个简易版的todo项目
- 使用简单的ts语法
- 使用vue3中的composition API
- 对于父子组件的通信有了一定的了解
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<add-bar :addTodo='addTodo'></add-bar>
<display
:todos='todos'
:deleteTodo='deleteTodo'
:updateTodo='updateTodo'
></display>
<panel
:todos='todos'
:checkAllItem = 'checkAllItem'
:clearAllCompletedTodos='clearAllCompletedTodos'
></panel>
</div>
</div>
</template>
<script lang="ts">
import {Todo} from './types/todo'
import { defineComponent,reactive, toRefs, watch,onMounted } from "vue";
import AddBar from "./components/AddBar.vue";
import Display from "./components/Display.vue";
import Panel from "./components/Panel.vue";
import {saveTodos,readTodos} from './utils/localStorage'
export default defineComponent({
name: "App",
components: {
AddBar,
Display,
Panel,
},
setup() {
// const state = reactive<{todos:Todo []}>({
// todos:[
// {id:1,title:'Alice',isCompleted:true},
// {id:2,title:'Bruce',isCompleted:false},
// {id:3,title:'Celina',isCompleted:true},
// {id:4,title:'Celina',isCompleted:true},
// ]
// })
// 页面加载完成之后在读取数据
const state = reactive<{todos:Todo[]}>({
todos:[]
})
onMounted(() => {
setTimeout(()=>{
state.todos = readTodos()
},1000)
})
// 添加新的数据
const addTodo = (todo:Todo) =>{
state.todos.unshift(todo)
}
//删除数据项
const deleteTodo = (index:number) =>{
state.todos.splice(index,1)
}
//修改todo的状态
const updateTodo = (todo:Todo,isCompleted:boolean) => {
todo.isCompleted = isCompleted
}
//修改所有todo的状态
const checkAllItem = (isCompleted:boolean)=>{
state.todos.forEach((todo)=>{
todo.isCompleted = isCompleted
})
}
//清理所有已完成的todo
const clearAllCompletedTodos = () =>{
state.todos = state.todos.filter(todo => !todo.isCompleted)
}
//监听todos的变化并保存到本地
watch(()=>state.todos,saveTodos,{deep:true})
return {
...toRefs(state),
addTodo,
deleteTodo,
updateTodo,
checkAllItem,
clearAllCompletedTodos
};
},
});
</script>
<style scpoed>
/*base*/
body {
background: #fff;
}
/*app*/
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
AddBar.vue
<template>
<div class="todo-header">
<input
type="text"
placeholder="请输入你的任务名称,按回车键确认"
v-model="title"
@keyup.enter="addItem" />
</div>
</template>
<script lang="ts">
import {Todo} from '../types/todo'
import { defineComponent,ref } from "vue";
export default defineComponent({
name: "AddBar",
props:{
addTodo:{
type:Function,
required:true
}
},
setup(props) {
const title = ref('')
const addItem = () => {
const text = title.value
if(!text.trim())return
const todo:Todo = {
id:Date.now(),
title:text,
isCompleted:false
}
props.addTodo(todo)
title.value=''
}
return {
title,
addItem
};
},
});
</script>
<style>
/*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>
Display.vue
<template>
<ul class="todo-main">
<display-item
v-for="todo,index in todos"
:key="todo.id"
:todo='todo'
:index = index
:deleteTodo='deleteTodo'
:updateTodo='updateTodo'>
</display-item>
</ul>
</template>
<script lang="ts">
import {Todo} from '../types/todo'
import { defineComponent } from "vue";
import DisplayItem from "./DisplayItem.vue";
export default defineComponent({
name: "Display",
components: {
DisplayItem,
},
props:{
todos:{
type:Object as () => Todo[],
required:true
},
deleteTodo:{
type:Function,
required:true
},
updateTodo:{
type:Function,
required:true
}
}
});
</script>
<style>
/*main*/
.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>
DisplayItem.vue
<template>
<li @mouseenter="mouseHandler(true)" @mouseleave="mouseHandler(false)">
<label :class="{ 'delete-line': isCompleted }">
<input type="checkbox" v-model="isCompleted" />
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" v-show="isShow" @click="delTodo">
删除
</button>
</li>
</template>
<script lang='ts'>
import { Todo } from "@/types/todo";
import { computed, defineComponent, ref } from "vue";
export default defineComponent({
name: "DisplayItem",
props: {
todo: {
type: Object as () => Todo,
required: true,
},
index: {
type: Number,
required: true,
},
deleteTodo: {
type: Function,
required: true,
},
updateTodo: {
type: Function,
required: true,
},
},
setup(props) {
//按钮是否显示
const isShow = ref(false);
const mouseHandler = (flag: boolean) => {
flag ? (isShow.value = true) : (isShow.value = false);
};
// 删除todo条目
const delTodo = () => {
if(window.confirm('delete?')){
props.deleteTodo(props.index);
}
};
const isCompleted = computed({
get() {
return props.todo.isCompleted;
},
set(val) {
props.updateTodo(props.todo, val);
},
});
return {
isShow,
mouseHandler,
delTodo,
isCompleted,
};
},
});
</script>
<style scoped>
/*item*/
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;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
li:hover {
background-color: pink;
}
.delete-line {
text-decoration-line: line-through;
}
.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;
}
</style>
types>todo.ts
export interface Todo{
id:number,
title:string,
isCompleted:boolean
}
utils>localStorage.ts
import {Todo} from '../types/todo'
//保存数据到浏览器的缓存中
export function saveTodos(todos:Todo[]):void{
localStorage.setItem('todos_key',JSON.stringify(todos))
}
export function readTodos():Todo[]{
return JSON.parse(localStorage.getItem('todos_key')||'[]')
}
下载
下载地址