印象中上過Udemy的線上課程
Todolist真的是很常拿來被當範例啊~
馬上來實作看看吧~ 官方範例
最後一個實用範例摟!Github
由於父子組件CSS互相干擾的情況
,有做了些許變動,
style 裡頭import的話就是Global,所以使用scoped就無效,會影響到全局
於是就把css下載下來後放進src資料夾,才能把css應用在單一組件內。
<style scoped src="../../assets/todomvc-app.css">
/* @import "https://unpkg.com/todomvc-app-css@2.4.1/index.css"; */
</style><style scoped src="../../assets/todomvc-app.css">
<!--
A fully spec-compliant TodoMVC implementation
https://todomvc.com/
-->
<script setup>
import { ref, computed, watchEffect } from 'vue'
const STORAGE_KEY = 'vue-todomvc'
const filters = {
all: (todos) => todos,
active: (todos) => todos.filter((todo) => !todo.completed),
completed: (todos) => todos.filter((todo) => todo.completed)
}
// state
const todos = ref(JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'))
const visibility = ref('all')
const editedTodo = ref()
// derived state
const filteredTodos = computed(() => filters[visibility.value](todos.value))
const remaining = computed(() => filters.active(todos.value).length)
// handle routing
window.addEventListener('hashchange', onHashChange)
onHashChange()
// persist state
watchEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos.value))
})
function toggleAll(e) {
todos.value.forEach((todo) => (todo.completed = e.target.checked))
}
function addTodo(e) {
const value = e.target.value.trim()
if (value) {
todos.value.push({
id: Date.now(),
title: value,
completed: false
})
e.target.value = ''
}
}
function removeTodo(todo) {
todos.value.splice(todos.value.indexOf(todo), 1)
}
let beforeEditCache = ''
function editTodo(todo) {
beforeEditCache = todo.title
editedTodo.value = todo
}
function cancelEdit(todo) {
editedTodo.value = null
todo.title = beforeEditCache
}
function doneEdit(todo) {
if (editedTodo.value) {
editedTodo.value = null
todo.title = todo.title.trim()
if (!todo.title) removeTodo(todo)
}
}
function removeCompleted() {
todos.value = filters.active(todos.value)
}
function onHashChange() {
const route = window.location.hash.replace(/#\/?/, '')
if (filters[route]) {
visibility.value = route
} else {
window.location.hash = ''
visibility.value = 'all'
}
}
</script>
<template>
<section class="todoapp">
<header class="header">
<h1>Todos</h1>
<input class="new-todo" autofocus placeholder="What needs to be done?" @keyup.enter="addTodo">
</header>
<section class="main" v-show="todos.length">
<input id="toggle-all" class="toggle-all" type="checkbox" :checked="remaining === 0" @change="toggleAll">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li v-for="todo in filteredTodos" class="todo" :key="todo.id"
:class="{ completed: todo.completed, editing: todo === editedTodo }">
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input v-if="todo === editedTodo" class="edit" type="text" v-model="todo.title"
@vue:mounted="({ el }) => el.focus()" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)">
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length">
<span class="todo-count">
<strong>{{ remaining }}</strong>
<span>{{ remaining === 1 ? ' item' : ' items' }} left</span>
</span>
<ul class="filters">
<li>
<a href="#/all" :class="{ selected: visibility === 'all' }">All</a>
</li>
<li>
<a href="#/active" :class="{ selected: visibility === 'active' }">Active</a>
</li>
<li>
<a href="#/completed" :class="{ selected: visibility === 'completed' }">Completed</a>
</li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
Clear completed
</button>
</footer>
</section>
</template>
<style scoped src="../../assets/todomvc-app.css">
/* @import "https://unpkg.com/todomvc-app-css@2.4.1/index.css"; */
</style><style scoped src="../../assets/todomvc-app.css">
這是一個完全符合規範的 TodoMVC 實現。TodoMVC 是一個展示各種 JavaScript 框架和庫如何實現相同 Todo 應用的網站。
import { ref, computed, watchEffect } from 'vue'
這段代碼在 Vue 3 中設置組件,使用 setup
語法糖。ref
、computed
和 watchEffect
是 Vue 的響應式 API。
const STORAGE_KEY = 'vue-todomvc'
定義了存儲鍵,用於 localStorage 中保存 todo 項。
const filters = {
all: (todos) => todos,
active: (todos) => todos.filter((todo) => !todo.completed),
completed: (todos) => todos.filter((todo) => todo.completed)
}
定義了三個過濾器:all
、active
和 completed
,分別用於篩選所有、未完成和已完成的 todo 項。
const todos = ref(JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'))
const visibility = ref('all')
const editedTodo = ref()
定義了三個響應式變量:todos
用於存儲所有的 todo 項,visibility
用於存儲當前的過濾狀態,editedTodo
用於存儲當前正在編輯的 todo 項。
const filteredTodos = computed(() => filters[visibility.value](todos.value))
const remaining = computed(() => filters.active(todos.value).length)
定義了兩個計算屬性:filteredTodos
根據當前的過濾狀態過濾 todos,remaining
計算未完成的 todo 項數量。
window.addEventListener('hashchange', onHashChange)
onHashChange()
監聽 URL 中的 hash 變化,以便根據 hash 更新過濾狀態。
watchEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos.value))
})
監聽 todos
的變化,並將其保存到 localStorage 中,以便下次加載時恢復。
function toggleAll(e) {
todos.value.forEach((todo) => (todo.completed = e.target.checked))
}
function addTodo(e) {
const value = e.target.value.trim()
if (value) {
todos.value.push({
id: Date.now(),
title: value,
completed: false
})
e.target.value = ''
}
}
function removeTodo(todo) {
todos.value.splice(todos.value.indexOf(todo), 1)
}
let beforeEditCache = ''
function editTodo(todo) {
beforeEditCache = todo.title
editedTodo.value = todo
}
function cancelEdit(todo) {
editedTodo.value = null
todo.title = beforeEditCache
}
function doneEdit(todo) {
if (editedTodo.value) {
editedTodo.value = null
todo.title = todo.title.trim()
if (!todo.title) removeTodo(todo)
}
}
function removeCompleted() {
todos.value = filters.active(todos.value)
}
function onHashChange() {
const route = window.location.hash.replace(/#\/?/, '')
if (filters[route]) {
visibility.value = route
} else {
window.location.hash = ''
visibility.value = 'all'
}
}
這些函數處理不同的事件,如切換所有項目完成狀態、添加新項目、刪除項目、編輯項目、取消編輯、完成編輯、刪除已完成項目以及處理 URL 中的 hash 變化。
<template>
<section class="todoapp">
<header class="header">
<h1>Todos</h1>
<input class="new-todo" autofocus placeholder="What needs to be done?" @keyup.enter="addTodo">
</header>
<section class="main" v-show="todos.length">
<input id="toggle-all" class="toggle-all" type="checkbox" :checked="remaining === 0" @change="toggleAll">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li v-for="todo in filteredTodos" class="todo" :key="todo.id"
:class="{ completed: todo.completed, editing: todo === editedTodo }">
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input v-if="todo === editedTodo" class="edit" type="text" v-model="todo.title"
@vue:mounted="({ el }) => el.focus()" @blur="doneEdit(todo)" @keyup.enter="doneEdit(todo)"
@keyup.escape="cancelEdit(todo)">
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length">
<span class="todo-count">
<strong>{{ remaining }}</strong>
<span>{{ remaining === 1 ? ' item' : ' items' }} left</span>
</span>
<ul class="filters">
<li>
<a href="#/all" :class="{ selected: visibility === 'all' }">All</a>
</li>
<li>
<a href="#/active" :class="{ selected: visibility === 'active' }">Active</a>
</li>
<li>
<a href="#/completed" :class="{ selected: visibility === 'completed' }">Completed</a>
</li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
Clear completed
</button>
</footer>
</section>
</template>
這段模板代碼定義了應用的結構。包括標題、新建待辦事項的輸入框、待辦事項列表、過濾器和清除已完成項目的按鈕。
<style scoped src="../../assets/todomvc-app.css">
/* @import "https://unpkg.com/todomvc-app-css@2.4.1/index.css"; */
</style>
這段代碼引用了 todomvc-app.css
樣式文件,以確保應用的外觀符合 TodoMVC 規範。
由於父組件仍會影響到子組件footer的呈現,所以就在上一層包了一個container讓子組件能順利排列。
<style scoped>
.todomvc-container {
display: flex;
flex-direction: column;
}
</style>
關於父子組件的CSS的影響還真的要好好研究一下,
不然創建好的組件以為隨時可以套用在各種場合~
沒處理好~都花時間在解決CSS衝突啊~
感覺未來JS也可能會衝突 www