ThingsBoard 前端项目背景图片部件开发

news2024/11/23 9:28:56

前言

ThingsBoard 是目前 Github 上最流行的开源物联网平台(14.4k Star),可以实现物联网项目的快速开发、管理和扩展, 是中小微企业物联网平台的不二之选。

本文介绍如何在 ThingsBoard 前端项目中开发背景图片部件。

产品需求

最近接到产品经理一个需求,在 TB 仪表板中部件的下面可以添加背景图片,实现类似如下的效果:

TB 仪表板整个界面是支持背景图设置的,单个部件只支持背景颜色设置,还不支持背景图片设置,原本想着加个背景图片设置功能就可以解决,结果被告知可能会存在两个部件共用一个背景图片的需求,这就有些麻烦了…

解决方案

经过我一番推敲,终于找到了一个合理的解决方案,那就是开发一个可拖拽到其他部件底部的图片部件,将上方的部件背景设置为透明,即可实现预期效果。

开发一个新的部件这个我有经验,难点在于实现拖拽的其他部件下方的部件,这个目前 TB 是不支持的。

在开发者工具中可以查看到,两个部件元素样式 z-index: 1 一致,均在一个层级上,所以无法重叠。

查看项目代码发现,该功能用到了网格布局插件:angular-gridster2,那么就查查看它的官网支不支持重叠功能了。

  • github:https://github.com/tiberiuzuld/angular-gridster2(1.2k)哎呦,star还不低的样子。
  • 官方文档:https://tiberiuzuld.github.io/angular-gridster2

官方文档是英文的,在我蹩脚的英文阅读水平下,终于找到了该功能:https://tiberiuzuld.github.io/angular-gridster2/multiLayer,感谢官方文档贴心的带 demo 演示功能。

允许项目分层显示关键参数:allowMultiLayer:allow items show in layers。

确定方案可行,那就开干!

背景图片部件

部件高级设置

首先我将背景图片部件定义为 Cards 部件库的一种,所以我在 ui-ngx\src\app\modules\home\components\widget\lib\settings\cards 目录下创建部件设置文件 image-widget-settings.component.htmlimage-widget-settings.component.ts

其中 image-widget-settings.component.html 代码:

<section class="tb-widget-settings" [formGroup]="imageWidgetSettingsForm">

  <fieldset class="fields-group">
    <legend class="group-title" translate>widgets.image.settings</legend>
    <div fxLayout.xs="column" fxLayout="column" fxLayoutGap="8px">
      <!--上传图片-->
      <tb-image-input label="{{ 'widgets.image.imageUrl' | translate }}"
                      formControlName="imageUrl">
      </tb-image-input>
      <!--是否为背景图片-->
      <mat-slide-toggle formControlName="isUnderLayer" class="slide-block">
        {{ 'widgets.image.isUnderLayer' | translate }}
      </mat-slide-toggle>
    </div>
  </fieldset>

</section>

用 FormGroup 表单控件 imageWidgetSettingsForm 存储数据,数据字段 imageUrl 用存储背景图,使用 TB 的 tb-image-input 上传图片组件。另一个数据字段 isUnderLayer 用来存储是否设定为背景图片,即是否可拖拽到其他部件下方,使用 UI 插件 Material 的滑块开关组件 mat-slide-toggle 来实现。

image-widget-settings.component.ts 代码如下:

import { Component } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';

@Component({
  selector: 'tb-image-widget-settings',
  templateUrl: './image-widget-settings.component.html',
  styleUrls: ['./../widget-settings.scss']
})
export class ImageWidgetSettingsComponent extends WidgetSettingsComponent {

  /*FormGroup表单*/
  imageWidgetSettingsForm: FormGroup;

  constructor(protected store: Store<AppState>,
              private fb: FormBuilder) {
    super(store);
  }

  protected settingsForm(): FormGroup {
    return this.imageWidgetSettingsForm;
  }

  /*初始化数据字段*/
  protected defaultSettings(): WidgetSettings {
    return {
      imageUrl: '',
      isUnderLayer: true,
    };
  }

