☰
IntraWeb+EasyUI老后台源码拆解:服务端直出与异步回填
2026/10/26 15:01:18 网站建设 项目流程

简介:Intraweb与EasyUI源面向使用Delphi进行Web开发的中级开发者,聚焦服务器端IW框架与jQuery前端EasyUI的协同使用。IW以组件模型承载按钮、表格、表单等服务器控件,并负责数据库连接、动态内容与业务逻辑;EasyUI则提供数据网格、下拉树、日期选择器等现成组件,降低HTML/CSS/JavaScript细节负担。两者结合后,可由IW通过JSON或AJAX输出数据,再由EasyUI在客户端完成展示、排序、过滤与编辑,适合企业后台、数据管理界面等场景。压缩包约4.22MB,页面显示文件总数为0,类型明细暂未提供,解压后以实际清单为准;资源说明中涉及EasyUI 1.4版API中文文档及IW集成EasyUI的示例或教程,可用于快速查阅组件属性和方法,并理解前后端无缝对接的组织方式。已有479人学习,对于需要在Delphi环境中搭建现代化Web界面、复用桌面端经验的读者,可作为组件查询与数据交互的落地参考。

1. 从一套老后台说起:IntraWeb 加 EasyUI 这套组合到底管什么用

接手一套跑了几年的内网后台,我通常先看两样东西:点一个按钮返回的是整页 HTML,还是一小段 JSON。返回整页的,大概率是 IntraWeb(下面统一写 IW)项目;再翻一眼静态目录,如果有easyui.css和jquery.easyui.min.js,那前端交互就是 jQuery EasyUI 在撑。这份 IntraWeb 与 EasyUI 源码,正好是这两种"老派但真管用"的技术叠在一起的样子:服务端用 Delphi 把 IWForm 直接渲染成网页,前端拿 EasyUI 的 datagrid、dialog、combobox 把表格和弹窗补齐。它适合手上压着一堆 Delphi 业务逻辑、又必须交付一个浏览器能打开的管理系统的人;也适合想搞清楚"不做前后端分离能不能写出后台"的开发者。方案不新,但改动快、部署轻,一个人扛一个模块是常态。

2. 技术栈与目录结构:Delphi 服务端直出,EasyUI 管交互

这套源码的整体思路可以一句话概括:页面结构由 IW 在服务端生成,交互细节交给 EasyUI 在浏览器里做。听上去简单,真正拆的时候会发现两边的边界很容易被写糊——有人把 EasyUI 的 ajax 全打开,绕开 IW 自己取数,结果会话、权限、连接池全部对不上;也有人全走 IW 的 postback,每点一次刷新整页,EasyUI 的组件状态全丢。这份源码的写法是折中的:数据查询走 IW 的异步回调,DOM 更新走 EasyUI 的 API,两边靠一段约定的 JSON 桥接。

2.1 运行环境与依赖清单

先把跑起来需要的东西摆清楚,别等编译报错再一个个找。这套源码的依赖不算多,但对版本比较敏感,尤其是 IW 和 Delphi 的匹配关系。

组件常见版本说明
Delphi10.x / 11 系列IW 14 之后基本只支持较新的编译器,Delphi 7 那套老 IW 是另一个分支,源码不通用
IntraWeb14.x / 15.x提供IWServerController、IWForm、IWTemplateProcessorHTML等单元
jQuery1.8 ~ 1.12EasyUI 1.5 依赖这个区间,别上 jQuery 3,会有兼容问题
jQuery EasyUI1.5.x / 1.9.x提供 layout、datagrid、dialog、combobox、form 等组件
数据库访问FireDAC 或 ADO源码里两种写法都能见到,新项目建议统一到 FireDAC
数据库SQL Server / MySQL连接串换一下即可,SQL 里有少量方言写法需要留意

