see https://cli.vuejs.org/zh/guide/prototyping.html
# install
npm install -g @vue/cli-service-global
# run
vue serve TodoApp.vuesee https://cli.vuejs.org/zh/guide/prototyping.html
# install
npm install -g @vue/cli-service-global
# run
vue serve TodoApp.vue| const STORAGE_KEY = `todo-app-vue2`; | |
| export const todoStorage = { | |
| fetch() { | |
| const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); | |
| todos.forEach((todo, index) => { | |
| todo.id = index; | |
| }); | |
| todoStorage.uid = todos.length; | |
| return todos; | |
| }, | |
| save(todos) { | |
| localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); | |
| }, | |
| }; | |
| // visibility filters | |
| export const filters = { | |
| all: function (todos) { | |
| return todos; | |
| }, | |
| active: function (todos) { | |
| return todos.filter(function (todo) { | |
| return !todo.completed; | |
| }); | |
| }, | |
| completed: function (todos) { | |
| return todos.filter(function (todo) { | |
| return todo.completed; | |
| }); | |
| }, | |
| }; |
| <template> | |
| <div id="app"> | |
| <section class="todoapp"> | |
| <header class="header"> | |
| <h1>Todos</h1> | |
| <input | |
| class="new-todo" | |
| type="text" | |
| autofocus | |
| autocomplete="off" | |
| placeholder="What needs to be done?" | |
| v-model="newTodo" | |
| @keyup.enter="addTodo" | |
| /> | |
| </header> | |
| <section class="main"> | |
| <input type="checkbox" id="toggle-all" class="toggle-all" v-model="allDone" /> | |
| <label for="toggle-all"></label> | |
| <ul class="todo-list"> | |
| <li | |
| class="todo" | |
| :class="{ completed: todo.completed, editing: todo === editedTodo }" | |
| v-for="todo in filteredTodos" | |
| :key="todo.id" | |
| > | |
| <div class="view"> | |
| <input type="checkbox" class="toggle" v-model="todo.completed" /> | |
| <label @dblclick="editTodo(todo)">{{ todo.title }}</label> | |
| <button class="destroy" @click="removeTodo(todo)"></button> | |
| </div> | |
| <input | |
| type="text" | |
| class="edit" | |
| v-model="todo.title" | |
| v-todo-focus="todo === editedTodo" | |
| @keyup.enter="doneEdit(todo)" | |
| @keyup.esc="cancelEdit(todo)" | |
| @blur="doneEdit(todo)" | |
| /> | |
| </li> | |
| </ul> | |
| </section> | |
| <footer class="footer"> | |
| <span class="todo-count"> | |
| <strong>{{ remaining }}</strong> | |
| {{ remaining | pluralize }} left | |
| </span> | |
| <ul class="filters"> | |
| <li><a href="#/all">All</a></li> | |
| <li><a href="#/active">Active</a></li> | |
| <li><a href="#/completed">Completed</a></li> | |
| </ul> | |
| <button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">Clear completed</button> | |
| </footer> | |
| </section> | |
| <footer class="info"> | |
| <p>Double-click to edit a todo</p> | |
| <p>Written by <a href="void:javascript(0)">Frank Fan</a></p> | |
| <p>Part of <a href="http://todomvc.com">TodoMVC</a></p> | |
| </footer> | |
| </div> | |
| </template> | |
| <script> | |
| import { todoStorage, filters } from './helper'; | |
| export default { | |
| data() { | |
| return { | |
| newTodo: '', | |
| todos: todoStorage.fetch(), | |
| editedTodo: null, | |
| visibility: this.getHash() || 'all', | |
| }; | |
| }, | |
| watch: { | |
| todos: { | |
| handler: function (todos) { | |
| todoStorage.save(todos); | |
| }, | |
| deep: true, | |
| }, | |
| }, | |
| computed: { | |
| filteredTodos() { | |
| return filters[this.visibility](this.todos); | |
| }, | |
| remaining() { | |
| return filters.active(this.todos).length; | |
| }, | |
| allDone: { | |
| get() { | |
| console.log('get'); | |
| return this.remaining === 0; | |
| }, | |
| set(value) { | |
| console.log('set ', value); | |
| this.todos.forEach(todo => todo.completed = value); | |
| }, | |
| }, | |
| }, | |
| mounted() { | |
| window.addEventListener('hashchange', () => { | |
| const visibility = this.getHash(); | |
| console.log('hashchange ', visibility); | |
| if (filters[visibility]) { | |
| this.visibility = visibility; | |
| } else { | |
| window.location.hash = ''; | |
| this.visibility = 'all'; | |
| } | |
| }); | |
| }, | |
| filters: { | |
| pluralize(n) { | |
| return n === 1 ? 'item' : 'items'; | |
| }, | |
| }, | |
| methods: { | |
| getHash() { | |
| return location.hash.replace(/#\/?/, ''); | |
| }, | |
| addTodo() { | |
| const todo = this.newTodo.trim(); | |
| if (!todo) return; | |
| this.todos.push({ | |
| id: todoStorage.uid++, | |
| title: todo, | |
| completed: false, | |
| }); | |
| this.newTodo = ''; | |
| }, | |
| removeTodo(todo) { | |
| this.todos.splice(this.todos.indexOf(todo), 1); | |
| }, | |
| editTodo(todo) { | |
| this.beforeEditCache = todo.title; | |
| this.editedTodo = todo; | |
| }, | |
| doneEdit(todo) { | |
| if (!this.editedTodo) return; | |
| this.editedTodo = null; | |
| todo.title = todo.title.trim(); | |
| if (!todo.title) { | |
| this.removeTodo(todo); | |
| } | |
| }, | |
| cancelEdit(todo) { | |
| this.editedTodo = null; | |
| todo.title = this.beforeEditCache; | |
| }, | |
| removeCompleted() { | |
| this.todos = filters.active(this.todos); | |
| }, | |
| }, | |
| directives: { | |
| 'todo-focus': function (el, binding) { | |
| if (binding.value) { | |
| el.focus(); | |
| } | |
| }, | |
| }, | |
| }; | |
| </script> | |
| <style> | |
| @import 'https://cdn.jsdelivr.net/npm/[email protected]/index.css'; | |
| </style> |