• VueCLI核心知识综合案例TodoList


    目录

    1 拿到一个功能模块首先需要拆分组件:

    2 使用组件实现静态页面的效果

    3 分析数据保存在哪个组件

    4 实现添加数据

    5 实现复选框勾选

    6 实现数据的删除

    7 实现底部组件中数据的统计

    8 实现勾选全部的小复选框来实现大复选框的勾选

    9 实现勾选大复选框来实现所有的小复选框都被勾选

    10 清空所有数据

    11 实现案例中的数据存入本地存储

    12 案例中使用自定义事件完成组件间的数据通信

    13 案例中实现数据的编辑

    14 实现数据进出的动画效果


    【分析】组件化编码的流程

    1. 实现静态组件:抽取组件,使用组件实现静态页面效果

    2.展示动态数据:

            2.1 数据的类型、名称是什么?

            2.2 数据保存在哪个组件?

    3.交互---从绑定事件监听开始


    1 拿到一个功能模块首先需要拆分组件:


    2 使用组件实现静态页面的效果

    【main.js】

    1. import Vue from 'vue'
    2. import App from './App.vue'
    3. Vue.config.productionTip = false
    4. new Vue({
    5. el: '#app',
    6. render: h => h(App)
    7. })

    【MyHeader】

    1. <template>
    2. <div class="todo-header">
    3. <input type="text"/>
    4. div>
    5. template>
    6. <script>
    7. export default {
    8. name: 'MyHeader',
    9. }
    10. script>
    11. <style scoped>
    12. /*header*/
    13. .todo-header input {
    14. width: 560px;
    15. height: 28px;
    16. font-size: 14px;
    17. border: 1px solid #ccc;
    18. border-radius: 4px;
    19. padding: 4px 7px;
    20. }
    21. .todo-header input:focus {
    22. outline: none;
    23. border-color: rgba(82, 168, 236, 0.8);
    24. box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    25. }
    26. style>

    【Item】

    1. <template>
    2. <li>
    3. <label>
    4. <input type="checkbox"/>
    5. <span v-for="todo in todos" :key="todo.id">{{todo.title}}span>
    6. label>
    7. <button class="btn btn-danger">删除button>
    8. li>
    9. template>
    10. <script>
    11. export default {
    12. name: 'Item',
    13. data() {
    14. return {
    15. todos: [
    16. {id: '001', title: '吃饭', done: true},
    17. {id: '002', title: '学习', done: false},
    18. {id: '003', title: '追剧', done: true},
    19. ]
    20. }
    21. },
    22. }
    23. script>
    24. <style scoped>
    25. /*item*/
    26. li {
    27. list-style: none;
    28. height: 36px;
    29. line-height: 36px;
    30. padding: 0 5px;
    31. border-bottom: 1px solid #ddd;
    32. }
    33. li label {
    34. float: left;
    35. cursor: pointer;
    36. }
    37. li label li input {
    38. vertical-align: middle;
    39. margin-right: 6px;
    40. position: relative;
    41. top: -1px;
    42. }
    43. li button {
    44. float: right;
    45. display: none;
    46. margin-top: 3px;
    47. }
    48. li:before {
    49. content: initial;
    50. }
    51. li:last-child {
    52. border-bottom: none;
    53. }
    54. li:hover {
    55. background-color: rgb(196, 195, 195);
    56. }
    57. li:hover button{
    58. display: block;
    59. }
    60. style>

    【List】

    1. <template>
    2. <ul class="todo-main">
    3. <Item>Item>
    4. <Item>Item>
    5. ul>
    6. template>
    7. <script>
    8. import Item from './Item.vue'
    9. export default {
    10. name: 'List',
    11. components:{
    12. Item
    13. },
    14. }
    15. script>
    16. <style scoped>
    17. /*main*/
    18. .todo-main {
    19. margin-left: 0px;
    20. border: 1px solid #ddd;
    21. border-radius: 2px;
    22. padding: 0px;
    23. }
    24. .todo-empty {
    25. height: 40px;
    26. line-height: 40px;
    27. border: 1px solid #ddd;
    28. border-radius: 2px;
    29. padding-left: 5px;
    30. margin-top: 10px;
    31. }
    32. style>

    【MyFooter】

    1. <template>
    2. <div class="todo-footer">
    3. <label>
    4. <input type="checkbox"/>
    5. label>
    6. <span>
    7. <span>已完成 0span> / 3
    8. span>
    9. <button class="btn btn-danger">清除已完成任务button>
    10. div>
    11. template>
    12. <script>
    13. export default {
    14. name: 'MyFooter',
    15. }
    16. script>
    17. <style scoped>
    18. /*footer*/
    19. .todo-footer {
    20. height: 40px;
    21. line-height: 40px;
    22. padding-left: 6px;
    23. margin-top: 5px;
    24. }
    25. .todo-footer label {
    26. display: inline-block;
    27. margin-right: 20px;
    28. cursor: pointer;
    29. }
    30. .todo-footer label input {
    31. position: relative;
    32. top: -1px;
    33. vertical-align: middle;
    34. margin-right: 5px;
    35. }
    36. .todo-footer button {
    37. float: right;
    38. margin-top: 5px;
    39. }
    40. style>

    【App】 

    1. <template>
    2. <div id="root">
    3. <div class="todo-container">
    4. <div class="todo-wrap">
    5. <MyHeader>MyHeader>
    6. <List>List>
    7. <MyFooter>MyFooter>
    8. div>
    9. div>
    10. div>
    11. template>
    12. <script>
    13. import MyHeader from './components/MyHeader.vue'
    14. import List from './components/List.vue'
    15. import MyFooter from './components/MyFooter.vue'
    16. export default {
    17. name:'App',
    18. components:{
    19. MyHeader,
    20. List,
    21. MyFooter
    22. }
    23. }
    24. script>
    25. <style>
    26. /*base*/
    27. body {
    28. background: #fff;
    29. }
    30. .btn {
    31. display: inline-block;
    32. padding: 4px 12px;
    33. margin-bottom: 0;
    34. font-size: 14px;
    35. line-height: 20px;
    36. text-align: center;
    37. vertical-align: middle;
    38. cursor: pointer;
    39. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    40. border-radius: 4px;
    41. }
    42. .btn-danger {
    43. color: #fff;
    44. background-color: #da4f49;
    45. border: 1px solid #bd362f;
    46. }
    47. .btn-danger:hover {
    48. color: #fff;
    49. background-color: #bd362f;
    50. }
    51. .btn:focus {
    52. outline: none;
    53. }
    54. .todo-container {
    55. width: 600px;
    56. margin: 0 auto;
    57. }
    58. .todo-container .todo-wrap {
    59. padding: 10px;
    60. border: 1px solid #ddd;
    61. border-radius: 5px;
    62. }
    63. style>

    通过以上代码就可以实现静态页面的效果了!!!

    3 分析数据保存在哪个组件

    在上述代码中数据是保存在Item组件中的,但是如果想要在后续实现一系列交互效果:在MyHeader组件中需要添加数据,而MyHeader组件和Item组件没有直接的关系, 就当前学习阶段的知识而言,并不能实现这两个组件之间的通信(后续会有解决方案),同理MyFooter也一样。


    【分析】因为App组件是所有组件的父组件,所以数据放在App组件中,再使用props配置,所有的子组件就都可以访问到。

    【App】(同时需要将数据传递到Item组件中,在当前阶段只能通过props配置一层一层往下传,所以是 App-->List,List-->Item

    1. 实现 App-->List 传递todos数据

    1. <template>
    2. <div id="root">
    3. <div class="todo-container">
    4. <div class="todo-wrap">
    5. <MyHeader>MyHeader>
    6. <List :todos="todos">List>
    7. <MyFooter>MyFooter>
    8. div>
    9. div>
    10. div>
    11. template>
    12. <script>
    13. import MyHeader from './components/MyHeader.vue'
    14. import List from './components/List.vue'
    15. import MyFooter from './components/MyFooter.vue'
    16. export default {
    17. name:'App',
    18. components:{
    19. MyHeader,
    20. List,
    21. MyFooter
    22. },
    23. data() {
    24. return {
    25. todos: [
    26. {id: '001', title: '吃饭', done: true},
    27. {id: '002', title: '学习', done: false},
    28. {id: '003', title: '追剧', done: true},
    29. ]
    30. }
    31. },
    32. }
    33. script>
    34. <style>
    35. /*base*/
    36. body {
    37. background: #fff;
    38. }
    39. .btn {
    40. display: inline-block;
    41. padding: 4px 12px;
    42. margin-bottom: 0;
    43. font-size: 14px;
    44. line-height: 20px;
    45. text-align: center;
    46. vertical-align: middle;
    47. cursor: pointer;
    48. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    49. border-radius: 4px;
    50. }
    51. .btn-danger {
    52. color: #fff;
    53. background-color: #da4f49;
    54. border: 1px solid #bd362f;
    55. }
    56. .btn-danger:hover {
    57. color: #fff;
    58. background-color: #bd362f;
    59. }
    60. .btn:focus {
    61. outline: none;
    62. }
    63. .todo-container {
    64. width: 600px;
    65. margin: 0 auto;
    66. }
    67. .todo-container .todo-wrap {
    68. padding: 10px;
    69. border: 1px solid #ddd;
    70. border-radius: 5px;
    71. }
    72. style>

    2. List接受todos数据

    1. <template>
    2. <ul class="todo-main">
    3. <Item v-for="todo in todos"
    4. :key="todo.id">Item>
    5. ul>
    6. template>
    7. <script>
    8. import Item from './Item.vue'
    9. export default {
    10. name: 'List',
    11. components:{
    12. Item
    13. },
    14. props: ['todos']
    15. }
    16. script>
    17. <style scoped>
    18. /*main*/
    19. .todo-main {
    20. margin-left: 0px;
    21. border: 1px solid #ddd;
    22. border-radius: 2px;
    23. padding: 0px;
    24. }
    25. .todo-empty {
    26. height: 40px;
    27. line-height: 40px;
    28. border: 1px solid #ddd;
    29. border-radius: 2px;
    30. padding-left: 5px;
    31. margin-top: 10px;
    32. }
    33. style>

    通过上述的代码便可以根据数据的数量来渲染出几个Item了,但是此时Item里面是没有内容的,所以需要 List-->Item 再次传递每条数据


    3. 实现 List-->Item 传递todo数据

    1. <template>
    2. <ul class="todo-main">
    3. <Item v-for="todo in todos"
    4. :key="todo.id" :todo="todo">Item>
    5. ul>
    6. template>
    7. <script>
    8. import Item from './Item.vue'
    9. export default {
    10. name: 'List',
    11. components:{
    12. Item
    13. },
    14. props: ['todos']
    15. }
    16. script>
    17. <style scoped>
    18. /*main*/
    19. .todo-main {
    20. margin-left: 0px;
    21. border: 1px solid #ddd;
    22. border-radius: 2px;
    23. padding: 0px;
    24. }
    25. .todo-empty {
    26. height: 40px;
    27. line-height: 40px;
    28. border: 1px solid #ddd;
    29. border-radius: 2px;
    30. padding-left: 5px;
    31. margin-top: 10px;
    32. }
    33. style>

    4. Item接受todo数据

    1. <template>
    2. <li>
    3. <label>
    4. <input type="checkbox"/>
    5. <span>{{todo.title}}span>
    6. label>
    7. <button class="btn btn-danger">删除button>
    8. li>
    9. template>
    10. <script>
    11. export default {
    12. name: 'Item',
    13. // 声明接收todo对象
    14. props:['todo'],
    15. }
    16. script>
    17. <style scoped>
    18. /*item*/
    19. li {
    20. list-style: none;
    21. height: 36px;
    22. line-height: 36px;
    23. padding: 0 5px;
    24. border-bottom: 1px solid #ddd;
    25. }
    26. li label {
    27. float: left;
    28. cursor: pointer;
    29. }
    30. li label li input {
    31. vertical-align: middle;
    32. margin-right: 6px;
    33. position: relative;
    34. top: -1px;
    35. }
    36. li button {
    37. float: right;
    38. display: none;
    39. margin-top: 3px;
    40. }
    41. li:before {
    42. content: initial;
    43. }
    44. li:last-child {
    45. border-bottom: none;
    46. }
    47. li:hover {
    48. background-color: rgb(196, 195, 195);
    49. }
    50. li:hover button{
    51. display: block;
    52. }
    53. style>

    4 实现添加数据

    【App】定义接收数据的回调函数

    1. <template>
    2. <div id="root">
    3. <div class="todo-container">
    4. <div class="todo-wrap">
    5. <MyHeader :addTodo="addTodo">MyHeader>
    6. <List :todos="todos">List>
    7. <MyFooter>MyFooter>
    8. div>
    9. div>
    10. div>
    11. template>
    12. <script>
    13. import MyHeader from './components/MyHeader.vue'
    14. import List from './components/List.vue'
    15. import MyFooter from './components/MyFooter.vue'
    16. export default {
    17. name:'App',
    18. components:{
    19. MyHeader,
    20. List,
    21. MyFooter
    22. },
    23. data() {
    24. return {
    25. todos: [
    26. {id: '001', title: '吃饭', done: true},
    27. {id: '002', title: '学习', done: false},
    28. {id: '003', title: '追剧', done: true},
    29. ]
    30. }
    31. },
    32. methods: {
    33. // 添加一个todo
    34. addTodo(todoObj) {
    35. this.todos.unshift(todoObj)
    36. }
    37. }
    38. }
    39. script>
    40. <style>
    41. /*base*/
    42. body {
    43. background: #fff;
    44. }
    45. .btn {
    46. display: inline-block;
    47. padding: 4px 12px;
    48. margin-bottom: 0;
    49. font-size: 14px;
    50. line-height: 20px;
    51. text-align: center;
    52. vertical-align: middle;
    53. cursor: pointer;
    54. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    55. border-radius: 4px;
    56. }
    57. .btn-danger {
    58. color: #fff;
    59. background-color: #da4f49;
    60. border: 1px solid #bd362f;
    61. }
    62. .btn-danger:hover {
    63. color: #fff;
    64. background-color: #bd362f;
    65. }
    66. .btn:focus {
    67. outline: none;
    68. }
    69. .todo-container {
    70. width: 600px;
    71. margin: 0 auto;
    72. }
    73. .todo-container .todo-wrap {
    74. padding: 10px;
    75. border: 1px solid #ddd;
    76. border-radius: 5px;
    77. }
    78. style>

    【MyHeader】实现添加数据的方法

    1. <template>
    2. <div class="todo-header">
    3. <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add" v-model="title"/>
    4. div>
    5. template>
    6. <script>
    7. import {nanoid} from 'nanoid' // 生成id
    8. export default {
    9. name: 'MyHeader',
    10. data() {
    11. return {
    12. title: ''
    13. }
    14. },
    15. props: ['addTodo'], // 接收父组件传过来的addTodo函数
    16. methods: {
    17. add(e) {
    18. // 校验数据
    19. if (!this.title.trim()) return alert('输入不能为空')
    20. // 将用户的输入包装成为一个todo对象
    21. const todoObj = {
    22. id: nanoid(),
    23. /* title: e.target.value, */
    24. title: this.title,
    25. done: false
    26. }
    27. console.log(todoObj)
    28. // console.log(e.target.value)
    29. // console.log(this.title)
    30. // 通知App组件去添加一个todo对象
    31. this.addTodo(todoObj)
    32. // 清空输入
    33. this.title = ''
    34. }
    35. }
    36. }
    37. script>
    38. <style scoped>
    39. /*header*/
    40. .todo-header input {
    41. width: 560px;
    42. height: 28px;
    43. font-size: 14px;
    44. border: 1px solid #ccc;
    45. border-radius: 4px;
    46. padding: 4px 7px;
    47. }
    48. .todo-header input:focus {
    49. outline: none;
    50. border-color: rgba(82, 168, 236, 0.8);
    51. box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
    52. }
    53. style>

    5 实现复选框勾选

    【App】也是要逐层传递

    1. <template>
    2. <div id="root">
    3. <div class="todo-container">
    4. <div class="todo-wrap">
    5. <MyHeader :addTodo="addTodo">MyHeader>
    6. <List :todos="todos" :changeTodo="changeTodo">List>
    7. <MyFooter>MyFooter>
    8. div>
    9. div>
    10. div>
    11. template>
    12. <script>
    13. import MyHeader from './components/MyHeader.vue'
    14. import List from './components/List.vue'
    15. import MyFooter from './components/MyFooter.vue'
    16. export default {
    17. name:'App',
    18. components:{
    19. MyHeader,
    20. List,
    21. MyFooter
    22. },
    23. data() {
    24. return {
    25. todos: [
    26. {id: '001', title: '吃饭', done: true},
    27. {id: '002', title: '学习', done: false},
    28. {id: '003', title: '追剧', done: true},
    29. ]
    30. }
    31. },
    32. methods: {
    33. // 添加一个todo
    34. addTodo(todoObj) {
    35. this.todos.unshift(todoObj)
    36. },
    37. // 勾选或者取消勾选一个todo
    38. changeTodo(id) {
    39. this.todos.forEach((todo) => {
    40. if (todo.id === id) todo.done = !todo.done
    41. })
    42. },
    43. }
    44. }
    45. script>
    46. <style>
    47. /*base*/
    48. body {
    49. background: #fff;
    50. }
    51. .btn {
    52. display: inline-block;
    53. padding: 4px 12px;
    54. margin-bottom: 0;
    55. font-size: 14px;
    56. line-height: 20px;
    57. text-align: center;
    58. vertical-align: middle;
    59. cursor: pointer;
    60. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    61. border-radius: 4px;
    62. }
    63. .btn-danger {
    64. color: #fff;
    65. background-color: #da4f49;
    66. border: 1px solid #bd362f;
    67. }
    68. .btn-danger:hover {
    69. color: #fff;
    70. background-color: #bd362f;
    71. }
    72. .btn:focus {
    73. outline: none;
    74. }
    75. .todo-container {
    76. width: 600px;
    77. margin: 0 auto;
    78. }
    79. .todo-container .todo-wrap {
    80. padding: 10px;
    81. border: 1px solid #ddd;
    82. border-radius: 5px;
    83. }
    84. style>

    【List】

    1. <template>
    2. <ul class="todo-main">
    3. <Item v-for="todo in todos"
    4. :key="todo.id" :todo="todo"
    5. :changeTodo="changeTodo">
    6. Item>
    7. ul>
    8. template>
    9. <script>
    10. import Item from './Item.vue'
    11. export default {
    12. name: 'List',
    13. components:{
    14. Item
    15. },
    16. props: ['todos', 'changeTodo']
    17. }
    18. script>
    19. <style scoped>
    20. /*main*/
    21. .todo-main {
    22. margin-left: 0px;
    23. border: 1px solid #ddd;
    24. border-radius: 2px;
    25. padding: 0px;
    26. }
    27. .todo-empty {
    28. height: 40px;
    29. line-height: 40px;
    30. border: 1px solid #ddd;
    31. border-radius: 2px;
    32. padding-left: 5px;
    33. margin-top: 10px;
    34. }
    35. style>

    【Item】

    1. <template>
    2. <li>
    3. <label>
    4. <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
    5. <span>{{todo.title}}span>
    6. label>
    7. <button class="btn btn-danger">删除button>
    8. li>
    9. template>
    10. <script>
    11. export default {
    12. name: 'Item',
    13. // 声明接收todo对象
    14. props:['todo', 'changeTodo'],
    15. methods: {
    16. // 勾选 or 取消勾选
    17. handleCheck(id) {
    18. // 通知 App组件将对应的todo对象的状态改变
    19. this.changeTodo(id)
    20. }
    21. }
    22. }
    23. script>
    24. <style scoped>
    25. /*item*/
    26. li {
    27. list-style: none;
    28. height: 36px;
    29. line-height: 36px;
    30. padding: 0 5px;
    31. border-bottom: 1px solid #ddd;
    32. }
    33. li label {
    34. float: left;
    35. cursor: pointer;
    36. }
    37. li label li input {
    38. vertical-align: middle;
    39. margin-right: 6px;
    40. position: relative;
    41. top: -1px;
    42. }
    43. li button {
    44. float: right;
    45. display: none;
    46. margin-top: 3px;
    47. }
    48. li:before {
    49. content: initial;
    50. }
    51. li:last-child {
    52. border-bottom: none;
    53. }
    54. li:hover {
    55. background-color: rgb(196, 195, 195);
    56. }
    57. li:hover button{
    58. display: block;
    59. }
    60. style>

    6 实现数据的删除

    【App】

    1. <template>
    2. <div id="root">
    3. <div class="todo-container">
    4. <div class="todo-wrap">
    5. <MyHeader :addTodo="addTodo">MyHeader>
    6. <List
    7. :todos="todos"
    8. :changeTodo="changeTodo"
    9. :deleteTodo="deleteTodo">
    10. List>
    11. <MyFooter>MyFooter>
    12. div>
    13. div>
    14. div>
    15. template>
    16. <script>
    17. import MyHeader from './components/MyHeader.vue'
    18. import List from './components/List.vue'
    19. import MyFooter from './components/MyFooter.vue'
    20. export default {
    21. name:'App',
    22. components:{
    23. MyHeader,
    24. List,
    25. MyFooter
    26. },
    27. data() {
    28. return {
    29. todos: [
    30. {id: '001', title: '吃饭', done: true},
    31. {id: '002', title: '学习', done: false},
    32. {id: '003', title: '追剧', done: true},
    33. ]
    34. }
    35. },
    36. methods: {
    37. // 添加一个todo
    38. addTodo(todoObj) {
    39. this.todos.unshift(todoObj)
    40. },
    41. // 勾选或者取消勾选一个todo
    42. changeTodo(id) {
    43. this.todos.forEach((todo) => {
    44. if (todo.id === id) todo.done = !todo.done
    45. })
    46. },
    47. // 删除一个todo
    48. deleteTodo(id) {
    49. this.todos = this.todos.filter((todo) => todo.id !== id)
    50. },
    51. }
    52. }
    53. script>
    54. <style>
    55. /*base*/
    56. body {
    57. background: #fff;
    58. }
    59. .btn {
    60. display: inline-block;
    61. padding: 4px 12px;
    62. margin-bottom: 0;
    63. font-size: 14px;
    64. line-height: 20px;
    65. text-align: center;
    66. vertical-align: middle;
    67. cursor: pointer;
    68. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    69. border-radius: 4px;
    70. }
    71. .btn-danger {
    72. color: #fff;
    73. background-color: #da4f49;
    74. border: 1px solid #bd362f;
    75. }
    76. .btn-danger:hover {
    77. color: #fff;
    78. background-color: #bd362f;
    79. }
    80. .btn:focus {
    81. outline: none;
    82. }
    83. .todo-container {
    84. width: 600px;
    85. margin: 0 auto;
    86. }
    87. .todo-container .todo-wrap {
    88. padding: 10px;
    89. border: 1px solid #ddd;
    90. border-radius: 5px;
    91. }
    92. style>

    【List】

    1. <template>
    2. <ul class="todo-main">
    3. <Item v-for="todo in todos"
    4. :key="todo.id"
    5. :todo="todo"
    6. :changeTodo="changeTodo"
    7. :deleteTodo="deleteTodo">
    8. Item>
    9. ul>
    10. template>
    11. <script>
    12. import Item from './Item.vue'
    13. export default {
    14. name: 'List',
    15. components:{
    16. Item
    17. },
    18. props: ['todos', 'changeTodo', 'deleteTodo']
    19. }
    20. script>
    21. <style scoped>
    22. /*main*/
    23. .todo-main {
    24. margin-left: 0px;
    25. border: 1px solid #ddd;
    26. border-radius: 2px;
    27. padding: 0px;
    28. }
    29. .todo-empty {
    30. height: 40px;
    31. line-height: 40px;
    32. border: 1px solid #ddd;
    33. border-radius: 2px;
    34. padding-left: 5px;
    35. margin-top: 10px;
    36. }
    37. style>

    【Item】

    1. <template>
    2. <li>
    3. <label>
    4. <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
    5. <span>{{todo.title}}span>
    6. label>
    7. <button class="btn btn-danger" @click="handleDelete(todo.id)">删除button>
    8. li>
    9. template>
    10. <script>
    11. export default {
    12. name: 'Item',
    13. // 声明接收todo对象
    14. props:['todo', 'changeTodo', 'deleteTodo'],
    15. methods: {
    16. // 勾选 or 取消勾选
    17. handleCheck(id) {
    18. // 通知 App组件将对应的todo对象的状态改变
    19. this.changeTodo(id)
    20. },
    21. // 删除操作
    22. handleDelete(id) {
    23. // console.log(id)
    24. if (confirm("确定删除吗?")) {
    25. // 通知App删除
    26. this.deleteTodo(id)
    27. }
    28. }
    29. }
    30. }
    31. script>
    32. <style scoped>
    33. /*item*/
    34. li {
    35. list-style: none;
    36. height: 36px;
    37. line-height: 36px;
    38. padding: 0 5px;
    39. border-bottom: 1px solid #ddd;
    40. }
    41. li label {
    42. float: left;
    43. cursor: pointer;
    44. }
    45. li label li input {
    46. vertical-align: middle;
    47. margin-right: 6px;
    48. position: relative;
    49. top: -1px;
    50. }
    51. li button {
    52. float: right;
    53. display: none;
    54. margin-top: 3px;
    55. }
    56. li:before {
    57. content: initial;
    58. }
    59. li:last-child {
    60. border-bottom: none;
    61. }
    62. li:hover {
    63. background-color: rgb(196, 195, 195);
    64. }
    65. li:hover button{
    66. display: block;
    67. }
    68. style>

    7 实现底部组件中数据的统计

    【分析】如果想要统计数据的数量,就需要将数据传递到MyFooter组件中

    【App】

    1. <template>
    2. <div id="root">
    3. <div class="todo-container">
    4. <div class="todo-wrap">
    5. <MyHeader :addTodo="addTodo">MyHeader>
    6. <List
    7. :todos="todos"
    8. :changeTodo="changeTodo"
    9. :deleteTodo="deleteTodo">
    10. List>
    11. <MyFooter
    12. :todos="todos"
    13. >
    14. MyFooter>
    15. div>
    16. div>
    17. div>
    18. template>
    19. <script>
    20. import MyHeader from './components/MyHeader.vue'
    21. import List from './components/List.vue'
    22. import MyFooter from './components/MyFooter.vue'
    23. export default {
    24. name:'App',
    25. components:{
    26. MyHeader,
    27. List,
    28. MyFooter
    29. },
    30. data() {
    31. return {
    32. todos: [
    33. {id: '001', title: '吃饭', done: true},
    34. {id: '002', title: '学习', done: false},
    35. {id: '003', title: '追剧', done: true},
    36. ]
    37. }
    38. },
    39. methods: {
    40. // 添加一个todo
    41. addTodo(todoObj) {
    42. this.todos.unshift(todoObj)
    43. },
    44. // 勾选或者取消勾选一个todo
    45. changeTodo(id) {
    46. this.todos.forEach((todo) => {
    47. if (todo.id === id) todo.done = !todo.done
    48. })
    49. },
    50. // 删除一个todo
    51. deleteTodo(id) {
    52. this.todos = this.todos.filter((todo) => todo.id !== id)
    53. },
    54. }
    55. }
    56. script>
    57. <style>
    58. /*base*/
    59. body {
    60. background: #fff;
    61. }
    62. .btn {
    63. display: inline-block;
    64. padding: 4px 12px;
    65. margin-bottom: 0;
    66. font-size: 14px;
    67. line-height: 20px;
    68. text-align: center;
    69. vertical-align: middle;
    70. cursor: pointer;
    71. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    72. border-radius: 4px;
    73. }
    74. .btn-danger {
    75. color: #fff;
    76. background-color: #da4f49;
    77. border: 1px solid #bd362f;
    78. }
    79. .btn-danger:hover {
    80. color: #fff;
    81. background-color: #bd362f;
    82. }
    83. .btn:focus {
    84. outline: none;
    85. }
    86. .todo-container {
    87. width: 600px;
    88. margin: 0 auto;
    89. }
    90. .todo-container .todo-wrap {
    91. padding: 10px;
    92. border: 1px solid #ddd;
    93. border-radius: 5px;
    94. }
    95. style>

    【MyFooter】

    在这个组件中可以使用计算属性实现数据的总长度和被勾选的数据的计算

    1. <template>
    2. <div class="todo-footer" v-if="todosLength">
    3. <label>
    4. <input type="checkbox"/>
    5. label>
    6. <span>
    7. <span>已完成 {{doneTotal}}span> / {{todosLength}}
    8. span>
    9. <button class="btn btn-danger">清除已完成任务button>
    10. div>
    11. template>
    12. <script>
    13. export default {
    14. name: 'MyFooter',
    15. props: ['todos'],
    16. computed: {
    17. todosLength() {
    18. return this.todos.length
    19. },
    20. doneTotal() {
    21. return this.todos.filter(todo => todo.done).length
    22. // 也可以使用下面求和来实现
    23. // return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
    24. }
    25. }
    26. }
    27. script>
    28. <style scoped>
    29. /*footer*/
    30. .todo-footer {
    31. height: 40px;
    32. line-height: 40px;
    33. padding-left: 6px;
    34. margin-top: 5px;
    35. }
    36. .todo-footer label {
    37. display: inline-block;
    38. margin-right: 20px;
    39. cursor: pointer;
    40. }
    41. .todo-footer label input {
    42. position: relative;
    43. top: -1px;
    44. vertical-align: middle;
    45. margin-right: 5px;
    46. }
    47. .todo-footer button {
    48. float: right;
    49. margin-top: 5px;
    50. }
    51. style>

    8 实现勾选全部的小复选框来实现大复选框的勾选

    :checked="isAll"

    isAll也是通过计算属性计算得来的

    【MyFooter】 

    1. <template>
    2. <div class="todo-footer" v-if="todosLength">
    3. <label>
    4. <input type="checkbox" :checked="isAll"/>
    5. label>
    6. <span>
    7. <span>已完成 {{doneTotal}}span> / {{todosLength}}
    8. span>
    9. <button class="btn btn-danger">清除已完成任务button>
    10. div>
    11. template>
    12. <script>
    13. export default {
    14. name: 'MyFooter',
    15. props: ['todos'],
    16. computed: {
    17. todosLength() {
    18. return this.todos.length
    19. },
    20. doneTotal() {
    21. return this.todos.filter(todo => todo.done).length
    22. // 也可以使用下面求和来实现
    23. // return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
    24. },
    25. isAll() {
    26. return this.doneTotal === this.todosLength && this.todosLength > 0
    27. },
    28. }
    29. }
    30. script>
    31. <style scoped>
    32. /*footer*/
    33. .todo-footer {
    34. height: 40px;
    35. line-height: 40px;
    36. padding-left: 6px;
    37. margin-top: 5px;
    38. }
    39. .todo-footer label {
    40. display: inline-block;
    41. margin-right: 20px;
    42. cursor: pointer;
    43. }
    44. .todo-footer label input {
    45. position: relative;
    46. top: -1px;
    47. vertical-align: middle;
    48. margin-right: 5px;
    49. }
    50. .todo-footer button {
    51. float: right;
    52. margin-top: 5px;
    53. }
    54. style>

    9 实现勾选大复选框来实现所有的小复选框都被勾选

    【App】

    1. <template>
    2. <div id="root">
    3. <div class="todo-container">
    4. <div class="todo-wrap">
    5. <MyHeader :addTodo="addTodo">MyHeader>
    6. <List
    7. :todos="todos"
    8. :changeTodo="changeTodo"
    9. :deleteTodo="deleteTodo">
    10. List>
    11. <MyFooter
    12. :todos="todos"
    13. :checkAllTodo="checkAllTodo"
    14. >
    15. MyFooter>
    16. div>
    17. div>
    18. div>
    19. template>
    20. <script>
    21. import MyHeader from './components/MyHeader.vue'
    22. import List from './components/List.vue'
    23. import MyFooter from './components/MyFooter.vue'
    24. export default {
    25. name:'App',
    26. components:{
    27. MyHeader,
    28. List,
    29. MyFooter
    30. },
    31. data() {
    32. return {
    33. todos: [
    34. {id: '001', title: '吃饭', done: true},
    35. {id: '002', title: '学习', done: false},
    36. {id: '003', title: '追剧', done: true},
    37. ]
    38. }
    39. },
    40. methods: {
    41. // 添加一个todo
    42. addTodo(todoObj) {
    43. this.todos.unshift(todoObj)
    44. },
    45. // 勾选或者取消勾选一个todo
    46. changeTodo(id) {
    47. this.todos.forEach((todo) => {
    48. if (todo.id === id) todo.done = !todo.done
    49. })
    50. },
    51. // 删除一个todo
    52. deleteTodo(id) {
    53. this.todos = this.todos.filter((todo) => todo.id !== id)
    54. },
    55. // 全选or取消全选
    56. checkAllTodo(done) {
    57. this.todos.forEach((todo) => {
    58. todo.done = done
    59. })
    60. },
    61. }
    62. }
    63. script>
    64. <style>
    65. /*base*/
    66. body {
    67. background: #fff;
    68. }
    69. .btn {
    70. display: inline-block;
    71. padding: 4px 12px;
    72. margin-bottom: 0;
    73. font-size: 14px;
    74. line-height: 20px;
    75. text-align: center;
    76. vertical-align: middle;
    77. cursor: pointer;
    78. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    79. border-radius: 4px;
    80. }
    81. .btn-danger {
    82. color: #fff;
    83. background-color: #da4f49;
    84. border: 1px solid #bd362f;
    85. }
    86. .btn-danger:hover {
    87. color: #fff;
    88. background-color: #bd362f;
    89. }
    90. .btn:focus {
    91. outline: none;
    92. }
    93. .todo-container {
    94. width: 600px;
    95. margin: 0 auto;
    96. }
    97. .todo-container .todo-wrap {
    98. padding: 10px;
    99. border: 1px solid #ddd;
    100. border-radius: 5px;
    101. }
    102. style>

    【MyFooter】

    1. <template>
    2. <div class="todo-footer" v-if="todosLength">
    3. <label>
    4. <input type="checkbox" :checked="isAll" @change="checkAll"/>
    5. label>
    6. <span>
    7. <span>已完成 {{doneTotal}}span> / {{todosLength}}
    8. span>
    9. <button class="btn btn-danger">清除已完成任务button>
    10. div>
    11. template>
    12. <script>
    13. export default {
    14. name: 'MyFooter',
    15. props: ['todos', 'checkAllTodo'],
    16. computed: {
    17. todosLength() {
    18. return this.todos.length
    19. },
    20. doneTotal() {
    21. return this.todos.filter(todo => todo.done).length
    22. // 也可以使用下面求和来实现
    23. // return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
    24. },
    25. isAll() {
    26. return this.doneTotal === this.todosLength && this.todosLength > 0
    27. },
    28. },
    29. methods: {
    30. checkAll(e) {
    31. this.checkAllTodo(e.target.checked)
    32. }
    33. }
    34. }
    35. script>
    36. <style scoped>
    37. /*footer*/
    38. .todo-footer {
    39. height: 40px;
    40. line-height: 40px;
    41. padding-left: 6px;
    42. margin-top: 5px;
    43. }
    44. .todo-footer label {
    45. display: inline-block;
    46. margin-right: 20px;
    47. cursor: pointer;
    48. }
    49. .todo-footer label input {
    50. position: relative;
    51. top: -1px;
    52. vertical-align: middle;
    53. margin-right: 5px;
    54. }
    55. .todo-footer button {
    56. float: right;
    57. margin-top: 5px;
    58. }
    59. style>

    10 清空所有数据

    【App】

    1. <template>
    2. <div id="root">
    3. <div class="todo-container">
    4. <div class="todo-wrap">
    5. <MyHeader :addTodo="addTodo">MyHeader>
    6. <List
    7. :todos="todos"
    8. :changeTodo="changeTodo"
    9. :deleteTodo="deleteTodo">
    10. List>
    11. <MyFooter
    12. :todos="todos"
    13. :checkAllTodo="checkAllTodo"
    14. :clearAllTodo="clearAllTodo"
    15. >
    16. MyFooter>
    17. div>
    18. div>
    19. div>
    20. template>
    21. <script>
    22. import MyHeader from './components/MyHeader.vue'
    23. import List from './components/List.vue'
    24. import MyFooter from './components/MyFooter.vue'
    25. export default {
    26. name:'App',
    27. components:{
    28. MyHeader,
    29. List,
    30. MyFooter
    31. },
    32. data() {
    33. return {
    34. todos: [
    35. {id: '001', title: '吃饭', done: true},
    36. {id: '002', title: '学习', done: false},
    37. {id: '003', title: '追剧', done: true},
    38. ]
    39. }
    40. },
    41. methods: {
    42. // 添加一个todo
    43. addTodo(todoObj) {
    44. this.todos.unshift(todoObj)
    45. },
    46. // 勾选或者取消勾选一个todo
    47. changeTodo(id) {
    48. this.todos.forEach((todo) => {
    49. if (todo.id === id) todo.done = !todo.done
    50. })
    51. },
    52. // 删除一个todo
    53. deleteTodo(id) {
    54. this.todos = this.todos.filter((todo) => todo.id !== id)
    55. },
    56. // 全选or取消全选
    57. checkAllTodo(done) {
    58. this.todos.forEach((todo) => {
    59. todo.done = done
    60. })
    61. },
    62. // 清空所有已经完成的todo
    63. clearAllTodo() {
    64. this.todos = this.todos.filter((todo) => !todo.done)
    65. }
    66. }
    67. }
    68. script>
    69. <style>
    70. /*base*/
    71. body {
    72. background: #fff;
    73. }
    74. .btn {
    75. display: inline-block;
    76. padding: 4px 12px;
    77. margin-bottom: 0;
    78. font-size: 14px;
    79. line-height: 20px;
    80. text-align: center;
    81. vertical-align: middle;
    82. cursor: pointer;
    83. box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    84. border-radius: 4px;
    85. }
    86. .btn-danger {
    87. color: #fff;
    88. background-color: #da4f49;
    89. border: 1px solid #bd362f;
    90. }
    91. .btn-danger:hover {
    92. color: #fff;
    93. background-color: #bd362f;
    94. }
    95. .btn:focus {
    96. outline: none;
    97. }
    98. .todo-container {
    99. width: 600px;
    100. margin: 0 auto;
    101. }
    102. .todo-container .todo-wrap {
    103. padding: 10px;
    104. border: 1px solid #ddd;
    105. border-radius: 5px;
    106. }
    107. style>

    【MyFooter】

    1. <template>
    2. <div class="todo-footer" v-if="todosLength">
    3. <label>
    4. <input type="checkbox" :checked="isAll" @change="checkAll"/>
    5. label>
    6. <span>
    7. <span>已完成 {{doneTotal}}span> / {{todosLength}}
    8. span>
    9. <button class="btn btn-danger" @click="clearTodo">清除已完成任务button>
    10. div>
    11. template>
    12. <script>
    13. export default {
    14. name: 'MyFooter',
    15. props: ['todos', 'checkAllTodo', 'clearAllTodo'],
    16. computed: {
    17. todosLength() {
    18. return this.todos.length
    19. },
    20. doneTotal() {
    21. return this.todos.filter(todo => todo.done).length
    22. // 也可以使用下面求和来实现
    23. // return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
    24. },
    25. isAll() {
    26. return this.doneTotal === this.todosLength && this.todosLength > 0
    27. },
    28. },
    29. methods: {
    30. checkAll(e) {
    31. this.checkAllTodo(e.target.checked)
    32. },
    33. clearTodo() {
    34. this.clearAllTodo()
    35. }
    36. }
    37. }
    38. script>
    39. <style scoped>
    40. /*footer*/
    41. .todo-footer {
    42. height: 40px;
    43. line-height: 40px;
    44. padding-left: 6px;
    45. margin-top: 5px;
    46. }
    47. .todo-footer label {
    48. display: inline-block;
    49. margin-right: 20px;
    50. cursor: pointer;
    51. }
    52. .todo-footer label input {
    53. position: relative;
    54. top: -1px;
    55. vertical-align: middle;
    56. margin-right: 5px;
    57. }
    58. .todo-footer button {
    59. float: right;
    60. margin-top: 5px;
    61. }
    62. style>

    11 实现案例中的数据存入本地存储

    【分析】首先我们要知道什么时候需要将数据存入本地存储?所以这就用到了watch监听,当todos的值发生变化时,将新的值存入本地存储。

    又因为当我们勾选复选框时,我们发现本地存储中的 done 值并没有发生变化?这主要是因为监听默认只会监听第一层,如果想要监听对象中某个数据发生变化时,就需要深度监视了。

    【App】

    这里使用 || 运算可以防止一开始本地存储中没有数据而报错

    12 案例中使用自定义事件完成组件间的数据通信

    这边以添加数据为例

    【App】

    给发送数据的组件绑定自定义事件

    1. <MyHeader @addTodo="addTodo">MyHeader>
    2. ...
    3. methods: {
    4. // 添加一个todo
    5. addTodo(todoObj) {
    6. this.todos.unshift(todoObj)
    7. },
    8. }

    【MyHeader】

    13 案例中实现数据的编辑

    需求分析:当点击编辑按钮时,变成input表单修改数据,此时编辑按钮隐藏,当失去焦点时,编辑完成,显示编辑后的数据,同时编辑按钮显示。

     这边使用全局事件总线来实现通信

    【App】

    1. methods: {
    2. ...
    3. // 更改
    4. updateTodo(id,title) {
    5. this.todos.forEach((todo) => {
    6. if (todo.id === id) todo.title = title
    7. })
    8. },
    9. ...
    10. },
    11. mounted() {
    12. this.$bus.$on('updateTodo', this.updateTodo)
    13. },
    14. beforeDestroy() {
    15. this.$bus.$off('updateTodo')
    16. }

    【Item】



    因为如果想要失去焦点时实现数据的修改,那么你必须提前获取焦点,但是由于Vue的执行机制,当Vue底层监视到数据发生改变时,它并不会立即去重新渲染模板,而是继续执行后面的代码,所以如果不加以处理的话,直接获取焦点,肯定会报错,因为页面中的元素还没有加载解析出,找不到获取焦点的input元素,所以可以通过以下的代码实现

    1. this.$nextTick(function() { // 告诉Vue,DOM渲染完毕后,再执行focus()方法
    2. this.$refs.inputTiltle.focus()
    3. })

    14 实现数据进出的动画效果

    【Item】

    使用标签包裹


  • 相关阅读:
    计算机网络-网络互连和互联网(四)
    PCycDB:全面准确的磷循环基因分析数据库
    Flink(林子雨慕课课程)
    国产高云FPGA:纯verilog实现视频图像缩放,提供6套Gowin工程源码和技术支持
    IDEA-插件开发踩坑记录-第三坑-自定义事件
    手把手教你Python如何抓包~【异常详细版】
    ResponseEntity下载包含点的文件名无法下载
    深度解析NLP文本摘要技术:定义、应用与PyTorch实战
    solidworks管道设计教程
    基于 RK3399 5G 通信和图像增强算法的交通监控系统设计
  • 原文地址:https://blog.csdn.net/m0_61495539/article/details/136113726