尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
ASP.NET餐饮管理系统源码实战:部署、排错与二次开发
简介面向餐饮行业信息化开发学习者与中小餐厅管理人员的 ASP.NET 餐饮管理系统完整项目包以 Web 形式实现菜单、订单、库存、会员、报表与后台权限等常见业务可作为课程设计、毕业设计或企业原型参考。包内共约 2000 个文件、整体 46.58MB涵盖 css/gif/js/png/jpg 等前端与图片素材dll/pdb/exe 等程序与依赖组件cs/aspx/master/ashx/asmx 等源码与接口文件以及 config/xml/sln/csproj 等配置与工程文件另含少量 scss、nupkg、ps1 等构建脚本目录层次较完整便于按表现层、业务逻辑层与数据访问层对照阅读。已有 949 人学习。读者可从中获得一套基于 SQL Server 2008 的三层架构实现范例理解控件与数据绑定、AJAX 局部刷新、WebService 与 ashx 接口调用以及 ADO.NET 或 Entity Framework 数据访问的落地写法并借鉴订单状态流转、库存预警、会员积分与销售报表等模块的设计思路用于二次开发与功能裁剪。1. 从一家小馆子“后厨不出单”说起去年帮朋友看一家做快餐的小馆子前台点完菜后厨得靠服务员跑一趟口头喊一忙就漏单、重单月底老板想知道卖了多少钱只能翻收银机纸带一张张对。他的诉求很朴素前台点餐、后厨自动出小票、月底能导出营业额。外面一套 SaaS 按年付费不便宜断网还不能用最后落到本地部署的 asp.net 餐饮管理系统上。这类源码包结构大同小异ASP.NET WebForms 页面加 SQL Server功能覆盖开台点餐、菜品管理、结账收银、会员和营业报表扔在局域网一台 IIS 上就能跑起来。适合两类人一类是给中小门店做本地化交付、需要按门店习惯改字段和小票模板的开发者另一类是想拿一套能跑的业务系统练手 CRUD 和事务的新手。下面按“能不能跑、怎么跑、怎么改”的顺序往下拆。2. 技术栈与整体架构一套跑在 IIS 上的 B/S 三层结构先把技术栈摆清楚免得你下载下来发现环境对不上。这套系统的典型组合是ASP.NET WebForms.NET Framework 4.x ADO.NET SQL Server前端是服务器控件加 jQuery样式多是 Bootstrap 3 或者自己写的一套 CSS跑在 IIS 上客户端就是一个浏览器。没有 Node、没有前后端分离、没有 Docker好处是部署简单——门店一台 Windows 小主机装好 .NET Framework 和 SQL Server Express 就能用坏处是所有老 WebForms 的毛病你都得接住ViewState 大、页面整页回发、打印依赖浏览器控件。2.1 技术选型WebForms ADO.NET SQL Server 为什么还值得看WebForms 这套东西现在看确实老但餐饮门店这个场景反而合适。门店的网络环境差很多店压根不给你外网纯 B/S 局域网部署是最省事的服务员用的收银机可能是五六年前的 Windows 一体机浏览器还停在某个老内核跑现代前端框架反而卡。WebForms 的服务器控件把表格、分页、弹窗都封装好了GridView绑定一个DataTable就能出列表改一个字段的显示不用动前端构建。数据访问层一般不会用 Entity Framework而是手写 ADO.NET封装一个SqlHelper类里面是ExecuteNonQuery、ExecuteDataTable、ExecuteScalar三个方法所有 DAL 类都调它。这种写法在今天的视角下不够优雅但胜在透明SQL 写在哪一眼能看到慢查询加个索引就能见效出了事不用猜 ORM 生成了什么鬼东西。提示拿到源码先看Web.config的targetFramework和compilation节点确认是 4.0 还是 4.5/4.8。版本对不上IIS 里应用程序池选错页面直接给你一个 500.19。数据库侧常见的是 SQL Server 2008 R2 及以上业务表十张左右报表用视图或者存储过程出。金额字段必须用decimal(18,2)别用money也别用float——这一点后面避坑章节会专门讲。2.2 分层与请求走向一次页面回发要经过哪些层虽然叫“三层”实际代码里通常是这样切的表现层.aspx页面 .aspx.cs后台代码事件写在Page_Load和按钮的OnClick里业务层 BLLDishBLL、OrderBLL、DeskBLL做参数校验和事务编排数据层 DALDishDAL、OrderDAL只负责拼 SQL、调SqlHelper实体层 Model和表一一对应的 POCO 类字段名跟列名基本一致。一次“点击下单”按钮的完整走向是这样的// Pages/OrderEdit.aspx.cs 里的下单按钮 protected void btnSubmit_Click(object sender, EventArgs e) { // 1. 从 Session 里取当前桌台和购物车 int deskId Convert.ToInt32(Session[CurrentDeskId]); ListCartItem cart Session[Cart] as ListCartItem; if (cart null || cart.Count 0) { ClientScript.RegisterStartupScript(GetType(), msg, alert(请先选择菜品);, true); return; } // 2. 组装订单主表实体 OrderMain order new OrderMain { OrderNo OrderNoHelper.Build(deskId), // 单号yyyyMMddHHmmss 桌号 DeskId deskId, PeopleNum Convert.ToInt32(txtPeopleNum.Text), Status 1, // 1进行中 CreateTime DateTime.Now, OperUserId Convert.ToInt32(Session[UserId]) }; // 3. 交给 BLL事务在 BLL 里开 int orderId new OrderBLL().SubmitOrder(order, cart); if (orderId 0) { Session[Cart] null; // 下单成功清购物车 Response.Redirect(OrderDetail.aspx?id orderId); } }这段代码里几个点值得注意。Session[CurrentDeskId]是开台时写进去的页面刷新不会丢但应用程序池一回收就没了这是 WebForms 门店系统最常见的“灵异事件”来源。OrderNoHelper.Build生成单号时拼了桌号是为了后厨小票一眼能认出来是哪桌比纯时间戳实用。Status 1是硬编码的魔法值正规做法是定义一个OrderStatus枚举或者常量类改的时候全局搜得到。2.3 登录与会话Forms 认证 Session 存角色权限这块老项目基本都用 Forms 认证Web.config里配一段system.web authentication modeForms !-- 未登录统一跳到登录页超时 120 分钟够一个班次用 -- forms loginUrl~/Login.aspx timeout120 defaultUrl~/Main.aspx / /authentication authorization deny users? / !-- 匿名用户一律拒绝 -- /authorization /system.web登录成功后的写法// Login.aspx.cs UserInfo user new UserBLL().Login(txtUserName.Text.Trim(), txtPwd.Text.Trim()); if (user null) { ClientScript.RegisterStartupScript(GetType(), msg, alert(用户名或密码错误);, true); return; } FormsAuthentication.SetAuthCookie(user.UserName, false); // 写认证票据 Session[UserId] user.UserId; Session[UserName] user.UserName; Session[RoleId] user.RoleId; // 1管理员 2收银 3服务员 Session[UserInfo] user; Response.Redirect(Main.aspx);SetAuthCookie的第二个参数是“是否持久化 Cookie”门店收银机是公用的一般传false关浏览器就失效。角色判断建议封装一层别在每个页面里裸写Session[RoleId].ToString() 1// App_Code/PermissionHelper.cs public static bool HasRight(string pageKey) { var roleId HttpContext.Current.Session[RoleId]; if (roleId null) return false; // Session 过期按无权限处理 return new RightBLL().Exists(Convert.ToInt32(roleId), pageKey); }页面加载时先判权限没权限直接跳走别等用户点了一圈才发现按钮是灰的。pageKey一般就是页面文件名比如DishList、OrderCheckout权限表里配角色和 pageKey 的对应关系加页面不用改代码只加一条配置。3. 目录结构与数据模型桌台、菜品、订单三张主表怎么串拿到源码先别急着点运行花十分钟把目录和表结构看一遍后面改需求能省掉大量翻文件的时间。3.1 目录拆解从 App_Code 到 Page 页面典型的目录长这样不同源码包名字有出入但骨架一致/RestaurantSys /App_Code -- 公共类SqlHelper、加密、单号生成、打印工具 /Model -- 实体类一个表一个 .cs /DAL -- 数据访问层 /BLL -- 业务逻辑层 /Admin -- 后台管理菜品、分类、桌台、用户、报表 /Pages -- 前台业务开台、点餐、结账、退单 /Templates -- 小票模板、导出模板 /Images /Css /Js -- 静态资源 /DB -- 建库脚本 初始数据 .sql Default.aspx Login.aspx Main.aspx Web.config看几个关键位置App_Code是 WebForms 的特殊目录里面的.cs会被自动编译不需要加到项目引用里所以公共类一般扔这/DB目录最重要建库脚本和初始数据都在没有它这套系统跑不起来/Templates如果有说明小票是模板化的改格式只改文本文件不用重编译这种设计比把格式硬编码在 C# 里好改得多。页面按模块分文件夹前台和后台分开/Pages下的文件名基本就是业务动作DeskOpen.aspx开台、OrderEdit.aspx点餐、Checkout.aspx结账、OrderBack.aspx退单/反结账。找功能按文件名找比按菜单找快。3.2 主表设计桌台、菜品、订单主从表餐饮系统的数据模型核心就四张表其余都是辅助。表名关键字段说明DeskInfoDeskId、DeskName、AreaId、Status、CurrentOrderId桌台Status是状态机核心DishInfoDishId、DishName、CategoryId、Price、IsSoldOut菜品IsSoldOut控制沽清OrderMainOrderId、OrderNo、DeskId、TotalAmount、RealAmount、Status订单主表一单一行OrderDetailDetailId、OrderId、DishId、Price、Quantity、Amount订单明细加菜就加行几处设计细节决定了好不好改。OrderDetail里一定要冗余DishName和Price不能让报表去 join 菜品表取名称和价格——菜名会改、价格会调历史订单必须保留当时的快照否则上个月的营业额对不上。OrderMain的金额字段拆成三个TotalAmount应收、DiscountAmount折扣减免、RealAmount实收结账时三个数都要落库只存实收的话事后查账查不清折扣是谁给的。-- 订单主表金额字段统一用 decimal(18,2)别用 float/money CREATE TABLE OrderMain ( OrderId INT IDENTITY(1,1) PRIMARY KEY, OrderNo VARCHAR(32) NOT NULL, DeskId INT NOT NULL, PeopleNum INT NOT NULL DEFAULT 1, TotalAmount DECIMAL(18,2) NOT NULL DEFAULT 0, DiscountAmount DECIMAL(18,2) NOT NULL DEFAULT 0, RealAmount DECIMAL(18,2) NOT NULL DEFAULT 0, PayType TINYINT NULL, -- 1现金 2微信 3支付宝 4会员卡 Status TINYINT NOT NULL DEFAULT 1, -- 1进行中 2已结账 3已退单 CreateTime DATETIME NOT NULL DEFAULT GETDATE(), CheckoutTime DATETIME NULL, OperUserId INT NOT NULL ); CREATE INDEX IX_OrderMain_Desk ON OrderMain(DeskId, Status); CREATE INDEX IX_OrderMain_CreateTime ON OrderMain(CreateTime);两个索引不是摆设。IX_OrderMain_Desk服务于“查某桌当前有没有未结订单”开台时每次都查IX_OrderMain_CreateTime服务于报表按日期范围统计营业数据攒到几十万行之后没这个索引月报能转十几秒。3.3 状态机桌台状态和订单状态必须对得上这是最容易出隐性 bug 的地方。桌台状态常见定义0空闲、1占用、2已预订、3待清台。订单状态1进行中、2已结账、3已退单。两者必须成对变更而且要在同一个事务里开台桌台0 → 1同时写CurrentOrderId生成一笔Status1的订单加菜只加OrderDetail桌台和订单状态都不动结账订单1 → 2写CheckoutTime桌台1 → 0清空CurrentOrderId退单订单1 → 3桌台1 → 0。如果有人只改了订单状态没改桌台桌台就永久卡在“占用”第二天早上开门第一件事就是手动去数据库里改状态。这类问题在源码包里非常常见后面排查章节会给出具体的巡检 SQL。3.4 报表从哪来视图和存储过程营业报表一般不会在 C# 里拼复杂 SQL而是建视图。常见的三张-- 日营业汇总视图按天统计单量和实收 CREATE VIEW v_DailyReport AS SELECT CONVERT(DATE, CheckoutTime) AS BizDate, COUNT(1) AS OrderCount, SUM(TotalAmount) AS TotalAmount, SUM(DiscountAmount) AS DiscountAmount, SUM(RealAmount) AS RealAmount FROM OrderMain WHERE Status 2 AND CheckoutTime IS NOT NULL GROUP BY CONVERT(DATE, CheckoutTime);报表页面上就是一个GridView绑这个视图加两个日期TextBox做筛选。改报表口径的时候改视图不用动页面代码改完刷新就生效这是这套结构里最舒服的一点。注意视图里筛选Status 2是硬条件已退单的订单Status 3和进行中的订单都不能算进营业额。见过有源码包漏了这个条件报表金额虚高老板一看就急。4. 核心链路代码走读一次开台点餐到结账的完整实现这一章按业务流程走一遍把关键代码贴出来讲。你二次开发时八成也是在这几个点上动刀。4.1 开台一条带条件的 UPDATE 防并发抢台开台看起来简单——把桌台状态改成占用、生成一张订单。但两个服务员同时点同一张桌就会开出两张单后厨收到两份重复小票。防这个的标准做法是“带条件的更新 判断受影响行数”// DeskBLL.cs 开台 public int OpenDesk(int deskId, int peopleNum, int userId) { using (SqlConnection conn new SqlConnection(SqlHelper.ConnStr)) { conn.Open(); using (SqlTransaction tx conn.BeginTransaction()) { try { // 1. 抢占桌台只有 Status0空闲才更新得动 string sqlLock UPDATE DeskInfo SET Status 1 WHERE DeskId DeskId AND Status 0; int rows SqlHelper.ExecuteNonQuery(tx, sqlLock, new SqlParameter(DeskId, deskId)); // rows0 说明已经被别人抢走了直接回滚 if (rows 0) { tx.Rollback(); return -1; } // 2. 生成订单主表 string sqlOrder INSERT INTO OrderMain (OrderNo, DeskId, PeopleNum, Status, CreateTime, OperUserId) VALUES (OrderNo, DeskId, PeopleNum, 1, GETDATE(), UserId); SELECT SCOPE_IDENTITY();; object orderId SqlHelper.ExecuteScalar(tx, sqlOrder, new SqlParameter(OrderNo, OrderNoHelper.Build(deskId)), new SqlParameter(DeskId, deskId), new SqlParameter(PeopleNum, peopleNum), new SqlParameter(UserId, userId)); // 3. 回写当前订单号 SqlHelper.ExecuteNonQuery(tx, UPDATE DeskInfo SET CurrentOrderIdOid WHERE DeskIdDeskId, new SqlParameter(Oid, orderId), new SqlParameter(DeskId, deskId)); tx.Commit(); return Convert.ToInt32(orderId); } catch { tx.Rollback(); throw; } } } }UPDATE ... WHERE Status 0加上行数判断是一条原子操作靠数据库的行锁保证只有一个请求能改成功。这比“先 SELECT 查状态、再 UPDATE”安全得多后者中间有窗口期并发一上来就翻车。SCOPE_IDENTITY()取刚插入的订单 ID比IDENTITY安全后者在触发器场景下会取错。4.2 点餐购物车放 Session 的取舍点餐页面的交互是左边菜品分类树右边已点列表点一下加一份可以改数量、写备注“不要辣”“少油”最后一起提交。购物车绝大多数源码包放在Session里结构大概这样// App_Code/CartItem.cs [Serializable] public class CartItem { public int DishId { get; set; } public string DishName { get; set; } public decimal Price { get; set; } public int Quantity { get; set; } public string Remark { get; set; } // 口味备注直接打到后厨小票上 public decimal Amount { get { return Price * Quantity; } } }加菜的逻辑就是先从Session取ListCartItem找到同DishId且Remark相同的项就累加数量找不到就新增一行然后重新绑Repeater。放Session的好处是快、代码短坏处有三个一是应用程序池回收就丢服务员点了一半去接个电话回来购物车空了二是多开浏览器标签会串三是没法做“暂存”客人先去停车回来接着点就没了。我一般这么处理购物车仍然放Session但每次变更都往一张CartTemp表里同步一份SessionId、DeskId、DishId、Quantity、Remark页面加载时如果Session为空就从表里回填。多一张表换掉一整类“购物车莫名清空”的投诉。4.3 下单落库一个事务写主表和明细提交订单是整套系统里最不能出错的一步。主表和明细必须在一个事务里要么全成功要么全回滚绝不能出现“主表有单、明细是空”的脏数据。// OrderBLL.cs 下单首次下单和加菜共用一个入口 public int SubmitOrder(OrderMain order, ListCartItem cart) { using (SqlConnection conn new SqlConnection(SqlHelper.ConnStr)) { conn.Open(); using (SqlTransaction tx conn.BeginTransaction()) { try { // 1. 主表如果有未结订单则是“加菜”复用原订单号 int orderId GetOpeningOrderId(conn, tx, order.DeskId); if (orderId 0) { orderId InsertOrderMain(conn, tx, order); // 首次下单 } // 2. 明细逐条插入金额在服务端重算不信前端传的值 decimal total 0m; foreach (var item in cart) { decimal amount Math.Round(item.Price * item.Quantity, 2, MidpointRounding.AwayFromZero); total amount; InsertOrderDetail(conn, tx, orderId, item, amount); } // 3. 回写应收金额 UpdateOrderTotal(conn, tx, orderId, total); tx.Commit(); return orderId; } catch (Exception ex) { tx.Rollback(); LogHelper.Write(下单失败, ex); // 落日志别只弹个框 return -1; } } } }三个要点。第一加菜复用订单号同一桌客人加菜不能生成新订单否则结账要结好几次报表单量也不对。第二金额服务端重算单价从数据库DishInfo拿不是从页面隐藏域拿前端传什么都不可信。第三异常必须落日志页面只给用户一句“下单失败请重试”日志里要有完整堆栈和orderNo否则第二天查问题只能靠猜。Math.Round(..., 2, MidpointRounding.AwayFromZero)这个重载要记住C# 默认用的是银行家舍入ToEven2.345会舍成2.34跟收银员心算的结果差一分钱对账的时候能被追着问半天。4.4 结账与反结账金额、折扣与桌台释放结账页要处理折扣、抹零、多种支付方式还要考虑“结完发现算错了要反结账”。// Checkout.aspx.cs 结账 public bool Checkout(int orderId, decimal discount, int payType, int userId) { using (SqlConnection conn new SqlConnection(SqlHelper.ConnStr)) { conn.Open(); using (SqlTransaction tx conn.BeginTransaction()) { try { // 1. 锁单只有进行中的订单能结账防止重复提交 string sqlLock UPDATE OrderMain SET Status2 WHERE OrderIdOid AND Status1; if (SqlHelper.ExecuteNonQuery(tx, sqlLock, new SqlParameter(Oid, orderId)) 0) { tx.Rollback(); return false; } // 2. 取应收算实收抹零规则向下取整到角 decimal total GetTotalAmount(conn, tx, orderId); decimal real Math.Floor((total - discount) * 10) / 10; if (real 0) real 0; // 3. 回写金额、支付方式、结账时间 string sqlUpd UPDATE OrderMain SET TotalAmountT, DiscountAmountD, RealAmountR, PayTypeP, CheckoutTimeGETDATE() WHERE OrderIdOid; SqlHelper.ExecuteNonQuery(tx, sqlUpd, /* 参数略 */); // 4. 释放桌台状态回空闲清空当前订单 string sqlDesk UPDATE DeskInfo SET Status0, CurrentOrderIdNULL WHERE CurrentOrderIdOid; SqlHelper.ExecuteNonQuery(tx, sqlDesk, new SqlParameter(Oid, orderId)); tx.Commit(); return true; } catch { tx.Rollback(); throw; } } } }第 1 步的锁单是关键WHERE Status1保证同一张单只结一次。收银员手快连点两下或者网络慢导致页面重复提交都会走到这里第二次更新影响行数为 0直接回滚返回失败。第 4 步释放桌台用的是WHERE CurrentOrderIdOid而不是WHERE DeskIdDeskId。看着绕其实更稳万一这桌被重开过一次CurrentOrderId已经指向新单了用DeskId会把新桌台误释放掉。反结账就是反过来做订单2 → 1清空CheckoutTime桌台0 → 1并回写CurrentOrderId。反结账一定要留痕单独建一张OrderBackLog表记“谁、什么时候、因为什么”反的不然月底对账又是一场扯皮。4.5 后厨出单小票拼装与打印触发后厨出单有两种做法一是接小票打印机走驱动页面直接调浏览器window.print()二是用厂商的打印控件客户端装一个 exe。源码包通常两种都留了口子/Js/print.js里能看到。拼小票的逻辑在App_Code/PrintHelper.cs里典型实现是把订单明细拼成一段带制表符的文本public static string BuildKitchenTicket(OrderMain order, ListOrderDetail details) { StringBuilder sb new StringBuilder(); sb.AppendLine( 厨 房 单); sb.AppendLine(--------------------------------); sb.AppendLine($桌号{order.DeskName} 人数{order.PeopleNum}); sb.AppendLine($单号{order.OrderNo}); sb.AppendLine($时间{order.CreateTime:MM-dd HH:mm}); sb.AppendLine(--------------------------------); foreach (var d in details.Where(x x.Status 1)) // 只打未出品的 { sb.AppendLine(${d.DishName} x{d.Quantity}); if (!string.IsNullOrEmpty(d.Remark)) sb.AppendLine($ * {d.Remark}); // 口味备注单独一行加星号 } sb.AppendLine(--------------------------------); return sb.ToString(); }details.Where(x x.Status 1)这个过滤很重要。加菜场景下第一次下单打过的菜不能再打一遍否则后厨重复做。所以OrderDetail里要有一个PrintStatus或Status字段打完更新成“已出品”这一步在打印成功回调里做别在拼字符串时就改。打印失败的处理也要想清楚打印机没纸、离线、卡纸程序是弹出提示让人重打还是静默重试门店场景建议弹提示 保留“重打上一单”按钮收银员自己能处理比后台默默失败强。5. 部署与排查从还原数据库到 IIS 上线的常见问题代码看着没问题跑不起来是常态。这一章按“装环境 → 配配置 → 排查”的顺序来。5.1 环境准备.NET Framework、IIS、SQL Server 三件套门店主机的标准配置Windows 10 / Server 2012 以上IIS 7 及以上.NET Framework 4.5 或 4.8看Web.config的targetFrameworkSQL Server 2008 R2 及以上Express 版够用但要确认实例名。装 IIS 的时候注意勾选“ASP.NET”和“ISAPI 扩展”很多人只装了默认的静态文件功能页面一访问就是 404.3。SQL Server 装完要开混合认证模式并启用 TCP/IP 协议否则sa账号连不上。5.2 连接字符串与 Web.config 的关键配置connectionStrings !-- Data Source 写实例名默认实例用 . 或 localhostExpress 版写 .\SQLEXPRESS -- add nameConnStr connectionStringData Source.;Initial CatalogRestaurantDB;User IDsa;Password你的密码; providerNameSystem.Data.SqlClient / /connectionStrings system.web compilation debugfalse targetFramework4.5 / httpRuntime maxRequestLength20480 / !-- 上传菜品图片单位 KB -- globalization requestEncodingutf-8 responseEncodingutf-8 fileEncodingutf-8 / /system.webData Source这一行是新手最容易卡住的地方。默认实例写.或localhostExpress 版必须写.\SQLEXPRESS写错了报“SQL Server 不存在或访问被拒绝”看着像密码问题其实是实例名不对。debugtrue上线前务必改false不是性能问题是安全黄页错误会把连接字符串和堆栈直接吐到浏览器上。IIS 侧应用程序池选.NET CLR v4.0托管管道模式选经典。WebForms 项目用集成模式大多也能跑但如果源码里用了URLRewriter之类的 HttpModule经典模式更省事。如果项目引用了 32 位的打印控件或 Office 组件还要在应用程序池高级设置里把“启用 32 位应用程序”改成True。5.3 排查清单5 个上线当天最容易撞上的坑坑一页面开出来一堆问号乱码现象菜品名称显示成????或者中文注释变方块。原因三处编码不一致——.aspx文件本身存的不是 UTF-8有些老源码是 GB2312、Web.config里没配globalization、数据库排序规则不是Chinese_PRC_CI_AS。解决先把文件另存为 UTF-8 带签名Web.config补上globalization三个属性再查库SELECT DATABASEPROPERTYEX(RestaurantDB,Collation)如果不是中文排序规则建库脚本里加COLLATE Chinese_PRC_CI_AS重建或者只对出问题的列单独指定。坑二点“打印”没反应控制台也不报错现象点了打印按钮什么都没发生换个浏览器还是没反应。原因这类系统的小票打印多数依赖客户端控件或者 ActiveX只认 IE 内核Chrome、Edge 的新版本直接把这个能力禁掉了。解决短期用 IE 模式或者给浏览器装 IE Tab 插件长期改造走浏览器原生window.print() CSSmedia print控制小票宽度虽然排版没控件精细但不用装任何东西维护成本低得多。我一般建议门店直接换成这套。坑三结账金额和手工算的差一分钱现象三份 3.33 元的菜系统算 9.99收银员算出来也是 9.99但加上 8.8 折之后两边对不上。原因C# 里decimal默认银行家舍入SQL 里money类型精度是 4 位来回转换时取舍规则不一致。解决统一用decimal(18,2)所有金额舍入显式写Math.Round(x, 2, MidpointRounding.AwayFromZero)SQL 侧用ROUND(x, 2)别混用money和float。另外折扣计算顺序也要固定先算折扣再抹零还是先抹零再打折差的是真金白银得跟老板确认清楚。坑四桌台卡在“占用”明明客人早走了现象早上开门好几张桌显示占用点开台提示“该桌台已被占用”。原因结账或退单的代码里只更新了订单状态没更新桌台或者事务中途return没提交。解决先手动救急跑一条巡检 SQL 把孤儿桌台清出来-- 找出“桌台占用但没有进行中订单”的脏数据 SELECT d.DeskId, d.DeskName, d.CurrentOrderId FROM DeskInfo d LEFT JOIN OrderMain o ON o.OrderId d.CurrentOrderId AND o.Status 1 WHERE d.Status 1 AND o.OrderId IS NULL;确认无误后统一UPDATE DeskInfo SET Status0, CurrentOrderIdNULL WHERE ...。根治要靠代码审查把“订单状态变更”和“桌台状态变更”封成一个方法别在两个地方各写一遍。坑五点餐点到一半购物车突然清空现象服务员去接了杯水回来页面还在已点的菜全没了。原因购物车存在Session里IIS 应用程序池默认空闲 20 分钟回收Session跟着没了或者Web.config里sessionState是InProc进程一重启就丢。解决短期把应用程序池的“空闲超时”调到 0不回收、“固定时间间隔”调大但这只是缓解正经做法是把sessionState改成StateServer或者直接把购物车落库见 4.2 节的CartTemp方案。门店系统我倾向于落库多一张表换来的是再也不用解释“为什么我的菜没了”。注意改应用程序池配置属于“治标”回收不只是空闲触发内存阈值、配置文件变更都会触发。指望调参数彻底解决Session丢失迟早还会翻车。6. 二次开发加一个套餐模块并验证没改坏原链路套餐是这类系统最常见的加需求。做法不用太复杂两张新表下单时把套餐展开成明细但小票上只打套餐名。-- 套餐主表和明细表 CREATE TABLE ComboInfo ( ComboId INT IDENTITY(1,1) PRIMARY KEY, ComboName NVARCHAR(50) NOT NULL, Price DECIMAL(18,2) NOT NULL, Status TINYINT NOT NULL DEFAULT 1 -- 1上架 0下架 ); CREATE TABLE ComboDetail ( Id INT IDENTITY(1,1) PRIMARY KEY, ComboId INT NOT NULL, DishId INT NOT NULL, Quantity INT NOT NULL DEFAULT 1, FOREIGN KEY (ComboId) REFERENCES ComboInfo(ComboId) );OrderDetail加两个字段IsCombo是否套餐父项和ParentDetailId子项指向父项。下单时给套餐插一行父项、价格取套餐价再给每个组成菜插子项、价格记 0、ParentDetailId指向父项。后厨打印只打IsCombo1的父项和普通菜子项过滤掉避免后厨看到一堆单价 0 的菜发懵库存扣减和成本核算则走子项两头都照顾到。改完必须回归。我自己的习惯是准备一份“结账前快照”改代码之前用固定的一桌菜走一遍开台→点餐→加菜→打折→结账把OrderMain的四个金额字段和桌台状态抄下来改完之后同样的操作再走一遍逐字段对比。数字一致才算过感觉“应该没问题”是最贵的四个字。另外记得在测试库跑别拿门店正在营业的库试手——有一次我在下午饭点前改了个存储过程结果整个午市的小票金额都是 0那顿骂我记到现在。从那以后我每次改核心链路都强制先在一个空的影子库里跑完整流程再上生产。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

