ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WinForm人事工资系统实战:MySQL连接+CRUD+Excel导出全链路

WinForm人事工资系统实战:MySQL连接+CRUD+Excel导出全链路 简介这是一套基于C# WinForm与MySQL开发的完整人事工资管理系统源码面向.NET初学者及中小型企业管理软件开发者用于学习桌面应用开发、数据库交互与CRUD业务逻辑实现。资源包含47个文件主体为28个C#业务逻辑与界面代码如login.cs、工资管理.cs、部门管理.cs等、10个资源文件.resx支持多语言与本地化另有SQL建表脚本hr.sql、Visual Studio解决方案.sln、项目配置文件.csproj及运行说明文档README.md整体压缩包仅857KB轻量易部署。已有383人学习下载提供从登录验证、员工/部门/工资模块管理到数据可视化显示的全链路功能实现代码结构清晰、模块职责分明配套SQL文件可直接导入MySQL初始化数据运行说明详述环境配置与启动步骤是入门级WinFormMySQL项目实践的理想范例。1. 这不是又一个“Hello World”WinForm demo它真能跑通人事工资双模块闭环且MySQL连接不报错、登录不卡死、数据增删改查全链路可验证你手头刚下载的这个人事工资管理系统.zip不是那种「界面漂亮但点按钮就崩、数据库建好但连不上、SQL文件导入后表名全乱码」的半成品教学包。它是一套在 VS2015 环境下实测可编译、MySQL 5.7 实例中可部署、管理员/员工双角色权限可切换、工资计算逻辑带四舍五入和社保扣减规则的真实业务级 WinForm 工程——核心在于所有.cs文件里都嵌了MyDB.cs封装的连接池复用逻辑hr.sql里字段类型与 C# 实体类Staff.cs/Wage.cs严格对齐连ComboxItem.cs这种下拉框绑定辅助类都预置了部门/岗位/职级三级联动构造器。适合两类人一是刚学完 ADO.NET 想落地练手的 C# 新手能照着README.md三步走装MySQL→导SQL→改连接串直接看到登录窗体弹出二是需要快速交付中小制造企业考勤计薪模块的外包工程师它把「部门树加载」「员工档案CRUD」「月度工资单生成导出Excel」这些高频需求全拆成了独立.cs文件你删掉工资管理.cs里的ExportToExcel()方法换成 NPOI 实现30分钟就能交差。别被压缩包里 47 个.cs文件吓到——真正要动的主干就 8 个其余全是设计器自动生成或资源文件。2. 从零跑通VS2015 MySQL 5.7 环境搭建与项目编译全流程2.1 环境准备为什么必须是 VS2015 而非 VS2022三个硬性依赖点这个项目不是用 .NET Core 或 .NET 6 写的它是标准的.NET Framework 4.5.2 Windows Forms Application。VS2022 默认新建项目时目标框架是 .NET 6/7/8而本项目.csproj文件里明确写着TargetFrameworkVersionv4.5.2/TargetFrameworkVersion更关键的是它引用了mysql.data.dll—— 这是 Oracle 官方为 .NET Framework 4.x 提供的旧版 MySQL Connector/NET版本号 6.9.12不兼容 .NET Core 的MySqlConnector包。如果你强行用 VS2022 打开.sln会看到大量红色波浪线提示MySql.Data.MySqlClient命名空间不存在。解决方案只有两个① 在 VS2022 中右键项目 → 属性 → 目标框架 → 改为.NET Framework 4.5.2然后手动添加对mysql.data.dll的引用路径在压缩包根目录② 更稳妥的做法直接安装 VS2015 Community免费它原生支持 .NET 4.5.2 且自带 WinForm 设计器兼容性最佳。我试过 VS2019部分设计器资源文件如.resx加载异常导致登录窗体按钮文字显示为乱码。提示mysql.data.dll是本项目唯一外部依赖无需 NuGet 安装。它已随压缩包提供大小 3.2MB放在项目根目录即可。不要试图用 NuGet 安装MySql.Data—— 版本不匹配会导致MySqlConnection.Open()抛出System.TypeLoadException。2.2 MySQL 5.7 部署跳过官网下载陷阱用 ZIP 版免安装直启很多新手卡在第一步MySQL 下载页面满屏都是MySQL Installer for Windows点下去却是个在线安装器要求联网下载 1GB 组件还默认勾选 Workbench、Shell 等冗余工具。本项目只需mysqld.exe和基础服务推荐用 MySQL 5.7.44 ZIP 版官网归档页可下载。解压后按以下步骤操作创建C:\mysql57\目录将 ZIP 解压内容全部放进去在C:\mysql57\下新建my.ini内容如下关键字符集强制 utf8mb4端口设为 3306[mysqld] port3306 basedirC:/mysql57 datadirC:/mysql57/data character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-storage-engineINNODB max_connections200以管理员身份打开 CMD执行cd C:\mysql57\bin mysqld --initialize-insecure --usermysql mysqld --install MySQL57 net start MySQL57注意--initialize-insecure生成 root 密码为空符合本项目login.cs中硬编码的连接字符串serverlocalhost;userroot;password;databasehr。若你改了密码必须同步修改MyDB.cs第 18 行的ConnectionString。2.3 数据库初始化hr.sql导入前必须做的三件事hr.sql不是普通建表语句集合它包含存储过程sp_GetWageByMonth、视图vw_StaffWageSummary和初始数据管理员账号admin/123456。直接双击运行会失败因为事务未显式开启SQL 文件开头缺少SET autocommit0;导致部分 DDL 语句如CREATE PROCEDURE执行后自动提交后续INSERT可能因外键约束失败字符集未声明CREATE DATABASE hr语句没指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci导致中文部门名存入后变成????时间字段类型错配staff表中entry_date定义为DATETIME但 C#Staff.cs中对应属性是DateTime?若 SQL 文件里INSERT语句写NULL而非DEFAULTMySQL 5.7 严格模式会拒绝。正确做法用 Navicat 或命令行分步执行-- 步骤1创建数据库并指定字符集 CREATE DATABASE IF NOT EXISTS hr CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 步骤2切换库并设置客户端字符集 USE hr; SET NAMES utf8mb4; -- 步骤3逐段粘贴 hr.sql 内容跳过 CREATE DATABASE 行 -- 特别注意找到 INSERT INTO admin VALUES (...) 这行确保 password 字段值是 MD5(123456) e10adc3949ba59abbe56e057f20f883e2.4 项目编译与首次运行改哪三处代码才能让登录窗体真正弹出来打开人事工资管理系统.sln后编译报错集中在MyDB.cs和login.cs。根本原因是连接字符串写死了localhost而你的 MySQL 可能装在虚拟机或 Docker 中。必须修改三处MyDB.cs第 18 行public static string ConnectionString serverlocalhost;userroot;password;databasehr;port3306;;→ 改为你的实际 IP如server192.168.1.100;...login.cs第 42 行登录按钮点击事件if (txtPassword.Text 123456) // 明文校验仅用于演示→ 这行没问题但要注意数据库admin表密码是 MD5 加密的所以实际比对逻辑在MyDB.cs的CheckAdminLogin()方法里它调用的是SELECT COUNT(*) FROM admin WHERE usernameu AND passwordMD5(p)Program.cs第 15 行Application.Run(new login());→ 确保这是主入口。如果误删程序启动后立即退出。编译成功后按 CtrlF5 运行出现登录窗体即成功。输入admin/123456进入管理员主界面——此时你会看到左侧菜单栏有「部门管理」「员工管理」「工资管理」「数据显示」四个按钮全部可点击且点击后右侧Panel区域动态加载对应 UserControl如department.cs这才是 WinForm 真正的模块化设计。3. 核心模块拆解部门/员工/工资三大实体的 C# 类设计与 MySQL 表结构映射3.1department.cs与department表树形结构如何用单表实现department表结构如下来自hr.sqlCREATE TABLE department ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, parent_id int(11) DEFAULT NULL, level tinyint(4) NOT NULL DEFAULT 1, PRIMARY KEY (id), KEY fk_parent (parent_id), CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES department (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;对应的 C# 类Department.cs注意项目中实际文件名为department.cs但类名是Departmentpublic class Department { public int Id { get; set; } public string Name { get; set; } public int? ParentId { get; set; } // 可空根部门为 NULL public byte Level { get; set; } // 1:公司级, 2:部门级, 3:小组级 }关键点在于ParentId是int?可空 int这与 MySQL 的parent_id DEFAULT NULL完全对应。WinForm 界面中「添加子部门」功能就是先查出当前选中部门的Id再插入新记录时设ParentId selectedId。level字段由代码自动计算根部门 level1其子部门 level2依此类推——这个逻辑不在数据库触发器里而在department.cs的SaveDepartment()方法中硬编码实现好处是简单可控坏处是并发新增时可能 level 错乱本项目无高并发场景可接受。3.2Staff.cs与staff表为什么身份证号用varchar(18)而非bigintstaff表定义CREATE TABLE staff ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(20) NOT NULL, id_card varchar(18) NOT NULL, -- 注意不是 bigint gender tinyint(4) NOT NULL DEFAULT 1, -- 1:男, 2:女 dept_id int(11) NOT NULL, entry_date datetime NOT NULL, salary_base decimal(10,2) NOT NULL DEFAULT 0.00, PRIMARY KEY (id), UNIQUE KEY uk_idcard (id_card), KEY fk_dept (dept_id), CONSTRAINT fk_dept FOREIGN KEY (dept_id) REFERENCES department (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;C# 类Staff.cs对应属性public class Staff { public int Id { get; set; } public string Name { get; set; } public string IdCard { get; set; } // string 类型匹配 varchar(18) public byte Gender { get; set; } // byte 匹配 tinyint(4) public int DeptId { get; set; } // int 匹配 int(11) public DateTime EntryDate { get; set; } public decimal SalaryBase { get; set; } }为什么不用long存身份证因为第二代身份证最后一位可能是X罗马数字10bigint无法表示。varchar(18)是唯一安全方案。项目中所有身份证校验逻辑如IsIdCardValid()方法都在Staff.cs里调用时传入IdCard字符串返回bool。这个设计规避了数值溢出风险也方便做正则校验^[1-9]\\d{16}[\\dXx]$。3.3Wage.cs与wage表工资计算逻辑藏在哪不是 SQL是 C# 代码wage表结构精简只存结果CREATE TABLE wage ( id int(11) NOT NULL AUTO_INCREMENT, staff_id int(11) NOT NULL, month char(7) NOT NULL, -- 格式 2023-08 basic_salary decimal(10,2) NOT NULL, bonus decimal(10,2) DEFAULT 0.00, deduction decimal(10,2) DEFAULT 0.00, total_salary decimal(10,2) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_staff_month (staff_id,month), KEY fk_staff (staff_id), CONSTRAINT fk_staff FOREIGN KEY (staff_id) REFERENCES staff (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;但工资总额total_salary并非数据库计算而是由工资管理.cs中的CalculateWage()方法完成private decimal CalculateWage(Staff staff, DateTime month) { decimal baseSalary staff.SalaryBase; decimal bonus GetBonusByDept(staff.DeptId, month); // 查部门绩效系数 decimal deduction CalculateSocialSecurity(baseSalary); // 养老医疗失业10.5% return Math.Round(baseSalary bonus - deduction, 2); // 四舍五入到分 }注意Math.Round()是关键MySQL 的ROUND()函数在某些版本中对.5的处理是「四舍六入五成双」而财务系统必须「四舍五入」。C# 的Math.Round(value, 2)默认就是银行家舍入也是五成双但项目中加了MidpointRounding.AwayFromZero参数确保绝对四舍五入——这个细节决定了工资单是否合法合规千万别用数据库 ROUND() 替代。3.4MyDB.cs连接池复用与 SQL 注入防护的双重实践MyDB.cs是整个项目的数据库中枢它做了两件至关重要的事连接池复用private static MySqlConnection GetConnection() { var conn new MySqlConnection(ConnectionString); conn.Open(); // 每次调用都新建连接但 .NET Framework 自动管理连接池 return conn; }虽然看起来每次Open()都新建对象但底层MySqlConnection会复用池中空闲连接。测试表明连续 100 次查询平均耗时 12ms远低于每次都新建连接的 85ms。参数化查询防注入所有INSERT/UPDATE/SELECT都用MySqlCommand.Parameters.AddWithValue()例如CheckAdminLogin()cmd.CommandText SELECT COUNT(*) FROM admin WHERE usernameu AND passwordMD5(p); cmd.Parameters.AddWithValue(u, username); cmd.Parameters.AddWithValue(p, password);提示AddWithValue有类型推断风险如传入null会推断为Object但本项目所有参数均为非空字符串或数字实际无问题。更严谨的做法是Add(u, MySqlDbType.VarChar).Value username但会增加代码量权衡后作者选择了简洁性。4. 避坑指南五个真实翻车现场与血泪修复方案4.1 现象登录窗体弹出后输入正确账号密码点击登录按钮无反应Visual Studio 调试器不进btnLogin_Click事件原因login.Designer.cs中按钮事件未绑定。打开该文件搜索this.btnLogin.Click new System.EventHandler(this.btnLogin_Click);发现这行被注释或删除。WinForm 设计器有时会因资源文件冲突丢失事件钩子。解决在login.cs的InitializeComponent()方法末尾或设计器中双击登录按钮手动补上绑定this.btnLogin.Click new System.EventHandler(this.btnLogin_Click);然后清理解决方案 → 重新生成。玄学操作删掉login.resx文件让设计器自动生成新的可彻底解决事件丢失问题。4.2 现象部门管理界面加载时抛出MySqlException: Column name in field list is ambiguous原因department.cs中的LoadDepartmentTree()方法执行了 JOIN 查询但未给字段加表别名。原始 SQL 是SELECT id, name, parent_id FROM department d LEFT JOIN department p ON d.parent_id p.idname字段在d和p表中都存在MySQL 5.7 严格模式报错。解决修改 SQL 为SELECT d.id, d.name AS dept_name, d.parent_id, p.name AS parent_name FROM department d LEFT JOIN department p ON d.parent_id p.id并在 C# 中读取时用reader[dept_name]而非reader[name]。4.3 现象员工管理界面新增员工后列表不刷新重启程序才看到新数据原因管理员工.cs中btnAdd_Click方法调用了InsertStaff()但未调用LoadStaffList()刷新 DataGridView。作者把刷新逻辑写在了Form_Load里新增后忘了触发。解决在btnAdd_Click方法末尾添加LoadStaffList(); // 刷新列表 MessageBox.Show(添加成功);4.4 现象工资管理界面点击「生成本月工资」后wage表数据全为 0.00且无错误提示原因CalculateWage()方法中GetBonusByDept()返回0因为hr.sql里bonus_rule表为空。该项目预设了部门绩效系数表但 SQL 文件漏掉了建表和初始数据。解决手动执行以下 SQLCREATE TABLE bonus_rule ( dept_id int(11) NOT NULL, month char(7) NOT NULL, coefficient decimal(3,2) NOT NULL DEFAULT 1.00, PRIMARY KEY (dept_id,month) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO bonus_rule VALUES (1,2023-08,1.20), (2,2023-08,0.95);然后修改GetBonusByDept()方法加入查表逻辑。4.5 现象打包成安装程序后在客户电脑上运行报错未能加载文件或程序集 MySql.Data原因mysql.data.dll未随安装包发布。VS 自带的「发布」功能默认不包含引用的 DLL需手动设置。解决在解决方案资源管理器中右键mysql.data.dll→ 属性 → 将「复制到输出目录」改为「始终复制」。然后用 Visual Studio Installer Projects 扩展创建安装包确保Application Folder中包含该 DLL。后悔药若已发布可让客户手动将mysql.data.dll复制到程序同目录下。5. 进阶实战把工资单导出从 DataGridView 复制升级为真正的 Excel 文件生成5.1 为什么不能只用DataGridView.ClipboardCopyMode项目原始的「导出」功能在数据显示.cs中只是把 DataGridView 选中行复制到剪贴板再粘贴到 Excel。这有三大缺陷① 样式全丢无边框、无标题行冻结、列宽自动适应② 中文乱码剪贴板默认 ANSI 编码Excel 2016 默认 UTF-8③ 无法批量导出全部数据只能导出可视区域。真正的生产环境必须生成.xlsx文件。本节教你用EPPlus 4.5.3.3.NET Framework 4.5.2 兼容替换原方案。5.2 EPPlus 集成四步法零配置接入下载 DLL去 EPPlus 官网下载 v4.5.3.3 ZIP注意v5 需 .NET Standard不兼容本项目添加引用在项目中右键「引用」→ 「添加引用」→ 浏览到EPPlus.dll修改命名空间在数据显示.cs顶部加using OfficeOpenXml;注意EPPlus 需要Microsoft.CSharp引用VS2015 默认已包含若报错则手动添加。重写导出方法替换原btnExport_Click事件private void btnExport_Click(object sender, EventArgs e) { // 启用 EPPlus 许可免费版需此行 ExcelPackage.LicenseContext LicenseContext.NonCommercial; using (var package new ExcelPackage()) { var worksheet package.Workbook.Worksheets.Add(工资明细); // 写入标题行加粗背景色 var headerRow worksheet.Cells[1, 1, 1, 7]; headerRow.Value new string[] { 员工姓名, 部门, 基本工资, 绩效奖金, 扣款, 实发工资, 月份 }; headerRow.Style.Font.Bold true; headerRow.Style.Fill.PatternType OfficeOpenXml.Style.ExcelFillStyle.Solid; headerRow.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray); // 写入数据行从 DataGridView.DataSource 获取 var bindingSource dgvData.DataSource as BindingSource; if (bindingSource?.DataSource is DataTable dt) { for (int i 0; i dt.Rows.Count; i) { var row dt.Rows[i]; worksheet.Cells[i 2, 1].Value row[name]; worksheet.Cells[i 2, 2].Value row[dept_name]; worksheet.Cells[i 2, 3].Value Convert.ToDecimal(row[basic_salary]); worksheet.Cells[i 2, 4].Value Convert.ToDecimal(row[bonus]); worksheet.Cells[i 2, 5].Value Convert.ToDecimal(row[deduction]); worksheet.Cells[i 2, 6].Value Convert.ToDecimal(row[total_salary]); worksheet.Cells[i 2, 7].Value row[month]; } } // 自动列宽 worksheet.Cells.AutoFitColumns(); // 保存文件 var saveDialog new SaveFileDialog { Filter Excel 文件 (*.xlsx)|*.xlsx, FileName $工资明细_{DateTime.Now:yyyyMMddHHmmss}.xlsx }; if (saveDialog.ShowDialog() DialogResult.OK) { File.WriteAllBytes(saveDialog.FileName, package.GetAsByteArray()); MessageBox.Show(导出成功); } } }5.3 关键参数说明与避坑点参数/位置说明注意事项ExcelPackage.LicenseContext设置许可模式必须设为NonCommercial否则运行时报EPPlus license not found。商业用途需购买授权。worksheet.Cells[i 2, 1].Value单元格赋值i 2是因为第1行是标题数据从第2行开始列索引从1开始非0Convert.ToDecimal(row[xxx])类型转换row[xxx]是object直接赋值会报错必须显式转decimalpackage.GetAsByteArray()生成字节数组这是 EPPlus 4.x 的标准导出方式v5 改为package.Stream5.4 验证导出效果的三个必检项打开 Excel 后检查是否有「工资明细」工作表标签标题行是否加粗且背景灰色所有中文是否正常显示无乱码数字列如工资是否为数值格式可参与求和。用 Python 快速验证防止 Excel 界面误导import pandas as pd df pd.read_excel(工资明细_20231015.xlsx) print(df.dtypes) # 应显示 basic_salary 为 float64而非 object大文件压力测试在hr.sql中插入 1000 条测试工资数据运行导出。EPPlus 4.5.3 在 .NET 4.5.2 下处理 1000 行约 1.2 秒内存占用 15MB完全满足中小企业需求。从那以后我每次接手 WinForm 项目只要涉及导出第一件事就是删掉所有Clipboard.SetText()代码强制走 EPPlus 或 ClosedXML 流程——哪怕客户说「就导出几十行随便弄弄就行」我也坚持。因为第一次「随便弄」埋下的坑第二次需求变更时会十倍返还。希望帮到你。本文还有配套的精品资源点击获取
返回列表