Skip to content

📝 Vue3 课堂实战:简易待办清单 (Simple ToDo List)

一、 项目介绍

简易待办清单--demo 。实现一个具备“增、删、查、统计”功能的任务管理小工具,并学习如何组织组件代码。

核心学习目标:

  1. 组件化开发:学习如何创建子组件并在主程序中使用 (import)。
  2. 响应式数据:使用 reactive 管理页面状态。
  3. 指令综合应用
    • v-model: 表单双向绑定
    • v-for: 列表循环渲染
    • v-on (@): 事件监听
    • v-show: 控制页面元素的显示/隐藏
    • computed: 实时计算属性

二、 功能需求 (User Stories)

我们将构建一个包含两个“页面”(视图)的单页应用:

1. 顶部标题栏

  • 要求:必须封装为一个独立的组件 Header.vue
  • 功能:仅展示项目名称,背景灰色,居中显示。

2. 任务列表页 (List View)

这是默认显示的页面,包含以下功能:

  • A. 任务统计:在列表上方实时显示“当前共有 X 个任务”。
  • B. 添加任务
    • 提供一个输入框和一个“添加”按钮。
    • 输入内容为空时,点击添加应弹出提示。
    • 添加成功后,输入框需自动清空,新任务显示在列表底部。
  • C. 任务展示
    • 以列表形式 (<ul>) 展示所有待办事项。
    • 每一行显示:序号、任务内容、删除按钮。
  • D. 删除任务:点击某行任务后的“删除”按钮,该任务立即从列表中移除。

3. 关于说明页 (About View)

  • 要求:展示一段简单的静态文本(如“这是 Vue3 教学案例”)。
  • 交互:默认隐藏,通过底部导航切换显示。

4. 底部导航

  • 功能:提供两个按钮:“任务列表” 和 “关于说明”。
  • 逻辑:点击不同按钮,切换上方的内容显示区域(使用 v-show 实现)。

三、 技术架构与文件设计

1. 文件目录结构

plain
src/
├── components/
│   └── Header.vue      <-- 子组件:只负责显示标题
└── App.vue             <-- 主组件:包含所有数据和业务逻辑

2. 数据结构设计 (App.vue)

我们将使用一个 reactive 对象 data 来管理所有状态:

变量名类型初始值作用描述
currentViewString'list'控制页面切换('list' 或 'about')
todoInputString''与输入框双向绑定的临时变量
todosArray['学习 Vue3', ...]存放所有待办任务的字符串数组

四、 开发流程指引

第一阶段:组件搭建 (HTML结构)

  1. 创建子组件:新建 Header.vue,写入简单的 <h3> 标签和 CSS。
  2. 引入子组件:在 App.vueimport MyHeader 并放置在 <template> 最上方。
  3. 搭建主骨架:在 App.vue 中写好“输入区”、“列表区”、“底部导航”的 HTML 标签(先不加 Vue 指令)。

第二阶段:数据驱动 (JavaScript逻辑)

  1. 定义数据:引入 reactive,在 script setup 中定义 data 对象。
  2. 渲染列表:使用 v-fordata.todos 渲染到 <li> 标签中。
  3. 双向绑定:使用 v-model 将输入框与 data.todoInput 绑定。

第三阶段:交互实现 (事件处理)

  1. 添加功能:编写 add() 函数(包含非空判断、push操作),绑定到添加按钮。
  2. 删除功能:编写 remove(index) 函数(splice操作),绑定到删除按钮。
  3. 统计功能:引入 computed,编写 totalCount 计算属性并展示。

第四阶段:页面切换 (v-show)

  1. 条件渲染:给列表区域的 div 加上 v-show="data.currentView === 'list'"
  2. 关于页:添加关于页 div,加上 v-show="data.currentView === 'about'"
  3. 切换事件:给底部两个按钮添加点击事件,修改 data.currentView 的值。

