2023/1/9 Vue学习笔记-5-TodoList案例

news2024/9/28 19:22:46

1 静态组件

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader/>
      <UserList/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {}
  },
  methods: {},
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

UserHeader.vue

<template>
  <div class="todo-header">
    <input type="text"
           placeholder="请输入你的任务名称,按回车键确认">
  </div>
</template>

<script>
export default {
  name: `UserList`
}
</script>

<style scoped>
  /* header */
.todo-header input{
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}
.todo-header 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>

UserList.vue

<template>
  <ul class="todo-main">
    <UserItem/>
    <UserItem/>
    <UserItem/>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem}
}
</script>


<style scoped>
/* main */
.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;
}
</style>

UserItem.vue

<template>
  <li>
    <label>
      <input type="checkbox"/>
      <span>xxxxx</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>

</template>

<script>
export default {
  name: `UserItem`
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

UserFooter.vue

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox"/>
    </label>
    <span>
      <span>已完成0</span> / 全部2
    </span>
    <button class="btn btn-danger">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: `UserFooter`
}
</script>

<style scoped>
/* 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>

在这里插入图片描述

2 TodoList案例_初始化列表

UserList.vue

<template>
  <ul class="todo-main">
    <UserItem v-for="todo in todoList" :key="todoList.id" :todo="todo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  }
}
</script>


<style scoped>
/* main */
.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;
}
</style>

UserItem.vue

<template>
  <li>
    <label>
      <input type="checkbox" :checked="todo.done"/>
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>

</template>

<script>
export default {
  name: `UserItem`,
  // 声明接受todo
  props: ['todo']
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

在这里插入图片描述

3 TodoList案例_添加

1)uuid标准:用于生成全球唯一的字符串编码。
(因为这个包太大了,所以本节中用的是uuid的精简版,nanoid)
2)nanoid安装指令:npm i nanoid
 nanoid的库用了分别暴露,所以需要这样引用:
  import {nanoid} from 'nanoid'
3)子组件传值父组件简单原理:
父亲先传给儿子一个函数
儿子在合适的时候调用,调用的时候父亲就能收到参数
4)整个添加的流程:
首先在MyHeader中,添加用了add方法,add方法调用 this.addTodo() 将 todoObj的值传给了App。
传给App后,addTodo方法获取值,操作data中的todos。
Vue收到todos变了,就开始重新解析模板。
重新解析的时候,显然todos已经变了,则MyList中收到的也会跟着变化。
MyList收到数据变化,也开始重新解析模板,通过比较数据,虚拟DOM增加,于是MyItem增加。

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList"/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

UserHeader.vue

<template>
  <div class="todo-header">
    <input type="text"
           placeholder="请输入你的任务名称,按回车键确认"
           @keyup.enter="add"
           v-model="inputText">
  </div>
</template>

<script>
import {nanoid} from "nanoid"

export default {
  name: `UserList`,
  data() {
    return {
      inputText: ''
    }
  },
  methods: {
    add() {
      // 将用户的输入包装成一个对象
      if (this.inputText.trim()) {
        const todo = {id: nanoid(), title: this.inputText, done: false}
        this.addTodo(todo)
        this.inputText = ''
      }
    }
  },
  props: ['addTodo']
}
</script>

<style scoped>
/* header */
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header 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>

UserList.vue

<template>
  <ul class="todo-main">
    <UserItem v-for="todo in todoList" :key="todo.id" :todo="todo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList']
}
</script>


<style scoped>
/* main */
.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;
}
</style>

在这里插入图片描述

4 TodoList案例_勾选

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo"/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

UserList.vue

<template>
  <ul class="todo-main">
    <UserItem
        v-for="todo in todoList"
        :key="todo.id" :todo="todo"
        :checkTodo="checkTodo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList', 'checkTodo']
}
</script>


<style scoped>
/* main */
.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;
}
</style>

UserItem.vue

<template>
  <li>
    <label>
      <!--      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>
</template>

<script>

