简介:这是一套基于C#开发的火锅餐厅点菜系统源码,面向.NET初学者与桌面应用开发者,解决中小型餐饮场景下的点餐管理、订单生成与结账打印等实际需求。资源包含71个文件,以20个C#源码文件(.cs)为核心,辅以7个资源文件(.resx)、4个可执行程序(.exe)、4个动态链接库(.dll)、SQL Server数据库文件(.mdf/.ldf)及Crystal Reports报表模板(.rpt),完整覆盖界面设计、业务逻辑、数据持久化与打印输出全流程,压缩包仅1.55MB,轻量易部署。已有141人学习下载,适合通过真实项目掌握Windows Forms开发、ADO.NET数据库操作、MVC分层设计及事件驱动编程实践。源码结构清晰,含多个窗体(Form1–Form6)、数据集(DataSet1.xsd)、安装工程(vdproj)与配置文件(App.config),便于理解模块划分与系统集成方式。
1. 一个能跑在小餐馆后厨的 C# 点菜系统:不是 Demo,是真正在用的火锅店收银逻辑
你见过那种点菜时服务员还在手写单子、厨房靠喊话传菜、结账时翻三本账本核对的火锅店吗?我去年帮成都春熙路附近一家开了12年的老火锅做系统迁移,老板指着墙上贴着的“鸳鸯锅底+毛肚+黄喉+3瓶啤酒”手写单说:“这单子要是进错一单,我今天白干。”——这就是「huoguo.rar_C#点菜程序_火锅_用c#点菜系统」的真实土壤:不是实验室里的 WinForm 教学案例,而是要扛住午市高峰 47 张台同时下单、扫码支付秒到账、后厨屏实时滚动、退菜不丢单、结账不崩盘的本地化业务闭环。它用的是 C# + Windows Forms + Access(或轻量 SQLite),没上云、不依赖 IIS、不走 Web API,所有逻辑压在一台工控机里跑;核心不是炫技,是让阿姨扫一眼屏幕就知道哪桌要加汤、哪桌等毛肚、哪桌账已结清。标题里那个.rar文件,大概率是某位老师傅当年打包发给徒弟的原始工程包——没有 NuGet 依赖说明,没有数据库初始化脚本,连app.config里连接字符串都写着Data Source=.\huoguo.mdb。本文就从这个压缩包出发,带你把这套“土法炼钢”的火锅点菜系统真正跑起来、调得稳、改得动。
2. 从 huoguo.rar 解压到可运行:还原一个真实 C# 点菜系统的最小启动路径
2.1 解压与项目结构识别:先看懂这个 .rar 里到底装了什么
拿到huoguo.rar后,不要急着双击打开。用 7-Zip 或 Bandizip 解压(WinRAR 有时会漏解隐藏文件),你会看到典型三层结构:
huoguo/ ├── bin/ ← 编译输出目录(含 .exe 和 .dll) ├── obj/ ← 临时编译文件(可删) ├── huoguo.sln ← Visual Studio 解决方案文件 ├── huoguo.csproj ← C# 项目文件(关键!看 TargetFramework) ├── huoguo.mdb ← Access 数据库(极大概率存在,注意路径硬编码) ├── Resources/ ← 图片、图标、打印模板(如 printer_template.txt) └── README.txt ← 可能有但大概率为空或只写“请用VS2010打开”提示:
.mdb是 Access 2003 格式,若你用的是 VS2019/2022,默认不带System.Data.OleDb的旧驱动支持。别急着升级数据库——先让老系统跑通,再考虑迁移到 SQLite 或 SQL Server LocalDB。
2.2 环境适配:VS 版本、.NET Framework 与 OleDb 驱动三件套
这个项目几乎必然基于 .NET Framework 4.0 或 4.5(查huoguo.csproj中<TargetFrameworkVersion>即可确认)。不要强行用 .NET 6+ 打开重编译——WinForm 控件生命周期、OleDb 连接池、甚至PrintDocument的纸张设置都会出玄学问题。
正确做法是:
- 安装 Visual Studio 2017(免费社区版)或 VS2019(需勾选“.NET desktop development”工作负载)
- 确保安装了Microsoft Access Database Engine 2010 Redistributable(32 位!即使你的系统是 64 位,C# WinForm 默认以 x86 模式运行,必须匹配 32 位驱动)
验证驱动是否就位:
# 在 PowerShell 中执行(需管理员权限) Get-OdbcDriver | Where-Object { $_.Name -like "*Access*" }若无输出,去微软官网搜 “AccessDatabaseEngine_X64.exe” 下载并手动选择 32 位版本安装(文件名含AccessDatabaseEngine.exe而非AccessDatabaseEngine_X64.exe)。
2.3 数据库路径修复:Access 文件不能“找不到”,而要“找得准”
打开huoguo.csproj或主窗体代码(通常是MainForm.cs),全局搜索"huoguo.mdb"或"Data Source="。你会发现类似这样的硬编码:
string connStr = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=.\huoguo.mdb;";问题来了:.\\表示程序当前目录,但 VS 调试时CurrentDirectory是bin\Debug\,而.mdb在项目根目录。直接运行.exe时又可能因快捷方式起始位置不同而报错。
可靠解法(三步):
- 将
huoguo.mdb复制到bin\Debug\和bin\Release\下(VS 编译后自动复制) - 修改连接字符串为绝对路径(开发阶段):
string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "huoguo.mdb"); string connStr = $@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={dbPath};";- 发布时用
ApplicationDeployment.CurrentDeployment.DataDirectory(ClickOnce)或配置文件管理路径(见第 4 章)
参数说明:
AppDomain.CurrentDomain.BaseDirectory返回.exe所在目录,比Environment.CurrentDirectory稳定 10 倍。Microsoft.Jet.OLEDB.4.0是 Access 2003 驱动,若你升级到.accdb(Access 2007+),需换为Provider=Microsoft.ACE.OLEDB.12.0并安装对应 ACE 驱动。
2.4 主窗体启动逻辑梳理:点菜、传单、结账三大入口在哪
打开Program.cs,找到Application.Run(new MainForm());——MainForm就是主界面。用 VS 的“转到定义”(F12)跳进去,重点看三个按钮事件:
btnOrder_Click:点菜入口 → 跳转OrderForm.cs(菜品选择 + 数量输入 + 桌号绑定)btnKitchen_Click:厨房屏同步 → 触发KitchenMonitor.cs(定时轮询未上菜订单,按锅底类型分组高亮)btnSettle_Click:结账入口 → 调用SettlementForm.cs(读取当前桌未结账单,计算折扣、抹零、打印小票)
关键观察点:
- 所有订单数据是否存入
Orders表?字段是否含TableID,DishID,Quantity,Status(0=待上菜, 1=已上, 2=已结)? - 结账时是否更新
Orders.Status = 2并写入Settlements表?还是仅标记Orders.IsSettled = true? - 打印逻辑是否调用
PrintDocument.Print()?模板是否用RichTextBox渲染?
这些细节决定你后续能否安全加功能——比如加会员积分,就得知道结账时哪一行代码真正“落库”。
3. 火锅场景特化:为什么普通点菜系统在这儿会翻车?三个必须重写的模块
3.1 锅底与菜品的强耦合:鸳鸯锅不是两个单,是一张动态订单
普通点菜系统把“锅底”当普通菜品处理:点一份“清油锅底” + 一份“番茄锅底” = 两条记录。但火锅店实际是:一张桌只能有一个锅底组合,且毛肚、黄喉等荤菜默认按锅底分区上菜。
看huoguo.mdb的Dishes表,你会发现CategoryID字段值为1(锅底)、2(荤菜)、3(素菜)、4(酒水)——但这不够。真正需要的是DishGroup字段:
DishGroup = 0:通用菜(啤酒、纸巾)DishGroup = 1:清油区专属(毛肚、鸭肠)DishGroup = 2:番茄区专属(肥牛、虾滑)DishGroup = 3:共用区(金针菇、土豆)
重写OrderForm的菜品加载逻辑:
// 加载菜品时,先查当前桌已选锅底 string sql = "SELECT GroupID FROM Tables WHERE TableID = @tableID"; int currentGroup = (int)cmd.ExecuteScalar(); // 假设 Tables 表存了当前锅底分组 // 再查菜品(排除不匹配分组的) sql = "SELECT * FROM Dishes WHERE CategoryID != 1 AND (DishGroup = 0 OR DishGroup = @group)"; cmd.Parameters.AddWithValue("@group", currentGroup);血泪经验:曾有店家反馈“点了鸳鸯锅,毛肚却送到番茄锅那边”。根源就是没做
DishGroup过滤,后厨屏按CategoryID分组显示,结果清油区的毛肚和番茄区的肥牛混在一起滚动。
3.2 退菜逻辑的原子性:退半份毛肚,不能只减 Quantity,要拆单
Access 里Orders表如果只有OrderID,DishID,Quantity,Price四个字段,退菜时直接UPDATE Orders SET Quantity = Quantity - 1 WHERE OrderID = 123 AND DishID = 5是危险的——万一网络抖动,Quantity变成负数,结账时总价就崩了。
正确做法是引入OrderDetails子表:
| DetailID | OrderID | DishID | Quantity | UnitPrice | Status |
|---|---|---|---|---|---|
| 1001 | 123 | 5 | 2 | 48.00 | 0 |
| 1002 | 123 | 5 | 1 | 48.00 | 1 |
退菜时不是改Quantity,而是插入一条Status = 1(已退)的新记录,并在结账时SUM(Quantity * UnitPrice)时过滤Status = 0。
修改btnRefund_Click事件:
// 插入退菜记录,而非 UPDATE string sql = "INSERT INTO OrderDetails (OrderID, DishID, Quantity, UnitPrice, Status) VALUES (@oid, @did, @qty, @price, 1)"; cmd.Parameters.AddWithValue("@oid", currentOrderID); cmd.Parameters.AddWithValue("@did", selectedDishID); cmd.Parameters.AddWithValue("@qty", refundQty); cmd.Parameters.AddWithValue("@price", unitPrice);3.3 打印小票的“火锅语法”:不是格式美观,是信息防错
标准小票打印模板(printer_template.txt)里常见:
【{店名}】 桌号:{TableID} 时间:{Time} ------------------------ {DishName} × {Qty} ¥{Price} ... 总计:¥{Total} 抹零:¥{Round}但火锅店需要额外字段:
- 锅底分区标识:在毛肚旁加
[清油],在肥牛旁加[番茄] - 加汤/加料标记:
[加汤×2]、[加香油×1](这些是服务项,不计价但要提醒后厨) - 特殊备注:
[不要葱]、[微辣](存在Orders.Remark字段,但常被忽略)
改造PrintDocument_PrintPage方法:
// 获取当前桌所有明细(含退菜) string sql = @" SELECT d.DishName, od.Quantity, od.UnitPrice, CASE WHEN d.DishGroup = 1 THEN '[清油]' WHEN d.DishGroup = 2 THEN '[番茄]' ELSE '' END AS GroupMark, CASE WHEN od.IsServiceItem = 1 THEN '[加' + d.DishName + '×' + od.Quantity + ']' ELSE '' END AS ServiceMark FROM OrderDetails od JOIN Dishes d ON od.DishID = d.DishID WHERE od.OrderID = @oid AND od.Status = 0";然后拼接行文本时:
string line = $"{row["DishName"]}{row["GroupMark"]}{row["ServiceMark"]} × {row["Quantity"]} ¥{row["Price"]}";玄学提示:小票打印机(如芯烨 XP-55B)对中文字符宽度敏感。
[清油]是 4 字节,[番茄]也是 4 字节,但[加汤×2]里×是全角符号(2 字节),必须统一用*(半角)或预处理替换,否则换行错位。
4. 避坑指南:C# 火锅点菜系统上线前必踩的 4 个坑
4.1 现象:午市高峰时点击“结账”按钮,界面卡死 10 秒,后台日志报OleDbException: 无法获得数据库锁
原因:Access 是文件级数据库,多用户并发写入时(尤其UPDATE Orders SET Status = 2+INSERT INTO Settlements),若事务未显式控制,OleDb 连接池会排队等待锁释放,UI 线程被阻塞。
解决:
- 所有数据库操作必须包裹在
using (var conn = new OleDbConnection(connStr)) { ... }中,禁止跨方法复用连接对象 - 结账逻辑改用
TransactionScope(.NET Framework 支持):
using (var scope = new TransactionScope()) { using (var conn = new OleDbConnection(connStr)) { conn.Open(); var cmd = conn.CreateCommand(); cmd.CommandText = "UPDATE Orders SET Status = 2 WHERE TableID = @tid"; cmd.Parameters.AddWithValue("@tid", tableID); cmd.ExecuteNonQuery(); cmd.CommandText = "INSERT INTO Settlements (...) VALUES (...)"; cmd.ExecuteNonQuery(); } scope.Complete(); // 提交事务 }- 终极方案:把
Settlements表迁移到内存 SQLite(System.Data.SQLite),只用 Access 存菜品、桌号等静态数据。
4.2 现象:服务员用扫码枪扫桌号(如“008”),程序识别成“8”,导致跳转错桌
原因:扫码枪默认输出为KEYBOARD WEDGE模式,Windows 把它当键盘输入。若焦点不在桌号输入框,扫描内容会进入其他控件(如菜单列表),触发KeyDown事件时e.KeyCode是Keys.D0,但e.KeyValue是 ASCII 码 48(‘0’),Text属性却已被清空。
解决:
- 全局监听
PreviewKeyDown事件(在MainForm构造函数中):
this.PreviewKeyDown += (s, e) => { if (e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9 || e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9) { // 拦截数字键,聚焦到桌号框 txtTableID.Focus(); SendKeys.SendWait(e.KeyCode.ToString().Replace("NumPad", "")); e.SuppressKeyPress = true; } };- 更稳做法:用
SerialPort直连扫码枪(需硬件支持),或要求扫码枪设为USB HID POS模式,输出带回车符,再监听txtTableID_KeyPress:
private void txtTableID_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == '\r') // 回车结束扫描 { LoadTableOrder(txtTableID.Text.Trim()); e.Handled = true; } }4.3 现象:更换新打印机后,小票内容挤成一团,汉字显示为方块
原因:PrintDocument默认使用GraphicsUnit.World,字体大小单位是“点”,但不同打印机 DPI 解析不同。Access 数据库里菜品名含全角空格( ),StringFormat未设置StringAlignment.Center,导致右对齐失效。
解决:
- 打印前强制设置
Graphics.PageUnit = GraphicsUnit.Millimeter - 中文文本用
Font指定new Font("SimSun", 9f, FontStyle.Regular)(宋体兼容性最好) - 关键代码段:
e.Graphics.PageUnit = GraphicsUnit.Millimeter; Font font = new Font("SimSun", 9f); StringFormat format = new StringFormat { Alignment = StringAlignment.Near }; e.Graphics.DrawString("桌号:" + tableID, font, Brushes.Black, 10, 5, format); // 注意:X 坐标单位是毫米,不是像素- 避坑口诀:打印前
e.Graphics.ResetTransform(),避免上次缩放残留。
4.4 现象:凌晨自动备份数据库后,第二天早上首次点菜报错未找到可安装的 ISAM
原因:Access 备份脚本(如xcopy huoguo.mdb huoguo_bak.mdb /Y)在.mdb文件被 OleDb 连接占用时执行,导致备份文件损坏。下次启动时OleDbConnection.Open()读取到坏文件,抛出 ISAM 错误。
解决:
- 备份前强制关闭所有连接:
// 在备份前调用 GC.Collect(); // 强制回收 GC.WaitForPendingFinalizers(); Thread.Sleep(100); // 等待连接释放- 生产级方案:改用
JetCompaction工具(esentutl.exe)在线压缩备份:
esentutl /y "huoguo.mdb" /d "huoguo_bak.mdb" /o- 更简单:每天关机前手动备份,或用 Windows 任务计划,在凌晨 2 点(无营业)执行
robocopy。
5. 让系统真正“活”起来:三个可立即落地的增强技巧
5.1 用 Windows 服务托管厨房屏:告别“开着电脑不敢关机”
当前KitchenMonitor.cs很可能是 WinForm 窗体,最小化后容易被误关。改成 Windows 服务,就能 24 小时后台运行,且开机自启。
步骤:
- 新建项目
HuoguoKitchenService(.NET Framework Console App) - 添加引用
System.ServiceProcess - 继承
ServiceBase,重写OnStart:
protected override void OnStart(string[] args) { // 启动一个 Timer,每 3 秒查一次未上菜订单 timer = new Timer(3000); timer.Elapsed += (s, e) => RefreshKitchenDisplay(); timer.Start(); }RefreshKitchenDisplay()中用OleDbConnection查询SELECT * FROM Orders WHERE Status = 0 ORDER BY CreateTime,通过SendMessage向已运行的MainForm窗口发送自定义消息(WM_USER + 100),触发 UI 刷新。
参数说明:
Timer必须是System.Threading.Timer(非System.Windows.Forms.Timer),后者依赖 UI 线程。SendMessage的HWND从MainForm.Handle获取,需在主窗体Load事件中保存到静态变量。
5.2 用 JSON 配置替代硬编码:让店长自己改价格、加新品
把Dishes表的Price字段从数据库挪到config\dishes.json:
[ { "ID": 5, "Name": "鲜毛肚", "Price": 48.00, "Group": 1 }, { "ID": 6, "Name": "手切肥牛", "Price": 58.00, "Group": 2 } ]加载逻辑(DishManager.cs):
public static List<Dish> LoadDishes() { string json = File.ReadAllText(@"config\dishes.json"); return JsonConvert.DeserializeObject<List<Dish>>(json); }再加个btnReloadDishes_Click按钮,点击后重新加载——店长改完 JSON 保存,点一下按钮,菜单价格实时刷新,不用重启程序。
安全边界:JSON 文件路径用
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config", "dishes.json"),禁止用户输入路径;解析失败时try-catch并弹窗提示“配置文件格式错误,请检查逗号和引号”。
5.3 用 SQLite 替代 Access:解决并发瓶颈的平滑过渡方案
Access 的并发上限约 5 用户,而火锅店午市常超 20 台终端。SQLite 是文件型数据库,但支持 WAL 模式(Write-Ahead Logging),并发读写能力提升 5 倍。
迁移步骤(不改代码结构):
- 用 DB Browser for SQLite 创建
huoguo.db,建表语句与 Access 一致(Orders,Dishes,Tables) - 安装 NuGet 包
System.Data.SQLite(注意选x86版本) - 修改连接字符串:
// 原 Access 连接 // string connStr = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=..."; // 新 SQLite 连接 string connStr = @"Data Source=huoguo.db;Version=3;Journal Mode=WAL;";- 关键适配:SQLite 不支持
TOP 10,改用LIMIT 10;IIF()函数改用CASE WHEN;日期函数Now()改用datetime('now')。
验证并发:开 3 个实例,同时点菜、结账、退菜,用sqlite3 huoguo.db ".stats"查看busy计数,应长期为 0。
我的习惯:上线前用
sqlite3 huoguo.db ".dump"导出 SQL,存为backup.sql,一旦出问题sqlite3 huoguo.db < backup.sql一键回滚。这比 Access 的.mdb备份可靠得多——毕竟 SQLite 是单文件,而 Access 的锁机制像黑匣子。
希望帮到你。
本文还有配套的精品资源,点击获取