一个简单todos的例子

文章目录

效果图

在这里插入图片描述

代码

主要代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="./04.css" />
  </head>
  <body>
    <div id="app">
      <!-- todos 标题的开始 -->
      <app-todo-header></app-todo-header>
      <!-- todos 标题的结束 -->
      <!-- 中间内容的开始 -->
      <div class="app-todo-content">
        <div class="todo-content-top">
          <span
            :class="[{'content-top-allCheck':true},{active:allChecked}]"
            @click.stop="handleAllCheck"
            >></span
          >
          <!-- 
                   1. input上面添加 键盘事件 => 按下 enter => 保存 
                   2. v-model 值绑定
                -->
          <input
            type="text"
            v-focus
            v-model="inputVal"
            @keyup.enter="saveTodo"
          />
        </div>
        <div class="todo-content-bottom">
          <!-- 代办清单列表 -->
          <div class="content-bottom-list">
            <div
              class="bottom-list-item"
              v-for="(item,index) in filterTodos"
              :key="item.id"
              @dblclick="editTodo(index)"
            >
              <input type="checkbox" name="" id="" v-model="item.checked" />
              <div>
                <span
                  :class="[{'title':true}, {hidden:selectIndex===index},{'complete':item.checked}]"
                  >{{item.text}}</span
                >
                <input
                  type="text"
                  v-model="item.text"
                  @blur="saveChangedTodo"
                  :class="[{hidden:selectIndex!=index}]"
                  v-todo-focus
                />
              </div>
              <span class="delete-item" @click="deleteTodo(index)">X</span>
            </div>
          </div>
        </div>
      </div>
      <!-- 中间内容结束 -->
      <!-- todos 底部工具栏开始 -->
      <div class="todo-bottom">
        <div class="todo-bottom-left">
          {{remaining}} 项剩下
        </div>
        <div class="todo-bottom-mid">
          <div
            @click.stop="handleItemClick('all')"
            :class="[{'bottom-mid-left':true},{active:activeItem==='all'}]"
          >
            全部
          </div>
          <div
            @click.stop="handleItemClick('active')"
            :class="[{'bottom-mid-mid':true},{active:activeItem==='active'}]"
          >
            激活
          </div>
          <div
            @click.stop="handleItemClick('complete')"
            :class="[{'bottom-mid-right':true},{active:activeItem==='complete'}]"
          >
            完成
          </div>
        </div>
        <div class="todo-bottom-right" @click.stop="clearCompleted">
          清除已完成
        </div>
      </div>
      <!-- todos 底部工具栏结束 -->
    </div>
  </body>
  <script src="./node_modules/vue/dist/vue.js"></script>
  <script src="./storageUtil.js"></script>
  <script>
    /* 
      定义一个 v-focus 可以使得 input 自动聚焦
      指令名字: focus
      全局指令
     */
    // Vue.directive("focus",{
    //   bind:function(el,binding,vnode,oldNode){
    //     console.log("指令已经绑定到元素上面");
    //   },
    //   // 指令所在的元素 已经插到 父节点中
    //   inserted:function(el,binding,vNonde,oldNode){
    //     console.log(el);
    //     el.focus();
    //   }
    // })

    let vm = new Vue({
      el: "#app",
      watch:{
        todos:{
          handler:function(value,oldValue){
            storageUtil.save("todos",value);
          },
          deep:true
        }
      },
      computed: {
        // 过滤之后的 todos
        filterTodos: function () {
          // activeItem
          // all
          if (this.activeItem === "all") {
            return this.todos;
          } else if (this.activeItem === "active") {
            // active => checked === false
            return this.todos.filter((todo, index) => {
              if (!todo.checked) {
                return true;
              }
            });
          } else {
            // complete
            return this.todos.filter((todo, index) => {
              // complete => checked === true
              if (todo.checked) {
                return true;
              }
            });
          }
        },
        // 剩下的todos
        remaining: function () {
          // todos => todo.checked => false => 挑选出来
          let remainTodos = this.todos.filter((todo, index) => {
            if (!todo.checked) {
              return true;
            }
          });
          return remainTodos.length;
        },
        allChecked: function () {
          // 检查todos 如果todos 中有没有被选中的值 allChecked false
          let checked = true;
          // 如果 todos 为空的情况下 checked = false
          // bug fix
          if (!this.todos.length) {
            checked = false;
          }
          this.todos.map((todo, index) => {
            if (!todo.checked) {
              checked = false;
            }
          });
          return checked;
        },
      },
      data: {
        activeItem: "all", // all 全部的todos active 激活的todos complete 完成的todos
        // allChecked: false,// 全选 默认情况下没有被选中
        selectIndex: -1, // -1 没有编辑项 0 正在编辑第一项 1 正在编辑第二项
        inputVal: "",
        /*
                {
                    id: Date.now(),
                    text:"ssss",
                    checked:false
                }
             */
        todos: storageUtil.fetch("todos"),
      },
      methods: {
        // 点击 全部 激活 完成的点击事件
        handleItemClick: function (activeItem) {
          this.activeItem = activeItem;
        },
        // 清除已完成
        clearCompleted: function () {
          // 过滤掉 todos => todo=> todo.checked === true
          // todo.checked === true 已完成
          // todo.checked === false 未完成
          let filterTodos = this.todos.filter((todo, index) => {
            if (!todo.checked) {
              return true;
            }
          });
          this.todos = filterTodos; // 替换todos
        },
        handleAllCheck: function () {
          let newTodos = JSON.parse(JSON.stringify(this.todos));
          // newTodos.forEach(())
          this.todos.map((todo, index) => {
            todo.checked = true;
          });
          // 现在所有的todo 都被选中了
          //   this.allChecked = true;
        },
        // 保存todo
        saveChangedTodo: function () {
          this.selectIndex = -1; // -1 表示没有双击编辑任何的 todo
        },
        // 编辑todo
        editTodo: function (index) {
          this.selectIndex = index;
        },
        // 删除 todo
        deleteTodo: function (index) {
          // splice(index,1)
          this.todos.splice(index, 1);
        },
        saveTodo: function () {
          console.log("获取到input 值", this.inputVal);
          if (!this.inputVal) {
            alert("输入不能为空!");
            return;
          }
          this.todos.push({
            id: Date.now(),
            text: this.inputVal,
            checked: false,
          });
        },
      },
      components: {
        "app-todo-header": {
          template: `
               <div class="app-todo-header">
               <span>todos</span>
           </div>   
          `,
        },
      },
      // 局部指令
      directives: {
        // "todo-focus":{
        //   // 节点插入父节点
        //   inserted:function(el,binding){
        //     el.focus();
        //   },
        //   // 节点更新的时候 update
        //   update:function(el,binding){
        //     console.log(el);
        //     el.focus();
        //   }
        // },
        // 如果 inserted 和 update 的时候 执行的相同的行为  可以写成以下形式
        "todo-focus": function (el, binding) {
          el.focus();
        },
        focus: {
          bind: function (el, binding, vNode, oldNode) {},
          inserted: function (el, binding, vNode, oldNode) {
            el.focus();
          },
        },
      },
    });
  </script>
