应用设计与脚手架
实战:任务管理应用 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
从零开始一个真实规模的项目:先把需求拆成页面、数据模型和组件树,再搭出可运行的骨架。这一节交付「项目能跑、空看板可见」的最小版本,后续三节在这个骨架上加功能。
问题场景
需求一句话:「做一个任务看板」。直接开写往往写到一半发现:状态散落在三个组件里、路由和筛选互相打架、刷新丢数据。项目规模和练习的区别就在「设计先行」——想清楚再动手,后面才不用返工。
核心解释
第一步:定页面与数据模型。看板应用最小页面集:
/:看板页(三列:待办 / 进行中 / 已完成)/task/:id:任务详情(可选,用于编辑)/about:关于页(验证路由切换不影响看板状态)
数据模型:
interface Task {
id: string; // crypto.randomUUID()
title: string;
status: "todo" | "doing" | "done";
priority: "low" | "mid" | "high";
createdAt: string; // ISO 时间
}
第二步:定状态归属。按 V11 的原则:任务列表跨页面共享 → Pinia;筛选条件要能分享/刷新保留 → URL query;纯展示派生(各列任务、统计)→ computed。
为什么用单一 status 字段而不是三个数组:状态迁移只改一个字段,列分组交给 computed 派生,不存在「三份数据不同步」的问题;排序也只对一份数据操作。
第三步:搭脚手架:执行 npm create vue@latest taskboard,勾选 TypeScript、Router、Pinia、Vitest、ESLint,然后 cd taskboard && npm install && npm run dev。
第四步:建 store 骨架(先能存空数组,再逐步加 action):
// src/stores/tasks.js
import { computed, ref } from "vue";
import { defineStore } from "pinia";
export const useTaskStore = defineStore("tasks", () => {
const tasks = ref([]);
const byStatus = computed(() => ({
todo: tasks.value.filter((t) => t.status === "todo"),
doing: tasks.value.filter((t) => t.status === "doing"),
done: tasks.value.filter((t) => t.status === "done"),
}));
function add(title) {
tasks.value.push({
id: crypto.randomUUID(),
title,
status: "todo",
priority: "mid",
createdAt: new Date().toISOString(),
});
}
return { tasks, byStatus, add };
});
第五步:组件树:
App.vue
├── AppHeader.vue (标题 + 新任务入口 + 筛选)
├── RouterView
│ ├── BoardView.vue (三列布局)
│ │ ├── BoardColumn.vue
│ │ │ └── TaskCard.vue
│ │ └── TaskDialog.vue(新建/编辑)
│ └── AboutView.vue
先只实现 BoardView + BoardColumn + TaskCard,数据为空,页面显示三个空列——这就是「骨架可运行」的标准。
示例拆解
BoardView 的第一版,只用 store 的 byStatus:
<script setup>
import { useTaskStore } from "../stores/tasks";
import BoardColumn from "../components/BoardColumn.vue";
const store = useTaskStore();
</script>
<template>
<main class="board">
<BoardColumn title="待办" status="todo" :tasks="store.byStatus.todo" />
<BoardColumn title="进行中" status="doing" :tasks="store.byStatus.doing" />
<BoardColumn title="已完成" status="done" :tasks="store.byStatus.done" />
</main>
</template>
<style scoped>
.board {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
padding: 16px;
}
@media (max-width: 768px) {
.board { grid-template-columns: 1fr; }
}
</style>
BoardColumn 接收 tasks 数组渲染列表,TaskCard 展示单条。此时还不能添加任务——骨架阶段只验收「三个空列渲染、移动端单列堆叠」。
动手任务
- 建项目并搭出三列骨架,验收:
npm run dev能跑、三列可见、窄屏单列。 - 把
byStatus换成「按优先级排序后分组」的 computed,观察派生逻辑只改一行。 - 画一张组件树与数据流图(谁读 store、谁发事件),贴在项目 README 里。
常见错误
- 状态散落在组件里:列表数据放 BoardView 的 ref,TaskCard 想改就得层层 emit。跨页面共享的状态进 store。
- 一开始就把筛选、排序、拖拽全写上:骨架没跑通就加功能,出问题分不清是架构错还是细节错。
- 数据模型缺 id:用 index 当 key(V07 的坑),后续增删排序全乱。
- 忘了移动端断点:三列在手机上挤成一条线。骨架阶段就把响应式做掉。
检查与复盘
- 能说清每个状态放在 store / URL / 组件 的理由
- 交付了可运行的三列骨架
- 能画出组件树与数据流
参考资料
- Vue 官方文档「快速上手」:create-vue 脚手架
- Pinia 官方文档:setup store 写法
读完这一课了?
标记完成后,工作台会安排接下来的学习。