export default {
  name: `UserItem`,
  // 声明接受todo
  props: ['todo', 'checkTodo'],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo 的 done 取反
      this.checkTodo(id)
    }
  }
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

在这里插入图片描述
提出一个新的问题:

 <input type="checkbox" v-model="todo.done"/>

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList"/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },

  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

UserList.vue

<template>
  <ul class="todo-main">
    <UserItem
        v-for="todo in todoList"
        :key="todo.id" :todo="todo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList']
}
</script>


<style scoped>
/* main */
.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;
}
</style>

UserItem.vue

<template>
  <li>
    <label>
      <!--      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
      <input type="checkbox" v-model="todo.done"/>
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" style="display: none">删除</button>
  </li>
</template>

<script>

export default {
  name: `UserItem`,
  // 声明接受todo pros是只读的、不可以修改
  props: ['todo'],
  methods: {}
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

在这里插入图片描述
1)数据在哪个页面,那么对数据的操作就应该在哪个页面。
2)本节中的第二个做法:

<input type="checkbox" v-model="todo.done"/>

因为v-model是双向绑定数据,所以修改有效。
什么叫props被修改了?

本节中的举例:
 Vue不能监测到的修改:let obj = {a:1,b:2},修改成 obj.a = 666
 Vue能监测到的修改:let obj = {a:1,b:2},修改成 obj = {x:100,y:200}
 也就是说Vue监视的是浅层次的修改。
不建议这样做,因为这样会违反原则修改props,虽然并没有被Vue监测到。

5 TodoList案例_删除

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

UserList.vue

<template>
  <ul class="todo-main">
    <UserItem
        v-for="todo in todoList"
        :key="todo.id" :todo="todo"
        :checkTodo="checkTodo"
        :deleteTodo="deleteTodo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList', 'checkTodo', 'deleteTodo']
}
</script>


<style scoped>
/* main */
.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;
}
</style>

UserItem.vue

<template>
  <li>
    <label>
      <!--      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template>

<script>

export default {
  name: `UserItem`,
  // 声明接受todo
  props: ['todo', 'checkTodo', 'deleteTodo'],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo 的 done 取反
      this.checkTodo(id)
    },
    handleDelete(id) {
      if (confirm('确定删除吗?')) {
        this.deleteTodo(id)
      }
    }
  }
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

在这里插入图片描述

6 TodoList案例_底部统计

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter :todoList="todoList"/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

UserFooter.vue

<template>
  <div class="todo-footer">
    <label>
      <input type="checkbox"/>
    </label>
    <span>
      <span>已完成{{ doneTotal }}</span> / 全部{{ todoList.length }}
    </span>
    <button class="btn btn-danger">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: `UserFooter`,
  props: ['todoList'],
  computed: {
    doneTotal: function () {
      // return this.todoList.filter(todo => todo.done).length
      return this.todoList.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
    }
  }
}
</script>

<style scoped>
/* 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>

在这里插入图片描述

7 TodoList案例_底部交互

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter :todoList="todoList" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: [
        {id: '0001', title: '抽烟', done: true},
        {id: '0002', title: '喝酒', done: false},
        {id: '0003', title: '烫头', done: true},
      ]
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    },
    checkAllTodo(done) {
      this.todoList.forEach(todo => {
        todo.done = done
      })
    },
    clearAllTodo() {
      this.todoList = this.todoList.filter(todo => !todo.done)
    }
  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

UserFooter.vue

<template>
  <div class="todo-footer" v-show="total">
    <label>
      <!--      <input type="checkbox" :checked="isAll" @change="checkAll"/>-->
      <input type="checkbox" v-model="isAll"/>
    </label>
    <span>
      <span>已完成{{ doneTotal }}</span> / 全部{{ total }}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: `UserFooter`,
  props: ['todoList', 'checkAllTodo', 'clearAllTodo'],
  computed: {
    doneTotal() {
      // return this.todoList.filter(todo => todo.done).length
      return this.todoList.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
    },
    total() {
      return this.todoList.length
    },
    // 只被读取、不被修改的方式-函数式
    /*isAll() {
      return this.doneTotal === this.total && this.total > 0
    },*/
    isAll: {
      get() {
        return this.doneTotal === this.total && this.total > 0
      },
      set(value) {
        this.checkAllTodo(value)
      }
    },


  },
  methods: {
    checkAll(event) {
      this.checkAllTodo(event.target.checked)
    },
    clearAll(event) {
      this.checkAllTodo()
    }
  }
}
</script>

