終於進入到實戰演練的環節,拿出我們前天建立好的專案,準備來練習To Do List!
◎題外話:今天原本感覺時間很充裕,還在慢慢刻樣式…結果也花太多時間在喬css了吧!(吐血~不得已只好分成上下篇…)
◎小提示:該流程只是擴增nav導覽列的筆記,已經準備好模板的人也可以直接往下移動!
◎小提示:如果使用VS 2022的人,要記得到「延伸模組/管理延伸模組」下載Force UTF-8,才不會出現中文亂碼!詳見:使用Visual Studio2022 vue 中文乱码
Step1.我們到App.vue複製一項頁籤,我取名為To Do List
Step2.接著在「src/views」下直接複製出一個分頁,稍後模板就會放在這裡
Step3.在新分頁中可以先稍微變換內容、以及重新命名class
Step4.最重要的是進到index.js註冊該頁!
Final Step.展示頁面準備就緒!
這裡我們就可以來放入喜歡的To Do List模板,就是我今天刻了老半天的東西…請笑納:
<div class="todolist">
<div class="todo_wrapper">
<h1 style="border-bottom:1px dotted">To Do List</h1>
<br>
<div class="add_block">
<input class="todo_input" type="text" placeholder="請輸入文字" />
<button class="add_item">加入</button>
</div>
<div class="nav_block">
<button class="nav_item">全部</button>
<button class="nav_item">進行中</button>
<button class="nav_item">已完成</button>
</div>
<ul class="todo_item">
<li>
<button class="btn_icon" type="button" style="float:left">✔</button>
<button class="btn_icon" type="button" style="float:left">☐</button>
<label style="margin-left: 5px; cursor: pointer;"></label>
<button class="btn_icon" type="button" style="float:right">✘</button>
</li>
</ul>
</div>
</div>
li {
width: 100%;
height: 50px;
list-style: none;
background-color: #ffffff;
padding: 13px;
cursor: pointer;
}
li:hover {
background-color: #cacaca;
}
.todolist {
min-height: 100vh;
display: flex;
align-items: center;
}
.todo_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items:center;
background-color: #ededed;
height:500px;
width:500px;
border-radius:5px;
}
.add_block {
display: inline-flex;
width: 85%;
justify-content: space-around
}
.todo_input {
border: none;
outline-style: none;
padding: .375rem .75rem;
width: 85%;
}
.add_item {
border: none;
background-color: #808080;
margin-left: 10px;
color: aliceblue;
cursor: pointer;
padding: .375rem .75rem;
}
.nav_block {
margin-top: 15px;
border-bottom: 1px solid #808080;
width: 100%
}
.nav_item {
border: none;
background-color: #ffffff;
margin-left: 10px;
color:#808080;
cursor: pointer;
padding: .375rem .75rem
}
.todo_item {
width: 85%;
height: 300px;
overflow-y: auto;
margin-top: 20px;
scrollbar-width: thin;
padding: inherit;
}
.btn_icon {
background: none;
border: none;
cursor: pointer;
}
看起來該有的元素都出現了,接下來就讓Vue來幫我們把To Do List活過來!
Step1.新增<script setup>
引入ref
,宣告todos
;使用content
跟html上的input做雙向資料綁定
<script setup>
import { ref } from 'vue'
const todos = ref([])
const num = ref(0) //作為todos中的id計數
const content = ref(''); //和v-model進行介面input輸入框的資料雙向綁定
</script>
<li v-for="todo in todos" :key="todo.id">
...
<label style="margin-left: 5px; cursor: pointer;">{{todo.text}}</label>
</li>
▲li標籤上要綁定todos,並將id作為key值;文字用{{todo.text}}
印出來
<input v-model="content" class="todo_input" type="text" placeholder="請輸入文字" />
▲input要加入v-model,才能雙向綁定
Step2.在新增的button上寫一個點擊事件
function sendText() {
if (content.value != "") { //檢查輸入框有沒有內容
const todo = {
id: num.value += 1, //計數加一,作為key值使用
text: content.value,
status: false //給一個false狀態,用於改變樣式使用
}
todos.value.push(todo) //塞進todos陣列中
content.value = "" //輸入框內容要重置
}
}
<button @click="sendText()" class="add_item">加入</button>
做到這裡我們已經可以新增一項代辦清單了,但好像有點奇怪?
Step3.調整一下li內容
☐和✔同時出現了,但此時還沒打勾應該只能顯示☐,這時候我們就要靠todos中的status判斷
<button v-if="todo.status" class="btn_icon" type="button" style="float:left">✔</button>
<button v-else type="button" class="btn_icon" style="float:left">☐</button>
▲加入v-if
及v-else
進行判斷
現在我們有了一項代辦事項,完成後希望能顯示剛才的✔,並把文字加上刪除線
<button @click.stop="changeTodo(todo.id)" v-if="todo.status" class="btn_icon" type="button" style="float:left">✔</button>
<button @click.stop="changeTodo(todo.id)" v-else type="button" class="btn_icon" style="float:left">☐</button>
▲在☐和✔上加入changeTodo(todo.id)
,@click.stop
可以避免觸發父層的點擊事件
function changeTodo(id) {
todos.value = todos.value.map(
todo => todo.id === id ?
{ ...todo, status: !todo.status } : todo)
//利用map()判斷相同id並更新當前status狀態等於true
}
<label :class="{ 'line-through': todo.status }" style="margin-left: 5px; cursor: pointer;">{{todo.text}}</label>
▲文字在✔後要出現刪除線,自定義一個class,並在下方css中加入樣式
.line-through {
text-decoration:line-through
}
寫到這裡我們越來越熟練了,在✘上加入deleteTodo(todo.id)
<button @click.stop="deleteTodo(todo.id)" class="btn_icon" type="button" style="float:right">✘</button>
function deleteTodo(id) {
todos.value = todos.value.filter(todo => todo.id !== id)
//利用filter()排除要刪除的項目,並重新指定給todos
}
新增刪除功能都完成了,最後我們在html加上v-if
、v-else
,判斷沒有資料的情況:
<ul v-if="todos.length > 0" class="todo_item">
...
</ul>
<ul class="todo_item" v-else>
<li>沒有資料</li>
</ul>
<div class="todolist">
<div class="todo_wrapper">
<h1 style="border-bottom:1px dotted">To Do List</h1>
<br>
<div class="add_block">
<input v-model="content" class="todo_input" type="text" placeholder="請輸入文字" />
<button @click="sendText()" class="add_item">加入</button>
</div>
<div class="nav_block">
<button class="nav_item">全部</button>
<button class="nav_item">進行中</button>
<button class="nav_item">已完成</button>
</div>
<ul v-if="todos.length > 0" class="todo_item">
<li v-for="todo in todos" :key="todo.id">
<button @click.stop="changeTodo(todo.id)" v-if="todo.status" class="btn_icon" type="button" style="float:left">✔</button>
<button @click.stop="changeTodo(todo.id)" v-else type="button" class="btn_icon" style="float:left">☐</button>
<label :class="{ 'line-through': todo.status }" style="margin-left: 5px; cursor: pointer;">{{todo.text}}</label>
<button @click.stop="deleteTodo(todo.id)" class="btn_icon" type="button" style="float:right">✘</button>
</li>
</ul>
<ul class="todo_item" v-else>
<li>沒有資料</li>
</ul>
</div>
</div>
li {
width: 100%;
height: 50px;
list-style: none;
background-color: #ffffff;
padding: 13px;
cursor: pointer;
}
li:hover {
background-color: #cacaca;
}
.todolist {
min-height: 100vh;
display: flex;
align-items: center;
}
.todo_wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items:center;
background-color: #ededed;
height:500px;
width:500px;
border-radius:5px;
}
.add_block {
display: inline-flex;
width: 85%;
justify-content: space-around
}
.todo_input {
border: none;
outline-style: none;
padding: .375rem .75rem;
width: 85%;
}
.add_item {
border: none;
background-color: #808080;
margin-left: 10px;
color: aliceblue;
cursor: pointer;
padding: .375rem .75rem;
}
.nav_block {
margin-top: 15px;
border-bottom: 1px solid #808080;
width: 100%
}
.nav_item {
border: none;
background-color: #ffffff;
margin-left: 10px;
color:#808080;
cursor: pointer;
padding: .375rem .75rem
}
.todo_item {
width: 85%;
height: 300px;
overflow-y: auto;
margin-top: 20px;
scrollbar-width: thin;
padding: inherit;
}
.btn_icon {
background: none;
border: none;
cursor: pointer;
}
.line-through {
text-decoration:line-through
import { ref } from 'vue'
const num = ref(0) //作為todolist中的id計數
const content = ref(''); //和v-model進行介面input輸入框的資料雙向綁定
const todos = ref([])
function sendText() {
if (content.value != "") {
const todo = {
id: num.value += 1,
text: content.value,
status: false
}
todos.value.push(todo)
content.value = ""
}
}
function deleteTodo(id) {
todos.value = todos.value.filter(todo => todo.id !== id)
}
function changeTodo(id) {
todos.value = todos.value.map(todo => todo.id === id ? { ...todo, status: !todo.status } : todo)
}
壓線好可怕,我們明天繼續把修改及頁籤功能寫完!
參考資料
使用 Vue3 Composition Api 實作 Todo List (Vue3)