ThingsBoard 前端项目轮播图部件开发

前言

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

本文介绍如何在 ThingsBoard 前端项目中开发轮播图部件。

产品需求

最近接到产品经理一个需求,在 TB 仪表板中添加轮播图部件,支持基本的轮播图管理和设置,可以点击跳转等。

我一想这简单啊,轮播图是一种比较常见前端组件,直接拿来引用改改就好,可大跌我眼镜(好吧,我不带眼镜)的是 TB 使用的前端 UI 框架 Material 竟然没有轮播图组件,是的!没有!我看两边是真的没有!实在是想不通难道老外不习惯轮播图这种方式么- -。

解决方案

没办法只得引用三方轮播图插件了,这里我踩了个坑- -,之前我开发导航菜单部件时引用了 NG-ZORRO 前端框架,正好在这个框架中找到了 Carousel 走马灯组件。但开发完后发现了个很奇怪的问题。从仪表板库列表进入仪表板轮播图不显示,改变下窗口大小就好了,效果如下:

这个问题我 Debug 了好久,最终只能是认定兼容问题,遂放弃 NG-ZORRO。轮播图最有名的插件莫过于 swiper,但遗憾我引入出现了问题,没有成功。最终在第三次尝试终于成功了,它就是大名鼎鼎的 Layui。

轮播图部件

高级设置

首先还是开发部件的高级设置功能。

首先我将轮播图部件定义为 Cards 部件库的一种,所以我在 ui-ngx\src\app\modules\home\components\widget\lib\settings\cards 目录下创建部件设置文件 carousel-widget-settings.component.htmlcarousel-widget-settings.component.tscarousel-widget-settings.component.scss

首先讲解 carousel-widget-settings.component.ts 文件的代码:

import { Component, OnInit } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { AbstractControl, FormArray, FormBuilder, FormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { UtilsService } from '@core/services/utils.service';

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

  /*FormGroup表单*/
  carouselWidgetSettingsForm: FormGroup;

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

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

  /*初始化数据字段*/
  protected defaultSettings(): WidgetSettings {
    return {
      carousels: [],
      autoPlaySpeed: 3000
    };
  }

  /*数据字段设置*/
  protected onSettingsSet(settings: WidgetSettings) {
    this.carouselWidgetSettingsForm = this.fb.group({
      carousels: this.prepareCarouselsFormArray(settings.carousels),
      autoPlaySpeed: [settings.autoPlaySpeed, []],
    });
  }


  protected doUpdateSettings(settingsForm: FormGroup, settings: WidgetSettings) {
    settingsForm.setControl('carousels', this.prepareCarouselsFormArray(settings.carousels), {emitEvent: false});
  }


  private prepareCarouselsFormArray(carousels: any | undefined): FormArray {
    const carouselsControls: Array<AbstractControl> = [];
    if (carousels) {
      carousels.forEach((item) => {
        console.log('item', item);
        /*处理数据*/
        const tempFormGroup = this.fb.group({
          name: item.name,
          navId: item.navId,
          expanded: item.expanded,
          carouselId: item.carouselId,
          imageUrl: item.imageUrl
        });
        carouselsControls.push(tempFormGroup);
      });
    }
    return this.fb.array(carouselsControls);
  }

  /*获取轮播图*/
  carouselsFormArray(): FormArray {
    return this.carouselWidgetSettingsForm.get('carousels') as FormArray;
  }

  public trackByCarouselControl(index: number, carouselControl: AbstractControl): any {
    return carouselControl;
  }

  /*删除轮播图*/
  public removeCarousel(index) {
    (this.carouselWidgetSettingsForm.get('carousels') as FormArray).removeAt(index);
  }

  /*添加轮播图*/
  public addCarousel() {
    const carouselsArray = this.carouselWidgetSettingsForm.get('carousels') as FormArray;
    const carouselGroup = this.fb.group({
      name: '',
      carouselId: 'carousel-' + this.utils.guid(),
      expanded: true,
      imageUrl: ''
    });
    carouselsArray.push(carouselGroup);
    console.log('carouselsArray', carouselsArray);
    this.carouselWidgetSettingsForm.updateValueAndValidity();
  }

  /*轮播图拖动排序*/
  carouselDrop(event: CdkDragDrop<string[]>) {
    const carouselsArray = this.carouselWidgetSettingsForm.get('carousels') as FormArray;
    const label = carouselsArray.at(event.previousIndex);
    carouselsArray.removeAt(event.previousIndex);
    carouselsArray.insert(event.currentIndex, label);
  }

}

