摸鱼少学习多

day77-todolist案例

todolist案例

总和

设计一个添加删除框,添加代办的事项,外加一个勾选框

可以一键删除所有已完成的事项

初期设计

首先设计静态功能:

分为头部 中间 尾部三部分

头部

一个添加框,输入代办的事情,进行添加,判断是否输入为空

中间体

分为list总表和小元素item表

list总表根据设计的数组遍历给对象添加元素

item表设计页面布局与样式

尾部

设计全选按钮与勾选框,并统计共有多少事项完成,设计删除全部已完成事件

头部header

复制代码
 <template>
 <div class="todo-header">
  <input type="text" placeholder="请输入你的任务名称,按回车键确认"
  v-model="title" @keyup.enter="add"/>
 </div>
 </template><script>
 import {nanoid} from 'nanoid'
 export default {
   name: "HeaderCom",
   props:['addTodo'],//接受传入addTodo
   data(){
     return{
       title:''//收集用户输入的title
     }
   },
   methods:{
     add(){
       //校验数据
       if (!this.title.trim()) return alert("输入不能为空")
       //将输入包装成todo对象
       const todoObj={id:nanoid(),title:this.title,done:false}
       //通知app组件添加一个todo对象
       this.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>
复制代码

 

中间体-list

 
复制代码
<template>
   <ul class="todo-main">
     <ItemCom
         v-for="todo in todoList"
           :key="todo.id"
           :todo="todo"
         :checkTodo="checkTodo"
     ></ItemCom>
   </ul>
 </template><script>
 import ItemCom from "@/components/ItemCom";
 export default {
   name: "ListCom",
   components:{
     ItemCom
   },
   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>
复制代码

 

中间体item

复制代码
 <template>
   <transition name="todo" appear>
     <li>
       <label>
         <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
         <span v-show="!todo.isEdit">{{todo.title}}</span>
         <input type="text" :value="todo.title" v-show="todo.isEdit"
                @blur="handleBlur(todo, $event)" ref="inputTitle">
       </label>
       <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
       <button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑</button>
     </li>
   </transition>
 </template><script>
 import pubsub from 'pubsub-js'
 ​
 export default {
   name: 'ItemCom',
   props: ['todo','checkTodo'],
   methods: {
     //勾选或取消勾选
     handleCheck(id) {
       // 通知App组件将对应todo的done取反
       // pubsub.publish('checkTodo', id)
       this.checkTodo(id)
     },
     handleDelete(id) {
       if(confirm('确定删除吗?')){
         // 通知App组件删除对应的todo
         pubsub.publish('deleteTodo', id)
       }
     },
     handleEdit(todo) {
       // eslint-disable-next-line no-prototype-builtins
       if(todo.hasOwnProperty('isEidt')) {
         todo.isEdit = true
       }else {
         this.$set(todo, 'isEdit', true)
       }
       this.$nextTick(() => {
         this.$refs.inputTitle.focus()
       })
     },
     handleBlur(todo, e) {
       let id = todo.id
       let title = e.target.value
       todo.isEdit = false
       if(!e.target.value.trim()) return alert('输入不能为空')
       pubsub.publish('updateTodo', {id, title} )
     }
   },
 }
 </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;
 }
 ​
 .todo-enter-active{
   animation: todo 0.5s linear;
 }
 ​
 .todo-leave-active{
   animation: todo 0.5s linear reverse;
 }
 ​
 @keyframes todo {
   from{
     transform: translateX(100%);
   }
   to{
     transform: translateX(0px);
   }
 }
 </style>
复制代码

 

尾部footer

 
复制代码
<template>
   <div class="todo-footer" v-show="total">
     <label>
 <!--      <input type="checkbox" :checked="isAll" @change="checkAll"/>-->
       <input type="checkbox" :checked="isAll" @change="checkAll"/>
     </label>
     <span>
       <span>已完成{{doneTodo}}</span> / 全部{{total}}
     </span>
     <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
   </div>
 </template><script>
 export default {
   name: "FooterCom",
   props: ['todoList','checkAllTodo','clearAllTodo'],
   computed:{
     total() {
       return this.todoList.length
     },
     doneTodo() {
       // return this.todoList.filter(todo => todo.done === true).length
       return this.todoList.reduce((pre,todo)=>pre+(todo.done ? 1 : 0),0)
     },
     isAll() {
       return this.doneTodo === this.total && this.total > 0
     }
   },
   methods: {
     checkAll(e){
       // this.$emit('checkAllTodo', e.target.checked)
       this.checkAllTodo(e.target.checked)
     },
 ​
     clearAll() {
       if(confirm("确定清除吗?")){
         // this.$emit('clearAllTodo')
         this.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>
复制代码

 

app总和

 
复制代码
<template>
   <div id="app">
     <div class="todo-container">
       <div class="todo-wrap"><HeaderCom :addTodo="addTodo"></HeaderCom><ListCom :todoList="todoList" :checkTodo="checkTodo"></ListCom><FooterCom :todoList="todoList" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"></FooterCom>
       </div>
     </div>
   </div>
 </template><script>
 import HeaderCom from "@/components/HeaderCom";
 import ListCom from "@/components/ListCom";
 import FooterCom from "@/components/FooterCom";
 import pubsub from "pubsub-js";
 export default {
   name: "App",
   components:{
     HeaderCom,
     ListCom,
     FooterCom
   },
   data() {
     return {
       todoList: JSON.parse(localStorage.getItem('todoList')) || []
     }
   },
   methods: {
     // 添加一个todo
     addTodo(todo){
       this.todoList.unshift(todo)
     },
     // 勾选或取消勾选一个todo
     checkTodo(id) {
       this.todoList.forEach(todo => {
         if(todo.id === id) {
           todo.done = !todo.done
         }
       })
     },
     // 更新一个todo
     updateTodo(_, data) {
       let { id, title } = data
       this.todoList.forEach(todo => {
         if(todo.id === id) {
           todo.title = title
         }
       })
     },
     // 删除一个todo
     deleteTodo(_, id) {
       this.todoList = this.todoList.filter(todo => todo.id !== id)
     },
     // 全选或取消全选
     checkAllTodo(done) {
       this.todoList.forEach(todo => {
         todo.done = done
       })
     },
     clearAllTodo() {
       this.todoList = this.todoList.filter(todo => todo.done === false)
     }
   },
   watch: {
     todoList: {
       handler(value) {
         localStorage.setItem('todoList', JSON.stringify(value))
       },
       deep: true
     }
   },
   mounted() {
     this.pubId_check = pubsub.subscribe('checkTodo', this.checkTodo)
     this.pubId_delete = pubsub.subscribe('deleteTodo', this.deleteTodo)
     this.pubId_update = pubsub.subscribe('updateTodo', this.updateTodo)
   },
   beforeDestroy() {
     pubsub.unsubsribe(this.pubId_check)
     pubsub.unsubsribe(this.pubId_delete)
     pubsub.unsubsribe(this.pubId_update)
   },
 ​
 }
 </script><style>
 /*base*/
 body {
   background: #fff;
 }
 ​
 .btn {
   display: inline-block;
   padding: 4px 12px;
   margin-bottom: 0;
   font-size: 14px;
   line-height: 20px;
   text-align: center;
   vertical-align: middle;
   cursor: pointer;
   box-shadow: inset 0 1px 0 rgba(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-edit {
   color: #fff;
   background-color: lightgreen;
   border: 1px solid green;
   margin-right: 5px;
 }
 ​
 .btn-danger:hover {
   color: #fff;
   background-color: #bd362f;
 }
 ​
 .btn-edit:hover {
   color: #fff;
   background-color: green;
 }
 ​
 .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>
复制代码

 

总结

 
复制代码
/*
 总结:
   1.组件化编码流程:
     1.拆分静态组件:组件要按照功能点拆分,命名不要与HTML元素冲突
     2.实现动态组件时,要考虑好数据的存放位置,数据是一个组件再用还是一些组件在用:
       1.一个组件在用:放在组件自身即可
       2.一些组件在用:放在共同的父组件上(状态提升)
     3.实现交互:从绑定事件开始
   2.props适用于:
     1.父组件 ==> 子组件通信
     2.子组件 ==> 父组件通信(要求父组件先给子组件一个函数)
   3.使用v-model时切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的
   4.props传过来的若是对象类型的值,修改对象中的属性时vue不会报错,但不推荐这样使用
 */
复制代码

 

over

posted @   北海之上  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
/* 粒子吸附*/
点击右上角即可分享
微信分享提示