2026/10/16 0:13:10

Delphi 老兵用 TMS WEB Core 写 Web:环境搭建、组件模型与前后端通信实战

Delphi 老兵用 TMS WEB Core 写 Web:环境搭建、组件模型与前后端通信实战 简介这份PDF教程面向希望用Delphi切入Web前端开发的初学者与进阶开发者围绕TMS WEB Core框架讲解如何以Delphi或Lazarus快速构建网站、Web应用及跨Windows、Mac、iOS、Android、Linux的App。内容从Delphi/Object Pascal的历史与现状讲起对比IntraWEB、uniGUI与TMS WEB Core三款Web框架的特点再深入配置TMS WEB Core环境、项目设置与自动化版本管理并覆盖Pas2JS编译器、RTL运行时库、预处理器、命令行编译、页面设计与调试等关键环节帮助传统Delphi程序员理解100%纯HTML/JS应用的开发思路。资源为单个PDF文件压缩包约3.84MB已有5000余人学习。目录结构清晰涵盖框架简介、快速入门、组件技能与部署分发等模块适合作为系统学习TMS WEB Core、查漏补缺的参考读物。1. 当 Delphi 老兵开始写 WebTMS WEB Core 到底解决了什么一个做了十几年 Delphi 桌面端的人第一次被要求「把现有系统搬到浏览器里」大概率会经历三个阶段先想用 uniGUI 或 IntraWeb 糊一个再想干脆重写成 Vue 后端 API最后发现两条路都要把积累多年的业务逻辑推倒重来。TMS WEB Core 出现的意义恰恰是给这批人第三条路——用 Object Pascal 写前端编译成 JavaScript在浏览器里跑同时保留 Delphi 的 IDE、调试器和 VCL 式的组件思维。它不是把 Delphi 变成 Web 框架而是把 Web 前端变成 Delphi 能编译的目标平台。这篇文章面向的是有 Delphi 基础、想低成本切入 Web 交付的一线开发者从环境搭建、组件模型、前后端通信到部署排错把这条链路走通一遍。2. 环境搭建与第一个可运行页面从 IDE 到浏览器2.1 安装 TMS WEB Core 与编译器链路TMS WEB Core 的安装包通过 TMS 官方的订阅渠道获取安装时会向 Delphi IDE 注册一套新的工程类型和一组组件面板页。安装完成后在 Delphi 的File New Other里能看到TMS WEB Core分类里面包含TMS Web Application、TMS Web Core Web Application等模板。这里有个容易忽略的点TMS WEB Core 依赖一个 Pascal-to-JavaScript 的编译器pas2js它随安装包一起部署但需要确认 IDE 的库路径里已经包含TMSWEBCore的源码目录否则编译时会报找不到WEBLib.Controls之类的单元。安装后建议先做一次环境自检确认编译器可用# 在 Delphi 安装目录下查找 pas2js 编译器可执行文件 # 常见路径形如 # C:\Program Files (x86)\Embarcadero\Studio\版本\TMSWeb\Compiler\pas2js.exe # 用 --version 确认编译器能独立运行 pas2js.exe --version这段命令的作用是确认 pas2js 编译器本身没有损坏。如果这一步就报错后面所有编译问题都不用查了直接重装 TMS WEB Core。参数上没什么可调的--version只是探活。真正需要关注的是 IDE 里Tools Options TMS WEB下的编译器路径配置如果团队里多人协作建议把这个路径写进项目组的统一环境说明避免有人用旧版本编译器产出不一致的 JS。2.2 用 TMS Web Application 模板跑通最小页面新建TMS Web Application后IDE 会生成一个包含Project1.html、Project1.dpr和主窗体单元的结构。主窗体的类型是TWebForm上面可以放TWebButton、TWebEdit、TWebLabel这类组件。写一个最小交互按钮点击后把输入框内容回显到标签上。// Unit1.pas procedure TForm1.WebButton1Click(Sender: TObject); begin // WebEdit1.Text 在浏览器端就是 input 的 value WebLabel1.Caption : 你输入的是 WebEdit1.Text; end;这段代码的逻辑很直白但背后发生的事值得说清楚WebButton1Click并不是在服务端执行的它被 pas2js 编译成 JavaScript 事件回调直接绑定到浏览器 DOM 上。WebEdit1.Text读写的是真实 DOM 节点的 value 属性WebLabel1.Caption最终落到 innerHTML。也就是说你写的是 Pascal跑的是前端。参数层面TWebEdit的Text属性是双向的但要注意它默认不触发OnChange的实时同步如果需要输入即响应得用OnKeyUp或开启TWebEdit的ImmediateUpdate类属性不同版本命名略有差异以组件面板实际属性为准。编译运行后IDE 会启动一个内置的轻量 Web 服务器把生成的 HTML、JS、CSS 托管在本地端口浏览器自动打开。这个内置服务器只用于开发调试不要拿它当生产部署方案。2.3 工程结构里哪些文件该进版本库一个 TMS WEB Core 工程编译后会产出大量中间文件如果全丢进 Git仓库会迅速膨胀。常见做法是只保留源码和配置文件/目录是否入库说明*.dpr/*.pas是工程与单元源码*.html是页面宿主文件含 JS 引用*.dproj是工程配置含编译选项TMSWeb\输出目录否编译产物含大量 JS*.js.map否调试映射按需生成.identcache/.dsk否IDE 本地状态这张表的价值在于避免团队协作时有人把编译产物提交上去导致每次拉取都产生冲突。我一般会在.gitignore里直接写死输出目录名而不是逐个文件排除。3. 组件模型与页面布局TWebForm 和 VCL 的差异在哪3.1 TWebForm 不是 TForm别用桌面思维套从 VCL 转过来的人最容易翻车的地方是把TWebForm当成TForm用。两者在生命周期上有本质区别VCL 的窗体是操作系统窗口TWebForm最终是一个div容器它的Show、Hide、Close对应的是 DOM 的显示隐藏和路由跳转。更关键的是TWebForm没有真正的「模态」概念ShowModal在 Web 端要么被模拟成遮罩层要么直接不可用取决于你用的组件版本。布局上VCL 的绝对坐标Left/Top在 Web 端会变成 CSS 的 absolute 定位一旦浏览器窗口尺寸变化页面就会错位。正确做法是优先使用TWebPanel配合Align属性或者用TWebResponsiveGrid这类响应式容器。我见过一个项目主界面全部用绝对坐标摆控件在开发机上看着没问题用户用笔记本一打开右侧按钮全跑到屏幕外这就是典型的桌面思维残留。3.2 用 TWebResponsiveGrid 做自适应布局的最小示例下面这段代码演示如何用响应式网格把三个卡片横向排列窄屏时自动换行// 在 FormCreate 里动态构建网格内容 procedure TForm1.WebFormCreate(Sender: TObject); var i: Integer; Card: TWebPanel; begin WebResponsiveGrid1.Columns : 3; // 宽屏三列 WebResponsiveGrid1.Breakpoint : 768; // 小于 768px 时降为单列 for i : 1 to 3 do begin Card : TWebPanel.Create(Self); Card.Parent : WebResponsiveGrid1; Card.ElementClassName : card; // 挂自定义 CSS 类 Card.Caption : 卡片 IntToStr(i); end; end;逻辑说明Columns控制宽屏下的列数Breakpoint是触发降列的像素阈值这两个参数决定了布局的响应行为。ElementClassName是关键它把 Delphi 组件和外部 CSS 类关联起来样式细节交给 CSS 文件去写而不是在 Pascal 里硬编码颜色和边距。参数调整上Breakpoint要根据目标用户的主流设备来定如果用户大量使用平板768 可能偏小可以调到 1024。失败时先看浏览器开发者工具里网格容器的实际宽度和 CSS 类是否生效很多时候是 CSS 文件没被正确引用。3.3 组件事件在浏览器端的真实执行时机TWebButton.OnClick、TWebEdit.OnChange这些事件编译后都是标准的 DOM 事件监听。这意味着它们的执行时机和浏览器事件模型完全一致OnClick在 mouseup 之后触发OnChange在 input 失焦或回车时触发。如果你在OnChange里做实时校验用户输入过程中不会触发体验上会觉得「没反应」。这时候应该改用OnKeyUp或OnInput如果组件暴露了的话。另一个坑是事件里的异常处理。在 VCL 里未捕获异常会弹出一个对话框在浏览器里未捕获异常只会静默写进 console用户看到的是「点了没反应」。所以每个事件处理里最好包一层 try-except把错误信息通过console.error或页面提示暴露出来否则排查起来就是黑匣子。4. 前后端通信TMS WEB Core 怎么和 Delphi 后端对接4.1 三种通信方式的选型对比TMS WEB Core 前端和 Delphi 后端之间常见有三条路REST/JSON、WebSocket、以及 TMS 自家的 RemoteDB。选型取决于数据实时性和团队技术栈。方式适用场景实时性实现复杂度REST/JSON常规增删改查请求-响应低WebSocket消息推送、协同编辑双向实时中RemoteDB直接操作数据集准实时中高大多数业务系统用 REST 就够了。RemoteDB 适合那种「前端要直接翻数据集」的场景但它对网络稳定性和权限控制要求更高不适合公网弱网环境。4.2 用 TWebHttpRequest 调 REST 接口的完整写法假设后端有一个 Delphi 写的 REST 服务返回 JSON 格式的用户列表。前端这样调procedure TForm1.LoadUsers; var Req: TWebHttpRequest; begin Req : TWebHttpRequest.Create(Self); Req.URL : https://your-api-host/api/users; Req.Command : httpGET; Req.Headers.Add(Authorization: Bearer FToken); Req.OnResponse : HandleUsersResponse; Req.Execute; end; procedure TForm1.HandleUsersResponse(Sender: TObject; const AResponse: string); var Arr: TJSArray; i: Integer; Obj: TJSObject; begin // AResponse 是原始 JSON 字符串用 TJSJSON 解析 Arr : TJSJSON.parse(AResponse) as TJSArray; for i : 0 to Arr.Length - 1 do begin Obj : Arr[i] as TJSObject; // 把数据填进 TWebResponsiveGrid 或表格组件 AddUserRow(Obj[name], Obj[email]); end; end;逻辑说明TWebHttpRequest是异步的Execute发出请求后不阻塞结果在OnResponse回调里处理。Command指定 HTTP 方法Headers用来带认证信息。解析部分用的是TJSJSON这是 pas2js 提供的 JS 互操作单元TJSArray和TJSObject直接映射到 JavaScript 的数组和对象。参数上URL建议从配置读取而不是硬编码Authorization头的 token 要处理过期刷新否则用户会莫名其妙被登出。失败排查顺序先看浏览器 Network 面板里请求是否发出、状态码是多少如果是 401查 token如果是 0 或 CORS 错误查后端是否允许了前端域名。CORS 是 Delphi 后端最容易漏配的一环需要在响应头里加Access-Control-Allow-Origin。4.3 后端 Delphi 侧返回 JSON 的注意事项后端如果用TWebModule或TIdHTTPServer手写 REST返回 JSON 时要注意字符编码。Delphi 字符串默认是 UTF-16直接写进响应流可能变成乱码。常见做法是用TJSONObject构造后ToJSON再按 UTF-8 编码写出。另外日期字段建议统一用 ISO 8601 字符串不要用 Delphi 的TDateTime浮点数直接序列化前端解析会疯掉。5. 避坑与常见问题那些让我加班到凌晨的细节5.1 编译通过但页面白屏现象IDE 里编译零错误浏览器打开一片空白console 里可能只有一行Uncaught SyntaxError。原因通常是某个单元的initialization段里访问了浏览器不存在的对象或者 HTML 宿主文件里 JS 引用路径写错。解决先看 console 第一条错误定位到具体 JS 文件再检查Project1.html里script标签的 src 是否指向了正确的输出目录。我一般会在 HTML 里加一个window.onerror钩子把错误直接显示在页面上省得每次开 console。5.2 中文乱码现象页面上的中文变成问号或方块。原因HTML 文件缺少meta charsetutf-8或者后端返回的 Content-Type 没带 charset。解决在 HTML 的 head 里显式声明 UTF-8后端响应头加Content-Type: application/json; charsetutf-8。Delphi 侧写字符串时确认用的是 UTF-8 编码流。5.3 事件绑定失效现象按钮点了没反应但代码逻辑没问题。原因组件在FormCreate之前就被创建或者动态创建的组件没有正确设置Parent。解决动态组件一定要先Create再设Parent最后挂事件顺序错了DOM 节点还没插入文档事件监听就绑不上。5.4 内存泄漏与组件销毁现象页面长时间运行后越来越卡。原因动态创建的TWebPanel、TWebHttpRequest没有在不用时Free。浏览器端虽然没有传统内存泄漏那么严重但 DOM 节点和 JS 对象堆积一样会拖慢页面。解决对一次性使用的请求对象在OnResponse末尾Free对动态 UI切换视图时显式清理旧节点。5.5 部署后接口 404现象本地开发一切正常部署到服务器后所有 API 请求 404。原因开发时用的是 IDE 内置服务器加相对路径部署后前端静态资源和后端 API 不在同一域名或路径下。解决把 API 基地址抽成配置项部署时按环境注入后端确认路由前缀和前端请求路径一致。这个问题没有玄学就是路径对不上但每次换环境都要重新确认一遍。6. 进阶把调试和构建流程固化下来走到这里基本链路已经通了。但要让这个方案在团队里真正跑起来还得解决两件事调试效率和构建一致性。调试方面TMS WEB Core 支持生成 source map浏览器开发者工具里能直接看到 Pascal 源码行号。开启方式是在工程选项里勾选生成 map 文件部署时把.js.map一起放上去生产环境可以关掉。我习惯在开发阶段始终开着 source map断点直接打在 Pascal 代码上比看编译后的 JS 舒服太多。如果遇到 source map 不生效先确认浏览器是否启用了「Enable JavaScript source maps」再看 map 文件里的 sources 路径是否和实际文件结构匹配。构建一致性方面pas2js 编译器版本、TMS WEB Core 组件版本、Delphi IDE 版本三者要锁死。我吃过一次亏本地用新版本编译器构建通过CI 机器上还是旧版本产出的 JS 在某个浏览器上行为不一致排查了一整天才定位到编译器差异。现在的做法是在项目根目录放一个build-env.md写清楚三个版本号CI 脚本里加一步版本校验不匹配直接失败。还有一个值得固化的习惯把常用的页面交互封装成可复用的TWebForm基类或辅助单元。比如统一的错误提示、统一的 loading 遮罩、统一的 token 刷新逻辑。这些在 VCL 时代就是基类干的事搬到 Web 端一样适用。不要每个页面都重写一遍TWebHttpRequest的创建和销毁那是给自己挖坑。最后说一个验证方法每次改完前端逻辑不要只在 IDE 内置浏览器里测至少再用一个真实浏览器Chrome 或 Edge的隐私模式跑一遍。内置浏览器和真实浏览器在缓存、CORS、安全策略上都有差异很多问题只有真实环境才暴露。这个习惯帮我省下了至少三次「上线后才发现」的尴尬。希望帮到你。本文还有配套的精品资源点击获取