TODO....
原始html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!--<link rel="stylesheet" href="./static/css/bootstrap.css">-->
<title>vue_demo</title>
<style>
.todo-container {
600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
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: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.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>
</head>
<body>
<div id="app">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" >
</div>
<ul class="todo-main">
<li :style="{background: bgColor}">
<label>
<input type="checkbox">
<span>todotitle</span>
</label>
<button class="btn btn-danger" >删除</button>
</li>
</ul>
<div class="todo-footer">
<label>
<input type="checkbox" >
</label>
<span>
<span>已完成</span>/全部
</span>
<button class="btn btn-danger" >清除已完成任务</button>
</div>
</div>
</div>
</div> <!--app -->
</body>
</html>
补充
当内部有嵌套div时 (回字结构)
onmouseenter onmouseleave 仅对外层边界有触发
onmouseover onmounseout 对内部边界也有触发
reduce forEach filter

1. step1 基本功能
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!--<link rel="stylesheet" href="./static/css/bootstrap.css">-->
<title>vue_demo</title>
<style>
.todo-container {
600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
/*item*/
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: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
/*footer*/
.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>
</head>
<body>
<div id="app">
</div> <!--app -->
</body>
</html>
main.js
/** * Created by infaa on 2018/9/19. */ import Vue from 'vue' import App from './App' import './base.css' /* eslint-disable no-new */ new Vue({ el: '#app', components: {App}, template: '<App/>' })
base.css
body { 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(225, 225, 225, 0.2), 0 1px 2px rgba(0, 0, 0,0.15); 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; }
app.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <TodoHeader :addTodo="addTodo"></TodoHeader> <TodoList :todos="todos" :deleteTodo="deleteTodo"></TodoList> <TodoFooter :todos="todos" :deleteCompleteTodos="deleteCompleteTodos" :selectAllTodos="selectAllTodos"></TodoFooter> </div> </div> </template> <script> import TodoHeader from './components/TodoHeader.vue' import TodoList from './components/TodoList.vue' import TodoFooter from './components/TodoFooter.vue' export default { components: { TodoHeader, TodoList, TodoFooter }, data () { return { todos: [ {title: '吃饭', complete: false}, {title: '睡觉', complete: true}, {title: 'coding', complte: false} ] } }, methods: { addTodo (todo) { this.todos.unshift(todo) }, deleteTodo (index) { this.todos.splice(index, 1) }, deleteCompleteTodos () { this.todos = this.todos.filter(todo => !todo.complete) }, selectAllTodos (check) { this.todos.forEach(todo => (todo.complete = check)) } } } </script> <style> </style>
components/TodoHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="addItem">
</div>
</template>
<script>
export default {
props: {
addTodo: Function
},
data () {
return {
title: ''
}
},
methods: {
addItem () {
const title = this.title.trim()
const addTodo = this.addTodo
if (!title) {
alert('title Kong')
return
}
const todo = {
title,
complete: false
}
addTodo(todo)
this.title = '' // 注意这里要操作this对象而不是函数内局部变量title
// 验证合法性 生成对象 添加 还原
}
}
}
</script>
<style>
</style>
components/TodoList.vue
<template>
<ul class="todo-main">
<TodoItem v-for="(todo,index) in todos" :todo="todo" :key="index" :index="index" :deleteTodo="deleteTodo"></TodoItem>
</ul>
</template>
<script>
import TodoItem from './TodoItem.vue'
export default {
props: {
todos: Array,
deleteTodo: Function
},
components: {
TodoItem
}
}
</script>
<style>
</style>
components/TodoItem.vue
<template>
<li :style="{background: bgColor}" @mouseenter="handleEnter(true)" @mouseleave="handleEnter(false)">
<label>
<input type="checkbox" v-model="todo.complete">
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="deleteItem" v-show="isShow">删除</button>
</li>
</template>
<script>
export default {
props: {
todo: Object,
index: Number,
deleteTodo: Function
},
data () {
return {
isShow: false,
bgColor: 'white'
}
},
methods: {
deleteItem () {
const index = this.index
const todo = this.todo
const deleteTodo = this.deleteTodo
if (window.confirm(`删除${todo.title}`)) {
deleteTodo(index)
}
},
handleEnter (isEnter) {
if (isEnter) {
this.isShow = true
this.bgColor = 'grey'
} else {
this.isShow = false
this.bgColor = 'white'
}
}
}
}
</script>
<style>
</style>
components/TodoFooter.vue
<template>
<div class="todo-footer">
<label>
<input type="checkbox" v-model="isAllChecked">
</label>
<span>
<span>已完成{{completeSize}}</span>/全部{{todos.length}}
</span>
<button class="btn btn-danger" v-show="completeSize" @click="deleteCompleteTodos">清除已完成任务</button>
</div>
</template>
<script>
export default {
props: {
todos: Array,
deleteCompleteTodos: Function,
selectAllTodos: Function
},
computed: {
completeSize () {
const todos = this.todos
return todos.reduce((preTotal, todo) => preTotal + (todo.complete ? 1 : 0), 0)
},
isAllChecked: {
get () {
return this.completeSize === this.todos.length && this.todos.length > 0
},
set (value) {
this.selectAllTodos(value)
}
}
}
}
</script>
<style>
</style>
2. 本地存储
实现刷新后不消失,关闭浏览器重新打开依然有效。
保存于浏览器localstorage

app.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <TodoHeader :addTodo="addTodo"></TodoHeader> <TodoList :todos="todos" :deleteTodo="deleteTodo"></TodoList> <TodoFooter :todos="todos" :deleteCompleteTodos="deleteCompleteTodos" :selectAllTodos="selectAllTodos"></TodoFooter> </div> </div> </template> <script> import TodoHeader from './components/TodoHeader.vue' import TodoList from './components/TodoList.vue' import TodoFooter from './components/TodoFooter.vue' export default { components: { TodoHeader, TodoList, TodoFooter }, data () { return { todos: JSON.parse(window.localStorage.getItem('todos_key') || '[]') } }, methods: { addTodo (todo) { this.todos.unshift(todo) }, deleteTodo (index) { this.todos.splice(index, 1) }, deleteCompleteTodos () { this.todos = this.todos.filter(todo => !todo.complete) }, selectAllTodos (check) { this.todos.forEach(todo => (todo.complete = check)) } }, watch: { todos: { deep: true, handler: function (value) { window.localStorage.setItem('todos_key', JSON.stringify(value)) } } } } </script> <style> </style>
由于todos是列表, 监听为deep,和python deepcopy 类似关注内部元素的内存地址。
把存储抽取为util
app.vue
<template> <div class="todo-container"> <div class="todo-wrap"> <TodoHeader :addTodo="addTodo"></TodoHeader> <TodoList :todos="todos" :deleteTodo="deleteTodo"></TodoList> <TodoFooter :todos="todos" :deleteCompleteTodos="deleteCompleteTodos" :selectAllTodos="selectAllTodos"></TodoFooter> </div> </div> </template> <script> import TodoHeader from './components/TodoHeader.vue' import TodoList from './components/TodoList.vue' import TodoFooter from './components/TodoFooter.vue' import storageUtil from './util/storageUtil.js' export default { components: { TodoHeader, TodoList, TodoFooter }, data () { return { todos: storageUtil.readTodos() } }, methods: { addTodo (todo) { this.todos.unshift(todo) }, deleteTodo (index) { this.todos.splice(index, 1) }, deleteCompleteTodos () { this.todos = this.todos.filter(todo => !todo.complete) }, selectAllTodos (check) { this.todos.forEach(todo => (todo.complete = check)) } }, watch: { todos: { deep: true, // handler: function (value) { //// window.localStorage.setItem('todos_key', JSON.stringify(value)) //// storageUtil.saveTodos(value) // } handler: storageUtil.saveTodos } } } </script> <style> </style>
storageUtil.js
/** * Created by infaa on 2018/9/20. */ const TODO_KEY = 'todos_key' export default { saveTodos(value) { window.localStorage.setItem(TODO_KEY, JSON.stringify(value)) }, readTodos() { return JSON.parse(window.localStorage.getItem(TODO_KEY) || '[]') } }
vue组件通信方式
1. props
2. vue自定义事件
3. 消息订阅发布pusub库
4.slot
5 vuex