一、设计一个简单的todolist的基本思路
在HTML文件中添加Vue框架,以及一个容器元素来放置todolist的所有元素。
在Vue实例中添加data对象,用于存储todolist的数据,例如一个数组来存储待办事项和已完成事项。
在Vue实例中添加方法,用于添加待办事项、完成事项、删除事项等操作。
在HTML中添加输入框和按钮,用于输入待办事项并添加到列表中。
在HTML中添加复选框和标记按钮,用于将待办事项标记为已完成并移到已完成列表中。
在HTML中添加删除按钮,用于删除已完成事项。
在HTML中添加两个列表,一个用于显示待办事项,一个用于显示已完成事项。
在Vue实例中添加计算属性,用于过滤待办事项和已完成事项。
在Vue实例中添加生命周期钩子,用于从本地存储中获取数据并保存数据。
根据需要添加样式表,美化todolist的外观。
3、涉及的基本功能
1、APP:
1、 数组todos存储待办事项,每项待办包括唯一ID,待办事项内容,是否完成(boolean)
2、 数组filtertodos用于存储根据某种条件过滤后的数组,用于实现“隐藏、显示已完成任务”功能
3、数组todos存储于localstorage中
4、根据需要将todos或filtertodos传递给子组件
5、增删改功能函数
2、myheader:将输入框中输入的数据存储到todos中
3、mylist:用于显示todos各项待办内容,每项待办均有删除和修改菜单,用于修改todos数据或删除
4、myfooter:统计完成任务数量,隐藏或显示已完成任务功能
三、涉及的一些知识
1、flex布局,浮动,滚动条(overflow) 2、利用props从父组件向子组件传送数据;通过on实现从子组件向父组件传递数据;生命周期钩子读取数据(watched);computed计算属性;localstorage相关知识
四、具体代码
//APP
<template>
<div id="app">
<div>
<MyHeader></MyHeader>
<MyList :todos="filtertodos"></MyList>
<MyFooter :todos="todos" :isshowitem="isshowitem"></MyFooter>
</div>
</div>
</template>
<script>
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
import MyHeader from "./components/MyHeader.vue";
export default {
name: "App",
components: {
MyHeader,
MyList,
MyFooter
},
data() {
return {
todos: JSON.parse(localStorage.getItem('todos')) || [],
filtertodos: JSON.parse(localStorage.getItem('todos')) || [],
isshowitem: false
}
},
methods: {
// 添加
recieve(x) {
this.todos.unshift(x);
this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
},
//对某个待办事项进行编辑
handupData(id, newvalue) {
this.todos.forEach((todo) => {
if (todo.id === id)
todo.todo = newvalue
})
this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
},
// 删除某个特定的待办
handleMove(id) {
this.todos = this.todos.filter(todo => todo.id !== id)
this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
},
//检查是否勾选
checkTodo(id) {
this.todos.forEach((todo) => {
if (todo.id === id) todo.done = !todo.done
})
this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
},
//全选
chooseAll(bool) {
this.todos.forEach((todo) => {
todo.done = bool;
})
this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
},
//删除已经完成的项目
deleteHaveDone() {
this.todos = this.todos.filter((todo) => {
return !todo.done
});
this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
},
changeisshowitem() {
this.isshowitem = !this.isshowitem;
this.filtertodos = this.isshowitem ? (this.todos.filter((todo) => { return !todo.done })) : this.todos;
}
},
watch: {
todos: {
deep: true,
handler(value) {
localStorage.setItem('todos', JSON.stringify(value))
// localStorage.clear()
},
},
},
mounted() {
this.$bus.$on('recieve', this.recieve)
this.$bus.$on('checkTodo', this.checkTodo)
this.$bus.$on('handleMove', this.handleMove)
this.$bus.$on('chooseAll', this.chooseAll)
this.$bus.$on('deleteHaveDone', this.deleteHaveDone)
this.$bus.$on('handupData', this.handupData)
this.$bus.$on('changeisshowitem', this.changeisshowitem)
},
beforeDestroy() {
this.$bus.$off('checkTodo')
this.$bus.$off('handleMove')
this.$bus.$off('recieve')
this.$bus.$off('chooseAll')
this.$bus.$off('deleteHaveDone')
},
}
</script>
<style>
#app {
padding: 10px;
border: 1px solid gray;
background-color: rgb(242, 242, 242);
display: flex;
align-items: center;
justify-content: center;
}
button {
margin: 5px 10px;
}
</style>
//myfooter
<template>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" v-model="isAll" />
</label>
<span>
<span>已完成{{ doneNum }}</span>/ 全部{{ total }}
</span>
<button @click="changeisshowitem">{{ isshowitem ? "显示所有" : "隐藏已完成" }}</button>
<button class="btn btn-danger" @click="clearHaveDone">清除已完成任务</button>
</div>
</template>
<script>
export default {
name: 'MyFooter',
// props: ['todos', 'chooseAll', 'deleteHaveDone'],
props: ['todos', 'isshowitem'],
data() {
return {
}
},
computed: {
total() {
return this.todos.length;
},
doneNum() {
// const x = this.todos.reduce((num, todo) => {
// return num + (todo.done ? 1 : 0)
// }, 0)
// return x;
return this.todos.reduce((num, todo) => num + (todo.done ? 1 : 0), 0)
},
isAll: {
get() {
if (this.todos.length === 0) {
return false;
}
return this.todos.length === this.doneNum
},
set(value) {
this.$bus.$emit('chooseAll', value);
}
},
},
methods: {
// checkAll(e) {
// // console.log(e.target.checked);
// this.chooseAll(e.target.checked);
// }
clearHaveDone() {
this.$bus.$emit('deleteHaveDone');
},
changeisshowitem() {
this.$bus.$emit('changeisshowitem');
}
},
}
</script>
<style scoped>
.todo-footer {
height: 50px;
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>
//mylist
<template>
<ul>
<MyItem v-for="(item) in todos" :key="item.id" :item="item">
</MyItem>
</ul>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem
},
props: ['todos']
}
</script>
<style>
ul {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
height: 200px;
overflow-y: auto;
overflow-x: hidden;
}
</style>
//myitem
<template>
<li>
<label>
<!-- <input type="checkbox" :checked="item.done" v-model="item.done" /> -->
<!-- //不建议使用v-model,因为props不能修改 -->
<input type="checkbox" :checked="item.done" @change="handleCheck(item.id)" />
<span :class="{ merge: item.done === false, more: item.done === true }" v-show="!item.isEdit">{{
item.todo
}}</span>
<input v-show="item.isEdit" @blur="upData($event, item)" ref="inputTitle" />
</label>
<button class="btn btn-danger" @click="handleCheck1(item.id)">删除</button>
<button @click="handleEidt(item)">编辑</button>
</li>
</template>
<script>
export default {
name: 'MyItem',
props: ['item'],
methods: {
handleCheck(id) {
this.$bus.$emit('checkTodo', id);
// this.isActive = !this.item.done;
// console.log(this.isActive)
console.log(this.$bus)
},
handleCheck1(id) {
this.$bus.$emit('handleMove', id);
},
handleEidt(item) {
if (item.hasOwnProperty('isEdit')) {
item.isEdit = true;
}
else {
this.$set(item, 'isEdit', true)
}
this.$nextTick(function () {
this.$refs.inputTitle.focus()
})
},
upData(e, item) {
item.isEdit = false;
const newvalue = e.target.value;
this.$bus.$emit('handupData', item.id, newvalue)
}
}
}
</script>
<style class="scoped">
.merge {
color: black;
}
.more {
text-decoration: line-through;
color: rgb(80, 80, 80)
}
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: 9px;
/* border: none; */
border-radius: 5px;
text-align: center;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
li:hover {
background-color: rgb(255, 255, 255);
}
li:hover button {
display: block;
/* background-color: rgb(245, 115, 115); */
}
</style>
//myheader
<template>
<input type="text" placeholder="请输入您的任务名称" @keyup.enter="add" />
</template>
<script>
import { nanoid } from 'nanoid'
export default {
name: 'MyHeader',
data() {
},
methods: {
add(e) {
if (e.target.value === '') {
return
};
const anitem = { id: nanoid(), todo: e.target.value, done: false };
this.$bus.$emit('recieve', anitem);
console.log(this.$bus.recieve);
e.target.value = '';
}
},
// props: ['recieve']
}
</script>
<style scoped>
/*header*/
input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
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>