尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C++Builder实战:从零构建带生产管理的进销存桌面系统

C++Builder实战:从零构建带生产管理的进销存桌面系统 1. 项目概述为什么选择CBuilder做进销存如果你在中小型制造业或者商贸公司待过肯定对“进销存”这三个字不陌生。进货、销售、库存听起来简单但真要把这三条线拧成一股绳让数据实时、准确、可追溯背后是一套复杂的业务逻辑和数据流转。市面上有成百上千的通用进销存软件但真正能完美贴合你公司那套独特的业务流程、审批规则、报表格式的凤毛麟角。这就是为什么很多企业尤其是那些有特殊生产管理需求的中小企业最终会选择自己开发或者找外包定制。那么开发工具怎么选看到“excelaccess 开发带生产的进销存系统”这个热词我特别有感触。这确实是很多小老板或非专业开发者的第一选择Excel做界面和简单计算Access当后台数据库上手快零成本。我见过不少小作坊用这种方式勉强维持了几年。但问题也显而易见数据量稍大就卡顿多用户同时操作极易冲突或数据丢失复杂的生产BOM物料清单和工序管理用Excel公式堆砌维护起来简直是噩梦更别提生成灵活的自定义报表了。当业务发展到一定规模这套“土法炼钢”的系统就成了最大的瓶颈。所以当我们需要一个更健壮、更高效、更能承载业务成长的解决方案时一个真正的桌面客户端开发工具就进入了视野。CBuilder这个经典的RAD快速应用开发工具就是其中一个非常务实的选择。它不像Java或.NET那样需要庞大的运行时环境编译出来就是一个独立的exe部署极其简单它拥有强大的VCL组件库拖拖拽拽就能做出专业级的Windows窗体界面开发效率很高更重要的是它原生支持多种数据库访问从本地的Access、SQLite到服务器的MySQL、SQL Server、Oracle都能轻松连接。对于熟悉C或Delphi的开发者或者那些希望系统拥有极致性能和控制力的团队来说CBuilder构建的进销存系统在响应速度、本地计算能力如复杂的成本核算和与硬件集成如扫码枪、条码打印机方面有着天然的优势。这个实战指南就是带你走一遍用CBuilder 10.4 Sydney社区版免费足够用开发一个涵盖基础进销存并扩展了简单生产管理模块的桌面系统全过程。我会重点分享那些官方手册里不会写的“坑”和“技巧”目标是让你做出的系统不仅能用而且稳定、高效、易于维护。2. 系统核心架构与数据库设计2.1 业务模块拆解与数据流设计一个完整的带生产管理的进销存系统远不止进货、销售、库存三个表。我们需要从业务流的角度来设计模块和数据流。核心业务流采购流程采购申请 - 供应商选择 - 采购订单 - 到货入库 - 应付账款。这里的关键是采购订单和入库单的分离允许分批到货。销售流程客户询价 - 销售报价 - 销售订单 - 发货出库 - 应收账款。同样销售订单和出库单分离支持分批发货。生产流程这是区别于普通进销存的核心。流程为销售订单或预测 - 主生产计划MPS - 物料需求计划MRP - 生成生产工单 - 领料出库 - 生产报工 - 成品入库。我们实战中会简化MPS/MRP聚焦于工单管理。库存核心所有流程都汇聚于库存。每一次入库采购、生产、退货、出库销售、生产领料、损耗都必须实时更新库存并记录库存流水库存台账这是对账和追溯的生命线。模块划分基础数据模块商品信息含原材料、半成品、成品、供应商、客户、仓库、部门、员工。这是系统的基石设计不好后面全乱。采购管理模块供应商管理、采购订单、采购入库单、采购退货单。销售管理模块客户管理、销售报价、销售订单、销售出库单、销售退货单。生产管理模块物料清单BOM管理、生产工单、生产领料单、生产入库单、工序报工可选。库存管理模块其他入库/出库单如盘盈盘亏、调拨、实时库存查询、库存流水台账、库存预警低库存、高库存。财务管理模块简化应收款、应付款流水记录与业务单关联。成熟的系统会对接专业财务软件我们初期可只做业务流水。报表中心模块基于以上数据生成采购/销售/库存/生产各类统计报表。注意不要试图在第一版就实现所有功能。建议采用迭代开发先实现基础数据、采购、销售、库存核心流水跑通主业务流程再加入生产模块。贪多嚼不烂。2.2 数据库选型与表结构设计要点数据库选型小型/单机版SQLite。零配置单文件CBuilder通过FireDAC组件原生完美支持。非常适合初期演示、微小企业或移动办公场景。但并发性能较弱。主流/网络版MySQL或Microsoft SQL Server Express。免费性能好支持多用户并发。CBuilder的FireDAC或dbExpress组件连接它们都很方便。本项目后续以MySQL为例。关键表结构设计心得商品表 (products)这是最复杂的表之一。CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(50) UNIQUE NOT NULL COMMENT 商品编码唯一, name VARCHAR(100) NOT NULL, spec VARCHAR(200) COMMENT 规格型号, unit VARCHAR(20) NOT NULL COMMENT 计量单位个、KG、米, category_id INT COMMENT 分类ID, type TINYINT NOT NULL DEFAULT 1 COMMENT 类型1-原材料2-半成品3-成品4-耗材, purchase_price DECIMAL(12,4) DEFAULT 0 COMMENT 最近采购价, sale_price DECIMAL(12,4) DEFAULT 0 COMMENT 标准售价, cost_method TINYINT DEFAULT 1 COMMENT 成本计价法1-移动加权平均, current_stock DECIMAL(12,4) DEFAULT 0 COMMENT 当前总库存实时计算可冗余, min_stock DECIMAL(12,4) DEFAULT 0 COMMENT 安全库存下限, max_stock DECIMAL(12,4) DEFAULT 0 COMMENT 库存上限, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;心得1code编码必须是唯一键且不可变。很多系统用id做内部关联用code做人工识别和导入导出。编码规则可以定为“类型字母序列号”如RM001原材料FG100成品。心得2type字段至关重要它决定了该物料在BOM中扮演的角色父项或子项以及能否被销售。心得3库存数量current_stock可以在商品表中冗余存储通过触发器或事务与库存流水表保持一致以提升查询效率。但所有库存变动必须通过流水表(stock_ledger)驱动。单据头与单据明细表这是进销存设计的核心模式务必掌握。采购订单表 (purchase_orders)存储订单概要信息单号、供应商、日期、总金额、状态。采购订单明细表 (purchase_order_details)存储订单中的商品明细商品ID、数量、单价、金额、备注。外键关联到头表id。所有业务单据销售订单、入库单、出库单、生产工单都应遵循这种“一头多细”的模式。好处结构清晰查询方便符合关系数据库范式。库存流水表 (stock_ledger)系统的“黑匣子”所有库存变动的唯一依据。CREATE TABLE stock_ledger ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, warehouse_id INT NOT NULL COMMENT 仓库ID, related_type VARCHAR(20) NOT NULL COMMENT 关联单据类型PO-采购入库SO-销售出库MO-生产领料MI-生产入库ADJ-调整, related_id INT NOT NULL COMMENT 关联单据ID如入库单id, related_detail_id INT COMMENT 关联单据明细ID, quantity DECIMAL(12,4) NOT NULL COMMENT 变动数量正为入库负为出库, unit_cost DECIMAL(12,4) NOT NULL COMMENT 当时单价用于成本计算, balance_quantity DECIMAL(12,4) NOT NULL COMMENT 变动后结存数量, balance_amount DECIMAL(12,4) NOT NULL COMMENT 变动后结存金额, trans_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, operator_id INT COMMENT 操作员 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;心得related_type和related_id构成了追溯任何一笔库存变化的完整链路。通过这个表可以重建任意时间点的库存快照这是实现库存台账和成本核算如移动加权平均的基础。任何绕过此表的直接库存数量修改都是绝对禁止的物料清单表 (boms)实现生产管理的灵魂。CREATE TABLE boms ( id INT PRIMARY KEY AUTO_INCREMENT, parent_product_id INT NOT NULL COMMENT 父项产品成品/半成品ID, component_product_id INT NOT NULL COMMENT 子项组件原材料/半成品ID, quantity_required DECIMAL(12,4) NOT NULL COMMENT 单件父项所需子项数量, loss_rate DECIMAL(5,4) DEFAULT 0 COMMENT 损耗率, effective_date DATE NOT NULL COMMENT BOM生效日期, is_active BOOLEAN DEFAULT TRUE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;心得1一个成品可能有多个版本的BOM用effective_date和is_active来控制有效性。生产工单必须关联到某个具体的BOM版本。心得2BOM可以是多层的半成品也有自己的BOM。在计算生产需求时需要进行递归或迭代展开MRP的核心。我们初期可以先实现单层BOM的直接领料。3. CBuilder开发环境搭建与核心组件运用3.1 环境准备与工程配置安装CBuilder 10.4 Community Edition从Embarcadero官网下载免费社区版。安装时注意勾选FireDAC数据库组件包和InterBase本地数据库可选。创建新工程建议创建一个MDI多文档界面或单窗体PageControl的应用程序。对于进销存这类多模块系统MDI更传统但单窗体动态创建Frame的方式现在更流行内存管理更清晰。配置数据库连接在窗体上放置一个TFDConnection组件FireDAC的主连接。双击配置其Params属性。对于MySQL典型参数如下DriverIDMySQL Serverlocalhost Databaseyour_inventory_db User_Nameroot Passwordyourpassword CharacterSetutf8mb4重要技巧将连接字符串保存在外部配置文件如.ini文件中而不是硬编码在程序里。使用TFDManager来管理连接定义便于后期切换数据库或修改配置。测试连接确保能连上你的MySQL数据库。3.2 主界面设计与模块化思维不要在一个窗体上堆砌所有按钮。采用经典的“菜单栏导航树/列表工作区”布局。主窗体 (MainForm)包含TMainMenu、TToolBar、TStatusBar以及一个TPanel作为导航容器一个TPanel作为工作区容器。导航方式方案A树形使用TTreeView根节点为模块采购、销售、库存...子节点为具体功能采购订单列表、新建订单...。点击节点时在工作区动态创建并显示对应的功能窗体或Frame。方案B图标列表使用TFlowPanel或TGridPanel容纳一系列功能图标按钮更直观现代。动态创建窗体/Frame这是保持主界面清爽的关键。// 示例在MDI或工作区中动态创建并显示“商品列表”窗体 void __fastcall TMainForm::ShowProductListForm(TObject *Sender) { TFormProductList *form new TFormProductList(Application); // 动态创建 form-FDConnection this-FDConnection1; // 传递数据库连接 // 如果是MDI // form-MdiParent this; // form-Show(); // 如果是Frame模式 ClearWorkArea(); // 清空工作区现有控件 form-Parent pnlWorkArea; // 将窗体嵌入到工作区Panel form-Align alClient; form-BorderStyle bsNone; form-Show(); }心得为每个功能模块如商品管理、订单录入创建独立的窗体或Frame类。通过传递共享的TFDConnection来确保所有模块使用同一数据库连接避免连接泄露。3.3 FireDAC组件链实战从连接到展示FireDAC是CBuilder进行数据库开发的利器。理解其组件链是高效开发的关键。标准数据操作链TFDConnection-TFDTransaction-TFDQuery/TFDTable-TDataSource-TDBGrid/TDBEdit。连接与事务 (TFDConnection,TFDTransaction)一个应用通常只需一个全局的TFDConnection。强烈建议为所有写操作增删改显式使用事务。这能保证数据一致性特别是在处理“一头多细”单据时。FDConnection1-StartTransaction(); // 开始事务 try { // 1. 保存采购订单头 FDQueryOrderHeader-SQL-Text INSERT INTO purchase_orders ...; FDQueryOrderHeader-ExecSQL(); int newOrderId FDConnection1-GetLastAutoGenValue(purchase_orders); // 获取自增ID // 2. 循环保存采购订单明细 for(...) { FDQueryOrderDetail-SQL-Text INSERT INTO purchase_order_details (order_id, ...) VALUES (:order_id, ...); FDQueryOrderDetail-ParamByName(order_id)-AsInteger newOrderId; // ... 设置其他参数 FDQueryOrderDetail-ExecSQL(); } // 3. 可能还需要更新库存流水... FDConnection1-Commit(); // 提交事务 ShowMessage(保存成功); } catch (...) { FDConnection1-Rollback(); // 回滚事务 ShowMessage(保存失败所有更改已撤销); throw; // 重新抛出异常 }数据集组件 (TFDQuery,TFDTable)TFDQuery灵活通过SQL语句操作数据推荐绝大多数场景使用。TFDTable直接映射整张表简单但不够灵活性能可能有问题。关键技巧使用参数化查询(:param_name)来防止SQL注入并提升查询性能。FDQuery1-SQL-Text SELECT * FROM products WHERE category_id :cat_id AND name LIKE :name_filter; FDQuery1-ParamByName(cat_id)-AsInteger 1; FDQuery1-ParamByName(name_filter)-AsString %螺丝%; FDQuery1-Open();数据感知控件 (TDBGrid,TDBEdit)通过TDataSource与TFDQuery绑定。TDBGrid使用心得在Columns编辑器中定制列设置FieldName、Title.Caption中文标题、Width、Alignment。对于布尔型字段设置DisplayValues为“是;否”。对于查找字段如商品ID显示商品名使用PickList或更优的TDBLookupComboBox在编辑时选择。注意数据感知控件在Post或ApplyUpdates前数据并未真正写入数据库。对于主从表如订单头与明细需要精心控制TFDQuery的MasterSource和MasterFields属性或手动编码维护关联。4. 核心业务模块实现详解4.1 基础数据管理商品与BOM商品管理窗体是使用频率最高的模块之一。除了基本的增删改查要特别注意编码自动生成在BeforePost事件中检查code字段是否为空若为空则按规则自动生成如查询当前最大序列号1。数据验证在OnValidate事件或BeforePost中验证关键字段如单位不能为空类型选择有效。模糊搜索商品列表上方提供组合查询条件编码、名称、分类、类型使用参数化SQL的LIKE语句实现。BOM管理是生产模块的起点。需要一个界面允许用户为一个“父项产品”添加多个“子项组件”。界面设计上半部分选择父项产品通过TDBLookupComboBox绑定products表筛选type为成品或半成品下半部分用TDBGrid显示和编辑该父项的BOM明细。数据关系使用两个TFDQuery。一个(qryBomMaster)获取或设置当前父项另一个(qryBomDetail)的SQL动态关联到父项IDSELECT * FROM boms WHERE parent_product_id :parent_id。当父项改变时重新参数化并打开qryBomDetail。有效性检查保存BOM时需要检查是否存在循环引用即子项又是父项的祖先这是一个经典的图论问题对于数据量不大的情况可以通过递归或迭代检查实现。4.2 采购与销售流程实现采购订单和销售订单的实现逻辑高度相似都遵循“创建订单-审核订单-生成入库/出库单-更新库存”的流程。以采购订单为例订单录入窗体使用两个TFDQuery和对应的TDBGrid分别管理订单头(purchase_orders)和订单明细(purchase_order_details)。主从关系实现设置明细TFDQuery的MasterSource指向订单头TDataSourceMasterFields设置为idDetailFields设置为order_id。当在订单头中移动记录时明细网格会自动刷新显示对应明细。但注意这种方式在新增订单头id尚未生成时可能有问题更稳健的做法是手动控制。明细行编辑在明细网格中商品列应使用TDBLookupComboBox让用户从商品列表中选择。选择商品后自动带出单位、最近采购价可从商品表或历史采购记录中获取。金额计算在明细行的数量或单价字段的OnChange事件中实时计算该行金额 数量 * 单价。在订单头的AfterScroll或明细的AfterPost事件中遍历所有明细行重新计算订单总金额、总税额等。状态流转与审核订单表应有status字段如0-草稿1-已审核2-已关闭3-已取消。提供“保存草稿”和“审核”按钮。“保存草稿”仅将数据写入数据库。“审核”按钮点击后除了更新状态还可能触发后续动作如发送邮件通知、生成下游单据采购入库单的草稿。重要原则已审核的单据不能被直接修改或删除。任何更改都应通过“变更”流程生成新的修正单据或记录变更日志。生成采购入库单审核采购订单后系统应允许基于该订单生成入库单。这通常是一个单独的功能或按钮。逻辑是新建一张采购入库单其“源单类型”为“采购订单”“源单号”为选中的订单号。然后将订单明细中未入库的数量需要有一个received_quantity字段来跟踪自动填充到入库单的明细中允许用户修改本次实际入库数量。4.3 库存管理核心流水与事务库存管理的所有秘密都在stock_ledger表。任何库存变动都必须通过向此表插入记录来完成并实时更新products表的current_stock可以通过触发器也可以在代码中同步更新以保证性能。入库事务的代码示例采购入库void __fastcall TFormStockIn::SaveStockInTransaction() { FDConnection1-StartTransaction(); try { // 1. 保存入库单头表 (stock_in_orders) FDQueryStockInHeader-FieldByName(total_amount)-AsFloat CalculateTotal(); FDQueryStockInHeader-Post(); // 假设已处于编辑状态 int stockInId FDConnection1-GetLastAutoGenValue(stock_in_orders); // 2. 遍历入库单明细更新库存流水和商品库存 FDQueryStockInDetails-First(); while (!FDQueryStockInDetails-Eof) { int productId FDQueryStockInDetails-FieldByName(product_id)-AsInteger; float qty FDQueryStockInDetails-FieldByName(quantity)-AsFloat; float unitCost FDQueryStockInDetails-FieldByName(unit_cost)-AsFloat; int warehouseId FDQueryStockInDetails-FieldByName(warehouse_id)-AsInteger; // 2.1 插入库存流水记录 TFDQuery *qryLedger new TFDQuery(this); qryLedger-Connection FDConnection1; qryLedger-SQL-Text INSERT INTO stock_ledger (product_id, warehouse_id, related_type, related_id, related_detail_id, quantity, unit_cost, balance_quantity, balance_amount) VALUES (:pid, :wid, PO_IN, :rel_id, :rel_did, :qty, :cost, (SELECT current_stock FROM products WHERE id:pid) :qty, // 计算新结存数量 (SELECT current_stock * cost_price FROM products WHERE id:pid) (:qty * :cost)); // 计算新结存金额 (简化) // ... 设置所有参数 qryLedger-ExecSQL(); delete qryLedger; // 2.2 更新商品表当前库存 (移动加权平均法计算新成本价) TFDQuery *qryUpdateProduct new TFDQuery(this); qryUpdateProduct-Connection FDConnection1; qryUpdateProduct-SQL-Text UPDATE products SET current_stock current_stock :qty, purchase_price ((current_stock * purchase_price) (:qty * :cost)) / (current_stock :qty) // 移动加权平均 WHERE id :pid; // ... 设置参数 qryUpdateProduct-ExecSQL(); delete qryUpdateProduct; FDQueryStockInDetails-Next(); } // 3. 更新关联的采购订单的“已入库数量” UpdatePurchaseOrderReceivedQty(stockInId); FDConnection1-Commit(); ShowMessage(入库成功库存已更新。); } catch (Exception e) { FDConnection1-Rollback(); ShowMessage(入库失败: e.Message); } }关键点整个入库操作保存单据、更新流水、更新商品库存、更新订单状态必须包裹在一个数据库事务中要么全部成功要么全部回滚确保数据一致性。成本计算上述代码示例了“移动加权平均法”。这是最常用的存货计价方法之一。每次入库后新的平均成本价 (原结存金额 本次入库金额) / (原结存数量 本次入库数量)。这个新成本价将用于下一次出库的成本计算。4.4 生产管理模块实现思路生产管理是进销存的深化核心是工单Work Order。创建生产工单基于销售订单或预测计划创建。指定要生产的成品、数量、计划开始/结束日期。系统根据该成品的有效BOM自动计算所需原材料清单毛需求并考虑现有库存生成净需求建议。初期可简化直接展示BOM清单。工单状态与执行工单状态计划-已下达-生产中-部分完工-完工入库-关闭。生产领料根据工单和BOM生成领料单。领料单审核后触发库存出库事务stock_ledger记录为MO_OUT类型扣减原材料库存。生产报工与入库工人或班组长汇报完成数量。根据完成数量创建生产入库单。入库单审核后触发成品入库事务stock_ledger记录为MO_IN类型增加成品库存同时可能反冲部分未使用的原材料如果BOM有损耗率设计。简化实现建议初期可以不实现复杂的MRP和工序管理。重点实现BOM维护、生产工单创建、手工指定领料、成品入库。领料和入库都复用之前实现的通用库存出入库界面和逻辑只是关联的单据类型(related_type)不同。5. 报表生成与数据导出5.1 使用FastReport设计报表CBuilder通常捆绑FastReport或FortesReport作为报表工具。FastReport功能强大且易用。集成FastReport在窗体上放置TfrxReport、TfrxDBDataset组件。连接数据TfrxDBDataset的DataSet属性指向你的TFDQuery例如一个已经写好汇总SQL的查询。设计报表双击TfrxReport组件打开报表设计器。拖拽Master Databand将字段从数据字典拖到设计界面。可以添加Header、Footer、Group Header等 band 实现分组统计如按客户统计销售额。代码调用// 先让数据集准备好数据 FDQuerySalesReport-SQL-Text SELECT customer_name, SUM(amount) as total FROM sales_orders GROUP BY customer_name; FDQuerySalesReport-Open(); // 关联报表数据集 frxDBDataset1-DataSet FDQuerySalesReport; // 预览报表 frxReport1-ShowReport();5.2 导出为Excel虽然可以用第三方组件但使用OLEWindows平台或直接生成CSV是最简单通用的方法。使用OLE自动化操作Excel需安装Excel#include ComObj.hpp void ExportToExcel(TFDQuery *qry) { Variant excel, workbook, worksheet; excel CreateOleObject(Excel.Application); excel.OlePropertySet(Visible, true); workbook excel.OlePropertyGet(Workbooks).OleFunction(Add); worksheet workbook.OlePropertyGet(Worksheets, 1); // 导出标题行 for (int i 0; i qry-FieldCount; i) { worksheet.OlePropertyGet(Cells, 1, i1).OlePropertySet(Value, qry-Fields-Fields[i]-FieldName); } // 导出数据行 qry-First(); int row 2; while (!qry-Eof) { for (int col 0; col qry-FieldCount; col) { worksheet.OlePropertyGet(Cells, row, col1).OlePropertySet(Value, qry-Fields-Fields[col]-AsVariant); } row; qry-Next(); } // 注意需要正确释放OLE对象这里为简化示例省略 }心得OLE方式灵活但速度慢且依赖客户端Excel环境。对于大量数据更推荐用TMS FlexCel等专业组件或直接生成CSV/HTML文件。6. 部署、调试与常见问题排查6.1 项目编译与打包部署编译配置在Project - Options中选择Build Configuration为Release以优化代码大小和运行速度。打包依赖CBuilder编译出的EXE文件通常需要一些运行时库如borlndmm.dll,cc3250mt.dll等。可以在Project - Options - Packages中取消勾选Build with runtime packages这样这些库会被静态链接进EXE生成单个文件但EXE体积会变大。如果使用FireDAC连接MySQL客户端机器需要安装MySQL的C API库libmysql.dll。最简单的方法是将这个dll放在EXE同目录下。数据库连接配置分发将数据库连接字符串服务器地址、数据库名、用户名、密码保存在一个加密的配置文件或注册表中。首次运行时可以让用户配置。6.2 开发与运行时常见问题“连接丢失”或“超时”原因数据库服务器不稳定、网络问题、连接池配置不当或长时间空闲连接被服务器断开。解决在TFDConnection中设置PooledTrue并配置合理的池参数在每次执行关键操作前使用FDConnection1-Connected true;来测试并激活连接实现一个心跳机制定期执行一个简单查询如SELECT 1保持连接活跃。多用户同时操作冲突更新丢失场景用户A和B同时打开同一条商品记录查看A先修改保存B后修改保存B的修改会覆盖A的。解决乐观锁在表中增加一个version版本号或last_update_time时间戳字段。更新时在WHERE子句中加上AND version :old_version。如果受影响行数为0说明数据已被他人修改提示用户刷新后重试。UPDATE products SET name:new_name, versionversion1 WHERE id:id AND version:old_version;悲观锁在编辑开始时就用SELECT ... FOR UPDATE锁定记录阻止他人编辑但会降低并发性需谨慎使用。数据感知控件显示乱码或问号原因数据库、连接字符集、控件字体不统一。解决确保MySQL数据库、表、字段的字符集为utf8mb4。在TFDConnection的Params中设置CharacterSetutf8mb4。将窗体、TDBGrid、TDBEdit等控件的Font.Charset属性设置为GB2312_CHARSET或DEFAULT_CHARSET并使用支持中文的字体如宋体、微软雅黑。执行SQL语句时参数错误现象Parameter ‘xxx‘ not found。检查确认SQL文本中的参数名如:name与ParamByName中使用的名字完全一致包括大小写。TFDQuery的参数是自动创建的但必须与SQL中的占位符匹配。程序发布后无法运行提示缺少VCL或RTL库解决如果使用了运行时包必须将对应的.bpl文件与EXE一起发布。更简单的方式是采用静态编译取消Build with runtime packages。对于社区版可能需要处理一些授权问题确保在目标机器上运行时符合社区版许可协议。开发这样一个系统最大的挑战往往不是某个技术点而是对业务逻辑的透彻理解和严谨的数据库事务设计。每一个按钮点击背后都可能涉及多张表的联动更新。务必坚持“先设计后编码先原型后美化先核心流程后边缘功能”的原则。从最简单的“商品管理”和“单仓库入库出库”做起逐步迭代你的CBuilder进销存系统就能稳健地成长起来。
返回列表