📝 Vue3 课堂实战:简易待办清单 (Simple ToDo List)
一、 项目介绍
简易待办清单--demo 。实现一个具备“增、删、查、统计”功能的任务管理小工具,并学习如何组织组件代码。
核心学习目标:
- 组件化开发:学习如何创建子组件并在主程序中使用 (
import)。 - 响应式数据:使用
reactive管理页面状态。 - 指令综合应用:
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 来管理所有状态:
| 变量名 | 类型 | 初始值 | 作用描述 |
|---|---|---|---|
currentView | String | 'list' | 控制页面切换('list' 或 'about') |
todoInput | String | '' | 与输入框双向绑定的临时变量 |
todos | Array | ['学习 Vue3', ...] | 存放所有待办任务的字符串数组 |
四、 开发流程指引
第一阶段:组件搭建 (HTML结构)
- 创建子组件:新建
Header.vue,写入简单的<h3>标签和 CSS。 - 引入子组件:在
App.vue中import MyHeader并放置在<template>最上方。 - 搭建主骨架:在
App.vue中写好“输入区”、“列表区”、“底部导航”的 HTML 标签(先不加 Vue 指令)。
第二阶段:数据驱动 (JavaScript逻辑)
- 定义数据:引入
reactive,在script setup中定义data对象。 - 渲染列表:使用
v-for将data.todos渲染到<li>标签中。 - 双向绑定:使用
v-model将输入框与data.todoInput绑定。
第三阶段:交互实现 (事件处理)
- 添加功能:编写
add()函数(包含非空判断、push操作),绑定到添加按钮。 - 删除功能:编写
remove(index)函数(splice操作),绑定到删除按钮。 - 统计功能:引入
computed,编写totalCount计算属性并展示。
第四阶段:页面切换 (v-show)
- 条件渲染:给列表区域的
div加上v-show="data.currentView === 'list'"。 - 关于页:添加关于页
div,加上v-show="data.currentView === 'about'"。 - 切换事件:给底部两个按钮添加点击事件,修改
data.currentView的值。
五、 课堂练习提示
- 易错点 1:
v-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>