ThingsBoard添加主题定制功能

B站演示地址https://www.bilibili.com/video/bv1RV41177Gb
此文具体代码在文末语雀链接,需额外付费

文章目录

  • 效果演示
  • 资料
  • 思路
  • 具体代码
    • 新建菜单组件

效果演示

https://www.bilibili.com/video/bv1RV41177Gb

说明:由于CE版没有权限功能,所以不能主题定制功能不能细分到每个用户一套主题。
本文实现的是:定制主题绑定租户,租户管理员和客户用户继承所属租户的主题,且租户管理员可修改主题;不同租户下的租户管理员和客户用户登录后分别显示自己的主题。这个效果在视频中有演示。
后面会发布关于登录界面的主题定制功能,可为每个租户设置专有域名和对应的登录界面。俩者结合就可以做到,对租户界面的特有定制,不同租户都拥有属于自己的UI界面。

资料

ngrx/store
angular canDeactivate
angular canDeactivate CSDN介绍
angular FormBuilder
angular FormGroup
Material Design 组件库 for Angular
material design icons

思路

  1. 新建菜单组件,ui-ngx/src/app/modules/home/pages/custom-ui/custom-ui.component.ts,scss,html
  2. 添加菜单路由, ui-ngx/src/app/modules/home/pages/home-settings/home-settings-routing.module.ts
    ThingsBoard添加主题定制功能_第1张图片
    其中:canDeactivate: [ConfirmOnExitGuard] 是离开页面时,做出逻辑判断,即下图效果。
    ThingsBoard添加主题定制功能_第2张图片
    实现此效果需要在组件内实现HasDirtyFlag接口,并定义isDirty变量,值为true显示未保存提示,false不显示。自己根据表单控制。详细代码在文末。
    ThingsBoard添加主题定制功能_第3张图片
  3. 在模块声明组件,ui-ngx/src/app/modules/home/pages/home-settings/home-settings.module.ts
    ThingsBoard添加主题定制功能_第4张图片
  4. 组件表单内容构建,其中图片上传组件可以参考ThingsBoard图片上传组件功能拓展,采用的是tb源码内置的组件
    ThingsBoard添加主题定制功能_第5张图片
  5. 国际化,ui-ngx/src/assets/locale/locale.constant-zh_CN.json中加入国际化信息
  6. 后端接口开发,无需额外建表,主题信息保存到租户的额外信息中。
  7. 前端请求服务开发,为了方便,还是定义到了ui-ngx/src/app/core/http/dashboard.service.ts 和文章的位置一样
  8. store相关开发,ui-ngx/src/app/core/core.state.ts,src/app/core/ui。
    1. 在app.component.ts初始化时请求当前登录用户对应租户的主题信息,没有则展示原始主题。并通过store订阅主题状态,改变则重新渲染。
    2. 登录成功后也需要请求一次后端主题信息,因为退出登录到重新登录,app组件不会再次初始化。
    3. home.component.ts也通过store订阅主题状态,用于改变logo和左下角的平台信息

具体代码

添加下面的代码即可实现此功能。
为减少文章篇幅,优化阅读体验,代码请滚动查看。
内容确实有点多,如果发现有整理遗漏的地方,请联系告知。

新建菜单组件

ui-ngx/src/app/modules/home/pages/custom-ui/下新建
custom-ui.component.ts

