vue3+ts实现todolist功能
先看一下实现效果:可以看到内部实现的内容有enter输入,单项删除,全选,以及删除选中项等功能具体在实现前需要常见有ts的vue3项目项目创建具体项目创建 就是 vue create 项目名称在创建后,选择的时候有vue2和vue3的选择,第三项是自定义,在自定义时需要选中ts(选择的键分别是向下键和空格键)在创建项目之后,先运行,查看是否可运行TodoList实现目录结构:运行文件:App.vu
·
先看一下实现效果:
可以看到内部实现的内容有enter输入,单项删除,全选,以及删除选中项等功能
具体在实现前需要常见有ts的vue3项目
项目创建
具体项目创建 就是 vue create 项目名称
在创建后,选择的时候有vue2和vue3的选择,第三项是自定义,在自定义时需要选中ts(选择的键分别是向下键和空格键)
在创建项目之后,先运行,查看是否可运行
TodoList实现
目录结构:
运行文件:App.vue
组件:components下的文件
配置文件:utils types
文件内容按上述介绍展示:
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<Header :addTodo="addTodo"/>
<List :todos='todos' :deleteTodo="deleteTodo" :updateTodo="updateTodo"/>
<Footer :todos="todos" :checkAll="checkAll" :clearAllCompletedTodos="clearAllCompletedTodos"/>
</div>
</div>
</template>
<script lang='ts'>
import { defineComponent,reactive,toRefs,watch,onMounted } from 'vue'
// 引入直接的子集组件
import Header from './components/Header.vue'
import List from './components/List.vue'
import Footer from './components/Footer.vue'
import {Todo } from './types/todo'
import {saveTodos, readTodos} from './utils/localStorageUtils'
export default defineComponent({
name:'App',
components: {
Header,
List,
Footer
},
// 数据存储为数组格式,数组内的为对象,对象中有三个属性(id, title, isSCompleted)
// 把数据定义到App.vue父级组件
setup(){
// 定义一个数组数据
// const state = reactive<{todos: Todo[]}>({
// todos: [
// {id: 1,title:'奔驰',isCompleted: false},
// {id: 2,title:'宝马',isCompleted: true},
// {id: 3,title:'奥迪',isCompleted: false},
// ]
// })
const state = reactive<{todos: Todo[]}>({
todos: []
})
// 界面加载完毕后再读取数据
onMounted(() => {
setTimeout(() => {
state.todos = readTodos()
},1000)
})
// 添加数据的方法
// eslint-disable-next-line
const addTodo = (todo:Todo) => {
state.todos.unshift(todo)
}
// 删除数据的方法
const deleteTodo = (index:number) =>{
state.todos.splice(index, 1)
}
// 修改todod的 isCompleted属性的状态
const updateTodo = (todo: Todo,isCompleted: boolean) => {
todo.isCompleted = isCompleted
console.log(todo);
}
// 全选或者全不选的方法
const checkAll = (isCompleted:boolean) => {
// 遍历数组
state.todos.forEach((todo) => {
todo.isCompleted = isCompleted
});
}
// 清理所有选中的数据
const clearAllCompletedTodos = () => {
state.todos = state.todos.filter(todo=>!todo.isCompleted)
}
// 监视操作:如果todos数组的数据变化了,直接存储到浏览器的缓存中
// watch(() => state.todos, (value)=> {
// // 保存到浏览器缓存中
// localStorage.setItem('todos_key',value)
// },{deep:true})
// watch(() => state.todos, (value)=> {
// // 保存到浏览器缓存中
// saveTodos(value)
// },{deep:true})
watch(() => state.todos, saveTodos, {deep:true})
return {
...toRefs(state),
addTodo,
deleteTodo,
updateTodo,
checkAll,
clearAllCompletedTodos
}
}
})
</script>
<style scoped>
.todo-container{
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap{
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
components 下的 Header.vue
<template>
<div class="todo-header">
<input type="text" placeholder='请输入你的任务名称,按回车键确认' @keyup.enter='add' v-model='title'>
</div>
</template>
<script lang='ts'>
import { defineComponent,ref } from 'vue'
export default defineComponent({
name:'Header',
props: {
addTodo: {
type: Function,
required: true // 必须
}
},
setup(props){
// 定义一个ref类型的数据
const title = ref('')
// 回车的事件回调函数,用来添加数据
const add = () => {
// 获取文本框中输入的数据,判断不能为空
const text = title.value
if(!text.trim()) return
//此时有数据,创建一个todo对象
const todo = {
id: Date.now(),
title: text,
isCompleted: false
}
// 调用方法addTodo方法
props.addTodo(todo)
// 情况文本框
title.value = ''
}
return {
title,
add
}
}
})
</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>
components 下的 List.vue
<template>
<ul class="todo-main">
<Item v-for="(todo, index) in todos" :key="todo.id" :index="index" :todo='todo' :deleteTodo="deleteTodo" :updateTodo="updateTodo" />
</ul>
</template>
<script lang='ts'>
import { defineComponent } from 'vue'
import Item from './Item.vue'
export default defineComponent({
name:'List',
components: {
Item,
},
props: ['todos','deleteTodo','updateTodo']
})
</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>
components 下的 Item.vue
<template>
<li @mouseenter="mouseHandler(true)" @mouseleave="mouseHandler(false)" :style="{backgroundColor:bgColor,color:myColor}">
<label>
<input type='checkbox' v-model='isCom'/>
<span>{{todo.title}}</span>
</label>
<button class='btn btn-danger' v-show="isShow" style='display;none' @click="delTodo">删除</button>
</li>
</template>
<script lang='ts'>
import { defineComponent,ref, computed } from 'vue'
import {Todo} from '../types/todo'
export default defineComponent({
name:'Item',
// props: {
// todo: Object as () => Todo // 函数返回的是Todo类型
// },
props: {
todo: {
type: Object as () => Todo, // 函数返回的是Todo类型
required: true
},
deleteTodo: {
type: Function,
required: true
},
index: {
type: Number,
required: true
},
updateTodo: {
type: Function,
required: true
}
},
data(){
return {
}
},
// computed: {
// isCom () {
// return this.todo.isCompleted
// }
// },
setup(props) {
const bgColor = ref('white')
const myColor = ref('black')
const isShow = ref(false)
// 鼠标进入和离开事件的回调函数
const mouseHandler = (flag: boolean) => {
if(flag){
// 鼠标进入
bgColor.value = 'pink'
myColor.value = 'white'
isShow.value = true
}else{
// 鼠标离开
bgColor.value = 'white'
myColor.value = 'black'
isShow.value = false
}
}
// 删除数据的方法
const delTodo = () => {
if(window.confirm('确定要删除吗?')){
props.deleteTodo(props.index)
}
}
// 计算属性方式---让当前复选框选中
const isCom = computed({
get(){
return props.todo.isCompleted
},
set(val){
props.updateTodo(props.todo, val)
}
})
return {
mouseHandler,
bgColor,
myColor,
isShow,
delTodo,
isCom
}
}
})
</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; */
margin-top: 3px;
}
li:before{
content: initial;
}
li:last-child{
border-bottom: none;
}
</style>
components 下的 Footer.vue
<template>
<div class="todo-footer">
<label>
<input type='checkbox' v-model="isCheckAll" />
</label>
<span><span>已完成{{count}}</span> /全部{{todos.length}} </span>
<button class='btn btn-danger' @click="clearAllCompletedTodos">清除已完成任务</button>
</div>
</template>
<script lang='ts'>
import { defineComponent,computed } from 'vue'
import {Todo} from '../types/todo'
export default defineComponent({
name:'Footer',
props: {
todos:{
type: Array as ()=> Todo[],
required: true
},
checkAll: {
type: Function,
required: true
},
clearAllCompletedTodos: {
type: Function,
required: true
}
},
setup(props){
// 已完成的计算属性操作
const count = computed(()=>{
return props.todos.reduce((pre,todo,index)=>pre+(todo.isCompleted?1:0),0)
})
const isCheckAll = computed({
get(){
return count.value>0&&props.todos.length===count.value
},
set(val){
props.checkAll(val)
}
})
return {
count,
isCheckAll
}
}
})
</script>
<style scoped>
.todo-footer{
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.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{
float: right;
margin-top: 5px;
}
</style>
types 下的 todo.ts
// 定义一个接口,约束state的数据类型
export interface Todo{
id: number,
title: string,
isCompleted: boolean
}
utils下的 localStorageUtils.ts
import {Todo} from '../types/todo'
// 保存数据到浏览器的缓存中
export function saveTodos(todos:Todo[]){
localStorage.setItem('todos_key',JSON.stringify(todos))
}
// 从浏览器缓存读取数据
export function readTodos():Todo[]{
return JSON.parse(localStorage.getItem('todos_key') || '[]')
}
完
将 localStorageUtils.ts相关内容全部删除可正常使用!
更多推荐
已为社区贡献10条内容
所有评论(0)