Vue TodoList 待办事项小案例(代码版)

main.js

js 复制代码
import Vue from 'vue'

import App from './App.vue'


// 关闭Vue生产提示
Vue.config.productionTip = false

// 创建vm
new Vue({
  el: '#app',
  render: h => h(App)
})

APP.vue

vue 复制代码
<template>
  <div id="root">
  <div class="todo-container">
    <div class="todo-wrap">
      <MyHeader :addTodo="addTodo"/>
      <MyList 
        :todos="todos" 
        :checkTodo="checkTodo"
        :deleteTodo="deleteTodo"
      />
      <MyFooter 
        :todos="todos" 
        :checkAllTodo="checkAllTodo" 
        :clearAllTodo="clearAllTodo"
      />
    </div>
  </div>
</div>
</template>

<script>
// 引入组件
// 最好不要叫Header 免得不合法跟html标签冲突
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
// 不用引入item组件 item属于list的子组件
import MyList from './components/MyList.vue'
export default {
  name:'App',
  // 注册
  components: {MyHeader, MyFooter, MyList},
  data() {
    return {
      todos:[
        {id:'001', title:'吃饭', done:true},
        {id:'002', title:'睡觉', done:false},
        {id:'003', title:'抽烟', done:true},
        {id:'004', title:'喝酒', done:true},
      ]
    }
  },
  methods: {
    // 添加一个todo
    addTodo(todoObj) {
      console.log('我是App组件 我收到了数据', todoObj)
      this.todos.unshift(todoObj)
    },
    // 勾选or取消勾选一个todo
    checkTodo(id) {
      this.todos.forEach((todo) => {
        if(todo.id === id) todo.done = !todo.done
      })
    },
    // 删除一个todo
    deleteTodo(id) {
      // filter 过滤掉我不想要的
      this.todos = this.todos.filter(todo => todo.id !== id)
    },
    // 全选or取消全选
    checkAllTodo(done) {
      this.todos.forEach((todo) => {
        todo.done = done
      })
    },
    // 清除所有完成的todo
    clearAllTodo() {
      this.todos = this.todos.filter(todo => !todo.done)
    }
  }
}

</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>

MyFooter.vue

vue 复制代码
<template>
  <!-- 如果total是0 就不展示效果 -->
<div class="todo-footer" v-show="total">
    <label>
      <!-- <input type="checkbox" :checked="isAll" @click="checkAll"/> -->

      <!-- 跟刚刚写的一样 当前标签又要进行数据传输 又要进行点击事件 就可以改成一个 v-model -->

       <!-- isAll是我们自己计算出来的属性 所以可以进行修改 就是要再计算属性中写出完整的get set写法 -->
      <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','checkAllTodo', 'clearAllTodo'],
  // 计算属性 因为我们要计算已完成的任务数
  computed: {
    total() {
      return this.todos.length
    },
    // 简写 写成函数形式
    doneTotal() {
      // 写法不高级
      // let i = 0
      // this.todos.forEach(todo => {if(todo.done) i++})
      // return i

      // 第一个参数就是一个函数 第二个参数 用来做统计的初始值 就像是定义 i = 0
      // 数组的长度是几 这个函数就被调用几次
      // pre 就是上一次的值, current就是当前的值
      // 返回值就是下一次执行这个函数的pre来进行接收
      // 最后一次调用函数的返回值就是整个reduce方法的返回值
      // current就是每一个todo对象 就是数组里面的下标为pre当前内容
      
      // const x = this.todos.reduce((pre, current) => {
      //   return pre + (current.done ? 1 : 0)
      // }, 0)
      // return x

      return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
    },
    // 当前是简写形式 只被读取 不被修改 但是v-model="isAll" 会进行修改 所以 不能采用计算属性的简写形式
    // isAll() {
    //   return this.doneTotal === this.total && this.total > 0
    // }

    isAll: {
      get() {
        // 读:决定 checkbox 是不是被勾上
        return this.doneTotal === this.total && this.total > 0
      },
      set(value) {
        // 写:当用户点击 checkbox,value 就是 true 或 false
        // Vue 会自动调用 set,并传入新值
        this.checkAllTodo(value)
        // 如果原来是 false,用户勾上,就变成了 true
        // Vue 自动调用 set(value),把 true 作为参数传进去
        // value 就是用户"点击后"的新状态
      }
    }
  },
  methods: {
    checkAll(e) {
      // e 来得到当前标签里面的属性值
      // console.log(e.target.checked)
      this.checkAllTodo(e.target.checked)
    },
    clearAll() {
      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>

MyHeader.vue

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

<script>
import {nanoid} from 'nanoid'
export default {
  name: 'MyHeader',
  props:['addTodo'],
  methods: {
    add(event) {
      // 检验数据
      if(!event.target.value.trim()) return alert('输入不能为空')
      // 将用户的输入包装成一个todo对象
      // console.log(event.target.value)
      const todoObj = {id:nanoid(), title: event.target.value, done:false}
      // console.log(todoObj)

      // 通知App组件添加一个todoObj对象
      this.addTodo(todoObj)
      event.target.value = ''
    }
  }
}

</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.vue

vue 复制代码
<template>
<ul class="todo-main">
  <MyItem 
    v-for="todoObj in todos" 
    :key="todoObj.id" 
    :todo="todoObj" 
    :checkTodo="checkTodo"
    :deleteTodo="deleteTodo"
  />
</ul>
</template>

<script>
// 引入item
import MyItem from './MyItem.vue'
export default {
  name: 'MyList',
  components:{MyItem},
  props:['todos', '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>

MyItem.vue

vue 复制代码
<template>
<li>
  <label>
    <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
    <!-- 这里的todo.done 是props传进来的 是只读的 但是这里却被修改了
但是Vue的监测比较宽松,比如:let obj = {a: 1, b: 2}
obj.a = 666 这种只修改属性值, Vue是监测不到的
obj = {x: 100, y: 200} 这种修改整个对象 才是Vue能够监测到的 -->
    <!-- <input type="checkbox" v-model="todo.done"/> -->
    <span>{{todo.title}}</span>
  </label>
  <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</template>

<script>
export default {
  name: 'MyItem',
  // 生命接收todo对象
props: ['todo', 'checkTodo', 'deleteTodo'],
  mounted() {
    console.log(this.todo)
  },
  methods: {
    handleCheck(id) {
      console.log(id)
      this.checkTodo(id)
    },
    handleDelete(id) {
      if(confirm('确定删除吗')) {
        console.log(id)
        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>
相关推荐
Dream-Y.ocean1 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn2 小时前
性能监控
前端
Highcharts.js2 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn3 小时前
构建工具
前端
FRing033 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷4 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen5 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒6 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫6 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus7 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude