用 vue2 写的一个 todolist 案例
2022-03-14 本文已影响0人
小小前端搬运工
整体效果图
vue2-todolist.png
此案例涉及到的知识点
在子组件中需要向父组件传值处使用this.$emit("function",param); 其中function为父组件定义函数,param为需要传递参数
子组件操作.png
父组件监听.png
父组件操作.png
父组件 在引用子组件时,通过属性绑定(v-bind:)的形式,把需要传递给子组件的数据,传递到子组件内部,供子组件使用 例 :remain="remain"
父子传值-父组件.png
子组件通过props接收父组件传递过来的数据
父子传值-子组件.png
localStorage.getItem(key):获取指定key本地存储的值
localStorage.setItem(key,value):将value存储到key字段
setItem-getItem.png
v-model 数据的双向绑定
v-model.png
首先写页面的入口文件,也就是页面的主页面 App.vue
<template>
<div id="app">
<todos-header></todos-header>
<todos-input @all_check_event="handleAllCheck" @save_todo_event="handleSaveTodo" :allChecked="allChecked"></todos-input>
<todos-item
v-for="(todo,index) in filterTodos" :key="todo.id"
:index="index" :todo="todo" @item-click-event="handleItemClick"
@delete-todo-event="handleItemDelete"></todos-item>
<todos-bottom :activeTab="activeTab" @status_change_event="handleStatusChange" :remaining="remaining" @clear_complete_event="handleClearComplete"></todos-bottom>
</div>
</template>
<script>
import TodosHeader from '@/components/todos-header';
import TodosInput from '@/components/todos-input'; // 引入组件
import TodosItem from '@/components/todos-item';
import TodosBottom from '@/components/todos-bottom';
export default {
watch: {
todos: {
deep:true,
handler(newTodos){
localStorage.setItem("todos",JSON.stringify(newTodos))
} // 进行本地数据存储,复杂数据不能直接存,需要转换为json字符串
},
},
computed: {
filterTodos(){
if (this.activeTab === "complete") { // 点击路由状态的改变
return this.todos.filter(c=>c.checked) // 将状态为 "complete" 的数据过滤出来
}
if (this.activeTab === "active") {
return this.todos.filter(c=>!c.checked)
} // 将状态非 "checked" 的数据过滤出来
return this.todos
},
remaining(){
return this.todos.filter(c=>!c.checked).length
}, // 返回非 "checked" 状态数据的长度
allChecked() {
return this.todos.every(t=>t.checked) // 将返回状态为 “checked” 的数据
},
},
methods: {
handleStatusChange(activeTab){
this.activeTab = activeTab
},
handleClearComplete(){
let filterTodos = this.todos.filter((todo)=>{
return !todo.checked
}) // 返回非 "checked" 状态的数据(删除"checked"状态的数据)
this.todos = filterTodos
},
handleItemDelete(index){
this.todos.splice(index,1) // 从当前索引位置开始,删除一个数据
},
handleItemClick:function({index,checked}){
this.todos[index].checked = checked
},
handleAllCheck(checked){
this.todos.map(t=>t.checked = checked) // 将所有数据设置为 “checked”
},
handleSaveTodo:function(todo) {
this.todos.push(todo) // 从数组尾部增加一条数据
},
},
data() {
return {
activeTab: "all",
todos:JSON.parse(localStorage.getItem("todos")||'[]')
}; // 获取本地数据并解析,将之前存储的JSON字符串先转成JSON对象再进行操作
},
components:{
"todos-header":TodosHeader, //
"todos-input":TodosInput,
"todos-item":TodosItem,
"todos-bottom":TodosBottom
}
}
</script>
<style lang="less">
#app {
width: 800px;
margin: 0 auto;
min-height: 100px;
border: 1px solid #ddd;
}
*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
todolist 头部组件 todos-header.vue
<template>
<div class="todos-header">
<h1>todos</h1>
</div>
</template>
<style lang="less" scoped>
.todos-header{
text-align: center;
font-size: 30px;
color: #ead7d7;
h1{
}
}
</style>
输入框的设置 组件todos-input.vue
<template>
<div>
<div class="todos-input">
<div class="todos-input-left" @click.stop="handleAllCheck">
<i :class="[{'iconfont':true}, {'icon-jiantouxia':true},{'active':allChecked}]"></i>
</div>
<div class="todos-input-right">
<input type="text" v-focus v-model="inputVal" @keyup.enter="saveTodo"> // 自动聚焦 按下回车键向数组尾部增加记录
</div>
</div>
<div class="error_msg">
<span style="color: red;">{{error_msg}}</span>
</div>
</div>
</template>
<script>
export default {
name: 'MytodosTodosInput',
props:['allChecked'], // 从父组件中接受数据
data() {
return {
inputVal:"",
"error_msg":''
};
},
directives:{
"focus":{
bind:function (el, binding, vnode, oldVnode) {
console.log(el,binding,vnode,oldVnode);
}, // 自定义 focus 组件
inserted:function(el){
el.focus()
}
}
},
methods: {
handleAllCheck(){
this.$emit('all_checked_event', !this.allChecked);
}, // 向父组件中传递数据,all_checked_event 为父组件定义函数,!this.allChecked 为传递的数据
saveTodo(){
if (!this.inputVal) {
this.error_msg = "todo 不能为空!"
return false
}
this.error_msg = ""
this.$emit('save_todo_event', {
id:Date.now(),
text:this.inputVal,
checked:false
});
this.inputVal = '' // 向数组中添加数据后,清空输入框
}
},
};
</script>
<style lang="less" scoped>
.error_msg{
text-align: center;
}
.todos-input{
height: 50px;
display: flex;
align-items: center;
.todos-input-left{
flex: 1;
text-align: center;
.active{
color: red;
}
}
.todos-input-right{
flex: 7;
input{
height: 40px;
width: 90%;
}
}
}
</style>
组件 todos-item.vue
<template>
<div class="todos-item">
<div class="todos-item-left" @click.stop="handleItemClick">
<i :class="[{'iconfont':true}, {'icon-xuanzhong':todo.checked},{'icon-weixuanzhong':!todo.checked}]"></i>
</div>
<div @dblclick.stop="handleEditTodo" :class="[{'todos-item-mid':true}, {'deleteItem':todo.checked}]">
<span :class="[{'todo-item-text':true},{'hidden':editing}]">{{todo.text}}</span>
<input type="text" @blur="handleSave" v-model="todo.text" :class="[{'todo-item-input':true},{'hidden':!editing}]">
</div>
<div class="todos-item-right" @click="deleteTodo">
<i class="iconfont icon-cross"></i>
</div>
</div>
</template>
<script>
export default {
props:{
todo:{
type:Object,
default:function(){
return {}
}
},
index:{
type:Number,
default:-1
}
},
data() {
return {
editing:false
};
},
methods: {
handleSave(){
this.editing = false
},
handleEditTodo(){
this.editing = true
},
deleteTodo:function(){
this.$emit('delete-todo-event',this.index)
},
handleItemClick:function(){
this.$emit('item-click-event', {index:this.index,checked:!this.todo.checked});
}
},
};
</script>
<style lang="less" scoped>
.todos-item{
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
border-bottom: 1px solid #ddd;
.todos-item-left{
.iconfont{
}
}
.todos-item-mid{
.hidden{
display: none;
}
.todo-item-input{
/* display: none; */
}
.todos-item-text{
}
}
.deleteItem{
color: #ddd;
text-decoration: line-through;
}
&:hover{
.todos-item-right{
visibility: visible;
}
}
.todos-item-right{
color: #cc9a9a;
visibility: hidden;
}
}
</style>
组件 todos-bottom.vue
<template>
<div class="todos-bottom">
<div class="todos-bottom-left">
<span v-if="remain>1">{{remaining}} items left</span>
<span v-else>{{remaining}} items left </span>
</div>
<div class="todos-bottom-mid">
<div :class="[{'bottom-mid-item':true},{active:activeTab==='all'}]" @click.stop="hanldeGoTo('/')">
all
</div>
<div :class="[{'bottom-mid-item':true},{active:activeTab==='active'}]" @click.stop="hanldeGoTo('active')">
active
</div>
<div :class="[{'bottom-mid-item':true},{active:activeTab==='complete'}]" @click.stop="hanldeGoTo('complete')">
completed
</div>
</div>
<div class="todos-bottom-right" @click.stop="handleClearComplete">
clear completed
</div>
</div>
</template>
<script>
export default {
name: 'MytodosTodosBottom',
props:['remaining','activeTab'],
data() {
return {
};
},
methods: {
handleClearComplete(){
this.$emit('clear_complete_event', '');
},
hanldeGoTo(path){
this.$router.push(path)
let activeTab = path
if (path==="/") {
activeTab = "all"
}
this.$emit("status_change_event",activeTab)
}
},
};
</script>
<style lang="less" scoped>
.todos-bottom{
height: 50px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
.todos-bottom-left{
}
.todos-bottom-mid{
display: flex;
.active{
border: 1px solid #e3d1d1;
}
.bottom-mid-item{
height: 30px;
// width: 50px;
padding: 0px 10px;
border-radius: 5px;
margin-right: 5px;
text-align: center;
line-height: 30px;
}
}
}
</style>
子组件操作.png
父组件监听.png
父组件操作.png
父子传值-父组件.png
父子传值-子组件.png