拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Delphi + uniGUI + ECharts 集成实践:实时监控看板开发指南

简介面向 Delphi 开发者这份 21KB 的示例项目演示了如何用 UniGUI 框架搭建 Web 服务端在 Delphi 中生成数据并通过开源图表库 ECharts 在浏览器端渲染出可视化图表解决传统桌面工具难以展示动态图表的痛点。压缩包内含 21 个文件包括 dfm 窗体设计、pas 单元与 dpr 工程文件、多个 txt 数据配置文件以及 echartsExample.js 图表脚本整体结构清晰便于直接打开编译。代码覆盖了从接收 HTTP 请求、读取文本数据、转换格式到前端图表渲染的完整链路适合有一定 Delphi 基础并希望拓展 Web 可视化能力的开发者学习。通过该项目可以掌握 UniGUI 与 ECharts 的集成思路还能将静态数据替换为数据库或实时接口进一步搭建监控大屏。目前已有 701 人学习下载是一个实用的入门参考。 我最早把 Delphi、uniGUI 和 ECharts 这三样东西放到同一个项目里是因为要交一版车间设备实时监控看板。后台数据全是 Delphi 老项目里的前台浏览器要的是动态折线图、甘特图还不能像截图一样静态摆着。当时第一反应是继续用 VCL 图表控件但试过一轮之后发现Web 页面和桌面控件之间那层“浏览器刷新一次就丢状态”的鸿沟不是靠拖几个控件能填平的。后来换成了 uniGUI 做页面框架前端图表全部交给 ECharts后端继续写 Delphi才真正把这块硬骨头啃下来。这个压缩包标题是 Delphi-unigui-echart.rar其实就是我当时整理的一套基础工程Delphi 负责业务逻辑和实时数据uniGUI 负责把界面发布成浏览器可访问的页面ECharts 负责所有图表渲染。这篇文章不打算只讲“怎么把 demo 跑起来”而是把整个集成过程里最值得注意的设计逻辑、踩坑路径和最终落地的方案拆开讲一遍适合正在用 Delphi 做 Web 化改造、又不想把前端体系全部推翻重来的团队参考。1. 为什么是 Delphi uniGUI ECharts这套组合解决了我什么痛点1.1 VCL 图表控件在 Web 化面前的后劲不足最早我在项目里用的是 TeeChart 和 DevExpress 图表在桌面窗体上表现确实好可一旦需求变成“同一个看板要能被车间几台电脑同时打开”问题就来了。VCL 控件渲染出来的是 GDI/GDI 画面浏览器拿不到原生的控件句柄要么把图表导出成图片再定时刷新要么走 ActiveX 或者装运行时这在浏览器环境里就是一场灾难。图片方案最大的问题是没法交互鼠标移上去想看具体数值、想看最近一段时间的明细都得重新生成一张图后端压力大用户体验也差。uniGUI 本身有封装好的 UniChart但它底层还是走服务端渲染再推给浏览器那一套碰上数据量稍大、图表类型比较特殊的场景性能和灵活度都不够。ECharts 则完全不一样它是在浏览器端用 Canvas/SVG 绘图的纯前端库数据只以 JSON 格式传过去图表类型、tooltip、缩放、动画全在前端处理。这意味着 Delphi 只需要专注一件事把数据算好、序列化成 JSON。页面交互和渲染的复杂度被完全隔离在浏览器里服务端轻松很多。1.2 一个压缩包项目应包含的四层结构我拿到类似标题的资源包时习惯先看目录结构而不是直接双击 .dproj 编译。一个能真正复用的 Delphi-unigui-echart 工程通常应该包含四层东西第一层是 uniGUI 主工程里面有 MainModule、MainForm 和页面单元第二层是静态资源目录专门放 echarts.min.js、jquery 这类前端文件第三层是数据封装单元负责把 Delphi 端的内存数据、数据库查询结果转成 JSON第四层是前端页面与 Delphi 的事件桥接层也就是 AjaxRequest、JS 回调这些逻辑。很多新手直接把 echarts 的 script 标签塞进 uniHTMLFrame 的 HTML 属性里然后发现图表要么不显示、要么只在第一次加载时出现问题往往就出在这四层没有分开。前端资源归前端资源Delphi 数据归 Delphi 数据两者通过 uniGUI 的会话机制通信职责边界清楚了后面所有问题都变得好排查。2. 让 uniGUI 页面先“长出”一张 ECharts 画布2.1 容器选择TUniHTMLFrame 还是 TUniURLFrame在 uniGUI 里嵌入 ECharts第一个要决策的是用哪个容器。TUniURLFrame 适合直接嵌入一个完整的外部页面比如你已经在别处写好了一个静态 HTML 图表页用 iframe 方式嵌进来。它的优点是隔离性好缺点是页面和 uniGUI 主程序之间的数据交换比较麻烦每次传数据都得通过 URL 参数或者 postMessage调试起来多一层窗口。我最终用的是 TUniHTMLFrame它可以在界面里直接放一段 HTML 内容最核心的是我在 HTML 里只放了一个带 id 的 div 容器不放任何脚本。真正的 ECharts 初始化脚本通过 UniSession.AddJSToInitScript 注入这样能保证 uniGUI 页面框架先加载完成再执行图表初始化逻辑避免脚本在 DOM 节点还没生成时就报错。2.2 init 时机与容器尺寸是白屏的头号原因ECharts 初始化最常见的问题是白屏而且这种白屏往往不是数据问题是容器问题。echarts.init 在获取容器宽高时如果容器还没有被渲染到页面里或者父级容器是隐藏状态拿到的高度就是 0图表自然画不出来。在 uniGUI 的异步页面加载机制下TUniHTMLFrame 的 HTML 属性赋值时机和浏览器渲染时机不是严格同步的所以我会在初始化代码里加一个简单的兜底function initChart() { var el document.getElementById(mainChart); if (el el.clientWidth 0) { window.chartRef echarts.init(el); window.chartRef.setOption(window.chartOption || {}); } else { setTimeout(initChart, 50); } }这段代码用轮询方式等待容器可用虽然简单但在 uniGUI 的页面加载场景下非常管用。容器高度方面建议直接给 div 设置一个确定的像 420px 这样的高度或者采用 100% 加父容器定高的组合。千万别只设置 height: 100%因为父容器如果高度是 auto所有浏览器都会算出一个零高度图表就永远画不出来。2.3 引入 echarts.min.js 的本地化方式ECharts 官方提供 CDN 链接用起来很方便但在 uniGUI 这类后端渲染的 Web 项目中我强烈建议把 echarts.min.js 下载到本地作为静态资源随应用一起发布。原因很现实生产环境可能部署在隔离网络或内网服务器上外部资源的访问策略不一定允许一旦 CDN 不稳定整个图表区域就是白屏而且这种问题通常只在客户现场才暴露。uniGUI 有专门的静态文件目录在 ServerModule 的 CustomFiles 里把 echarts.min.js 加进去页面上用script src/files/echarts.min.js/script引用即可。本地化之后还要注意版本锁定ECharts 的大版本之间 API 有差异比如 4.x 和 5.x 对 tooltip、dataset 的处理不完全一样项目里统一固定一个版本避免某天有人顺手升级后所有图表行为都变了。3. Delphi 数据到前端图表的三种通道以及我最后长期用哪一种3.1 页面初始化时直接注入 JSON最简单的数据通道是页面初始化时把数据塞进 JavaScript 全局变量。在 uniGUI 的窗体 OnCreate 或者页面显示事件里把 Delphi 端组装好的 JSON 字符串通过 UniSession.AddJSToInitScript 输出到前端比如procedure TMainForm.UniFormCreate(Sender: TObject); var LJson: string; begin LJson : BuildChartDataJson; // 业务数据转 JSON UniSession.AddJSToInitScript( Format(window.chartOption %s;, [LJson]) ); end;前端 initChart 里再读取 window.chartOption 去 setOption。这个方案的优点是快、不依赖多余的请求适合报表类、看板类页面的首屏加载。缺点也很明显它只适合一次性数据如果图表要定时刷新或者用户点了某个按钮后要切换数据维度光靠这种方式是不够的。3.2 用 AjaxRequest 实现前端主动拉数uniGUI 的前后端交互核心是 AjaxRequest它的逻辑是前端 JavaScript 调用一个组件事件Delphi 端在事件里处理业务、返回结果再通过 AddJS 把新的 JSON 推回浏览器。我在项目里通常放一个隐藏的 TUniButton前端需要新数据时这样调function refreshChart() { ajaxRequest(hiddenBtn, GetChartData, []); }对应的 Delphi 事件里写procedure TMainForm.hiddenBtnAjaxEvent(Sender: TComponent; EventName: string; EventParams: TUniStrings); var LJson: string; begin if EventName GetChartData then begin LJson : BuildChartDataJson; UniSession.AddJS( Format(updateChart(%s);, [LJson]) ); end; end;而前端 updateChart 里调用 chart.setOption只更新必要的数据系列。这个通道是我项目里最常用的因为它把“数据从哪来”完全交给 Delphi 控制页面上无论有多少个图表、触发逻辑多复杂后端统一在事件里处理逻辑最清晰。3.3 定时刷新、轮询与会话保持的取舍看板类页面通常需要每隔几秒刷新一次数据。我的做法是前端用 window.setInterval 定时调用 refreshChart而不是在 Delphi 端用 TTimer 往前台推。原因是在 uniGUI 的请求响应模型里服务端主动推数据的实现比较复杂而且很容易和 session 机制冲突前端轮询则简单可靠每次请求都是一次完整的 Ajax 生命周期Delphi 端查询完数据库立即返回不占用额外连接。轮询间隔要结合数据量和业务容忍度来定。车间设备状态这种实时性要求高的设 3 到 5 秒报表类数据 30 秒甚至一分钟也够。间隔太短会导致数据库查询压力成倍增加间隔太长又失去“实时看板”的意义。我在做这类功能时一般会把查询逻辑包一层缓存如果数据没有变化就直接返回上次的 JSON前端 setOption 时数据引用没变也能减少不必要的图表重绘。4. 我实现过的三个图表场景折线图末点提示、甘特图、富文本标签4.1 移动端折线图渲染完成后自动显示最后一个点的 tooltip有个需求是折线图在手机浏览器上打开后不需要用户点击就要自动显示最后一个数据点的数值提示。这个需求看起来简单直接调用 dispatchAction 就行但在移动端很容易踩坑因为 ECharts 在触摸设备上的 tooltip 触发行为和鼠标设备不一样尤其是显示时机必须等 setOption 渲染完成之后。chart.setOption(option); setTimeout(function () { chart.dispatchAction({ type: showTip, seriesIndex: 0, dataIndex: option.xAxis.data.length - 1 }); }, 100);这里加 setTimeout 是为了确保 setOption 的动画帧已经完成。我在实际项目中还遇到一个问题如果图表同时有多个 seriesshowTip 的 seriesIndex 只传 0其他系列的数据不会一起显示。可以多调几次 dispatchAction或者给 tooltip 配置 trigger: axis再配合 axisPointer 的 link 属性把多个坐标系下的系列关联起来。4.2 ECharts 甘特图的实现思路ECharts 官方没有“甘特图”这种现成类型所以网上一搜全是“echart 甘特图”说明需求很普遍。ECharts 做甘特图的常规思路是使用堆叠条形图x 轴设成 time 类型每个任务对应两个 series一个透明的占位条表示任务开始日期一个有颜色的条表示任务持续时长。开始日期用 stack 相同的名字把占位条和实际条叠在一起视觉上就形成了甘特图效果。另外还有一种思路是用 custom series自己写 renderItem 函数在画布上直接绘制矩形和文本。custom 方式的优点是控制力强可以做出带有依赖箭头、里程碑标记的复杂甘特图但代码量也明显增加。如果只是做项目排期看板堆叠条形图已经完全够用如果要做带前后置关系的生产计划图再考虑 custom 方案。4.3 富文本与自定义标签的实战路线ECharts 的 label 和 tooltip 都支持 rich 配置可以在图表节点上显示带颜色、图标、多行样式的富文本。这个功能的实战价值很大比如设备看板上每台设备的标签里既有设备编号又有运行状态小圆点还有温度数字。用 formatter 返回字符串加自定义 HTML 标签的方式在 Canvas 渲染下是无效的必须走 ECharts 的 rich 体系。我的经验是先把富文本样式定义成公共对象放在单独的 JS 文件里不同图表复用时直接引用。如果遇到了“富文本编辑器”这类需求也就是希望在页面上可视化编辑图表配置而不是写代码那就需要一个额外的编辑器界面把 JSON 配置保存到数据库前端加载时再 echarts.setOption 应用。这套东西本身不复杂但要考虑配置校验和数据权限我一般不建议在第一版就引入。5. 踩坑排错会话超时、实例残留、数据更新后图表不刷新5.1 uniGUI SessionTimeout 为什么会让图表页面“跳回主页”uniGUI 自带 SessionTimeout 机制默认时间到了之后页面会自动跳转到登录页或主页。这个机制本身没问题但在图表看板应用里容易被忽略前端轮询虽然一直在请求后端但如果请求间隔大于 SessionTimeout 的时间会话依然会被回收。反过来如果前端一直高频轮询又会拉长会话寿命导致服务器内存里堆积大量无效 session。我的处理方案是分两层。第一层在 MainModule 的 SessionTimeout 属性里设置一个合理的秒数比如 30 分钟并且把到期后的跳转指向专门的“会话已过期”提示页而不是简单回登录页这样用户能明白发生了什么。第二层前端定时器里做一个简单的滑动窗口判断当页面处于隐藏状态时暂停轮询恢复可见时立刻刷新一次数据并重置定时器避免后台标签页白白占用会话。5.2 重复 init 导致内存上涨与图表闪烁图表页面如果存在多个 Tab 页或者用户反复进出页面很容易出现一个隐蔽问题同一个 div 节点被 echarts.init 初始化了多次旧实例没有被销毁内存不断上涨页面切换时图表还会闪烁。ECharts 官方要求 init 之前先判断容器上有没有实例var chart echarts.getInstanceByDom(el); if (!chart) { chart echarts.init(el); }在 uniGUI 的动态页面机制下页面关闭时脚本里的变量会被清理但如果页面只是被 Tab 切换隐藏而没有真正卸载实例还是会残留。所以我在页面隐藏事件里调用 chart.dispose()显示事件里重新 init 并 setOption。这样一个完整的生命周期闭环实测能明显减少内存上涨和切换时的白屏闪烁。5.3 数据更新时的 setOption 正确姿势很多人写 setOption 时习惯每次刷新都把完整 option 传进去这样其实会触发 ECharts 重建整个图表的开销而且容易产生动画跳动。正确做法是利用 setOption 的第二个参数 notMerge默认 false 表示合并更新只传需要变化的 series.data 和 xAxis.data 即可chart.setOption({ xAxis: { data: newCategories }, series: [{ data: newValues }] });但要注意如果图表的 series 结构在数据刷新后可能发生变化比如任务数量从 5 个变成 8 个那就要传 notMerge 为 true强制整体替换。否则新增的 series 可能不会正常渲染这是实际项目中很容易遇到又不好定位的问题。最后在每次刷新前对空数据做判断data 长度为 0 时用 chart.clear() 清空画布而不是带着空数组强行 setOption。6. 版本兼容与交付部署从 Delphi 10.4 到新版的注意事项6.1 uniGUI 控件包按 Delphi 版本编译的坑uniGUI 这类第三方控件包不像 VCL 自带组件那样装上就能用它的编译版本和 Delphi 版本绑定得很紧。同一个 uniGUI 版本针对 Delphi 10.4、11、12 甚至更新的版本需要分别编译出对应的 dcu 文件如果安装时选错版本编译阶段就会报大量找不到 .dcu 的错误。解决方案很简单重新执行 uniGUI 的 Install 脚本选择当前 IDE 对应版本重新编译即可。还有一个经验旧项目从老版本 Delphi 升级到新版时不要只升级 uniGUI还要检查项目里有没有用了 And 关键字、SM4 加密这类与编译器版本相关的单元。有些代码在老版本编译器里能过新版本对语法检查更严格之后会冒出一堆新的编译提示这些都要提前扫一遍别等客户现场编译时才炸。6.2 静态资源、跨域与最终交付形态最终交付时uniGUI 应用通常编译成独立可执行文件静态资源打包在 exe 同目录的 files 文件夹下或者通过 ServerModule 的 CustomFiles 配置一并发布。ECharts 的本地 JS 文件要确保路径正确如果发现浏览器控制台报 404多半是 CustomFiles 里的相对路径和页面引用不一致。跨域问题上如果 uniGUI 应用只需要访问同一个服务端的 API一般不需要处理跨域如果图表数据来自另一个域名建议后端统一做数据聚合接口由 Delphi 端去请求远程数据再返回 JSON这样避免前端处理跨域凭证问题也更安全。图表数据量大时还可以开启服务端的 GZip 压缩ECharts 对 JSON 的解析本身很快瓶颈通常在网络传输压缩一次效果立竿见影。如果要说有什么最值得一开始就做对我会把前端 bridge 独立成一个公共单元把容器查找、init 实例、setOption 更新这三个动作封装成统一接口后面任何页面要加图表都只是十分钟的事。希望这份踩坑记录能让你少走几趟夜路。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门