import { AfterViewInit, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { environment as env } from '@env/environment';
import { TranslateService } from '@ngx-translate/core';
import { DashboardService } from '@core/http/dashboard.service';
import { UIInfo } from '@shared/models/dashboard.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ActionTenantUIChangeAll } from '@core/ui/tenant-ui.actions';
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard';
import { TenantUIState } from '@core/ui/tenant-ui.models';
import { PageComponent } from '@shared/components/page.component';
import { initialState } from '@core/ui/tenant-ui.reducer';

@Component({
  selector: 'tb-custom-ui',
  templateUrl: './custom-ui.component.html',
  styleUrls: ['./custom-ui.component.scss']
})
export class CustomUiComponent extends PageComponent implements OnInit, HasDirtyFlag,AfterViewInit {
  isDirty = false;
  faviconMaxKBytes = 256;
  logoMaxKBytes = 4096;
  customUiFormGroup: FormGroup;
  initData: any;
  previousData: any;

  constructor(
    protected store: Store<AppState>,
    private translate: TranslateService,
    private dashboardService: DashboardService,
    private fb: FormBuilder
  ) {
    super(store);
    this.initForm();
    this.writeFormByHttp();
  }

  ngAfterViewInit() {
  }

  ngOnInit(): void {
    this.customUiFormGroup.valueChanges.subscribe(data => {
      Reflect.ownKeys(data).forEach(key => data[key.toString()] = data[key.toString()] === '' ? null : data[key.toString()]);
      if(JSON.stringify(this.initData) !== JSON.stringify(data)){
        this.isDirty = true;
        this.previousData = data;
        this.store.dispatch(new ActionTenantUIChangeAll(data));
      }else{
        this.isDirty = false;
        if(JSON.stringify(this.previousData) !== JSON.stringify(data)){
          this.store.dispatch(new ActionTenantUIChangeAll(data));
        }
      }
    });
  }

  writeFormByHttp() {
    this.dashboardService.getTenantUIInfo().subscribe(ui => {
      this.patchFormValue(ui);
      this.initData = this.customUiFormGroup.value;
      this.previousData = this.customUiFormGroup.value;
    });
  }

  patchFormValue(ui: UIInfo | TenantUIState) {
    this.customUiFormGroup.get('applicationTitle').patchValue(ui.applicationTitle);
    this.customUiFormGroup.get('iconImageUrl').patchValue(ui.iconImageUrl);
    this.customUiFormGroup.get('logoImageUrl').patchValue(ui.logoImageUrl);
    this.customUiFormGroup.get('logoImageHeight').patchValue(ui.logoImageHeight);
    this.customUiFormGroup.get('platformMainColor').patchValue(ui.platformMainColor);
    this.customUiFormGroup.get('platformTextMainColor').patchValue(ui.platformTextMainColor);
    this.customUiFormGroup.get('platformButtonColor').patchValue(ui.platformButtonColor);
    this.customUiFormGroup.get('platformMenuColorActive').patchValue(ui.platformMenuColorActive);
    this.customUiFormGroup.get('platformMenuColorHover').patchValue(ui.platformMenuColorHover);
    this.customUiFormGroup.get('showNameVersion').patchValue(ui.showNameVersion);
    this.customUiFormGroup.get('platformName').patchValue(ui.platformName);
    this.customUiFormGroup.get('platformVersion').patchValue(ui.platformVersion);
  }

  //恢复到tb原始设置
  reset($event: Event) {
    if ($event) {
      $event.stopPropagation();
    }
    this.patchFormValue(initialState);
    this.store.dispatch(new ActionTenantUIChangeAll(this.customUiFormGroup.value));
    this.isDirty = true;
  }
  //撤销本次操作
  cancel($event: Event) {
    if ($event) {
      $event.stopPropagation();
    }
    this.writeFormByHttp();
  }

  //初始化表单
  initForm() {
    this.customUiFormGroup = this.fb.group({
      applicationTitle: [null, []],
      iconImageUrl: [null, []],
      logoImageUrl: [null, []],
      logoImageHeight: [null, []],
      platformMainColor: [null, []],
      platformTextMainColor: [null, []],
      platformButtonColor: [null, []],
      platformMenuColorActive: [null, []],
      platformMenuColorHover: [null, []],
      showNameVersion: [false, []],
      platformName: [env.appTitle, []],
      platformVersion: [env.tbVersion, []]
    });
    this.initData = this.customUiFormGroup.value;
    this.previousData = this.customUiFormGroup.value;
  }

  submit($event: Event) {
    if ($event) {
      $event.stopPropagation();
    }
    this.dashboardService.saveTenantUIInfo(this.customUiFormGroup.value as UIInfo).subscribe(res => {
    });
    this.store.dispatch(new ActionTenantUIChangeAll(this.customUiFormGroup.value));
    this.isDirty = false;
  }

  formatSlider(value: number) {
    return value + 'px';
  }

  // advancedCssClick() {
  //
  // }
}

custom-ui.component.scss

@media screen and (min-width: 1280px){
  .mat-card.settings-card {
    width: 60%;
  }
}
.mat-card.settings-card {
  margin: .5rem;
}
.mat-headline{
  font: 400 1.5rem/2rem Roboto,Helvetica Neue,sans-serif;
  letter-spacing: normal;
  margin: 0 0 1rem;
}
.mat-card-content{
  padding-top: 1rem;
}

custom-ui.component.tml
后续内容在语雀文章添加主题设置功能

你可能感兴趣的:(ThingsBoard,白标签,主题定制,UI设置)