简介:面向使用Delphi语言进行Web应用程序开发的工程师,这份资料将Delphi环境下成熟的Intraweb后端框架与基于jQuery的EasyUI前端界面库结合起来,重点解决在传统服务器控件模式下难以兼顾现代交互体验的痛点。压缩包整体约4.22MB,包含EasyUI 1.4版中文API手册,以及Intraweb与EasyUI协同应用的示例说明;前者帮助开发者快速查阅数据网格、下拉树、日期选择器等组件的属性、事件与方法,后者则展示如何将IW服务器控件产生的数据通过JSON或AJAX输出到前端,交给EasyUI进行展示与操作。目前已有479人学习下载。通过学习这份内容,开发者可以理解Intraweb组件模型如何延续桌面开发经验,如何在服务器端完成数据库连接、动态内容生成与模板定制,同时把握EasyUI以简单配置实现复杂交互的诀窍;典型场景包括不刷新页面完成数据排序、过滤和编辑,适合正在从传统Delphi桌面应用向Web环境迁移的团队。资源也适合技术负责人用于评估前后端分层方案,帮助团队在保持Delphi技术栈的前提下引入现代UI组件库,减少重复开发工作量。对于希望降低前端学习成本、快速搭建数据驱动界面的开发者来说,这套组合提供了一条清晰可行的技术路径。
1. Intraweb与EasyUI源码包:先看懂这套组合能干什么
如果你所在团队的存量业务代码是用 Delphi 写的,客户却突然要求出一个 B/S 端管理系统,这套「Intraweb 与 EasyUI」源码包就是典型的参考答案。它把 Intraweb 这个 Delphi 系 Web 框架当作后端容器,再把 jQuery 时代的 EasyUI 组件库接到前端,最终拼出一套能登录、能查列表、能提交表单的业务系统骨架。拆这套源码时最直观的感受是:它不追求新潮架构,而是用最短路径把老代码搬上浏览器。适合两类人——一类是手里有 Delphi 存量系统、想快速包装成 Web 端的开发者,另一类是刚接触 IW 工程、想找一个完整可编译项目照着改的新手。下面从技术栈开始逐层拆。
2. 技术栈与目录结构:Intraweb 后端与 EasyUI 前端的配合方式
2.1 为什么是 Intraweb + EasyUI
先明确这套组合的定位。Intraweb(简称 IW)是 Delphi/C++Builder 环境下的 Web 开发框架,它的核心思路是把传统 VCL 表单映射成网页,服务端保留 Form 和 DataModule 的开发习惯。EasyUI 则是基于 jQuery 的 UI 组件库,提供现成的 datagrid、tree、tabs、dialog,数据格式约定为total + rows的标准 JSON。两者结合时,常见做法是:Intraweb 负责会话管理、业务逻辑和数据库访问,EasyUI 负责页面上的表格、树形菜单和弹窗交互。
我拆这套源码时,第一印象是它没有引入额外中间层。比起 REST + 独立前端的方案,这里属于“服务端渲染页面 + 前端局部 AJAX”的混合模式。登录页和主框架页由 IWForm 输出,列表数据和保存动作走 IW 的 WebAction 返回 JSON。这样做的收益是开发模型统一,老 Delphi 工程师不用学 Vue/React 就能上手;代价是前后端耦合度高,静态资源和动态页面混在一起,部署时要格外注意路径。
2.2 源码包目录结构一览
打开压缩包后,典型布局如下:
| 路径 | 作用 |
|---|---|
*.groupproj/*.dpr | Delphi 工程组文件和主程序入口 |
Source/ | 存放 IWForm、IWDataModule、ServerController 等单元 |
wwwroot/ | EasyUI 的 css、js、图片等静态资源 |
Database/ | 初始化脚本,包含建表语句和初始账号 |
bin/ | 编译产物,Standalone 模式下的可执行文件 |
说明一点:Intraweb 有两种部署形态。一种是 Standalone 模式,exe 自己监听端口,适合内网演示;另一种是 ISAPI DLL 挂在 IIS 下。这套源码通常默认按 Standalone 配置,改起来也最简单,后面第 4 章会讲怎么切换。
2.3 工程整体编译顺序
一个 IW 工程组里往往包含多个项目,编译顺序有讲究。先编译运行时包相关项目(如果有的话),再编译主工程,否则主工程会找不到刚生成的 DCU。实际拆解时我一般先打开.groupproj,右键主工程,检查 Project Options 里的 Runtime Path 是否指向源码包自带的 Intraweb 版本,接着按以下顺序走:
- 清理
bin和__history目录,避免旧文件干扰。 - 编译主工程,确认没有单元文件缺失。
- 如果提示缺少
*.bpl,检查 Tools > Options > Environment Variables 里的 Library 路径是否补齐。
这一步的常见翻车点是:下载的源码包里写死了绝对路径,换一台机器编译就报找不到文件。解法是把工程里的$(IWPath)这类宏变量统一成相对路径,再在 Delphi 的 Library 路径里加一次。
3. 核心模块拆解:登录会话、查询列表与数据提交的完整链路
3.1 页面骨架:IWForm 与 EasyUI 静态资源的整合
Intraweb 默认会生成自己的样式,要做成 EasyUI 风格,核心是接管 IWForm 的 HTML 模板。源码里通常有一个主窗体,在OnCreate里把模板内容塞进去:
procedure TMainForm.IWAppFormCreate(Sender: TObject); begin Self.Template.Text := '<html>' + '<head>' + ' <title>#TITLE#</title>' + ' <link rel="stylesheet" href="/wwwroot/easyui/themes/default/easyui.css" />' + ' <link rel="stylesheet" href="/wwwroot/easyui/themes/icon.css" />' + ' <script src="/wwwroot/easyui/jquery.min.js"></script>' + ' <script src="/wwwroot/easyui/jquery.easyui.min.js"></script>' + '</head>' + '<body>' + ' #BODY#' + '</body>' + '</html>'; end;逻辑说明:模板里的#TITLE#和#BODY#是 Intraweb 的固定替换标记,运行时会被替换为窗体标题和控件渲染结果。参数说明:/wwwroot/开头表示静态资源走 Intraweb 的虚拟目录映射,能这样做的前提是 ServerController 里配置了静态文件路径指向源码包的wwwroot目录,否则浏览器会 404。拆这类工程时,优先检查这里,因为 EasyUI 的样式是否生效基本由这行决定。
3.2 查询链路:服务端拼 JSON,前端 datagrid 消费
列表页是 EasyUI 应用最常见的场景。源码里的查询一般遵循一个固定范式:客户端 datagrid 发起请求 -> 服务端执行 SQL -> 输出{total: n, rows: [...]}。下面是一段简化后的服务端实现,去掉业务字段只剩骨架:
procedure TDataModuleMain.BuildGridJson(AResponse: TWebResponse); var Sql: string; SB: TStringBuilder; I: Integer; begin Sql := 'SELECT user_id, user_name, dept_name FROM sys_user ORDER BY user_id'; qryGrid.Close; qryGrid.SQL.Text := Sql; qryGrid.Open; SB := TStringBuilder.Create; try SB.Append('{"total":'); SB.Append(qryGrid.RecordCount); SB.Append(',"rows":['); I := 0; while not qryGrid.Eof do begin if I > 0 then SB.Append(','); SB.Append('{'); SB.Append('"user_id":' + IntToStr(qryGrid.FieldByName('user_id').AsInteger) + ','); SB.Append('"user_name":"' + qryGrid.FieldByName('user_name').AsString + '",'); SB.Append('"dept_name":"' + qryGrid.FieldByName('dept_name').AsString + '"'); SB.Append('}'); Inc(I); qryGrid.Next; end; SB.Append(']}'); AResponse.Content := SB.ToString; finally SB.Free; end; end;这段代码的逻辑说明:用TStringBuilder手工拼 JSON,避免了在循环里不断做字符串加法导致的内存碎片问题,是 IW 工程里比较老练的写法。参数说明:qryGrid是 Intraweb 自带的TIWQuery组件,连接串在IWDataModule的OnCreate里设置;字段名必须和表结构一致,拼错了前端拿到的就是空值。一个值得注意的细节是:RecordCount在 Intraweb 的查询下可以直接读取,因为 TIWQuery 默认走的是客户端游标,拿全量数据内存中处理,适合内网数据量不大的系统。
前端部分就是标准的 EasyUI datagrid 用法:
$('#dg').datagrid({ url: '/webaction/grid_data', method: 'get', pagination: true, pageSize: 20, fitColumns: true, singleSelect: true, columns: [[ { field: 'user_id', title: '用户ID', width: 60 }, { field: 'user_name', title: '用户名', width: 120 }, { field: 'dept_name', title: '部门', width: 140 } ]] });逻辑说明:EasyUI 的 datagrid 会自动把url返回的 JSON 解析成表格行,前提是返回体里必须有total和rows两个字段,恰好对应服务端拼的格式。参数说明:pagination开启分页栏后,EasyUI 会给 URL 自动追加page和rows参数,服务端拿到后可以做 LIMIT 分页,代码里没利用这个参数,所以是假分页,二次开发时可以顺手改成真分页。
3.3 数据提交:AJAX 回调到 WebAction
查完列表就要做增删改。源码里的保存逻辑通常也是走 WebAction,前端用 EasyUI 的$.post,服务端解析参数并执行 SQL。一个可控的实现套路是这样的:
procedure TWebAppServerControllerHandleAction(Action: TIWWebAction; var Handled: Boolean); var LActionName: string; LParam: string; begin LActionName := Action.Name; if LActionName = 'save_user' then begin Handled := True; LParam := Action.QueryFields.Values['user_name']; // 这里执行 update/insert SQL Action.Response.Write('{"success":true, "msg":"保存成功"}'); end; end;逻辑说明:TIWWebAction是 Intraweb 对 HTTP 请求的封装,Action.Name是 URL 里 action 名,QueryFields拿到 GET 参数,POST 参数则在ContentFields里。参数说明:Handled := True表示该请求已处理,如果忘了设置,IW 会继续走默认逻辑,导致浏览器收到一整个 HTML 页面而不是 JSON,那种翻车现场一般都能从 Network 面板里一眼看出来。
前端配合的调用代码是:
function saveUser() { var row = $('#dg').datagrid('getSelected'); $.post('/webaction/save_user', { user_id: row ? row.user_id : 0, user_name: $('#txtName').val() }, function(res) { if (res.success) { $('#dg').datagrid('reload'); } }, 'json'); }3.4 菜单与权限骨架
业务系统不能没有菜单。这套源码里的菜单结构一般是tree组件 + 固定层级表,权限则简化为用户表里的角色字段,登录后把角色存进 IWUserSession:
// 登录成功后写入会话 UserSession.CurrentRole := qryLogin.FieldByName('role_code').AsString;菜单渲染时根据角色拼接不同的节点。常见做法是用一个公共函数输出菜单树的 HTML,角色为admin的渲染全部模块,普通角色只渲染授权目录。这块源码的精髓不在于多复杂,而是让你看清楚 IW 的会话机制怎么和前端交互——用户状态始终保存在服务端UserSession,EasyUI 只负责展示,这一点和纯前端 Token 方案完全不同。
4. 跑通项目:环境准备与三步启动流程
4.1 环境准备清单
先把运行环境理清楚。这套源码需要的软件集中在 Windows + Delphi 总部件:
- Delphi XE 或更高版本,建议 RIO 以上,对 Intraweb 15 的兼容性更好。
- Intraweb 运行包,源码包里如果带
*.bpl就直接用,没有的话需要自行安装对应版本。 - 数据库,常见是 SQL Server 或 MySQL,脚本在
Database/目录下。 - 浏览器,推荐 Chrome 的兼容模式或 Firefox,EasyUI 的 AJAX 行为更标准。
环境里最容易出问题的是 Intraweb 版本不匹配。Delphi 自带的版本和工程编译时要求的版本不一致,会直接报F2048或F2588这类单元版本错误。我一般会先看.groupproj文件里引用的 IW 版本号,再对照ServerController里属性有无明显差异,比如ServerController.Port这类配置项在 15.x 和之前的版本就有些不同。
4.2 编译与启动:Standalone 模式第一步
打开工程组,确认主项目是ServerController或IWMain开头,编译器选项里目标平台是 Win32(IW 对 x64 兼容一般)。然后按 F9 编译运行。首次启动时如果 IDE 报找不到IW15.DELPHI包,去 Component > Install Packages 里把源码包提供的运行时包加进去。
启动成功后,控制台或 IDE 状态栏会显示端口号,默认常见是http://localhost:8888。注意此时源码包里的静态资源和 DLL 是否被自动拷贝到了输出路径,如果没有,手动把wwwroot复制到bin下,否则登录页虽然能打开,CSS 却全部失效。
4.3 数据库初始化和连接串修改
打开Database/init.sql看一眼表结构。常见的坑是脚本里用了 SQL Server 方言,而你本机是 MySQL,导致执行一半报错。解决办法是让脚本按目标库切换语法,或者干脆手动建核心表。
连接串一般在DataModule的OnCreate里,或独立放在一个Config.ini文件里。以 MySQL 为例,改成:
DriverName=MySQL HostName=127.0.0.1 Database=erp_demo User_Name=root Password=123456改完重新编译,访问登录页,用脚本里的初始账号登录。常见初始账号是admin/admin123,在sys_user表里可以看到密码列,如果密码是密文,说明登录时做了加密处理,想改密码需要先看OnLogin里的哈希算法。登录成功后,看到 EasyUI 风格的主框架和 datagrid 列表,跑通流程就完成了。
5. 避坑记录:Intraweb 与 EasyUI 联调最常见的五个坑
5.1 页面样式全丢,浏览器控制台一堆 404
现象:登录页能显示 HTML,但没有任何样式,F12 看到easyui.css报 404。
原因:Intraweb 的静态资源映射没有配置。IW 默认只处理动态请求,/wwwroot/下的文件需要显式告诉 ServerController 这个虚拟目录存在。
解决:在ServerController的OnConfig里配置静态路径为源码包wwwroot目录。我一般直接在ServerController.Port下方加上:
ServerController.StaticFiles.Add(ServerController.FilesDir);FilesDir是 IW 内置变量,运行时指向 exe 所在目录下的wwwroot。改完重新生成,就不用每次手动复制静态目录了。
5.2 datagrid 请求返回 HTML 而不是 JSON
现象:列表一直空白,点刷新没反应,Network 面板里响应内容是整段 HTML。
原因:请求没有走对应的 WebAction,被 IW 默认的页面控制器拦截了。另一种可能是Handled没置 True,导致 IW 把请求当成页面请求处理。
解决:确认 URL 里的 action 名和Action.Name完全一致,并检查是否在每个分支都设置了Handled := True。一个隐秘的坑是大小写:IW 的 action 区分大小写,Grid_Data和grid_data会被当成两个名字。
5.3 登录成功后跳转主页面失败,停留半天
现象:登录按钮点了没反应,或主页面迟迟打不开,日志里报 Session 超时。
原因:Intraweb 默认使用服务端 Session 校验,EasyUI 的 AJAX 请求如果没有携带 IW 的 Session Cookie,会被当成新会话处理。最常见的是前端代码用了跨站请求或跳过了带 Cookie 的请求方法。
解决:所有 EasyUI 的$.post和 datagrid 的url请求保持同源,不要加绝对 IP 地址,并且确认浏览器接受 Cookie。我遇到过最严重的场景是团队为了调试把域名改成了localhost,而服务端启动时绑定的是机器名,导致 Cookie 的 Domain 不匹配,Session 永远验证不过。
5.4 中文乱码,列表显示一堆问号
现象:数据库里的中文没问题,前端展示时变成???或乱码。
原因:服务端拼 JSON 时没有设置响应编码,Intraweb 默认按 ANSI 输出,而 EasyUI 的 datagrid 是按 UTF-8 解析的。
解决:输出前显式设置响应头部:
AResponse.ContentType := 'application/json; charset=utf-8'; AResponse.CharSet := 'utf-8';如果是表单提交乱码,检查数据库连接的字符集配置,MySQL 要在连接串里加Charset=utf8mb4。这类乱码问题和 IW 版本关系不大,基本上都是编码声明遗漏造成的。
5.5 IWForm 里嵌 EasyUI 组件导致事件不触发
现象:页面上有 EasyUI 的按钮和 datagrid,但点击之后没反应,查看 HTML 发现 IW 控件被渲染成了一套 VCL 风格的样式。
原因:Intraweb 对 Form 内的 VCL 控件有自己的渲染机制,直接往 IWForm 上放TIWButton然后试图用 EasyUI 的样式去覆盖,会出现双框架冲突。源码包里的正确姿势是:EasyUI 负责视觉,IW 只做数据通道,不混用渲染。
解决:页面框架用 HTML 模板手写,EasyUI 组件直接写在模板里,IW 控件退化为隐藏的TIWButton或者干脆用 WebAction 完成全部交互。如果一定要用 IW 的组件,就在OnRender里做样式替换,不要指望 CSS 全局覆盖能把TIWButton变成 EasyUI 按钮。
6. 二次开发思路:给列表页加一个操作列并验证数据链路
看懂了上述链路,最后的改造以小见大。多数业务系统都会在 datagrid 里放“编辑、删除”操作按钮,而操作列在 EasyUI 里是用formatter实现的,不需要额外引入组件。改造步骤如下:
第一步,在 datagrid 的columns末尾增加一个字段:
{ field: '_operate', title: '操作', width: 120, formatter: function(value, row) { return '<a href="#" onclick="editUser(' + row.user_id + ')">编辑</a> ' + '<a href="#" onclick="deleteUser(' + row.user_id + ')">删除</a>'; } }第二步,新增对应的两个函数,分别打开编辑弹窗和提交删除请求:
function editUser(id) { $.get('/webaction/get_user', { user_id: id }, function(userRow) { $('#txtName').val(userRow.user_name); $('#dlg').dialog('open'); }, 'json'); } function deleteUser(id) { if (confirm('确认删除该用户?')) { $.post('/webaction/delete_user', { user_id: id }, function(res) { if (res.success) { $('#dg').datagrid('reload'); } }, 'json'); } }第三步,服务端补两个 WebAction。get_user返回单行 JSON,delete_user执行物理删除并返回success标志。这里有一个值得养成的验证习惯:每次改完接口,先用浏览器直接访问/webaction/delete_user?user_id=1,如果返回的不是{"success":true}这类纯 JSON,说明 action 名拼错了或Handled没设置,此时再去调前端就白折腾了。我把这个步骤当作联调的底线检查。
菜单层级也是一样。角色为admin时渲染全部模块,普通角色只渲染授权目录。做法是在登录后把角色码写入UserSession,渲染菜单时对比当前角色。这套源码里,二次开发的最大收益点就藏在这里:把散落在 HTML 模板里的菜单字符串改成一个公共函数,后续加页面只加一条路由记录就行。
拆完这个源码,我最大的教训是:Intraweb 的“有状态”特性会让很多前端熟悉的调试方法失效,每次拿到 IW 工程,别急着写界面,先强制走一遍最小数据链路,确认一个 action 的返回从服务端能完整透传到浏览器,再碰按钮和弹窗。这样改十次有九次不会再翻车,希望帮到你。
本文还有配套的精品资源,点击获取