</html>

04.css代码

#app {
  width: 700px;
  margin: 0 auto;
  border: 1px solid #ddd;
  min-height: 300px;
}
#app .app-todo-header {
  height: 50px;
  text-align: center;
  color: #ead7d7;
  font-weight: 100;
  font-size: 30px;
}
#app .app-todo-content .todo-content-top {
  display: flex;
  justify-content: center;
  align-items: center;
}
#app .app-todo-content .todo-content-top .content-top-allCheck {
  display: inline-block;
  transform: rotate(90deg);
}
#app .app-todo-content .todo-content-top .active {
  color: #e91f50;
}
#app .app-todo-content .todo-content-top input {
  width: 90%;
  height: 30px;
}
#app .app-todo-content .todo-content-bottom .content-bottom-list .bottom-list-item {
  height: 30px;
  display: flex;
  justify-content: space-between;
  padding: 5px;
  border-bottom: 1px solid #ddd;
}
#app .app-todo-content .todo-content-bottom .content-bottom-list .bottom-list-item .hidden {
  display: none;
}
#app .app-todo-content .todo-content-bottom .content-bottom-list .bottom-list-item .complete {
  color: #d9d9d9;
  text-decoration: line-through;
}
#app .app-todo-content .todo-content-bottom .content-bottom-list .bottom-list-item .delete-item {
  visibility: hidden;
}
#app .app-todo-content .todo-content-bottom .content-bottom-list .bottom-list-item:hover .delete-item {
  visibility: visible;
}
#app .todo-bottom {
  height: 50px;
  display: flex;
  justify-content: space-between;
  padding: 5px;
  align-items: center;
}
#app .todo-bottom .todo-bottom-mid {
  display: flex;
  align-items: center;
}
#app .todo-bottom .todo-bottom-mid .active {
  border: 1px solid #ddd;
  color: red;
}
#app .todo-bottom .todo-bottom-mid .bottom-mid-left {
  margin-right: 5px;
}
#app .todo-bottom .todo-bottom-mid .bottom-mid-mid {
  margin-right: 5px;
}
#app .todo-bottom .todo-bottom-mid .bottom-mid-right {
  margin-right: 5px;
}

storageUtil.js

/* 
        1. fetch(key) => 获取localStorage 中key 的记录 返回对象
        2. save(key,obj) => 保存 key 为 key对象 => localStorage
     */
(function () {
  let storageUtil = {};
  storageUtil.fetch = function (key) {
    let rawObj = localStorage.getItem(key) || "[]"; // 字符串
    return JSON.parse(rawObj);
  };
  storageUtil.save = function (key, obj) {
    localStorage.setItem(key, JSON.stringify(obj));
  };
  window.storageUtil = storageUtil;
})();

此处没有用脚手架 直接安装vue插件完成的

npm install --save vue

然后在HTML内根据安装的路径引用安装的vue.js

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值