请原谅我大幅粘贴- -,以上为完整代码,下面讲解代码核心内容。

首先在 defaultSettings() 在函数中声明两个重要变量,carousels: [] 用来存储轮播图片,autoPlaySpeed: 3000 用来设置轮播切换时间,默认 3 秒。

prepareCarouselsFormArray() 函数中对 carousels 数据进行格式处理,创建新的 FormGroup 实例以便在模板文件中获取。

新增轮播图 addCarousel() 函数,先获取 carousels 变量,插入新的轮播图 FormGroup,包含轮播图名称:name。轮播图 ID:carouselId,新增 id 是为了后面添加动作相关功能会使用到。展开标识:expanded,默认为 true 展开。轮播图链接:imageUrl,这里使用 TB 自带的组件,图片会以 base64 文本形式保存。

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

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

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

export class WidgetSettingsModule {
}

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

接下来是 carousel-widget-settings.component.html 文件:

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

  <fieldset class="fields-group" >
    <legend class="group-title" translate>widgets.carousel.carousel-item</legend>
    <div fxLayout="column">
      <div class="tb-control-list tb-drop-list" cdkDropList cdkDropListOrientation="vertical"
           (cdkDropListDropped)="carouselDrop($event)">
        <div cdkDrag class="tb-draggable" *ngFor="let carouselControl of carouselsFormArray().controls; trackBy: trackByCarouselControl; let $index = index; last as isLast;"
             fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
          <mat-expansion-panel class="carousel-item" [formGroup]="carouselControl" [expanded]="carouselControl.get('expanded').value">
            <mat-expansion-panel-header>
              <div fxFlex fxLayout="row" fxLayoutAlign="start center">
                <mat-panel-title>
                  <div fxLayout="row" fxFlex fxLayoutAlign="start center">
                    {{ carouselControl.get('name').value }}
                  </div>
                </mat-panel-title>
                <span fxFlex></span>
                <button mat-icon-button style="min-width: 40px;"
                        type="button"
                        (click)="removeCarousel($index)"
                        matTooltip="{{ 'action.remove' | translate }}"
                        matTooltipPosition="above">
                  <mat-icon>delete</mat-icon>
                </button>
              </div>
            </mat-expansion-panel-header>

            <ng-template matExpansionPanelContent>
              <div fxLayout="column" fxLayoutGap="0.5em">
                <mat-divider></mat-divider>
                <section class="tb-widget-settings" fxLayout="column">
                  <mat-form-field style="padding-bottom: 16px;">
                    <mat-label translate>widgets.carousel.name</mat-label>
                    <input required matInput formControlName="name">
                  </mat-form-field>
                  <tb-image-input required
                                  label="{{ 'widgets.carousel.imageUrl' | translate }}"
                                  formControlName="imageUrl">
                  </tb-image-input>
                </section>
              </div>
            </ng-template>
          </mat-expansion-panel>

        </div>
      </div>
      <div *ngIf="!carouselsFormArray().controls.length">
      <span translate fxLayoutAlign="center center"
            class="tb-prompt">widgets.carousel.no-carousels</span>
      </div>
      <div style="padding-top: 16px;">
        <button mat-raised-button color="primary"
                type="button"
                (click)="addCarousel()">
          <span translate>widgets.carousel.add-carousel</span>
        </button>
      </div>
    </div>
  </fieldset>

  <fieldset class="fields-group" >
    <legend class="group-title" translate>widgets.carousel.carousel-settings</legend>
    <div fxLayout="column">
      <!--切换时间(毫秒)-->
      <mat-form-field fxFlex>
        <mat-label translate>widgets.carousel.autoPlaySpeed</mat-label>
        <input matInput type="number" min="0" formControlName="autoPlaySpeed">
      </mat-form-field>
    </div>
  </fieldset>
