简单的Web前端毕业设计:从零实现一个可部署的实战项目
摘要:许多计算机专业学生在完成毕业设计时,常因缺乏工程化思维而陷入“能跑就行”的陷阱,导致项目难以部署、维护或展示。本文以“简单的Web前端毕业设计”为切入点,手把手带你构建一个结构清晰、技术栈现代、具备完整 CI/CD 流程的实战应用。你将掌握组件化开发、状态管理、API 对接及静态资源优化等关键技能,并获得一个可直接用于答辩或 GitHub 展示的高质量项目模板。
1. 背景痛点:为什么“能跑就行”行不通
每年 4 月,实验室的走廊里总会响起同一句话:“老师,我本地能跑,部署就 404!”——这几乎成了毕业设计季的固定彩蛋。把时钟拨回大三暑假,我也一样:一个index.html里塞满script标签,CSS 与 JS 混写,接口硬编码http://localhost:8080,最后把文件夹 zip 发给导师,结果对方打不开,只能尴尬地现场演示。
归纳起来,学生项目最容易踩的坑有三类:
- 代码耦合:所有逻辑堆在
App.vue,修改一行,全局报错。 - 零测试:点击流程靠手点,答辩现场一紧张就“手滑”。
- 无法部署:本地 devServer 正常,上线后白屏、资源 404、接口跨域。
毕业设计不仅是“跑起来”,更是“跑得远”。下面用 1 个周末时间,带你把玩具项目升级成“能写进简历”的实战作品。
2. 技术选型:快、轻、现代
技术选型遵循三条原则:①学习成本低;②社区活跃;③部署方便。对比一圈后,我锁定如下组合:
| 维度 | 方案 A(选用) | 方案 B | 理由 |
|---|---|---|---|
| 构建工具 | Vite | Create React App | CRA 慢、配置黑盒;Vite 秒开 HMR,毕业设计不需要 eject |
| 框架 | Vue 3 | React 18 | 模板语法易读,导师一眼看懂 |
| 语言 | TypeScript | JavaScript | 类型提示减少低级 bug,答辩更自信 |
| 状态管理 | Pinia | Redux | 模块化、无 mutations,写起来像普通对象 |
| UI 库 | Element Plus | Ant Design Vue | 文档全,主题色可一键换,答辩 PPT 好配色 |
| 部署平台 | Vercel | GitHub Pages | 推送即部署,自动 HTTPS,免费额度足够 |
图:技术栈总览
3. 核心实现:Vue 3 + TS + Vite 三板斧
功能需求保持“最小可用”——登录、数据展示、表单提交,却覆盖 80% 日常场景。
3.1 项目初始化
npm create vite@latest grad-project --template vue-ts cd grad-project npm i vue-router@4 pinia element-plus axios npm i -D @types/node目录约定(src 下):
- api/ # 接口层 - assets/ # 静态资源 - components/ # 通用组件 - stores/ # Pinia 模块 - styles/ # 全局样式 - utils/ # 工具函数 - views/ # 页面级组件3.2 路由与权限
router/index.ts:
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', redirect: '/login' }, { path: '/login', component: () => import('@/views/Login.vue') }, { path: '/dashboard', component: () => import('@/views/Dashboard.vue'), meta: { requiresAuth: true } } ] const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }) router.beforeEach((to, _, next) => { const token = localStorage.getItem('token') if (to.meta?.requiresAuth && !token) next('/login') else next() }) export default router关键点:用createWebHistory去掉#,URL 更优雅;但上线需服务端配置回退。
3.3 状态管理(Pinia)
stores/user.ts:
import { defineStore } from 'pinia' import { login } from '@/api/user' export const useUserStore = defineStore('user', { state: () => ({ token: '' }), actions: { async login(account: string, pwd: string) { const { data } = await login(account, pwd) this.token = data.token localStorage.setItem('token', data.token) } } })组件内使用:
import { useUserStore } from '@/stores/user' const user = useUserStore() await user.login(form.account, form.pwd)3.4 接口层封装
utils/request.ts:
import axios from 'axios' import { ElMessage } from 'element-plus' const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE, timeout: 6000 }) http.interceptors.response.use( res => res.data, err => { ElMessage.error(err.response?.data?.msg || '服务异常') return Promise.reject(err) } ) export default http环境变量.env.development:
VITE_API_BASE=http://localhost:3000.env.production:
VITE_API_BASE=https://api.grad-demo.vercel.app3.5 页面组件示例
views/Dashboard.vue:
<template> <el-table :data="list" v-loading="loading"> <el-table-column prop="name" label="姓名" /> <el-table-column prop="score" label="分数" /> </el-table> <el-button type="primary" @click="showForm = true">新增记录</el-button> <el-dialog v-model="showForm" title="录入成绩"> <ScoreForm @success="onSubmit" /> </el-dialog> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' import { getScores } from '@/api/score' import ScoreForm from '@/components/ScoreForm.vue' const list = ref([]) const loading = ref(true) const showForm = ref(false) const onSubmit = () => { showForm.value = false loadData() } const loadData = async () => { loading.value = true list.value = await getScores() loading.value = false } onMounted(loadData) </script>ScoreForm.vue采用v-model+emit模式,保持单向数据流,代码略。
4. Clean Code 小贴士
- 一个函数只做一件事:登录逻辑放
stores/user,表单校验放组件内部。 - 魔法数字收敛:HTTP 状态码、路由名用
enum封装。 - 注释写“为什么”而不是“做什么”:
// 刷新 token 有效期,防止答辩演示时掉线 const refreshToken = () => { ... }- 统一异常提示:后端返回
{code:0, msg:'ok'},前端统一拦截,避免每页写alert。
5. 性能与安全:让导师挑不出刺
5.1 打包体积
vite.config.ts:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { visualizer } from 'rollup-plugin-visualizer' export default defineConfig({ plugins: [vue(), visualizer({ open: true })], build: { rollupOptions: { output: { manualChunks: { 'element-plus': ['element-plus'], 'vue-vendor': ['vue', 'vue-router', 'pinia'] } } } 历 } })开启 Gzip + CDN 后,首屏 JS 从 450 KB 降到 120 KB。
5.2 懒加载
路由级已自动拆包;图片懒加载用v-lazy指令:
<img v-lazy="avatar" />5.3 XSS 防护
- 所有用户输入走
{{ }}双大括号,Vue 默认转义。 - 富文本场景用
DOMPurify二次清洗。 - 接口返回
Content-Type: application/json; charset=utf-8,拒绝text/html。
6. 生产环境避坑指南
- 环境变量必须以
VITE_开头,否则客户端收不到。 - 若用
createWebHistory,Nginx 需加:
location / { try_files $uri $uri/ /index.html; }- 静态资源路径:Vite 打包后
base默认/,若部署到 GitHub Pages 的子路径/grad-project/,需在vite.config里同步base: '/grad-project/'。 - 自动部署:GitHub 仓库 → Vercel 一键导入,选择
Output Directory: dist,Install Command: npm ci,Build Command: npm run build。推送 main 分支即触发 CI,30 秒后就能拿到 HTTPS 域名,写进答辩 PPT 瞬间高大上。
7. 可扩展方向(把“玩具”变“产品”)
单元测试:Vitest + @vue/test-utils,给
ScoreForm写一条“输入负数分数应提示错误”的用例,答辩加分。持续集成:GitHub Actions 跑
npm run build && npm run test,PR 自动检测,老师看到你的绿色对勾,印象分 +10。全栈演进:
- 把 mock 数据换成 NestJS + MongoDB,实现注册、JWT 刷新。
- 用 Docker 把前后端一起
docker-compose up,简历写上“熟悉容器化部署”。
微前端:若宿舍兄弟也要交毕设,一起挂到
qiankun主框架,老师直呼“团队协作能力强”。
8. 结语:先跑起来,再跑更远
写完这篇,我把模板仓库丢到 GitHub,已经收到两位同学的 issue:“部署成功,谢谢学长!”——其实我只是比他们早踩了那些坑。毕业设计不是终点,而是第一次把代码推向公网、接受陌生人检验的机会。先让页面在 Vercel 亮起来,再逐步加上测试、监控、后端,甚至多语言、暗黑模式。等你站在答辩教室投影前,输入网址,页面秒开,数据图表实时刷新,你会感谢那个提前 2 周折腾部署的自己。
下一步?打开终端,运行npm create vite@latest,把第一个 commit 推上去吧。期待在 GitHub Trending 看到你的毕业设计 2.0。