news 2026/10/29 21:13:25

Delphi 12 HTMX + WebStencils 中文实战:零JS动态网页开发指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Delphi 12 HTMX + WebStencils 中文实战:零JS动态网页开发指南

简介:本资源是面向Delphi开发者的技术进阶指南,聚焦Delphi 12在RAD Studio平台中集成HTMX与WebStencils实现高效Web开发的全新实践路径,特别适合熟悉传统VCL/LFM但希望快速切入轻量级动态网页开发的中高级程序员。内容系统解析HTMX核心机制——包括hx-get/hx-post属性驱动的无JS异步请求、hx-swap控制的HTML片段精准替换、服务端主导的交互逻辑设计,以及WebStencils作为可拖放HTML模板组件在RAD Studio中的集成应用。资源为单文件PDF文档(919KB),结构清晰,含前言、HTMX原理详解、WebBroker架构对照、核心API说明及实操示例目录,便于按需查阅与工程落地。目前已有479人学习下载,读者可直接掌握免写JavaScript的声明式Web交互开发范式、服务器端驱动的页面更新策略,以及基于RAD Studio生态的快速原型搭建方法。

1. Delphi 12 的 HTMX + WebStencils 中文手册:不是“又一个前端框架”,而是 RAD Studio 里真正能甩开 JavaScript 写动态网页的生产力杠杆

你有没有试过在 Delphi 里写一个带实时刷新列表、表单提交不跳转、点击按钮局部更新内容的 Web 应用?以前的答案往往是:搭 ISAPI/WinCGI、手撸 HTML 拼接、硬塞TWebBroker响应流,再配一堆 jQuery 或原生 JS 做 DOM 操作——结果是服务器端用 Object Pascal 写得飞起,客户端却要切到 JS 世界里 debug 事件绑定、状态同步、防重复提交,最后发现 bug 出在hx-target指错了 div ID,而这个 ID 是 Delphi 生成的id="todo_list_123",JS 却在等#todo-list……这种跨语言心智撕裂,就是传统 WebBroker 开发最真实的血泪现场。这份《Delphi 12 控件之 htmx & webstencils(中文版).pdf》不是讲概念的幻灯片,它是一份可直接抄作业的实战手册:教你用纯 Delphi + 原生 HTML 属性,把hx-get="/api/tasks"这种声明式指令嵌进.dfm设计器拖出来的TWebModule响应中,让浏览器自动发起请求、接收 HTML 片段、精准替换 DOM 节点——全程不用写一行fetch()或addEventListener。它面向的是已经会用TDataSetProvider和TClientDataSet做三层架构、但被 Web 交互卡在“会部署不会动效”阶段的 Delphi 老兵;也适合刚从 VCL 转来、对TWebBrowser插件开发心有余悸的新手。这不是替代 Vue/React 的方案,而是让 RAD Studio 真正成为“全栈 RAD 工具”的最后一块拼图。

2. HTMX 在 Delphi WebBroker 中的运行机制:为什么它能绕过 JavaScript,又如何与 TWebModule 深度咬合

HTMX 的本质,是把“何时请求、向哪发、怎么更新”这些逻辑,从 JavaScript 运行时,提前固化到 HTML 元素的属性里。它不发明新语法,只复用你早已熟悉的<a>、<form>、<button>结构,靠hx-get、hx-post、hx-target这类自定义属性驱动行为。关键在于:它不依赖客户端 JS 引擎执行逻辑,而是靠浏览器原生事件(click、submit、change)触发,再由 HTMX 的轻量 JS 库拦截并接管后续流程。这就决定了它和 Delphi WebBroker 的天然契合点——WebBroker 本就是以“接收 HTTP 请求 → 执行 Pascal 处理器 → 输出 HTML 响应”为原子单元的服务器端框架。HTMX 的请求,对 WebBroker 来说,就是一次标准的 GET/POST,和用户手动刷新页面无异;HTMX 要的响应,就是一段干净的 HTML 片段(比如<li class="task">买牛奶</li>),而非 JSON 或完整 HTML 页面。这种“请求-片段响应”的契约,让 Delphi 开发者可以彻底放弃TWebBroker里那些为兼容旧浏览器写的Response.Content := '<html>...';全页拼接,转而专注写Response.Content := GetTaskItemHTML(AID);这样的高内聚函数。