<style scoped>
/* 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>

在这里插入图片描述

8 TodoList案例_总结

1.组件化编码流程:

1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
一个组件在用:放在组件自身即可。
一些组件在用:放在他们共同的父组件上(状态提升)
3)实现交互:从绑定事件开始。

2.props适用于:

1)父组件 ==> 子组件 通信
2)子组件 ==> 父组件 通信 (要求父先给子一个函数)

3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

9 浏览器本地存储

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="localStorage">
    <button onclick="save()">点击保存一个数据</button>
    <button onclick="read()">点击读取一个数据</button>
    <button onclick="deleteOne()">点击删除一个数据</button>
    <button onclick="clearAll()">点击清空所有数据</button>
</div>
<script type="text/javascript">
    let person = {name: 'zhaoshuai-lc', age: 27}
    let name = 'zhaoshuai-lc'
    let age = '27'
    function save() {
        localStorage.setItem('name', name)
        localStorage.setItem('age', 'age')
        localStorage.setItem('person', JSON.stringify(person))
    }

    function read() {
        console.log(localStorage.getItem('person'));
        console.log(localStorage.getItem('name'));
        console.log(localStorage.getItem('age'));
    }

    function deleteOne() {
        localStorage.removeItem('name')
    }

    function clearAll() {
        localStorage.clear()
    }
</script>
</body>
</html>

在这里插入图片描述
清空之后 再读取数据:
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="sessionStorage">
    <button onclick="save()">点击保存一个数据</button>
    <button onclick="read()">点击读取一个数据</button>
    <button onclick="deleteOne()">点击删除一个数据</button>
    <button onclick="clearAll()">点击清空所有数据</button>
</div>
<script type="text/javascript">
    let person = {name: 'zhaoshuai-lc', age: 27}
    let name = 'zhaoshuai-lc'
    let age = '27'

    function save() {
        sessionStorage.setItem('name', name)
        sessionStorage.setItem('age', 'age')
        sessionStorage.setItem('person', JSON.stringify(person))
    }

    function read() {
        console.log(sessionStorage.getItem('person'));
        console.log(sessionStorage.getItem('name'));
        console.log(sessionStorage.getItem('age'));
    }

    function deleteOne() {
        sessionStorage.removeItem('name')
    }

    function clearAll() {
        sessionStorage.clear()
    }
</script>
</body>
</html>

1.存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
2.浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3.相关API:

1)xxxxxStorage.setItem( 'key', 'value' );
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
2)xxxxxStorage.getItem( 'person' );
该方法接受一个键名作为参数,返回键名对应的值。
3)xxxxxStorage.removeItem( 'key' );
 该方法接受一个键名作为参数,并把该键名从存储中删除。
4)xxxxxStorage.clear()
 该方法会清空存储中的所有数据。

4.备注:

1)SessionStorage存储的内容会随着浏览器窗口关闭而消失。
2)LocalStorage存储的内容,需要手动清楚才会消失。
3)xxxxxStorage.getItem( xxx ) 如果xxx对应的value获取不到,那么getItem的返回值是null4JSON.parse(null)的结果依然是null

10 TodoList_本地存储

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader :addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter :todoList="todoList" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: JSON.parse(localStorage.getItem('todoList')) || []
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    },
    checkAllTodo(done) {
      this.todoList.forEach(todo => {
        todo.done = done
      })
    },
    clearAllTodo() {
      this.todoList = this.todoList.filter(todo => !todo.done)
    }
  },

  watch: {
    // 默认监视的是数组层次-浅监视
    /*    todoList(value) {
          localStorage.setItem('todoList', JSON.stringify(value))
        },*/
    todoList: {
      // 深度监视
      deep: true,
      handler(newValue, oldValue) {
        localStorage.setItem('todoList', JSON.stringify(newValue))
      }
    },

  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