</section>

高级设置 html 文件展示分为两个区域 <fieldset>,轮播图管理和设置。

通过 [formGroup]="carouselWidgetSettingsForm" 指令管理一个表单组。通过 formControlName="key" 指令将 FormGroup 中的 FormControl 按名称同步到一个表单控制元素。

因为轮播图需要支持拖动排序,所以使用 cdkDrag 指令完成。

使用 <tb-image-input> 内置的组件上传轮播图片。

*ngFor="let carouselControl of carouselsFormArray().controls; 遍历所有的轮播图,并通过 carouselControl.get(key).value 的方式获取轮播图的各属性。

最后是 carousel-widget-settings.component.scss 文件:

:host {
  display: block;
  .mat-expansion-panel {
    box-shadow: none;
    &.carousel-item {
      border: 1px groove rgba(0, 0, 0, .25);
    }
  }
}

样式主要是将拖动模块设置上边框,更加美观。

轮播图展示

轮播图展示我最终使用到的是 layui 插件,首先我们引入它。

ui-ngx/src/assets 目录下创建 layui 文件夹,将 layui 官网上下载的插件拖进来。

在入口文件 index.html 中通过标签的方式引入 css 和 js 文件。

<head>
  <link rel="stylesheet" href="./assets/layui/css/layui.css" />
  <script src="./assets/layui/layui.js"></script>
</head>

这是第一步,我们在创建轮播图展示文件,在 ui-ngx\src\app\modules\home\components\widget\lib\ 目录下创建 carousel.models.tscarousel-widget.component.htmlcarousel-widget.component.scsscarousel-widget.component.ts

carousel.models.ts 文件中声明导入导出。

import { NgModule } from '@angular/core';
import { CarouselWidgetComponent } from '@home/components/widget/lib/carousel-widget.component';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@app/shared/shared.module';

@NgModule({
  declarations:
    [
      CarouselWidgetComponent
    ],
  imports: [
    RouterModule,
    CommonModule,
    SharedModule
  ],
  exports: [
    CarouselWidgetComponent
  ]
})
export class CarouselModule {}

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

import { CarouselModule } from '@home/components/widget/lib/carousel.models';

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

export class WidgetComponentsModule {
}

然后是 carousel-widget.component.ts 文件:

import { ChangeDetectorRef, Component, Input, OnInit, AfterViewInit } 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';
declare const layui: any;

interface CarouselWidgetSettings {
  carousels: Array<any>;
  autoPlaySpeed: number;
}

@Component({
  selector: 'tb-carousel-widget',
  templateUrl: './carousel-widget.component.html',
  styleUrls: ['./carousel-widget.component.scss']
})
export class CarouselWidgetComponent extends PageComponent implements OnInit, AfterViewInit {

  settings: CarouselWidgetSettings;

  @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;
  }

  ngAfterViewInit() {
    layui.use(['carousel'], () => {
      const carousel = layui.carousel;
      // 常规轮播
      carousel.render({
        elem: '#my-carousel',
        arrow: 'hover',
        width: '100%',
        height: '100%',
        interval: this.settings.autoPlaySpeed,
      });
    });

    setTimeout(() => {
      /*添加轮播图动作事件*/
      this.ctx.customCarouselActions.forEach((action, index) => {
        const ele = document.querySelector('#' + action.descriptor.carouselId.substr(16));
        ele.addEventListener('click', () => {
          action.onAction(event);
        });
      });
    }, 50);
  }
}

