☰
ThingsBoard 自定义 Time-Series 时序表格部件开发:Widget Editor 完整实战与订阅数据模型源码解析
2026/10/3 20:27:12 网站建设 项目流程
  • 物联网
  • 后端
  • 数据可视化
  • 消息队列

【免费下载链接】thingsboard

All-in-one IoT Platform - Device management, data collection, processing and visualization.

项目地址:https://gitcode.com/GitHub_Trending/th/thingsboard
点击查看免费下载

本文以 ThingsBoard 前端仓库(ui-ngx)内置的官方示例 timeseries_widget.md 为核心,手把手讲解如何在Widgets Bundle 视图中从零创建一个自定义Time-Series 时序部件:通过 HTML/CSS/JavaScript 三个代码区实现"每个数据源一个页签、页签内以表格展示最新时序数据"的效果,并深入解析部件subscription(订阅)对象与defaultSubscription的数据结构,帮助读者掌握 ThingsBoard 自定义部件的完整开发与调试能力。

一、前置认知:Widget Editor 与 Time-Series 部件类型

ThingsBoard 的自定义部件开发都在Widget Editor(部件编辑器)中完成,它允许开发者用 HTML、CSS、JavaScript(Angular 模板语法)定义部件的外观与行为。部件类型决定订阅的数据结构:

  • Latest values(最新值)与Time-Series(时序)部件:订阅对象提供datasources(数据源数组)与data(最新/时序数据数组);
  • Alarm(告警)部件:提供alarmSource与alarms;
  • RPC / Static部件:订阅对象可选,通常不包含必要的数据信息。

官方对部件生命周期函数与订阅对象的完整说明见 widget_js_fn.md 与 widget_js_subscription_object.md。本示例聚焦Time-Series类型,其最终效果为:多个数据源以mat-tab页签切换,每个页签内渲染一张"时间戳 + 各数据键(dataKey)数值"的表格。

二、创建步骤:从 Widgets Bundle 进入 Time-Series 模板

  1. 打开Widgets Bundle(部件库)视图,点击屏幕右下角的大"+"按钮;
  2. 在弹出的菜单中点击"Create new widget type"(创建新部件类型);
  3. 在Select widget type(选择部件类型)弹窗中点击Time-Series按钮;
  4. Widget Editor随即打开,并自动填充默认的Time-Series模板部件内容(此时"Resources"区已包含 CSS、HTML 两个标签页,"JavaScript"区已包含默认部件骨架代码)。

接下来只需分别替换三处代码并点击Run即可看到运行结果。

三、替换 CSS:定义表格表头左对齐样式

将 "Resources" 区CSS标签页的内容替换为:

.my-data-table th { text-align: left; }

该样式为示例表格定义了my-data-table类,使表头(th)文本左对齐,避免默认居中样式影响阅读。

四、替换 HTML:以页签 + 表格渲染多数据源时序数据

将 "Resources" 区HTML标签页的内容替换为:

<mat-tab-group style="height: 100%;"> <mat-tab *ngFor="let datasource of datasources; let $dsIndex = index" label="{{datasource.name}}"> <table class="my-data-table" style="width: 100%;"> <thead> <tr> <th>Timestamp</th> <th *ngFor="let dataKeyData of datasourceData[$dsIndex]">{{dataKeyData.dataKey.label}}</th> <tr> </thead> <tbody> <tr *ngFor="let data of datasourceData[$dsIndex][0].data; let $dataIndex = index"> <td>{{data[0] | date : 'yyyy-MM-dd HH:mm:ss'}}</td> <td *ngFor="let dataKeyData of datasourceData[$dsIndex]">{{dataKeyData.data[$dataIndex] && dataKeyData.data[$dataIndex][1]}}</td> </tr> </tbody> </table> </mat-tab> </mat-tab-group>

