BÀI 17: COMPONENT THỐNG KÊ + HIỆU ỨNG CHUYỂN ĐỘNG

jk5587725

By jk5587725

Đăng ngày Tháng 7 7, 2025

Component Thống kê + Hiệu ứng chuyển động trong Vue giúp ứng dụng trực quan, sinh động hơn và có trải nghiệm người dùng tốt hơn.

Mục tiêu:

  • Tạo component thống kê số lượng việc (tổng, đã làm, chưa làm)
  • Thêm hiệu ứng khi todo xuất hiện / biến mất bằng <transition>
  • Dùng tốt computed() và CSS animation

Phần 1: Tạo TodoStats.vue – Thống kê

src/components/TodoStats.vue

<template>
  <div class="stats">
    Tổng: <strong>{{ total }}</strong> |
    Đã làm: <strong>{{ done }}</strong> |
    Chưa làm: <strong>{{ undone }}</strong>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { useTodoStore } from '@/store/todoStore'

const store = useTodoStore()

const total = computed(() => store.todos.length)
const done = computed(() => store.todos.filter(t => t.completed).length)
const undone = computed(() => store.todos.filter(t => !t.completed).length)
</script>

<style scoped>
.stats {
  margin-bottom: 10px;
  padding: 10px;
  background: #f3f3f3;
  border-radius: 6px;
  font-weight: bold;
}
</style>

Cập nhật TodoList.vue

<template>
  <div>
    <TodoStats />
    <div class="filter">
      <button :class="{active: filter === 'all'}" @click="filter = 'all'">Tất cả</button>
      <button :class="{active: filter === 'done'}" @click="filter = 'done'">Đã làm</button>
      <button :class="{active: filter === 'undone'}" @click="filter = 'undone'">Chưa làm</button>
      <input v-model="keyword" placeholder="🔍 Tìm kiếm..." />
    </div>

    <p v-if="store.loading">⏳ Đang tải...</p>
    <p v-if="store.error" class="error">❌ {{ store.error }}</p>

    <transition-group name="fade" tag="div">
      <TodoItem
        v-for="todo in filteredTodos"
        :key="todo.id"
        :todo="todo"
        @toggle="store.toggleTodo"
        @delete="store.deleteTodo"
      />
    </transition-group>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { useTodoStore } from '@/store/todoStore'
import TodoItem from './TodoItem.vue'
import TodoStats from './TodoStats.vue'

const store = useTodoStore()
onMounted(() => store.fetchTodos())

const filter = ref('all')
const keyword = ref('')

const filteredTodos = computed(() => {
  return store.todos.filter(todo => {
    const matchStatus =
      filter.value === 'all' ||
      (filter.value === 'done' && todo.completed) ||
      (filter.value === 'undone' && !todo.completed)

    const matchKeyword = todo.title.toLowerCase().includes(keyword.value.toLowerCase())

    return matchStatus && matchKeyword
  })
})
</script>

<style scoped>
.fade-enter-active, .fade-leave-active {
  transition: all 0.4s ease;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
  transform: translateY(10px);
}

.filter {
  margin-bottom: 16px;
}
.filter button {
  margin-right: 8px;
  padding: 5px 10px;
}
.filter button.active {
  background-color: #2b8a3e;
  color: white;
}
.filter input {
  margin-left: 10px;
  padding: 6px;
  width: 200px;
}
</style>
Kỹ thuật VueVai trò
computed()Tính toán dữ liệu thống kê tự động
<transition-group>Hiệu ứng khi thêm/xóa hàng loạt
scoped CSS + animationLàm mượt trải nghiệm người dùng
  1. Thêm thời gian tạo vào mỗi todo
  2. Hiển thị thống kê % hoàn thành dạng progress bar
  3. Chuyển hiệu ứng thành slide, hoặc dùng thư viện như Animate.css

📂 Chuyên mục:

Thảo luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Đăng ký nhận tin mới

Nhận bài học, tài nguyên và cơ hội việc làm qua email hàng tuần.

[global_subscribe_form]

Chúng tôi cam kết không spam. Bạn có thể hủy bất cứ lúc nào.