因为 layui 不是 TypeScript 编写的,并且 TypeScript 可能无法识别 layui 的类型。所以我们使用 declare const layui: any这样的方式绕过。

声明选择器 tb-carousel-widget,这个一会创建新部件要用到。

所有的高级设置都在 this.settings 对象中,在 ngAfterViewInit() 页面加载完成后,进行轮播图渲染和点击事件绑定操作。

carousel.render({...}) 渲染轮播图,对应 idmy-carousel 的容器,arrow: 'hover' 设置轮播图前后箭头在鼠标悬浮后显示,轮播时间 interval 为高级设置中的 autoPlaySpeed 字段值。

后面的绑定事件需要通过 this.ctx.customCarouselActions 获取自定义事件,绑定到对应轮播图 id 上,这个在下文的轮播图添加动作中介绍。

carousel-widget.component.html 文件:

<div class="layui-carousel" id="my-carousel" lay-filter="my-carousel">
  <div carousel-item="">
    <div *ngFor="let item of settings.carousels">
      <img class="carousel-img" id="{{ item.carouselId }}" src="{{item.imageUrl}}" alt="">
    </div>
  </div>
</div>

根据 layui 轮播图写法,遍历所有轮播图数据 *ngFor="let item of settings.carousels"

carousel-widget.component.scss 文件:

:host {
  display: flex;
  width: 100%;
  height: 100%;
}

没什么好说的,将轮播图片全部铺满展示。

导入部件部

想要看到最终效果,我们需要先将轮播图部件添加到部件库中,登录系统管理员账号 sysadmin@thingsboard.org / sysadmin,登录系统管理员账号操作是因为添加后会默认显示为系统部件包。

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

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

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

添加好部件好,我们在仪表板中添加该部件。切换回 tenant@thingsboard.org / tenant 用户,仪表板中添加轮播图部件,添加轮播图图片。

最终效果如下:

添加动作

好了,难点来了,需求要求轮播图可以点击支持跳转,TB 部件内置支持只支部件顶部按钮添加跳转,但具体的每个内容点击跳转并不支持。

所以需要开发这部分内容,可以参考部件顶部按钮跳转功能。

首先在 widget-action-dialog.component.ts 部件添加动作窗口增加轮播图动作源:

constructor(...) {
    console.log('data', data, this.widgetSettings);
    // 轮播图部件动作源
    if (this.widgetSettings.carousels) {
      let pre = 0;
      this.widgetSettings.carousels.forEach((item) => {
        pre ++;
        // z- 目的是为了排序在headerButton 部件顶部按钮之后
        this.data.actionsData.actionSources[this.dealPreFix(pre, 'z-carousel') + '-' + item.carouselId] = {
          name: item.name,
          value: item.carouselId,
          multiple: true,
          hasShowCondition: true
        };
      });
    }
}

dealPreFix(pre, str): string {
    let preString = pre.toString();
    while (preString.length < 5) {
      preString = '0' + preString;
    }
    return str + preString;
  }

dealPreFix() 函数目的是为了使动作源排序正常,因为默认是按照字符串排序会比较奇怪。这样我们就可以在 actionSources 中添加所有的动作源。效果如下:

我们还需要改写下动作源添加后的列表名称显示,在 manage-widget-actions.component.ts 文件中新增:

ngOnInit(): void {
    // 轮播图部件动作源名称
    if (this.widgetSettings.carousels) {
      this.widgetSettings.carousels.forEach((item) => {
        this.widgetService.carouselIdTranslate[item.carouselId] = item.name;
      });
    }
    console.log('carouselIdTranslate', this.widgetService.carouselIdTranslate);
  }
}

actionSourceName(actionSourceId): string {
    if (actionSourceId.indexOf('carousel-') !== -1){
      return this.widgetService.carouselIdTranslate[actionSourceId.slice(16)];
    }else {
      return actionSourceId;
    }
  }

carouselIdTranslate 打印输出如下:

