Vue每日一题 实现一个待办事项列表(Todo List)

题目描述
使用Vue 3组合式API实现一个功能完整的待办事项列表,需要包含以下功能:
1.
添加新的待办事项
2.
标记待办事项为已完成/未完成
3.
删除待办事项
4.
显示待办事项总数和已完成数量
5.
筛选显示:全部、未完成、已完成
要求
使用Vue 3组合式API(Composition API)
数据需要响应式更新
样式简洁美观
代码结构清晰
题解

点击查看代码
<template>
  <div class="todo-app">
    <h1>Vue待办事项列表</h1>
    
    <!-- 添加新待办 -->
    <div class="add-todo">
      <input 
        v-model="newTodo" 
        @keyup.enter="addTodo"
        placeholder="输入新的待办事项..."
        class="todo-input"
      />
      <button @click="addTodo" class="add-btn">添加</button>
    </div>

    <!-- 统计信息 -->
    <div class="todo-stats">
      <span>总计: {{ todos.length }}</span>
      <span>已完成: {{ completedCount }}</span>
      <span>未完成: {{ uncompletedCount }}</span>
    </div>

    <!-- 筛选按钮 -->
    <div class="filter-buttons">
      <button 
        v-for="filter in filters" 
        :key="filter.key"
        @click="currentFilter = filter.key"
        :class="{ active: currentFilter === filter.key }"
        class="filter-btn"
      >
        {{ filter.label }}
      </button>
    </div>

    <!-- 待办列表 -->
    <ul class="todo-list">
      <li 
        v-for="todo in filteredTodos" 
        :key="todo.id"
        :class="{ completed: todo.completed }"
        class="todo-item"
      >
        <input 
          type="checkbox" 
          v-model="todo.completed"
          class="todo-checkbox"
        />
        <span class="todo-text">{{ todo.text }}</span>
        <button @click="deleteTodo(todo.id)" class="delete-btn">删除</button>
      </li>
    </ul>

    <div v-if="filteredTodos.length === 0" class="empty-state">
      {{ getEmptyMessage() }}
    </div>
  </div>
</template>

<script setup>
import { ref, computed, reactive } from 'vue'

// 响应式数据
const newTodo = ref('')
const currentFilter = ref('all')
const todos = ref([])
let nextId = 1

// 筛选选项
const filters = reactive([
  { key: 'all', label: '全部' },
  { key: 'uncompleted', label: '未完成' },
  { key: 'completed', label: '已完成' }
])

// 计算属性
const completedCount = computed(() => 
  todos.value.filter(todo => todo.completed).length
)

const uncompletedCount = computed(() => 
  todos.value.filter(todo => !todo.completed).length
)

const filteredTodos = computed(() => {
  switch (currentFilter.value) {
    case 'completed':
      return todos.value.filter(todo => todo.completed)
    case 'uncompleted':
      return todos.value.filter(todo => !todo.completed)
    default:
      return todos.value
  }
})

// 方法
const addTodo = () => {
  if (newTodo.value.trim() === '') {
    alert('请输入待办事项内容!')
    return
  }
  
  todos.value.push({
    id: nextId++,
    text: newTodo.value.trim(),
    completed: false
  })
  
  newTodo.value = ''
}

const deleteTodo = (id) => {
  const index = todos.value.findIndex(todo => todo.id === id)
  if (index > -1) {
    todos.value.splice(index, 1)
  }
}

const getEmptyMessage = () => {
  switch (currentFilter.value) {
    case 'completed':
      return '暂无已完成的待办事项'
    case 'uncompleted':
      return '恭喜!所有待办事项都已完成'
    default:
      return '暂无待办事项,快来添加一个吧!'
  }
}
</script>

<style scoped>
.todo-app {
  max-width: 600px;
  margin: 50px auto;
  padding: 20px;
  font-family: 'Arial', sans-serif;
}

h1 {
  text-align: center;
  color: #2c3e50;
  margin-bottom: 30px;
}

.add-todo {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.todo-input {
  flex: 1;
  padding: 12px;
  border: 2px solid #ddd;
  border-radius: 6px;
  font-size: 16px;
}

.todo-input:focus {
  outline: none;
  border-color: #3498db;
}

.add-btn {
  padding: 12px 20px;
  background-color: #3498db;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 16px;
}

.add-btn:hover {
  background-color: #2980b9;
}

.todo-stats {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
  padding: 10px;
  background-color: #f8f9fa;
  border-radius: 6px;
}

.todo-stats span {
  font-weight: bold;
  color: #555;
}

.filter-buttons {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.filter-btn {
  padding: 8px 16px;
  border: 2px solid #ddd;
  background-color: white;
  border-radius: 20px;
  cursor: pointer;
  transition: all 0.3s;
}

.filter-btn:hover {
  border-color: #3498db;
}

.filter-btn.active {
  background-color: #3498db;
  color: white;
  border-color: #3498db;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 15px;
  border: 1px solid #eee;
  border-radius: 6px;
  margin-bottom: 10px;
  transition: all 0.3s;
}

.todo-item:hover {
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.todo-item.completed {
  background-color: #f8f9fa;
  opacity: 0.8;
}

.todo-checkbox {
  width: 18px;
  height: 18px;
  cursor: pointer;
}

.todo-text {
  flex: 1;
  font-size: 16px;
}

.todo-item.completed .todo-text {
  text-decoration: line-through;
  color: #999;
}

.delete-btn {
  padding: 6px 12px;
  background-color: #e74c3c;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

.delete-btn:hover {
  background-color: #c0392b;
}

.empty-state {
  text-align: center;
  color: #999;
  font-style: italic;
  padding: 40px;
}
</style>
知识点解析 1. 组合式API的使用 ref(): 创建响应式的基本数据类型 reactive(): 创建响应式的对象 computed(): 创建计算属性 2. Vue模板语法 v-model: 双向数据绑定 v-for: 列表渲染 v-if/v-show: 条件渲染 @click/@keyup: 事件监听 :class: 动态类绑定 3. 响应式原理 数据变化时,相关的DOM会自动更新 计算属性会根据依赖的数据自动重新计算 4. 组件设计思想 数据驱动视图 单一职责原则 良好的用户体验 这道题目涵盖了Vue的核心概念,适合初学者练习和理解Vue的响应式系统!
posted @ 2025-10-23 10:33  踩一脚  阅读(6)  评论(0)    收藏  举报