
物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载在地图类仪表板组件中放置地图项Place Map Item动作允许用户在地图上直接绘制 Marker、矩形、圆形、多边形或折线并触发自定义 JavaScript 动作。本文基于 ThingsBoard 前端仓库ui-ngx提供的官方示例place-map-item-sample-js.raw讲解如何编写一个自定义对话框Custom Dialog让用户在地图上放置一个地图项后通过表单创建一个资产Asset或设备Device同时把地图项的坐标信息自动保存为实体的属性。读完本文你将掌握 ThingsBoard 自定义动作中注入服务 → 打开自定义对话框 → 表单校验 → 保存实体与属性的完整调用链并能直接将其改造为适用于自己业务场景的仪表板交互。一、功能背景Place Map Item 动作与自定义 JavaScriptThingsBoard 的地图组件如 OpenStreetMap、Google Map、图像地图等支持在工具栏配置地图动作按钮或在部件头部配置动作按钮其中一类动作就是placeMapItem。当用户点击该按钮后地图进入绘制模式用户在地图上放置指定类型的地图项Marker / Rectangle / Polygon / Circle / Polyline随后动作对应的自定义函数被执行。从源码看地图组件的入口实现位于 map.tspublic placeMapItem(actionData: PlaceMapItemActionData): void { switch (actionData.action.mapItemType) { case MapItemType.marker: this.createMarker(actionData); break; case MapItemType.polygon: this.createPolygon(actionData); break; case MapItemType.rectangle: this.createRectangle(actionData); break; case MapItemType.circle: this.createCircle(actionData); break; case MapItemType.polyline: this.createPolyline(actionData); break; } }在自定义动作配置界面中如果动作类型选择 Place Map Item前端会自动载入官方示例模板HTML 模板来自 place-map-item-sample-html.rawJS 控制器来自 place-map-item-sample-js.raw。两者的默认装配逻辑见 custom-action.models.ts 的toPlaceMapItemAction函数。本文讲解的正是这份官方 JS 示例在地图上放置地图项后弹出一个对话框来创建资产或设备并自动保存地图坐标属性。二、示例整体工作流程这段示例的核心流程可以分为四步获取服务通过widgetContext.$scope.$injector与widgetContext.servicesMap取得customDialog、assetService、deviceService、attributeService四个服务打开对话框调用customDialog.customDialog(htmlTemplate, AddEntityDialogController)渲染自定义对话框填写并保存表单用户在表单中输入实体名称、类型、标签及自定义属性控制器根据所选类型调用saveAsset或saveDevice写入位置属性在实体创建成功后根据地图项类型$event.shape把坐标或图形数据以SERVER_SCOPE属性的形式保存到新实体上最后调用widgetContext.updateAliases()刷新仪表板别名让新实体立即可见。示意图点击地图动作按钮 ↓ 地图进入绘制模式用户放置地图项 ↓ 执行自定义 JS$event 携带地图项类型与坐标 ↓ customDialog 打开表单对话框htmlTemplate AddEntityDialogController ↓ 用户填写并提交 ↓ saveEntityObservable创建 Asset / Device ↓ saveAttributes保存自定义属性 地图位置属性 ↓ widgetContext.updateAliases() 刷新别名关闭对话框三、获取注入服务widgetContext 与 servicesMap示例开头通过两行关键代码拿到所需的 Angular 服务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));这里的widgetContext是动作执行时注入的部件上下文widgetContext.$scope指向部件所在组件的 Angular 作用域因此widgetContext.$scope.$injector可以获得真实的 Angular Injector。而widgetContext.servicesMap是一个服务名称 → 服务类型的映射表定义于 services.map.ts。该表中可用服务包括服务名key对应服务用途customDialogCustomDialogService打开自定义对话框deviceServiceDeviceService设备 CRUDassetServiceAssetService资产 CRUDattributeServiceAttributeService实体属性读写entityRelationServiceEntityRelationService实体关系管理dialogsDialogService通用对话框translateTranslateService国际化翻译httpHttpClientHTTP 请求routerRouter路由跳转dateDatePipe日期格式化示例中额外用到了widgetContext.rxjsRxJS 操作符与of、widgetContext.updateAliases()、widgetContext.mapInstance与additionalParams这些都是自定义动作可直接访问的上下文能力。四、打开自定义对话框customDialog API示例通过openAddEntityDialog打开对话框openAddEntityDialog(); function openAddEntityDialog() { customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe(); }其中htmlTemplate是自定义动作中可直接使用的字符串变量保存着对话框的 HTML 模板即 Place Map Item 示例自带的 HTML见 place-map-item-sample-html.raw。customDialog()方法的底层实现位于 custom-dialog.service.tscustomDialog(template: string, controller: (instance: CustomDialogComponent) void, data?: any, config?: MatDialogConfig): Observableany { const imports [this.sharedModule, CommonModule, this.sharedHomeComponentsModule, this.homeComponentsModule, this.widgetComponentsModule]; if (Array.isArray(this.customImports)) { imports.push(...this.customImports); } return this.dynamicComponentFactoryService.createDynamicComponent( class CustomDialogComponentInstance extends CustomDialogComponent {}, template, imports).pipe( mergeMap((componentType) { const dialogData: CustomDialogContainerData { controller, customComponentType: componentType, data }; let dialogConfig: MatDialogConfig { disableClose: true, panelClass: [tb-dialog, tb-fullscreen-dialog], data: dialogData }; if (config) { dialogConfig {...dialogConfig, ...config}; } return this.dialog.openCustomDialogContainerComponent, CustomDialogContainerData, any( CustomDialogContainerComponent, dialogConfig).afterClosed().pipe( tap(() { this.dynamicComponentFactoryService.destroyDynamicComponent(componentType); })); })); }从源码可以看出该 API 的几个关键特性模板动态编译htmlTemplate字符串会被DynamicComponentFactoryService动态编译为一个组件因此你可以自由编写模板并使用 Angular Material 组件控制器注入第二个参数AddEntityDialogController是一个回调函数会在自定义对话框组件实例创建时被调用并把实例作为参数传入详见 custom-dialog.component.ts。该实例预置了fbUntypedFormBuilder、validatorsAngularValidators、dialogRefMatDialogRef、data自定义数据等能力返回 ObservablecustomDialog()返回一个 Observable订阅它即可感知对话框的关闭对话框关闭行为默认disableClose: true即点击对话框外部不会关闭必须由控制器显式调用vm.dialogRef.close(...)。对话框容器组件 custom-dialog-container.component.ts 会把你的控制器与模板组合成动态组件并渲染到MatDialog中若模板或控制器编译失败会抛出对应的widget-action.custom-pretty-template-error/custom-pretty-controller-error错误提示。五、控制器实现表单定义与校验规则控制器AddEntityDialogController是整个动作的业务核心它接收instance参数即动态对话框组件实例并把业务逻辑挂载到vm上function AddEntityDialogController(instance) { let vm instance; vm.allowedEntityTypes [ASSET, DEVICE]; vm.addEntityFormGroup vm.fb.group({ entityName: [, [vm.validators.required]], entityType: [DEVICE], entityLabel: [null], type: [, [vm.validators.required]], attributes: vm.fb.group({ address: [null], owner: [null] }) }); ... }表单字段说明表单字段默认值校验规则说明entityName必填实体名称entityTypeDEVICE无实体类型示例限定为ASSET/DEVICEentityLabelnull无实体显示标签可选type必填实体子类型Asset/Device Profile 名称attributes.addressnull无自定义属性地址attributes.ownernull无自定义属性负责人allowedEntityTypes用于限制tb-entity-type-select组件可选的实体类型范围vm.fb、vm.validators均来自对话框组件基类CustomDialogComponent与标准 Angular Reactive Forms 的FormBuilder、Validators完全一致。也就是说你可以在此基础上扩展更多字段、更复杂的嵌套表单组与自定义校验器。cancel与save是模板中按钮绑定的两个方法vm.cancel function() { vm.dialogRef.close(null); }; vm.save function() { vm.addEntityFormGroup.markAsPristine(); saveEntityObservable().pipe( widgetContext.rxjs.switchMap((entity) saveAttributes(entity.id)) ).subscribe(() { widgetContext.updateAliases(); vm.dialogRef.close(null); }); };save()的执行要点markAsPristine()会重置表单的 dirty 状态配合 HTML 模板中Create 按钮在!addEntityForm.dirty时禁用的逻辑避免用户重复提交先执行saveEntityObservable()创建实体再通过 RxJS 的switchMap把实体对象流转到saveAttributes(entity.id)保存属性形成先建实体、后写属性的链式调用全部完成后调用widgetContext.updateAliases()刷新当前仪表板的实体别名使新创建的实体能立即被数据源别名引用最后vm.dialogRef.close(null)关闭对话框。六、HTML 模板解析动作默认配套的 HTML 模板place-map-item-sample-html.raw与控制器协同工作核心结构如下form #addEntityFormngForm [formGroup]addEntityFormGroup (ngSubmit)save() stylewidth: 552px !-- 顶部工具栏标题 关闭按钮 -- mat-toolbar colorprimary h2Add entity/h2 button mat-icon-button (click)cancel() typebutton mat-iconclose/mat-icon /button /mat-toolbar !-- 表单主体 -- div mat-dialog-content !-- 实体名称 / 实体标签 -- mat-form-field 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 !-- 实体类型选择tb-entity-type-select限定 ASSET / DEVICE -- tb-entity-type-select formControlNameentityType [allowedEntityTypes]allowedEntityTypes [showLabel]true/tb-entity-type-select !-- 子类型根据 entityType 动态切换 Asset / Device 子类型自动补全 -- tb-entity-subtype-autocomplete *ngIfentityType ! ASSET .../tb-entity-subtype-autocomplete !-- 自定义属性地址 / 负责人 -- div formGroupNameattributes input matInput formControlNameaddress input matInput formControlNameowner /div /div !-- 底部操作按钮 -- div mat-dialog-actions button mat-button typebutton (click)cancel()Cancel/button button mat-raised-button typesubmit [disabled]addEntityForm.invalid || !addEntityForm.dirtyCreate/button /div /form模板中使用到的关键组件tb-entity-type-select实体类型下拉选择组件allowedEntityTypes限定可选范围tb-entity-subtype-autocomplete实体子类型自动补全组件根据当前entityType动态切换加载 Asset Profile 或 Device Profile 列表mat-progress-bar、mat-form-field、mat-dialog-actions等均为 Angular Material 组件由于customDialog()内部导入了共享模块、Material 相关模块与部件组件模块这些标签可以直接在模板中使用。你可以完全替换这份模板例如增加经纬度输入框、描述字段、下拉选择等只要保持控制器与表单控件名一一对应即可。七、保存实体saveEntityObservable 的分支逻辑saveEntityObservable根据表单选择的实体类型创建不同实体function saveEntityObservable() { const formValues vm.addEntityFormGroup.value; let entity { name: formValues.entityName, type: formValues.type, label: formValues.entityLabel }; if (formValues.entityType ASSET) { return assetService.saveAsset(entity); } else if (formValues.entityType DEVICE) { return deviceService.saveDevice(entity); } }这里构造的entity对象包含name名称、type子类型/Profile与可选的label标签字段直接对应 ThingsBoard 的 Asset/Device 数据模型。assetService.saveAsset与deviceService.saveDevice是前端AssetService、DeviceService的标准保存方法返回 RxJS Observable。该方法返回后save()中的switchMap会拿到新实体的id随即进入属性保存阶段。八、保存地图位置属性getMapItemLocationAttributessaveAttributes是本示例最体现Place Map Item特色的部分——它把用户在地图上放置的地图项几何信息自动转换为实体属性function saveAttributes(entityId) { let attributes vm.addEntityFormGroup.get(attributes).value; let attributesArray getMapItemLocationAttributes(); for (let key in attributes) { if(attributes[key] ! null) { attributesArray.push({key: key, value: attributes[key]}); } } if (attributesArray.length 0) { return attributeService.saveEntityAttributes(entityId, SERVER_SCOPE, attributesArray); } return widgetContext.rxjs.of([]); }其逻辑是先获取表单中attributes组address、owner的值再调用getMapItemLocationAttributes()生成位置属性数组然后过滤掉空值后合并最后统一调用attributeService.saveEntityAttributes(entityId, SERVER_SCOPE, attributesArray)以服务器端作用域SERVER_SCOPE保存。若没有任何属性数组为空则直接返回空 Observable 避免无效请求。位置属性的生成逻辑基于地图项类型$event.shapefunction getMapItemLocationAttributes() { const attributes []; const mapItemType $event.shape; if (mapItemType Marker) { const mapType widgetContext.mapInstance.type(); attributes.push({key: mapType image ? xPos : latitude, value: additionalParams.coordinates.x}); attributes.push({key: mapType image ? yPos : longitude, value: additionalParams.coordinates.y}); } else if (mapItemType Rectangle || mapItemType Polygon || mapItemType Line) { attributes.push({key: perimeter, value: additionalParams.coordinates}); } else if (mapItemType Circle) { attributes.push({key: circle, value: additionalParams.coordinates}); } return attributes; }各分支的语义地图项类型$event.shape保存的属性取值来源Marker 普通地图latitude/longitudeadditionalParams.coordinates.x / .yMarker 图像地图mapInstance.type() imagexPos/yPos同上Rectangle/Polygon/LineperimeteradditionalParams.coordinates几何数据对象CirclecircleadditionalParams.coordinates其中$event是触发本次动作的 DOM 事件包含地图项形状shapeadditionalParams是动作上下文注入的附加参数对象包含绘制完成的坐标coordinateswidgetContext.mapInstance则是当前地图实例其type()方法用于判断地图类型从而决定 Marker 保存经纬度还是图像像素坐标。这两组变量在自定义动作编辑器的自动补全中都有定义说明见 custom-action.models.ts。这样新创建的资产或设备在创建的一瞬间就拥有了自己的位置属性可直接被后续的数据源、规则链或后端逻辑引用例如按经纬度过滤设备、计算周长/圆形范围等。九、把示例应用到自己的仪表板要在自己的仪表板中启用这段能力操作路径如下进入仪表板编辑模式添加一个地图类部件例如 OpenStreetMap并在部件高级设置Actions中新建一个动作将动作类型配置为 Place Map Item并选择要放置的地图项类型Marker / Rectangle / Polygon / Circle / Polyline在动作的 Custom Action 编辑器中JS 与 HTML 默认会载入官方示例分别对应 place-map-item-sample-js.raw 与 place-map-item-sample-html.raw也可以在此基础上直接修改保存动作后地图工具栏会出现对应的绘制按钮用户点击按钮并在地图上放置地图项即可触发对话框创建实体需要地图按钮工具栏时可在部件设置中的mapActionButtons配置动作按钮参考 map.ts 的setupCustomActions逻辑。注意事项动作执行需要用户具备创建资产/设备的权限后端saveAsset/saveDevice会按租户权限进行校验对话框的disableClose: true意味着必须通过 Cancel/Create 按钮关闭避免误操作创建成功后widgetContext.updateAliases()会刷新别名数据若仪表板别名缓存较新数据建议保存后稍作等待再刷新以确保新实体进入数据源若修改了模板或控制器导致编译错误对话框会直接报错关闭custom-pretty-template-error/custom-pretty-controller-error可通过浏览器控制台查看具体错误信息。十、小结本文围绕官方 Place Map Item 示例place_map_item/create_dialog_js文档完整梳理了在地图上放置地图项 → 打开自定义对话框 → 创建资产/设备 → 自动保存位置属性的完整链路。其中关键的可复用知识点包括通过widgetContext.$scope.$injectorwidgetContext.servicesMap按需注入任意注册服务服务清单见 services.map.tscustomDialog.customDialog(htmlTemplate, controller)的动态模板与控制器装配机制实现见 custom-dialog.service.ts控制器实例自带fb、validators、dialogRef等能力可直接编写响应式表单saveEntityObservablesaveAttributes的先建实体、后写属性RxJS 链式调用模式基于$event.shape与additionalParams.coordinates将地图项几何数据映射为latitude/longitude、xPos/yPos、perimeter、circle等属性的通用做法。掌握这段示例后你可以轻松扩展出地图打点创建设备并初始化告警阈值圈选区域创建资产并保存围栏数据绘制路线后创建巡检任务等更复杂的物联网交互场景。赞分享物联网后端数据可视化消息队列【免费下载链接】thingsboardAll-in-one IoT Platform - Device management, data collection, processing and visualization.项目地址https://gitcode.com/GitHub_Trending/th/thingsboard点击查看免费下载相关推荐ThingsBoard 地图组件 Place Map Item 操作实战创建设备/资产对话框 HTML 模板全解ThingsBoard 地图组件 Place Map Item 操作实战创建设备/资产对话框 HTML 模板全解 本篇技术指南围绕 ThingsBoard物联网后端数据可视化消息队列Task 模板引擎全解析从变量插值到函数库的 Templating Reference 实战指南Task 模板引擎全解析从变量插值到函数库的 Templating Reference 实战指南 导读 Task本项目为 GitHub 加速计划 / ta物联网后端数据可视化消息队列ThingsBoard 仪表板自定义动作实战用 customDialog 与 HTML 模板实现设备/资产创建对话框ThingsBoard 仪表板自定义动作实战用 customDialog 与 HTML 模板实现设备/资产创建对话框 本指南聚焦 ThingsBoard 仪表物联网后端数据可视化消息队列上一篇Vue Router 2 程序化导航完全指南push、replace、go 与浏览器历史记录操作下一篇PEFT 安全策略与威胁模型全解漏洞报告流程、信任边界与模型工件加载安全实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考