几个需要提前说明的点。IW 的版本和 Delphi 版本是强绑定的,14.x 和 15.x 的属性名有出入,比如模板目录相关的属性在不同小版本里叫法不完全一样,编译时报"未声明标识符"多半是版本对不上,不是代码写错。EasyUI 的版本差异主要影响datagrid的loadData参数结构,1.5 之后要求传对象{total: n, rows: [...]},直接传数组在某些场景下分页会算错。

提示:先把 Delphi 编译器和 IW 的版本对上,再谈其他依赖。我见过太多次卡在第一步的,最后发现是拿 IW 15 的源码去喂 Delphi 7。

2.2 目录结构与单元职责

源码的目录分得比较清楚,服务端单元、页面模板、静态资源三块互不干扰,这也是 IW 项目比较舒服的地方——它不像前后端分离那样需要构建工具,改完 HTML 刷新浏览器就能看到。

intraweb-easyui-demo/ ├── src/ │ ├── Project1.dpr # 工程入口,注册 IW 的 HandleException │ ├── ServerController.pas # IW 服务端控制器:端口、会话超时、静态资源目录 │ ├── UserSessionUnit.pas # 每个浏览器会话一份,放数据库连接和当前用户 │ ├── DataModuleMain.pas # 数据模块:FDConnection + FDQuery,被各页面共用 │ ├── IWFormMain.pas/.dfm # 主页面(IWForm 子类) │ └── IWFormGoods.pas/.dfm # 商品维护页,演示 dialog + form 的写法 ├── wwwroot/ │ ├── templates/ │ │ ├── main.html # 主页面模板,用 {%控件名%} 占位 │ │ └── goods.html │ ├── easyui/ # EasyUI 的 themes / plugins 原样放进来 │ ├── js/app.js # 业务脚本:datagrid 初始化、与服务端同步数据 │ └── css/site.css └── bin/ # 编译输出,运行时 wwwroot 要跟着拷过去

单元职责上,ServerController.pas只管全局配置,不碰业务;UserSessionUnit.pas是每个会话独立实例,数据库连接放这里最合适,别塞到 IWForm 里,否则换页就断;DataModuleMain.pas放查询和更新逻辑,页面单元只负责组装参数和回写 UI。这套分层是老 IW 项目里比较健康的一种,虽然比不上正规的分层架构,但至少业务代码不会散落在各个AsyncClick事件里。

2.3 三种宿主模式,源码默认跑哪一种

IW 有个容易被忽略的点:它不是只能跑成独立服务。同一个IWServerController,可以编译成三种宿主形态,源码里的配置项和各处散落的路径写死了其中一种。

第一种是 Standalone,编译出来是个 exe,双击就能起一个 HTTP 服务,自带端口监听。开发阶段用这个最省事,改完代码重新编译运行,浏览器刷新就行,不用碰 IIS。第二种是 ISAPI,编译成 dll 挂到 IIS 的应用程序池下,适合已经有 IIS 环境、要跟其他站点共存的情况。第三种是 Apache 模块,用得比较少,一般出现在很老的部署环境里。

这份源码默认是 Standalone 模式,Port写在ServerController里。切到 ISAPI 时要改的不只是编译目标,还有两处:静态资源路径不能再依赖ParamStr(0)的同级目录,要用 IIS 下的物理路径或者虚拟目录;SessionTimeout要重新评估,IIS 的应用池回收策略会跟 IW 自己的会话机制打架,配置不当会出现"用户点一下就被踢回登录页"。这块后面在部署一节还会展开。

3. 核心链路拆解:一个 IWForm 页面如何挂上 EasyUI 组件

真正读懂这套源码,关键在于搞清楚一个页面从服务端生成到浏览器渲染的完整链路。很多人第一次看 IW 代码会懵,因为.pas里找不到任何Write('<table>')这样的输出语句,页面上却实实在在出现了表格和按钮,中间的转换过程是 IW 的模板引擎在做。把这条链路摸清,后面加页面、改布局、调事件就有章可循了。