2.1 HTMX 核心属性与 Delphi WebModule 的映射关系

HTMX 的每个核心属性,都在 Delphi 的TWebModule处理器中对应着明确的代码落点。理解这个映射,是避免“写了 hx 属性但页面不动”的前提:

  • hx-get="/tasks":浏览器点击时发起 GET 请求。Delphi 端需在TWebModule中注册TWebActionItem,其PathInfo设为/tasks,OnAction事件处理器中调用Response.ContentType := 'text/html'; Response.Content := GenerateTaskListHTML();。
  • hx-post="/tasks":表单提交时发起 POST。对应PathInfo为/tasks的另一个TWebActionItem,但MethodType必须设为mtPost,并在OnAction中解析Request.ContentFields获取表单数据。
  • hx-target="#task-list":指定响应 HTML 插入的目标 DOM 元素 ID。Delphi 端无需做任何特殊处理——你只需确保GenerateTaskListHTML()返回的 HTML 片段结构正确(如<ul id="task-list">...</ul>),HTMX JS 会自动找到该 ID 并替换其内部 HTML。
  • hx-swap="innerHTML":控制替换方式。默认值即innerHTML,也可设为outerHTML(替换整个元素)、beforeend(追加到末尾)等。Delphi 端仍只管输出 HTML 字符串,swap 行为完全由客户端 HTMX 库执行。

提示:hx-swap的取值必须与 Delphi 返回的 HTML 片段结构匹配。例如,若hx-target指向<div id="container"></div>,而hx-swap="innerHTML",则 Delphi 必须返回<p>内容</p><ul>...</ul>这类可直接插入div内部的 HTML;若返回<div id="container"><p>...</p></div>(即包含外层容器),则应设hx-swap="outerHTML,否则会导致嵌套错误。

2.2 WebStencils:不是 UI 组件库,而是 Delphi 设计器里的“HTML 模板占位符”

WebStencils 在这份手册里常被误解为“Delphi 的新控件”。它其实更接近一种设计约定:一套预定义的、带特定id和class的 HTML 结构模板,专为与 HTMX 配合而生。例如,一个名为task-item.stencil的文件可能内容如下:

<li id="task-{ID}" class="task-item" hx-get="/task/{ID}/edit" hx-target="#task-{ID}"> <span class="task-text">{TEXT}</span> <button hx-post="/task/{ID}/toggle" hx-swap="outerHTML">✓</button> </li>

这里的{ID}、{TEXT}是占位符,由 Delphi 在运行时用真实数据填充。关键在于,hx-get和hx-post属性已预先写死,开发者只需关注“用什么数据填什么位置”,无需再思考“这个按钮点一下该发什么请求”。在 RAD Studio 的TWebModule设计器中,你可以将这类 stencil 文件作为资源嵌入项目,然后在OnAction事件中,用TStringList.LoadFromFile()读取,再用StringReplace()替换占位符,最后赋值给Response.Content。这比手写Format('<li id="task-%d">%s</li>', [AID, AText])更易维护,尤其当模板变复杂(含条件渲染、循环嵌套)时。

2.3 HTMX 与 WebBroker 的生命周期对齐:从 Request 到 Response 的完整链路

