<div id="app" class="container my-3">
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text" id="basic-addon1">待辦事項</span>
    </div>
    <input type="text" class="form-control" placeholder="準備要做的任務"  v-model="newTodo" placeholder="準備要做的任務" @keyup.enter="addTodo">
    <div class="input-group-append">
      <button class="btn btn-primary" type="button" @click="addTodo">新增</button>
    </div>
  </div>
  <div class="card text-center">
    <div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link" href="#" :class="{'active': visibility == 'all'}" @click.prevent=" visibility ='all' ">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " href="#" :class="{'active': visibility == 'doing'}" @click.prevent=" visibility ='doing' ">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#" :class="{'active': visibility == 'done'}" @click.prevent=" visibility ='done' ">已完成</a>
        </li>
      </ul>
    </div>
    <ul class="list-group list-group-flush text-left">
      <li class="list-group-item" v-for="(item, key) in filteredTodos" @dblclick="editTodo(item)">
        <div class="d-flex" v-if="item.id !== cacheTodo.id">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" :id="item.id" v-model="item.completed">
            <label class="form-check-label" :for="item.id" :class="{'completed': item.completed}">
              {{ item.title }}
            </label>
          </div>
          <button type="button" class="close ml-auto" aria-label="Close" @click="removeTodo(item)">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <input type="text" class="form-control" v-if="item.id == cacheTodo.id" v-model="cacheTitle" @keyup.esc="cancelEdit()" @keyup.enter="doneEdit(item)">
      </li>
      
      
    </ul>
    <div class="card-footer d-flex justify-content-between">
      <span>還有 {{ uncompletedCount }} 筆任務未完成</span>
      <a href="#" @click.prevent="cleanAll">清除所有任務</a>
    </div>
  </div>
</div>
.completed {
  text-decoration: line-through
}
var app = new Vue({
  el: "#app",
  data: {
    newTodo: "",
    todos: [
      /* 這是資料推進來的模板
      {
          id: '',
          title: '',
          completed: false
       }, */
    ],
    cacheTodo: {},
    cacheTitle: "",
    visibility: "all",
    uncompletedTodos: [],
    uncompletedNum: ""
  },
  methods: {
    addTodo: function() {
      var value = this.newTodo.trim();
      if (!value) {
        return;
      }
      var timestamp = Math.floor(Date.now());
      this.todos.push({ id: timestamp, title: value, completed: false });
      this.newTodo = "";
    },
    removeTodo: function(todo) {
      var vm = this;
      var newIndex = vm.todos.findIndex(function(item, key) {
        return todo.id === item.id;
      });
      this.todos.splice(newIndex, 1);
    },
    editTodo: function(item) {
      // 將雙擊的項目傳入 data 暫存
      this.cacheTodo = item;
      this.cacheTitle = item.title;
    },
    cancelEdit: function() {
      // 切換到未雙擊時的狀態
      this.cacheTodo = {};
    },
    doneEdit: function(item) {
      // 按下 Enter 的瞬間把編輯中文字變為編輯完成的項目
      item.title = this.cacheTitle;
      // 回復到未點擊狀態
      this.cacheTitle = "";
      this.cacheTodo = {};
    },
    cleanAll: function() {
      this.todos = [];
    }
  },
  computed: {
    filteredTodos: function() {
      if (this.visibility == "all") {
        return this.todos;
      } else if (this.visibility == "doing") {
        var anotherTodos = [];
        this.todos.forEach(function(item) {
          if (!item.completed) {
            anotherTodos.push(item);
          }
        });
        return anotherTodos;
      } else if (this.visibility == "done") {
        var anotherTodos = [];
        this.todos.forEach(function(item) {
          if (item.completed) {
            anotherTodos.push(item);
          }
        });
        return anotherTodos;
      }
      //return [];
    },
    uncompletedCount: function() {
      var uncompletedTodos = this.todos.filter(function(item) {
        return item.completed == false;
      });
      return uncompletedTodos.length;
    }
  }
});

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js