TodoList案例

主要功能展示

1、新增列表功能:在输入框内输入数据,点击键盘上的enter”按钮,将输入的数据添加到列表中,并清空输入框的内容。如果用户没有输入数据,将会提示。

2、高亮和删除功能:鼠标经过列表里的每一项,都会让当前列表背景颜色改变,并显示后面的删除按钮。点击删除将会提示是否删除,如果点击删除,则删除此项列表。鼠标离开时将消失。

3、计算属性:可以根据前面input框前面是否勾选对号来判断当前事项是否完成,并将已完成事项和总事项通过Footer子组件中的computed属性计算得出。

4、一次删除多个选中功能:点击 ‘清除已完成’ 按钮会弹出是否清除的弹窗,点击确定则会将选中的列表项清除,并提示并渲染出对应的列表数据。

5、实现数据存储:实现数据的本地存储,用户添加的选项列表如果没有被删除,则将存储在本地的浏览器中。

6、防止数据被恶意修改:如果主动删除了或更改了Application中的value值,使其不是一个完整的对象,则会自动删除所有数据,并提示本地缓存异常,数据已重置。

案例实现基本思路

在vue脚手架的基础上做案例,

在src文件夹下 新建文件main.js,App.vue。新建文件夹components,里面新建Header.vue,List.vue,Item.vue,Footer.vue,(分别代表不同的组件)编写组件的结构,样式。

这里面的List相当于Item的父组件

组件之间的层级结构图

组件使用步骤

1、定义

2、引入并注册

3、使用

  编写程序之前更重要的是分析需求,我是一小块一小块功能实现的。做一做,测一测,遇到问题及时解决。

  比如实现添加列表的功能,你可以通过自定义属性去完成。想要添加,输入框是在Header子组件里面,所以你就要在Header子组件里拿到用户输入的内容,然后给输入框绑定key.enter键盘事件,去调用methods里面的对应的方法,方法中首先判断用户输入的内容是否为空,如果不为空根据用户输入内容生成一个Todo对象,然后使用自定义事件去通知App在data中添加一个todo,这里要在App中提前写好对应的方法,将子组件中传递过来的数据进行渲染并放在列表的最前方。最后再清空输入框。最终实现这个小功能。

就这样根据所学知识,以及对功能需求的分析,一步一步来,最终完成案例。对于Vue初学者来说,Todolist这个案例还是很有必要去做一做的,下面是我做的案例对应的源码,上面的注释也很详细,有需要的小伙伴可以参考。

核心代码:

 main.js

// 引入Vue
import Vue from 'vue';
// 引入APP
import App from './App.vue'
//关闭生产提示
Vue.config.productionTip = false
new Vue({
	el:'#app',
/* 
vue.runtime.common.js和vue.js有何区别?
vue.runtime.common.js(项目中用的多) : 
	1.不包含模板解析器,打包后体积小
	2.配置项中的不能写template,要用render: h => h(App)代替
vue.js : 
	1.包含解模板析器,打包后体积大
	2.配置可以写template
*/
// 调用h函数相当于帮你做了3件事
// components:{App},
// template:`<App/>`
// 加上请外援vue-template-compiler(Vue模板解析器)相当于一个loader
	render: h => h(App)
})

 App.vue

<template>
  <div class="todo-container">
    <div class="todo-wrap">
        <!-- 头部 使用自定义事件去实现-->
      <Header @add-todo="addtodo"/>
      <!-- 列表 -->
     <List  :todos='todos' :updateTodo="updateTodo" 
     :deleteTodo="deleteTodo"/>
      <!-- 底部 -->
    <Footer :todos='todos' :updateAll="updateAll" :clearAll="clearAll"/>
    </div>
  </div>
</template>

<script>
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'

  export default {
  name:'App',
  components:{Header,List,Footer},
  data(){
    //数据
			const localData = localStorage.getItem('todos')
			let todos
			try {
				//尝试解析localStorage中的数据,如有数据,直接使用,无数据,使用空数组。
				todos = JSON.parse(localData) || []
			} catch (error) {
				alert('本地缓存数据异常,数据已重置')
				localStorage.removeItem('todos')
				todos = []
			}
			return {
				todos
      }
  
  },
  methods: {
    //添加一个Todo
    addtodo(todoObj){
      //unshift后添加的东西往前放
      this.todos.unshift(todoObj)
    },
    //更新一个todo----通过ID
    // updateTodo(id,done) {
    //  this.todos= this.todos.map((todo)=>{
    //     if(id===todo.id) return {...todo,done:done}
    //     else return todo
    //   })
    // },
    //更新一个todo----通过index
      updateTodo(index,done) {
    this.todos[index].done=done
    },
       //删除一个Todo
    deleteTodo(index){
       this.todos.splice(index,1)
      // console.log(index);
      },
      //全选or全不选
      updateAll(done){
        this.todos=this.todos.map((todo)=>{
          return {...todo,done}
        })
      },
      // 清除所有已完成
      clearAll(){
        this.todos=this.todos.filter((todoObj)=>{
          return !todoObj.done
        })
      },
     
  },
  watch:{
			todos:{
				deep:true, //开启深度监视
				handler(value){
					localStorage.setItem('todos',JSON.stringify(value))
				}
			}
		}
  }