然后在 manage-widget-actions.component.html 模板文件中输出:

<div fxFlex class="table-container">
      <table mat-table [dataSource]="dataSource"
                 matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
        <ng-container matColumnDef="actionSourceName">
          <mat-header-cell *matHeaderCellDef mat-sort-header style="width: 20%"> {{ 'widget-config.action-source' | translate }} </mat-header-cell>
          <mat-cell *matCellDef="let action">
            <!--修改处-->
            {{ actionSourceName(action.actionSourceName) }}
          </mat-cell>
        </ng-container>
    </table>
</div>

动作列表显示如下:

具体的轮播图添加动作的逻辑在文件 widget.component.ts

/*轮播图动作*/
this.widgetContext.customCarouselActions = [];
const carouselActionsDescriptors = this.getCarouselActionDescriptors(this.widgetContext);
console.log('carouselActionsDescriptors', carouselActionsDescriptors);
carouselActionsDescriptors.forEach((descriptor) =>
{
  let useShowWidgetCarouselActionFunction = descriptor.useShowWidgetActionFunction || false;
  let showWidgetCarouselActionFunction: ShowWidgetCarouselActionFunction = null;
  if (useShowWidgetCarouselActionFunction && isNotEmptyStr(descriptor.showWidgetActionFunction)) {
    try {
      showWidgetCarouselActionFunction =
        new Function('widgetContext', 'data', descriptor.showWidgetActionFunction) as ShowWidgetCarouselActionFunction; // TODO
    } catch (e) {
      useShowWidgetCarouselActionFunction = false;
    }
  }
  const carouselAction: WidgetCarouselAction = {
    name: descriptor.name,
    displayName: descriptor.displayName,
    icon: descriptor.icon,
    descriptor,
    useShowWidgetCarouselActionFunction,
    showWidgetCarouselActionFunction,
    onAction: $event => {
      const entityInfo = this.getActiveEntityInfo();
      const entityId = entityInfo ? entityInfo.entityId : null;
      const entityName = entityInfo ? entityInfo.entityName : null;
      const entityLabel = entityInfo ? entityInfo.entityLabel : null;
      console.log('carouselAction', descriptor);
      this.handleWidgetAction($event, descriptor, entityId, entityName, null, entityLabel);
    }
  };
  this.widgetContext.customCarouselActions.push(carouselAction);
  console.log('this.widgetContext', this.widgetContext);
});

private getCarouselActionDescriptors(widgetContext): Array<WidgetActionDescriptor> {
    let result = [];
    console.log('widgetContext', widgetContext.widget.config.actions);
    const allActions = widgetContext.widget.config.actions;
    for (const key in allActions) {
      if (allActions.hasOwnProperty(key)) {
        // console.log(key, allActions[key]);
        // 轮播图动作
        if (key.indexOf('carousel-') !== -1 && allActions[key].length !== 0) {
          allActions[key].forEach((item, index) => {
            allActions[key][index].displayName = allActions[key][index].name;
            allActions[key][index].carouselId = key;
          });
          result.push(allActions[key][allActions[key].length - 1]);
        }
      }
    }
    if (!result) {
      result = [];
    }
    console.log('getCarouselActionDescriptors', result);
    return result;
  }

上述代码功能为将所有的轮播图动作 carouselAction 添加到自定义的轮播图动作数组 customCarouselActions 中,模仿原顶部动作 customHeaderActions 的写法。

最后在轮播图展示页面 carousel-widget.component.ts 中,通过绑定某一轮播图,点击触发其动作。

this.ctx.customCarouselActions.forEach((action, index) => {
        const ele = document.querySelector('#' + action.descriptor.carouselId.substr(16));
        ele.addEventListener('click', () => {
          action.onAction(event);
        });
      });

最终效果如下:

大功告成,Nice~

结语

本文展示了 99% 的实现源码,省略了部分中英翻译、变量声明等部分,大家可以自行补充。

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

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

你学“废”了么?

