vue——todo案例

1.创建工程和组件

①:初始化todo工程:     

vue create todo-demo  (将欢迎界面清理,重置app.vue)

②: 创建3个组件(布局样式略) :
src-components-TodoHeader.vue+TodoMain.vue+TodoFooter.vue
③:把styles的样式文件准备好:
     3.1放在src中
     3.2(app.vue)import "./styles/index.css"
                               import "./styles/base.css"
④: App.vue引入组件注册使用, 最外层容器类名todoapp  :
     4.1组件引入:
          import TodoHeader from './components/TodoHeader'
          import TodoMain from './components/TodoMain'
          import TodoFooter from './components/TodoFooter'
     4.2组件注册:
export default {
  // 1.2 组件注册
  components: {
    TodoHeader,
    TodoMain,
    TodoFooter,
  },
    }

     4.3组件使用:

<template>
  <div class="todoapp">
    <TodoHeader></TodoHeader>
    <TodoMain></TodoMain>
    <TodoFooter></TodoFooter>
  </div>
</template>

 2.循环展示任务 

需求1: 把待办任务, 展示到页面TodoMain.vue组件上

需求2: 关联选中状态, 设置相关样式

2.1准备数据并传入TodoMain.vue中

app.vue:

    <TodoMain :arr="list"></TodoMain>
data(){
    return {
      list: [
        {id:100,name:"吃饭",isDone:true},
        {id:201,name:"睡觉",isDone:false},
        {id:103,name:"打豆豆",isDone:true},

      ]
}
}

TodoMain.vue:

export default {
  // 2.1 定义props
  props: ['arr'],
}

2.2v-for循环展示数据

TodoMain.vue:

 v-model绑定复选框选中状态

<template>
  <ul class="todo-list">
    <!-- 2.2 循环任务-关联选中状态-铺设数据 -->
    <!-- completed: 完成的类名 -->
    <li class="{completed: obj.isDone}" v-for="obj in arr" :key='obj.id'>
      <div class="view">
        <input class="toggle" type="checkbox" v-model="obj.isDone"/>
        <label>{{ obj.name }}</label>
        <button class="destroy"></button>
      </div>
    </li>
  </ul>
</template>

3.添加任务

需求: 输入任务敲击回车, 新增待办任务

①: TodoHeader.vue – 输入框 – 键盘事件 – 回车按键

②: 子传父, 把待办任务 – App.vue中 – 加入数组list里
③: 原数组改变, 所有用到的地方都会更新
④: 输入框为空, 提示用户必须输入内容

3.1.绑定键盘回车事件

TodoHeader.vue:

输入框 - v-model获取值

<template>
  <header class="header">
    <h1>todos</h1>
    <input id="toggle-all" class="toggle-all" type="checkbox"/>
    <label for="toggle-all"></label>
    <!-- 3.0 键盘事件-回车按键
         3.1 输入框 - v-model获取值
     -->
    <input
      class="new-todo"
      placeholder="输入任务名称-回车确认"
      autofocus
      @keydown.enter="downFn"
      v-model="task"
    />
  </header>
</template>