secureCRT-7.0.0实战:SSH2公钥、端口转发与日志脚本配置

secureCRT-7.0.0实战:SSH2公钥、端口转发与日志脚本配置

简介:SecureCRT 7.0.0 是面向 IT 运维、网络管理员及开发测试人员的 64 位终端仿真与文件传输工具包,主要用于 shell 远程登录、服务器维护及多协议连接管理。压缩包共93个文件,约15.86MB,含26个dll运行库、14个vbs与6个py脚本、5…

📅 2026/11/5 4:06:14
ASP.NET餐饮管理系统:从桌台订单事务到扫码点餐

ASP.NET餐饮管理系统:从桌台订单事务到扫码点餐

简介:面向餐饮行业信息化入门与课程实践的ASP.NET餐饮管理系统完整源码包,基于ASP.NET Web Forms与三层架构结合SQL Server 2008构建,涵盖菜单、订单、库存、会员、报表及后台权限等常见业务模块,适合计算机专业学生用作课程设计、…

📅 2026/11/5 4:06:14
AI 开始攻击机械方向了??

AI 开始攻击机械方向了??

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/11/5 4:01:14
MORE NEWS

更多资讯

📰

PySceneDetect终极性能对比分析:为什么它是最佳场景检测工具选择

PySceneDetect终极性能对比分析:为什么它是最佳场景检测工具选择 【免费下载链接】PySceneDetect :movie_camera: Python and OpenCV-based scene cut/transition detection program & library. 项目地址: https://gitcode.com/GitHub_Trending/py/PySceneDet…