</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-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>

Header.vue

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

<script>
 export default {
     name:'Header',
     data(){
         return{
             name
         }
     },
     methods:{
         add(){
             if(!this.name.trim()) return alert('输入信息不能为空')
             //根据用户输入内容生成一个Todo对象
             const todoObj={id:Date.now(),name:this.name,done:false}
             //使用自定义事件去通知App在data中添加一个todo
             this.$emit('add-todo',todoObj)
             //清空输入
             this.name=''
         }
     }
 }
</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>

Footer.vue

<template>
       <div class="todo-footer">
        <label>
          <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
          <span>已完成{{doneCount}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAllDone">清除已完成任务</button>
      </div>
</template>

<script>
 export default {
     name:'Footer',
     props:['todos','updateAll','clearAll'],
     computed:{
       doneCount(){
          //这里引入一些reduce的知识点
        // arr.reduce(function(prev,cur,index,arr){
        // ...
        // }, init);
        // 其中,
        // arr 表示原数组;
        // prev 表示上一次调用回调时的返回值,或者初始值 init;
        // cur 表示当前正在处理的数组元素;
        // index 表示当前正在处理的数组元素的索引,若提供 init 值,则索引为0,否则索引为1;
        // init 表示初始值。
        // 例子:
        // var arr = [3,9,4,3,6,0,9];
        // var sum = arr.reduce(function (prev, cur) {
        //   return prev + cur;
        // },0);
        // 由于传入了初始值0,所以开始时prev的值为0,cur的值为数组第一项3,
        // 相加之后返回值为3作为下一轮回调的prev值,然后再继续与下一个数组项相加,
        // 以此类推,直至完成所有数组项的和并返回。
       return this.todos.reduce((pre,current)=>pre+=current.done?1:0,0)
       },
       total(){
         return this.todos.length
       },
      isAll:{
        //使用计算属性的set去实现全选
        set(flag){
          this.updateAll(flag)
        },
        get(){
          return this.doneCount === this.total && this.total>0
        }

      }

     },
     methods:{
      //  使用方法去全选
      //  checkAll(event){
      //     console.log('@',event.target.checked);
      //   this.updateAll(event.target.checked)  
      //  }
      clearAllDone(){
        if(confirm('确定清除所有已完成的tido吗?')){
          this.clearAll()
        }
      }
     }
 }
</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>

 Item.vue

<template>
       <li
       @mouseenter="isEnter=true"
       @mouseleave="isEnter=false"
       :class="{'high-light':isEnter}">
       <!-- 当更改数据和点击事件同时存在时,事件优先 -->
          <label>
            <input 
            type="checkbox"
            :checked="todo.done"
            @click="update(index,$event)"/>
            <span>{{todo.name}}</span>
          </label>
          <button 
          class="btn btn-danger" 
          :style="{display:isEnter ?'block':'none'}"
          @click="deleteT(index)">删除</button>  
        </li>

</template>

<script>
 export default {
     name:'Item',                     
     props:['todo','updateTodo',"deleteTodo",'index'],
     data(){
       return{
         isEnter:false//标识鼠标是否移入
       }
     },
     methods: {
       //用id去更新
    //    update(id,event){
    //      console.log(id,event.target.checked);
    //      //通知app去更新这个todo
    //      const{checked}=event.target
    //      this.updateTodo(id,checked)
    //    }
    //  },
    //用index去更新
    deleteT(index){
				if(confirm('确定删除吗')){
          this.deleteTodo(index)
          //  this.deteleTodo(index)           
				}
			},
       update(index,event){
        //  console.log(id,event.target.checked);
         //通知app去更新这个todo
         const{checked}=event.target
         this.updateTodo(index,checked)
       }
     },
   
 }
</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;
}
.high-light{
  background-color: #bbb;
}
</style>

List.vue

<template>
<div>
   <ul class="todo-main">
   <Item 
   v-for="(t,index) in todos" 
   :key="t.id" 
   :todo="t"
   :updateTodo="updateTodo"
   :index='index'
  :deleteTodo="deleteTodo"
  />
      </ul>
      </div>
</template>
<script>
import Item from './Item'
 export default {
     name:'List',
     components:{Item},
     props:['todos','updateTodo','deleteTodo']//声明接收props,声明后可以在vc上找的到
 }
</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>

  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值