编码实现:任务列表与状态管理
在明确了 TaskMaster 的需求和技术选型后,本节将进入编码实现阶段,重点完成任务列表的展示和状态管理功能。我们将使用 Vue 3 的 Composition API、Pinia 进行状态管理,并结合 Element Plus 的组件库实现用户界面。通过逐步构建任务列表视图和核心逻辑,帮助你掌握 Vue 3 在实际项目中的应用。
目标与功能
实现目标
- 任务列表展示:显示所有任务,支持按状态过滤。
- 状态管理:添加、编辑任务及其状态,使用 Pinia 存储数据。
- 数据持久化:将任务保存到 LocalStorage。
功能分解
- 任务数据模型:定义任务结构。
- Pinia Store:管理任务状态和操作。
- 任务列表组件:展示任务并支持交互。
编码实现
1. 定义任务数据模型
任务将包含以下字段:
id:唯一标识。title:任务标题。description:任务描述。status:任务状态(todo、in-progress、done)。dueDate:截止日期。
2. 配置 Pinia Store
stores/task.ts
// src/stores/task.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';
interface Task {
id: number;
title: string;
description: string;
status: 'todo' | 'in-progress' | 'done';
dueDate: string; // ISO 日期格式,如 '2025-03-01'
}
export const useTaskStore = defineStore('task', () => {
const tasks = ref<Task[]>([]);
// 从 LocalStorage 加载数据
const loadTasks = () => {
const savedTasks = localStorage.getItem('tasks');
if (savedTasks) {
tasks.value = JSON.parse(savedTasks);
}
};
// 保存到 LocalStorage
const saveTasks = () => {
localStorage.setItem('tasks', JSON.stringify(tasks.value));
};
// 添加任务
const addTask = (task: Omit<Task, 'id'>) => {
tasks.value.push({
id: Date.now(),
...task
});
saveTasks();
};
// 更新任务
const updateTask = (id: number, updates: Partial<Task>) => {
const index = tasks.value.findIndex(t => t.id === id);
if (index !== -1) {
tasks.value[index] = { ...tasks.value[index], ...updates };
saveTasks();
}
};
// 删除任务
const deleteTask = (id: number) => {
tasks.value = tasks.value.filter(t => t.id !== id);
saveTasks();
};
// 初始化加载
loadTasks();
return { tasks, addTask, updateTask, deleteTask };
});
3. 创建任务列表组件
components/TaskList.vue
<template>
<div>
<el-input
v-model="newTaskTitle"
placeholder="输入任务标题"
@keyup.enter="addNewTask"
style="margin-bottom: 20px;"
/>
<el-table :data="filteredTasks" style="width: 100%;">
<el-table-column prop="title" label="任务标题" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="dueDate" label="截止日期" />
<el-table-column label="状态" width="150">
<template #default="{ row }">
<el-select v-model="row.status" @change="updateTaskStatus(row)">
<el-option label="待办" value="todo" />
<el-option label="进行中" value="in-progress" />
<el-option label="已完成" value="done" />
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="{ row }">
<el-button type="danger" size="small" @click="deleteTask(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-select v-model="filterStatus" placeholder="筛选状态" style="margin-top: 20px;">
<el-option label="全部" value="all" />
<el-option label="待办" value="todo" />
<el-option label="进行中" value="in-progress" />
<el-option label="已完成" value="done" />
</el-select>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
import { useTaskStore } from '@/stores/task';
import { ElMessage } from 'element-plus';
export default defineComponent({
setup() {
const taskStore = useTaskStore();
const newTaskTitle = ref('');
const filterStatus = ref('all');
const filteredTasks = computed(() => {
if (filterStatus.value === 'all') return taskStore.tasks;
return taskStore.tasks.filter(task => task.status === filterStatus.value);
});
const addNewTask = () => {
if (newTaskTitle.value.trim()) {
taskStore.addTask({
title: newTaskTitle.value,
description: '',
status: 'todo',
dueDate: new Date().toISOString().split('T')[0] // 默认今天
});
ElMessage.success('任务添加成功');
newTaskTitle.value = '';
}
};
const updateTaskStatus = (task: { id: number; status: string }) => {
taskStore.updateTask(task.id, { status: task.status });
ElMessage.info('任务状态已更新');
};
const deleteTask = (id: number) => {
taskStore.deleteTask(id);
ElMessage.success('任务已删除');
};
return {
newTaskTitle,
filterStatus,
filteredTasks,
addNewTask,
updateTaskStatus,
deleteTask
};
}
});
</script>
<style scoped>
.el-table {
margin-bottom: 20px;
}
</style>
4. 集成到主应用
App.vue
<template>
<div class="app-container">
<h1>TaskMaster</h1>
<TaskList />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import TaskList from './components/TaskList.vue';
export default defineComponent({
components: { TaskList },
setup() {}
});
</script>
<style scoped>
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
</style>
main.ts
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import App from './App.vue';
const app = createApp(App);
app.use(createPinia());
app.use(ElementPlus);
app.mount('#app');
实现效果
- 任务添加:输入标题,按回车创建新任务,默认状态为“待办”。
- 状态切换:通过下拉框更改任务状态,实时更新并保存。
- 列表过滤:选择状态筛选任务,动态刷新表格。
- 数据持久化:刷新页面后任务数据保留。
代码分析
Pinia Store
- 状态管理:
tasks使用ref存储任务数组,支持响应式更新。 - 持久化:
loadTasks和saveTasks实现 LocalStorage 读写。 - 操作方法:
addTask、updateTask和deleteTask确保数据一致性。
TaskList 组件
- UI 组件:使用
el-table和el-select实现列表和筛选。 - 响应式计算:
filteredTasks通过computed动态过滤。 - 用户反馈:
ElMessage提供操作提示。
测试验证
手动测试
- 添加任务,检查列表更新和 LocalStorage。
- 更改状态,验证筛选功能。
- 删除任务,确认数据移除。
单元测试(可选)
// tests/TaskList.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import TaskList from '@/components/TaskList.vue';
import { createPinia, setActivePinia } from 'pinia';
describe('TaskList', () => {
it('adds a new task', async () => {
setActivePinia(createPinia());
const wrapper = mount(TaskList);
const input = wrapper.find('input');
await input.setValue('New Task');
await input.trigger('keyup.enter');
expect(wrapper.text()).toContain('New Task');
});
});
注意事项
- 数据校验:
- 当前仅检查标题非空,后续可添加更多验证。
- 性能:
- 大量任务可能需优化(如虚拟滚动)。
- 错误处理:
- LocalStorage 存储失败时需提示用户。
总结
本节实现了 TaskMaster 的任务列表和状态管理功能,利用 Vue 3、Pinia 和 Element Plus 完成了核心需求。通过响应式数据和组件化设计,你已掌握了基本的编码实践。下一节将扩展功能,添加任务编辑和排序,完善应用体验!