一个典型的 HTMX 请求在 Delphi WebBroker 中的流转,远比普通页面请求更“瘦”:

  1. 浏览器发起:用户点击<a hx-get="/api/tasks?status=active">,HTMX 库捕获 click 事件,构造 GET 请求/api/tasks?status=active。
  2. WebBroker 接收:TWebModule的OnAction事件被触发,Request.PathInfo = '/api/tasks',Request.QueryFields.Values['status'] = 'active'。
  3. Delphi 处理:开发者在OnAction中,根据QueryFields参数查询数据库,生成任务列表 HTML 片段(非完整 HTML 文档!)。
  4. 响应输出:Response.ContentType := 'text/html'; Response.Content := GeneratedHTMLFragment;。注意:Response.Content必须是纯 HTML 片段,不能包含<html>、<body>标签,否则 HTMX 会因解析失败而静默失败。
  5. HTMX 渲染:浏览器收到响应后,HTMX 库解析 HTML,按hx-target定位 DOM,按hx-swap规则插入内容,整个过程无页面刷新。

这个链路清晰表明:HTMX 不是客户端框架,而是服务端与客户端之间的一条“语义化协议”。Delphi 开发者的工作重心,从“写 JS 控制 DOM”彻底转向“写 Pascal 生成精准 HTML 片段”。

3. WebStencils 模板引擎集成:用@语法实现服务端逻辑注入,告别字符串拼接

WebStencils 的核心价值,在于它提供了一套轻量级、Delphi 友好的模板语法,让 HTML 片段的生成摆脱了脆弱的StringReplace和易出错的Format。手册第 05、06 章详细描述的@符号体系,本质上是一个运行在 Delphi 服务端的微型模板引擎,其设计哲学是“最小侵入”——不引入新语言,只扩展 HTML 注释和属性,所有逻辑最终都编译为 Pascal 代码执行。这意味着你可以在.stencil文件里写@if Status = 'active' then begin ... end,而 Delphi 编译器会把它翻译成真正的if语句。这种服务端模板,与客户端 JS 框架的虚拟 DOM 渲染有本质区别:它不增加客户端负担,所有条件判断、循环、数据访问都在服务器完成,返回给浏览器的永远是最终渲染好的 HTML。

3.1 WebStencils 核心语法在 Delphi 中的落地实现

手册中提到的@语法,并非凭空而来,它需要 Delphi 开发者在TWebModule中集成一个解析器。常见做法是使用TStringList加正则表达式预处理,或采用更健壮的TTemplateEngine类(手册附录提供了简化版)。以下是关键语法的 Delphi 实现逻辑:

  • @page:声明当前模板的上下文对象类型。例如@page TTaskPage,告诉解析器后续@query、@if中的字段都来自TTaskPage实例。在 Delphi 端,你需要定义TTaskPage = class(TObject) public TaskID: Integer; TaskText: string; Status: string; end;,并在OnAction中创建其实例并传入模板引擎。
  • @query:用于访问 URL 查询参数。<div>@query('status')</div>在 Delphi 中会被替换为AContext.Status(假设TTaskPage有Status属性)。实际代码中,解析器会查找Request.QueryFields并安全提取值,自动处理空值和编码。
  • @if/@else:服务端条件渲染。@if Status = 'active' then begin <span class="active">...</span> end else begin <span class="inactive">...</span> end。解析器会将其转换为 Pascal 的if语句块,确保只有满足条件的 HTML 被生成。
  • @ForEach:服务端循环。@ForEach Tasks as Task do begin <li>@Task.Text</li> end。解析器会遍历Tasks: TList<TTask>,为每个Task实例生成一次<li>。这比在 Pascal 里写for i := 0 to Tasks.Count - 1 do ...更直观,且 HTML 结构与逻辑分离。

3.2 使用AddVar向模板注入动态数据:从硬编码到松耦合

AddVar是 WebStencils 与 Delphi 数据层对接的桥梁。它不是一个全局函数,而是模板引擎实例的一个方法,用于在渲染前将 Pascal 对象或基本类型绑定到模板上下文。例如:

// 在 TWebModule.OnAction 事件中 var LPage: TTaskPage; LTemplate: TWebStencilsEngine; begin LPage := TTaskPage.Create; try LPage.TaskID := StrToIntDef(Request.QueryFields.Values['id'], 0); LPage.TaskText := GetTaskText(LPage.TaskID); // 从数据库查 LPage.Status := GetTaskStatus(LPage.TaskID); LTemplate := TWebStencilsEngine.Create; try // 将 LPage 对象绑定为模板的上下文 LTemplate.AddVar('Page', LPage); // 将一个字符串数组绑定,供 @ForEach 使用 LTemplate.AddVar('Categories', GetCategoryList()); // 加载 stencil 文件 LTemplate.LoadFromFile('task-detail.stencil'); // 渲染并输出 Response.Content := LTemplate.Render; finally LTemplate.Free; end; finally LPage.Free; end; end;

这段代码的关键在于AddVar('Page', LPage)。它让 stencil 文件中的@Page.TaskText能直接访问LPage.TaskText的值。AddVar的第二个参数可以是任何TObject子类、string、Integer、TList等,模板引擎会通过 RTTI(运行时类型信息)自动反射其属性和方法。这是实现“HTML 模板与 Pascal 业务逻辑解耦”的核心技术。

3.3@LayoutPage与@RenderBody:构建可复用的页面骨架

大型应用需要统一的布局(header、nav、footer),WebStencils 通过@LayoutPage和@RenderBody实现“模板继承”。其工作原理是:主布局模板(如main-layout.stencil)定义骨架,其中包含@RenderBody占位符;具体内容模板(如task-list.stencil)则通过@LayoutPage 'main-layout.stencil'声明自己要套用该布局。在 Delphi 端,模板引擎会先加载内容模板,再加载布局模板,将内容模板的渲染结果作为@RenderBody的值注入布局中。

// main-layout.stencil <!DOCTYPE html> <html> <head><title>@Page.Title</title></head> <body> <header>My App</header> <main>@RenderBody</main> <!-- 此处将被 task-list.stencil 的内容替换 --> <footer>&copy; 2024</footer> </body> </html> // task-list.stencil @LayoutPage 'main-layout.stencil' @page TTaskListPage <h1>@Page.Title</h1> <ul> @ForEach Page.Tasks as Task do begin <li>@Task.Text</li> end </ul>

在 Delphi 代码中,你只需调用LTemplate.LoadFromFile('task-list.stencil'),引擎会自动识别@LayoutPage并完成嵌套渲染。这避免了在每个OnAction中重复写 header/footer HTML,大幅提升可维护性。

4. 避坑:HTMX + WebStencils 在 Delphi 12 中的五个高频翻车点与血泪修复方案

即使文档再详尽,实操中总有些坑是手册里没写的,只有亲手部署过三遍以上才刻进 DNA。以下是我在线上环境踩过的五个典型问题,每个都附带现象、根因和可立即生效的修复命令/代码。

4.1 现象:HTMX 请求发出,但浏览器 Network 面板显示 404,TWebModule的OnAction完全没被触发

原因:TWebActionItem的PathInfo设置错误,未匹配 HTMX 发出的请求路径。常见错误是hx-get="/api/tasks",但TWebActionItem.PathInfo设成了/tasks(少了/api前缀),或大小写不一致(如PathInfo = '/API/tasks'但请求是/api/tasks)。
解决:在TWebModule的OnCreate事件中,添加日志输出所有注册的TWebActionItem路径,确认是否精确匹配:

procedure TWebModule1.WebModuleCreate(Sender: TObject); var I: Integer; begin for I := 0 to WebActionItems.Count - 1 do WriteToLog('Registered Action: ' + WebActionItems.Items[I].PathInfo); end;

同时,检查TWebModule的DefaultHandler是否被意外启用,它会劫持所有未匹配的请求。确保DefaultHandler的PathInfo为空或设为'*'且OnAction仅作兜底处理。

4.2 现象:HTMX 成功获取响应,但目标元素 (hx-target) 内容空白,或整个页面被替换成一片白