3.2将当前任务名加入list数组(子传父

app.vue(父):

    <TodoHeader @create="createFn"></TodoHeader>

TodoHeader.vue(子):

 methods: {
   downFn() {
      // 3.2(重要) - 当前任务名字要加到list数组里
      // 子传父技术
      this.$emit("create", this.task);
//回车后输入框清空
      this.task = "";
}}

app.vue(父):

methods: {
    createFn(taskName) {
      // 添加任务
      // 3.3 push到数组里
      let id =
        this.list.length == 0 ? 100 : this.list[this.list.length - 1].id + 1;
      this.list.push({
        id: id,
        name: taskName,
        isDone: false,
      });
    },
}

4.删除功能

点击任务后的x, 删除当前这条任务

①: x标签 – 点击事件 – 传入id区分

②: 子传父, 把id传回– App.vue中 – 删除数组list里某个对应的对象
③: 原数组改变, 所有用到的地方都会 更新

 TodoMain.vue(子):

<!-- 4.0 注册点击事件 -->
        <button class="destroy" @click="delFn(obj.id)"></button>
methods: {
    delFn(id) {
      // 4.1 子传父
      this.$emit("del", id);
    },
  },

app.vue(父):

 <TodoMain :arr="list" @del="deleteFn"></TodoMain>
deleteFn(theId) {
      // 删除任务
      let index = this.list.findIndex((obj) => obj.id === theId);
      this.list.splice(index, 1);
    },

5.底部统计

统计当前剩余任务条数

①: App.vue中 – 数组list – 传给TodoFooter.vue

②: 直接在标签上显示 / 定义计算属性用于显示都可以
 
③: 原数组只要改变, 所有用到此数组的地方都会更新

父传子: 

app.vue(父):

 <TodoFooter  :farr="list" ></TodoFooter>

 TodoFooter.vue(子):

<span class="todo-count">剩余<strong>{{ farr.length }}</strong></span>

以上方法可用计算属性替代:

 <span class="todo-count">剩余<strong>{{ count }}</strong></span>
// 5.1 计算属性 - 任务数量
  computed: {
    count(){
      return this.farr.length
    }
  },

6.底部数据切换

需求1:点击底部数据切换(加边框)

需求2:对应切换不同数据显示

①: TodoFooter.vue – 定义isSel – 值为all, yes, no其中一种

②: 多个class分别判断谁应该有类名selected
③: 点击修改isSel的值
④: 子传父, 把类型isSel传到App.vue
⑤: 定义计算属性showArr, 决定从list里显示哪些数据给TodoMain.vue和TodoFooter.vue

6.1点击高亮 

 TodoFooter.vue:

// 6. 目标: 点谁谁亮
  // 6.0 变量isSel
  data(){
    return {
      isSel: 'all' // 全部:'all', 已完成'yes', 未完成'no'
    }
  },
 <li>
        <!-- 6.1 判断谁应该有高亮样式: 动态class
            6.2 用户点击要切换isSel里保存的值
         -->
        <a :class="{selected: isSel === 'all'}" href="javascript:;" @click="isSel='all'">全部</a>
      </li>
      <li>
        <a :class="{selected: isSel === 'no'}" href="javascript:;" @click="isSel='no'">未完成</a>
      </li>
      <li>
        <a :class="{selected: isSel === 'yes'}" href="javascript:;" @click="isSel='yes'">已完成</a>
      </li>

6.2将isSel传给app.vue

app.vue(父):

 <TodoFooter
      :farr="list"
      @changeType="typeFn"
    ></TodoFooter>

 TodoFooter.vue(子):

<ul class="filters" @click="fn">
      <li>
        <!-- 6.1 判断谁应该有高亮样式: 动态class
            6.2 用户点击要切换isSel里保存的值
         -->
        <a :class="{selected: isSel === 'all'}" href="javascript:;" @click="isSel='all'">全部</a>
      </li>
      <li>
        <a :class="{selected: isSel === 'no'}" href="javascript:;" @click="isSel='no'">未完成</a>
      </li>
      <li>
        <a :class="{selected: isSel === 'yes'}" href="javascript:;" @click="isSel='yes'">已完成</a>
      </li>
    </ul>
methods: {
    fn(){ // 切换筛选条件
      // 6.3 子 -> 父 把类型字符串传给App.vue 
      this.$emit("changeType", this.isSel)
    },
}

 app.vue(父):

data() {
    return {
      // 6.4 先中转接收类型字符串
      getSel: "all", // 默认显示全部
    };
  },
typeFn(str) {
      // 'all' 'yes' 'no' // 修改类型
      this.getSel = str;
    },
// 6.5 定义showArr数组 - 通过list配合条件筛选而来
  computed: {
    showArr() {
      if (this.getSel === "yes") {
        // 显示已完成
        return this.list.filter((obj) => obj.isDone === true);
      } else if (this.getSel === "no") {
        // 显示未完成
        return this.list.filter((obj) => obj.isDone === false);
      } else {
        return this.list; // 全部显示
      }
    },
  },

7.底部清空已完成

需求: 点击右下角链接标签, 清除已完成任务

①: 清空标签 – 点击事件

②: 子传父 – App.vue – 一个清空方法
③: 过滤未完成的覆盖list数组 (不考虑恢复)

 TodoFooter.vue(子):

<!-- 7. 目标: 清除已完成 -->
    <!-- 7.0 点击事件 -->
    <button class="clear-completed" @click="clearFn">清除已完成</button>
methods: {

    clearFn(){ // 清空已完成任务
      // 7.1 触发App.vue里事件对应clearFun方法
      this.$emit('clear')
    }
  }

 app.vue(父): 

    <TodoFooter
      :farr="showArr"
      @changeType="typeFn"
      @clear="clearFun"
    ></TodoFooter>
clearFun() {
      // 清除已完成
      this.list = this.list.filter((obj) => obj.isDone == false);
    },

8.数据缓存

需求: 无论如何变化 – 都保证刷新后数据还在

①: App.vue – 侦听list数组改变 – 深度

②: 覆盖式存入到本地 – 注意本地只能存入JSON字符串
③: 刷新页面 – list应该默认从本地取值 – 要考虑无数据情况空数组
 // 8. 目标: 数据缓存
  watch: {
    list: {
      deep: true,
      handler() {
        // 8.0 只要list变化 - 覆盖式保存到localStorage里
        localStorage.setItem("todoList", JSON.stringify(this.list));
      },
    },
  },
data() {
    return {
      // 8.1 默认从本地取值
      list: JSON.parse(localStorage.getItem("todoList")) || [],
      // 6.4 先中转接收类型字符串
      getSel: "all", // 默认显示全部
    };
  },

9.全选功能

需求1: 点击全选 – 小选框受到影响

需求2: 小选框都选中(手选) – 全选自动选中状态
①: TodoHeader.vue – 计算属性 - isAll
②: App.vue – 传入数组list – 在isAll的set里影响小选框
③: isAll的get里统计小选框最后状态, 影响isAll – 影响全选状态
④: 考虑无数据情况空数组 – 全选不应该勾选

TodoHeader.vue:

 <!-- 9. 目标: 全选状态
      9.0 v-model关联全选状态
      页面变化(勾选true, 未勾选false) -> v-model -> isAll变量
     -->
    <input id="toggle-all" class="toggle-all" type="checkbox" v-model="isAll" />
    <label for="toggle-all"></label>

 TodoHeader.vue(子):

  // 9.1 定义计算属性
  computed: {
    isAll: {
      set(checked) {
        // 只有true / false
        // 9.3 影响数组里每个小选框绑定的isDone属性
        this.arr.forEach((obj) => (obj.isDone = checked));
      },
      get() {
        // 9.4 小选框统计状态 -> 全选框
        // 9.5 如果没有数据, 直接返回false-不要让全选勾选状态
        return (
          this.arr.length !== 0 && this.arr.every((obj) => obj.isDone === true)
        );
      },
    },
  },
  // 9.2 父 -> 子 list数组
  props: ["arr"],

 app.vue(父): 

    <TodoHeader :arr="list" @create="createFn"></TodoHeader>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值