简介:winform宾馆酒店管理系统源码是一套基于VB语言开发的完整程序,内置SQL数据库脚本,面向初学Windows窗体开发或需要快速搭建酒店管理项目的读者。系统覆盖客房信息维护、入住登记、退房结账、预订管理等典型业务模块,代码中附带详细注释,能帮助理解界面事件处理、数据绑定及数据库增删改查等关键环节。资源包共354个文件,包含124个vb源码文件、58个resx与resources界面资源、38个ico图标,以及17个sql脚本、配置文件与可执行程序等,整体仅3.59MB,工程目录规整便于定位和二次修改。包内同时提供数据库脚本、项目工程文件、可运行exe及必要依赖,将其还原或附加数据库后即可直接运行。目前已有216人学习下载,适合作为课程设计、毕业设计或业务系统开发的参考模板。
1. 先判断这套 WinForms 酒店管理系统值不值得你动手
如果你在找一个 winform 宾馆酒店管理系统源码,点进来大概率是三种情况之一:课设/毕设要交一个能演示的酒店前台系统;公司或亲戚的小旅馆想拿现成代码改造上线;或者刚看完一堆 Java 课设案例,想看看 vb 开发语言在 WinForms 里到底怎么写业务。这套系统的典型组成很固定:几个窗体、一个公共模块、一份数据库脚本,覆盖房态、预订、入住、退房、结账这些刚需。真正值钱的不是窗体代码,而是那份建库脚本——房型、房价、房间初始化数据都在里面。下面按“先看库、再读码、后换肤”的顺序把它拆开。
2. 拆开 vb 源码包:数据库脚本是入口,连接串是命门
拿到压缩包先别急着双击 .sln,先解压看目录。WinForms 项目在 Visual Studio 里的文件结构有固定规律,但课设包经常混着奇怪的东西。看懂文件类型,比看懂代码本身更省时间。
2.1 源码包里必须有的三类文件:.vb、App.config、.sql
一个完整可运行的 WinForms 酒店管理源码包,至少要有三类东西:解决方案与项目文件、窗体代码文件、数据库脚本。常见文件清单和它们各自负责的事:
| 文件/目录 | 作用 | 最容易踩的坑 |
|---|---|---|
| Hotel.sln / Hotel.vbproj | 决定用什么框架版本打开 | .NET 版本不对,VS 打不开或控件错位 |
| Form1.vb / Form1.Designer.vb | 窗体逻辑和设计器生成代码 | 新手手改 Designer 会导致界面崩 |
| Module1.vb | 公共模块,通常放连接串和通用函数 | 连接串往往藏在这里,别只找 App.config |
| App.config | 配置文件 | 改完不重新编译,程序还是读旧配置 |
| hotel.sql / db.sql | 数据库脚本 | 先看它再碰代码,90% 的坑在这 |
这里要特别分清“vb”的两种意思:WinForms 里用的是 VB.NET,源码文件是 .vb;如果解压出来全是 .frm 和 .bas,那就是 VB6 的老程序,连数据库走的是 ADO 而不是 SqlClient,跟 WinForms 不是一回事。我一般先看有没有 .vbproj,有就是 .NET 系,直接进 Visual Studio;只有 .frm 的话,这套“WinForms 源码”的说法就要打个问号。
2.2 数据库脚本怎么读:核心表和初始化数据就在前 200 行
数据库脚本是这套源码的黑匣子。大多数课设包的 .sql 文件结构都差不多:建库、建表、插初始化数据、可选几个视图。不用从头到尾读,先抓六张核心表:RoomType(房型)、RoomInfo(房间)、GuestInfo(住客档案)、ReserveInfo(预订)、CheckInInfo(入住登记)、AccountBill(账务流水)。典型建表脚本长这样:
CREATE TABLE RoomType ( RoomTypeId INT IDENTITY(1,1) PRIMARY KEY, TypeName NVARCHAR(20) NOT NULL, -- 标准间 / 大床房 / 套房 Price DECIMAL(10,2) NOT NULL -- 门市价,必须用 decimal,不能用 float ); GO CREATE TABLE RoomInfo ( RoomId INT IDENTITY(1,1) PRIMARY KEY, RoomNo VARCHAR(10) NOT NULL UNIQUE, -- 房号,例如 101、102 RoomTypeId INT NOT NULL REFERENCES RoomType(RoomTypeId), RoomState TINYINT NOT NULL DEFAULT 0 -- 0空净 1脏房 2维修 3入住 4预订 ); GO为什么价格一定用 DECIMAL(10,2)?用 Float/Double 存金额,累计加减会在小数位上跑偏,酒店一天几百笔流水,月底对账差一分钱就够你查半天。为什么房间状态用 TINYINT 不用字符串?字符串 “入住”“已入住”“在住”写法一多,统计报表就废了,用数字状态加前端翻译是课程设计源码里最常见的正确做法。外键不要加太多,RoomInfo 建了外键没问题,但如果你看到表之间十几个外键互相引用,删数据时会连环报错。
2.3 最小启动三步:跑脚本、改连接串、按 F5
把脚本导进数据库这一步,常见做法是先用 SSMS 打开 .sql 文件直接执行,也可以用命令行:
sqlcmd -S .\SQLEXPRESS -E -i D:\Hotel\db\hotel.sql参数说明:-S 指定 SQL Server 实例名,.-E 表示用 Windows 身份登录,-i 是脚本文件路径。如果没装 sqlcmd,直接在 SSMS 里打开脚本按 F5 效果一样,只是没有命令行回显,报错要自己看消息窗口。
脚本跑完后,定位连接串。WinForms 源码里连接串只有两个存放位置:App.config 的 connectionStrings 节点,或者 Module1.vb 里的全局变量。常见 App.config 写法:
<connectionStrings> <add name="HotelConn" connectionString="Server=.\SQLEXPRESS;Database=HotelDB;User Id=sa;Password=123456;" providerName="System.Data.SqlClient" /> </connectionStrings>' 公共模块里也经常有一份,优先以这里为准 Public ConnStr As String = "Server=.\SQLEXPRESS;Database=HotelDB;User Id=sa;Password=123456;"连接串里最需要理解的是 Server 部分:写成 Server=. 是连默认实例,写成 Server=.\SQLEXPRESS 是连具名实例,两者完全不同。装 SQL Server 时如果实例名带了版本号,这里也必须写全。
提示:先确认脚本是 SQL Server 还是 Access。WinForms 课设九成是 SQL Server,但确实有 Access 版,连接串变成 Provider=Microsoft.Jet.OLEDB.4.0,整套代码的取数方式都不一样。
| 参数 | 含义 | 常见误写 |
|---|---|---|
| Server | 实例名 | 把 . 写成 localhost 有时能通,有时不能 |
| Database | 库名 | 跟脚本里的 CREATE DATABASE 不一致 |
| User Id / Password | SQL 账号 | 混合认证没开,怎么连都报登录失败 |
| Integrated Security | Windows 身份 | 设成 true 还带账号密码会冲突 |
3. 房态与客房面板:看懂五态转换和 DataGridView,就看懂一半源码
酒店系统的核心不是结账算法,而是房态。前台一天所有操作的最终落点,都是“房间现在处于什么状态”。这一章把房态模型和界面绑定讲透,你就能看懂大部分源码在干什么。
3.1 五态房态模型:一张房间表撑起前台一整天
大多数 WinForms 酒店管理源码把房间状态做成五个数字,含义固定:
| 状态码 | 含义 | 触发动作 | 界面示意 |
|---|---|---|---|
| 0 | 空净房,可销售 | 退房并保洁完成 | 绿色 |
| 1 | 脏房,已退未打扫 | 退房完成 | 灰色 |
| 2 | 维修房,不可销售 | 工程报修 | 红色 |
| 3 | 入住中 | 入住开单 | 蓝色 |
| 4 | 已预订占用 | 预订或锁房 | 橙色 |
为什么要用固定状态集合而不是自由填备注?因为前台、保洁、店长三个角色都在改状态,退房的人不知道房间什么时候打扫完,保洁也不知道哪间该优先打扫。状态机把流程约束死了,换班才不会打架。源码里如果你看到 RoomState 字段是 int,并且有对应的字典表或者常量定义,这套代码的基本功就是靠谱的;如果状态存在房间表里直接写“有人”“没人”,后患无穷。
3.2 用 DataGridView 把房间状态翻译成人话
状态数字入库,界面不能给前台看 0、1、2、3。最小做法是查询后翻译再绑定:
Dim sql As String = "SELECT r.RoomNo, t.TypeName, t.Price, r.RoomState " & "FROM dbo.RoomInfo r LEFT JOIN dbo.RoomType t " & "ON r.RoomTypeId = t.RoomTypeId" Dim da As New SqlDataAdapter(sql, conn) Dim dt As New DataTable() da.Fill(dt) ' 状态列不直接显示数字,翻译成中文后追加一列 dt.Columns.Add("StateText", GetType(String)) For Each row As DataRow In dt.Rows Select Case CInt(row("RoomState")) Case 0 : row("StateText") = "空净房" Case 1 : row("StateText") = "脏房" Case 2 : row("StateText") = "维修" Case 3 : row("StateText") = "入住" Case 4 : row("StateText") = "预订" End Select Next DataGridView1.DataSource = dt DataGridView1.Columns("RoomState").Visible = False为什么直接绑定 DataTable 而不是手动 AddRow?绑定之后的排序、筛选、分页都能继续用 DataView 做,手动加行则每次刷新都要重新拼一遍。为什么用循环翻译而不是 CellFormatting 事件?循环翻译出的文本进了数据源,导出 Excel 时不会变成数字;CellFormatting 只影响屏幕显示,导出时仍然是一堆 0 和 1。这个细节很多源码都没做干净。RoomState 列隐藏但保留在表里,是为了后面写筛选条件时还能引用原值。
3.3 一键生成 1~100 号房的初始化脚本
很多源码包里只有十几条房间 INSERT,自己演示不够用。常见做法是直接改初始化脚本重新生成,不用手写一百行 INSERT:
INSERT INTO dbo.RoomInfo (RoomNo, RoomTypeId, RoomState) SELECT '1' + RIGHT('0' + CAST(RN AS VARCHAR(2)), 2) AS RoomNo, CASE WHEN RN % 3 = 0 THEN 2 ELSE 1 END AS RoomTypeId, 0 AS RoomState FROM ( SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RN FROM sys.all_objects a CROSS JOIN sys.all_objects b ) t WHERE RN BETWEEN 1 AND 30;这里 RN 是生成的行序号,RIGHT + CAST 是给个位补零,让房号可以拼出 101、102 这种格式;CASE 让每 3 间房穿插一个其他房型,演示时列表更真实。这段 SQL 跑出来是 1 楼的 01 到 30 号房,把 WHERE 改成 BETWEEN 1 AND 100 就是一百间。这种写法看着花哨,但课设演示足够,而且比手写一百行 INSERT 好维护。要注意 sys.all_objects 在不同版本的 SQL Server 里行数不一样,1503 行以内不会出问题,超过 1537 行会报错,实际业务系统别用这套,规规矩矩手写或者用存储过程。
3.4 房费怎么算:按天、跨天、钟点房的取值规则
房费计算是退房结账的命门,也是源码里最容易藏逻辑错误的地方。最常见的计价规则是:按天算,住不够一天按一天;还有钟点房按小时算,超过 4 小时按全天。用 VB 写最小实现:
' 按天计费:不足一天按一天,向上取整 Dim days As Decimal = Math.Ceiling((dtOut - dtIn).TotalDays) Dim roomFee As Decimal = days * roomPrice ' 钟点房计费:4 小时内按小时,超过 4 小时按全天 Dim hours As Double = (dtOut - dtIn).TotalHours Dim fee As Decimal = If(hours <= 4, hours * hourPrice, Math.Ceiling(hours / 24) * dayPrice)为什么用 Math.Ceiling 而不是 (dtOut - dtIn).TotalDays 直接取整?中午 12 点入住、第二天中午 12 点退房,TotalDays 正好是 1.0,没问题;但如果是晚上 23:30 入住、第二天 00:30 退房,TotalDays 是 0.04,直接取整会算成 0 天,房费归零。向上取整让最短住一晚的钱能收到,这是行业惯例,不是代码偷懒。参数说明:hourPrice 和 dayPrice 建议在 RoomType 表里各存一列,不要在前端写死,否则改价要重新发布程序。
4. 从预订到结账:三表联动、押金流水和历史记录的落地写法
前面讲了房态,这一章把“客人从订房到退房”的完整链路串起来。这块的坑不在界面,而在数据库事务和流水顺序。搞不清这件事,系统跑三个月就开始对不上账。
4.1 三表联动:预订表、房间表、账务表如何转一圈
一条完整业务流是这样的:客人预订时 ReserveInfo 插入一行,RoomInfo 状态改成 4;客人到店 CheckInInfo 插入一行,RoomInfo 状态改成 3,同时往 AccountBill 记一笔押金;退房时算房费、冲押金、RoomInfo 状态改成 1。其中“入住开单”这一步最容易写坏:
Using conn As New SqlConnection(connStr) conn.Open() Using tran As SqlTransaction = conn.BeginTransaction() Try ' 1. 房间状态 4(预订) -> 3(入住) Dim sql1 As String = "UPDATE RoomInfo SET RoomState = 3 WHERE RoomId = @rid" ' 2. 写入住登记单 Dim sql2 As String = "INSERT INTO CheckInInfo(GuestId, RoomId, CheckInTime, Deposit) " & "VALUES(@gid, @rid, @inTime, @deposit)" ' 3. 写押金流水,AccountBill 里记一笔正数 Dim sql3 As String = "INSERT INTO AccountBill(CheckInId, BillType, Amount, CreateTime) " & "VALUES(@cid, '押金', @deposit, @inTime)" ' 依次 ExecuteNonQuery,最后 tran.Commit() Catch ex As Exception tran.Rollback() Throw End Try End Using End Using为什么这三条必须在一个事务里?如果房间状态改成入住、登记单没写进去,前台看到的是房间已入住,系统里却找不到客人记录,对账时这就是一笔打架的单子。我见过不少翻车案例,都是这里图省事没开事务,程序一崩就全乱了。参数化查询 @gid、@rid、@inTime、@deposit 不只是防 SQL 注入,还能顺便避免中文字符编码问题——VB 老代码里最爱用字符串拼接 SQL,遇到中文客人姓名直接变问号。
4.2 押金与流水:先记流水、后关账,避免对不上账
押金不要只存在入住单一个字段里。账务上“收了 200、消费 120、退 80”要有据可查,所以 AccountBill 表每一行是一条流水:
| BillId | CheckInId | BillType | Amount | CreateTime |
|---|---|---|---|---|
| 1 | 1001 | 押金 | 200.00 | 2024-06-01 14:00 |
| 2 | 1001 | 房费 | -120.00 | 2024-06-03 12:00 |
| 3 | 1001 | 退款 | -80.00 | 2024-06-03 12:05 |
结账退款的金额计算,常见做法是把这一单的流水聚合成收支差:
' 取该入住单押金流水的合计 Dim sql As String = "SELECT ISNULL(SUM(Amount),0) AS TotalDeposit " & "FROM AccountBill WHERE CheckInId = @cid AND BillType = '押金'" ' 退房应退 = 押金合计 - 房费 - 其他消费 Dim refund As Decimal = totalDeposit - roomFee - otherCost为什么要“先记流水、后关账”?因为流水是只追加的账本,操作到一半断了,账还在那里可以追查;如果直接把押金存成入住单上的一个字段,退房时直接扣,人走了账也清了,出问题一点痕迹都没有。退房操作的正确顺序是:先写入退款流水,再改房间状态,最后更新 CheckInInfo 的结账状态。这个顺序反过来,可能出现“房态已退、钱还没算”的中间状态。
4.3 打开历史记录:给主界面加一个“最近开房/退房”列表
不少课程设计源码只能看当天数据,退完房就找不到了。加一个历史记录模块其实很简单,本质就是一张联表查询:
Dim sql As String = "SELECT TOP 50 c.CheckInNo, g.GuestName, r.RoomNo, " & "c.CheckInTime, c.CheckOutTime, c.TotalAmount, c.Status " & "FROM dbo.CheckInInfo c " & "JOIN dbo.GuestInfo g ON c.GuestId = g.GuestId " & "JOIN dbo.RoomInfo r ON c.RoomId = r.RoomId " & "ORDER BY c.CheckInTime DESC"为什么用 JOIN 而不是 LEFT JOIN?查不到客人信息的单子本身就是脏数据,不该出现在历史记录里,LEFT JOIN 会让列表多出一堆空的 GuestName。为什么 TOP 50 加倒序?前台只看最近记录,一次查全量数据在数据量大时会卡界面。双击某一行再联查消费明细,是我的常见做法,这样历史账单页和当日账单页共用一套账务查询逻辑。
4.4 经营日报:把 DataGridView 导出成 Excel 的最小做法
酒店源码最常见的短板是没有报表导出,界面看完就完了。最小可用的导出方式是剪贴板加 Excel COM:
' 先把表格内容塞进剪贴板,再交给 Excel dgvReport.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText dgvReport.SelectAll() Clipboard.SetDataObject(dgvReport.GetClipboardContent()) Dim xlApp As Object = CreateObject("Excel.Application") xlApp.Visible = True xlApp.Workbooks.Add() xlApp.ActiveSheet.Paste()这套代码不引第三方 DLL,是 WinForms 里最“土但能跑”的导出。注意三个参数:ClipboardCopyMode 必须先设为包含表头;SelectAll 之后再 GetClipboardContent,顺序反了拿到的是空内容;用完要调 xlApp.Quit(),否则后台会残留 Excel 进程。如果目标电脑没装 Office,这个方案会直接抛异常,那就退一步用 SaveFileDialog 写 CSV 文件,同样能打开。
5. 避坑与排查:数据库脚本、源码版本和账单误差的 5 个现场
课设源码跑不起来的坑其实很固定,十次有八次死在环境和连接串上,不是死在代码逻辑。这五条是我按频率排出来的现场记录,每一条按“现象 → 原因 → 解决”说清楚。
5.1 SQL 脚本导入失败:“数据库已存在”和“找不到路径”
现象:执行脚本报 “Database 'HotelDB' already exists” 或 “Directory lookup for the file ... failed”。原因:脚本里写了 CREATE DATABASE 加上固定物理路径,你电脑上已经建过同名库;或者脚本里的数据文件路径在你机器上根本不存在。解决:先查有没有同名库,然后看脚本里 FILENAME 的路径指向哪里。
-- 仅限开发环境演示前清理 IF DB_ID('HotelDB') IS NOT NULL DROP DATABASE HotelDB GO这句只适合开发演示,真实系统里直接删库是事故。另一个更稳的做法是:把 CREATE DATABASE 段落的 FILENAME 路径改成你本地 SQL Server data 目录的实际路径,或者干脆注释掉建库语句,只跑后续的建表和 INSERT 部分。
5.2 连接串不匹配:F5 启动就闪退
现象:程序一启动窗口闪一下没了,或者登录时报 “在建立到服务器的连接时出错”。原因:连接串 Server=. 连的是默认实例,脚本却装进了 .\SQLEXPRESS;或者密码不对但代码里没做异常提示,直接崩。解决顺序:先用 SSMS 用代码里那套账号密码手动连一次,连得通再改连接串;改完 App.config 记得重新编译,不编译程序还读旧配置。
# 命令行验证连通性 sqlcmd -S .\SQLEXPRESS -U sa -P 123456 -Q "SELECT 1"sqlcmd 能跑通,程序里就一定能连上;sqlcmd 连不上,先回 SSMS 检查账号和实例名,别在代码里找原因。这条是最高频的启动失败原因,不是什么玄学,就是配置没对齐。
5.3 局域网连不上:SQL Server 默认不开远程访问
现象:前台那台电脑跑得好好的,收银台另一台电脑连不上数据库。原因:SQL Server 默认只监听本机,TCP/IP 协议被禁用,Windows 防火墙还挡着 1433 端口。解决:打开 Sql Server Configuration Manager,在 SQL Server 网络配置里启用 TCP/IP,把 IPALL 的端口设为 1433,重启 SQL Server 服务,再在防火墙放行 1433。程序里的连接串 Server 要从 .\SQLEXPRESS 改成 192.168.x.x\SQLEXPRESS。
如果不想碰数据库网络配置,更省事的方案是:前台和收银台都不直接连数据库,把程序装在一台电脑,另一台用远程桌面连过去操作。小旅馆两三个窗口这么干完全够用,还省得处理并发和连接数问题。
5.4 拿到的是编译好的 exe 而不是 .vb 源码:先识别再动手
现象:压缩包解压后只有 exe、dll、pdb,没有 .sln;或者看到的是一堆 .frm 和 .bas。原因:你下载的包是编译产物,或者它其实是 VB6 老程序,不是 WinForms。WinForms 的源码文件是 .vb 加 .vbproj,VB6 才是 .frm。标题写了 vb 开发语言,你要先确认这个“vb”到底指哪一种。解决:先找 .vbproj;只有 exe 的包,用 vb decompiler 也只能还原部分界面代码,业务逻辑和字段含义基本丢光,逆向重写成本比直接换源码高得多。我的建议是直接换一个标注“含完整 .vb 源码 + 数据库脚本”的包,别在 exe 上浪费时间。
5.5 账单差一分钱、差一整天:浮点数和时间截断
现象:退房时算出的金额跟手算对不上,或者客人明明住了两天只收了一天钱。原因:金额字段用 Float/Double 存,精度丢了“一分钱”;退房时间直接用 DateTime.Now 减入住时间,分钟数被截断,晚上 23:30 入住、次日 00:10 退房会被算成 0 天。解决:金额一律用 decimal,代码里出现 Single 和 Double 的地方全局替换;房费天数统一用 Math.Ceiling 向上取整;退房结账界面把“房费明细”列出来给客人确认再收款。
' 结账前先用这句把房费天数看清楚 Dim debugText As String = String.Format("入住 {0},退房 {1},按 {2} 天计", dtIn, dtOut, days)这也就是前面强调 Price 必须 DECIMAL(10,2) 的原因。源码里如果到处是 Float,直接替换成 Decimal 比改算法快得多,副作用最小。
6. 上架前的两件事:界面美化和恢复演练
6.1 用三行样式让前台界面“像产品”
WinForms 界面美化不用急着引入第三方皮肤,先把底色和按钮统一,效果立刻不一样:
Me.BackColor = Color.White For Each ctl As Control In Me.Controls If TypeOf ctl Is Button Then ctl.BackColor = Color.FromArgb(41, 128, 185) ctl.ForeColor = Color.White ctl.FlatStyle = FlatStyle.Flat End If Next这套循环放到公共模块里,新窗体拖进来就能调用。不要一上来就找开源仪表盘控件,酒店前台要的是“房态一眼看清”,不是动效炫技。先把布局对齐、字体统一、留白拉开,比换皮肤更实际。
6.2 上线前必做的一次恢复演练
部署前唯一不能省的事是备份验证。数据库脚本是后悔药,但得证明它真的能吃:
-- 每周一次备份 BACKUP DATABASE HotelDB TO DISK = 'D:\backup\HotelDB_20240601.bak' WITH INIT -- 换另一台电脑做恢复演练 RESTORE DATABASE HotelDB FROM DISK = 'D:\backup\HotelDB_20240601.bak' WITH REPLACEWITH INIT 表示覆盖同名文件,WITH REPLACE 表示恢复时覆盖现有库。这两句是实施现场最常用的两条救命命令。我习惯上线前一天把备份恢复到另一台电脑,跑一遍登录、开单、退房,确认脚本能还原才敢把前台电脑交出去。这比任何界面美化都重要——真出问题那天,能救你的只有这份演练过的备份。希望帮到你。
本文还有配套的精品资源,点击获取