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>
View Code

 

补充

当内部有嵌套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>
View Code

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/>'
})
View Code

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;
}
View Code

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>
View Code

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>
View Code

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>
View Code

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>
View Code

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>
View Code

 

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>
View Code

 由于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>
View Code

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) || '[]')
  }
}
View Code

 

 

 

 

vue组件通信方式

1. props

2. vue自定义事件

3. 消息订阅发布pusub库

4.slot

5 vuex

 

posted @ 2018-09-19 20:26  EngineTang  阅读(200)  评论(0编辑  收藏  举报