五分钟构建一个 Delphi Web CRUD 页面

· 组件
五分钟构建一个 Delphi Web CRUD 页面

每一个业务应用中都包含同样的一个页面,并且要为每张表重新写一遍:一个带搜索框和分页器的列表,一个用于新建和编辑的表单,一个删除确认,以及负责写入的代码。我们自己的管理后台演示中,仅客户这一项就有四个请求处理程序和大约四百行页面构建代码,然后供应商又要重写一遍,发票再重写一遍。

sgcWebSockets 2026.10 添加了 TsgcHTMLComponent_CRUD,由它全权负责这个页面。演示程序现在用一个只有六个方法的适配器来构建客户区域,并把手写版本保留在旁边,方便你对照阅读。

页面

uses
  sgcHTML_Component_CRUD;

FCRUD := TsgcHTMLComponent_CRUD.Create(Self);
FCRUD.CRUDID := 'customers';
FCRUD.RoutePrefix := '/customers';
FCRUD.Title := 'Customers';
FCRUD.KeyField := 'ID';
FCRUD.PageSize := 15;
FCRUD.Actions := [caCreate, caEdit, caDelete, caView, caExport];
FCRUD.DataSet := FDQueryCustomers;

绑定到一个数据集后,它会自行读取各个列,这就是整个页面:列表、搜索、分页、新建和编辑表单、删除确认,以及对列表当前显示内容的 CSV 导出。

或者使用你自己的 SQL

数据集只是存储记录的一种方式,并非唯一方式。所有修改记录的操作都要经过一个适配器,因此该组件永远不会直接接触 SQL、记录或数据库驱动:

type
  TCustomersAdapter = class(TsgcHTMLCRUDAdapter)
  public
    function Locate(const aKey: string): Boolean; override;
    function Read(const aKey: string; aValues: TStrings): Boolean; override;
    function List(const aSearch: string; aPage, aPageSize: Integer;
      aRows: TStrings): Integer; override;
    function Insert(aValues: TStrings; var aKey: string;
      aErrors: TStrings): Boolean; override;
    function Update(const aKey: string; aValues, aErrors: TStrings)
      : Boolean; override;
    function Delete(const aKey: string; aErrors: TStrings): Boolean; override;
  end;

FCRUD.SetAdapter(TCustomersAdapter.Create(FCRUD));

六个方法,架在你已有的任何东西之上。在演示程序中,每个方法都只是对应用程序数据库连接池的一次调用。

接下来你原本要自己写的部分

乐观锁。指定一个版本列,保存时会把浏览器提交的内容与已存储的内容进行比较,这样第二个点击保存的人会被告知记录已发生变化,而不是悄悄覆盖第一个人的修改:

FCRUD.VersionField := 'UPDATED_AT';

主从表。一条记录的子列表本身就是另一个 CRUD 组件,会在记录被编辑时渲染在表单下方:

with FCRUD.Details.Add do
begin
  CRUD := FCRUDOrderLines;
  MasterField := 'ORDER_ID';
  Caption := 'Lines';
end;

校验的是你的数据,而不是表单本身。必填列是组件自己的职责;而那些与客户业务相关的规则则放在 OnValidate 中。每发现一个问题就添加一行,写入就会被拒绝,表单会带着这些问题信息返回。

两种开始方式

IDE 中现在多了一个向导:Tools › New sgcHTML CRUD page。填写表名、主键和各个列,组件就会被创建在正在设计的窗体上,同时接好它所需的两行代码也已经放进剪贴板。

在 IDE 之外还有一个命令行工具 sgcHTMLGen,它能以 Object Pascal 或 C# 写出该页面的源代码:

sgcHTMLGen crud --table Customers --prefix /customers ^
  --fields "ID:key CODE:req:32 NAME:req:120 CITY:80 NOTES:noform"

页面如何应答

页面上的每一个控件都以同样的方式提交:搜索框、新建按钮、每一行的编辑和删除,以及表单本身,都带有三个隐藏字段:actioncrud,以及在有意义的场合下的 key。宿主程序读取这些字段并调用 ProcessAction,由它给出要替换进去的标记。

通过 socket 发送的消息不带路径,因此它永远不会到达路由器,也就得不到路由器自身的检查。对它进行授权是应用程序自己的职责,而引擎现在把完成这件事所需的东西交到了你手上:

oSession := FHTMXEngine.MessageSession(aConnection, FAuth);
try
  if not Assigned(oSession) then
    Exit;   // a write with nobody behind it is not a write
  oPage.ProcessAction(vAction, oParams.Values['key'], oParams, aResponse);
finally
  oSession.Free;
end;

从浏览器传来的 key 会先被定位,然后才会执行任何写入,因此一个谁都不属于的 key 什么都写不进去。

查看两个版本

Demos\60.HTML\01.RunTime\02.AdminCRUD 中,/customers 是该组件,/customers-classic 是它所取代的页面。同一个数据库,同样的数据,同样的外观。其中一个足足少了四百行代码。

升级

该组件是 sgcHTML 的一部分,属于增量功能:在你把它放到窗体上之前,现有页面不会有任何变化。

延伸阅读

观看视频

关于此功能的简短视频发布在eSeGeCe 频道上。

有问题、反馈,或需要迁移方面的帮助?欢迎联系我们 — 你会收到编写这些代码的人的回复。