3.1 页面骨架:模板占位符怎么对上 IWForm 控件

IW 渲染页面的方式有两种,一种是完全由服务端生成 HTML(LayoutMgr 为空),另一种是挂一个TIWTemplateProcessorHTML,把提前写好的 HTML 模板里的{%控件名%}替换成真实控件。这份源码用的是第二种,原因很实际:EasyUI 的 layout 组件需要固定的嵌套结构,纯服务端拼 HTML 根本没法维护。

主页面模板长这样:

<!-- wwwroot/templates/main.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>商品管理</title> <link rel="stylesheet" href="/easyui/themes/default/easyui.css"> <link rel="stylesheet" href="/easyui/themes/icon.css"> <script src="/easyui/jquery.min.js"></script> <script src="/easyui/jquery.easyui.min.js"></script> <script src="/js/app.js"></script> </head> <body> <!-- EasyUI 的 layout:把页面切成上中下三块 --> <div id="cc" class="easyui-layout" style="width:100%;height:100vh;"> <div>procedure TIWFormMain.IWAppFormCreate(Sender: TObject); begin // 指定这个页面用哪个 HTML 模板 IWTemplateProcessorHTML1.Templates.Default := 'main.html'; LayoutMgr := IWTemplateProcessorHTML1; // 把 JSON 容器的 HTML id 固定下来,方便前端脚本定位 IWLabelJSON.HTMLName := 'jsonbox'; IWLabelJSON.Css := 'display:none;'; // 初次打开页面就把数据查出来,避免用户看到空表格 LoadGoodsToJSON(''); end;

这里的逻辑要说清楚三层。第一层是Templates.Default,它指向wwwroot/templates/下的文件名,这个目录的根由ServerController的静态资源目录决定,改路径时要两边一起改。第二层是LayoutMgr,赋了模板处理器之后,IWForm 上所有Visible := True的控件都会去找模板里同名的{%...%}占位符,找不到的控件会被忽略但不报错——这是排查"控件怎么不显示"最常见的原因之一。第三层是HTMLName,IW 默认会给控件 id 加前缀,直接$('#IWLABELJSON')往往取不到,指定HTMLName是最稳的做法。

注意:占位符名字是控件的Name属性,不是HTMLName。写{%IWLabelJSON%}对应的是控件名,HTMLName只影响最终渲染出来的 HTML id。

3.2 服务端异步事件:点一下按钮,数据怎么回到 datagrid

数据回填是这套源码里最值得拆的一段。传统 IW 的写法是按钮点击后走完整 postback,IWLabel的Text一改,整页重渲染,EasyUI 的表格、弹窗状态全部丢光。这份源码改成了异步回调,只把结果回写一部分,页面其余部分不动。

服务端事件长这样:

procedure TIWFormMain.IWButtonQueryAsyncClick(Sender: TObject; EventParams: TStringList); begin // 1. 按关键词重新查一遍,把结果序列化进隐藏容器 LoadGoodsToJSON(IWEditKeyword.Text); // 2. 异步回调结束后,在浏览器端执行这段脚本 // 注意是"回调结束后",不是立刻执行 WebApplication.CallBackResponse.AddJavaScriptToExecute('syncFromServer();'); end;

LoadGoodsToJSON是这段链路的核心,它负责把查询结果转成 EasyUI 认识的 JSON 结构:

procedure TIWFormMain.LoadGoodsToJSON(const AKeyword: string); var LRows: TJSONArray; LItem: TJSONObject; LPack: TJSONObject; begin LRows := TJSONArray.Create; try FDQuery1.Close; // 参数化查询,关键词为空时用 '%' 匹配全部 FDQuery1.SQL.Text := 'SELECT id, name, price, stock FROM goods WHERE name LIKE :kw ORDER BY id'; FDQuery1.ParamByName('kw').AsString := '%' + AKeyword + '%'; FDQuery1.Open; while not FDQuery1.Eof do begin LItem := TJSONObject.Create; LItem.AddPair('id', FDQuery1.FieldByName('id').AsInteger); LItem.AddPair('name', FDQuery1.FieldByName('name').AsString); LItem.AddPair('price', FDQuery1.FieldByName('price').AsFloat); LItem.AddPair('stock', FDQuery1.FieldByName('stock').AsInteger); LRows.AddElement(LItem); FDQuery1.Next; end; // EasyUI 1.5+ 的 loadData 要求 {total, rows} 结构 LPack := TJSONObject.Create; LPack.AddPair('total', LRows.Count); LPack.AddPair('rows', LRows); IWLabelJSON.Text := LPack.ToJSON; finally LRows.Free; end; end;

参数上有几个点值得注意。AKeyword走的是LIKE模糊匹配,前后都加%,如果数据量上万,这个写法会全表扫,最好加个索引或者改成前缀匹配;total这里直接取了LRows.Count,只有做真分页时才需要单独COUNT(*),现在是一次性全查再全传,行数多的时候页面会明显卡,这是这份源码在生产环境里需要优化的第一位。LRows.Free放在finally里,是因为LPack接管了LRows之后由LPack负责释放,但中间任何一步抛异常,没接管的数组就会泄漏——Delphi 的引用计数在这些嵌套对象上特别容易翻车。

浏览器端的接收脚本放在app.js里:

// 页面初始化:先把 datagrid 的列定义好,数据留空 $(function () { $('#dg').datagrid({ singleSelect: true, pagination: false, columns: [[ { field: 'id', title: '编号', width: 80 }, { field: 'name', title: '名称', width: 220 }, { field: 'price', title: '单价', width: 100, align: 'right', formatter: function (v) { return Number(v).toFixed(2); } }, { field: 'stock', title: '库存', width: 100, align: 'right' } ]] }); // 首次进入,服务端已经在 IWAppFormCreate 里查过一次了 syncFromServer(); }); // 服务端异步回调结束后由 AddJavaScriptToExecute 调用 function syncFromServer() { var box = document.getElementById('jsonbox'); if (!box) { return; } var raw = box.textContent || box.innerText; if (!raw || raw.length < 2) { return; } try { var data = JSON.parse(raw); $('#dg').datagrid('loadData', data); } catch (err) { // 返回的不是合法 JSON,多半是会话过期返回了登录页 HTML console.error('服务端返回内容无法解析', raw.substring(0, 200)); } }

loadData这里有个细节容易被忽略:EasyUI 的loadData会清空当前表格并重建,如果用户正在编辑某一行,编辑框会丢。做行内编辑的页面要把loadData换成增量更新,或者干脆用edatagrid插件。formatter里做Number(v).toFixed(2)是为了避开23.100000000000001这种浮点噪声,数据库是decimal类型时也会出现,属于玄学范围之外的必然现象。

3.3 数据层:会话级连接与查询单元

UserSessionUnit.pas是 IW 里最容易被写烂的地方。它的生命周期跟浏览器会话绑定,每个用户一份,所以放数据库连接、当前登录人、权限缓存都合适。反过来,千万别把TFDQuery放这里再被多个页面同时用,异步回调并发时会撞车。

procedure TIWUserSession.DataModuleCreate(Sender: TObject); begin FDConnection1.Params.Clear; FDConnection1.Params.Add('DriverID=MSSQL'); FDConnection1.Params.Add('Server=127.0.0.1'); FDConnection1.Params.Add('Database=erp'); FDConnection1.Params.Add('User_Name=app_user'); FDConnection1.Params.Add('Password=******'); FDConnection1.Params.Add('Pooled=True'); // 连接池打开,减少握手开销 FDConnection1.LoginPrompt := False; // 服务端环境必须关掉,否则会弹窗卡死 FDConnection1.Connected := True; end;