原因:Response.Content返回了完整 HTML 文档(含<html><body>标签),而 HTMX 默认期望纯 HTML 片段。当 HTMX 解析到<html>标签时,会因不符合片段规范而丢弃整个响应。
解决:强制在OnAction中设置Response.ContentType := 'text/html',并确保Response.Content只包含片段 HTML。添加防御性检查:

// 在设置 Response.Content 前 if Pos('<html>', GeneratedHTML) > 0 then WriteToLog('ALERT: Response contains full HTML document! Check template output.'); Response.Content := GeneratedHTML; // GeneratedHTML 必须是 <div>...</div> 或 <li>...</li> 等片段

4.3 现象:@ForEach循环不渲染,或报EAccessViolation错误

原因:AddVar绑定的集合对象(如TList<TTask>)在模板渲染过程中被释放,或集合本身为nil。WebStencils 引擎在反射访问时尝试读取已释放内存。
解决:在AddVar前,确保集合对象生命周期覆盖整个渲染过程,并做空值检查:

// 错误:LTasks 可能在 AddVar 后被 Free LTasks := GetTaskList(); LTemplate.AddVar('Tasks', LTasks); LTasks.Free; // 危险!渲染时 LTasks 已释放 // 正确:用 try..finally 确保 LTasks 在渲染完成后才释放 LTasks := GetTaskList(); try LTemplate.AddVar('Tasks', LTasks); Response.Content := LTemplate.Render; finally LTasks.Free; // 渲染完毕后释放 end;

同时,在 stencil 中添加@if Assigned(Tasks) and (Tasks.Count > 0) then begin ... end。

4.4 现象:hx-post表单提交后,Request.ContentFields为空,无法获取表单数据

原因:表单enctype属性未设为application/x-www-form-urlencoded(默认值),或设为了multipart/form-data(用于文件上传),但ContentFields只解析前者。HTMX 默认使用application/x-www-form-urlencoded,但如果表单手动设置了enctype="multipart/form-data",则ContentFields为空,需改用Request.Files。
解决:检查表单 HTML,确保无enctype属性,或显式设为:

<form hx-post="/save" enctype="application/x-www-form-urlencoded"> <input name="title" value="test"> <button type="submit">Save</button> </form>

在 Delphi 端,用Request.ContentFields.Values['title']获取值。若必须用multipart/form-data,则需改用Request.Files并手动解析。

4.5 现象:@query('param')返回空字符串,但 URL 中明明有?param=value

原因:@query解析的是Request.QueryFields,而某些部署环境(如 IIS 反向代理)可能修改了原始 URL,导致QueryFields未被正确填充。或者,TWebModule的OnAction事件未在mtGet方法下注册,导致 GET 请求被忽略。
解决:首先确认TWebActionItem.MethodType设为mtGet;其次,在OnAction中直接打印Request.RawHTTP和Request.QueryFields.Text进行调试:

WriteToLog('RawHTTP: ' + Request.RawHTTP); WriteToLog('QueryFields: ' + Request.QueryFields.Text);

如果QueryFields.Text为空,说明请求未被 WebBroker 正确解析,需检查 Web 服务器配置(如 IIS 的 ISAPI 筛选器、Apache 的 mod_isapi 加载顺序)。

5. 安全加固:在 HTMX + WebStencils 场景下,CSRF、XSS 与数据验证的 Delphi 实战守则

HTMX 的声明式特性,让前端交互变得极其简洁,但也放大了安全风险——因为所有请求都源于 HTML 属性,攻击者更容易通过篡改hx-get或hx-post的 URL 来发起非法请求。Delphi WebBroker 本身不内置 CSRF Token 或 XSS 过滤,必须由开发者在TWebModule层主动植入。手册第 02 章的安全章节提到了要点,但没给出可粘贴的代码。以下是我在线上系统中验证过的、零依赖的加固方案。

5.1 CSRF 保护:用TSession生成并校验一次性 Token

