มาแต่ง todo list กัน
มาลองแต่ง Todo List ด้วย DaisyUI กัน
├── src
├── App.vue --> root component
├── router
│ └── index.js --> ตำแหน่งรวม router (ตำแหน่งเดิม)
├── stores
│ └── todo.js --> store สำหรับเก็บ todo list ไว้
└── views
│ ├── EditView.vue --> หน้าสำหรับแก้ไข EditView.vue
│ └── HomeView.vue --> หน้าสำหรับหน้าหลัก
└── main.js
เรามีหน้าจะต้องแต่ง 2 หน้าคือ
- EditView.vue
- HomeView.vue
โดยเราจะกำหนด layout ใหญ่สุดของหน้าจอก่อน ว่าจะไม่ให้เกิน 768px ไป (เพื่อให้ UI ไม่ใหญ่จนเกินไป)
ที่ App.vue เราจะเพิ่ม class มาห่อ RouterView
เป็น
<script setup>
import { RouterView } from 'vue-router'
</script>
<template>
<div class="max-w-3xl mx-auto my-0 p-4">
<RouterView />
</div>
</template>
เริ่ม แต่งหน้า HomeView.vue
สิ่งที่เราจะทำ
- ทำการเรียง input ตีให้เต็มหน้าจอ คู่กับปุ่ม Add (เรียงบรรทัดแรกใหม่)
- เปลี่ยน icon ปุ่มให้เป็นปุ่มแก้ไข และปุ่มลบ (เอา svg มาจาก https://fontawesome.com/)
- แก้ไข loading ให้ตี overlay ทั้งหน้าจอแทน
- เรียงของใหม่โดยเอา status ออกและเอา checkbox เข้ามาแทน (เป็นการ mark status done แทนที่จะเปลี่ยนจาก dropdown)
- ทำการเพิ่ม tab เข้ามาเพื่อแยก status ระหว่าง Pending, Doing, Done ออกจากกัน
- เพิ่มขีดฆ่าเข้ามากับเคสที่ Done แล้ว (ถ้าติ๊กออกให้กลับมา Doing)
ผลลัพธ์จะออกมาเป็นแบบนี้
code ทั้งหมดของ HomeView.vue
<script setup>
import { onMounted, ref, computed } from 'vue'
import { RouterLink } from 'vue-router'
import { useTodoStore } from '../stores/todo'
import Loading from '../components/Loading.vue'
const todoStore = useTodoStore()
const todoText = ref('')
const isLoading = ref(false)
const selectedStatus = ref('Pending')
onMounted(async () => {
isLoading.value = true
await todoStore.loadTodos()
isLoading.value = false
// console.log(todoStore.list) มันเป็น proxy = เราก็แก้ผ่านตัวนี้ก็ได้
})
const todoFilteredList = computed(() => {
return todoStore.list.filter(todo => todo.status === selectedStatus.value)
})
const changeSelectedStatus = (newStatus) => {
selectedStatus.value = newStatus
}
const addTodo = async (todoText) => {
isLoading.value = true
try {
await todoStore.addTodo(todoText)
await todoStore.loadTodos()
} catch (error) {
console.log('error', error)
}
isLoading.value = false
}
const updateStatus = async (todoId, todoStatus) => {
isLoading.value = true
try {
await todoStore.editTodo({
status: todoStatus
}, todoId)
} catch (error) {
console.log('error', error)
}
isLoading.value = false
}
const changeDoneStatus = async (event, todoId) => {
try {
if (event.target.checked) {
await updateStatus(todoId, 'Done')
await todoStore.loadTodos()
}
} catch (error) {
console.log('error', error)
}
}
const removeTodo = async (id) => {
isLoading.value = true
try {
await todoStore.removeTodo(id)
await todoStore.loadTodos()
} catch (error) {
console.log('error', error)
}
isLoading.value = false
}
</script>
<template>
<div class="flex">
<input
class="input input-bordered input-info w-full"
type="text"
placeholder="Add your item"
v-model="todoText"
>
<button class="btn ml-4" @click="addTodo(todoText)">Add</button>
</div>
<div>
<Loading v-if="isLoading"></Loading>
<div>
<div class="tabs tabs-boxed my-2">
<a
v-for="status in todoStore.statuses" :key="status"
:class="status === selectedStatus ? 'tab tab-active' : 'tab'"
@click="changeSelectedStatus(status)">
{{ status }}
</a>
</div>
<div class="flex items-center justify-between my-2" v-for="todo in todoFilteredList" :key="todo.id">
<input type="checkbox" :checked="todo.isDone" class="checkbox" @change="changeDoneStatus($event, todo.id)" />
<div :class="todo.isDone ? 'line-through' : ''">
Item {{ todo.name }}
</div>
<div>
<RouterLink :to="{ name: 'edit-view', params: { id: todo.id } }">
<button class="btn btn-square btn-outline">
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M471.6 21.7c-21.9-21.9-57.3-21.9-79.2 0L362.3 51.7l97.9 97.9 30.1-30.1c21.9-21.9 21.9-57.3 0-79.2L471.6 21.7zm-299.2 220c-6.1 6.1-10.8 13.6-13.5 21.9l-29.6 88.8c-2.9 8.6-.6 18.1 5.8 24.6s15.9 8.7 24.6 5.8l88.8-29.6c8.2-2.7 15.7-7.4 21.9-13.5L437.7 172.3 339.7 74.3 172.4 241.7zM96 64C43 64 0 107 0 160V416c0 53 43 96 96 96H352c53 0 96-43 96-96V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v96c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H96z"/></svg>
</button>
</RouterLink>
<button class="btn btn-square btn-outline ml-2" @click="removeTodo(todo.id)">
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
svg {
fill: white;
}
</style>
แต่งหน้า EditView.vue
สิ่งที่เราจะทำ
- แก้ไขให้ขนาดหน้าจอเล็กลงครึ่ งหนึ่ง และอยู่ตรงกลางหน้าจอ
- เรียง form input ใหม่ (input name, status)
- เปลี่ยนปุ่มให้เป็นสี primary และตีเต็มแทน
- โชว์ badge item id
- เพิิ่ม loading เข้ามา
- update เสร็จ ให้บอกผ่าน Toast ออกมาว่า update เรียบร้อย
code ทั้งหมดของ EditView.vue
<script setup>
import { onMounted, ref, reactive } from 'vue'
import { useTodoStore } from '../stores/todo'
import { useRoute, RouterLink } from 'vue-router'
import Loading from '../components/Loading.vue'
const todoStore = useTodoStore()
const route = useRoute()
const todoId = ref(-1)
/* Status */
const isLoading = ref(false)
const isUpdated = ref(false)
const isLoaded = ref(false)
const todoData = reactive({
name: '',
status: ''
})
const editTodo = async (todoData, todoId) => {
try {
isLoading.value = true
await todoStore.editTodo(todoData, todoId)
isUpdated.value = true
isLoading.value = false
// set update ออกไป
setTimeout(() => {
isUpdated.value = false
}, 2000)
} catch (error) {
console.log('error', error)
}
}
onMounted(async () => {
try {
isLoading.value = true
todoId.value = parseInt(route.params.id)
await todoStore.loadTodo(todoId.value)
todoData.name = todoStore.selectedTodo.name
todoData.status = todoStore.selectedTodo.status
isLoaded.value = true
isLoading.value = false
} catch (error) {
console.log('error', error)
}
})
</script>
<template>
<div class="w-1/2 mx-auto">
<div class="flex items-center">
<Loading v-if="isLoading"></Loading>
<RouterLink :to="{ name: 'home' }">
<button class="btn btn-square">
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
</button>
</RouterLink>
<div class="ml-2">
Edit
</div>
</div>
<div class="flex flex-col" v-if="isLoaded">
<div class="flex gap-4">
<div class="text-sm ml-1">Item id</div>
<span class="badge badge-primary">12</span>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Item name</span>
</label>
<input class="input input-bordered w-full" type="text" v-model="todoData.name">
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Status</span>
</label>
<select class="select select-bordered" v-model="todoData.status">
<option
v-for="status in todoStore.statuses"
:key="status"
:value="status"
>
{{ status }}
</option>
</select>
</div>
<div class="mt-4">
<button class="btn btn-primary w-full" @click="editTodo(todoData, todoId)">
Update
</button>
</div>
</div>
<div class="toast toast-top toast-start">
<div v-show="isUpdated" class="alert alert-success">
<span>Update successful.</span>
</div>
</div>
</div>
</template>
<style scoped>
svg {
fill: white;
}
</style>
ผลลัพธของ code นี้
และนี่คือผลลัพธ์ทั้งหมดของ Session นี้
เมื่อรวมกันทั้ง 2 หน้า action ทั้งหมดและ UI ทั้งหมดก็จะหน้าตาประมาณนี้
2 Session ต่อไป เราจะจัดหนักจัดเต็มกับการแต่ง Style CSS นะครับ หลังจากที่เราปูพื้นฐานทั้งหมดไป เราจะกลับมาสู่แผนภาพของเรา Easy Commerce กันจริงๆและ