LoginPrompt := False这一行是血泪经验,服务端没有交互界面,一旦驱动试图弹登录框,整个会话就挂在那里不动,表现是浏览器一直转圈、服务端 CPU 却不涨。Pooled=True让 FireDAC 在会话之间复用物理连接,几十个人用的内网后台足够了,人再多就要调池大小。

查询用的TFDQuery放在DataModuleMain里,每个 IWForm 通过UserSession.DataModuleMain拿到同一个实例。这里要保证同一时刻只有一个异步回调在用,IW 的异步回调默认是串行的(同一个会话内),所以实际不会并发,跨会话则是不同实例,安全。

4. 跑起来:从零编译、配置到发布

代码看懂是一回事,能不能跑起来是另一回事。这套源码的启动路径不算复杂,但有几个环节是必须手动处理的,尤其是静态资源目录和端口,配置错了表现是页面能打开、样式全丢,看着像前端问题,其实是路径没对上。

4.1 首次编译:从 .dpr 到能打开的页面

打开Project1.dpr,先确认几件事:编译目标平台(32 位还是 64 位,要和引用的 IW 运行库一致)、输出目录(默认bin)、搜索路径里包含 IW 的Source目录。这三项在.dproj里已经配好,直接用 IDE 打开项目文件就行,不用手动加。

编译前还要检查ServerController.pas的初始化代码:

procedure TIWServerController.IWServerControllerBaseCreate(Sender: TObject); begin // 静态资源根目录:EasyUI 的 js/css、templates 都在这个下面 // 用 ExtractFilePath(ParamStr(0)) 是为了让 exe 和 wwwroot 同级就能跑 ContentFilesDir := ExtractFilePath(ParamStr(0)) + 'wwwroot\'; // 会话 20 分钟超时。太长会在内存里堆一堆会话对象, // 太短用户填一半表单就被踢,内网后台 20 分钟是折中值 SessionTimeout := 20; // Standalone 模式的监听端口 Port := 8888; end;

编译成功后,bin目录下会生成 exe,但wwwroot不会自动拷过去。第一次运行的典型翻车现象是:浏览器打开http://localhost:8888能看到页面标题,但整个布局是乱的,按钮没有样式,控制台一堆 404。原因就是 exe 找不到wwwroot/easyui/。

手动把wwwroot整个目录拷到 exe 同级,再运行一次。控制台按 F9 启动时,IW 会自动打开浏览器,如果没有自动打开,手动访问http://localhost:8888/即可。

提示:ContentFilesDir结尾那个反斜杠不能省,少了它路径拼接会变成wwwrooteasyui,表现还是 404,但排查的时候容易看漏。

4.2 配置项:端口、模板目录、数据库连接串

配置分三处。端口在ServerController里,改完要重启进程。数据库连接串在UserSessionUnit里,改完不用重启,新会话生效。模板和静态资源目录在ServerController里,改完重启。

数据库连接串的写法跟驱动有关,源码默认给的是 SQL Server 的:

// SQL Server FDConnection1.Params.Add('DriverID=MSSQL'); FDConnection1.Params.Add('Server=127.0.0.1'); FDConnection1.Params.Add('Database=erp'); // MySQL 版本换成这样,注意 Port 参数名不一样 // FDConnection1.Params.Add('DriverID=MySQL'); // FDConnection1.Params.Add('Server=127.0.0.1'); // FDConnection1.Params.Add('Port=3306'); // FDConnection1.Params.Add('Database=erp'); // FDConnection1.Params.Add('CharacterSet=utf8mb4');

换数据库时最容易出问题的是 SQL 方言。源码里用了LIKE,这个两库通用,但如果业务代码里还用了TOP 10或者ISNULL(),MySQL 要改成LIMIT 10和IFNULL()。另外CharacterSet不设成utf8mb4,中文商品名会变问号,这个坑在老项目迁移里出现频率很高。