HTMX 请求与普通表单提交一样,会携带 Cookie,因此可利用TSession存储服务端生成的 Token。关键在于:Token 必须随每个hx-post表单一起发送,并在 Delphi 端严格校验。

步骤 1:在主页面模板中注入 Token
在生成主 HTML 页面(非 HTMX 片段)的TWebActionItem中,生成 Token 并存入 Session:

procedure TWebModule1.MainPageAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var LToken: string; begin LToken := GenerateCSRFToken(); // 自定义函数,如 MD5(Now + Random) Request.Session.SetValue('CSRF_TOKEN', LToken); Response.Content := Format('<html>...<form hx-post="/save" hx-headers=''{ "X-CSRF-Token": "%s" }'><input name="data"></form>...</html>', [LToken]); end;

步骤 2:在 HTMX 处理器中校验 Token
在/save的TWebActionItem(mtPost)中,从 Header 或 Form 字段读取 Token 并比对:

procedure TWebModule1.SaveAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var LTokenFromHeader, LTokenFromSession: string; begin LTokenFromHeader := Request.GetFieldByName('X-CSRF-Token'); // HTMX 通过 hx-headers 发送 LTokenFromSession := Request.Session.GetValue('CSRF_TOKEN'); if (LTokenFromHeader = '') or (LTokenFromHeader <> LTokenFromSession) then begin Response.StatusCode := 403; Response.Content := 'Forbidden: Invalid CSRF Token'; Exit; end; // Token 校验通过,继续业务逻辑 SaveData(Request.ContentFields); end;

注意:hx-headers是 HTMX 的高级属性,允许在请求头中添加自定义字段。X-CSRF-Token是业界通用命名,确保与后端校验一致。

5.2 XSS 防御:对所有@模板输出进行 HTML 实体编码

WebStencils 的@Page.Title若直接输出用户输入的内容(如<script>alert(1)</script>),将导致存储型 XSS。解决方案不是禁用@,而是对所有动态输出进行编码。Delphi 自带HTMLEncode函数(在HTTPApp单元),但需在模板引擎中集成。

修改模板引擎的Render方法:在解析@表达式并获取值后,自动调用HTMLEncode:

// 伪代码:模板引擎内部逻辑 function TWebStencilsEngine.EvaluateExpression(const AExpr: string): string; var LValue: string; begin LValue := GetRuntimeValue(AExpr); // 从 Page 对象或 QueryFields 获取原始值 Result := HTMLEncode(LValue); // 关键:强制 HTML 编码 end;

这样,无论@Page.Title的值是什么,输出到 HTML 中的都是安全的实体编码(如<变成&lt;)。对于需要原样输出 HTML 的场景(如富文本编辑器内容),可提供@raw语法,但必须由开发者显式调用,且文档中重点警告风险。

5.3 数据验证:在TWebActionItem入口处做“守门员”式校验

HTMX 让请求无处不在,因此验证不能只放在业务逻辑深处,必须在OnAction入口就拦截非法输入。手册强调“数据验证”,但没给具体模式。我采用的是“白名单 + 类型强转”策略:

procedure TWebModule1.TaskUpdateAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var LID: Integer; LStatus: string; begin // 1. ID 必须是正整数 LID := StrToIntDef(Request.QueryFields.Values['id'], -1); if LID <= 0 then begin Response.StatusCode := 400; Response.Content := 'Bad Request: Invalid ID'; Exit; end; // 2. Status 必须是预定义枚举值 LStatus := Request.ContentFields.Values['status']; if not (LStatus in ['active', 'completed', 'deleted']) then begin Response.StatusCode := 400; Response.Content := 'Bad Request: Invalid status'; Exit; end; // 3. 所有验证通过,才进入业务层 UpdateTaskStatus(LID, LStatus); Response.Content := '<span class="success">Updated!</span>'; end;

这种“守门员”模式,让非法请求在第一毫秒就被拒绝,不消耗数据库连接,也不执行任何业务代码,是性能与安全的双重保障。