(完)


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

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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自动化测试、压力测试、持续集成

因为项目的原因&#xff0c;前段时间研究并使用了 SoapUI 测试工具进行自测开发的 api。下面将研究的成果展示给大家&#xff0c;希望对需要的人有所帮助。 SoapUI 是什么&#xff1f; SoapUI 是一个开源测试工具&#xff0c;通过 soap/http 来检查、调用、实现 Web Service …

python笔记(1)安装环境

1&#xff0c;官网下载自己电脑位数的安装包 https://www.python.org/downloads/windows/ install时勾选中add to path&#xff0c;把路径自动添加到环境变量 安装pycharm就不讲了 安装后选中自己的python安装包 file-> setting->project:yourprojectname ->pyt…

k8s-8 ingress

ExternalName类型 当集群外的资源往集群内迁移时&#xff0c;地址并不稳定&#xff0c;访问域名或者访问方式等会产生变化&#xff1b; 使用svc的方式来做可以保证不会改变&#xff1a;内部直接访问svc&#xff1b;外部会在dns上加上解析&#xff0c;以确保访问到外部地址。 …

CleanMyMac X2024(Mac优化清理工具)v4.14.5中文版

CleanMyMac X是一款颇受欢迎的专业清理软件&#xff0c;拥有十多项强大的功能&#xff0c;可以进行系统清理、清空废纸篓、清除大旧型文件、程序卸载、除恶意软件、系统维护等等&#xff0c;并且这款清理软件操作简易&#xff0c;非常好上手&#xff0c;特别适用于那些刚入手苹…

数字中台建设指南(大数据平台)

制定数字中台战略规划&#xff1a;制定符合企业实际情况的数字中台战略规划&#xff0c;明确建设目标、重点任务和时间表。确定数字中台架构&#xff1a;根据企业业务需求和特点&#xff0c;确定数字中台的架构&#xff0c;包括技术架构、应用架构和数据架构。搭建数字中台基础…

java设计模式学习之【享元模式】

文章目录 引言享元模式简介定义与用途实现方式 使用场景优势与劣势在Java中的应用享元模式在Spring中的应用画图示例代码地址 引言 想象一下&#xff0c;您正在开发一个游戏&#xff0c;游戏中有成千上万的树木和建筑。如果每个对象都独立存储它的所有数据&#xff0c;将会占用…

postman接口测试系列: 时间戳和加密

在使用postman进行接口测试的时候&#xff0c;对于有些接口字段需要时间戳加密&#xff0c;这个时候我们就遇到2个问题&#xff0c;其一是接口中的时间戳如何得到&#xff1f;其二就是对于现在常用的md5加密操作如何在postman中使用代码实现呢&#xff1f; 下面我们以一个具体的…

蚂蚁SEO实用的网络baidu蜘蛛有哪些

网络蜘蛛是一种用于从互联网上自动抓取信息的程序。它们根据给定的规则和指令&#xff0c;遍历网站上的页面&#xff0c;收集信息并将其存储在数据库中。网络蜘蛛在搜索引擎、数据挖掘、信息提取等领域有着广泛的应用。本文将介绍一种实用的网络蜘蛛&#xff0c;并探讨其实现原…

快速二维相位解包算法基于按照非连续路径进行可靠性排序

Miguel Arevallilo Herra ez, David R. Burton, Michael J. Lalor, and Munther A. Gdeisat 摘要&#xff1a; 据我们所知&#xff0c;我们描述了一种新的相位展开技术。已经提出了几种基于首先展开最可靠像素的算法。这些仅限于连续路径&#xff0c;并且在定义起始像素时会遇…

结合eNSP实验讲VLAN,让理论生动

目录 一、VLAN的简介 1、定义 2、产生的原因--解决传统以太网的问题 3、VLAN的作用 4、VLAN数据帧格式--插入VLAN标签 5、VLAN的种类 5.1静态VLAN--常用 5.1.1静态vlan的范围 5.2动态VLAN 6、VLAN的三种端口类型 6.1Access接口 6.2Trunk接口 6.3Hybrid接口 二、配置…