还有一个隐藏配置是ServerController里的缓存路径。IW 会把模板和部分资源缓存到临时目录,默认在系统 temp 下。多人共用一台服务器调试时,缓存目录最好改成项目目录下,方便排查。

4.3 发布:Standalone 与 IIS 两种落地方式

Standalone 发布最简单:把bin目录整个打包,拷到目标机器,配一个开机自启(Windows 计划任务或者做成服务),防火墙放行端口。优点是零依赖,缺点是没法跟其他站点共用 80 端口,前端还要配一个反向代理才能挂到域名下。

IIS 发布要把编译目标切成 ISAPI(生成 dll),在 IIS 里建应用程序池,注意几点:

配置项值说明
应用程序池 .NET CLR 版本无托管代码IW 是原生 ISAPI,不需要 .NET
启用 32 位应用程序按 dll 位数选64 位 dll 必须关,否则报 500.19
应用程序池回收固定时间 / 禁用回收会清掉内存里的会话,用户被踢
静态资源目录虚拟目录指向 wwwroot或把 wwwroot 内容放进站点根目录

应用程序池回收这一项特别容易踩坑。默认配置是 1740 分钟回收一次,看着没事,但 IW 的会话状态存在进程内存里,进程一换,所有在线用户的会话全丢,表现是"用着用着突然跳登录页"。稳妥的做法是把回收时间设成凌晨,或者把回收策略改成"仅内存超限时回收",再配合SessionTimeout做保底。

5. 常见问题排查:这几处我踩过不止一次

这套组合跑了这么久,出问题的位置其实很集中。下面五条是我在实际项目里反复遇到的,按现象、原因、解决三段写,遇到类似的先对照着看。

5.1 EasyUI 组件在异步回调后失效,表格变白板

现象:点查询按钮,Network 面板里请求成功、返回 200,但datagrid直接变成空白,表头都没了,控制台没有任何报错。

原因:IW 的异步回调只替换目标控件的 DOM 片段,浏览器不会重新执行$(document).ready,EasyUI 在首次加载时初始化的那些事件绑定和内部状态全丢了。如果回写的时候顺手把整个容器 innerHTML 换掉,表格连结构都不剩。

解决:不要在服务端替换包含 EasyUI 组件的容器。数据统一通过一个隐藏容器传,前端用datagrid('loadData', ...)更新内容而不是重建 DOM。回调结束的脚本用AddJavaScriptToExecute注入,确保在 DOM 更新之后执行。另外检查一下是不是误把IWRegion的AsyncRefresh打开覆盖到了表格所在的区域。

5.2$('#控件ID')返回 null,前端脚本全挂

现象:脚本里取输入框的值,$('#IWEditKeyword').val()一直是undefined,但页面上明明有这个框。

原因:IW 渲染控件时会改 id,不同版本的前缀不一样(IWCL_、IW_、IWFORM_都见过),直接按控件名取基本取不到。另外控件的HTMLName如果没设,默认是空的,IW 会自己在运行时生成。

解决:给需要前端操作的控件显式设HTMLName,比如IWEditKeyword.HTMLName := 'kw';,前端统一用$('#kw')。已经在生产的项目改不动服务端的话,用属性选择器绕过:$('input[name="IWEDITKEYWORD"]'),但这种方式对 IW 版本很敏感,能改服务端就别用。

5.3 会话过期后点按钮,控制台报 JSON 解析错误

现象:页面闲置一段时间,再点查询按钮,数据不更新,控制台出现Unexpected token '<'。

原因:会话超时后,IW 收到异步请求会把用户重定向到登录页,返回的是完整 HTML。前端脚本仍然按 JSON 去解析,JSON.parse碰到<html>直接抛异常。

解决:syncFromServer里先做内容判断,raw.trim().charAt(0) !== '{'就直接跳登录页,别硬解析。同时把SessionTimeout调到业务能接受的长度,前端加一个定时心跳请求(每 5 分钟打一次空接口)保活。这个问题的根子在会话机制和 ajax 的错配上,不在 JSON 解析本身,改解析只是止血。

