尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java Swing花店管理系统实战:MVC架构与GUI业务流设计

Java Swing花店管理系统实战:MVC架构与GUI业务流设计 简介这是一套面向高校Java课程设计的花店管理系统纯GUI实现专为Java初学者及课设学生打造聚焦基础Swing组件开发与数据库交互能力训练完全满足课程验收与答辩需求。资源包共54个文件含10个核心Java源码如Login.java、Admin.java、Flower.java等、22个编译后class文件、9个XML配置文件用于界面布局与数据映射、11张系统界面截图涵盖登录、管理、查询等关键操作场景以及项目配置文件iml、gitignore和IDEA工程配置项整体仅202KB轻量易部署。已有141人学习下载说明其结构清晰、运行稳定、贴合教学实际。用户可直接导入IDE运行获得完整可执行系统包含用户注册登录、鲜花信息增删改查、管理员后台管理等全功能模块所有SQL脚本已内置于压缩包无需额外配置即可连接本地MySQL完成全流程验证。1. 用 Swing 搭建可运行、可扩展的花店管理系统 GUI不是“画界面”而是“建业务流”这不是一个只拖拽按钮、填几个 JTextField 就交差的 Java 课设演示工程。真正的花店管理系统 GUI必须承载「库存实时变动」「订单状态流转」「员工权限隔离」「商品分类检索」四类核心业务逻辑——而这些全要靠 Swing 组件的事件驱动模型、MVC 分层结构和数据模型绑定来落地。很多同学卡在“界面能弹出来但点不动”本质是没把 JButton 的 ActionListener 和业务 Service 层解耦也有人用 NetBeans GUI Builder 自动生成代码结果改个布局就编译报错因为没理解 GroupLayout 与手动布局的生命周期差异。本文面向已掌握 Java 基础语法含集合、IO、异常处理但尚未独立完成过完整 GUI 项目的同学从零写出可编译、可调试、可增删功能模块的花店系统重点讲清为什么选 DefaultTableModel 而非 Vector 承载表格数据、如何用 CardLayout 实现收银台/库存管理/员工设置三屏切换、怎样通过 PropertyChangeListener 监听商品价格变更并自动刷新销售报表——所有代码均可直接粘贴进 IDEA 运行不依赖任何第三方 UI 库。2. 用 Swing MVC 构建可维护的花店系统骨架2.1 为什么不用 JavaFX 或第三方框架课设场景下的技术选型逻辑Java 课设明确要求“纯 GUI”且多数高校教学环境仍以 JDK 8/11 为基准IDEA 默认配置未启用 JavaFX 模块。Swing 的优势在于JDK 原生支持无需额外依赖、组件事件模型成熟ActionListener/DocumentListener 稳定、布局管理器对小规模业务界面控制力强GridBagLayout 可精确约束商品列表宽度。对比 JavaFX其 Scene Builder 需单独安装、FXML 加载易出 ClassNotFoundException对比 SWT需匹配操作系统本地库Windows/Linux/macOS 二进制不同对比 TornadoFXKotlin脱离 Java 课设语言要求。实际开发中我们采用“Swing 组件 自定义 Model 独立 Controller”的轻量 MVCJFrame 仅负责容器生命周期JTable 数据由 FlowerInventoryModel 管理按钮点击逻辑全部委托给 OrderController 处理——这样当老师要求增加“会员折扣计算”功能时只需新增 DiscountService 类无需修改任何 GUI 代码。提示不要在 JFrame 构造方法里写业务逻辑。所有数据初始化如加载 flower.csv 文件应放在 Controller 的 init() 方法中GUI 类只做视图渲染。2.2 核心 GUI 组件选型与职责划分表组件类型具体类名选用理由典型使用场景关键参数说明主窗口容器JFrame支持 setDefaultCloseOperation(EXIT_ON_CLOSE)整个系统主窗体setExtendedState(JFrame.MAXIMIZED_BOTH)实现全屏适配导航面板JTabbedPane内置标签页切换无需手动管理 CardLayout收银台/库存管理/员工设置三模块setTabPlacement(JTabbedPane.LEFT)左侧标签提升操作效率商品列表JTableDefaultTableModel表格数据可动态增删支持排序监听显示鲜花名称、单价、库存量model.addRow(new Object[]{name, price, stock})插入新行表单输入JTextFieldJSpinnerSpinner 对数字输入防误输如库存量不能为负商品价格输入框、库存数量调整spinner.setModel(new SpinnerNumberModel(0, 0, 999, 1))设定范围步长操作按钮JButton事件响应稳定支持图标文字混合“添加商品”、“生成订单”、“导出报表”button.setActionCommand(ADD_FLOWER)统一命令标识便于 Controller 分发2.3 创建可复用的 Flower 类与库存数据模型Flower 类不是简单 POJO需实现Comparable接口支持按价格排序并重写toString()便于 JList 显示public class Flower implements ComparableFlower { private String id; // 唯一编码如 ROSE-001 private String name; // 名称如 红玫瑰 private double price; // 单价元 private int stock; // 库存数量 private String category; // 分类切花、盆栽、花束 public Flower(String id, String name, double price, int stock, String category) { this.id id; this.name name; this.price price; this.stock stock; this.category category; } // getter/setter 省略课设要求必须有 Override public int compareTo(Flower o) { return Double.compare(this.price, o.price); // 按价格升序 } Override public String toString() { return String.format(%s (%.2f元), name, price); // JList 显示格式 } }库存数据模型继承DefaultTableModel封装增删改查逻辑public class FlowerInventoryModel extends DefaultTableModel { private ListFlower flowers new ArrayList(); public FlowerInventoryModel() { super(new Object[]{编号, 名称, 单价(元), 库存, 分类}, 0); } // 向表格添加一行数据 public void addFlower(Flower f) { flowers.add(f); addRow(new Object[]{f.getId(), f.getName(), f.getPrice(), f.getStock(), f.getCategory()}); } // 根据编号删除商品业务关键删除前校验库存是否为0 public boolean removeFlowerById(String id) { for (int i 0; i flowers.size(); i) { if (flowers.get(i).getId().equals(id)) { if (flowers.get(i).getStock() 0) { return false; // 库存未清空禁止删除 } flowers.remove(i); removeRow(i); return true; } } return false; } // 获取指定位置的商品对象供双击编辑使用 public Flower getFlowerAt(int rowIndex) { return flowers.get(rowIndex); } }注意removeRow(i)只删除表格显示行flowers.remove(i)才真正移除内存对象。两者必须同步调用否则出现“界面上删了但数据还在”的 Bug。3. 实现三大核心业务模块的 GUI 交互逻辑3.1 收银台模块用 JComboBox 动态联动与订单生成收银台需解决“选商品 → 输数量 → 计算金额 → 生成订单”闭环。关键点在于 JComboBox 的ItemListener与 JTextField 的DocumentListener联动// 商品选择下拉框加载所有鲜花 JComboBoxFlower flowerCombo new JComboBox(flowerModel.toArray(new Flower[0])); flowerCombo.addItemListener(e - { if (e.getStateChange() ItemEvent.SELECTED) { Flower selected (Flower) flowerCombo.getSelectedItem(); priceField.setText(String.valueOf(selected.getPrice())); // 自动填充单价 stockLabel.setText(库存 selected.getStock()); // 显示当前库存 } }); // 数量输入框实时计算小计 quantityField.getDocument().addDocumentListener(new DocumentListener() { Override public void insertUpdate(DocumentEvent e) { updateSubtotal(); } Override public void removeUpdate(DocumentEvent e) { updateSubtotal(); } Override public void changedUpdate(DocumentEvent e) { updateSubtotal(); } private void updateSubtotal() { try { double price Double.parseDouble(priceField.getText()); int qty Integer.parseInt(quantityField.getText()); subtotalLabel.setText(小计 String.format(%.2f, price * qty)); } catch (NumberFormatException ex) { subtotalLabel.setText(小计0.00); } } });生成订单按钮触发完整流程orderButton.addActionListener(e - { String flowerId ((Flower) flowerCombo.getSelectedItem()).getId(); int quantity Integer.parseInt(quantityField.getText()); double totalPrice Double.parseDouble(subtotalLabel.getText().substring(3)); // 1. 库存扣减业务强校验 Flower target inventoryModel.getFlowerById(flowerId); if (target.getStock() quantity) { JOptionPane.showMessageDialog(null, 库存不足当前剩余 target.getStock()); return; } target.setStock(target.getStock() - quantity); // 2. 更新表格显示 int row inventoryModel.findRowById(flowerId); inventoryModel.setValueAt(target.getStock(), row, 3); // 更新库存列 // 3. 写入订单日志文件课设硬性要求 try (PrintWriter writer new PrintWriter(new FileWriter(orders.log, true))) { writer.println(LocalDateTime.now() \t flowerId \t quantity \t totalPrice); } catch (IOException ex) { ex.printStackTrace(); } JOptionPane.showMessageDialog(null, 订单生成成功); });3.2 库存管理模块用 JTable 编辑器实现双击修改JTable 默认不可编辑需为每列设置TableCellEditor。价格列用JSpinner库存列用JSpinner分类列用JComboBox// 为价格列设置 Spinner 编辑器 TableColumn priceCol table.getColumnModel().getColumn(2); priceCol.setCellEditor(new DefaultCellEditor(new JSpinner( new SpinnerNumberModel(0.0, 0.0, 9999.99, 0.01) ))); // 为库存列设置 Spinner 编辑器整数步长 TableColumn stockCol table.getColumnModel().getColumn(3); stockCol.setCellEditor(new DefaultCellEditor(new JSpinner( new SpinnerNumberModel(0, 0, 9999, 1) ))); // 为分类列设置下拉编辑器 String[] categories {切花, 盆栽, 花束, 永生花}; JComboBoxString categoryCombo new JComboBox(categories); TableColumn categoryCol table.getColumnModel().getColumn(4); categoryCol.setCellEditor(new DefaultCellEditor(categoryCombo));双击单元格触发编辑后需监听TableModelEvent捕获修改inventoryModel.addTableModelListener(e - { if (e.getType() TableModelEvent.UPDATE) { int row e.getFirstRow(); Flower updated inventoryModel.getFlowerAt(row); // 同步更新内存对象避免表格显示与对象状态不一致 updated.setPrice((Double) inventoryModel.getValueAt(row, 2)); updated.setStock((Integer) inventoryModel.getValueAt(row, 3)); updated.setCategory((String) inventoryModel.getValueAt(row, 4)); } });3.3 员工设置模块用 JTree 管理角色权限树课设常要求“管理员可修改价格店员只能查看”。用 JTree 构建权限节点树右键菜单触发权限分配// 权限树数据模型 DefaultMutableTreeNode root new DefaultMutableTreeNode(权限体系); DefaultMutableTreeNode roleNode new DefaultMutableTreeNode(角色); DefaultMutableTreeNode adminNode new DefaultMutableTreeNode(管理员); DefaultMutableTreeNode staffNode new DefaultMutableTreeNode(店员); roleNode.add(adminNode); roleNode.add(staffNode); root.add(roleNode); JTree permissionTree new JTree(root); permissionTree.setShowsRootHandles(true); // 右键菜单 JPopupMenu popup new JPopupMenu(); JMenuItem grantItem new JMenuItem(授予此权限); grantItem.addActionListener(e - { TreePath path permissionTree.getSelectionPath(); if (path ! null path.getPathCount() 3) { // 确保选中叶子节点 String roleName path.getPathComponent(1).toString(); String permName path.getPathComponent(2).toString(); // 实际存储到 employeePermissions Map 中 permissions.put(roleName, permName); JOptionPane.showMessageDialog(null, 已授予 roleName [ permName ]); } }); popup.add(grantItem); permissionTree.addMouseListener(new MouseAdapter() { Override public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) { permissionTree.setSelectionPath(permissionTree.getPathForLocation(e.getX(), e.getY())); popup.show(permissionTree, e.getX(), e.getY()); } } });4. 解决课设高频报错与性能优化技巧4.1 四类必现 Bug 的定位与修复方案报错现象根本原因修复命令/代码java.lang.NullPointerException在table.getValueAt(row, col)未检查row是否为-1用户未选中行if (row -1) { JOptionPane.showMessageDialog(null, 请先选择一行); return; }表格数据不刷新fireTableRowsUpdated()无效直接修改Vector数据源而非调用model.addRow()改用model.setValueAt(value, row, col)或model.fireTableDataChanged()窗口关闭后程序未退出进程残留setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)未改为EXIT_ON_CLOSEframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)必须显式设置中文乱码文件读写/控制台输出JVM 默认编码非 UTF-8启动参数加-Dfile.encodingUTF-8或代码中new InputStreamReader(new FileInputStream(data.csv), UTF-8)4.2 课设答辩必答如何让 GUI 响应更快学生常被问“为什么点按钮要等 2 秒才出结果”。真实瓶颈不在 GUI 渲染而在 IO 或计算阻塞 EDT事件分发线程。解决方案是将耗时操作移出 EDT// ❌ 错误在 ActionListener 中直接读文件 saveButton.addActionListener(e - { saveToCsv(); // 阻塞 EDT界面冻结 }); // ✅ 正确用 SwingWorker 异步执行 saveButton.addActionListener(e - { SwingWorkerVoid, Void worker new SwingWorkerVoid, Void() { Override protected Void doInBackground() throws Exception { saveToCsv(); // 在后台线程执行 return null; } Override protected void done() { try { get(); // 等待完成 JOptionPane.showMessageDialog(null, 保存成功); } catch (Exception ex) { JOptionPane.showMessageDialog(null, 保存失败 ex.getMessage()); } } }; worker.execute(); });4.3 用 Properties 文件管理界面配置避免硬编码课设验收常要求“修改字体大小只需改一个地方”。将界面参数外置到config.properties# config.properties ui.font.size14 ui.table.row.height25 ui.button.width100 theme.color.primary#4CAF50加载方式Properties config new Properties(); try (InputStream is getClass().getClassLoader().getResourceAsStream(config.properties)) { config.load(is); } int fontSize Integer.parseInt(config.getProperty(ui.font.size, 12)); Font defaultFont new Font(微软雅黑, Font.PLAIN, fontSize); UIManager.put(Label.font, defaultFont); UIManager.put(Button.font, defaultFont);5. 课设加分项用 JFreeChart 嵌入销售趋势图答辩时展示动态图表能显著提升评分。JFreeChart 是 Swing 兼容性最好的图表库无需 JavaFX!-- pom.xml 添加依赖若用 Maven -- dependency groupIdorg.jfree/groupId artifactIdjfreechart/artifactId version1.5.3/version /dependency嵌入到 JPanel 的最小可行代码private JFreeChart createSalesChart() { // 模拟最近7天销售额课设可用静态数据 DefaultCategoryDataset dataset new DefaultCategoryDataset(); String[] days {周一, 周二, 周三, 周四, 周五, 周六, 周日}; int[] sales {1200, 1500, 1350, 1800, 2100, 3200, 2800}; for (int i 0; i days.length; i) { dataset.addValue(sales[i], 销售额(元), days[i]); } JFreeChart chart ChartFactory.createBarChart( 本周销售趋势, 日期, 销售额元, dataset, PlotOrientation.VERTICAL, true, true, false ); chart.setBackgroundPaint(Color.WHITE); return chart; } // 将图表嵌入 GUI ChartPanel chartPanel new ChartPanel(createSalesChart()); chartPanel.setPreferredSize(new Dimension(600, 300)); tabbedPane.addTab(销售统计, chartPanel);提示课设中图表数据不必真实但必须体现“数据来源可替换”。在createSalesChart()方法开头加注释// TODO: 此处应从 orders.log 读取真实数据按日期聚合表明你理解数据流向。用ChartPanel替代JPanel是关键——它自动处理重绘、缩放、鼠标悬停提示且完全兼容 Swing 布局管理器。当老师问“怎么让柱状图颜色随销售额变化”你只需指出BarRenderer的setSeriesPaint()方法即可证明掌握扩展点。本文还有配套的精品资源点击获取
返回列表