
在实际 Java Web 项目开发中一个功能完整、前后端分离的“美食菜谱点餐系统”是检验全栈能力非常好的练手项目。它涵盖了用户管理、菜品展示、购物车、订单处理等核心业务模块同时要求开发者对 Spring Boot 后端 API 设计、Vue3 前端工程化、数据库设计以及前后端联调有清晰的理解。很多同学在准备毕业设计或丰富个人简历时往往卡在如何将零散的技术点串联成一个可运行、有逻辑的完整系统上。本文将带你从零开始手把手搭建一个基于 Spring Boot 3.x 和 Vue 3 的美食点餐系统重点不是罗列代码而是解释每一步的设计意图、关键配置和开发中必然会遇到的坑确保你能理解原理并独立复现。1. 项目核心设计与技术栈选型在动手写代码之前必须先明确我们要构建什么以及为什么选择这些技术。一个清晰的设计蓝图能避免后期大量返工。1.1 系统功能模块拆解一个基础的在线点餐系统至少需要以下模块用户模块注册、登录、登出、个人信息管理。这是系统权限控制的基础。菜品模块菜谱分类如川菜、粤菜、菜品列表、菜品详情图片、描述、价格。这是系统的核心数据。购物车模块用户添加菜品、修改数量、删除菜品、清空购物车。需要处理临时数据的存储。订单模块将购物车生成订单、查看历史订单、订单状态管理如待支付、已支付、已完成。这是业务流程的终点。这些模块构成了典型的前后端数据流前端页面触发请求 - 后端 Controller 接收 - Service 处理业务 - Mapper 操作数据库 - 原路返回数据 - 前端渲染。1.2 前后端技术栈详解后端Spring Boot 3.x MyBatis-Plus MySQLSpring Boot 3.x作为项目骨架它提供了自动配置、内嵌 Web 服务器和强大的 Starter 依赖管理让我们能快速搭建可独立运行的 RESTful API 服务。选择 3.x 版本是为了使用最新的 Java 特性和性能改进。MyBatis-Plus一个强大的 MyBatis 增强工具。它提供了通用的 Mapper 和 Service 实现通过极少的代码即可实现单表 CRUD 操作极大提高了开发效率。对于毕业设计或快速原型它能节省大量时间。MySQL 8.0最流行的开源关系型数据库之一。我们将用它来持久化用户、菜品、订单等结构化数据。前端Vue 3 Vite Element Plus AxiosVue 3采用 Composition API逻辑组织更灵活尤其适合复杂组件。它是当前前端开发的主流选择。Vite下一代前端构建工具启动速度和热更新远超 Webpack开发体验极佳。Element Plus基于 Vue 3 的桌面端组件库提供了丰富的、美观的 UI 组件按钮、表单、表格、弹窗等让我们能快速搭建出专业的后台管理界面。Axios基于 Promise 的 HTTP 客户端用于前端向后端发起 API 请求并处理响应。为什么选择这个组合这个组合在社区活跃度、学习资源、开发效率之间取得了很好的平衡。Spring Boot 和 Vue 3 都是各自领域最热门的技术意味着你遇到的问题很容易找到解决方案。MyBatis-Plus 和 Element Plus 则分别解决了后端重复 CRUD 代码和前端基础组件的问题让我们能更专注于业务逻辑本身。2. 后端工程Spring Boot 项目搭建与核心配置我们从后端开始因为 API 是前后端通信的契约。先定义好数据结构和接口前端开发才有据可依。2.1 初始化项目与依赖管理使用 Spring Initializr 或 IDE如 IntelliJ IDEA创建项目。关键依赖选择如下Spring Web用于构建 RESTful API。MySQL Driver连接 MySQL 数据库。MyBatis Framework基础的 MyBatis 支持。Lombok通过注解自动生成 Getter/Setter 等方法减少样板代码。创建完成后在pom.xml中手动加入 MyBatis-Plus 的 Starter 依赖这是提升效率的关键。!-- pom.xml 中新增依赖 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version !-- 请使用与Spring Boot 3.x兼容的最新版本 -- /dependency注意MyBatis-Plus 版本需要与 Spring Boot 版本兼容。Spring Boot 3.x 建议使用 MyBatis-Plus 3.5.x 及以上版本否则可能出现启动类扫描等问题。2.2 数据库设计与连接配置首先在 MySQL 中创建数据库例如food_order_db。然后设计核心表这里以dish菜品表为例CREATE TABLE dish ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(100) NOT NULL COMMENT 菜品名称, category_id bigint NOT NULL COMMENT 分类id, price decimal(10,2) NOT NULL COMMENT 价格, image varchar(500) DEFAULT COMMENT 图片url, description varchar(500) DEFAULT COMMENT 描述信息, status int DEFAULT 1 COMMENT 状态 1:在售, 0:停售, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品表;接下来在application.yml中配置数据库连接和 MyBatis-Plus# src/main/resources/application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/food_order_db?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password # 替换为你的数据库密码 # MyBatis-Plus 配置 mybatis-plus: configuration: # 下划线转驼峰映射 map-underscore-to-camel-case: true # 控制台打印SQL开发环境开启生产环境关闭 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 全局逻辑删除字段名和值如果使用逻辑删除 logic-delete-field: is_deleted logic-delete-value: 1 logic-not-delete-value: 0关键配置解释map-underscore-to-camel-case: true自动将数据库的update_time字段映射到实体类的updateTime属性这是必须的。log-impl在开发阶段开启可以在控制台看到 MyBatis-Plus 生成的 SQL 语句便于调试。2.3 实体类、Mapper 与 Service 快速开发利用 MyBatis-Plus我们可以用极少的代码完成数据层操作。首先创建实体类Dish// src/main/java/com/yourdomain/food/entity/Dish.java package com.yourdomain.food.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; Data TableName(dish) // 指定对应表名 public class Dish { TableId(type IdType.AUTO) // 主键自增 private Long id; private String name; private Long categoryId; private BigDecimal price; private String image; private String description; private Integer status; // 1在售0停售 TableField(fill FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; }然后创建 Mapper 接口它只需要继承 MyBatis-Plus 提供的BaseMapper// src/main/java/com/yourdomain/food/mapper/DishMapper.java package com.yourdomain.food.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.yourdomain.food.entity.Dish; import org.apache.ibatis.annotations.Mapper; Mapper // 或是在启动类上使用 MapperScan public interface DishMapper extends BaseMapperDish { // 无需编写任何方法BaseMapper已提供了基础的CRUD方法 }最后创建 Service 接口及其实现类。同样我们可以继承 MyBatis-Plus 的IService和ServiceImpl// src/main/java/com/yourdomain/food/service/DishService.java package com.yourdomain.food.service; import com.baomidou.mybatisplus.extension.service.IService; import com.yourdomain.food.entity.Dish; public interface DishService extends IServiceDish { // 可以在此定义复杂的业务方法 } // src/main/java/com/yourdomain/food/service/impl/DishServiceImpl.java package com.yourdomain.food.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yourdomain.food.entity.Dish; import com.yourdomain.food.mapper.DishMapper; import com.yourdomain.food.service.DishService; import org.springframework.stereotype.Service; Service public class DishServiceImpl extends ServiceImplDishMapper, Dish implements DishService { // 复杂的业务逻辑可以在这里实现 }至此对于Dish表的增删改查save,removeById,updateById,getById,list等功能已经全部就绪无需编写任何 SQL。这是 MyBatis-Plus 的核心便利之处。2.4 控制器与 RESTful API 设计现在我们创建 Controller 来暴露 API。遵循 RESTful 风格设计/dish相关的接口。// src/main/java/com/yourdomain/food/controller/DishController.java package com.yourdomain.food.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.yourdomain.food.common.R; import com.yourdomain.food.entity.Dish; import com.yourdomain.food.service.DishService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; Slf4j RestController RequestMapping(/dish) public class DishController { Autowired private DishService dishService; // 新增菜品 PostMapping public RString save(RequestBody Dish dish) { log.info(新增菜品{}, dish); dishService.save(dish); return R.success(新增菜品成功); } // 分页查询菜品 GetMapping(/page) public RPageDish page(int page, int pageSize, String name) { log.info(分页查询页码{}, 大小{}, 名称{}, page, pageSize, name); PageDish pageInfo new Page(page, pageSize); LambdaQueryWrapperDish queryWrapper new LambdaQueryWrapper(); // 如果传入了name则进行模糊查询 queryWrapper.like(name ! null, Dish::getName, name); // 按更新时间倒序 queryWrapper.orderByDesc(Dish::getUpdateTime); dishService.page(pageInfo, queryWrapper); return R.success(pageInfo); } // 根据ID查询菜品用于回显 GetMapping(/{id}) public RDish getById(PathVariable Long id) { Dish dish dishService.getById(id); return dish ! null ? R.success(dish) : R.error(未找到该菜品); } // 修改菜品 PutMapping public RString update(RequestBody Dish dish) { log.info(修改菜品{}, dish); dishService.updateById(dish); return R.success(修改菜品成功); } // 删除菜品逻辑删除或物理删除 DeleteMapping public RString delete(RequestParam(ids) Long id) { log.info(删除菜品id{}, id); dishService.removeById(id); return R.success(删除菜品成功); } }这里用到了一个统一的响应包装类R这是一种常见的实践用于规范 API 返回格式。// src/main/java/com/yourdomain/food/common/R.java package com.yourdomain.food.common; import lombok.Data; import java.io.Serializable; Data public class RT implements Serializable { private Integer code; // 编码1成功0和其他数字失败 private String msg; // 错误信息 private T data; // 数据 public static T RT success(T object) { RT r new RT(); r.data object; r.code 1; r.msg success; return r; } public static T RT error(String msg) { RT r new RT(); r.msg msg; r.code 0; return r; } }为什么这样设计R类统一了成功和失败的响应格式前端可以固定地从code判断成功与否从data取数据从msg取提示信息。Controller 方法返回RT使得 API 风格一致易于前端处理。使用RequestBody接收 JSON 格式的请求体PathVariable获取路径参数RequestParam获取查询参数这是标准的 RESTful 做法。2.5 解决跨域问题在前后端分离开发时前端运行在localhost:5173Vite 默认端口后端运行在localhost:8080浏览器会因为同源策略而阻止请求。我们需要在后端配置 CORS。// src/main/java/com/yourdomain/food/config/WebMvcConfig.java package com.yourdomain.food.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) // 对所有接口生效 .allowedOriginPatterns(*) // 允许所有来源生产环境应指定具体域名 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) // 允许的HTTP方法 .allowedHeaders(*) // 允许所有请求头 .allowCredentials(true) // 允许携带Cookie等凭证 .maxAge(3600); // 预检请求的缓存时间秒 } }注意allowedOriginPatterns(*)在生产环境中是极不安全的应替换为具体的前端域名如http://your-frontend-domain.com。开发环境为了方便可以暂时如此配置。启动 Spring Boot 应用访问http://localhost:8080/dish/page?page1pageSize10应该能看到返回的分页数据 JSON 结构。后端 API 部分就基本完成了。3. 前端工程Vue 3 Element Plus 项目搭建与页面开发后端 API 就绪后我们开始构建用户交互界面。前端项目将使用 Vite 快速搭建。3.1 初始化 Vue 3 项目并安装核心依赖使用以下命令创建项目并安装必要依赖# 使用 npm 创建 Vite 项目选择 Vue 模板 npm create vitelatest food-order-frontend -- --template vue cd food-order-frontend # 安装 Element Plus 和 Axios npm install element-plus axios # 安装 Element Plus 图标库可选但推荐 npm install element-plus/icons-vue # 安装路由和状态管理本项目简单可先不装复杂项目需要 # npm install vue-router4 pinia安装完成后需要在main.js或main.ts中全局引入 Element Plus 和 Axios。// src/main.js import { createApp } from vue import ./style.css import App from ./App.vue // 引入 Element Plus 及其样式 import ElementPlus from element-plus import element-plus/dist/index.css // 引入 Axios 并设置为全局属性或使用 provide/inject import axios from axios const app createApp(App) // 将 axios 挂载到全局方便在组件中使用 this.$axios app.config.globalProperties.$axios axios // 可以在这里配置 axios 的默认 baseURL axios.defaults.baseURL http://localhost:8080 app.use(ElementPlus) app.mount(#app)3.2 菜品管理页面开发我们创建一个菜品管理的页面包含查询、新增、编辑、删除功能。首先在src/views目录下创建DishManagement.vue。!-- src/views/DishManagement.vue -- template div classdish-management !-- 搜索和新增区域 -- el-row :gutter20 classmb-4 el-col :span6 el-input v-modelsearchName placeholder请输入菜品名称 clearable clearhandleSearch keyup.enterhandleSearch template #append el-button :iconSearch clickhandleSearch / /template /el-input /el-col el-col :span18 classtext-right el-button typeprimary :iconPlus clickhandleAdd新增菜品/el-button /el-col /el-row !-- 菜品表格 -- el-table :datatableData border stripe stylewidth: 100% el-table-column propid labelID width80 / el-table-column propname label菜品名称 / el-table-column propcategoryId label分类ID width100 / el-table-column propprice label价格(元) width120 template #defaultscope {{ scope.row.price.toFixed(2) }} /template /el-table-column el-table-column propstatus label状态 width100 template #defaultscope el-tag :typescope.row.status 1 ? success : info {{ scope.row.status 1 ? 在售 : 停售 }} /el-tag /template /el-table-column el-table-column propupdateTime label更新时间 width180 / el-table-column label操作 width200 fixedright template #defaultscope el-button sizesmall :iconEdit clickhandleEdit(scope.row)编辑/el-button el-button sizesmall typedanger :iconDelete clickhandleDelete(scope.row)删除/el-button /template /el-table-column /el-table !-- 分页组件 -- el-pagination classmt-4 v-model:current-pagecurrentPage v-model:page-sizepageSize :page-sizes[5, 10, 20, 50] :totaltotal layouttotal, sizes, prev, pager, next, jumper size-changehandleSizeChange current-changehandleCurrentChange / !-- 新增/编辑菜品对话框 -- el-dialog v-modeldialogVisible :titledialogTitle width500px el-form :modelform :rulesrules refformRef label-width80px el-form-item label菜品名称 propname el-input v-modelform.name placeholder请输入菜品名称 / /el-form-item el-form-item label分类 propcategoryId el-select v-modelform.categoryId placeholder请选择分类 stylewidth: 100% el-option v-forcat in categoryOptions :keycat.id :labelcat.name :valuecat.id / /el-select /el-form-item el-form-item label价格 propprice el-input-number v-modelform.price :min0 :precision2 :step0.1 stylewidth: 100% / /el-form-item el-form-item label图片 !-- 这里可以集成上传组件暂时用输入框 -- el-input v-modelform.image placeholder请输入图片URL / /el-form-item el-form-item label描述 el-input v-modelform.description typetextarea :rows3 placeholder请输入菜品描述 / /el-form-item el-form-item label状态 el-radio-group v-modelform.status el-radio :label1在售/el-radio el-radio :label0停售/el-radio /el-radio-group /el-form-item /el-form template #footer span classdialog-footer el-button clickdialogVisible false取消/el-button el-button typeprimary clicksubmitForm确定/el-button /span /template /el-dialog /div /template script setup import { ref, reactive, onMounted } from vue import { Search, Plus, Edit, Delete } from element-plus/icons-vue import { ElMessage, ElMessageBox } from element-plus // 表格数据 const tableData ref([]) const searchName ref() const currentPage ref(1) const pageSize ref(10) const total ref(0) // 对话框相关 const dialogVisible ref(false) const dialogTitle ref() const formRef ref() const form reactive({ id: null, name: , categoryId: null, price: 0, image: , description: , status: 1 }) // 表单验证规则 const rules reactive({ name: [{ required: true, message: 请输入菜品名称, trigger: blur }], categoryId: [{ required: true, message: 请选择分类, trigger: change }], price: [{ required: true, message: 请输入价格, trigger: blur }] }) // 分类下拉选项模拟数据实际应从后端获取 const categoryOptions ref([ { id: 1, name: 热菜 }, { id: 2, name: 凉菜 }, { id: 3, name: 汤羹 }, { id: 4, name: 主食 } ]) // 获取菜品列表 const fetchDishList async () { try { const params { page: currentPage.value, pageSize: pageSize.value, name: searchName.value } // 使用全局挂载的 axios const response await window.$axios.get(/dish/page, { params }) if (response.data.code 1) { tableData.value response.data.data.records total.value response.data.data.total } else { ElMessage.error(response.data.msg || 获取列表失败) } } catch (error) { console.error(获取菜品列表失败:, error) ElMessage.error(网络请求失败) } } // 搜索 const handleSearch () { currentPage.value 1 fetchDishList() } // 分页大小变化 const handleSizeChange (val) { pageSize.value val fetchDishList() } // 页码变化 const handleCurrentChange (val) { currentPage.value val fetchDishList() } // 新增菜品 const handleAdd () { dialogTitle.value 新增菜品 // 重置表单 Object.keys(form).forEach(key { if (key status) { form[key] 1 } else if (key price) { form[key] 0 } else { form[key] } }) form.id null dialogVisible.value true } // 编辑菜品 const handleEdit (row) { dialogTitle.value 编辑菜品 // 将行数据复制到表单 Object.assign(form, row) dialogVisible.value true } // 删除菜品 const handleDelete (row) { ElMessageBox.confirm(确认删除菜品“${row.name}”吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(async () { try { const response await window.$axios.delete(/dish, { params: { ids: row.id } }) if (response.data.code 1) { ElMessage.success(删除成功) fetchDishList() // 刷新列表 } else { ElMessage.error(response.data.msg || 删除失败) } } catch (error) { console.error(删除失败:, error) ElMessage.error(删除请求失败) } }).catch(() { // 用户取消 }) } // 提交表单新增/编辑 const submitForm async () { // 表单验证 await formRef.value.validate((valid) { if (!valid) { ElMessage.error(请检查表单填写是否正确) return } }) const api form.id ? window.$axios.put : window.$axios.post const url form.id ? /dish : /dish try { const response await api(url, form) if (response.data.code 1) { ElMessage.success(form.id ? 修改成功 : 新增成功) dialogVisible.value false fetchDishList() // 刷新列表 } else { ElMessage.error(response.data.msg || 操作失败) } } catch (error) { console.error(提交失败:, error) ElMessage.error(网络请求失败) } } // 组件挂载时加载数据 onMounted(() { fetchDishList() }) /script style scoped .dish-management { padding: 20px; } .mb-4 { margin-bottom: 1rem; } .text-right { text-align: right; } .mt-4 { margin-top: 1rem; } /style这个组件实现了菜品管理的核心功能。关键点在于数据绑定使用ref和reactive管理响应式数据。API 调用通过全局挂载的$axios调用后端接口。Element Plus 组件使用了el-table,el-pagination,el-dialog,el-form等组件快速搭建界面。表单验证通过rules定义验证规则并在提交前校验。交互反馈使用ElMessage和ElMessageBox给用户操作反馈。3.3 配置路由与主页面为了让页面能访问需要配置路由。首先安装vue-routernpm install vue-router4创建路由配置文件// src/router/index.js import { createRouter, createWebHistory } from vue-router import DishManagement from ../views/DishManagement.vue const routes [ { path: /, redirect: /dish // 默认重定向到菜品管理页 }, { path: /dish, name: DishManagement, component: DishManagement } // 后续可以在这里添加其他路由如 /user, /order, /cart ] const router createRouter({ history: createWebHistory(), routes }) export default router修改main.js使用路由// src/main.js (更新部分) import { createApp } from vue import ./style.css import App from ./App.vue import ElementPlus from element-plus import element-plus/dist/index.css import axios from axios import router from ./router // 导入路由 const app createApp(App) app.config.globalProperties.$axios axios axios.defaults.baseURL http://localhost:8080 app.use(ElementPlus) app.use(router) // 使用路由 app.mount(#app)最后修改App.vue提供一个基本的布局容器!-- src/App.vue -- template div idapp el-container el-header stylebackground-color: #409EFF; color: white; line-height: 60px; text-align: center; h1美食点餐管理系统/h1 /el-header el-container el-aside width200px stylebackground-color: #e9eef3; min-height: calc(100vh - 60px); el-menu router default-active/dish styleborder-right: none; el-menu-item index/dish el-iconMenu //el-icon span菜品管理/span /el-menu-item !-- 后续可以添加其他菜单项 -- !-- el-menu-item index/order el-iconDocument //el-icon span订单管理/span /el-menu-item -- /el-menu /el-aside el-main router-view / !-- 这里是页面内容渲染的地方 -- /el-main /el-container /el-container /div /template script setup import { Menu } from element-plus/icons-vue /script style #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { margin: 0; } /style现在运行前端项目npm run dev访问http://localhost:5173你应该能看到带有侧边栏的页面点击“菜品管理”即可进入我们刚刚开发的页面并与后端进行数据交互。4. 联调、部署与常见问题排查前后端分别开发完成后联调是验证系统能否正常工作的关键步骤。4.1 前后端联调要点启动顺序先启动后端 Spring Boot 应用端口 8080再启动前端 Vite 开发服务器端口 5173。检查网络请求打开浏览器开发者工具的“网络”(Network) 标签页。在前端页面进行操作如点击查询观察是否有请求发出请求的 URL、方法、参数是否正确响应状态码和返回数据是否符合预期。处理跨域如果遇到CORS错误确认后端WebMvcConfig配置已生效且前端axios的baseURL配置正确。数据格式确保前端axios发送的请求体如POST/PUT是 JSON 格式axios默认即是后端使用RequestBody接收。URL 参数使用params对象传递。4.2 常见问题与解决方案在开发过程中你几乎一定会遇到以下问题。这里提供排查思路。问题现象可能原因检查方式解决方案前端页面空白控制台报错Failed to resolve import ...依赖未安装或路径错误检查终端是否有npm install报错检查import语句路径重新运行npm install检查node_modules是否存在修正import路径前端请求后端接口报4041. 后端服务未启动2. 请求 URL 错误3. 后端 Controller 路径不匹配1. 确认后端控制台是否启动成功2. 核对浏览器 Network 中的请求 URL 与后端RequestMapping路径3. 检查是否有拼写错误1. 启动后端服务2. 修正前端axios的baseURL或请求路径3. 检查后端 Controller 类和方法上的注解前端请求报CORS错误后端未配置跨域或配置不正确查看浏览器控制台错误信息确认是CORS错误检查并确保后端的WebMvcConfig配置类被扫描到且allowedOriginPatterns包含了前端地址如http://localhost:5173后端控制台报Field xxx doesnt have a default value实体类属性与数据库表字段不匹配或插入时未给非空字段赋值1. 检查实体类TableField注解或数据库表结构2. 查看 MyBatis-Plus 打印的 SQL 语句确认插入的字段1. 确保实体类属性名与数据库字段名映射正确开启下划线转驼峰2. 在插入前为实体类的非空属性赋值修改数据后前端列表未刷新前端在操作成功后未重新调用查询接口查看前端代码在新增、编辑、删除的成功回调中是否调用了fetchDishList()在操作成功的回调函数中调用刷新列表的方法Element Plus 组件样式丢失未正确引入 Element Plus 的 CSS 文件检查main.js中是否导入了element-plus/dist/index.css确保在main.js中正确引入样式文件4.3 项目打包与简易部署前端打包npm run build该命令会在项目根目录生成一个dist文件夹里面是静态文件HTML, JS, CSS。你可以将这个文件夹放到任何静态文件服务器如 Nginx上。后端打包使用 Maven 打包为可执行的 JAR 文件。# 在 Spring Boot 项目根目录下 mvn clean package打包成功后在target目录下会生成your-project-name-0.0.1-SNAPSHOT.jar。你可以使用以下命令运行java -jar target/your-project-name-0.0.1-SNAPSHOT.jar生产环境部署注意事项数据库将application.yml中的数据库连接信息改为生产环境的地址、用户名和密码。切勿将密码硬编码在代码中应使用环境变量或配置中心。CORS将WebMvcConfig中的allowedOriginPatterns(*)改为具体的前端域名。日志关闭 MyBatis-Plus 的 SQL 日志log-impl设置为空或Slf4jImpl并配置logback-spring.xml将日志输出到文件。静态资源可以将前端dist目录下的内容复制到 Spring Boot 项目的src/main/resources/static目录下然后打包。这样后端 JAR 包就同时提供了 API 和前端页面部署更简单。但这种方式前后端耦合适用于小型项目。4.4 扩展方向与最佳实践建议完成基础功能后这个项目还有很大的完善空间这也是你简历上的亮点用户认证与授权集成 Spring Security 或 JWT实现登录、权限控制如管理员和普通用户。文件上传实现菜品图片上传功能可以使用本地存储或云存储如阿里云 OSS、七牛云。购物车与订单实现完整的购物车逻辑本地存储或服务端存储和订单创建、支付模拟、状态流转流程。数据校验在后端使用Valid注解进行更严格的数据校验避免非法数据入库。全局异常处理创建全局异常处理器ControllerAdvice统一处理业务异常和系统异常返回友好的错误信息。API 文档集成 Swagger 或 Knife4j自动生成 API 文档方便前后端协作。前端状态管理随着组件增多考虑使用 Pinia 来管理全局状态如用户信息。代码优化将前端的 API 请求封装成独立的 Service 层提高代码复用性和可维护性。这个项目从设计到实现覆盖了现代 Web 应用开发的主要环节。理解每个环节为什么这样做比单纯复制代码更重要。当你能够独立解决上述扩展点遇到的问题时你对 Spring Boot 和 Vue 3 的全栈开发就算真正入门了。接下来你可以尝试为这个系统加入搜索、推荐、数据统计等更复杂的功能让它成为一个真正拿得出手的个人作品。