📰

PySceneDetect入门指南:5分钟学会视频场景自动检测

PySceneDetect入门指南:5分钟学会视频场景自动检测 【免费下载链接】PySceneDetect :movie_camera: Python and OpenCV-based scene cut/transition detection program & library. 项目地址: https://gitcode.com/GitHub_Trending/py/PySceneDetect PySc…

📰

PySceneDetect核心功能详解:10种场景检测算法全解析

PySceneDetect核心功能详解:10种场景检测算法全解析 【免费下载链接】PySceneDetect :movie_camera: Python and OpenCV-based scene cut/transition detection program & library. 项目地址: https://gitcode.com/GitHub_Trending/py/PySceneDetect PyS…

📰

ai-goofish-monitor 闲鱼商品监控完整指南:5 分钟部署到 AI 自动捡漏

ai-goofish-monitor 闲鱼商品监控完整指南:5 分钟部署到 AI 自动捡漏 【免费下载链接】ai-goofish-monitor 基于 Playwright 和AI实现的闲鱼多任务实时/定时监控与智能分析系统,配备了功能完善的后台管理UI。帮助用户从闲鱼海量商品中,找到心…

📰

ElixirOffice架构解读:6层架构如何基于ONLYOFFICE引擎打造开源办公套件

【免费下载链接】ElixirOffice Open Source Office suite for users used to MS Office - multi-platform, tuned for performance 项目地址: https://gitcode.com/gh_mirrors/el/ElixirOffice 点击查看 免费下载 ElixirOffice 是一套面向 Microsoft Office 用户的…

📰

ADS122U04+PIC18F96J94:24位ADC工业测温方案解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