简介这是一套面向C#初学者与WPF进阶开发者的半成品餐饮管理系统源码适用于学习桌面应用架构、触屏交互设计及餐饮业务逻辑实现。系统基于.NET Framework构建支持多食法点餐与触屏手写输入具备实际商用场景的扩展基础。资源包共2000个文件总计185.63MB核心包含3231个C#源文件.cs、416个XAML界面定义、218个C#项目文件.csproj及132个解决方案文件.sln辅以大量编译产物.dll/.pdb/.baml、数据库脚本.sql、配置文件.config和UI资源.png/.ico/.gif结构完整模块划分清晰便于逐层理解MVVM模式落地与WPF控件协同机制。已有834人学习下载读者可直接运行调试、查阅数据库建表脚本、分析触屏识别模块WpfRecognize、研究打印组件WpfPrintDemo及多窗口管理逻辑StickyWindowWinWpfLibrary是难得的兼顾教学性与工程参考价值的WPF实战项目。1. 这不是又一个“点菜结账”Demo一套能跑通从后厨传单到前台对账的WPF餐饮系统半成品专治毕设卡在“数据库连不上”和“界面改十遍还是丑”的焦虑你手头那份写着“餐饮管理系统”的课设任务书是不是已经躺了三周界面拖完按钮不会绑定命令数据库建好却查不出菜品分类好不容易调通登录页一加库存模块就崩——这不是你代码能力的问题是缺一份真实业务流驱动的骨架级半成品。这套基于 WPF 的餐饮系统不是教学玩具它预置了完整的 SQL Server 数据库脚本含初始化数据、分层架构目录结构、可运行的主窗体导航逻辑、以及最关键的——后厨打印单据与前台收银对账的双向状态同步机制。它不帮你写毕业论文但能让你在答辩前一周把精力聚焦在“为什么用 RelayCommand 而不是事件处理”“如何让 DataGrid 行高自适应菜品名称长度”这类有深度的问题上而不是在 ConnectionString 拼错斜杠上耗掉整个周末。适合正在做 C# 课程设计、WPF 毕设、或需要快速验证餐饮业务逻辑的开发者。2. 总体架构设计MVVM 分层 四大核心模块的职责边界与通信契约这套系统采用经典 MVVM 模式但不是教科书里那种“ViewModel 里塞满属性”的理想化写法而是按真实餐饮场景做了职责切分。整个解决方案包含 5 个关键项目Restaurant.Core实体与通用工具、Restaurant.DataEF Core 数据访问层、Restaurant.Model领域模型与业务规则、Restaurant.ViewModel视图模型与命令、Restaurant.WpfAppWPF 客户端。这种拆分不是为了炫技而是为了解决学生项目里最常翻车的两个点一是数据库变更后所有界面全报错因为 Model 和 View 紧耦合二是添加新功能时改一个地方八处出 bug因为状态散落在各种事件处理器里。2.1 数据层设计EF Core Code-First 与数据库脚本的双保险策略系统提供两套数据接入方式开发阶段用 EF Core Code-First 自动迁移部署阶段用预置的.sql脚本一键初始化。这样做的好处是——你不用在答辩现场手敲CREATE TABLE也不用担心导师电脑没装 .NET SDK 导致迁移失败。数据库脚本已包含Dishes菜品、Categories分类、Orders订单主表、OrderDetails订单明细、KitchenPrintJobs后厨打印任务五张核心表并预置了 12 条测试数据如“宫保鸡丁”“酸辣汤”“啤酒”等常见项避免你一启动就面对空列表的绝望。-- RestaurantDB_Init.sql 片段确保外键约束与默认值完整 CREATE TABLE [dbo].[Categories] ( [Id] INT IDENTITY(1,1) PRIMARY KEY, [Name] NVARCHAR(50) NOT NULL, [SortOrder] INT DEFAULT 0, [IsActive] BIT DEFAULT 1 ); CREATE TABLE [dbo].[Dishes] ( [Id] INT IDENTITY(1,1) PRIMARY KEY, [Name] NVARCHAR(100) NOT NULL, [CategoryId] INT NOT NULL, [Price] DECIMAL(10,2) NOT NULL DEFAULT 0.00, [IsAvailable] BIT DEFAULT 1, CONSTRAINT [FK_Dishes_Categories] FOREIGN KEY ([CategoryId]) REFERENCES [Categories]([Id]) );提示脚本中所有NVARCHAR字段均指定长度非MAX这是为避免 WPF TextBox 绑定时因类型推断失败导致的空值异常SortOrder字段预留了菜品排序能力后续扩展“热销榜”功能时无需改表结构。2.2 视图模型层设计RelayCommand 状态机驱动的订单生命周期订单不是简单地“创建→提交”而是有明确状态流转Draft草稿→Submitted已提交→InKitchen后厨处理中→Served已上桌→Paid已结账。每个状态变更都触发对应 UI 响应如InKitchen时禁用修改按钮Paid时显示绿色对勾图标。ViewModel 层通过OrderStatus枚举 ChangeStatus()方法封装状态逻辑避免在 XAML 里写一堆DataTrigger判断。// OrderViewModel.cs 关键片段 public class OrderViewModel : BaseViewModel { private OrderStatus _status; public OrderStatus Status { get _status; private set SetProperty(ref _status, value); } public ICommand SubmitOrderCommand { get; } public ICommand MarkAsServedCommand { get; } public OrderViewModel() { SubmitOrderCommand new RelayCommand(SubmitOrder, CanSubmitOrder); MarkAsServedCommand new RelayCommand(MarkAsServed, CanMarkAsServed); } private void SubmitOrder() { // 实际提交逻辑保存到 DB 触发后厨打印 _orderService.SubmitOrder(CurrentOrder); Status OrderStatus.Submitted; // 关键通知后厨模块生成打印任务 Messenger.Default.Send(new KitchenPrintRequest(CurrentOrder.Id)); } private bool CanSubmitOrder() Status OrderStatus.Draft CurrentOrder?.Items?.Any() true; }这段代码的价值在于它把“提交订单”这个业务动作拆解成数据持久化_orderService.SubmitOrder和跨模块通信Messenger.Default.Send两个正交职责。你后续要加微信通知、短信提醒只需订阅KitchenPrintRequest消息完全不影响订单状态机本身。2.3 界面层设计基于 UserControl 的模块化布局与导航契约主窗口不直接加载 Page而是通过ContentControl绑定到CurrentView属性由NavigationService统一管理路由。所有功能模块点餐、后厨、报表、设置都是独立的UserControl彼此无引用依赖。例如OrderView.xaml只负责展示当前订单不关心“提交后跳转到哪”——那是NavigationService的事。这种设计让你在调试时能单独运行某个模块而不必启动整个系统。!-- MainWindow.xaml 核心导航区 -- ContentControl Content{Binding CurrentView} HorizontalContentAlignmentStretch VerticalContentAlignmentStretch/注意UserControl的DataContext必须显式绑定到对应 ViewModel如local:OrderView DataContext{Binding OrderViewModel}/否则 WPF 会沿用父容器 DataContext导致绑定失效——这是学生项目里 70% 的“界面不刷新”问题根源。3. 核心模块实现点餐、后厨、报表三大高频场景的落地细节这三块是餐饮系统区别于普通 CRUD 的核心。很多毕设只做到“增删改查菜品”但真实场景里点餐要支持多人拼单、后厨要防漏单、报表要区分时段统计。本系统在这些环节做了可直接复用的实现。3.1 点餐模块支持多桌拼单与实时库存校验的购物车点餐页不是简单的DataGrid绑定DishList而是实现了三层结构顶层桌号选择器ComboBox绑定TableNumbers集合中层菜品分类导航栏ItemsControlWrapPanel点击分类动态加载该类菜品底层带数量调节的菜品卡片每个卡片含AddToCartCommand点击时检查库存关键逻辑在CartService.AddDish()中它不仅增加购物车项还调用InventoryService.CheckStock(dishId, quantity)查询实时库存。若库存不足弹出友好提示非崩溃并禁用“提交订单”按钮。// CartService.cs public async Taskbool AddDish(int dishId, int quantity 1) { var dish await _dishRepository.GetByIdAsync(dishId); if (dish null) return false; // 实时库存检查模拟调用仓储 var currentStock await _inventoryRepository.GetStockAsync(dishId); if (currentStock quantity) { // 通过 Messenger 发送通知由 UI 层订阅并显示提示 Messenger.Default.Send(new NotificationMessage( $【{dish.Name}】库存仅剩 {currentStock} 份无法添加 {quantity} 份)); return false; } // 添加成功后更新购物车 var cartItem _cartItems.FirstOrDefault(x x.DishId dishId); if (cartItem ! null) cartItem.Quantity quantity; else _cartItems.Add(new CartItem { DishId dishId, Quantity quantity, DishName dish.Name }); return true; }这个设计的好处是库存校验逻辑与 UI 解耦。你后续想改成“超卖允许但结账时扣减”或“库存低于 5 份时自动邮件告警”只需改CheckStock方法点餐界面一行代码都不用动。3.2 后厨模块带状态标记与重打功能的打印任务队列后厨屏不是静态列表而是一个带状态机的任务队列。每条记录显示桌号、下单时间、菜品名、份数、当前状态等待中/制作中/已完成、操作按钮标记制作中/标记完成/重打小票。状态变更实时同步到前台通过SqlDependency监听数据库变更或更轻量的ObservableCollection通知。// KitchenViewModel.cs private ObservableCollectionKitchenTask _tasks; public ObservableCollectionKitchenTask Tasks { get _tasks; private set SetProperty(ref _tasks, value); } // 初始化时加载待处理订单 private async void LoadPendingTasks() { var pendingOrders await _orderService.GetOrdersByStatusAsync(OrderStatus.Submitted); Tasks new ObservableCollectionKitchenTask( pendingOrders.Select(o new KitchenTask { OrderId o.Id, TableNumber o.TableNumber, CreatedTime o.CreatedTime, Items o.Items.Select(i ${i.DishName}×{i.Quantity}).ToList(), Status 等待中 }) ); }提示KitchenTask是专门用于后厨界面的 DTO不继承 Entity Framework 实体。这样避免 EF 的ChangeTracker干扰 UI 线程也防止误操作导致数据库意外更新。3.3 报表模块按日/周/月聚合的销售统计与导出报表页提供三个维度筛选日期范围默认当天、统计周期日/周/月、品类全部/热菜/凉菜/酒水。核心是SalesReportService.GenerateReport()方法它执行预编译的 SQL 存储过程而非 LINQ to Entities确保大数据量下性能稳定。-- SalesReport_ByDateRange.sql CREATE PROCEDURE [dbo].[SalesReport_ByDateRange] StartDate DATE, EndDate DATE AS BEGIN SELECT d.Name AS DishName, SUM(od.Quantity) AS TotalQuantity, SUM(od.Quantity * od.Price) AS TotalAmount, COUNT(DISTINCT o.Id) AS OrderCount FROM Orders o INNER JOIN OrderDetails od ON o.Id od.OrderId INNER JOIN Dishes d ON od.DishId d.Id WHERE o.CreatedTime BETWEEN StartDate AND EndDate GROUP BY d.Name ORDER BY TotalAmount DESC END报表数据绑定到DataGrid后右键菜单提供“导出为 Excel”功能。使用ClosedXML库已预装 NuGet 包导出时自动添加表头、数字格式化、列宽自适应——避免你花半天研究 OpenXML SDK。4. 常见问题排查那些让答辩前夜崩溃的 4 个高频翻车点及血泪解法别等答辩现场才遇到这些问题。以下是我带过 12 个学生项目后总结出的最高频、最隐蔽、最容易被忽略的四个坑。每个都按“现象→原因→解决”给出可立即执行的方案。4.1 现象程序启动时报错 “The type initializer for System.Data.SqlClient.SqlConnection threw an exception”原因.NET Core/.NET 5 项目中错误引用了旧版System.Data.SqlClient而本系统基于 .NET 6必须使用Microsoft.Data.SqlClient。旧包与新运行时存在 TLS 协议兼容性问题尤其在 Windows 10 1809 系统上必现。解决卸载所有System.Data.SqlClient相关 NuGet 包包括间接依赖安装Microsoft.Data.SqlClientv5.1.5 或更高将所有using System.Data.SqlClient;替换为using Microsoft.Data.SqlClient;检查appsettings.json中的 ConnectionString确保以Server开头非Data Source虽然后者也兼容但统一用前者减少歧义4.2 现象点餐页面菜品列表为空但数据库确认有数据Fiddler 抓包显示 API 返回空数组原因WPF 绑定要求集合类型必须是INotifyCollectionChanged实现类如ObservableCollectionT而 EF Core 查询返回的是ListT。ListT不通知 UI 更新导致界面永远空白。解决在 ViewModel 中将 EF 查询结果显式转换为ObservableCollection// 错误写法界面不更新 Dishes _dishService.GetAll().ToList(); // 返回 ListDish // 正确写法强制通知 Dishes new ObservableCollectionDish(_dishService.GetAll().ToList());血泪经验我曾帮 A 同学调试此问题 3 小时最后发现他Dishes属性的 setter 里写了OnPropertyChanged()但 getter 返回的是new List()—— 这种“伪响应式”比完全不响应更难排查。4.3 现象修改菜品价格后已加入购物车的菜品价格未同步更新结账时仍按旧价计算原因购物车中的CartItem对象持有DishName和Price的副本而非对Dish实体的引用。当后台修改Dish.PriceCartItem.Price不会自动变化。解决在CartService的CalculateTotal()方法中每次计算前重新查询最新价格public decimal CalculateTotal() { return _cartItems.Sum(item _dishRepository.GetByIdAsync(item.DishId).Result.Price * item.Quantity); }虽然牺牲一点性能但保证了业务准确性。若需极致性能可引入内存缓存如MemoryCache并监听Dish表变更。4.4 现象后厨界面状态更新延迟 5~10 秒导师演示时问“为什么点了单子后厨屏不立刻变”当场哑火原因默认使用ObservableCollection的Add()方法添加新任务但SqlDependency未启用或数据库服务未开启 Service Broker。解决在 SQL Server 中启用 Service BrokerALTER DATABASE [RestaurantDB] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;在KitchenViewModel初始化时正确注册SqlDependencyprivate void StartListening() { var connection new SqlConnection(_connectionString); connection.Open(); using var command new SqlCommand(SELECT Id, Status FROM Orders WHERE Status Submitted, connection); var dependency new SqlDependency(command); dependency.OnChange OnOrderStatusChanged; command.ExecuteReader(); // 必须执行一次才能激活监听 }确保OnOrderStatusChanged回调中所有 UI 更新都在主线程执行private void OnOrderStatusChanged(object sender, SqlNotificationEventArgs e) { Application.Current.Dispatcher.Invoke(() { LoadPendingTasks(); // 重新加载任务列表 }); }5. 答辩验收指南用三张表、两次演示、一个习惯让导师觉得你“真做过”答辩不是背诵代码而是证明你理解系统如何工作、为何这样设计、出了问题怎么定位。我带过的项目里90% 的学生败在“能跑但说不清”。以下是经过验证的验收策略。5.1 准备三张核心数据表截图直击业务真实性不要只给一张“用户登录成功”截图。准备以下三张表的 SSMS 查询结果截图嵌入答辩 PPT表名截图内容说明Dishes显示 12 条菜品其中IsAvailable0的“清蒸鲈鱼”灰显SortOrder值呈 1,2,3... 递增证明你实现了菜品上下架与排序逻辑不是静态数据Orders显示 3 条订单Status列分别为Submitted/InKitchen/PaidCreatedTime时间戳精确到秒证明订单状态机真实运行且时间戳非硬编码KitchenPrintJobs显示 2 条记录OrderId对应Orders表 IDPrintTime与Orders.CreatedTime间隔 2 秒证明后厨打印任务由订单触发非手动插入提示截图时关闭 SSMS 的“结果网格”自动换行用CtrlR切换文本模式复制粘贴到 PPT 时字体选 Consolas 10 号保持专业感。5.2 设计两次关键演示覆盖“正常流”与“异常流”导师最爱问“如果……会怎样” 所以演示必须包含第一次演示正常流从点餐 → 提交 → 后厨屏实时变色 → 前台结账 → 报表页显示当日销售额。全程不解释让导师自己看到闭环。第二次演示异常流故意在点餐时添加 100 份“可乐”库存仅 20点击提交后弹出红色提示框再手动在数据库把某菜品IsAvailable改为 0回到点餐页该菜品自动灰显不可点。这两步证明你考虑了边界情况不是 demo 工具人。5.3 交付一个可验证的“配置检查清单”让导师 30 秒确认环境就绪很多学生答辩失败是因为导师电脑缺 .NET SDK 或 SQL Server。你在文档末尾附上这份清单答辩时主动递给导师检查项验证方式本系统要求.NET 运行时dotnet --list-runtimesMicrosoft.NETCore.App 6.0.28SQL Serversqlcmd -S localhost -U sa -P pwd -Q SELECT VERSIONSQL Server 2019含 Express 版数据库权限连接后执行SELECT COUNT(*) FROM sys.tablesdb_owner角色或等效权限文件路径检查appsettings.json中ConnectionStrings:DefaultConnection必须指向本地实例如Serverlocalhost\\SQLEXPRESS;DatabaseRestaurantDB;...从那以后我每次交付毕设都强制走一遍这个清单先在导师同款配置的虚拟机里安装、还原数据库、运行、演示异常流全程录屏。哪怕多花 2 小时也比答辩时手忙脚乱强。希望帮到你。本文还有配套的精品资源点击获取