5.4 中文变问号,或者写库时报字符集错误

现象:页面上显示的商品名是???,或者插入时报Incorrect string value。

原因:三个地方要同时是 UTF-8——HTML 模板的meta charset、HTTP 响应的Content-Type、数据库连接的字符集。任何一处是 GBK 都会出问题,MySQL 还要额外确认列和表的排序规则是不是utf8mb4。

解决:模板里固定写<meta charset="utf-8">;ServerController里有个Charset相关属性,设成utf-8;连接串加CharacterSet=utf8mb4。改造老库时注意,表字符集是utf8的话存 emoji 会失败,得改表。这个问题排查起来看着玄学,其实三处对齐就没事。

5.5 IIS 下报 500.19,或者站点能开但静态资源 404

现象:部署到 IIS 后访问首页报HTTP 错误 500.19,或者首页能开但 EasyUI 的 css/js 全部 404。

原因:500.19 一般是应用程序池位数和 ISAPI dll 不匹配,或者web.config里配了 IIS 不认识的 handler。静态资源 404 是虚拟目录没指对,IW 按ContentFilesDir去找物理路径,IIS 下这个路径必须用绝对路径。

解决:先把应用程序池的"启用 32 位应用程序"调成和 dll 一致,再用%windir%\system32\inetsrv\appcmd list config看 handler 配置有没有语法问题。静态资源这块,ContentFilesDir直接写死绝对路径,别用ParamStr(0),ISAPI 模式下ParamStr(0)指向的是w3wp.exe,路径全是错的。

6. 二次开发:把页面改造成自己的业务,别急着推倒

拿到这套源码的人,绝大多数最后都会做同一件事:把示例里的商品表换掉,接上自己的业务。这件事本身不难,难的是别一上来就大规模重构。我见过太多人第一步就是把 IW 的页面全删了换成 REST API 加 Vue,结果三个月后发现业务逻辑藏在各个AsyncClick里,重写成本远超预期。

比较务实的改造路径是"先扩后换"。第一步,把DataModuleMain里的查询抽成通用方法,参数改成表名、字段列表、过滤条件,让新增一个业务表只需要配一次。第二步,把页面模板里重复的 layout 结构抽出来,用 IW 的母版页机制(MasterForm)统一,避免每个页面都复制一份 EasyUI 的初始化代码。第三步才是替换,选一个不核心的模块先试水,跑顺了再扩大范围。

新增一个业务页面的最小改动清单,我一般按这个顺序走:

步骤改动位置检查点
建表与查询DataModuleMain.pas参数化查询、字段名统一小写
建 IWForm新增IWFormXxx.pas/.dfmIWAppFormCreate里挂模板
写模板wwwroot/templates/xxx.html占位符名与控件Name一致
写脚本wwwroot/js/xxx.js用HTMLName定位控件
注册路由菜单或ServerController会话内的跳转不要用绝对 URL

这里有个技巧值得单独说:新页面的 JSON 回写不要每个页面都写一遍LoadXxxToJSON,在DataModuleMain里做一个DatasetToEasyUIJSON(ADataset: TDataSet): string,只要字段名和前端columns的field对上,直接复用。我后来所有页面都走这一个方法,省下来的重复代码比想象中多。

验证改造是否成功,别只看页面能不能打开。我的习惯是同时开三个检查:浏览器 Network 面板看每次请求的响应体是不是合法 JSON、Delphi 的异常日志里有没有EJSONParseException、数据库连接数是不是随会话数线性增长(说明连接没被正确释放)。这三项都干净,才算改造完成。

从那以后我每次接手这类 IW 项目,第一件事都是先把ContentFilesDir、SessionTimeout、HTMLName这三个配置过一遍,再开始看业务代码——这三个地方没对齐,后面所有调试都是在错误的前提上打转,浪费的时间比读代码多得多。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询