  /*数据字段设置*/
  protected onSettingsSet(settings: WidgetSettings) {
    this.imageWidgetSettingsForm = this.fb.group({
      imageUrl: [settings.imageUrl, []],
      isUnderLayer: [settings.isUnderLayer, []]
    });
  }

  /*数据字段验证*/
  protected updateValidators(emitEvent: boolean) {
    this.imageWidgetSettingsForm.get('imageUrl').updateValueAndValidity({emitEvent});
    this.imageWidgetSettingsForm.get('isUnderLayer').updateValueAndValidity({emitEvent});
  }
}

代码并不多,我就都贴上了,定义组件选择器 tb-image-widget-settings,定义 FormGroup 表单 imageWidgetSettingsForm, 初始化数据字段 imageUrlisUnderLayer,剩下的数据设置和验证功能和其他的部件设置写法是一致的。

最后记得将 Class ImageWidgetSettingsComponent 在部件设置模块文件 widget-settings.module.ts 中引入声明和导出。

import {
  ImageWidgetSettingsComponent
} from '@home/components/widget/lib/settings/cards/image-widget-settings.component';

@NgModule({
  declarations: [
  ...
  ImageWidgetSettingsComponent
  ],
  exports: [
  ...
  ImageWidgetSettingsComponent
  ]

export class WidgetSettingsModule {
}

export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsComponent>} = {
  ...
  'tb-image-widget-settings': ImageWidgetSettingsComponent
};

部件功能展示

创建好部件的高级设置功能,我们来将部件效果展示出来。

ui-ngx\src\app\modules\home\components\widget\lib 目录下创建三个文件 image-widget.component.htmlimage-widget.component.tsimage-widget.component.css

其中 image-widget.component.html 代码如下:

<div class="bg-img" style="background-image: url('{{settings.imageUrl}}')">
</div>

超级简单有木有,因为就是一个背景图效果展示而已,背景图片的路径就是 imageUrl

image-widget.component.css 代码如下:

:host {
  display: flex;
  width: 100%;
  height: 100%;
  .bg-img{
    width: 100%;
    height: 100%;
    background-repeat: no-repeat;
    background-size: 100% 100%;
  }
}

Css 用来设置部件的宽高和背景图片的样式,background-size: 100% 100%; 实现背景图片可随着容器拉伸,达到图片全部展示的效果。

image-widget.component.ts 代码如下:

import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';

interface ImageWidgetSettings {
  imageUrl: string;
  isUnderLayer: boolean;
}

@Component({
  selector: 'tb-image-widget',
  templateUrl: './image-widget.component.html',
  styleUrls: ['./image-widget.component.css']
})
export class ImageWidgetComponent extends PageComponent implements OnInit {

  settings: ImageWidgetSettings;

  @Input()
  ctx: WidgetContext;

  constructor(protected store: Store<AppState>,
              protected cd: ChangeDetectorRef) {
    super(store);
  }

  ngOnInit(): void {
    this.ctx.$scope.imageWidget = this;
    this.settings = this.ctx.settings;
  }
}

其中的 settings 字段存储部件设置的 imageUrlisUnderLayer

和高级设置文件一样,Class ImageWidgetComponent 需要在部件模块文件 widget-components.module.ts 中引入声明和导出。

import { ImageWidgetComponent } from '@home/components/widget/lib/image-widget.component';

@NgModule({
  declarations: [
  ...
  ImageWidgetComponent
  ],
  exports: [
  ...
  ImageWidgetComponent
  ]

export class WidgetComponentsModule {
}

设置部件层级

咳咳,敲黑板,接下来是最核心的部分,怎么设置背景图片部件的层级,实现可拖拽到其他部件的底层。

首先找到 angular-gridster2 选项设置的代码,在 dashboard.component.ts 文件中,添加分层选项 allowMultiLayer

ngOnInit(): void {
    this.dashboardWidgets.parentDashboard = this.parentDashboard;
    if (!this.dashboardTimewindow) {
      this.dashboardTimewindow = this.timeService.defaultTimewindow();
    }
    this.gridsterOpts = {
      gridType: GridType.ScrollVertical,
      keepFixedHeightInMobile: true,
      disableWarnings: false,
      disableAutoPositionOnConflict: false,
      pushItems: false,
      swap: false,
      maxRows: 100,
      minCols: this.columns ? this.columns : 24,
      maxCols: 3000,
      maxItemCols: 1000,
      maxItemRows: 1000,
      maxItemArea: 1000000,
      outerMargin: true,
      margin: isDefined(this.margin) ? this.margin : 10,
      minItemCols: 1,
      minItemRows: 1,
      defaultItemCols: 8,
      defaultItemRows: 6,
      
      <!--新增参数-->
      allowMultiLayer: true,
      baseLayerIndex: 1,
      defaultLayerIndex: 2,
      maxLayerIndex: 10,
      
      resizable: {enabled: this.isEdit},
      draggable: {enabled: this.isEdit},
      itemChangeCallback: item => this.dashboardWidgets.sortWidgets(),
      itemInitCallback: (item, itemComponent) => {
        (itemComponent.item as DashboardWidget).gridsterItemComponent = itemComponent;
      }
    };
  }

allowMultiLayer: true 设置为允许分层重叠,因为其他部件要在背景图片部件的上方,所以将默认层 defaultLayerIndex 设置 2,按照官方文档的说法,final z-index should be baseLayerIndex + layerIndex,所以最终部件的层级为 baseLayerIndex + defaultLayerIndex,也就是层级为 3,打开控制工具查看,正好为 3。

那么问题来了,这是设置整个网格布局的参数,单个部件的层级怎么设置,官方文档 API 并没有给出。

于是,我想到了面向 ChatGPT 编程:

哦?setZIndex(),原来如此!还好我有 ChatGPT,真是… 真是胡说八道啊,试了压根不生效好叭!亏我这么相信你… 压根没这个方法,真是一本正经胡编乱造啊… 算了,看你之前帮了我那么多次,这次我就不跟你计较了…

靠人不如靠己,继续研究官方文档我发现右上角有查看源码按钮… 点开阅读源码终于找到如何设置 layerIndex

原来将数据设置一个 layerIndex 就可以了,找到循环体数据源打印出来:

果然找到了栅格单元参数 cols, rows, y, x,我们只需要找到对应的背景图片部件,设置上 layerIndex 属性即可,那怎么找到呢?对了,就是通过高级设置的字段 isUnderLayer,在 dashboard.component.ts 文件中 updateWidgets() 方法体中加入判断。

private updateWidgets() {
    this.dashboardWidgets.setWidgets(this.widgets, this.widgetLayouts);
    this.dashboardWidgets.doCheck();
    
    <!--背景图片部件层级设置-->
    this.dashboardWidgets.dashboardWidgets.forEach((widget) => {
      if (widget.widgetContext.widget.config.settings?.isUnderLayer) {
        widget.layerIndex = 1;
      }
    });
  }

因为其他的部件我设置了默认 layerIndex 为 2,背景图片部件在其他部件的下层,所以设置更小的值 1。

最终效果演示

首先我们需要先将背景图片部件添加到部件库中,登录系统管理员账号 sysadmin@thingsboard.org / sysadmin,登录系统管理员账号操作是因为添加后会默认显示为系统部件包。

打开部件库菜单,打开 Cards 部件包,右下角点击添加新的部件类型->创建新的部件类型->静态部件,进行背景图片部件初始化设置:

  1. 设置部件标题,如“Image”
  2. 设置 HTML :<tb-image-widget [ctx]="ctx"></tb-image-widget>
  3. 清空 JavaScript 内容
  4. widget.widget-settings 中 widget.settings-form-selector 设置为 tb-image-widget-settings

其中第 2 项中 [ctx]="ctx" 为组件传值必须项,不能省略;第 4 项的 tb-image-widget-settings 为部件高级设置选择器,不能填错。

添加好部件好,我们在仪表板中添加该部件。

在高级设置中上传一张图片,并将是否为背景图片设置为是,保存后我们来查看下部件元素的 z-index

背景图片部件的 z-index 为 2 生效了,接下来就是见证奇迹额时刻!

大功告成,Nice~

结语

由于 TB 的受众面很小,所以如果你没研究过 TB 看不懂这篇文章也是很正常的- -,跳过就好,TB 的相关文章更多的是作为本人的一个工作知识记录,如果能对一小部分人有所帮助那就更好啦~

好啦,以上就是 ThingsBoard 前端项目背景图片部件开发的全部内容,希望对你有所帮助,如有问题可通过我的博客 https://echeverra.cn 或微信公众号 echeverra 联系我。

你学“废”了么?

(完)


文章首发于我的博客 https://echeverra.cn/tb3,原创文章,转载请注明出处。

欢迎关注我的微信公众号 echeverra,一起学习进步!不定时会有资源和福利相送哦!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1044547.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

掌握可扩展和可维护应用程序:12-Factor应用程序开发的全面指南

1*vhxOhTuKSyuuAKu-nUQ5SA.jpeg 在今天的快节奏世界中&#xff0c;软件开发人员需要创建可扩展、可维护和适应性强的应用程序。12-Factor应用程序方法论是一组最佳实践&#xff0c;可以帮助开发人员实现这些目标。 本文深入探讨了12个因素&#xff0c;详细解释了它们的重要性以…

python 深度学习 解决遇到的报错问题5

目录 一、conda安装shapefile失败 二、conda安装osmnx失败&#xff1a;To search for alternate channels that may provide the conda package yourelooking for, navigate to 三、ERROR: Could not build wheels for llvmlite, which is required to install pyproject.to…

算法基础--位运算

一、常见位运算总结&#xff1a; 1、基础位运算&#xff08;^&#xff09; 其中异或^有2种理解。 2、位图bitset相关&#xff08;&|&#xff09; test判断第x位是1函数0: 可以让n右移&#xff0c;也可以让1左移&#xff0c;习惯上选择第一种 (n>>x)&1 判…

初创企业应该选一款怎样的客服系统?

互联网经济时代的飞速发展&#xff0c;促使客户市场对于企业服务的要求越来越高。客户在选择产品的时候已经不单单却决于产品功能和价格&#xff0c;客户服务也是其关注的重点。 优质的客户服务所带来的是客户满意度的提升&#xff0c;以及品牌影响力的提高。所以&#xff0c;…

Linux 服务器下 pypy 下载数据集

Linux 服务器下 pypy 下载数据集 安装 pip install bypy链接自己的百度网盘 命令行直接输入 byby info 就行 byby info查看网盘里面的内容 bypy listbyby list 只显示自动生成的bypy中的文件&#xff0c;上传也是在这个目录中&#xff0c;可以自己在里面新建文件夹 4. 上传…

tsar-性能监控工具

简介 tsar是淘宝自己开发的一个采集工具&#xff0c;主要用来收集服务器的系统信息&#xff08;如cpu&#xff0c;io&#xff0c;mem&#xff0c;tcp等&#xff09;&#xff0c;以及应用数据&#xff08;如squid haproxy nginx等&#xff09;。收集到的数据存储在磁盘上&#…

SMOKE-CMAQ实践技术应用

大气污染物排放是空气污染的源头&#xff0c;气象因素是影响污染程度的重要因素&#xff0c;因此空气质量模式要求气象资料和污染物排放清单作为输入&#xff0c;其中由于大气污染源复杂性、数据滞后性、动态变化、规律性不明显等特点&#xff0c;使得大气污染源排放清单输入准…

【牛客网】OR63 删除公共字符串

思路 创建哈希表,将第二个字符串中出现过的字符添加到哈希表中创建StringBuffer来拼接最后的结果字符串遍历字符串一,如果字符在哈希表中出现过,就不拼接到字符串中,反之则拼接 Java代码 import java.util.*;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public cl…

ISIS的高级特性

1、IS-IS邻接关系建立原则 L1的路由器只能和L1的路由器建立邻接关系&#xff0c;也可以和L1、2的路由建立邻接关系 L2的路由器只能和L2的路由器建立邻接关系&#xff0c;也可以和L1、2的路由建立邻接关系 DIS只有在广播型网络中才会选举 LSP相当于OSPF中的LSA IS-IS链路状态报文…

JUC第十讲:CAS,Unsafe和原子类详解

JUC第十讲&#xff1a;CAS,Unsafe和原子类详解 JUC中多数类是通过volatile和CAS来实现的&#xff0c;CAS本质上提供的是一种无锁方案&#xff0c;而Synchronized和Lock是互斥锁方案; java原子类本质上使用的是CAS&#xff0c;而CAS底层是通过Unsafe类实现的。本文是JUC第十讲&a…

广东海颐开发笔试编程题回顾

题目一 1、现以序列{22, 24, 30, 14, 10, 17, 15, 20, 16, 23}的顺序进行输入&#xff0c;请画出构造出的平衡二叉树?请写出实现二叉树左旋的代码?&#xff08;具体题目忘记了&#xff0c;就随机搞个排序&#xff0c;思路和方法都是一样的&#xff09; 图 顺序 {22, 14, 10…

【C++】多态,从使用到底层。

文章目录 前言一、多态的概念二、多太的定义和实现2.1 多太的构造条件2.2 虚函数2.3 重写(覆盖)2.4 C11 override 和 final2.5 重载&#xff0c;隐藏&#xff0c;重写 三、多态的原理3. 1虚函数表3.2 虚函数表如何完成多态的功能3.3 虚函数表存储在内存空间的那个区域&#xff…

服务断路器_Resilience4j超时降级

创建模块cloud-consumer-resilience4j-order80 POM引入依赖 <dependencies><!-- 引入Eureka 客户端依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</a…

【Java 进阶篇】数据库介绍与MySQL详细介绍

数据库是信息科技领域中不可或缺的一部分&#xff0c;它们在我们日常生活中扮演着重要的角色&#xff0c;从手机应用到云计算&#xff0c;无处不在。在本篇博客中&#xff0c;我们将深入探讨数据库的基本概念以及MySQL这一流行的开源关系型数据库的详细信息。不需要数据库专业知…

AI写作生成器-AI写作生成器下载和用途

在当今数字化的时代&#xff0c;AI写作生成器已经成为了各行各业的创作者、企业家和学生的得力助手。这些智能工具以其强大的自然语言处理技术&#xff0c;正在解决着许多用户的写作难题。本文将深入探讨AI写作生成器&#xff0c;以及它如何在不同领域解决用户的写作问题。 147…

【算法分析与设计】递归与分治策略

目录 一、学习要点二、算法总体思想三、递归的概念例1 阶乘函数例2 Fibonacci数列例3 Ackerman函数例4 整数划分问题例5 Hanoi塔问题递归小结 四、分治法1、分治法的适用条件2、二分搜索技术3、大整数的乘法4、Strassen矩阵乘法5、棋盘覆盖6、合并排序7、快速排序8、线性时间选…

嵌入式 - 经典的有刷电机和先进的无刷电机

自从无刷直流电机诞生&#xff0c;“古老的”有刷电机就开始没落&#xff0c;但它依然是低成本应用的可靠选择&#xff0c;并且实现起来简单。 在有刷电机中&#xff0c;磁极方向的跳转是通过移动固定位置的接触点来完成的&#xff0c;该接触点在电机转子上与电触点相对连接。这…

无法从 /var/lib/rpm 打开软件包数据库

使用yum命令安装软件包时&#xff0c;报错“无法从 /var/lib/rpm 打开软件包数据库” 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.原因 是误操作导致 rpm 数据库损坏。&#xff08;/var/lib/rpm 目录下的文件被损坏&#xff09; 2.解决 当RPM 数据库发生损坏&a…

【SAP后台配置】如何通过前台屏幕字段找到对应SPRO后台路径?

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;阿里云社区专家博主&#xff0c;华为云云享专家&#xff0c;腾讯云社区认证作者&#xff0c;CSDN SAP应用技术领域优质创作者。在学习工作中&#xff0c;我通常使用偏后端的开发语言ABAP&#xff0c;SQL进行任务的完成…

C++,对象赋值与对象拷贝的区别、深浅拷贝

在C中&#xff0c;对象赋值和对象拷贝是两个不同的操作&#xff0c;它们有明显的区别&#xff1a; 1. 对象赋值&#xff08;Object Assignment&#xff09;&#xff1a; - 对象赋值是指将一个已经存在的对象的值复制给另一个已经存在的对象。这通常通过赋值操作符&#xff08;…