4.vue3-仿todoMVC练习
梓泓 2020-08-28 vue vue3
# todoMVC
个人的vue3仿todoMVC开发的代码
点击上方链接看原todoMVC的例子
# CODE
<template>
<section class="todoapp">
<header class="header">
<h1>Vue3 todos</h1>
<input class="new-todo" placeholder="想干的事" v-model="newTodo" @keyup.enter="addTodo" />
</header>
<section class="main">
<input v-model="isAll" @click="all()" id="toggle-all" type="checkbox" class="toggle-all" />
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li
v-for="(todo, index) in filteredTodos"
class="todo"
:class="{'completed':todo.isCheck,'editing': todo == editedTodo}"
:key="todo.id"
>
<div class="view">
<input @change="clickCheck" type="checkbox" v-model="todo.isCheck" class="toggle" />
<label
@dblclick="editTodo(todo,index,$event)"
@blur="blurTodo"
@keyup.enter="editedTodo = ''"
@input="handleInput(todo,$event)"
v-text="todo.title"
:contenteditable="todo == editedTodo"
></label>
<button class="destroy" @click="removeTodo(index)"></button>
</div>
</li>
</ul>
</section>
<footer class="footer">
<span class="todo-count">
<strong>{{itemNum}}</strong> items left
</span>
<ul class="filters">
<li>
<a @click="select('all')" :class="{selected: visibility == 'all'}">All</a>
</li>
<li>
<a @click="select('active')" :class="{selected: visibility == 'active'}">Active</a>
</li>
<li>
<a @click="select('completed')" :class="{selected: visibility == 'completed'}">Completed</a>
</li>
</ul>
<button @click="clearCom" class="clear-completed">Clear completed</button>
</footer>
</section>
</template>
<script>
import { ref, toRaw, reactive, toRefs, onMounted, computed, nextTick } from 'vue'
export default {
setup() {
const state = reactive({
newTodo: '',
todos: [],
isAll: false,
visibility: 'all',
editedTodo: '',
beforeEditCache: '',
tid: 0,
filters: {
all: function (todos) {
return todos
},
active: function (todos) {
return todos.filter(function (todo) {
return !todo.isCheck
})
},
completed: function (todos) {
return todos.filter(function (todo) {
return todo.isCheck
})
},
},
})
const fun = reactive({
select: (val) => {
state.visibility = val
},
addTodo: () => {
const value = state.newTodo && state.newTodo.trim()
if (!value) return
state.tid++
window.localStorage.setItem('tid', JSON.stringify(state.tid))
state.todos.push({
id: state.tid,
title: value,
isCheck: false,
})
window.localStorage.setItem('todos', JSON.stringify(state.todos))
state.newTodo = ''
},
removeTodo: (num) => {
state.todos.splice(num, 1)
window.localStorage.setItem('todos', JSON.stringify(state.todos))
},
all: () => {
state.isAll = !state.isAll
state.todos.forEach((item) => {
item.isCheck = state.isAll
})
},
clickCheck: () => {
state.isAll = state.todos.every((item) => {
return item.isCheck
})
window.localStorage.setItem('todos', JSON.stringify(state.todos))
},
clearCom: () => {
state.todos = state.todos.filter((item) => {
return !item.isCheck
})
window.localStorage.setItem('todos', JSON.stringify(state.todos))
},
editTodo: (todo, i, el) => {
state.editedTodo = todo
nextTick(() => {
el.target.focus()
})
},
blurTodo: () => {
state.editedTodo = ''
window.localStorage.setItem('todos', JSON.stringify(state.todos))
},
handleInput: (todo, $event) => {
todo.title = $event.target.innerText
},
onMounted: onMounted(() => {
if (JSON.parse(window.localStorage.getItem('todos')) != null) {
state.todos = JSON.parse(window.localStorage.getItem('todos'))
state.tid = JSON.parse(window.localStorage.getItem('tid'))
}
}),
})
const com = reactive({
itemNum: computed(() => {
let num = state.todos.filter((item) => {
return !item.isCheck
})
return num.length
}),
filteredTodos: computed(() => {
return state.filters[state.visibility](state.todos)
}),
})
return {
...toRefs(state),
...toRefs(fun),
...toRefs(com),
}
},
}
</script>
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
html, body
margin 0
padding 0
button
margin 0
padding 0
border 0
background none
font-size 100%
vertical-align baseline
font-family inherit
font-weight inherit
color inherit
-webkit-appearance none
appearance none
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
body
font 14px 'Helvetica Neue', Helvetica, Arial, sans-serif
line-height 1.4em
background #f5f5f5
color #4d4d4d
min-width 230px
max-width 550px
margin 0 auto
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
font-weight 300
:focus
outline 0
.hidden
display none
.toggle
left 0
.todoapp
background #fff
margin 130px 0 40px 0
position relative
box-shadow 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1)
input
&::-webkit-input-placeholder
font-style italic
font-weight 300
color #e6e6e6
&::-moz-placeholder
font-style italic
font-weight 300
color #e6e6e6
&::input-placeholder
font-style italic
font-weight 300
color #e6e6e6
h1
position absolute
top -155px
width 100%
font-size 100px
font-weight 100
text-align center
color rgba(175, 47, 47, 0.15)
-webkit-text-rendering optimizeLegibility
-moz-text-rendering optimizeLegibility
text-rendering optimizeLegibility
.new-todo, .edit
position relative
margin 0
width 100%
font-size 24px
font-family inherit
font-weight inherit
line-height 1.4em
border 0
color inherit
padding 6px
border 1px solid #999
box-shadow inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2)
box-sizing border-box
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
.new-todo
padding 16px 16px 16px 60px
border none
background rgba(0, 0, 0, 0.003)
box-shadow inset 0 -2px 1px rgba(0, 0, 0, 0.03)
.main
position relative
z-index 2
border-top 1px solid #e6e6e6
.toggle-all
text-align center
border none /* Mobile Safari */
opacity 0
position absolute
& + label
width 60px
height 34px
font-size 0
position absolute
top -52px
left -13px
-webkit-transform rotate(90deg)
transform rotate(90deg)
& + label:before
content '❯'
font-size 22px
color #e6e6e6
padding 10px 27px 10px 27px
&:checked + label:before
color #737373
.todo-list
margin 0
padding 0
list-style none
li
position relative
font-size 24px
border-bottom 1px solid #ededed
&:last-child
border-bottom none
&.editing
border-bottom none
padding 0
.edit
display block
width 506px
padding 12px 16px
margin 0 0 0 43px
label
box-shadow 0 0px 5px 0 #333
background-image none !important
input, button
display none !important
&.completed
label
color #333333
text-decoration none
.toggle
text-align center
width 40px
/* auto, since non-WebKit browsers doesn't support input styling */
height auto
position absolute
top 0
bottom 0
margin auto 0
border none /* Mobile Safari */
-webkit-appearance none
appearance none
opacity 0
& + label
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E')
background-repeat no-repeat
background-position center left
&:checked + label
background-image url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E')
label
word-break break-all
padding 15px 40px 15px 60px
display block
line-height 1.2
transition color 0.4s
text-align left
&.completed
label
color #d9d9d9
text-decoration line-through
.destroy
display none
position absolute
top calc(50% - 20px)
right 10px
width 40px
height 40px
margin auto 0
font-size 30px
color #cc9a9a
margin-bottom 11px
transition color 0.2s ease-out
&:hover
color #af5b5e
&:after
content '×'
&:hover .destroy
display block
.edit
display none
.editing:last-child
margin-bottom -1px
.footer
color #777
padding 10px 15px
height 20px
text-align center
border-top 1px solid #e6e6e6
.footer:before
content ''
position absolute
right 0
bottom 0
left 0
height 50px
overflow hidden
box-shadow 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2)
.todo-count
float left
text-align left
.todo-count strong
font-weight 300
.filters
margin 0
padding 0
list-style none
position absolute
right 0
left 0
li
display inline
a
color inherit
margin 3px
padding 3px 7px
text-decoration none
border 1px solid transparent
border-radius 3px
&:hover
border-color rgba(175, 47, 47, 0.1)
&.selected
border-color rgba(175, 47, 47, 0.2)
.clear-completed, html .clear-completed:active
float right
position relative
line-height 20px
text-decoration none
cursor pointer
.clear-completed:hover
text-decoration underline
.info
margin 65px auto 0
color #bfbfbf
font-size 10px
text-shadow 0 1px 0 rgba(255, 255, 255, 0.5)
text-align center
p
line-height 1
a
color inherit
text-decoration none
font-weight 400
&:hover
text-decoration underline
@media screen and (-webkit-min-device-pixel-ratio 0)
.toggle-all, .todo-list li .toggle
background none
.todo-list li .toggle
height 40px
@media (max-width 430px)
.footer
height 50px
.filters
bottom 10px
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319