扩展功能:任务编辑与排序
在完成了 TaskMaster 的任务列表和状态管理后,本节将扩展功能,添加任务编辑和排序能力,进一步完善用户体验。任务编辑允许用户修改任务详情,排序则提供按截止日期或其他字段组织任务的灵活性。我们将继续使用 Vue 3、Pinia 和 Element Plus,通过逐步实现这些功能,展示如何扩展现有代码并优化应用。
目标与功能
实现目标
- 任务编辑:支持修改任务的标题、描述和截止日期。
- 任务排序:允许按截止日期排序任务。
功能分解
- 编辑表单:创建弹窗表单编辑任务。
- 排序逻辑:实现按截止日期升序/降序排序。
- UI 更新:集成新功能到任务列表组件。
编码实现
1. 更新 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;
}
export const useTaskStore = defineStore('task', () => {
const tasks = ref<Task[]>([]);
const loadTasks = () => {
const savedTasks = localStorage.getItem('tasks');
if (savedTasks) tasks.value = JSON.parse(savedTasks);
};
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();
};
// 按截止日期排序
const sortTasksByDueDate = (ascending: boolean = true) => {
tasks.value.sort((a, b) => {
const dateA = new Date(a.dueDate).getTime();
const dateB = new Date(b.dueDate).getTime();
return ascending ? dateA - dateB : dateB - dateA;
});
saveTasks();
};
loadTasks();
return { tasks, addTask, updateTask, deleteTask, sortTasksByDueDate };
});
2. 创建任务编辑组件
components/TaskEdit.vue
<template>
<el-dialog v-model="visible" title="编辑任务" width="30%">
<el-form :model="form" label-width="100px">
<el-form-item label="标题">
<el-input v-model="form.title" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" type="textarea" />
</el-form-item>
<el-form-item label="截止日期">
<el-date-picker v-model="form.dueDate" type="date" format="YYYY-MM-DD" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="saveTask">保存</el-button>
</template>
</el-dialog>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
import { useTaskStore } from '@/stores/task';
import { ElMessage } from 'element-plus';
export default defineComponent({
props: {
task: {
type: Object,
default: () => ({ id: 0, title: '', description: '', dueDate: '', status: 'todo' })
}
},
emits: ['update:visible'],
setup(props, { emit }) {
const taskStore = useTaskStore();
const visible = ref(false);
const form = ref({ ...props.task });
watch(() => props.task, (newTask) => {
form.value = { ...newTask };
visible.value = !!newTask.id;
});
const saveTask = () => {
taskStore.updateTask(props.task.id, {
title: form.value.title,
description: form.value.description,
dueDate: new Date(form.value.dueDate).toISOString().split('T')[0]
});
ElMessage.success('任务已更新');
visible.value = false;
};
watch(visible, (val) => emit('update:visible', val));
return { visible, form, saveTask };
}
});
</script>
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="截止日期" sortable />
<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="150">
<template #default="{ row }">
<el-button type="primary" size="small" @click="editTask(row)">编辑</el-button>
<el-button type="danger" size="small" @click="deleteTask(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="8">
<el-select v-model="filterStatus" placeholder="筛选状态">
<el-option label="全部" value="all" />
<el-option label="待办" value="todo" />
<el-option label="进行中" value="in-progress" />
<el-option label="已完成" value="done" />
</el-select>
</el-col>
<el-col :span="8">
<el-button @click="sortTasks(true)">按截止日期升序</el-button>
<el-button @click="sortTasks(false)">按截止日期降序</el-button>
</el-col>
</el-row>
<TaskEdit :task="editingTask" v-model:visible="showEditDialog" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
import { useTaskStore } from '@/stores/task';
import { ElMessage } from 'element-plus';
import TaskEdit from './TaskEdit.vue';
export default defineComponent({
components: { TaskEdit },
setup() {
const taskStore = useTaskStore();
const newTaskTitle = ref('');
const filterStatus = ref('all');
const showEditDialog = ref(false);
const editingTask = ref({ id: 0, title: '', description: '', dueDate: '', status: 'todo' });
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('任务已删除');
};
const editTask = (task: typeof editingTask.value) => {
editingTask.value = { ...task };
showEditDialog.value = true;
};
const sortTasks = (ascending: boolean) => {
taskStore.sortTasksByDueDate(ascending);
ElMessage.info(`已按截止日期${ascending ? '升序' : '降序'}排序`);
};
return {
newTaskTitle,
filterStatus,
filteredTasks,
showEditDialog,
editingTask,
addNewTask,
updateTaskStatus,
deleteTask,
editTask,
sortTasks
};
}
});
</script>
<style scoped>
.el-table {
margin-bottom: 20px;
}
</style>
实现效果
- 任务编辑:
- 点击“编辑”按钮,弹出对话框,修改标题、描述和截止日期后保存。
- 任务排序:
- 点击“升序”或“降序”按钮,按截止日期重新排列任务。
- 用户交互:
- 编辑和排序操作后显示提示,数据实时更新并持久化。
代码分析
Pinia Store
- 扩展方法:新增
sortTasksByDueDate支持按日期排序。 - 持久化:所有操作后调用
saveTasks确保数据保存。
TaskEdit 组件
- 弹窗表单:使用
el-dialog和el-form,双向绑定任务数据。 - 状态同步:通过
watch和v-model与父组件通信。
TaskList 组件
- 编辑集成:添加
editTask方法和TaskEdit组件。 - 排序按钮:调用 Store 的排序方法,动态更新列表。
测试验证
手动测试
- 编辑任务,检查标题、描述和截止日期更新。
- 排序任务,验证按日期顺序排列。
- 刷新页面,确认数据和排序状态保留。
E2E 测试
cypress/e2e/taskList.cy.js
describe('TaskList', () => {
beforeEach(() => cy.visit('/'));
it('edits a task', () => {
cy.get('input').type('Test Task{enter}');
cy.get('.el-table').contains('Test Task').parent().find('.el-button--primary').click();
cy.get('.el-dialog').within(() => {
cy.get('input').first().clear().type('Edited Task');
cy.get('.el-button--primary').click();
});
cy.get('.el-table').should('contain', 'Edited Task');
});
it('sorts tasks by due date', () => {
cy.get('input').type('Task 1{enter}');
cy.get('input').type('Task 2{enter}');
cy.get('.el-button').contains('按截止日期升序').click();
cy.get('.el-table tr').eq(1).should('contain', 'Task 1');
});
});
注意事项
- 日期格式:
- 确保
dueDate为有效 ISO 格式,避免排序错误。
- 确保
- 性能:
- 大量任务时,排序可能需优化(如缓存排序结果)。
- 用户体验:
- 编辑表单可添加输入验证。
总结
本节扩展了 TaskMaster 的功能,实现任务编辑和排序,利用 Vue 3 的组件化和 Pinia 的状态管理完成了需求。通过实践,你掌握了如何在现有基础上添加新特性,并通过测试验证功能完整性。下一节将优化用户体验和性能,完善 TaskMaster 应用!