该模板使用 Angular 结构型指令*ngFor(官方参考 Angular NgForOf API):

  • 外层*ngFor="let datasource of datasources; let $dsIndex = index":遍历当前订阅的所有数据源,为每个数据源渲染一个mat-tab页签,页签标题为datasource.name;
  • 表头*ngFor="let dataKeyData of datasourceData[$dsIndex]":按数据源索引读取$scope.datasourceData,将每个dataKeyData的dataKey.label渲染为列标题(首列固定为 "Timestamp");
  • 表体*ngFor="let data of datasourceData[$dsIndex][0].data":以第一个 dataKey 的data数组为行基准,data[0]为毫秒级 Unix 时间戳(通过 Angulardate管道格式化为yyyy-MM-dd HH:mm:ss),dataKeyData.data[$dataIndex][1]为对应时间点的数值;
  • 最后一行使用Angular 表达式求值短路(A && B):当某 dataKey 在对应索引处无数据点时返回空,避免渲染undefined。

五、替换 JavaScript:初始化订阅数据并响应数据更新

将JavaScript区的内容替换为:

self.onInit = function() { self.ctx.widgetTitle = 'My first Time-Series widget'; self.ctx.$scope.datasources = self.ctx.defaultSubscription.datasources; self.ctx.$scope.data = self.ctx.defaultSubscription.data; self.ctx.$scope.datasourceData = []; var currentDatasource = null; var currentDatasourceIndex = -1; for (var i=0;i<self.ctx.$scope.data.length;i++) { var dataKeyData = self.ctx.$scope.data[i]; if (dataKeyData.datasource != currentDatasource) { currentDatasource = dataKeyData.datasource currentDatasourceIndex++; self.ctx.$scope.datasourceData[currentDatasourceIndex] = []; } self.ctx.$scope.datasourceData[currentDatasourceIndex].push(dataKeyData); } self.ctx.updateWidgetParams(); } self.onDataUpdated = function() { self.ctx.detectChanges(); }

代码要点:

  • self是部件实例的内置引用,每个部件函数都应定义为self的属性(详见 widget_js_fn.md);
  • self.ctx是 WidgetContext 类型引用,提供部件所需的全部 API 与数据;
  • onInit在部件初始化时调用:将defaultSubscription.datasources(数据源数组)与defaultSubscription.data(各 dataKey 的时序数据数组)挂载到$scope,使其在 HTML 模板中可直接访问;
  • $scope.datasourceData是本示例引入的关键重组结构:由于data是按datasource+dataKey平铺的(见下文订阅结构),这里遍历data,每当dataKeyData.datasource发生变化就开启一个新的数据源索引,把属于同一数据源的所有dataKeyData收集到datasourceData[索引]数组中——HTML 中便可以通过datasourceData[$dsIndex]按数据源索引灵活取用;
  • updateWidgetParams()同步部件参数(底层实现见下节源码解析);
  • onDataUpdated在订阅收到新数据时被调用,这里仅执行detectChanges(),触发一次新的 Angular 变更检测,使表格中的数据及时刷新。

六、运行与预览

完成上述替换后,点击Widget Editor Toolbar上的Run按钮,即可在Widget preview(部件预览)区看到运行结果:

预览中可以看到:每个数据源对应一个页签,表格首列展示格式化的时间戳,其余列按 dataKey 的 label 展示各数据键的数值。

七、源码级解析:subscription 订阅对象与 defaultSubscription

示例中的核心数据来源是self.ctx.defaultSubscription,它是 IWidgetSubscription 的实例(源码定义于 widget-api.models.ts),包含该订阅的全部信息与当前数据。在 widget-component.models.ts 中,WidgetContext声明了defaultSubscription: IWidgetSubscription = null,部件代码中可随时读取。

7.1 datasources:数据源数组

