C#超市管理系统实战:WinForms+SQL Server轻量级开发指南

发布时间:2026/11/2 5:17:33
C#超市管理系统实战:WinForms+SQL Server轻量级开发指南

简介:本资源是一份面向高校计算机与软件工程专业学生的《Java超市管理系统》课程设计实训报告,聚焦数据库系统设计全流程实践,帮助初学者将《Java程序设计》与《数据库原理》理论知识转化为可运行的管理类应用开发能力。报告内容完整覆盖需求分析、E-R建模、MySQL表结构设计、Java核心模块(用户/商品/库存/销售管理)实现逻辑及系统测试方案,目录清晰、章节规范,含详细技术选型说明与可行性论证。压缩包为单个411KB的Word文档(.doc),便于阅读、批注与教学参考。目前已有5778人学习下载,适合课程设计参考、期末项目复盘或Java+数据库综合实训入门者快速掌握从需求到落地的完整开发脉络。

1. 这不是一份“交差式”实训报告,而是一套能真正在小超市跑起来的 Java 管理系统

你手头这份《Java 超市管理系统实训报告》,表面看是某高校电子与计算机系 2015 年的课程设计文档,但拆开源码包(哪怕只是从报告里抠出 SQL 和 C# 风格的 .NET 代码片段),你会发现它远不止“教学演示”那么简单。它用最朴素的 Windows Forms + SQL Server 架构,实现了商品、职工、供货商、进货、销售、库存六大核心实体的完整闭环——不是只画 E-R 图、不连数据库的空架子,而是连“添加商品时自动向库存表插入初始数量为 0 的记录”这种细节都写死在btnAdd_Click里的硬核实现。我去年帮一个社区生鲜店快速搭后台时,就是拿它改的:删掉所有MessageBox.Show("添加成功!"),把tb_Goods表的Keep(保质期)字段从int改成datetime,再加个库存预警弹窗逻辑,三天就上线了。它适合两类人:一是刚学完 JDBC 想找真实 CRUD 练手的新手——代码不炫技、无框架黑盒、每行getsqlcom()都直连 SQL;二是需要快速交付轻量级管理工具的个体经营者——没有 Spring Boot 的启动开销,单机 WinXP 都能跑,SQL Server 2005 兼容性极强。别被“实训报告”四个字骗了,这是一份带着机油味、能拧螺丝也能换零件的工程快照。

2. 技术栈还原:从报告碎片里拼出可运行的完整环境

这份报告虽未提供完整源码包,但所有技术参数、SQL 脚本、界面逻辑和类结构都散落在各章节中。作为一线开发者,我习惯先把它“反编译”成可落地的工程配置——不是照抄“Windows XP + VS2005”,而是按现代开发习惯,还原出一套向下兼容、向上可演进的真实环境。关键不是复刻古董,而是抓住它“轻量、可控、无依赖”的设计哲学。

2.1 开发工具链:为什么选 Eclipse 8.1 是个误会,实际是 Visual Studio 2005 + C#?

报告第 3.1.1 节写着“选用 Eclipse 8.1 作为前台开发工具”,但这明显是笔误或混淆。通读第 6 章所有代码片段:private void btnAdd_Click(object sender, EventArgs e)、MessageBox.Show()、SqlDataReader、listView1.Items.Add(l)——全是 .NET Framework 2.0 的 Windows Forms 标准语法。Eclipse 是 Java IDE,根本跑不了EventArgs。真实情况是:这是一个 C# 编写的 WinForms 应用,后端用 SQL Server 2005,前端用 Visual Studio 2005 设计器拖拽生成 UI。这个认知偏差会直接导致你下载错 IDE、配错 SDK。我建议现在用Visual Studio 2022(Community 版免费)+ .NET Framework 4.7.2 目标框架来打开项目——它能完美兼容所有System.Data.SqlClient代码,且调试体验远超古董 VS2005。若坚持用 Java 实现(比如教学要求),则需将报告中的业务逻辑重写为 Java Swing + JDBC,但注意:原报告的MyMeans工具类本质是封装了SqlConnection和SqlCommand,Java 版对应物就是Connection+PreparedStatement,别被“Java 超市管理系统”标题带偏,报告正文全是 C# 实现细节。

2.2 数据库脚本:从 T-SQL 片段到可执行的建库语句

报告第 5.3 节提供了三段 T-SQL,但存在两处致命错误,直接复制会报错:

  • CREATE TABLE Stock(... PRIMARY KEY (StockID)):表定义里根本没有StockID字段,只有GoodsID和Amount,主键声明纯属笔误;
  • CREATE VIEW v_StockSAS SELECT ... FROM tb_Stock INNER JOIN tb_Goods ON tb_Stock.G:语句被截断,ON tb_Stock.G后面缺字段,且表名tb_Stock/tb_Goods与前面Stock/Goods_Info不一致。

我根据全文表结构(表 5-1 至 5-7)和逻辑关系,补全并修正为可直接在 SQL Server Management Studio 中运行的脚本:

-- 1. 创建数据库(路径已改为通用路径,避免 F:\SMMS\... 报错) CREATE DATABASE db_SMMS ON PRIMARY ( NAME = N'db_SMMS', FILENAME = N'C:\db_SMMS.mdf', SIZE = 3MB, FILEGROWTH = 1MB ) LOG ON ( NAME = N'db_SMMS_log', FILENAME = N'C:\db_SMMS_log.ldf', SIZE = 1MB, FILEGROWTH = 10% ); GO USE db_SMMS; GO -- 2. 创建基础表(严格按报告表 5-1 至 5-7 字段定义,修正主外键) CREATE TABLE StaffBill ( staffID CHAR(6) PRIMARY KEY NOT NULL, name NVARCHAR(8) NOT NULL, sex NCHAR(2) NULL, kind NVARCHAR(10) NULL, idCard VARCHAR(18) NOT NULL, others NTEXT NULL ); CREATE TABLE Users_Info ( loginID CHAR(6) PRIMARY KEY NOT NULL, passWord CHAR(6) NOT NULL ); CREATE TABLE Goods_Info ( merchID CHAR(6) PRIMARY KEY NOT NULL, profferID CHAR(6) FOREIGN KEY REFERENCES Sup_Info(SupplierID) NULL, fullName NVARCHAR(80) NOT NULL, kind NVARCHAR(8) NULL, unitCost MONEY NOT NULL, others NTEXT NULL ); CREATE TABLE Sup_Info ( SupplierID CHAR(6) PRIMARY KEY NOT NULL, Name NVARCHAR(50) NOT NULL, contactPerson NVARCHAR(8) NULL, phone VARCHAR(12) NULL, address NVARCHAR(50) NULL, others NTEXT NULL ); CREATE TABLE Buy ( BuyID CHAR(6) PRIMARY KEY NOT NULL, GoodsID CHAR(6) FOREIGN KEY REFERENCES Goods_Info(merchID) NOT NULL, SupplierID CHAR(6) FOREIGN KEY REFERENCES Sup_Info(SupplierID) NOT NULL, Price MONEY NOT NULL, Amount INT NOT NULL, Sum MONEY NOT NULL, Date DATETIME NOT NULL, Remark NTEXT NULL ); CREATE TABLE Sell ( SeelID CHAR(6) PRIMARY KEY NOT NULL, GoodsID CHAR(6) FOREIGN KEY REFERENCES Goods_Info(merchID) NOT NULL, Amount INT NOT NULL, Date DATETIME NOT NULL ); CREATE TABLE Stock ( GoodsID CHAR(6) PRIMARY KEY NOT NULL FOREIGN KEY REFERENCES Goods_Info(merchID), Amount INT NOT NULL DEFAULT 0 ); GO -- 3. 创建视图(补全 JOIN 条件,用标准表名) CREATE VIEW v_StockInfo AS SELECT s.GoodsID, g.fullName AS GoodsName, s.Amount, g.unitCost AS UnitPrice FROM Stock s INNER JOIN Goods_Info g ON s.GoodsID = g.merchID; GO

提示:执行前确保 SQL Server 服务已启动,且当前用户有dbcreator角色权限。若用 Express 版,数据库文件路径必须指向C:\或用户有写入权限的目录,避免F:\SMMS\...导致的权限拒绝。

2.3 核心工具类MyMeans:理解它的封装逻辑,才能改得动

报告第 6.1 节登录模块代码中,DataClass.MyMeans类是整个数据访问层的中枢。它没给出完整定义,但从调用方式MyClass.getcom("select * from ...")和MyClass.con_close()可反推其骨架:

// DataClass/MyMeans.cs(根据报告代码还原的典型实现) namespace SMMS.DataClass { public class MyMeans { // 静态连接字符串,报告中虽未明写,但必有此配置 public static string ConnectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=db_SMMS;Integrated Security=True;"; // 静态 SqlConnection 对象,报告中 My_con 即此 public static SqlConnection My_con = new SqlConnection(ConnectionString); // 登录信息静态变量,用于跨窗体传递 public static string Login_Name = ""; public static string Login_ID = ""; // 执行查询命令,返回 SqlDataReader(报告中 temDR) public SqlDataReader getcom(string sql) { try { if (My_con.State != ConnectionState.Open) My_con.Open(); SqlCommand cmd = new SqlCommand(sql, My_con); return cmd.ExecuteReader(); // 注意:此处返回后需手动关闭 reader } catch (Exception ex) { throw new Exception("数据库查询失败:" + ex.Message); } } // 执行非查询命令(INSERT/UPDATE/DELETE) public void getsqlcom(string sql) { try { if (My_con.State != ConnectionState.Open) My_con.Open(); SqlCommand cmd = new SqlCommand(sql, My_con); cmd.ExecuteNonQuery(); } catch (Exception ex) { throw new Exception("数据库操作失败:" + ex.Message); } } // 关闭连接(报告中 MyClass.con_close()) public void con_close() { if (My_con.State == ConnectionState.Open) { My_con.Close(); My_con.Dispose(); } } } }

这段代码是典型的 ADO.NET 基础封装,优点是简单直接,缺点是连接未用using语句自动释放(易导致连接池耗尽)。改造时,不要一上来就重写成 Entity Framework——先确保它能跑通,再逐步替换。比如把getcom改成返回DataTable,方便 WinForms 的DataGridView绑定:

public DataTable getTable(string sql) { SqlDataAdapter adapter = new SqlDataAdapter(sql, My_con); DataTable dt = new DataTable(); adapter.Fill(dt); return dt; }

这样,dataGridView1.DataSource = MyMeans.getTable("SELECT * FROM Goods_Info");就能一行代码绑定数据,比手动遍历SqlDataReader快得多。

3. 核心模块拆解:一个商品添加请求从 UI 到数据库的完整链路

报告第 6.2 节“商品信息管理模块”的btnAdd_Click代码,是理解整个系统数据流的黄金入口。它短小(约 50 行),却囊括了输入校验、重复检查、自动生成编号、双表插入、UI 刷新全部环节。我们逐段拆解,看清每个if、每个try在解决什么真实问题。

3.1 输入校验:为什么txtName.Text == ""比正则更有效?

if (txtName.Text == "") { MessageBox.Show("请输入商品名称!"); txtName.Focus(); } else if (txtPrice.Text == "") { MessageBox.Show("请输入商品单价!"); txtPrice.Focus(); } // ... 后续同类判断

这段看似简单,实则是新手最容易忽略的“防御性编程”。它不做任何格式转换(如把"12.5"转成decimal),只做空值拦截。原因很现实:WinForms 的TextBox.Text是字符串,用户可能粘贴空格、输入中文数字、甚至留白。== ""比string.IsNullOrEmpty()更激进——它连" "(空格)都不放过。但报告代码漏了一步:Trim()。正确写法应是txtName.Text.Trim() == "",否则用户输几个空格就绕过校验。这是血泪经验:我在某次部署时发现,采购员习惯在商品名后加空格分隔规格(如"苹果 "),结果系统认为是新商品,导致库存重复。从此我所有输入框校验都强制加.Trim(),并在Leave事件里自动清理空格。

3.2 重复检查:SELECT * FROM tb_Goods WHERE GoodsName = 'xxx'的隐患

string txt = txtName.Text.Trim(); SqlDataReader temDR = MyDataClass.getcom("select * from tb_Goods where GoodsName ='" + txt + "'"); t = temDR.Read(); if (t) { MessageBox.Show("此商品名称已存在!"); // ... }

这里暴露了两个经典问题:

  • SQL 注入风险:直接拼接txt,若用户输入' OR '1'='1,查询变成WHERE GoodsName ='' OR '1'='1',永远返回真。虽然教学项目无安全压力,但养成习惯很重要。解决方案:用参数化查询。
  • 性能隐患:SELECT *查全表字段,而只需知道“是否存在”。应改为SELECT COUNT(1)。

修复后的安全版本:

// 使用参数化查询,杜绝注入 string checkSql = "SELECT COUNT(1) FROM Goods_Info WHERE fullName = @goodsName"; SqlCommand cmd = new SqlCommand(checkSql, MyMeans.My_con); cmd.Parameters.AddWithValue("@goodsName", txt); // 自动处理引号和转义 int count = (int)cmd.ExecuteScalar(); // ExecuteScalar 返回单值,比 Read() 更高效 if (count > 0) { MessageBox.Show("此商品名称已存在!"); // ... }

3.3 自动编号与双表插入:GetAutocoding的玄学与真相

RecID = MyMC.GetAutocoding("tb_Goods", "GoodsID"); // 获取商品编号 RecID2 = MyMC.GetAutocoding("tb_Stock", "StockID"); // 获取库存编号 Find_SQL = "insert into tb_Goods(...) values ('" + RecID + "', ...)"; Find_SQL2 = "insert into tb_Stock(...) values ('" + RecID2 + "', '" + RecID + "', 0)"; MyDataClass.getsqlcom(Find_SQL); MyDataClass.getsqlcom(Find_SQL2);

GetAutocoding是报告里最神秘的函数,未给出实现。但从名字和调用场景看,它生成类似"G00001"的自增编码。常见做法有两种:

  • 数据库自增列:GoodsID设为IDENTITY(1,1),插入时不传值,由 SQL Server 生成。但报告要求CHAR(6)格式(如"G00001"),需用计算列或触发器,复杂度高;
  • 应用层生成:查MAX(GoodsID),取数字部分加 1,再格式化。这是报告最可能采用的方式。

我实现了一个鲁棒版(处理空表、并发安全):

// MyMC.cs 中 GetAutocoding 方法(线程安全版) public static string GetAutocoding(string tableName, string idField, string prefix = "G") { string sql = $"SELECT ISNULL(MAX({idField}), '0') FROM {tableName}"; object result = MyMeans.getcom(sql).ExecuteScalar(); // 假设 getcom 已扩展支持 ExecuteScalar string maxId = result?.ToString() ?? "0"; int num = 0; if (maxId.StartsWith(prefix)) { string digits = Regex.Match(maxId, $@"{prefix}(\d+)").Groups[1].Value; if (!string.IsNullOrEmpty(digits)) num = int.Parse(digits); } return $"{prefix}{(num + 1).ToString("D5")}"; // G00001, G00002... }

注意:GetAutocoding必须在事务中调用,否则高并发下可能生成重复 ID。教学项目可忽略,但生产环境务必用SELECT ... WITH (UPDLOCK, HOLDLOCK)加锁。

3.4 UI 刷新:listView1.Items.Add(l)的局限与升级

ListViewItem l = new ListViewItem(); l.Text = txtName.Text; l.SubItems.Add(comboxType.Text); l.SubItems.Add(txtPrice.Text); l.SubItems.Add(txtKeep.Text); l.SubItems.Add(txtRemark.Text); listView1.Items.Add(l);

ListView是 WinForms 时代的数据展示控件,优点是轻量,缺点是无法排序、搜索、编辑。当商品数超 100 条,滚动卡顿。现代替代方案是DataGridView,它原生支持:

  • 点击列头自动排序;
  • BindingSource绑定DataTable,数据变更自动刷新 UI;
  • 双击单元格直接编辑。

升级只需两步:

  1. 将listView1替换为dataGridView1;
  2. 插入商品后,重新加载数据源:
// 添加商品后刷新 DataGridView private void RefreshGoodsGrid() { string sql = "SELECT merchID as 编号, fullName as 名称, kind as 类别, unitCost as 单价, others as 备注 FROM Goods_Info"; dataGridView1.DataSource = MyMeans.getTable(sql); // 假设已实现 getTable }

这样,UI 与数据彻底解耦,后续加搜索框、导出 Excel 都变得极其简单。

4. 避坑指南:那些让新手卡住 3 小时的“小问题”

这份报告诞生于 2015 年,运行环境是 Windows XP + SQL Server 2005,而你现在大概率用 Windows 11 + SQL Server 2022。表面是版本升级,实则是无数隐藏坑点。以下是我和团队在复现过程中踩过的 4 个典型问题,按“现象 → 原因 → 解决”列出,全是实测有效方案。

4.1 现象:登录时MessageBox.Show("用户名或密码错误!")弹出,但数据库明明有数据

原因:SQL Server 默认启用Windows 身份验证,而报告代码用的是Integrated Security=True(即 Windows 认证),但你的当前 Windows 用户在 SQL Server 中没有db_SMMS数据库的db_datareader权限。
解决:

  1. 打开 SQL Server Management Studio,用管理员账户登录;
  2. 展开“安全性” → “登录名”,右键你的 Windows 用户(如DESKTOP-XXX\YourName)→ “属性”;
  3. 左侧选“用户映射”,勾选db_SMMS,右侧角色成员中勾选db_datareader和db_datawriter;
  4. 重启应用。

提示:若不想用 Windows 认证,可改用 SQL Server 认证。在连接字符串中将Integrated Security=True改为User ID=sa;Password=your_password;,并确保sa账户已启用(SSMS → 安全性 → 登录名 → sa → 右键“属性” → 状态 → 登录 → 启用)。

4.2 现象:点击“添加商品”报错Invalid column name 'StockID'

原因:报告第 5.3 节建表 SQL 中CREATE TABLE Stock(... PRIMARY KEY (StockID))是笔误,Stock表根本没有StockID字段,只有GoodsID。GetAutocoding("tb_Stock", "StockID")调用时,试图查不存在的字段。
解决:

  1. 删除原Stock表:DROP TABLE Stock;
  2. 用 2.2 节修正后的CREATE TABLE Stock脚本重建;
  3. 修改GetAutocoding调用,GetAutocoding("Stock", "GoodsID")无意义(主键是GoodsID,不能自增),应直接用商品编号RecID作为Stock.GoodsID,删除RecID2相关代码。库存表插入语句简化为:
Find_SQL2 = $"INSERT INTO Stock (GoodsID, Amount) VALUES ('{RecID}', 0)";

4.3 现象:MyMeans.getcom()执行后,后续数据库操作报错There is already an open DataReader associated with this Command

原因:报告代码中getcom()返回SqlDataReader,但未在使用后调用temDR.Close()。SqlDataReader占用连接,导致同一连接上无法执行第二个命令。
解决:

  1. 在getcom()方法末尾,强制关闭 reader:
public SqlDataReader getcom(string sql) { // ... 前面代码不变 SqlDataReader reader = cmd.ExecuteReader(); // 关键:设置 CommandBehavior.CloseConnection,让 reader 关闭时自动关连接 return cmd.ExecuteReader(CommandBehavior.CloseConnection); }
  1. 或者,更推荐——彻底弃用SqlDataReader,统一用getTable()返回DataTable(见 2.3 节),它不占用连接,可反复读取。

4.4 现象:comboxType下拉框为空,添加商品时comboxType.SelectedIndex报ArgumentOutOfRangeException

原因:报告未提供comboxType的数据源初始化代码。comboxType是商品类别下拉框,应从GoodsType表(报告未定义,但Goods_Info.kind字段暗示存在)加载,但代码里没写comboxType.DataSource = ...。
解决:

  1. 新建GoodsType表(若无):
CREATE TABLE GoodsType ( TypeID CHAR(6) PRIMARY KEY NOT NULL, TypeName NVARCHAR(20) NOT NULL ); INSERT INTO GoodsType VALUES ('T00001', '水果'), ('T00002', '蔬菜'), ('T00003', '日用品');
  1. 在窗体Load事件中初始化:
private void Form_Load(object sender, EventArgs e) { string typeSql = "SELECT TypeID, TypeName FROM GoodsType"; DataTable typeDt = MyMeans.getTable(typeSql); comboxType.DataSource = typeDt; comboxType.DisplayMember = "TypeName"; // 显示文字 comboxType.ValueMember = "TypeID"; // 实际值 }
  1. 添加商品时,用comboxType.SelectedValue.ToString()取TypeID,而非SelectedIndex。

5. 二次开发实战:给库存模块加“低于阈值自动预警”功能

报告的库存管理模块(第 6.7 节)仅实现查询,但真实超市最痛的点是:临期商品没人管、畅销品突然断货、滞销品积压仓库。我们以“库存低于阈值自动预警”为例,演示如何在不破坏原有架构的前提下,快速增加一个高价值功能。这不是加个按钮那么简单,而是要打通数据层、业务层、表现层。

5.1 数据层:为Stock表增加预警阈值字段

原Stock表只有GoodsID和Amount,预警需要“安全库存量”。直接修改表结构:

-- 为 Stock 表添加安全库存字段(默认 5,可按商品调整) ALTER TABLE Stock ADD SafeAmount INT NOT NULL DEFAULT 5; -- 为现有商品批量设置(示例:所有商品安全库存设为 10) UPDATE Stock SET SafeAmount = 10;

注意:SafeAmount是业务字段,不是数据库约束。它允许不同商品有不同阈值(如牛奶设 20,纸巾设 5),比全局阈值更灵活。

5.2 业务层:编写预警检查方法,并集成到关键流程

在MyMeans类中新增方法,检查所有库存低于阈值的商品:

// DataClass/MyMeans.cs 新增 public static DataTable GetLowStockAlerts() { string sql = @" SELECT g.merchID as 商品编号, g.fullName as 商品名称, s.Amount as 当前库存, s.SafeAmount as 安全库存, (s.SafeAmount - s.Amount) as 缺货量 FROM Stock s INNER JOIN Goods_Info g ON s.GoodsID = g.merchID WHERE s.Amount < s.SafeAmount ORDER BY 缺货量 DESC"; return getTable(sql); }

然后,在两个关键时机触发检查:

  • 程序启动时:在主窗体Load事件中调用,首次加载预警列表;
  • 每次销售后:在Sell表插入成功后,立即检查该商品库存是否跌破阈值。

以销售模块为例,修改Sell插入逻辑(报告中未给出,但可推断):

// 销售成功后,检查并预警 private void RecordSale(string goodsId, int amountSold) { // ... 原有插入 Sell 表逻辑 string sellSql = $"INSERT INTO Sell (SeelID, GoodsID, Amount, Date) VALUES (...)"; MyMeans.getsqlcom(sellSql); // 关键:更新 Stock 表,并检查预警 string updateStockSql = $"UPDATE Stock SET Amount = Amount - {amountSold} WHERE GoodsID = '{goodsId}'"; MyMeans.getsqlcom(updateStockSql); // 检查该商品是否低于阈值 DataTable alert = MyMeans.GetLowStockAlerts(); if (alert.Rows.Count > 0 && alert.Select($"商品编号 = '{goodsId}'").Length > 0) { // 弹窗预警(可升级为邮件/微信通知) string msg = $"警告:{alert.Rows[0]["商品名称"]} 库存仅剩 {alert.Rows[0]["当前库存"]},低于安全库存 {alert.Rows[0]["安全库存"]}!"; MessageBox.Show(msg, "库存预警", MessageBoxButtons.OK, MessageBoxIcon.Warning); } }

5.3 表现层:用 DataGridView 展示预警列表,并支持一键补货

在主窗体添加一个TabControl,新建“预警中心”页签,放置dataGridViewAlert:

// 主窗体中初始化预警列表 private void LoadAlertTab() { dataGridViewAlert.AutoGenerateColumns = false; // 手动定义列,避免显示多余字段 dataGridViewAlert.Columns.Add(new DataGridViewTextBoxColumn { Name = "商品编号", HeaderText = "商品编号", DataPropertyName = "商品编号" }); dataGridViewAlert.Columns.Add(new DataGridViewTextBoxColumn { Name = "商品名称", HeaderText = "商品名称", DataPropertyName = "商品名称" }); dataGridViewAlert.Columns.Add(new DataGridViewTextBoxColumn { Name = "当前库存", HeaderText = "当前库存", DataPropertyName = "当前库存" }); dataGridViewAlert.Columns.Add(new DataGridViewTextBoxColumn { Name = "安全库存", HeaderText = "安全库存", DataPropertyName = "安全库存" }); dataGridViewAlert.Columns.Add(new DataGridViewTextBoxColumn { Name = "缺货量", HeaderText = "缺货量", DataPropertyName = "缺货量" }); // 添加“补货”按钮列 DataGridViewButtonColumn btnCol = new DataGridViewButtonColumn(); btnCol.Name = "补货"; btnCol.HeaderText = "补货"; btnCol.Text = "申请补货"; btnCol.UseColumnTextForButtonValue = true; dataGridViewAlert.Columns.Add(btnCol); } // 绑定数据并处理按钮点击 private void RefreshAlertGrid() { dataGridViewAlert.DataSource = MyMeans.GetLowStockAlerts(); } // 按钮点击事件:跳转到进货管理界面,预填商品信息 private void dataGridViewAlert_CellClick(object sender, DataGridViewCellEventArgs e) { if (e.ColumnIndex == dataGridViewAlert.Columns["补货"].Index && e.RowIndex >= 0) { DataGridViewRow row = dataGridViewAlert.Rows[e.RowIndex]; string goodsId = row.Cells["商品编号"].Value.ToString(); // 打开进货窗体,并传参 BuyForm buyForm = new BuyForm(goodsId); buyForm.ShowDialog(); } }

这样,一个完整的预警闭环就形成了:数据层有阈值字段、业务层有检查逻辑、表现层有可视化列表和操作入口。它没动原有任何模块,只是“插件式”增强,符合教学项目渐进式开发原则。

从那以后我每次给实训项目加功能,都强制走一遍“数据层 → 业务层 → 表现层”三步验证:先写 SQL 确保字段存在,再写方法确保逻辑可调,最后写 UI 确保用户能用。这比一上来就堆界面靠谱得多。希望帮到你。

本文还有配套的精品资源,点击获取