MainTodo组件实现
# 十二. MainTodo 组件实现
# 1 核心组件拆分
可以拆分为 3 个部分
- input 输入框
- todoItem 组件
- todoInfo 组件
# 2 MainTodo 基本实现
# 1) 页面
示例
<template>
<div class="main-todo">
<input type="text" class="add-todo" placeholder="what to do?" autofocus />
</div>
</template>
1
2
3
4
5
2
3
4
5
# 2) 样式
示例
<style lang="stylus" scoped>
.main-todo
margin: 0 auto
width: 600px
background-color: #fff
box-shadow: 0 0 5px #666
.add-todo
padding: 16px 16px 16px 36px
width: 100%
font-size: 24px
font-family: inherit
font-weight: inherit
color: inherit
border: none
outline: none
box-sizing: border-box
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3 TodoItem 组件页面实现
# 1) 页面
示例
<template>
<div class="todo-item">
<input type="checkbox" />
<label>todo1</label>
<button></button>
</div>
</template>
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2) 样式
在 styles 中创建 mixins.styl, 将一些通用样式封装成函数(mixins)
示例
cleanDefaultStyle()
appearance: none
border: none
outline: none
1
2
3
4
2
3
4
在 webpack 中添加 images 的别名
resolve: {
alias: {
'vue': 'vue/dist/vue.js',
'@': path.resolve(__dirname, '../src'),
'styles': path.resolve(__dirname, '../src/assets/styles'),
'images': path.resolve(__dirname, '../src/assets/images')
}
},
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
示例
<style lang="stylus" scoped>
@import '~styles/theme.styl'
@import '~styles/mixins.styl'
.todo-item
display: flex
padding: 10px
border-top: 1px solid rgba(0, 0, 0, 0.1)
font-size: 24px
justify-content: space-between
&:hover
button:after
content: 'x'
font-size: 24px
color: $lightred
&.completed
label
color: #d9d9d9
text-decoration: line-through
input
width: 50px
height: 30px
text-align: center
cleanDefaultStyle()
&:after
content: url('~/http://image.brojie.cn/unChecked.svg')
&:checked:after
content: url('~/http://image.brojie.cn/Checked.svg')
label
flex: 1
transition: color 0.4s
button
width: 40px
background-color: transparent
cleanDefaultStyle()
cursor: pointer
</style>
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
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
# 4 TodoItem 组件业务实现
业务需求分析
- 添加功能
- 选中功能
- 删除功能
# 1) 添加功能
具体需求
- 输入内容, 按下键盘回车键时, 添加一条待办记录
- 如果没有输入内容, 不添加
- 添加后, 之前的内容清空
实现
核心点: 父组件向子组件传值
# i 数据结构
首先, 将数据集中管理, 在 MainTodo 中, 定义 todoData, todoData 是一个数组, 每个元素都是一个对象
示例
data() {
return {
todoData: [
{
id: 0,
content: 'todo1',
completed: false
},
{
id: 1,
content: 'todo2',
completed: false
}
]
}
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ii 绑定 input 事件
示例
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
data() { return { content: '' }},methods: { addTodo() { if (this.content === '') return this.todoData.unshift({ id: 2, content: this.content, completed: false }) this.content = '' }},
1
# iii 遍历显示
<todo-item v-for="(item, index) in todoData" :key="index"></todo-item>
1
# iv 组件传值
在 MainTodo 中, 给子组件添加一个属性来传值
<todo-item
v-for="(item, index) in todoData"
:key="index"
:todo="item"
></todo-item>
1
2
3
4
5
2
3
4
5
在 TodoItem 中, 接受传递过来的值, 并显示
<template>
<div class="todo-item">
<input type="checkbox" />
<label>{{ todo.content }}</label>
<button></button>
</div>
</template>
<script>
export default { name: 'TodoItem', props: { todo: Object } }
</script>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# v 删除测试代码
示例
MainTodo.vue
<template>
<div class="main-todo">
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
<todo-item
v-for="(item, index) in todoData"
:key="index"
:todo="item"
></todo-item>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'let id = 0export default { name: 'MainTodo', data() { return { todoData: [], content: '' } }, methods: { addTodo() { if (this.content === '') return this.todoData.unshift({ id: id++, content: this.content, completed: false }) this.content = '' } }, components: { TodoItem }}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2) 选中功能
具体需求
- 点击选中按钮, 按钮变为选中样式, 并且文本显示被删除(有删除线)
- 再次点击选中按钮, 按钮变为末选中样式, 并且文本正常显示(没有删除线)
实现
核心点: 样式绑定
<template>
<div :class="['todo-item', todo.completed ? 'completed' : '']">
<input type="checkbox" v-model="todo.completed" />
<label>{{ todo.content }}</label>
<button></button>
</div>
</template>
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3) 删除功能
具体需求
- 点击删除按钮时, 删除这条待办记录
实现
核心点: 子组件向父组件传值
示例
TodoItem.vue
<template>
<div :class="['todo-item', todo.completed ? 'completed' : '']">
<input type="checkbox" v-model="todo.completed" />
<label>{{ todo.content }}</label>
<button @click="delItem"></button>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props: { todo: Object },
methods: {
delItem() {
this.$emit('del', this.todo.id)
},
},
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MainTodo.vue
<template>
<div class="main-todo">
<input
type="text"
class="add-todo"
placeholder="what to do?"
autofocus
v-model="content"
@keyup.enter="addTodo"
/>
<todo-item
v-for="(item, index) in todoData"
:key="index"
:todo="item"
@del="handleDeleteItem"
></todo-item>
</div>
</template>
<script>
import TodoItem from './coms/TodoItem.vue'let id = 0export default { name: 'MainTodo', data() { return { todoData: [], content: '' } }, methods: { addTodo() { if (this.content === '') return this.todoData.unshift({ id: id++, content: this.content, completed: false }) this.content = '' }, handleDeleteItem(id) { this.todoData.splice( this.todoData.findIndex(item => item.id === id), 1 ) } }, components: { TodoItem }}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5 TodoInfo 组件页面实现
# 1) 页面
页面由 3 部分组成
- 总计
- tab 选项卡
- 清除框
TodoInfo.vue
<template>
<div>
<span>1 item left</span>
<div>
<a v-for="(item, index) in states" :key="index">{{ item }}</a>
</div>
<button>Clear Completed</button>
</div>
</template>
<script>
export default {
name: 'TodoInfo',
data() {
return { states: ['all', 'active', 'completed'] }
},
}
</script>
<style lang="stylus" scoped></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在 MainTodo.vue 中引入 TodoInfo 组件
# 2) 样式
mixins.styl
btn(c, border = false) padding: 0 10px border-radius: 5px cursor: pointer appearance: none border: none outline: none if (border == true) border: 1px solid c else background-color: c color: #fffprimaryBtn() btn(rgb(252, 157, 154))primaryBorderBtn() btn(rgb(252, 157, 154), true)infoBtn() btn(rgb(131, 175, 155))
1
优化之后
<template> <div class="todo-info"> <span class="total">1 item left</span> <div class="tabs"> <a class="btn primary border" v-for="(item, index) in states" :key="index" >{{item}}</a> </div> <button class="btn info">Clear Completed</button> </div></template><script> export default { name: 'TodoInfo', data() { return { states: ['all', 'active', 'completed'] } } }</script><style lang="stylus" scoped> @import '~styles/theme.styl' @import '~styles/mixins.styl' .todo-info display: flex justify-content: space-between padding: 5px 10px font-weight: 400 line-height: 30px border-top: 1px solid rgba(0, 0, 0, 0.1) .total color: $red .tabs display: flex justify-content: space-between width: 200px .btn.primary.border primaryBorderBtn() &.actived primaryBtn() .btn.info infoBtn()</style>
1
# 6 TodoInfo 组件业务实现
业务需求分析
- 统计功能
- 切换显示不同状态功能
- 删除功能
# 1) 统计功能
具体需求
- 实时统计剩余的待办事项
实现
核心技术点: 监听器 watch
# MainTodo.vue
data 部分
data() { return { todoData: [], content: '', total: 0 }},
1
watch 部分
watch: { todoData: { deep: true, handler() { this.total = this.todoData.filter( item => item.completed == false ).length } }},
1
- deep: true--表示监听每个一个属性的变化
- hander--处理函数, 过滤没有完成的数组, 并获取长度
template 部分
<todo-info :total="total"></todo-info>
1
# TodoInfo.vue
template 部分
<span class="total">{{total}} item left</span>
1
props 部分
props: { total: Number},
1
# 2) 切换显示功能
具体需求
- 点击不同的状态分别显示不同状态的待办事项
- 点击 all, 显示所有的待办事项
- 点击 active, 显示未完成的待办事项
- 点击 completed, 显示已完成的待办事项
实现
核心技术点: 计算属性 computed
# TodoInfo.vue
<template>
<div class="todo-info">
<span class="total">{{ total }} item left</span>
<div class="tabs">
<a
v-for="(item, index) in states"
:key="index"
:class="['btn', 'primary', 'border', state == item ? 'actived' : '']"
@click="toggleState(item)"
>
{{ item }}
</a>
</div>
<button class="btn info">Clear Completed</button>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default { name: 'TodoInfo', props: { total: Number }, data() { return { states: ['all', 'active', 'completed'], state: 'all' } }, methods: { toggleState(state) { this.state = state this.$emit('toggleState', state) } }}
</script>
1
2
3
2
3
# MainTodo.vue
template 部分
<todo-item
v-for="(item, index) in filterData"
:key="index"
:todo="item"
@del="handleDeleteItem"
></todo-item>
<todo-info :total="total" @toggleState="handleToggleState"></todo-info>
1
2
3
4
5
6
7
2
3
4
5
6
7
data 部分
data() { return { todoData: [], content: '', total: 0, filter: 'all' }},
1
methods 部分
handleToggleState(state) { this.filter = state}
1
computed 部分
computed: { filterData() { switch (this.filter) { case 'all': return this.todoData break case 'active': return this.todoData.filter(item => item.completed == false) break case 'completed': return this.todoData.filter(item => item.completed == true) break } }},
1
# 3) 删除功能
具体需求
- 点击 clear completed, 删除所有已经完成的待办事项
# TodoInfo.vue
template 部分
<button class="btn info" @click="clearCompleted">Clear Completed</button>
1
script 部分
methods: { clearCompleted() { this.$emit('clearCompleted') }}
1
MainTodo.vue
template 部分
<todo-info
:total="total"
@toggleState="handleToggleState"
@clearCompleted="handleClear"
></todo-info>
1
2
3
4
5
2
3
4
5
script 部分
handleClear() { this.todoData = this.todoData.filter(item => item.completed == false)}
1
如果觉得有帮助, 可以微信扫码, 请杰哥喝杯咖啡~
上次更新: 2021/09/03, 15:32:17