AI 辅助开发实战:基于 Vue 的健身项目毕业设计全流程解析
毕业设计最怕“功能越堆越乱、代码越写越脏”。去年我用 Vue 3 写健身管理项目时,把 GitHub Copilot 和通义灵码混着用,两周搞定从需求到部署。今天把踩过的坑、省下的时间、留下的代码全部摊开,给还在熬夜改 Bug 的同学一条能抄的近路。
1. 毕业设计三宗罪:为什么你总被导师打回
- 功能冗余:把“健身”做成大杂烩,饮食、社交、商城全塞进去,结果每个模块都只有 30% 完成度。
- 逻辑耦合:页面里直接调 axios,请求地址硬编码,后期换接口要改 40 个文件。
- 缺乏工程规范:文件夹层级随意,命名拼音与英文混用,README 只有一句“本项目基于 Vue”。
这些问题单靠自己“多写几遍”很难根治,AI 工具的价值就在于把“规范”变成“一键生成”。
2. AI 工具选型:Copilot 与通义灵码实测对比
| 维度 | GitHub Copilot | 通义灵码 |
|---|---|---|
| 组件骨架生成 | 快捷键Ctrl+Enter直接出.vue三段式模板,样式用 Tailwind,需要再改 class | 输入“生成健身计划卡片组件”,自动带<script setup>与 TypeScript 类型 |
| 注释补全 | 能识别函数意图,英文注释精准;中文需手动改 | 原生中文语义,方法级注释一次到位 |
| 单元测试 | 生成 Jest 代码,但断言简单,常漏边界 | 默认给出 vitest 示例,覆盖率为 0 的 case 会标红提示 |
| 安全校验 | 不会主动加权限判断 | 在生成路由守卫时附带meta.requiresAuth模板 |
结论:
Copilot 适合“快”,通义灵码适合“准”。我通常让 Copilot 先生成 70% 骨架,再用通义灵码补测试、补注释,最后人工 merge。
3. 核心实现:AI 如何帮你写业务代码
3.1 训练计划模块(Composition API)
需求:根据用户目标(减脂/增肌)返回 7 日训练表,支持拖拽调整顺序。
操作步骤:
在
src/composables/useTrainingPlan.ts里输入注释:// 根据 goal 返回计划列表,支持拖拽排序,数据格式见 TrainingItemCopilot 自动生成接口类型与核心逻辑。
手动把拖拽库换成
vue-draggable-plus,AI 会误引旧版vuedraggable,需要pnpm add vue-draggable-plus并改 import。用通义灵码补单测:
// useTrainingPlan.spec.ts it('应把新增动作插入到对应星期', () => { const { addMovement } = useTrainingPlan('fat-loss') addMovement({ name: '跳绳', sets: 4 }) expect(plan.value[0].movements).toContainEqual(expect.objectContaining({ name: '跳绳' })) })
关键收获:AI 生成的类型 90% 可用,但数组方法顺序可能不符合业务,务必 review。
3.2 全局状态:Pinia 用户模块
让通义灵码“生成 Pinia 用户 store,包含登录、注册、token 刷新”。
得到src/stores/user.ts雏形后,手动加两项约束:
- 刷新接口必须携带
refreshToken,AI 常漏判。 - 登录后把
avatar写入本地localStorage,防止 F5 后闪烁。
export const useUserStore = defineStore('user', () => { const token = ref(localStorage.getItem('tk') ?? '') const profile = ref<Profile | null>(null) async function login(form: LoginForm) { const { data } = await http.post<LoginRsp>('/auth/login', form) token.value = data.accessToken localStorage.setItem('tk', data.accessToken) // AI 忘记持久化 await fetchProfile() } async function fetchProfile() { const { data } = await http.get<Profile>('/user/me') profile.value = data localStorage.setItem('avatar', data.avatar) // 手动补 } return { token, profile, login, fetchProfile } })3.3 表单验证:AI 生成 yup 方案
需求:体脂率字段仅在目标为“减脂”时必填,范围 5-50。
提示词:
目标为减脂时体脂率必填,number,min:5,max:50,给出 yup + vee-validate 代码通义灵码返回:
import * as yup from 'yup' export const bodySchema = yup.object({ goal: yup.string().oneOf(['fat-loss', 'bulk']), bodyFat: yup.number().when('goal', { is: 'fat-loss', then: schema => schema.min(5, '体脂率不低于 5').max(50, '体脂率不高于 50').required('请输入体脂率'), otherwise: schema => schema.strip() }) })直接可用,无需改一行。
4. 可运行代码片段:Clean Code 示范
下面是把训练计划卡片拆出来的<PlanCard>,AI 先生成,我删掉冗余div,补aria-label提升可访问性。
<!-- src/components/PlanCard.vue --> <template <template> <li class="plan-card" role="article" :aria-label="`第${dayIndex + 1}天计划`"> <header class="plan-card__header"> <h3>第 {{ dayIndex + 1 }} 天</h3> <Tag :type="plan.goal">{{ plan.goal }}</Tag> </header> <draggable v-model="plan.movements" item-key="id" @end="emit('sorted')"> <template #item="{ element }"> <MovementRow :movement="element" @remove="handleRemove(element.id)" /> </template> </draggable> <footer class="plan-card__footer"> <Button size="sm" @click="emit('add')">添加动作</Button> </footer> </li> </template> <script setup lang="ts"> import draggable from 'vue-draggable-plus' import type { TrainingPlan } from '@/types' interface Props { plan: TrainingPlan; dayIndex: number } defineProps<Props>() const emit = defineEmits<{ sorted: [] add: [] }>() function handleRemove(id: string) { emit('remove', id) } </script> <style scoped> .plan-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 1rem; } .plan-card__header { display: flex; justify-content: space-between; align-items: center; } </style>要点:
- 逻辑只处理“删”,增与排序交给父组件,保持单一职责。
aria-label让自动化测试能抓到节点,导师看代码也挑不出毛病。
5. AI 代码的暗礁:别等线上爆了才发现
- 幂等性缺失:AI 生成的“创建计划”接口调用放在
onMounted里,用户刷新就会重复插入。解决:用createNew参数或唯一索引约束。 - 安全校验遗漏:Copilot 给出的 JWT 解析函数不验签,直接
atob解码。上线前必须换成jose库。 - 过度查询:AI 喜欢
select *,把用户密码哈希也返回到前端。要在后端仓库另开 PR,和毕业设计一起打分。
6. 生产环境避坑指南
路由守卫:通义灵码会写
router.beforeEach,但经常忘记next()调用顺序。模板:router.beforeEach((to, from, next) => { const user = useUserStore() if (to.meta.requiresAuth && !user.token) next('/login') else next() })注意
next()必须被调用一次且仅一次,否则白屏。Mock → 真实接口:用
vite-plugin-mock-dev-server,在.env.production中把VITE_MOCK=false即可一键切换,无需改业务代码。架构失控检查清单:
- 任何 AI 生成的 service 文件必须单元测试覆盖 ≥ 80%。
- 每个
composable对应一个.md说明输入输出,防止后人“看不懂就重写”。 - 代码评审自己给自己开 PR,用 GitHub 的
copilot-review动作跑一遍,有 warning 就修不通过。
7. 动手环节:重构一个 AI 组件
请把 AI 生成的MovementRow.vue拿过来,完成以下任务:
- 把行内样式全部替换成 Tailwind utility。
- 用
computed拆分“显示重量单位”逻辑,杜绝魔法字符串。 - 补一条
aria-live提示,确保删除动作后屏幕阅读器能播报“已删除”。
做完后对比 AI 原始版本,你会直观看到“人机协作”的边界:AI 负责 0→1,人负责 1→90 分。
8. 写在最后:毕业设计不是终点,是协作模式的起点
把 AI 当“加速键”而非“万能钥匙”,我的项目 70% 代码由机器产出,但 100% 事故由人兜底。下次面对需求,先写清晰注释,再让 AI 跑通主干,最后用测试与 Code Review 把住质量关——这套流程,比任何花哨的 prompt 都更能帮你准时下班,也让导师在答辩时挑不出刺。祝你一次过、不二辩,代码干净,身材也精干。