使用vue3完成todolist案例
- 介绍
- 遇到的问题
- base.css
- 存储工具类
- 定义类型
- app.vue
- Header.vue
- List.vue
- Item.vue
- Footer.vue
介绍
- 尚硅谷2021最新Vue.JS教程快速入门到项目实战(Vue3/VueJS技术详解)
- Vue3+TS 快速上手
组件层次关系
页面效果
遇到的问题
- ESLint 规则: eslint-disable vue/no-mutating-props
源代码
复制代码
1
2<input type="checkbox" v-model="todo.isCompleted" />
按照规则修改
复制代码
1
2<input type="checkbox" :checked="todo.isCompleted" @input="$emit('input', $event.target.todo.isCompleted)" />
base.css
- 在index.html 引入的页面基本样式 base.css
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32body { 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; }
存储工具类
- localStorageUtil.ts
复制代码
1
2
3
4
5
6
7
8// 保存数据到浏览器的缓存中 export function saveArray(key: string,value: []) { localStorage.setItem(key,JSON.stringify(value)) } export function readArray(key: string){ return JSON.parse(localStorage.getItem(key)|| '[]') }
定义类型
- todo.ts
复制代码
1
2
3
4
5
6
7// 定义一个接口,约束state的数据类型 export interface Todo { id: number, title: string, isCompleted: boolean }
app.vue
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87<template> <div class="todo-container"> <div class="todo-wrap"> <Header :addTodo="addTodo" /> <List :todos = "todos" :delTodo = "delTodo" :updateTodo = "updateTodo" /> <Footer :todos = "todos" :checkAll ="checkAll" :clear = "clearAllCompletedTodos"/> </div> </div> </template> <script lang='ts'> import { defineComponent, onMounted, reactive, toRefs, watch } from "vue"; import Header from './components/Header.vue' import List from './components/List.vue' import Footer from './components/Footer.vue' import {Todo} from './type/todo' import { saveArray, readArray } from './utils/localStorageUtil' export default defineComponent({ name: "App", components: { Header, List, Footer }, setup() { // const state = reactive<{todos: Todo[]}>({ // todos: [ // {id:1,title: '记单词',isCompleted: false}, // {id:2,title: '编程',isCompleted: true} // ] // }) const state = reactive<{todos: Todo[]}>({ todos: [] }) const key = 'todos_key' // 界面加载完毕后过一会后再读取数据 onMounted(()=>{ setTimeout(()=>{ state.todos = readArray(key) },1000) }) const addTodo = (todo: Todo) => { state.todos.unshift(todo) } // 添加数组的方法,放在数组头部 const delTodo = (index: number) => { state.todos.splice(index,1) } // 删除数据 const updateTodo = (todo: Todo, isComplete: boolean) => { todo.isCompleted = isComplete } // 修改todo的isCompleted属性的状态,属性的修改应该让父组件来决定 const checkAll = (isComplete: boolean)=>{ state.todos.forEach(todo => { todo.isCompleted = isComplete }) } const clearAllCompletedTodos = ()=> { state.todos = state.todos.filter(todo=>!todo.isCompleted) } // 监视操作: 如果todos数组的数据变化了,直接存储到浏览器的缓存中 // watch(()=>state.todos,(value)=>{ // localStorage.setItem('todos_key',JSON.stringify(value)) // },{deep:true}) watch(()=>state.todos,(value: Todo [])=>{ saveArray(key, value as []) },{deep:true}) return { ...toRefs(state), addTodo, delTodo, 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>
Header.vue
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"> </div> </template> <script lang='ts'> import {defineComponent, ref} from 'vue' export default defineComponent({ name: 'Header', props: { addTodo: { type: Function, require: true // 必须要传 } }, setup(props) { const title = ref('') const add = () => {`` if (!title.value.trim()) return; props.addTodo?.({ id: Date.now(), title: title.value, isCompleted: false }) 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>
List.vue
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33<template> <ul class="todo-main"> <Item v-for="(todo, index) in todos " :key="todo.id" :todo="todo" :delTodo="delTodo" :index="index" :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', 'delTodo', 'updateTodo'] }) </script> <style scoped> .todo-main { margin-left: 0; border: 1px solid #ddd; border-radius: 2px; padding: 0; } .todo-empty{ height: 40px; line-height: 20px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
Item.vue
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104/* eslint-disable vue/no-mutating-props */ <template> <li @mouseenter="mouseHandler(true)" @mouseleave="mouseHandler(false)" :style="{backgroundColor:bgColor,color:myColor}" > <label > <input type="checkbox" v-model="isComplete" /> <span> {{todo.title}}</span> </label> <button class="btn btn-danger" v-if="isShow" @click="del">删除</button> </li> </template> <script lang='ts'> import { computed, defineComponent, ref } from "vue"; import { Todo } from "../type/todo" export default defineComponent({ name: "Item", props: { todo: { type: Object as () => Todo, required: true }, delTodo: { type: Function, required : true }, index: { type: Number, required: true }, updateTodo: { type: Function, required: true } }, 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 = 'green' isShow.value = true } else { bgColor.value = 'white' myColor.value = 'black' isShow.value = false } } const del = () => { if (window.confirm("是否输出该任务")) { props.delTodo?.(props.index) } } const isComplete = computed({ get() { return props.todo.isCompleted }, set(val: boolean) { props.updateTodo?.(props.todo, val) } }) // 利用计算属性的方式 来让当前的复选框选中/不选中 return { bgColor, myColor, isShow, mouseHandler, del, isComplete } } }); </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; margin-top: 3px; } li::before { content: initial; } li:last-child { border-bottom: none; } </style>
Footer.vue
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79<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="delAll">清除已完成的任务</button> </div> </template> <script lang='ts'> import { Todo } from "@/type/todo"; import { computed, defineComponent } from "vue"; export default defineComponent({ name: "Footer", props: { todos: { type: Array as () => Todo[], required: true }, checkAll: { type: Function, required: true }, clear: { type: Function, required: true } }, setup(props) { const count = computed(()=>{ return props.todos?.reduce((pre,todo)=>pre+(todo.isCompleted?1:0),0) }) const isCheckAll = computed({ get() { return count.value>0 && props.todos.length === count.value }, set(val) { props.checkAll(val) } }) const delAll = ()=>{ if (window.confirm("是否清除已完成的任务")) { props.clear() } } return { count, isCheckAll, delAll } } }); </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>
最后
以上就是感性胡萝卜最近收集整理的关于使用vue3完成todolist案例的全部内容,更多相关使用vue3完成todolist案例内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复