see https://cli.vuejs.org/zh/guide/prototyping.html
# install
npm install -g @vue/cli-service-global
# run
vue serve TodoApp.vue
see 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/todomvc-app-css@2.3.0/index.css'; | |
</style> |