vue part3.2 小案例 todo 列表展示删除
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 { width: 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 { width: 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