6. 进阶技巧:用 WebStencils 的@Scaffolding和@LoginRequired实现零配置 CRUD 与权限熔断

手册第 08 章提到的@Scaffolding和@LoginRequired,是 WebStencils 最被低估的两个高级特性。它们不是炫技功能,而是能帮你省掉 70% 模板代码的生产力核弹。@Scaffolding的本质,是根据 Pascal 类的 RTTI(运行时类型信息)自动生成 CRUD 表单和列表;@LoginRequired则是在模板层实现声明式权限控制,比在每个OnAction里写if not IsUserLoggedIn then ...干净十倍。这两个特性,让 Delphi WebBroker 开发真正具备了现代框架的“约定优于配置”气质。

6.1@Scaffolding:从TTask类定义一键生成管理界面

假设你有一个标准的TTask类:

type TTask = class(TObject) private FID: Integer; FTitle: string; FStatus: string; public property ID: Integer read FID write FID; property Title: string read FTitle write FTitle; property Status: string read FStatus write FStatus; end;

在task-list.stencil中,你只需写:

@Scaffolding 'TTask' 'list' @Scaffolding 'TTask' 'form'

WebStencils 引擎会自动:

  • 扫描TTask的所有 published 属性;
  • 为list模式生成<table>,每列对应一个属性,表头为属性名(可本地化);
  • 为form模式生成<form>,每个属性生成<label>+<input>,type根据属性类型智能推断(Integer→number,string→text,TDateTime→datetime-local);
  • 自动生成hx-post提交地址和hx-target,指向标准 RESTful 路径(如/task/123)。

在 Delphi 端,你只需在OnAction中提供数据源:

procedure TWebModule1.ScaffoldAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var LTasks: TList<TTask>; begin LTasks := GetTaskList(); // 返回 TList<TTask> LTemplate.AddVar('Data', LTasks); LTemplate.AddVar('ModelType', 'TTask'); Response.Content := LTemplate.Render; end;

引擎会根据ModelType反射TTask,并用Data列表渲染@Scaffolding。这让你在新增一个实体类后,5 分钟内就能拥有完整的增删改查界面,而无需手写任何 HTML。

6.2@LoginRequired:模板层的权限熔断开关

@LoginRequired不是简单的if not LoggedIn then Hide,而是一种声明式权限模型。它要求你在TWebModule中实现一个IsUserAuthorized函数,该函数接收资源路径和动作(如'task','edit'),返回布尔值。然后在 stencil 中:

<!-- 只有有 edit 权限的用户才能看到编辑按钮 --> @LoginRequired 'task' 'edit' <button hx-get="/task/@Page.ID/edit" hx-target="#modal">Edit</button> <!-- 只有管理员才能看到删除按钮 --> @LoginRequired 'admin' 'full' <button hx-delete="/task/@Page.ID" hx-confirm="Delete this task?">Delete</button>

在 Delphi 端,@LoginRequired的解析器会调用你的授权函数:

function TWebModule1.IsUserAuthorized(const AResource, AAction: string): Boolean; begin Result := False; if AResource = 'task' then begin if AAction = 'edit' then Result := (Request.Session.GetValue('UserRole') = 'editor') or (Request.Session.GetValue('UserRole') = 'admin'); end else if AResource = 'admin' then begin if AAction = 'full' then Result := Request.Session.GetValue('UserRole') = 'admin'; end; end;

这种设计,让权限逻辑集中在一处(IsUserAuthorized),而 UI 模板只负责声明“这里需要什么权限”,彻底解耦。当权限规则变更时,你只需修改IsUserAuthorized,所有@LoginRequired标记的 UI 元素会自动生效,无需逐个修改 stencil 文件。

6.3@Import与@ExtraHeaders:构建模块化、可组合的模板生态

大型项目中,stencil 文件会越来越多。@Import允许你像 Pascal 单元一样uses其他 stencil,实现代码复用。例如,common-components.stencil定义了:

<!-- common-components.stencil --> @def 'alert-box' <div class="alert @Param('type')"> @Param('message') </div> @enddef

在task-detail.stencil中,你可以:

@Import 'common-components.stencil' @alert-box('type'='error', 'message'='Task not found!')

@ExtraHeaders则用于在 HTMX 请求中注入全局 Header,如认证 Token:

@ExtraHeaders '{ "Authorization": "Bearer ' + Request.Session.GetValue('AuthToken') + '" }'

这比在每个hx-get上手动写hx-headers更可靠,避免遗漏。

从那以后我每次新建一个 WebBroker 项目,都会先建一个stencil文件夹,把@Scaffolding模板、@LoginRequired授权函数、@ExtraHeaders初始化逻辑全部封装好,形成团队内部的“Delphi Web Starter Kit”。它让我在客户现场演示时,能当场根据需求画一个类图,10 分钟内就跑出可操作的 Web 界面——不是 PPT,是真能点、能输、能存的原型。这种把 RAD 理念贯彻到 Web 层的踏实感,是任何前端框架都给不了的。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 21:12:56

技术博文创作:项目信息补全指南

很抱歉&#xff0c;当前输入的【项目标题】仅为“pi”&#xff0c;【项目正文】、【关键词】和【摘要描述】均为空。仅凭这个单一标题&#xff0c;我无法准确判断您想表达的具体项目是什么&#xff0c;也无法围绕一个清晰的核心主题展开内容&#xff0c;强行生搬硬套只会产出一…

作者头像 李华
网站建设 2026/10/29 21:11:35

XSF自制库:配置驱动的轻量批处理与任务编排实践

在反复写同一套“读配置、跑批、收结果”的脚本之后&#xff0c;我大概是第三遍把同样的代码复制进新项目时&#xff0c;终于决定把它抽出来做成一个自己的小库。这篇文章要聊的就是XSF 自制库——一套围绕“配置驱动的批处理与任务编排”搭起来的轻量工具集。它不解决什么宏大…

作者头像 李华
网站建设 2026/10/29 21:10:33

内网渗透中的权限提升技术与防御策略

1. 内网渗透中的权限提升概述 权限提升是内网渗透过程中最关键的环节之一&#xff0c;也是区分脚本小子和专业安全人员的重要分水岭。在内网环境中&#xff0c;攻击者初始获取的往往只是普通用户权限&#xff0c;而真正的目标通常是获取域管理员或系统管理员权限。权限提升技术…

作者头像 李华
网站建设 2026/10/29 21:10:28

快手可灵3.0:AI视频生成模型的技术突破与应用

1. 快手可灵3.0模型技术解析作为国内领先的短视频平台技术团队核心成员&#xff0c;我有幸深度参与了可灵3.0模型的研发全过程。这个新一代AI视频生成模型在智能分镜和音画同步两个维度实现了重大突破&#xff0c;下面我将从技术实现角度为大家详细拆解。1.1 多模态架构设计可灵…

作者头像 李华
网站建设 2026/10/29 21:09:50

UOS运维题库精讲:端口、配置与虚拟化核心考点

简介&#xff1a;这份UOS运维系统运维工程师题库面向备考UOS运维认证、准备运维岗笔试面试的技术人员&#xff0c;也适合作为日常运维知识自测的练习材料。内容以单选题、多选题和判断题为主&#xff0c;覆盖NFS服务器配置、Ansible模块与模板语法、Apache与httpd配置、sshd安全…

作者头像 李华
网站建设 2026/10/29 21:09:19

AI拼豆创意工坊:图像处理与自动化设计

1. 项目概述&#xff1a;AI拼豆创意工坊 去年夏天我在手工市集看到一个拼豆摊位前排起长队&#xff0c;突然意识到这种看似简单的创意活动蕴含着巨大的技术升级空间。传统拼豆需要手工挑选颜色、排列组合&#xff0c;耗时耗力且容易出错。于是我用三个月时间开发了这个开源AI拼…

作者头像 李华