在这里插入图片描述

11 组件自定义事件_绑定

子组件给父组件传值:

School.vue

<template>
  <!--  组件的结构-->
  <div class="sc">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <button @click="sendSchoolName">传值给App-SchoolName</button>
  </div>
</template>

<script>

export default {
  name: `School`, // 跟文件名保持一致
  data() {
    return {
      name: '北京大学',
      address: '北京'
    }
  },
  methods: {
    sendSchoolName() {
      return this.getSchoolName(this.name)
    }
  },
  props: ['getSchoolName']
}
</script>

<style>
/*组件的样式*/
.sc {
  background-color: aquamarine;
  padding: 5px;
}
</style>

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <School :getSchoolName="getSchoolName"/>
    <Student/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-zhaoshuai-lc',
    }
  },
  methods: {
    getSchoolName(name) {
      console.log(name)
    }
  },
  mounted() {
  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

在这里插入图片描述

自定义事件-1:通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on)

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>
    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <Student v-on:inspur="getStudentName"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-zhaoshuai-lc',
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name) {
      console.log('getStudentName', name)
    }
  },
  mounted() {
  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

Student.vue

<template>
  <!--  组件的结构-->
  <div class="student">
    <h2>学生名字:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">传值给App-StudentName</button>
  </div>
</template>

<script>

export default {
  name: `Student`,
  data() {
    return {
      name: 'zhaoshuai-lc',
      sex: 'male'
    }
  },
  methods: {
    sendStudentName() {
      // 触发Student组件身上的inspur事件
      this.$emit('inspur', this.name)
    }
  }
}
</script>

<style>
/*组件的样式*/
.student {
  background-color: pink;
  padding: 5px;
}
</style>

在这里插入图片描述

自定义事件-2 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref)

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <!--    <Student v-on:inspur.once="getStudentName"/>-->

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <Student ref="student"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-zhaoshuai-lc',
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      console.log('getStudentName', name, params)
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 触发一次
      this.$refs.student.$once('inspur', this.getStudentName)
    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

Student.vue

<template>
  <!--  组件的结构-->
  <div class="student">
    <h2>学生名字:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">传值给App-StudentName</button>
  </div>
</template>

<script>

export default {
  name: `Student`,
  data() {
    return {
      name: 'zhaoshuai-lc',
      sex: 'male'
    }
  },
  methods: {
    sendStudentName() {
      // 触发Student组件身上的inspur事件
      this.$emit('inspur', this.name, this.sex)
    }
  }
}
</script>

<style>
/*组件的样式*/
.student {
  background-color: pink;
  padding: 5px;
}
</style>

在这里插入图片描述

12 组件自定义事件_绑定

销毁了当前student组件的实例vc,销毁后所有student实例的自定义事件全都不奏效。

App.vue

