ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ThingsBoard 自定义 Widget 动作:用 JavaScript + HTML 模板实现设备/资产编辑对话框

2026/10/3 2:14:19 拓冰建站 浏览量
ThingsBoard 自定义 Widget 动作:用 JavaScript + HTML 模板实现设备/资产编辑对话框 物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载导读本文围绕 ThingsBoard开源版 IoT 平台Dashboard 中「自定义动作Custom Action」的核心能力以官方帮助文档提供的**「编辑设备或资产」示例**custom_pretty_edit_dialog_js.mdcustom_pretty_edit_dialog_html.md为主线完整讲解如何在 Widget 中通过 JavaScript 函数与 HTML 模板组合出一个可运行的「自定义对话框」实现对目标实体的名称/标签、服务端属性、实体关系的新增、修改与删除。读完本文你将掌握widgetContext、servicesMap、customDialog的调用方式理解其底层运行原理并能够把这段示例改造为适合自己的业务对话框。一、示例文档在项目中的位置与定位在 ThingsBoard 前端的帮助文档体系中本示例位于JavaScript 函数ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_js.mdHTML 模板ui-ngx/src/assets/help/en_US/widget/action/examples_custom_pretty/custom_pretty_edit_dialog_html.md它是「Custom action (with HTML template) function」帮助主题下的五个官方示例之一其余还包括创建对话框、创建用户、编辑图片属性、克隆设备等同目录下均有_js与_html两个文件。这些示例被 Dashboard 的 Widget 动作配置界面Action 弹窗中的tb-help-popup直接引用用户在界面上即可打开查看与复制。示例对应的动作函数签名如下见 custom_pretty_action_fn.mdfunction ($event, widgetContext, entityId, entityName, htmlTemplate, additionalParams, entityLabel): void其中htmlTemplate参数即为用户在「HTML」标签页中定义的模板字符串用于渲染自定义对话框entityId、entityName、entityLabel为动作触发时由 Widget 传入的目标实体信息。二、函数骨架从 widgetContext 获取服务示例 JavaScript 的第一步是从widgetContext取出 Angular 注入器与相关服务let $injector widgetContext.$scope.$injector; let customDialog $injector.get(widgetContext.servicesMap.get(customDialog)); let entityService $injector.get(widgetContext.servicesMap.get(entityService)); let assetService $injector.get(widgetContext.servicesMap.get(assetService)); let deviceService $injector.get(widgetContext.servicesMap.get(deviceService)); let attributeService $injector.get(widgetContext.servicesMap.get(attributeService)); let entityRelationService $injector.get(widgetContext.servicesMap.get(entityRelationService));原理说明widgetContext是 ThingsBoard Widget 运行时提供给动作函数的核心对象其类型定义见 widget-component.models.ts其中servicesMapMapstring, Typeany预注册的服务类型映射表$injectorAngularInjector用于按类型实例化服务$scopeIDynamicWidgetComponent动态组件作用域可借此访问其$injectorrxjs{...RxJS, ...RxJSOperators}即 rxjs 全量 API 及操作符集合本示例中的forkJoin、of均来自该对象。也就是说示例中的取服务方式等价于先通过servicesMap查得服务类再通过$injector.get()创建实例。这些服务对应后端 REST API 封装其定义位于 ui-ngx/src/app/core/http 目录下。三、入口函数与控制器customDialog 的调用模式openEditEntityDialog(); function openEditEntityDialog() { customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe(); } function EditEntityDialogController(instance) { let vm instance; // ... 控制器逻辑 }customDialog.customDialog(template, controller, data?, config?)是服务公开的唯一入口其实现见 custom-dialog.service.ts。调用流程为将sharedModule、CommonModule及各 Home 组件模块作为 imports调用DynamicComponentFactoryService.createDynamicComponent()把htmlTemplate编译成一个继承自CustomDialogComponent的动态组件类型将controller与动态组件类型一起封装进CustomDialogContainerData以disableClose: true、全屏面板类tb-dialog/tb-fullscreen-dialog打开MatDialog对话框对话框关闭后通过tap()销毁动态组件避免内存泄漏。而控制器EditEntityDialogController(instance)接收的instance就是CustomDialogComponent实例。从 custom-dialog.component.ts 可以看到该基类内置了dialogRefMatDialogRef控制器中vm.dialogRef.close(...)即用它关闭对话框fbUntypedFormBuilder用于构建响应式表单validatorsAngularValidators对象dataCUSTOM_DIALOG_DATA注入令牌。构造函数末尾会立即调用this.data.controller(this)把实例交给控制器初始化。四、响应式表单结构编辑对话框的字段设计控制器用vm.fb.group()构建了表单模型vm.editEntityFormGroup vm.fb.group({ entityName: [, [vm.validators.required]], entityType: [null], entityLabel: [null], type: [, [vm.validators.required]], attributes: vm.fb.group({ latitude: [null], longitude: [null], address: [null], owner: [null], number: [null, [vm.validators.pattern(/^-?[0-9]$/)]], booleanValue: [false] }), oldRelations: vm.fb.array([]), relations: vm.fb.array([]) });字段说明字段类型校验用途entityName文本required实体名称示例中设为只读entityType文本无实体类型DEVICE / ASSET只读entityLabel文本无实体标签可编辑type文本required实体类型设备/资产所属自定义类型只读attributes.latitude/longitude数值无服务端属性经纬度attributes.address/owner文本无服务端属性attributes.number数值pattern(/^-?[0-9]$/)整数属性非法值会触发校验错误attributes.booleanValue布尔无布尔属性oldRelationsFormArray—已存在的实体关系只读回显relationsFormArray—待新增的实体关系关系条目也通过表单组约束新增关系必须填写相关实体、关系类型与方向vm.addRelation function() { vm.relations().push(vm.fb.group({ relatedEntity: [null, [vm.validators.required]], relationType: [null, [vm.validators.required]], direction: [null, [vm.validators.required]] })); };方向取值为vm.entitySearchDirection {from: FROM, to: TO}即 ThingsBoard 关系模型中的FROM当前实体为源与TO当前实体为目标。五、数据加载forkJoin 并发拉取实体信息对话框打开后控制器调用getEntityInfo()通过widgetContext.rxjs.forkJoin并行发起四个请求widgetContext.rxjs.forkJoin([ entityRelationService.findInfoByFrom(entityId), // 当前实体作为 FROM 源的关系 entityRelationService.findInfoByTo(entityId), // 当前实体作为 TO 目标的关系 attributeService.getEntityAttributes(entityId, SERVER_SCOPE), // 服务端属性 entityService.getEntity(entityId.entityType, entityId.id) // 实体详情 ]).subscribe(...)在回调中getEntityRelations(data.slice(0, 2))分别遍历relationsFromFROM 方向与relationsToTO 方向把每条关系转换为{direction, relationType, relatedEntity}结构存入vm.oldRelationsData并调用addOldRelation()往oldRelationsFormArray 追加一条禁用状态的条目已存在关系不允许在表单里改方向与类型只能删除getEntityAttributes(data[2])把服务端属性数组转换为{key: value}对象存入vm.attributes作为后续比对「哪些属性被修改」的基线vm.entity data[3]保存实体对象vm.editEntityFormGroup.patchValue({...}, {emitEvent: false})一次性回填表单emitEvent: false避免回填过程触发校验与订阅通知。注意attributeService.getEntityAttributes(entityId, SERVER_SCOPE)与saveEntityAttributes(entityId, SERVER_SCOPE, attributesArray)使用的都是SERVER_SCOPE服务端作用域即这些属性只能由平台/规则引擎修改、对设备不可见适合存放经纬度、地址等元数据。该作用域枚举在 attribute.service.ts 中定义。六、保存逻辑属性、关系、实体的三段式提交vm.save()同样使用forkJoin合并三类保存任务vm.save function() { vm.editEntityFormGroup.markAsPristine(); widgetContext.rxjs.forkJoin([ saveAttributes(entityId), saveRelations(entityId), saveEntity() ]).subscribe(function () { widgetContext.updateAliases(); vm.dialogRef.close(null); }); };1. 保存属性差量更新saveAttributes遍历表单attributes子组的每个 key与加载时保存的vm.attributes基线比对只把值发生变化的属性组装成{key, value}数组提交避免无谓写入for (let key in attributes) { if (attributes[key] ! vm.attributes[key]) { attributesArray.push({key: key, value: attributes[key]}); } } if (attributesArray.length 0) { return attributeService.saveEntityAttributes(entityId, SERVER_SCOPE, attributesArray); } return widgetContext.rxjs.of([]); // 无变化时返回空 Observable保证 forkJoin 可正常完成widgetContext.rxjs.of([])是关键细节forkJoin需要所有 Observable 都发出值才能汇聚因此「没有需要保存的内容」时也要返回一个空流。2. 保存关系新增 删除saveRelations处理两类任务新增关系遍历relationsFormArray 的值根据direction确定from/to端点FROM表示当前实体是关系源TO表示当前实体是关系目标补上typeGroup: COMMON后调用entityRelationService.saveRelation(relation)删除旧关系用户在对话框中点击旧关系条目的删除按钮时removeOldRelation(index, relation)会把该关系对象 push 进vm.relationsToDelete保存时统一调用entityRelationService.deleteRelation(relation.from, relation.type, relation.to)。这些 REST 方法定义在 entity-relation.service.tssaveRelation对应关系新增/更新接口deleteRelation按(from, type, to)三元组精确删除。3. 保存实体仅更新标签saveEntity只关心表单里可编辑的entityLabel如果标签发生了变化才按实体类型分别调用assetService.saveAsset(vm.entity)或deviceService.saveDevice(vm.entity)提交实体对象否则返回空流。这也是为什么entityName、entityType、type在表单中一律设为readonly——这些字段由平台管理示例刻意不允许用户在动作中篡改。七、HTML 模板要点表单与对话框的绑定配套的 HTML 模板custom_pretty_edit_dialog_html.md核心绑定关系如下form #editEntityFormngForm [formGroup]editEntityFormGroup (ngSubmit)save()根表单提交时触发保存mat-toolbar标题动态拼接Edit {{entityType.toLowerCase()}} {{entityName}}mat-progress-bar通过isLoading$ | async控制加载进度条该订阅变量由CustomDialogComponent基类的PageComponent提供顶层mat-form-field使用formControlNameentityName / entityLabel / entityType / type与表单组绑定属性区使用formGroupNameattributes分组latitude/longitude/address/owner/number/booleanValue各有对应输入控件其中number字段在hasError(pattern)时显示「Invalid integer value.」错误提示关系区使用formArrayNameoldRelations与formArrayNamerelations遍历两条 FormArray每个条目内用formGroupNamei索引绑定directionmat-selectentitySearchDirection、relationTypetb-relation-type-autocomplete关系类型自动补全组件、relatedEntitytb-entity-select实体选择组件旧关系条目的删除按钮调用removeOldRelation(i, relation.value)新关系条目的删除按钮调用removeRelation(i)「Add」按钮调用addRelation()底部操作区Cancel按钮调用cancel()vm.dialogRef.close(null)Save提交按钮的禁用条件是editEntityForm.invalid || !editEntityForm.dirty即表单非法或未做任何修改时不可提交。模板中使用的tb-relation-type-autocomplete、tb-entity-select是 ThingsBoard 自带的复合表单控件它们在运行时由CustomDialogService注入的sharedHomeComponentsModule/homeComponentsModule/widgetComponentsModule等模块提供这正解释了为什么对话框里能直接用这些组件——它们是动态组件编译时被合并进 imports 的。八、从源码验证完整调用链结合源码可以把整个动作的运行时链路串起来Dashboard 中 Widget 配置的 JS 动作函数被执行时框架注入widgetContextwidget-component.models.ts函数从$injector取出customDialogCustomDialogService等服务CustomDialogService.customDialog()调用DynamicComponentFactoryService把 HTML 模板实时编译为 Angular 组件custom-dialog.service.ts动态组件继承CustomDialogComponent其构造函数执行this.data.controller(this)即调用我们的EditEntityDialogControllercustom-dialog.component.ts控制器内通过vm.fb、vm.validators、vm.dialogRef完成表单构建、校验与关闭保存时经attributeService/entityRelationService/assetService/deviceService位于 ui-ngx/src/app/core/http调用后端 REST API全部保存完成后调用widgetContext.updateAliases()刷新 Widget 的实体别名数据再dialogRef.close(null)关闭对话框。与示例同目录的其他文件custom_pretty_create_dialog_js.md、custom_pretty_clone_device_js.md 等复用同一套customDialog 控制器模式可对比学习新增、克隆等不同业务场景的写法。此外Widget 动作配置内置的示例模板 custom-sample-js.raw 也给出了customDialog.customDialog(htmlTemplate, ...)的最小可运行骨架可作为改造起点。九、改造与复用建议基于本示例你可以按以下思路进行定制调整属性集合修改attributes子组的字段与对应 HTML 输入控件即可编辑任意服务端属性如需编辑共享/客户端作用域属性把两处SERVER_SCOPE换成SHARED_SCOPE或CLIENT_SCOPE即可作用域枚举见 attribute.service.ts扩展校验规则在表单字段上追加vm.validators.required、pattern、min/max等并同步在 HTML 中增加mat-error提示参考number字段的整数校验写法开放更多实体字段若希望允许修改实体名称等字段可去掉对应输入框的readonly并在saveEntity中按需提交entity.name更换目标实体类型把saveEntity中的分支扩展为其他实体如entityService.saveEntity或专门的 service即可支持更多实体类型后端统一封装入口可参考 entity.service.ts对话框层级与行为customDialog()的第四个参数config可覆盖MatDialogConfig如disableClose、width需要非全屏或可点击遮罩关闭时可在此调整。小结本文完整还原了 ThingsBoard「编辑设备/资产」自定义动作的官方示例从widgetContext获取服务、customDialog动态编译 HTML 模板、控制器初始化响应式表单到属性差量保存、关系新增/删除、实体标签更新再到底层CustomDialogService、CustomDialogComponent与各 HTTP 服务类的源码佐证。掌握这套「JS 函数 HTML 模板 控制器」三段式写法后你可以在 ThingsBoard Dashboard 中自由构建属于自己的编辑、创建、克隆类业务对话框而无需改动平台本体代码。赞分享物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载相关推荐ThingsBoard 自定义操作实战用 HTML 模板构建美化版编辑对话框编辑设备/资产ThingsBoard 自定义操作实战用 HTML 模板构建美化版编辑对话框编辑设备/资产 本篇指南讲解 ThingsBoard 物联网平台中「自定义操作物联网后端数据可视化消息队列Fleet 后端开发模式指南API 输入校验、Go 与 MySQL 工程实践与 GitOps 落地Fleet 后端开发模式指南API 输入校验、Go 与 MySQL 工程实践与 GitOps 落地 Fleet 是一套开源的设备管理平台Open devic物联网后端数据可视化消息队列Task 模板引擎全解析从变量插值到函数库的 Templating Reference 实战指南Task 模板引擎全解析从变量插值到函数库的 Templating Reference 实战指南 导读 Task本项目为 GitHub 加速计划 / ta物联网后端数据可视化消息队列上一篇163MusicLyrics如何用开源工具三步搞定多平台歌词管理下一篇如何让老款Mac免费升级最新系统OpenCore Legacy Patcher终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考