五、 课堂练习提示

  • 易错点 1v-for 循环时,千万不要忘记加 :key="index"
  • 易错点 2:在 <script> 中修改 reactive 对象的数据时,不需要 .value;但如果是 ref 定义的,则需要 .value(本项目统一使用 reactive 以降低难度)。
  • 思考题:如果我想让新添加的任务显示在列表的 最上面add 函数里的 push 应该改成什么方法?(答案:unshift
vue
<script setup>
// 1. 引入 Vue 核心功能
import { reactive, computed } from 'vue'
// 2. 引入刚才写的子组件
import MyHeader from './components/Header.vue'

// --- 数据定义区 ---
const data = reactive({
  currentView: 'list',  // 控制页面切换:'list' (列表页) 或 'about' (关于页)
  todoInput: '',        // 输入框绑定的内容
  todos: [              // 初始待办数据
    '学习 Vue3 基础',
    '完成今日作业'
  ]
})

// --- 计算属性区 ---
// 实时计算任务总数
const totalCount = computed(() => {
  return data.todos.length
})

// --- 方法定义区 ---
// 添加任务
const add = () => {
  // 非空判断 (.trim() 去除首尾空格)
  if (data.todoInput.trim() === '') {
    alert("写点什么吧!")
    return
  }
  data.todos.push(data.todoInput) // 数组操作:push (末尾添加)
  data.todoInput = ''             // 清空输入框
}

// 删除任务
const remove = (index) => {
  data.todos.splice(index, 1)     // 数组操作:splice (删除指定位置元素)
}
</script>

<template>
  <!-- 1. 使用子组件 -->
  <MyHeader />

  <!-- 2. 页面区域:主列表 (v-show 控制显示) -->
  <div v-show="data.currentView === 'list'" style="padding: 20px;">
    
    <!-- 统计信息 (computed) -->
    <p>当前共有 <strong>{{ totalCount }}</strong> 个任务</p>

    <!-- 输入区域 (v-model & v-on) -->
    <div style="margin-bottom: 20px;">
      <!-- v-model 双向绑定输入框 -->
      <input type="text" v-model="data.todoInput" placeholder="输入任务...">
      <!-- @click 绑定点击事件 -->
      <button @click="add">添加</button>
    </div>

    <!-- 列表区域 (v-for) -->
    <ul>
      <!-- v-for 循环渲染列表,必须加 :key -->
      <li v-for="(item, index) in data.todos" :key="index">
        <span>{{ index + 1 }}. {{ item }}</span>
        <!-- 传入 index 以删除特定项 -->
        <button @click="remove(index)">删除</button>
      </li>
    </ul>
  </div>

  <!-- 3. 页面区域:关于页 (v-show 控制显示) -->
  <div v-show="data.currentView === 'about'" style="padding: 20px; text-align: center;">
    <p>这是 Vue3 课程的教学案例</p>
    <p>用于演示基础指令的用法</p>
  </div>

  <!-- 4. 底部简单的导航按钮 -->
  <div class="nav-bar">
    <!-- 点击切换 currentView 的值 -->
    <button @click="data.currentView = 'list'">任务列表</button>
    <button @click="data.currentView = 'about'">关于说明</button>
  </div>
</template>

<style scoped>
/* 极简样式,仅为了区分布局,不干扰教学重点 */
ul {
  padding-left: 20px;
}
li {
  margin-bottom: 10px;
  /* 让删除按钮稍微隔开一点 */
  display: flex;
  justify-content: space-between;
  width: 300px; /* 限制宽度方便看清布局 */
  border-bottom: 1px dashed #eee;
}
.nav-bar {
  margin-top: 50px;
  border-top: 1px solid #ccc;
  padding: 10px;
  text-align: center;
}
button {
  cursor: pointer;
  margin-left: 5px;
}
</style>
vue
<template>
  <!-- 简单的头部结构 -->
  <div class="header">
    <h3>📝 简易待办清单</h3>
  </div>
</template>

<script setup>
  // 这是一个纯展示组件,没有逻辑代码
</script>

<style scoped>
  /* 简单的灰色背景和居中对齐 */
  .header {
    background-color: #f0f0f0;
    padding: 10px;
    text-align: center;
    border-bottom: 1px solid #ccc;
  }
</style>
plain
<script setup>
// 1. 引入 Vue 核心功能
import { reactive, computed } from 'vue'
// 2. 引入刚才写的子组件
import MyHeader from './components/Header.vue'

// --- 数据定义区 ---
const data = reactive({
  currentView: 'list',  // 控制页面切换:'list' (列表页) 或 'about' (关于页)
  todoInput: '',        // 输入框绑定的内容
  todos: [              // 初始待办数据
    '学习 Vue3 基础',
    '完成今日作业'
  ]
})
const totalCount = computed(() => {
  return data.todos.length
})

// --- 方法定义区 ---
// 添加任务
const add = () => {
  // 非空判断 (.trim() 去除首尾空格)
  if (data.todoInput.trim() === '') {
    alert("写点什么吧!")
    return
  }
  data.todos.push(data.todoInput) // 数组操作:push (末尾添加)
  data.todoInput = ''             // 清空输入框
  console.log(data.todos)
}

// 删除任务
const remove = (index) => {
  data.todos.splice(index, 1)     // 数组操作:splice (删除指定位置元素)
}
</script>

<template>
  <!-- 1. 使用子组件 -->
  <MyHeader />

  <p>当前共有{{ totalCount }} 个任务</p>

  <!-- 输入区域 (v-model & v-on) -->
  <div style="margin-bottom: 20px;">
    <!-- v-model 双向绑定输入框 -->
    <input type="text" v-model="data.todoInput" placeholder="输入任务...">
    <!-- @click 绑定点击事件 -->
    <button @click="add">添加</button>
  </div>
  
</template>