datasources = [ { // datasource type: 'entity',// type of the datasource. Can be "function" or "entity" name: 'name', // name of the datasource (in case of "entity" usually Entity name) aliasName: 'aliasName', // name of the alias used to resolve this particular datasource Entity entityName: 'entityName', // name of the Entity used as datasource entityType: 'DEVICE', // datasource Entity type (for ex. "DEVICE", "ASSET", "TENANT", etc.) entityId: '943b8cd0-576a-11e7-824c-0b1cb331ec92', // entity identificator presented as string uuid. dataKeys: [ // array of keys (attributes or timeseries) of the entity used to fetch data { // dataKey name: 'name', // the name of the particular entity attribute/timeseries type: 'timeseries', // type of the dataKey. Can be "timeseries", "attribute" or "function" label: 'Sin', // label of the dataKey. Used as display value (for ex. in the widget legend section) color: '#ffffff', // color of the key. Can be used by widget to set color of the key data. funcBody: "", // only applicable for datasource with type "function" and "function" key type. settings: {} // dataKey specific settings with structure according to the defined Data key settings form. }, //... ] }, //... ]

type为function时代表模拟数据源,配合funcBody生成仿真数据;为entity时代表真实实体数据源,aliasName指向仪表板中配置的实体别名。

7.2 data:各 dataKey 的时序数据点

data = [ { datasource: {}, // datasource object of this data. See datasource structure above. dataKey: {}, // dataKey for which the data is held. See dataKey structure above. data: [ // array of data points [ // data point 1498150092317, // unix timestamp of datapoint in milliseconds 1, // value, can be either string, numeric or boolean ], //... ] }, //... ]

data的每个元素(DatasourceData)对应一个数据源 + 一个 dataKey的组合,数据点以[时间戳, 数值]二元数组表示。这也解释了示例 JavaScript 为什么要按dataKeyData.datasource归组:data是平铺结构,而 HTML 表格需要按数据源聚合,datasourceData正是为这种按数据源索引访问而引入的重组视图。

7.3 IWidgetSubscription 接口中的相关成员

在 IWidgetSubscription 接口中可看到与示例直接对应的成员:

  • datasources?: Array<Datasource>—— 订阅使用的数据源数组;
  • data?: Array<DatasourceData>—— 订阅范围内收到的数据;
  • latestData?: Array<DatasourceData>—— 最新值数据(Latest values 部件使用);
  • type: widgetType、callbacks: WidgetSubscriptionCallbacks等成员共同支撑订阅的生命周期与回调(如onDataUpdated对应接口中的WidgetSubscriptionCallbacks.onDataUpdated)。

7.4 WidgetContext 的 detectChanges 与 updateWidgetParams 底层实现

示例中调用的两个 API 在 widget-component.models.ts 中有明确实现:

  • detectChanges():在ngZone内调用changeDetectorValue?.detectChanges()触发 Angular 变更检测(带updateWidgetParams可选参数,为true时先同步部件参数)。这正是示例注释中所说的"收到新数据时必须执行新的变更检测周期"的底层原因;
  • updateWidgetParams():通过setTimeout(..., 0)异步调用dashboardWidget.updateWidgetParams()同步部件参数。

八、调试技巧与扩展方向

官方文档 widget_js_fn.md 还给出了部件代码的两类调试方法:

  1. Web 控制台输出:在部件 JavaScript 任意位置放置console.log(...),点击Run重启部件代码,在浏览器控制台观察输出;
  2. 浏览器调试器:在感兴趣的位置放置debugger;语句并点击Run,浏览器会自动在断点处暂停,可用调试工具逐步分析执行过程。

基于本示例的datasourceData重组模式,可以进一步扩展:

  • 把data[0]的毫秒时间戳配合date管道展示更丰富的日期格式;
  • 参照 latest_values_widget.md 使用latestData实现最新值部件;
  • 为表格列标题绑定dataKey.color实现图例着色;
  • 借鉴 widget_js_fn.md 中onResize、onDestroy、getSettingsForm、typeParameters、actionSources等生命周期函数,为部件增加设置表单与用户动作。

至此,一个可运行的多数据源 Time-Series 表格部件已完成创建。整个示例的全部代码与说明均可在仓库 ui-ngx/src/assets/help/en_US/widget/editor/examples/timeseries_widget.md 中查看与复现。

  • 物联网
  • 后端
  • 数据可视化
  • 消息队列

【免费下载链接】thingsboard

All-in-one IoT Platform - Device management, data collection, processing and visualization.

项目地址:https://gitcode.com/GitHub_Trending/th/thingsboard
点击查看免费下载

相关推荐

上一篇:终极指南:如何使用WandEnhancer免费解锁WeMod高级功能
下一篇:ColorControl:高性能跨设备显示控制与智能协同系统深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询