ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ThingsBoard 自定义动作 HTML 模板实战:打造设备/资产创建对话框

ThingsBoard 自定义动作 HTML 模板实战:打造设备/资产创建对话框 物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载导读本文基于 ThingsBoard 前端仓库ui-ngx内置的官方帮助文档 custom_pretty_create_dialog_html.md完整讲解自定义动作带 HTML 模板Custom Action with HTML Template中用于创建设备或资产的对话框 HTML 模板。你将掌握这套模板如何与配套的 JavaScript 控制器协作、每个表单控件的职责与校验逻辑、tb-entity-type-select/tb-entity-subtype-autocomplete/tb-entity-select等 ThingsBoard 内置组件的用法以及如何把它挂载到 Widget 的动作事件上实现填表→创建实体→写入属性→建立关系的完整链路。一、这套 HTML 模板在 ThingsBoard 中的定位ThingsBoard 的 Widget尤其是实体表格类 Widget允许在行点击、动作单元格按钮、表头按钮等事件上配置自定义动作。其中 Custom Action (with HTML Template)自定义动作带 HTML 模板把工作拆成两部分HTML 标签页定义对话框的 UI 结构即本文讲解的模板JavaScript 标签页定义动作逻辑与对话框控制器仓库中对应的配套示例是 custom_pretty_create_dialog_js.md。从官方动作函数说明 custom_pretty_action_fn.md 可以看到完整签名function ($event, widgetContext, entityId, entityName, htmlTemplate, additionalParams, entityLabel): void其中htmlTemplate参数就是 HTML 标签页中定义的模板字符串运行时被传给对话框服务用于动态渲染。该帮助文档在 Examples 一节中专门引用了custom_pretty_create_dialog_js与custom_pretty_create_dialog_html两个示例正对应本文与配对 JS 文件examples_custom_pretty/目录下还有一份内容相同的副本供帮助弹窗引用。二、完整模板源码以下代码直接取自仓库文档 custom_pretty_create_dialog_html.md为便于阅读去掉了顶部的代码块元数据form #addEntityFormngForm [formGroup]addEntityFormGroup (ngSubmit)save() classadd-entity-form mat-toolbar classflex flex-row colorprimary h2Add entity/h2 span classflex-1/span button mat-icon-button (click)cancel() typebutton mat-icon classmaterial-iconsclose/mat-icon /button /mat-toolbar mat-progress-bar colorwarn modeindeterminate *ngIfisLoading$ | async /mat-progress-bar div styleheight: 4px; *ngIf!(isLoading$ | async)/div div mat-dialog-content classflex flex-col div classflex flex-row gap-2 xs:flex-col xs:gap-0 mat-form-field classmat-block flex-1 mat-labelEntity Name/mat-label input matInput formControlNameentityName required mat-error *ngIfaddEntityFormGroup.get(entityName).hasError(required) Entity name is required. /mat-error /mat-form-field mat-form-field classmat-block flex-1 mat-labelEntity Label/mat-label input matInput formControlNameentityLabel /mat-form-field /div div classflex flex-row gap-2 xs:flex-col xs:gap-0 tb-entity-type-select classmat-block formControlNameentityType [showLabel]true [allowedEntityTypes]allowedEntityTypes /tb-entity-type-select tb-entity-subtype-autocomplete *ngIfaddEntityFormGroup.get(entityType).value ASSET classmat-block flex-1 formControlNametype [required]true [entityType]ASSET /tb-entity-subtype-autocomplete tb-entity-subtype-autocomplete *ngIfaddEntityFormGroup.get(entityType).value ! ASSET classmat-block flex-1 formControlNametype [required]true [entityType]DEVICE /tb-entity-subtype-autocomplete /div div formGroupNameattributes classflex flex-col div classflex flex-row gap-2 xs:flex-col xs:gap-0 mat-form-field classmat-block flex-1 mat-labelLatitude/mat-label input typenumber stepany matInput formControlNamelatitude /mat-form-field mat-form-field classmat-block flex-1 mat-labelLongitude/mat-label input typenumber stepany matInput formControlNamelongitude /mat-form-field /div div classflex flex-row gap-2 xs:flex-col xs:gap-0 mat-form-field classmat-block flex-1 mat-labelAddress/mat-label input matInput formControlNameaddress /mat-form-field mat-form-field classmat-block flex-1 mat-labelOwner/mat-label input matInput formControlNameowner /mat-form-field /div div classflex flex-row gap-2 xs:flex-col xs:gap-0 mat-form-field classmat-block flex-1 mat-labelInteger Value/mat-label input typenumber step1 matInput formControlNamenumber mat-error *ngIfaddEntityFormGroup.get(attributes.number).hasError(pattern) Invalid integer value. /mat-error /mat-form-field div classboolean-value-input flex flex-1 flex-col items-center justify-start label classcheckbox-labelBoolean Value/label mat-checkbox formControlNamebooleanValue stylemargin-bottom: 40px; {{ (addEntityFormGroup.get(attributes.booleanValue).value ? value.true : value.false) | translate }} /mat-checkbox /div /div /div div classrelations-list div classmat-body-1 stylepadding-bottom: 10px; color: rgba(0,0,0,0.57);Relations/div div classbody [class.!hidden]!relations().length div classrow flex flex-row items-center justify-start formArrayNamerelations *ngForlet relation of relations().controls; let i index; div [formGroupName]i classmat-elevation-z2 flex flex-1 flex-row stylepadding: 5px 0 5px 5px; div classflex flex-1 flex-col div classflex flex-row gap-2 xs:flex-col xs:gap-0 mat-form-field classmat-block stylemin-width: 100px; mat-labelDirection/mat-label mat-select formControlNamedirection namedirection mat-option *ngForlet direction of entitySearchDirection | keyvalue [value]direction.value {{ (relation.search-direction. direction.value) | translate}} /mat-option /mat-select mat-error *ngIfrelation.get(direction).hasError(required) Relation direction is required. /mat-error /mat-form-field tb-relation-type-autocomplete classmat-block flex-1 formControlNamerelationType [required]true /tb-relation-type-autocomplete /div div classflex flex-row xs:flex-col tb-entity-select classmat-block flex-1 [required]true formControlNamerelatedEntity /tb-entity-select /div /div div classflex flex-col items-center justify-center button mat-icon-button colorprimary aria-labelRemove typebutton (click)removeRelation(i) matTooltipRemove relation matTooltipPositionabove mat-iconclose/mat-icon /button /div /div /div /div div button mat-raised-button colorprimary typebutton (click)addRelation() matTooltipAdd Relation matTooltipPositionabove Add /button /div /div /div div mat-dialog-actions classflex flex-row items-center justify-end button mat-button colorprimary typebutton [disabled](isLoading$ | async) (click)cancel() cdkFocusInitial Cancel /button button mat-button mat-raised-button colorprimary typesubmit [disabled](isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty Create /button /div /form模板基于 Angular Materialmat-toolbar、mat-form-field、mat-select、mat-checkbox、mat-progress-bar等与 ThingsBoard 内置实体选择组件整体是一个 Reactive Forms响应式表单驱动的对话框。三、模板逐段拆解3.1 表单绑定与提交入口form #addEntityFormngForm [formGroup]addEntityFormGroup (ngSubmit)save() classadd-entity-form[formGroup]addEntityFormGroup把整张表单绑定到控制器JavaScript 侧AddEntityDialogController中通过vm.fb.group({...})创建的表单组#addEntityFormngForm建立了模板引用变量使模板能直接读取addEntityForm.invalid/addEntityForm.dirty用于按钮禁用判断(ngSubmit)save()把提交动作指向控制器暴露的save()方法该方法是整个创建实体流程的入口。3.2 顶部工具栏与关闭按钮mat-toolbar classflex flex-row colorprimary h2Add entity/h2 span classflex-1/span button mat-icon-button (click)cancel() typebutton mat-icon classmaterial-iconsclose/mat-icon /button /mat-toolbar使用mat-toolbar colorprimary作为对话框标题栏右侧的close图标按钮调用控制器的cancel()。在示例 JS 中cancel()的实现是vm.cancel function () { vm.dialogRef.close(null); };注意按钮显式声明typebutton避免误触发表单的ngSubmit。3.3 加载进度条mat-progress-bar colorwarn modeindeterminate *ngIfisLoading$ | async /mat-progress-bar div styleheight: 4px; *ngIf!(isLoading$ | async)/div当isLoading$这个 Observable 发出true时显示不确定进度条modeindeterminate当不为加载中时用一个 4px 高的空div占位防止进度条出现/消失引起布局抖动底部动作区的两个按钮也用[disabled](isLoading$ | async)在加载期间禁用避免重复提交。模板约定控制器需暴露isLoading$这一 Observable 成员在实际项目中你可以把它接到创建请求上例如在保存期间发出true、完成后发出false。3.4 基础字段Entity Name / Entity Labelmat-form-field classmat-block flex-1 mat-labelEntity Name/mat-label input matInput formControlNameentityName required mat-error *ngIfaddEntityFormGroup.get(entityName).hasError(required) Entity name is required. /mat-error /mat-form-fieldentityName绑定到表单组的同名控件HTML 侧有requiredJS 侧则通过vm.validators.required做了 Reactive Forms 校验见第四节校验失败时mat-error显示 Entity name is required.entityLabel是可选项对应 ThingsBoard 实体的 Label 字段默认展示名称可自定义显示名。3.5 实体类型与子类型选择tb-entity-type-select classmat-block formControlNameentityType [showLabel]true [allowedEntityTypes]allowedEntityTypes /tb-entity-type-selecttb-entity-type-select是 ThingsBoard 内置的实体类型下拉组件源码位于 entity-type-select.component.ts通过[allowedEntityTypes]限制可选范围——示例控制器里设置为[ASSET, DEVICE]即只允许创建资产或设备entityType控件在 JS 中的默认值为DEVICE。tb-entity-subtype-autocomplete *ngIfaddEntityFormGroup.get(entityType).value ASSET classmat-block flex-1 formControlNametype [required]true [entityType]ASSET /tb-entity-subtype-autocompletetb-entity-subtype-autocomplete是实体子类型type自动补全组件源码位于 entity-subtype-autocomplete.component.ts它通过entityType入参区分 DEVICE/ASSET 各自的子类型列表如设备的 default、资产的自定义类型模板用*ngIf根据当前entityType的值动态渲染两个实例选 ASSET 时加载资产子类型否则加载设备子类型[required]true保证创建时必须填写类型因为 ThingsBoard 的type字段是必填的服务端保存接口要求。3.6 attributes 属性组外层formGroupNameattributes对应控制器中嵌套的表单组里面收集要写入实体属性Attributes的额外字段控件表单控件类型约束说明Latitudeattributes.latitudetypenumber stepany纬度允许小数Longitudeattributes.longitudetypenumber stepany经度允许小数Addressattributes.address文本地址Ownerattributes.owner文本所有者Integer Valueattributes.numbertypenumber step1整数值JS 侧以vm.validators.pattern(/^-?[0-9]$/)校验mat-error显示 Invalid integer value.Boolean Valueattributes.booleanValuemat-checkbox布尔开关文案随值动态切换并通过translate管道本地化其中整数校验是模板与控制器联动的典型例子HTML 只负责展示错误真正的校验规则正则/^-?[0-9]$/定义在 JS 的表单组配置里attributes: vm.fb.group({ latitude: [null], longitude: [null], address: [null], owner: [null], number: [null, [vm.validators.pattern(/^-?[0-9]$/)]], booleanValue: [null] })保存时这些字段会被组装成键值对以SERVER_SCOPE作用域写入实体属性见 4.3 节。3.7 relations 关系编辑区这是模板最复杂的部分用于在创建实体的同时建立它与其它实体之间的关联关系div classbody [class.!hidden]!relations().length div classrow flex flex-row items-center justify-start formArrayNamerelations *ngForlet relation of relations().controls; let i index; div [formGroupName]i ...外层formArrayNamerelations绑定控制器中的FormArrayrelations()是控制器暴露的访问方法返回addEntityFormGroup.get(relations)当数组为空时通过[class.!hidden]!relations().length隐藏关系行每一行是一个由[formGroupName]i包裹的关系表单组包含三个控件Direction方向mat-select formControlNamedirection namedirection mat-option *ngForlet direction of entitySearchDirection | keyvalue [value]direction.value {{ (relation.search-direction. direction.value) | translate}} /mat-option /mat-select选项来自控制器中的entitySearchDirection对象{ from: FROM, to: TO }通过keyvalue管道遍历显示文本用relation.search-direction.*翻译键本地化选中值FROM/TO决定保存关系时当前实体是发起方还是接收方见 4.4 节。Relation Type关系类型tb-relation-type-autocomplete classmat-block flex-1 formControlNamerelationType [required]true /tb-relation-type-autocompletetb-relation-type-autocomplete是 ThingsBoard 内置的关系类型自动补全组件位于 relation-type-autocomplete.component.ts可选值来自服务器端预定义的关系类型如Contains、Manages等可自定义。Related Entity目标实体tb-entity-select classmat-block flex-1 [required]true formControlNamerelatedEntity /tb-entity-selecttb-entity-select是通用实体选择器位于 entity-select.component.ts通过搜索框从全部实体中挑选目标。行内删除与新增button mat-icon-button ... (click)removeRelation(i) ... mat-iconclose/mat-icon /button ... button mat-raised-button colorprimary ... (click)addRelation() ...Add/buttonremoveRelation(i)调用vm.relations().removeAt(index)并markAsDirty()保证删除后表单状态可被感知addRelation()往 FormArray 里push一个新的表单组三个控件初始为null且全部required。3.8 底部操作区button mat-button colorprimary typebutton [disabled](isLoading$ | async) (click)cancel() cdkFocusInitial Cancel /button button mat-button mat-raised-button colorprimary typesubmit [disabled](isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty Create /buttonCancel 调用cancel()关闭对话框cdkFocusInitial让取消按钮获得初始焦点防止误触回车提交Create 是typesubmit触发表单的ngSubmit→save()它的禁用条件是三选一加载中isLoading$为 true、表单校验不通过addEntityForm.invalid、表单未被修改!addEntityForm.dirty。其中invalid综合了 entityName、type、attributes.number 正则、relations 各必填项的全部校验结果——这也解释了为何所有关系行控件都要required。四、配套 JavaScript 控制器模板背后的大脑HTML 模板只是皮真正完成创建逻辑的是同一示例包中的 custom_pretty_create_dialog_js.md。理解它能帮你掌握模板中每个成员addEntityFormGroup、allowedEntityTypes、entitySearchDirection、save、cancel、addRelation、removeRelation、relations()的来源与职责。4.1 服务注入与对话框打开let $injector widgetContext.$scope.$injector; let customDialog $injector.get(widgetContext.servicesMap.get(customDialog)); 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)); openAddEntityDialog(); function openAddEntityDialog() { customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(); }所有服务都通过widgetContext.servicesMap从注入器获取customDialog就是仓库里的CustomDialogService其核心方法customDialog(template, controller, data?, config?)的实现位于 custom-dialog.service.ts先用DynamicComponentFactoryService把模板字符串动态编译成组件再用MatDialog.open(CustomDialogContainerComponent)打开对话框关闭后销毁动态组件。4.2 控制器实例的上下文function AddEntityDialogController(instance) { let vm instance; ... }instance是仓库中 CustomDialogComponent 的实例该基类已经注入了fb——UntypedFormBuilder所以控制器里可以直接vm.fb.group(...)validators——Validators所以可以用vm.validators.required、vm.validators.pattern(...)dialogRef——MatDialogRef对应模板/控制器里的vm.dialogRef.close(null)data—— 注入的CUSTOM_DIALOG_DATA。基类构造函数里执行this.data.controller(this)把控制权交给AddEntityDialogController随后控制器通过vm.xxx ...往实例上挂载模板所需的成员。4.3 保存流程实体 → 属性 → 关系vm.save function () { vm.addEntityFormGroup.markAsPristine(); saveEntityObservable().subscribe( function (entity) { widgetContext.rxjs.forkJoin([ saveAttributes(entity.id), saveRelations(entity.id) ]).subscribe( function () { widgetContext.updateAliases(); vm.dialogRef.close(null); } ); } ); };保存链路分三步创建实体saveEntityObservable()根据entityType调用assetService.saveAsset(entity)或deviceService.saveDevice(entity)实体的name/type/label取自表单的entityName/type/entityLabel并行写属性与关系拿到新实体的id后用widgetContext.rxjs.forkJoin并行执行saveAttributes与saveRelationssaveAttributes把attributes组里所有非null的字段组装成{key, value}数组调用attributeService.saveEntityAttributes(entityId, SERVER_SCOPE, attributesArray)即写入服务器作用域属性saveRelations按direction决定方向FROM时relation.from 新实体、relation.to 目标实体TO时相反关系对象的type取自relationType、typeGroup: COMMON然后entityRelationService.saveRelation(relation)刷新与关闭全部完成后调用widgetContext.updateAliases()刷新组件别名让当前仪表盘感知新实体最后dialogRef.close(null)。五、在界面上如何挂载这套模板在 ThingsBoard 仪表盘的 Widget 编辑器中按以下步骤使用仓库只读以下均为平台界面的操作方式打开某个实体表格类 Widget 的编辑页进入Actions动作配置添加动作动作来源可以是On row click行点击、Action cell button动作单元格按钮等动作类型选择Custom action with HTML template自定义动作带 HTML 模板在JavaScript标签页粘贴 custom_pretty_create_dialog_js.md 中的完整函数在HTML标签页粘贴本文第二节的完整模板保存后点击测试对话框应能完成选择类型→填名称/类型→填属性→添加关系→创建并自动把经纬度、地址、Owner、整数值、布尔值写入新实体属性同时按所选方向建立关系。如果你需要把该动作的触发上下文如当前行的实体信息传给对话框可参考 custom_additional_params.md 中关于additionalParams的约定实体表格类 Widget 会传入{ entity: EntityData }告警表格传入{ alarm: AlarmDataInfo }地图 Widget 传入FormattedData等。六、关联资源索引本文涉及的仓库资源汇总如下供继续深入阅读本文模板来源custom_pretty_create_dialog_html.md另有供帮助弹窗引用的副本 custom_pretty_create_dialog_html.md配套 JS 控制器custom_pretty_create_dialog_js.md动作函数签名与参数说明custom_pretty_action_fn.mdadditionalParams各 Widget 取值约定custom_additional_params.md对话框服务实现custom-dialog.service.ts对话框控制器基类提供fb/validators/dialogRefcustom-dialog.component.ts模板用到的内置组件entity-type-select.component.ts、entity-subtype-autocomplete.component.ts、entity-select.component.ts、relation-type-autocomplete.component.ts同类场景还可参考仓库中examples_custom_pretty/目录下的其它成对示例编辑设备/资产对话框、创建用户对话框、编辑图片属性对话框、克隆设备对话框它们的 HTML 模板与 JS 控制器结构与本文高度一致可作为扩展模板复用的起点。赞分享物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载相关推荐ThingsBoard 仪表板自定义动作实战用 customDialog 与 HTML 模板实现设备/资产创建对话框ThingsBoard 仪表板自定义动作实战用 customDialog 与 HTML 模板实现设备/资产创建对话框 本指南聚焦 ThingsBoard 仪表物联网后端数据可视化消息队列Fleet 后端开发模式指南API 输入校验、Go 与 MySQL 工程实践与 GitOps 落地Fleet 后端开发模式指南API 输入校验、Go 与 MySQL 工程实践与 GitOps 落地 Fleet 是一套开源的设备管理平台Open devic物联网后端数据可视化消息队列ThingsBoard 自定义 Widget 动作用 JavaScript HTML 模板实现设备/资产编辑对话框ThingsBoard 自定义 Widget 动作用 JavaScript HTML 模板实现设备/资产编辑对话框 导读 本文围绕 ThingsBoard物联网后端数据可视化消息队列上一篇2025年终极指南Greenkeeper项目实战问题全解析与解决方案下一篇font-carrier 项目常见问题解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表