Tích hợp LocalStorage + Dark Mode + Responsive UI giúp ứng dụng Vue thân thiện hơn với người dùng, chạy mượt mà kể cả khi offline, đồng thời giao diện đẹp hơn trên mobile.
Mục tiêu:
- Lưu công việc (todos) vào
localStorage
để không mất khi F5 - Thêm nút Dark / Light Mode
- Responsive: hiển thị tốt trên mobile
Phần 1: Lưu todos
vào LocalStorage
Cập nhật todoStore.js
export const useTodoStore = defineStore('todo', {
state: () => ({
todos: JSON.parse(localStorage.getItem('todos') || '[]'),
loading: false,
error: null
}),
actions: {
saveToLocal() {
localStorage.setItem('todos', JSON.stringify(this.todos))
},
async fetchTodos() {
if (this.todos.length > 0) return // đã có dữ liệu, không gọi API nữa
this.loading = true
try {
const res = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5')
this.todos = await res.json()
this.saveToLocal()
} catch (e) {
this.error = 'Không tải được dữ liệu'
} finally {
this.loading = false
}
},
async addTodo(title) {
const res = await fetch('https://jsonplaceholder.typicode.com/todos', {
method: 'POST',
body: JSON.stringify({ title, completed: false }),
headers: { 'Content-Type': 'application/json' }
})
const data = await res.json()
this.todos.unshift(data)
this.saveToLocal()
},
async deleteTodo(id) {
await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
method: 'DELETE'
})
this.todos = this.todos.filter(todo => todo.id !== id)
this.saveToLocal()
},
async toggleTodo(id) {
const todo = this.todos.find(t => t.id === id)
todo.completed = !todo.completed
this.saveToLocal()
}
}
})
Phần 2: Thêm Dark Mode
Cập nhật App.vue
<template>
<div :class="{ dark: isDark }">
<h2>🌗 Todo App</h2>
<nav>
<router-link to="/">🏠 Trang chủ</router-link> |
<router-link to="/add">➕ Thêm</router-link>
<button @click="toggleDark" class="toggle-btn">
{{ isDark ? '☀️ Sáng' : '🌙 Tối' }}
</button>
</nav>
<router-view></router-view>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
const isDark = ref(localStorage.getItem('theme') === 'dark')
watchEffect(() => {
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
document.body.classList.toggle('dark', isDark.value)
})
function toggleDark() {
isDark.value = !isDark.value
}
</script>
<style>
body {
transition: background 0.4s, color 0.4s;
}
body.dark {
background: #121212;
color: #fff;
}
.toggle-btn {
margin-left: 20px;
padding: 5px 10px;
}
</style>
Phần 3: Responsive UI
Cập nhật CSS toàn cục
Trong App.vue
hoặc file CSS chính:
@media (max-width: 600px) {
nav {
display: flex;
flex-direction: column;
gap: 8px;
}
input[type="text"] {
width: 100%;
}
.todo {
font-size: 16px;
}
}
Bạn đã học được:
Kỹ thuật | Ứng dụng |
---|---|
localStorage | Lưu dữ liệu offline |
Dark Mode | Trải nghiệm người dùng tốt hơn |
Responsive CSS | Dùng @media để tối ưu trên mobile |
watchEffect() | Theo dõi & phản ứng khi dữ liệu thay đổi |
Bài tập thực hành:
- Thêm tính năng lọc todo: tất cả / đã làm / chưa làm
- Lưu cài đặt dark mode và đọc lại khi vào lại web
- Sử dụng CSS framework nhẹ như UnoCSS hoặc Tailwind để làm đẹp thêm
Thảo luận