<template>
  <div class="app">
    <h1>{{ msg }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <Student @inspur.once="getStudentName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <!--    <Student ref="student"/>-->
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-zhaoshuai-lc',
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      console.log('getStudentName', name, params)
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 触发一次
      this.$refs.student.$once('inspur', this.getStudentName)
    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

Student.vue

<template>
  <!--  组件的结构-->
  <div class="student">
    <h2>学生名字:{{ name }}</h2>
    <h2>学生性别:{{ sex }}</h2>
    <button @click="sendStudentName">传值给App-StudentName</button>
    <button @click="unbind">解绑inspur事件</button>
  </div>
</template>

<script>

export default {
  name: `Student`,
  data() {
    return {
      name: 'zhaoshuai-lc',
      sex: 'male'
    }
  },
  methods: {
    sendStudentName() {
      // 触发Student组件身上的inspur事件
      this.$emit('inspur', this.name, this.sex)
    },
    unbind() {
      // 解绑一个自定义事件
      // this.$off('inspur')

      // 解绑多个自定义事件 用一个数组传值
      // this.$off(['inspur'])

      // 所有的自定义事件全都解绑
      this.$off([])
    }
  }
}
</script>

<style>
/*组件的样式*/
.student {
  background-color: pink;
  padding: 5px;
}
</style>

13 组件自定义事件_总结

App.vue

<template>
  <div class="app">
    <h1>{{ msg }} {{ studentName }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <Student @inspur.once="getStudentName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <!--    <Student ref="student"/>-->
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-',
      studentName: ''
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      this.studentName = name
      console.log('getStudentName', name, params)
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 触发一次
      this.$refs.student.$once('inspur', this.getStudentName)
    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

在这里插入图片描述

引出问题1:

<template>
  <div class="app">
    <h1>{{ msg }} {{ studentName }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <!--    <Student @inspur.once="getStudentName"/>-->

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <Student ref="student"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-',
      studentName: ''
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      this.studentName = name
      console.log('getStudentName', name, params)
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 改写方式一:
      this.$refs.student.$on('inspur', function () {
        this.studentName = name
      })

    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

在这里插入图片描述
在这里插入图片描述
正确方式1:

this.$refs.student.$on('inspur', this.getStudentName)

正确方式2:箭头函数 this->App.vue

<template>
  <div class="app">
    <h1>{{ msg }} {{ studentName }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <!--    <Student @inspur.once="getStudentName"/>-->

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <Student ref="student"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-',
      studentName: ''
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      this.studentName = name
      console.log('getStudentName', name, params)
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 改写方式一:
      this.$refs.student.$on('inspur', (name) => {
        console.log(this)
        this.studentName = name
      })

    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

引出问题2: 自定义组件绑定原生事件 (原生事件为自定义事件)

<Student ref="student" @click.native="showAlert"/>
<template>
  <div class="app">
    <h1>{{ msg }} {{ studentName }}</h1>
    <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
    <School :getSchoolName="getSchoolName"/>

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第一种写法,使用@或v-on) -->
    <!--    <Student @inspur.once="getStudentName"/>-->

    <!-- 通过父组件给子组件绑定一个自定义事件:子给父传递数据(第二种写法,使用ref) -->
    <Student ref="student" @click.native="showAlert"/>
  </div>
</template>

<script>
import School from "./components/School"
import Student from './components/Student'

export default {
  name: "App",
  components: {Student, School},
  data() {
    return {
      msg: 'Hello-',
      studentName: ''
    }
  },
  methods: {
    getSchoolName(name) {
      console.log('getSchoolName', name)
    },
    getStudentName(name, ...params) {
      this.studentName = name
      console.log('getStudentName', name, params)
    },
    showAlert() {
      alert('@click.native="showAlert"')
    }
  },
  mounted() {
    setTimeout(() => {
      // this.$refs.student.$on('inspur', this.getStudentName)
      // 改写方式一:
      this.$refs.student.$on('inspur', (name) => {
        console.log(this)
        this.studentName = name
      })

    }, 0)

  },
}
</script>

<style scoped>
.app {
  background-color: gray;
}
</style>

在这里插入图片描述
组件的自定义事件
1.一种组件间通信的方式,适用于:子组件 ==> 父组件。
2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
3.绑定自定义事件:
(1)第一种方式,在父组件中:

<Demo @inpsur="test"/><Demo v-on:inspur="test"/>

(2)第二种方式,在父组件中:

<Demo ref="demo">
......

mounted(){
    this.$refs.xxx.$on('inspur',this.test)
}

(3)若想让自定义事件只能触发一次,可以使用 once 修饰符或 $once 方法。

4.触发自定义事件:this.$emit('inspur',数据)
5.解绑自定义事件:this.$off('inspur')
6.组件上也可以绑定原生DOM事件,需要使用 native 修饰符。
7.注意:通过 this.$refs.xxx.$on('inspur',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

14 TodoList案例_自定义事件

App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
      <UserHeader @addTodo="addTodo"/>
      <UserList :todoList="todoList" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
      <UserFooter :todoList="todoList" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
    </div>
  </div>
</template>
<script>

import UserHeader from "@/components/UserHeader"
import UserFooter from "@/components/UserFooter"
import UserList from "@/components/UserList"

export default {
  name: 'App',
  data() {
    return {
      todoList: JSON.parse(localStorage.getItem('todoList')) || []
    }
  },
  methods: {
    addTodo(data) {
      this.todoList.unshift(data)
    },
    checkTodo(id) {
      this.todoList.forEach(todo => {
        if (todo.id === id) {
          todo.done = !todo.done
        }
      })
    },
    deleteTodo(id) {
      this.todoList = this.todoList.filter(todo => {
        return todo.id !== id
      })
    },
    checkAllTodo(done) {
      this.todoList.forEach(todo => {
        todo.done = done
      })
    },
    clearAllTodo() {
      this.todoList = this.todoList.filter(todo => !todo.done)
    }
  },

  watch: {
    todoList: {
      // 深度监视
      deep: true,
      handler(newValue, oldValue) {
        localStorage.setItem('todoList', JSON.stringify(newValue))
      }
    },

  },
  components: {UserHeader, UserList, UserFooter}
}
</script>

<style>
/* base */
body {
  background-color: #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(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  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;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>

UserHeader.vue

<template>
  <div class="todo-header">
    <input type="text"
           placeholder="请输入你的任务名称,按回车键确认"
           @keyup.enter="add"
           v-model="inputText">
  </div>
</template>

<script>
import {nanoid} from "nanoid"

export default {
  name: `UserHeader`,
  data() {
    return {
      inputText: ''
    }
  },
  methods: {
    add() {
      // 将用户的输入包装成一个对象
      if (this.inputText.trim()) {
        const todo = {id: nanoid(), title: this.inputText, done: false}
        this.$emit('addTodo', todo)
        this.inputText = ''
      }
    }
  },
}
</script>

<style scoped>
/* header */
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}

.todo-header 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>

UserFooter.vue

<template>
  <div class="todo-footer" v-show="total">
    <label>
      <input type="checkbox" :checked="isAll" @change="checkAll"/>
      <!--      <input type="checkbox" v-model="isAll"/>-->
    </label>
    <span>
      <span>已完成{{ doneTotal }}</span> / 全部{{ total }}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: `UserFooter`,
  props: ['todoList'],
  computed: {
    doneTotal() {
      // return this.todoList.filter(todo => todo.done).length
      return this.todoList.reduce((pre, current) => pre + (current.done ? 1 : 0), 0)
    },
    total() {
      return this.todoList.length
    },
    // 只被读取、不被修改的方式-函数式
    /*isAll() {
      return this.doneTotal === this.total && this.total > 0
    },*/
    isAll: {
      get() {
        return this.doneTotal === this.total && this.total > 0
      },
      set(value) {
        // this.checkAllTodo(value)
        this.$emit('checkAllTodo', value)
      }
    },


  },
  methods: {
    checkAll(event) {
      // this.checkAllTodo(event.target.checked)
      this.$emit('checkAllTodo', event.target.checked)
    },
    clearAll(event) {
      // this.clearAllTodo()
      this.$emit('clearAllTodo')
    }
  }
}
</script>

<style scoped>
/* 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>

UserList.vue

<template>
  <ul class="todo-main">
    <UserItem
        v-for="todo in todoList"
        :key="todo.id" :todo="todo"
        :checkTodo="checkTodo"
        :deleteTodo="deleteTodo"></UserItem>
  </ul>
</template>

<script>
import UserItem from "@/components/UserItem";

export default {
  name: `UserList`,
  components: {UserItem},
  data() {
    return {}
  },
  props: ['todoList', 'checkTodo', 'deleteTodo']
}
</script>


<style scoped>
/* main */
.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;
}
</style>

UserItem.vue

<template>
  <li>
    <label>
      <!--      <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>-->
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
      <span>{{ todo.title }}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template>

<script>

export default {
  name: `UserItem`,
  // 声明接受todo
  props: ['todo', 'checkTodo', 'deleteTodo'],
  methods: {
    handleCheck(id) {
      // 通知App组件将对应的todo 的 done 取反
      this.checkTodo(id)
    },
    handleDelete(id) {
      if (confirm('确定删除吗?')) {
        this.deleteTodo(id)
      }
    }
  }
}
</script>

<style scoped>
/* 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;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/150427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计组】FPGA和ASIC--《深入浅出计算机组成原理》(七)

课程链接&#xff1a;深入浅出计算机组成原理_组成原理_计算机基础-极客时间 目录 一、FPGA &#xff08;一&#xff09;FPGA 的解决方案步骤 1、用存储换功能实现组合逻辑 2、对于需要实现的时序逻辑电路&#xff0c;在 FPGA 里面直接放上 D 触发器&#xff0c;作为寄存…

工业清洗企业资质证书

工业清洗在美国、日本、新加坡、西欧等国发展较早&#xff0c;已经建立起专业化程度很高的化学清洗体系。我国的工业清洗发展很快&#xff0c;目前已经初步形成了新兴的清洗产业网络&#xff0c;清洗技术也已达到国际先进水平&#xff0c;具备了清洗大型设备的能力和经验。 工业…

CANoe-诊断控制台实现同一个目标ECU的物理寻址和功能寻址

接触过UDS诊断的人应该知道,诊断通信有两种方式:物理寻址和功能寻址。那什么是物理寻址和功能寻址呢? 简单点说,物理寻址是单播,功能寻址是多播。具体来说,由于UDS诊断通信的C/S模式(客户端Tester/服务器ECU),物理寻址是Tester发送的诊断请求,只有一个目标ECU回复诊…

MySQL模块

目录 1.在项目中操作数据库的步骤 2.安装与配置 mysql 模块 1.安装模块 2.配置mysql模块 3.测试模块是否正常工作 3.使用 mysql 模块操作 MySQL 数据库 查询数据&#xff1a; 插入数据&#xff1a; 快捷插入数据&#xff1a; 更新数据&#xff1a; 快捷更新数据&am…

node.js(3)--线程和进程、node简介

目录 进程和线程 Node.js 简介 历史 进程和线程 进程 负责为程序的运行提供必备的环境就相当于工厂中的车间&#xff08;专门存放代码的地方&#xff09; 线程 计算机中最小的计算单位&#xff0c;线程负责进程中的程序就相当于工厂中的工人 单线程 JS是单线程 多线程 …

ansible (第三天)

2.6 lineinfile模块 lineinfile模块&#xff0c;确保"某一行文本"存在于指定的文件中&#xff0c;或者确保从文件中删除指定的"文本"&#xff08;即确保指 定的文本不存在于文件中&#xff09;&#xff0c;还可以根据正则表达式&#xff0c;替换"某一…

测牛学堂:软件测试python基础学习之数据类型详解(一)

python数据类型详解 为什么需要数据类型呢&#xff1f; 我们人脑可以轻松的区别不同类型的数据&#xff0c;比如看到1你就知道是数字&#xff0c;但是计算机做不到。 计算机工作的过程就是完成不同的类型的计算&#xff0c;例如做数学运算&#xff0c;做文件存储&#xff0c;逻…

【技术分享】Windows平台低延迟RTMP、RTSP播放器接口设计探讨

背景我们看过了太多介绍RTSP、RTMP播放相关的技术资料&#xff0c;大多接口设计简约&#xff0c;延迟和扩展能力也受到一定的局限&#xff0c;好多开发者希望我们能从接口设计的角度&#xff0c;大概介绍下大牛直播SDK关于RTMP、RTSP播放器开发设计&#xff0c;本文以Windows平…

redis 运维讲解02

一、数据持久化 1、为什么要持久化 redis 重启后&#xff0c;redis 存在内存数据中数据丢失&#xff0c;不管之前是多少G数据&#xff0c;秒丢&#xff0c;而且无法恢复&#xff0c;数据在内存中 [root86-5-master ~]# redis-cli -p 6379 127.0.0.1:6379> MSET k1 v1 k2…

浏览器相关知识

本文主要进行浏览器相关知识的整理总结。 一、浏览器的存储 浏览器的存储包括cookie&#xff0c;session&#xff0c;LocalStorage&#xff0c;sessionStorage&#xff0c;indexedDB。 作用cookiesessionsessionStorageLocalStorageindexedDB储存时间设置或不设置默认30分钟仅…

就只有这么简单?全自动加药装置远程维护解决方案

一、行业背景说起工业生产&#xff0c;给人们的普遍印象都是浓烟&#xff0c;废水&#xff0c;环境污染。尤其是石油、化工、发电厂等一些具有大型设备的地方&#xff0c;确实常常都会有浓烟和污水产出&#xff0c;让人看了恨不得离得越远越好&#xff01;但是随着现代科技的发…

java和vue开发的电子书系统自动检测敏感词小说网站

简介 电子书系统&#xff0c;注册用户上传txt&#xff0c;系统自动检测敏感词汇并且自动生成章节。管理员审核电子书&#xff0c;管理电子书分类和用户&#xff0c;评论等。注册用户可以搜索浏览电子书&#xff0c;在线阅读和下载电子书。 演示视频&#xff1a;https://www.b…

Java设计模式-单例模式Singleton

介绍 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法(静态方法)。 比如 Hibernate 的 SessionFactory&#xff0c;它充当数据存储源的代理&#xff0…

系分 - 案例分析 - 系统规划

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录系分 - 案例分析 - 系统规划典型例题 1题目描述参考答案典型例题 2题目描述参考答案典型例题 3题目描述参考答案系分 - 案例分析 - 系统规划 典型例题 1 题目描述 某软件开发企业受对外贸易公司委托开发…

STM32F429连接USB飞行摇杆

本文介绍如何使用stm32连接usb接口的飞行摇杆。开发环境硬件: STM32F429IGT6开发板&#xff0c;USB接口的飞行摇杆。软件&#xff1a;STM32CubeIDE 1.11仿真器&#xff1a;stlink参考书&#xff1a;《圈圈教你玩USB》USB设备描述符一个USB设备只有一个设备描述符。USB主机通过不…

springboot3.0+GraalVM搭建云原生环境

1.首先下载安装GraalVM 选择java17.windows(amd64),最好选择VPN下载 下载完成以后解压&#xff0c;如图 然后配置环境变量 配置GRAALVM_HOME&#xff0c;如图 然后在PATH里面添加 %GRAALVM_HOME%\bin 配置完成以后&#xff0c;在cmd里面执行java -version,可以看到jdk已经是…

Apache Shiro教程(2)

Shiro实战教程 1、权限的管理 1.1、什么是权限管理 1、基本上涉及到用户参与的系统都需要进行权限管理&#xff0c;权限管理属于系统安全的范畴&#xff0c;权限管理实现对用户访问系统的控制&#xff0c;按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资…

【 uniapp - 黑马优购 | 加入购物车】如何配置 vuex、加入购物车功能并持久化

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

ABP VNext 的日志机制 + SeriLog

** ABP VNext 的日志机制 ** 正用ABP VNext做个系统&#xff0c;由于框架默认带来日志处理机制&#xff0c;开发阶段基本能用&#xff0c;也一直没有去动它&#xff0c;快要上线了&#xff0c;思考了一下正式环境的日志管理流程&#xff0c;由于系统不大&#xff0c;预计访问…

第一章.机器学习基本概念

第一章.机器学习基本概念 1.1 第一章.机器学习基本概念 机器学习的本质就是&#xff1a;寻找一个函数。 1.不同种类的函数 1).Regression&#xff1a;The function outputs a scalar 说明:根据今天的PM2.5的一些参数来预测明天PM2.5值 2).Classification:Given option(classes…