美文网首页
【Vue】Todo案例-子组件

【Vue】Todo案例-子组件

作者: 图骨南 | 来源:发表于2022-03-06 19:40 被阅读0次

    子组件MyHeader

    组件结构

    MyHeader组件结构

    具体代码

    <template>
    <!--  todo头部输入框-->
      <div class="todo-header">
    <!--    待办事项输入框-->
    <!--    显示提示信息,自动获取焦点,敲击回车后完成待办事项的添加,利用v-model实现双向绑定-->
        <input
          type="text"
          placeholder="往我这填要干的事,填完以后按回车"
          autofocus="autofocus"
          v-model="title"
          @keyup.enter="add"
        />
      </div>
    </template>
    
    <script>
    // 引入nanoid库,用于生成作为待办事项唯一id的uuid
    import {nanoid} from 'nanoid'
    export default {
      name: 'MyHeader',
      data () {
        return {
          title: '' // 待办事项内容初始值为空,利用v-model实现数据双向绑定后可根据用户输入内容实现动态更新
        }
      },
      methods: {
        // 添加待办
        add () {
          // 校验数据 删去输入数据前后空格 若输入为空则发出提醒
          if (!this.title.trim()) return alert('输入不能为空')
          // 将用户输入包装成一个todo对象,对象中包括id,输入内容和完成状态
          const todoObj = {id: nanoid(), title: this.title, completed: false}
          // 使用$emit让父组件App监听到addTodo事件,即去添加一个todo对象
          this.$emit('addTodo', todoObj)
          // 清空输入
          this.title = ''
        }
      }
    }
    </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>
    

    子组件MyList

    组件结构

    MyList组件结构

    具体代码

    <template>
    <!--  todo列表  -->
      <ul class="todo-main">
    <!--    待办项动画,用transition-group包裹来控制多个事项的动画效果,   -->
    <!--    transition-group必须要设置key值,appear用于添加入场效果-->
        <transition-group name="todo" appear>
    <!--      todo列表中的todo事件  -->
    <!--      令每个todos对象数组中的todoObj对象的key值都与其id相同,并令todo为todoObj  -->
          <MyItem
            v-for="todoObj in todos"
            :key="todoObj.id"
            :todo="todoObj"
          />
        </transition-group>
      </ul>
    </template>
    
    <script>
    // 引入组件
    import MyItem from './MyItem'
    
    export default {
      name: 'MyList',
      // 注册组件
      components: {MyItem},
      // 声明接收App传递过来的数据
      props: ['todos']
    }
    </script>
    
    <!--scoped表示此样式仅作用于当下的模块-->
    <style scoped>
    /*main*/
    /*todo列表主体样式*/
    .todo-main {
      margin-left: 0;
      border: 1px solid #ddd;
      border-radius: 2px;
      padding: 0;
      overflow: hidden;
    }
    /*todo列表为空时样式*/
    .todo-empty {
      height: 40px;
      line-height: 40px;
      border: 1px solid #ddd;
      border-radius: 2px;
      padding-left: 5px;
      margin-top: 10px;
    }
    /*添加待办事项的动画过渡效果*/
    .todo-enter-active{
      animation: mergeDown 1s;
    }
    /*删除待办事项的动画过渡效果*/
    .todo-leave-active{
      animation: mergeDown 1s reverse;
    }
    /*使用@keyframes可以创建动画*/
    /*此处创建下沉效果*/
    @keyframes mergeDown  {
      from{
        transform: translateY(-30%);
        overflow: hidden;
      }
      to{
        transform: translateY(0px);
      }
    }
    </style>
    

    子组件MyItem

    组件结构

    MyItem组件结构

    具体代码

    <template>
      <li>
    <!--    标签 当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上  -->
        <label>
    <!--      勾选框,根据待办事项完成状态改变勾选状态,状态改变时调用handleCheck方法 -->
          <input
            type="checkbox"
            :checked="todo.completed"
            @change="handleCheck(todo.id)"
          />
          <!--      如下代码也能实现功能,但是不太推荐,因为修改了props,有些违反原则-->
          <!--      <input type="checkbox" v-model="todo.completed"/>-->
    <!--      待办事项  -->
          <!--未进入编辑状态时,显示待办事项-->
          <span v-show="!todo.isEdit">{{todo.title}}</span>
          <!--在编辑状态中,显示包含原内容的输入框,失焦后调用handleBlur方法完成更改-->
          <input
            type="text"
            :value="todo.title"
            v-show="todo.isEdit"
            @blur="handleBlur(todo,$event)"
            ref="inputTitle"
          >
        </label>
    <!--    编辑按钮  -->
        <!--点击按钮前未进入编辑状态,isEdit为假,按钮显示,点击后isEdit为真,按钮消失,进入编辑状态-->
        <button
          class="btn btn-edit"
          v-show="!todo.isEdit"
          @click="handleEdit(todo)"
        >
          编辑
        </button>
    <!--    删除按钮,点击时调用handleDelete方法-->
        <button
          class="btn btn-danger"
          @click="handleDelete(todo.id)"
        >
          删除
        </button>
      </li>
    </template>
    
    <script>
    import pubsub from 'pubsub-js' // 引入pubsub
    export default {
      name: 'MyItem',
      // 声明接收MyList里的todo对象
      props: ['todo'],
      methods: {
        // 勾选 or 取消勾选
        handleCheck (id) {
          // 通知App组件将对应的todo对象的completed值取反
          // this.checkTodo(id)
          this.$bus.$emit('checkTodo', id) // 通过全局总线bus触发App中的checkTodo事件,对对应id的对象调用checkTodo方法
        },
        // 删除
        handleDelete (id) {
          if (confirm('Do you mean it???')) { // confirm() 弹出对话框,点击确认后继续
            // this.deleteTodo(id)
            // this.$bus.$emit('deleteTodo', id)
            pubsub.publish('deleteTodo', id) // 发布deleteTodo消息并传递id数据
          }
        },
        // 编辑
        handleEdit (todo) {
          if (todo.hasOwnProperty('isEdit')) { // 如果该todo已有isEdit属性(不管属性真假)
            todo.isEdit = true // 令isEdit为真,将文字变为输入框
          } else { // 如果todo还没有isEdit属性
            this.$set(todo, 'isEdit', true) // 利用set函数将isEdit属性赋给该todo,且令初始值为真
          }
          this.$nextTick(function () { // $nextTick指定的回调函数会在DOM节点更新后执行
            this.$refs.inputTitle.focus() // 先点击编辑按钮令文字变为输入框后,再令输入框获取焦点
          })
        },
        // 失去焦点回调(真正执行修改逻辑)
        handleBlur (todo, event) {
          todo.isEdit = false // 令isEdit为假,失焦后输入框消失
          if (!event.target.value.trim()) return alert('Input section is empty!') // 若去掉输入内容前后空格后,输入内容为空,弹出提示框
          this.$bus.$emit('updateTodo', todo.id, event.target.value) // 若输入内容非空,触发全局事件总线中的updateTodo事件,并传递该todo的id与输入框中的值
        }
      }
    }
    </script>
    
    <style scoped>
    /*item*/
    /*待办事项样式*/
    li {
      list-style: none;
      height: 36px;
      line-height: 36px;
      padding: 0 5px;
      border-bottom: 1px solid #ddd;
    }
    /*列表中被label包裹内容的样式*/
    li label {
      float: left;
      cursor: pointer;
    }
    li label li input {
      vertical-align: middle;
      margin-right: 5px;
      position: relative;
      top: -1px;
    }
    /*列表中所有按钮的样式*/
    li button {
      float: right;
      display: none;
      margin-top: 3px;
    }
    /*在每个li元素前插入内容的样式*/
    li:before {
      content: initial;
    }
    /*属于其父元素的最后一个子元素的li元素的样式*/
    li:last-child {
      border-bottom: none;
    }
    /*鼠标指针悬浮于li上时的样式*/
    li:hover {
      background-color: #dddddd;
      font-weight: bold;
      font-size: large;
    }
    li:hover button {
      display: block;
    }
    /*删除按钮样式*/
    .btn-danger {
      color: #f5f5f5;
      background-color: #da4f49;
      border: 1px solid #bd362f;
      margin-right: 5px;
    }
    /*鼠标指针移动到删除按钮上时的样式*/
    .btn-danger:hover {
      color: #f5f5f5;
      background-color: #bd362f;
      margin-right: 5px;
    }
    /*编辑按钮样式*/
    .btn-edit {
      color: #f5f5f5;
      background-color: lightskyblue;
      border: 1px solid cornflowerblue;
    }
    /*鼠标移动到编辑按钮上时的样式*/
    .btn-edit:hover {
      color: #f5f5f5;
      background-color: cornflowerblue;
    }
    </style>
    

    子组件MyFooter

    组件结构

    MyFooter组件结构

    具体代码

    <template>
    <!-- todo底部-->
      <div class="todo-footer" v-show="total">
        <!--      全选勾选框-->
        <label>
    <!--      v-model="isAll"等同于: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: 'MyFooter',
      props: ['todos'], // 引入父组件App中的todos对象数组
      computed: { // 计算属性,可用于处理复杂逻辑
        // 总数
        total () {
          return this.todos.length // 返回todos对象数组的长度,即待办事项总数
        },
        // 已完成数
        doneTotal () {
          // reduce()方法按顺序对数组的每个元素回调函数,并按顺序从前一个元素的计算中传入返回值。最终计算为一个值。
          // 判断todo对象是否完成,若完成则操作数为1,若未完成则操作数为0,返回pre+操作数,令pre的初始值为0
          return this.todos.reduce((pre, todo) => pre + (todo.completed ? 1 : 0), 0) // 最终返回对象数组中所有已完成事项的总数
        },
        // 控制全选框
        isAll: {
          // 全选框是否勾选
          get () {
            return this.doneTotal === this.total && this.total > 0 // 若列表不为空且待办事项的已完成数和总数相同,返回真,全选框被勾选
          },
          // isAll被修改时set被调用
          set (value) {
            this.$emit('checkAllTodo', value) // 当isAll被修改时向App传递checkAllTodo事件,将数组内所有对象的完成状态更改为value
          }
        }
      },
      methods: {
        // 勾选或取消一个待办事项的已完成状态需要对父组件App里的todos对象进行修改,且todos被多个组件使用,所以最好提交给父组件来改
        // checkAll (todo) {
        //   this.checkAllTodo(todo.target.checked)
        // }
        clearAll () {
          this.$emit('clearAllTodo')
        }
      }
    }
    </script>
    
    <style scoped>
    /*footer*/
    /*页面底部样式*/
    .todo-footer {
      height: 40px;
      line-height: 40px;
      padding-left: 5px;
      margin-top: 5px;
    }
    
    .todo-footer label {
      display: inline-block;
      margin-right: 5px;
      cursor: pointer;
    }
    /*页面底部勾选框样式*/
    .todo-footer label input {
      position: relative;
      top: -1px;
      vertical-align: middle;
      margin-right: 15px;
    }
    /*页面底部按钮样式*/
    .todo-footer button {
      float: right;
      margin-top: 5px;
    }
    /*清除按钮样式*/
    .btn-danger {
      color: #f5f5f5;
      background-color: #da4f49;
      border: 1px solid #bd362f;
      margin-right: 5px;
    }
    /*鼠标指针移动到清除按钮上时的样式*/
    .btn-danger:hover {
      color: #f5f5f5;
      background-color: #bd362f;
      margin-right: 5px;
    }
    </style>
    

    相关文章

      网友评论

          本文标题:【Vue】Todo案例-子组件

          本文链接:https://www.haomeiwen.com/subject/dismrrtx.html