Nodejs 第二十三章(Markdown 转 html)

Markdown 转换html 是一个非常常见的需求 什么是 Markdown ? Markdown 是一种轻量级标记语言&#xff0c;它允许人们使用易读易写的纯文本格式编写文档。 我们需要用到三个库实现 EJS&#xff1a;一款强大的JavaScript模板引擎&#xff0c;它可以帮助我们在HTML中嵌入动态内…

linux中堡垒机

堡垒机 堡垒机概念目的 安装Jumpserver使用资产管理资产列表创建需要管理的服务器创建用户权限管理页面进行资产授权操作视频 应用管理应用管理页面创建需要管理的应用&#xff0c;这里用数据库mysql举例进入后点击创建资产管理创建登录应用所需的用户选择创建mysql关系型数据库…

IP地址在流量管理中的作用

随着互联网的快速发展&#xff0c;流量管理已成为各行业面临的重要问题。IP地址作为一种标识网络中设备的重要标识符&#xff0c;在流量管理中发挥着重要作用。本文将介绍IP地址在流量管理中的应用&#xff0c;以帮助读者更好地理解这一领域的发展。 一、IP地址的分类与标识 I…

【C++】输入输出流 ⑥ ( cout 标准输出流对象 | cout 常用 api 简介 | cout.put(char c) 函数 )

文章目录 一、cout 标准输出流对象1、cout 标准输出流对象简介2、cout 常用 api 简介 二、cout.put(char c) 函数1、cout.put(char c) 函数 简介2、代码示例 - cout.put(char c) 函数 一、cout 标准输出流对象 1、cout 标准输出流对象简介 cout 是 标准输出流 对象 , 是 ostrea…

聚焦本田XR-V和福特领睿:两大SUV综合实力对比,谁更胜一筹?

在当今的SUV市场中&#xff0c;家庭用户的选择变得越来越多样化。特别是对于那些追求时尚、功能性以及技术先进性的用户来说&#xff0c;选择正确的SUV显得尤为重要。本文将重点对比福特领睿和本田XR-V这两款SUV&#xff0c;探讨它们在各方面的表现&#xff0c;做一个综合实力的…

GPTs prompts灵感库:创意无限,专业级创作指南,打造吸睛之作的秘诀

GPTs prompts灵感库&#xff1a;创意无限&#xff0c;专业级创作指南&#xff0c;打造吸睛之作的秘诀 优质prompt展示 1.1 极简翻译 中英文转换 你是一个极简翻译工具&#xff0c;请在对话中遵循以下规则&#xff1a; - Prohibit repeating or paraphrasing any user instru…

Vue运用之input本地上传文件,实现传参file:(binary)

前言 功能场景是,实现列表的【批量导入】的效果,在Excel里维护好信息,本地上传好文件,再点击【确认】触动接口,将flie信息传值后端接口。 html代码 input的type设置为file,支持格式设置为仅支持Excel类型 <div class="btn-box"><div class=&quo…

HNU数据库大作业-世界杯比赛系统

前言 之前做的那个版本bug较多&#xff0c;后进行了大量优化。 此项目是一个前后端分离的项目&#xff0c;前端主要使用htmlcssjs搭建&#xff0c;使用的是layui框架 后端使用php语言&#xff0c;仅实现了简单的查询数据库功能&#xff0c;无法实现多并发查询等复杂情况 数…

LeetCode刷题--- 验证二叉搜索树

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 http://t.csdnimg.cn/ZxuNL个人专栏&#xff1a;力扣递归算法题 http://t.csdnimg.cn/ZxuNL 【C】 http://t.csdnimg.cn/c9twt 前言&#xff1a;这个专栏主要讲述递归递归、搜索与回溯算法&#x…

想学精MySQL,得先捋一捋高可用架构

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…