thinksboard 网络请求方式

html模块  deleteImage点击事件

<div class="tb-images tb-absolute-fill" [ngClass]="{'tb-dialog-mode': dialogMode, 'mat-padding': !dialogMode}">
  <div fxFlex fxLayout="column" class="tb-images-content" [ngClass]="{'tb-outlined-border': pageMode}">
    <mat-toolbar class="mat-mdc-table-toolbar"
                 fxLayout.lt-lg="column" fxLayoutAlign.lt-lg="start stretch"
                 [fxShow]="!textSearchMode && (mode === 'grid' || dataSource?.selection.isEmpty())" [ngClass.lt-lg]="{'multi-row': !isSysAdmin}">
      <div fxFlex fxLayout="row" fxLayoutAlign="start center">
        <div class="mat-toolbar-tools">
          <span fxHide fxShow.gt-sm class="tb-images-title" translate>image.gallery</span>
          <div class="tb-images-view-type-toolbar"
               fxLayout="row"
               fxLayoutAlign="start center">
            <div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'list'}">
              <button mat-icon-button
                      (click)="setMode('list')"
                      matTooltip="{{'image.list-mode' | translate }}"
                      matTooltipPosition="below">
                <mat-icon>view_list</mat-icon>
              </button>
            </div>
            <div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'grid'}">
              <button mat-icon-button
                      (click)="setMode('grid');"
                      matTooltip="{{'image.grid-mode' | translate }}"
                      matTooltipPosition="below">
                <tb-icon>mdi:view-grid</tb-icon>
              </button>
            </div>
          </div>
          <mat-slide-toggle *ngIf="!isSysAdmin"
                            fxHide fxShow.gt-md
                            [ngModel]="includeSystemImages"
                            (ngModelChange)="includeSystemImagesChanged($event)">{{ 'image.include-system-images' | translate }}李发林</mat-slide-toggle>
        </div>
        <section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap.gt-md="16px">
          <span fxFlex></span>
          <section fxLayout="row" fxLayoutAlign="start center">
            <button [disabled]="isLoading$ | async"
                    mat-icon-button
                    (click)="enterFilterMode()"
                    matTooltip="{{'action.search' | translate }}"
                    matTooltipPosition="above">
              <mat-icon>search</mat-icon>
            </button>
            <button [disabled]="isLoading$ | async"
                    mat-icon-button
                    (click)="updateData()"
                    matTooltip="{{'action.refresh' | translate }}"
                    matTooltipPosition="above">
              <mat-icon>refresh</mat-icon>
            </button>
            <button [disabled]="isLoading$ | async"
                    mat-icon-button
                    (click)="importImage()"
                    matTooltip="{{'image.import-image' | translate }}"
                    matTooltipPosition="above">
              <tb-icon>mdi:file-import</tb-icon>
            </button>
            <button fxHide fxShow.lt-lg
                    [disabled]="isLoading$ | async"
                    mat-icon-button
                    color="primary"
                    (click)="uploadImage()"
                    matTooltip="{{'image.upload-image' | translate }}"
                    matTooltipPosition="above">
              <mat-icon>add</mat-icon>
            </button>
          </section>
          <button fxHide fxShow.gt-md
                  [disabled]="isLoading$ | async"
                  mat-flat-button
                  color="primary"
                  (click)="uploadImage()">
            {{ 'image.upload-image' | translate }}
          </button>
        </section>
      </div>
      <mat-slide-toggle *ngIf="!isSysAdmin" fxHide fxShow.lt-lg
                        [ngModel]="includeSystemImages"
                        (ngModelChange)="includeSystemImagesChanged($event)">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
    </mat-toolbar>
    <mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="textSearchMode && (mode === 'grid' || dataSource?.selection.isEmpty())">
      <div class="mat-toolbar-tools">
        <button mat-icon-button
                matTooltip="{{ 'image.search' | translate }}"
                matTooltipPosition="above">
          <mat-icon>search</mat-icon>
        </button>
        <mat-form-field fxFlex>
          <mat-label>&nbsp;</mat-label>
          <input #searchInput matInput
                 [formControl]="textSearch"
                 placeholder="{{ 'image.search' | translate }}"/>
        </mat-form-field>
        <button mat-icon-button (click)="exitFilterMode()"
                matTooltip="{{ 'action.close' | translate }}"
                matTooltipPosition="above">
          <mat-icon>close</mat-icon>
        </button>
      </div>
    </mat-toolbar>
    <mat-toolbar class="mat-mdc-table-toolbar" color="primary" [fxShow]="mode === 'list' && !dataSource?.selection.isEmpty()">
      <div class="mat-toolbar-tools">
        <span>
          {{ translate.get('image.selected-images', {count: dataSource?.selection.selected.length}) | async }}
        </span>
        <span fxFlex></span>
        <button mat-icon-button [disabled]="isLoading$ | async"
                matTooltip="{{ 'action.delete' | translate }}"
                matTooltipPosition="above"
                (click)="deleteImages($event)">
          <mat-icon>delete</mat-icon>
        </button>
      </div>
    </mat-toolbar>
    <div fxFlex *ngIf="mode === 'list'" fxLayout="column">
      <div fxFlex class="table-container">
        <table mat-table [dataSource]="dataSource" [trackBy]="trackByEntity"
               matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
          <ng-container matColumnDef="select" sticky>
            <mat-header-cell *matHeaderCellDef style="width: 30px;">
              <mat-checkbox (change)="$event ? dataSource.masterToggle() : null"
                            [checked]="dataSource.selection.hasValue() && (dataSource.isAllSelected() | async)"
                            [indeterminate]="dataSource.selection.hasValue() && (dataSource.isAllSelected() | async) === false">
              </mat-checkbox>
            </mat-header-cell>
            <mat-cell *matCellDef="let image">
              <mat-checkbox (click)="$event.stopPropagation()"
                            [fxShow]="deleteEnabled(image)"
                            (change)="$event ? dataSource.selection.toggle(image) : null"
                            [checked]="dataSource.selection.isSelected(image)">
              </mat-checkbox>
            </mat-cell>
          </ng-container>
          <ng-container matColumnDef="preview">
            <mat-header-cell *matHeaderCellDef style="width: 50px; min-width: 50px;"></mat-header-cell>
            <mat-cell *matCellDef="let image">
              <div class="tb-image-preview-cell">
                <img class="tb-image-preview" [src]="image.link | image: {preview: true} | async" alt="{{ image.title }}">
              </div>
            </mat-cell>
          </ng-container>
          <ng-container matColumnDef="title">
            <mat-header-cell *matHeaderCellDef mat-sort-header style="width: 100%;"> {{ 'image.name' | translate }} </mat-header-cell>
            <mat-cell *matCellDef="let image">
              {{ image.title }}
            </mat-cell>
          </ng-container>
          <ng-container matColumnDef="createdTime">
            <mat-header-cell *matHeaderCellDef mat-sort-header style="width: 160px; min-width: 160px;"> {{ 'image.created-time' | translate }} </mat-header-cell>
            <mat-cell *matCellDef="let image">
              {{ image.createdTime | date:'yyyy-MM-dd HH:mm:ss' }}
            </mat-cell>
          </ng-container>
          <ng-container matColumnDef="resolution">
            <mat-header-cell *matHeaderCellDef style="width: 80px; min-width: 80px;"> {{ 'image.resolution' | translate }} </mat-header-cell>
            <mat-cell *matCellDef="let image">
              {{ image.descriptor.width }}x{{ image.descriptor.height }}
            </mat-cell>
          </ng-container>
          <ng-container matColumnDef="size">
            <mat-header-cell *matHeaderCellDef style="width: 80px; min-width: 80px;"> {{ 'image.size' | translate }} </mat-header-cell>
            <mat-cell *matCellDef="let image">
              {{ image.descriptor.size | fileSize }}
            </mat-cell>
          </ng-container>
          <ng-container matColumnDef="system">
            <mat-header-cell *matHeaderCellDef style="width: 60px; min-width: 60px;"> {{ 'image.system' | translate }} </mat-header-cell>
            <mat-cell *matCellDef="let image">
              <mat-icon class="material-icons mat-icon">{{isSystem(image) ? 'check_box' : 'check_box_outline_blank'}}</mat-icon>
            </mat-cell>
          </ng-container>
          <ng-container matColumnDef="actions" stickyEnd>
            <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: '240px', maxWidth: '240px', width: '240px' }">
            </mat-header-cell>
            <mat-cell *matCellDef="let image" [ngStyle.gt-md]="{ minWidth: '240px', maxWidth: '240px', width: '240px' }">
              <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
                <button mat-icon-button [disabled]="isLoading$ | async"
                        matTooltip="{{ 'image.download-image' | translate }}"
                        matTooltipPosition="above"
                        (click)="downloadImage($event, image)">
                  <mat-icon>file_download</mat-icon>
                </button>
                <button mat-icon-button [disabled]="isLoading$ | async"
                        matTooltip="{{ 'image.export-image' | translate }}"
                        matTooltipPosition="above"
                        (click)="exportImage($event, image)">
                  <tb-icon>mdi:file-export</tb-icon>
                </button>
                <button mat-icon-button [disabled]="isLoading$ | async"
                        matTooltip="{{ 'image.embed-image' | translate }}"
                        matTooltipPosition="above"
                        (click)="embedImage($event, image)">
                  <mat-icon>code</mat-icon>
                </button>
                <button mat-icon-button [disabled]="isLoading$ | async"
                        matTooltip="{{ (readonly(image) ? 'image.image-details' : 'image.edit-image') | translate }}"
                        matTooltipPosition="above"
                        (click)="editImage($event, image)">
                  <mat-icon>edit</mat-icon>
                </button>
                <button mat-icon-button [disabled]="(isLoading$ | async) || !deleteEnabled(image)"
                        matTooltip="{{ 'image.delete-image' | translate }}"
                        matTooltipPosition="above"
                        (click)="deleteImage($event, image)">
                  <mat-icon>delete</mat-icon>
                </button>
              </div>
              <div fxHide fxShow.lt-lg>
                <button mat-icon-button
                        (click)="$event.stopPropagation()"
                        [matMenuTriggerFor]="cellActionsMenu">
                  <mat-icon class="material-icons">more_vert</mat-icon>
                </button>
                <mat-menu #cellActionsMenu="matMenu" xPosition="before">
                  <button mat-menu-item
                          [disabled]="isLoading$ | async"
                          (click)="downloadImage($event, image)">
                    <mat-icon>file_download</mat-icon>
                    <span translate>image.download-image</span>
                  </button>
                  <button mat-menu-item
                          [disabled]="isLoading$ | async"
                          (click)="exportImage($event, image)">
                    <tb-icon matMenuItemIcon>mdi:file-export</tb-icon>
                    <span translate>image.export-image</span>
                  </button>
                  <button mat-menu-item
                          [disabled]="isLoading$ | async"
                          (click)="embedImage($event, image)">
                    <mat-icon>code</mat-icon>
                    <span>{{ 'image.embed-image' | translate }}</span>
                  </button>
                  <button mat-menu-item
                          [disabled]="isLoading$ | async"
                          (click)="editImage($event, image)">
                    <mat-icon>edit</mat-icon>
                    <span>{{ (readonly(image) ? 'image.image-details' : 'image.edit-image') | translate }}</span>
                  </button>
                  <button mat-menu-item
                          [disabled]="isLoading$ | async"
                          [fxShow]="deleteEnabled(image)"
                          (click)="deleteImage($event, image)">
                    <mat-icon>delete</mat-icon>
                    <span translate>image.delete-image</span>
                  </button>
                </mat-menu>
              </div>
            </mat-cell>
          </ng-container>
          <ng-container matColumnDef="imageSelect" stickyEnd>
            <mat-header-cell *matHeaderCellDef></mat-header-cell>
            <mat-cell *matCellDef="let image">
              <button mat-stroked-button
                      color="primary"
                      (click)="selectImage($event, image)">{{ 'action.select' | translate }}</button>
            </mat-cell>
          </ng-container>
          <mat-header-row [ngClass]="{'mat-row-select': true}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
          <mat-row [fxShow]="!dataSource.dataLoading"
                   [ngClass]="{'mat-row-select': true,
                               'mat-selected': dataSource.selection.isSelected(image)}"
                   *matRowDef="let image; columns: displayedColumns;" (click)="rowClick($event, image)"></mat-row>
        </table>
        <ng-container *ngIf="(dataSource.isEmpty() | async) && !dataSource.dataLoading">
          <ng-container *ngTemplateOutlet="noImages"></ng-container>
        </ng-container>
        <span [fxShow]="dataSource.dataLoading"
              fxLayoutAlign="center center"
              class="no-data-found">{{ 'common.loading' | translate }}</span>
      </div>
      <mat-divider></mat-divider>
      <mat-paginator [length]="dataSource.total() | async"
                     [pageIndex]="pageLink.page"
                     [pageSize]="pageLink.pageSize"
                     [pageSizeOptions]="pageSizeOptions"
                     [hidePageSize]="hidePageSize"
                     showFirstLastButtons></mat-paginator>
    </div>
    <div *ngIf="mode === 'grid'" fxFlex [ngClass]="{'mat-padding': !dialogMode}" fxLayout="column">
      <tb-scroll-grid fxFlex
        [columns]="gridColumns"
        [itemSize]="gridImagesItemSizeStrategy"
        [fetchFunction]="gridImagesFetchFunction"
        [filter]="gridImagesFilter"
        [itemCard]="imageCard"
        [loadingCell]="imageLoadingCard"
        [dataLoading]="loadingImages"
        [noData]="noImages">
      </tb-scroll-grid>
    </div>
  </div>
</div>
<ng-template #loadingImages>
  <div fxLayout="column"
       fxLayoutAlign="center center" class="tb-absolute-fill">
        <span>
          {{ 'common.loading' | translate }}
        </span>
  </div>
</ng-template>
<ng-template #noImages>
  <div class="tb-no-images">
    <div class="tb-no-data-bg"></div>
    <div class="tb-no-data-text" translate>image.no-images</div>
  </div>
</ng-template>
<ng-template #imageCard let-item="item" let-itemIndex="itemIndex">
  <div class="tb-image-card tb-primary-fill">
    <div class="tb-image-card-overlay">
      <div *ngIf="!selectionMode" class="tb-image-card-overlay-buttons">
        <button mat-icon-button [disabled]="isLoading$ | async"
                matTooltip="{{ 'image.download-image' | translate }}"
                matTooltipPosition="above"
                (click)="downloadImage($event, item)">
          <mat-icon>file_download</mat-icon>
        </button>
        <button mat-icon-button [disabled]="isLoading$ | async"
                matTooltip="{{ 'image.export-image' | translate }}"
                matTooltipPosition="above"
                (click)="exportImage($event, item)">
          <tb-icon>mdi:file-export</tb-icon>
        </button>
        <button mat-icon-button [disabled]="isLoading$ | async"
                matTooltip="{{ 'image.embed-image' | translate }}"
                matTooltipPosition="above"
                (click)="embedImage($event, item, itemIndex)">
          <mat-icon>code</mat-icon>
        </button>
        <button *ngIf="deleteEnabled(item)"
                mat-icon-button [disabled]="(isLoading$ | async)"
                matTooltip="{{ 'image.delete-image' | translate }}"
                matTooltipPosition="above"
                (click)="deleteImage($event, item, itemIndex)">
          <mat-icon>delete</mat-icon>
        </button>
      </div>
    </div>
    <div class="tb-image-preview-container">
      <div class="tb-image-preview-overlay" (click)="selectionMode && selectImage($event, item)">
        <button
          *ngIf="!selectionMode"
          mat-flat-button
          color="primary"
          (click)="editImage($event, item, itemIndex)">
          {{ (readonly(item) ? 'image.image-details' : 'image.edit-image') | translate }}
        </button>
        <button
          *ngIf="selectionMode"
          mat-flat-button
          color="primary"
          (click)="selectImage($event, item)">{{ 'action.select' | translate }}
        </button>
      </div>
      <div class="tb-image-preview-spacer"></div>
      <img class="tb-image-preview" [src]="item.link | image: {preview: true} | async" alt="{{ item.title }}">
    </div>
    <div class="tb-image-details">
      <div class="tb-image-title-container">
        <div class="tb-image-title">
          {{ item.title }}
        </div>
        <div *ngIf="isSystem(item)" class="tb-image-sys">sys</div>
      </div>
      <div class="tb-image-info-container">
        <div>{{ item.descriptor.width }}x{{ item.descriptor.height }}</div>
        <mat-divider vertical></mat-divider>
        <div>{{ item.descriptor.size | fileSize }}</div>
      </div>
    </div>
  </div>
</ng-template>
<ng-template #imageLoadingCard>
  <div class="tb-image-card tb-primary-fill loading-cell">
    <div class="tb-image-preview-container">
      <div class="tb-image-preview-spacer"></div>
      <div class="tb-image-preview"></div>
    </div>
    <div class="tb-image-details"></div>
  </div>
</ng-template>

找到deleteImage方法 调用imageService服务 image.service.ts文件下的deleteImage方法

///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///

import {
  ImageResourceInfo,
  ImageResourceInfoWithReferences,
  imageResourceType,
  toImageDeleteResult
} from '@shared/models/resource.models';
import { forkJoin, merge, Observable, of, Subject, Subscription } from 'rxjs';
import { ImageService } from '@core/http/image.service';
import { TranslateService } from '@ngx-translate/core';
import { PageLink, PageQueryParam } from '@shared/models/page/page-link';
import { catchError, debounceTime, distinctUntilChanged, map, skip, takeUntil } from 'rxjs/operators';
import {
  AfterViewInit,
  ChangeDetectorRef,
  Component,
  ElementRef, EventEmitter, HostBinding,
  Input,
  OnDestroy,
  OnInit, Output,
  ViewChild,
  ViewEncapsulation
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort, SortDirection } from '@angular/material/sort';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { DialogService } from '@core/services/dialog.service';
import { FormBuilder } from '@angular/forms';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { ResizeObserver } from '@juggle/resize-observer';
import { hidePageSizePixelValue } from '@shared/models/constants';
import { coerceBoolean } from '@shared/decorators/coercion';
import { ActivatedRoute, QueryParamsHandling, Router } from '@angular/router';
import { isEqual, isNotEmptyStr, parseHttpErrorMessage } from '@core/utils';
import { BaseData, HasId } from '@shared/models/base-data';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
import { GridEntitiesFetchFunction, ScrollGridColumns } from '@shared/components/grid/scroll-grid-datasource';
import { ItemSizeStrategy, ScrollGridComponent } from '@shared/components/grid/scroll-grid.component';
import { MatDialog } from '@angular/material/dialog';
import {
  UploadImageDialogComponent,
  UploadImageDialogData
} from '@shared/components/image/upload-image-dialog.component';
import { ImageDialogComponent, ImageDialogData } from '@shared/components/image/image-dialog.component';
import { ImportExportService } from '@shared/import-export/import-export.service';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import {
  ImagesInUseDialogComponent,
  ImagesInUseDialogData
} from '@shared/components/image/images-in-use-dialog.component';
import { ImagesDatasource } from '@shared/components/image/images-datasource';
import { EmbedImageDialogComponent, EmbedImageDialogData } from '@shared/components/image/embed-image-dialog.component';

interface GridImagesFilter {
  search: string;
  includeSystemImages: boolean;
}

const pageGridColumns: ScrollGridColumns = {
  columns: 2,
  breakpoints: {
    'screen and (min-width: 2320px)': 10,
    'screen and (min-width: 2000px)': 8,
    'gt-lg': 7,
    'screen and (min-width: 1600px)': 6,
    'gt-md': 5,
    'screen and (min-width: 1120px)': 4,
    'gt-xs': 3
  }
};

const dialogGridColumns: ScrollGridColumns = {
  columns: 2,
  breakpoints: {
    'gt-md': 4,
    'gt-xs': 3
  }
};

@Component({
  selector: 'tb-image-gallery',
  templateUrl: './image-gallery.component.html',
  styleUrls: ['./image-gallery.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class ImageGalleryComponent extends PageComponent implements OnInit, OnDestroy, AfterViewInit {

  @HostBinding('style.display')
  private display = 'block';

  @HostBinding('style.width')
  private width = '100%';

  @HostBinding('style.height')
  private height = '100%';

  @Input()
  @coerceBoolean()
  pageMode = true;

  @Input()
  @coerceBoolean()
  dialogMode = false;

  @Input()
  mode: 'list' | 'grid' = 'list';

  @Input()
  @coerceBoolean()
  selectionMode = false;

  @Output()
  imageSelected = new EventEmitter<ImageResourceInfo>();

  @ViewChild('searchInput') searchInputField: ElementRef;

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  @ViewChild(ScrollGridComponent) gridComponent: ScrollGridComponent<ImageResourceInfo, string>;

  defaultPageSize = 10;
  defaultSortOrder: SortOrder = { property: 'createdTime', direction: Direction.DESC };
  hidePageSize = false;

  displayedColumns: string[];
  pageSizeOptions: number[];
  pageLink: PageLink;

  textSearchMode = false;

  dataSource: ImagesDatasource;

  textSearch = this.fb.control('', {nonNullable: true});
  includeSystemImages = false;

  gridColumns: ScrollGridColumns;

  gridImagesFetchFunction: GridEntitiesFetchFunction<ImageResourceInfo, GridImagesFilter>;
  gridImagesFilter: GridImagesFilter = {
    search: '',
    includeSystemImages: false
  };

  gridImagesItemSizeStrategy: ItemSizeStrategy = {
    defaultItemSize: 200,
    itemSizeFunction: itemWidth => itemWidth + 72
  };

  authUser = getCurrentAuthUser(this.store);

  private updateDataSubscription: Subscription;

  private widgetResize$: ResizeObserver;
  private destroy$ = new Subject<void>();
  private destroyListMode$: Subject<void>;

  constructor(protected store: Store<AppState>,
              private route: ActivatedRoute,
              private router: Router,
              private dialog: MatDialog,
              public translate: TranslateService,
              private imageService: ImageService,
              private dialogService: DialogService,
              private importExportService: ImportExportService,
              private elementRef: ElementRef,
              private cd: ChangeDetectorRef,
              private fb: FormBuilder) {
    super(store);

    this.gridImagesFetchFunction = (pageSize, page, filter) => {
      const pageLink = new PageLink(pageSize, page, filter.search, {
        property: 'createdTime',
        direction: Direction.DESC
      });
      return this.imageService.getImages(pageLink, filter.includeSystemImages);
    };
  }

  ngOnInit(): void {
    this.gridColumns = this.dialogMode ? dialogGridColumns : pageGridColumns;
    this.displayedColumns = this.computeDisplayedColumns();
    let sortOrder: SortOrder = this.defaultSortOrder;
    this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
    const routerQueryParams: PageQueryParam = this.route.snapshot.queryParams;
    if (this.pageMode) {
      if (routerQueryParams.hasOwnProperty('direction')
        || routerQueryParams.hasOwnProperty('property')) {
        sortOrder = {
          property: routerQueryParams?.property || this.defaultSortOrder.property,
          direction: routerQueryParams?.direction || this.defaultSortOrder.direction
        };
      }
    }
    this.pageLink = new PageLink(this.defaultPageSize, 0, null, sortOrder);
    if (this.pageMode) {
      if (routerQueryParams.hasOwnProperty('page')) {
        this.pageLink.page = Number(routerQueryParams.page);
      }
      if (routerQueryParams.hasOwnProperty('pageSize')) {
        this.pageLink.pageSize = Number(routerQueryParams.pageSize);
      }
      const textSearchParam = routerQueryParams.textSearch;
      if (isNotEmptyStr(textSearchParam)) {
        const decodedTextSearch = decodeURI(textSearchParam);
        this.textSearchMode = true;
        this.pageLink.textSearch = decodedTextSearch.trim();
        this.textSearch.setValue(decodedTextSearch, {emitEvent: false});
      }
    }
    if (this.mode === 'list') {
      this.dataSource = new ImagesDatasource(this.imageService, null,
          entity => this.deleteEnabled(entity));
    }
  }

  ngOnDestroy(): void {
    if (this.widgetResize$) {
      this.widgetResize$.disconnect();
    }
    if (this.destroyListMode$) {
      this.destroyListMode$.next();
      this.destroyListMode$.complete();
    }
    this.destroy$.next();
    this.destroy$.complete();
  }

  ngAfterViewInit() {
    this.textSearch.valueChanges.pipe(
      debounceTime(150),
      distinctUntilChanged((prev, current) =>
        ((this.mode === 'list' ? this.pageLink.textSearch : this.gridImagesFilter.search) ?? '') === current.trim()),
      takeUntil(this.destroy$)
    ).subscribe(value => {
      if (this.mode === 'list') {
        if (this.pageMode) {
          const queryParams: PageQueryParam = {
            textSearch: isNotEmptyStr(value) ? encodeURI(value) : null,
            page: null
          };
          this.updatedRouterParamsAndData(queryParams);
        } else {
          this.pageLink.textSearch = isNotEmptyStr(value) ? value.trim() : null;
          this.paginator.pageIndex = 0;
          this.updateData();
        }
      } else {
        this.gridImagesFilter = {
          search: isNotEmptyStr(value) ? value.trim() : null,
          includeSystemImages: this.includeSystemImages
        };
        this.cd.markForCheck();
      }
    });
    this.updateMode();
  }

  public includeSystemImagesChanged(value: boolean) {
    this.includeSystemImages = value;
    this.displayedColumns = this.computeDisplayedColumns();
    this.gridImagesFilter = {
      search: this.gridImagesFilter.search,
      includeSystemImages: this.includeSystemImages
    };
    if (this.mode === 'list') {
      this.paginator.pageIndex = 0;
      this.updateData();
    } else {
      this.cd.markForCheck();
    }
  }

  public setMode(targetMode: 'list' | 'grid') {
    if (this.mode !== targetMode) {
      if (this.widgetResize$) {
        this.widgetResize$.disconnect();
        this.widgetResize$ = null;
      }
      if (this.destroyListMode$) {
        this.destroyListMode$.next();
        this.destroyListMode$.complete();
        this.destroyListMode$ = null;
      }
      this.mode = targetMode;
      if (this.mode === 'list') {
        this.dataSource = new ImagesDatasource(this.imageService, null,
          entity => this.deleteEnabled(entity));
      }
      setTimeout(() => {
        this.updateMode();
      });
    }
  }

  public get isSysAdmin(): boolean {
    return this.authUser.authority === Authority.SYS_ADMIN;
  }

  private computeDisplayedColumns(): string[] {
    let columns: string[];
    if (this.selectionMode) {
      columns = ['preview', 'title'];
      if (!this.isSysAdmin && this.includeSystemImages) {
        columns.push('system');
      }
      columns.push('imageSelect');
    } else {
      columns = ['select', 'preview', 'title', 'createdTime', 'resolution', 'size'];
      if (!this.isSysAdmin && this.includeSystemImages) {
        columns.push('system');
      }
      columns.push('actions');
    }
    return columns;
  }

  private updateMode() {
    if (this.mode === 'list') {
      this.initListMode();
    } else {
      this.initGridMode();
    }
  }

  private initListMode() {
    this.destroyListMode$ = new Subject<void>();
    this.widgetResize$ = new ResizeObserver(() => {
      const showHidePageSize = this.elementRef.nativeElement.offsetWidth < hidePageSizePixelValue;
      if (showHidePageSize !== this.hidePageSize) {
        this.hidePageSize = showHidePageSize;
        this.cd.markForCheck();
      }
    });
    this.widgetResize$.observe(this.elementRef.nativeElement);
    if (this.pageMode) {
      this.route.queryParams.pipe(
        skip(1),
        takeUntil(this.destroyListMode$)
      ).subscribe((params: PageQueryParam) => {
        this.paginator.pageIndex = Number(params.page) || 0;
        this.paginator.pageSize = Number(params.pageSize) || this.defaultPageSize;
        this.sort.active = params.property || this.defaultSortOrder.property;
        this.sort.direction = (params.direction || this.defaultSortOrder.direction).toLowerCase() as SortDirection;
        const textSearchParam = params.textSearch;
        if (isNotEmptyStr(textSearchParam)) {
          const decodedTextSearch = decodeURI(textSearchParam);
          this.textSearchMode = true;
          this.pageLink.textSearch = decodedTextSearch.trim();
          this.textSearch.setValue(decodedTextSearch, {emitEvent: false});
        } else {
          this.pageLink.textSearch = null;
          this.textSearch.reset('', {emitEvent: false});
        }
        this.updateData();
      });
    }
    this.updatePaginationSubscriptions();
    this.updateData();
  }

  private initGridMode() {

  }

  private updatePaginationSubscriptions() {
    if (this.updateDataSubscription) {
      this.updateDataSubscription.unsubscribe();
      this.updateDataSubscription = null;
    }
    const sortSubscription$: Observable<object> = this.sort.sortChange.asObservable().pipe(
      map((data) => {
        const direction = data.direction.toUpperCase();
        const queryParams: PageQueryParam = {
          direction: (this.defaultSortOrder.direction === direction ? null : direction) as Direction,
          property: this.defaultSortOrder.property === data.active ? null : data.active
        };
        queryParams.page = null;
        this.paginator.pageIndex = 0;
        return queryParams;
      })
    );
    const paginatorSubscription$ = this.paginator.page.asObservable().pipe(
        map((data) => ({
          page: data.pageIndex === 0 ? null : data.pageIndex,
          pageSize: data.pageSize === this.defaultPageSize ? null : data.pageSize
        }))
      );
    this.updateDataSubscription = (merge(sortSubscription$, paginatorSubscription$) as Observable<PageQueryParam>).pipe(
      takeUntil(this.destroyListMode$)
    ).subscribe(queryParams => this.updatedRouterParamsAndData(queryParams));
  }

  clearSelection() {
    this.dataSource.selection.clear();
    this.cd.detectChanges();
  }

  updateData() {
    if (this.mode === 'list') {
      this.pageLink.page = this.paginator.pageIndex;
      this.pageLink.pageSize = this.paginator.pageSize;
      if (this.sort.active) {
        this.pageLink.sortOrder = {
          property: this.sort.active,
          direction: Direction[this.sort.direction.toUpperCase()]
        };
      } else {
        this.pageLink.sortOrder = null;
      }
      console.log('点击删除按钮,李发林,获取新的数据',this.pageLink)
      this.dataSource.loadEntities(this.pageLink, this.includeSystemImages);
    } else {
      this.gridComponent.update();
    }
  }

  private imageUpdated(image: ImageResourceInfo, index = -1) {
    if (this.mode === 'list') {
      this.updateData();
    } else {
      this.gridComponent.updateItem(index, image);
    }
  }

  private imageDeleted(index = -1) {
    console.log('点击删除按钮,李发林,删除成功,刷新数据,判断mode',this.mode)
    if (this.mode === 'list') {
      this.updateData();
    } else {
      this.gridComponent.deleteItem(index);
    }
  }

  enterFilterMode() {
    this.textSearchMode = true;
    setTimeout(() => {
      this.searchInputField.nativeElement.focus();
      this.searchInputField.nativeElement.setSelectionRange(0, 0);
    }, 10);
  }

  exitFilterMode() {
    this.textSearchMode = false;
    this.textSearch.reset();
  }

  trackByEntity(index: number, entity: BaseData<HasId>) {
    return entity;
  }

  isSystem(image?: ImageResourceInfo): boolean {
    return !this.isSysAdmin && image?.tenantId?.id === NULL_UUID;
  }

  readonly(image?: ImageResourceInfo): boolean {
    return this.authUser.authority !== Authority.SYS_ADMIN && this.isSystem(image);
  }

  deleteEnabled(image?: ImageResourceInfo): boolean {
    return this.authUser.authority === Authority.SYS_ADMIN || !this.isSystem(image);
  }

  deleteImage($event: Event, image: ImageResourceInfo, itemIndex = -1) {
    if ($event) {
      $event.stopPropagation();
    }
    const title = this.translate.instant('image.delete-image-title', {imageTitle: image.title});
    const content = this.translate.instant('image.delete-image-text');
    this.dialogService.confirm(title, content,
      this.translate.instant('action.no'),
      this.translate.instant('action.yes')).subscribe((result) => {
      if (result) {
        console.log('点击删除按钮,李发林')
        this.imageService.deleteImage(imageResourceType(image), image.resourceKey, false, {ignoreErrors: true}).pipe(
          map(() => toImageDeleteResult(image)),
          catchError((err) => of(toImageDeleteResult(image, err)))
        ).subscribe(
          (deleteResult) => {
            console.log('点击删除按钮,李发林,有返回值',deleteResult)
            if (deleteResult.success) {
              this.imageDeleted(itemIndex);
            } else if (deleteResult.imageIsReferencedError) {
              this.dialog.open<ImagesInUseDialogComponent, ImagesInUseDialogData,
                ImageResourceInfo[]>(ImagesInUseDialogComponent, {
                disableClose: true,
                panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
                data: {
                  multiple: false,
                  images: [{...image, ...{references: deleteResult.references}}]
                }
              }).afterClosed().subscribe((images) => {
                if (images) {
                  this.imageService.deleteImage(imageResourceType(image), image.resourceKey, true).subscribe(
                    () => {
                      this.imageDeleted(itemIndex);
                    }
                  );
                }
              });
            } else {
              const errorMessageWithTimeout = parseHttpErrorMessage(deleteResult.error, this.translate);
              setTimeout(() => {
                this.store.dispatch(new ActionNotificationShow({message: errorMessageWithTimeout.message, type: 'error'}));
              }, errorMessageWithTimeout.timeout);
            }
        });
      }
    });
  }

  deleteImages($event: Event) {
    if ($event) {
      $event.stopPropagation();
    }
    const selectedImages = this.dataSource.selection.selected;
    if (selectedImages && selectedImages.length) {
      const title = this.translate.instant('image.delete-images-title', {count: selectedImages.length});
      const content = this.translate.instant('image.delete-images-text');
      this.dialogService.confirm(title, content,
        this.translate.instant('action.no'),
        this.translate.instant('action.yes')).subscribe((result) => {
        if (result) {
          const tasks = selectedImages.map((image) =>
            this.imageService.deleteImage(imageResourceType(image), image.resourceKey, false, {ignoreErrors: true}).pipe(
              map(() => toImageDeleteResult(image)),
              catchError((err) => of(toImageDeleteResult(image, err)))
            )
          );
          forkJoin(tasks).subscribe(
            (deleteResults) => {
              const anySuccess = deleteResults.some(res => res.success);
              const referenceErrors = deleteResults.filter(res => res.imageIsReferencedError);
              const otherError = deleteResults.find(res => !res.success);
              if (anySuccess) {
                this.updateData();
              }
              if (referenceErrors?.length) {
                const imagesWithReferences: ImageResourceInfoWithReferences[] =
                  referenceErrors.map(ref => ({...ref.image, ...{references: ref.references}}));
                this.dialog.open<ImagesInUseDialogComponent, ImagesInUseDialogData,
                  ImageResourceInfo[]>(ImagesInUseDialogComponent, {
                  disableClose: true,
                  panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
                  data: {
                    multiple: true,
                    images: imagesWithReferences
                  }
                }).afterClosed().subscribe((forceDeleteImages) => {
                  if (forceDeleteImages && forceDeleteImages.length) {
                    const forceDeleteTasks = forceDeleteImages.map((image) =>
                      this.imageService.deleteImage(imageResourceType(image), image.resourceKey, true)
                    );
                    forkJoin(forceDeleteTasks).subscribe(
                      () => {
                        this.updateData();
                      }
                    );
                  }
                });
              } else if (otherError) {
                const errorMessageWithTimeout = parseHttpErrorMessage(otherError.error, this.translate);
                setTimeout(() => {
                  this.store.dispatch(new ActionNotificationShow({message: errorMessageWithTimeout.message, type: 'error'}));
                }, errorMessageWithTimeout.timeout);
              }
            }
          );
        }
      });
    }
  }

  downloadImage($event, image: ImageResourceInfo) {
    if ($event) {
      $event.stopPropagation();
    }
    this.imageService.downloadImage(imageResourceType(image), image.resourceKey).subscribe();
  }

  exportImage($event, image: ImageResourceInfo) {
    if ($event) {
      $event.stopPropagation();
    }
    this.importExportService.exportImage(imageResourceType(image), image.resourceKey);
  }

  importImage(): void {
    this.importExportService.importImage().subscribe((image) => {
      if (image) {
        if (this.selectionMode) {
          this.imageSelected.next(image);
        } else {
          this.updateData();
        }
      }
    });
  }

  selectImage($event, image: ImageResourceInfo) {
    if ($event) {
      $event.stopPropagation();
    }
    this.imageSelected.next(image);
  }

  rowClick($event, image: ImageResourceInfo) {
    if (this.selectionMode) {
      this.selectImage($event, image);
    } else {
      if (this.deleteEnabled(image)) {
        this.dataSource.selection.toggle(image);
      }
    }
  }

  uploadImage(): void {
    this.dialog.open<UploadImageDialogComponent, UploadImageDialogData,
      ImageResourceInfo>(UploadImageDialogComponent, {
      disableClose: true,
      panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
      data: {}
    }).afterClosed().subscribe((result) => {
      if (result) {
        if (this.selectionMode) {
          this.imageSelected.next(result);
        } else {
          this.updateData();
        }
      }
    });
  }

  editImage($event: Event, image: ImageResourceInfo, itemIndex = -1) {
    if ($event) {
      $event.stopPropagation();
    }
    this.dialog.open<ImageDialogComponent, ImageDialogData,
      ImageResourceInfo>(ImageDialogComponent, {
      disableClose: true,
      panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
      data: {
        image,
        readonly: this.readonly(image)
      }
    }).afterClosed().subscribe((result) => {
      if (result) {
        this.imageUpdated(result, itemIndex);
      }
    });
  }

  embedImage($event: Event, image: ImageResourceInfo, itemIndex = -1) {
    if ($event) {
      $event.stopPropagation();
    }
    this.dialog.open<EmbedImageDialogComponent, EmbedImageDialogData,
      ImageResourceInfo>(EmbedImageDialogComponent, {
      disableClose: true,
      panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
      data: {
        image,
        readonly: this.readonly(image)
      }
    }).afterClosed().subscribe((result) => {
      if (result) {
        this.imageUpdated(result, itemIndex);
      }
    });
  }

  protected updatedRouterParamsAndData(queryParams: object, queryParamsHandling: QueryParamsHandling = 'merge') {
    if (this.pageMode) {
      this.router.navigate([], {
        relativeTo: this.route,
        queryParams,
        queryParamsHandling
      });
      if (queryParamsHandling === '' && isEqual(this.route.snapshot.queryParams, queryParams)) {
        this.updateData();
      }
    } else {
      this.updateData();
    }
  }

}
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils';
import { Observable, of, ReplaySubject } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import {
  NO_IMAGE_DATA_URI,
  ImageResourceInfo,
  imageResourceType,
  ImageResourceType,
  IMAGES_URL_PREFIX, isImageResourceUrl, ImageExportData, removeTbImagePrefix
} from '@shared/models/resource.models';
import { catchError, map, switchMap } from 'rxjs/operators';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { blobToBase64 } from '@core/utils';
import { ResourcesService } from '@core/services/resources.service';

@Injectable({
  providedIn: 'root'
})
export class ImageService {

  private imagesLoading: { [url: string]: ReplaySubject<Blob> } = {};

  constructor(
    private http: HttpClient,
    private sanitizer: DomSanitizer,
    private resourcesService: ResourcesService
  ) {
  }

  public uploadImage(file: File, title: string, config?: RequestConfig): Observable<ImageResourceInfo> {
    if (!config) {
      config = {};
    }
    const formData = new FormData();
    formData.append('file', file);
    formData.append('title', title);
    return this.http.post<ImageResourceInfo>('/api/image', formData,
      defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest));
  }

  public updateImage(type: ImageResourceType, key: string, file: File, config?: RequestConfig): Observable<ImageResourceInfo> {
    if (!config) {
      config = {};
    }
    const formData = new FormData();
    formData.append('file', file);
    return this.http.put<ImageResourceInfo>(`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(key)}`, formData,
      defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest));
  }

  public updateImageInfo(imageInfo: ImageResourceInfo, config?: RequestConfig): Observable<ImageResourceInfo> {
    const type = imageResourceType(imageInfo);
    const key = encodeURIComponent(imageInfo.resourceKey);
    return this.http.put<ImageResourceInfo>(`${IMAGES_URL_PREFIX}/${type}/${key}/info`,
      imageInfo, defaultHttpOptionsFromConfig(config));
  }

  public updateImagePublicStatus(imageInfo: ImageResourceInfo, isPublic: boolean, config?: RequestConfig): Observable<ImageResourceInfo> {
    const type = imageResourceType(imageInfo);
    const key = encodeURIComponent(imageInfo.resourceKey);
    return this.http.put<ImageResourceInfo>(`${IMAGES_URL_PREFIX}/${type}/${key}/public/${isPublic}`,
      imageInfo, defaultHttpOptionsFromConfig(config));
  }

  public getImages(pageLink: PageLink, includeSystemImages = false, config?: RequestConfig): Observable<PageData<ImageResourceInfo>> {
    return this.http.get<PageData<ImageResourceInfo>>(
      `${IMAGES_URL_PREFIX}${pageLink.toQuery()}&includeSystemImages=${includeSystemImages}`,
      defaultHttpOptionsFromConfig(config));
  }

  public getImageInfo(type: ImageResourceType, key: string, config?: RequestConfig): Observable<ImageResourceInfo> {
    return this.http.get<ImageResourceInfo>(`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(key)}/info`,
      defaultHttpOptionsFromConfig(config));
  }

  public getImageDataUrl(imageUrl: string, preview = false, asString = false, emptyUrl = NO_IMAGE_DATA_URI): Observable<SafeUrl | string> {
    const parts = imageUrl.split('/');
    const key = parts[parts.length - 1];
    parts[parts.length - 1] = encodeURIComponent(key);
    const encodedUrl = parts.join('/');
    const imageLink = preview ? (encodedUrl + '/preview') : encodedUrl;
    return this.loadImageDataUrl(imageLink, asString, emptyUrl);
  }

  private loadImageDataUrl(imageLink: string, asString = false, emptyUrl = NO_IMAGE_DATA_URI): Observable<SafeUrl | string> {
    let request: ReplaySubject<Blob>;
    if (this.imagesLoading[imageLink]) {
      request = this.imagesLoading[imageLink];
    } else {
      request = new ReplaySubject<Blob>(1);
      this.imagesLoading[imageLink] = request;
      const options = defaultHttpOptionsFromConfig({ignoreLoading: true, ignoreErrors: true});
      this.http.get(imageLink, {...options, ...{ responseType: 'blob' } }).subscribe({
        next: (value) => {
          request.next(value);
          request.complete();
        },
        error: err => {
          request.error(err);
        },
        complete: () => {
          delete this.imagesLoading[imageLink];
        }
      });
    }
    return request.pipe(
      switchMap(val => blobToBase64(val).pipe(
        map((dataUrl) => asString ? dataUrl : this.sanitizer.bypassSecurityTrustUrl(dataUrl))
      )),
      catchError(() => of(asString ? emptyUrl : this.sanitizer.bypassSecurityTrustUrl(emptyUrl)))
    );
  }

  public resolveImageUrl(imageUrl: string, preview = false, asString = false, emptyUrl = NO_IMAGE_DATA_URI): Observable<SafeUrl | string> {
    imageUrl = removeTbImagePrefix(imageUrl);
    if (isImageResourceUrl(imageUrl)) {
      return this.getImageDataUrl(imageUrl, preview, asString, emptyUrl);
    } else {
      return of(asString ? imageUrl : this.sanitizer.bypassSecurityTrustUrl(imageUrl));
    }
  }

  public downloadImage(type: ImageResourceType, key: string): Observable<any> {
    return this.resourcesService.downloadResource(`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(key)}`);
  }

  public deleteImage(type: ImageResourceType, key: string, force = false, config?: RequestConfig) {
    return this.http.delete(`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(key)}?force=${force}`, defaultHttpOptionsFromConfig(config));
  }

  public exportImage(type: ImageResourceType, key: string, config?: RequestConfig): Observable<ImageExportData> {
    return this.http.get<ImageExportData>(`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(key)}/export`,
      defaultHttpOptionsFromConfig(config));
  }

  public importImage(imageData: ImageExportData, config?: RequestConfig): Observable<ImageResourceInfo> {
    return this.http.put<ImageResourceInfo>('/api/image/import',
      imageData, defaultHttpOptionsFromConfig(config));
  }

}

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

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

相关文章

elementPlus自定义el-select下拉样式

如何在f12元素选择器上找到下拉div呢&#xff1f; 给el-select添加 :popper-append-to-body"false" 即可&#xff0c;这样就可以将下拉框添加到body元素中去&#xff0c;否则当我们失去焦点&#xff0c;下拉就消失了&#xff0c;在元素中找不到el-select。剩下就可以…

Amos结构方程模型---探索性分析

初级 第5讲 探索性分析_哔哩哔哩_bilibili amos中基本操作&#xff1a; 椭圆潜变量&#xff0c;不可预测 数据导入 改变形状 判定系数 方差估计和假设检验&#xff1a; 探索性分析&#xff1a; ses&#xff08;潜变量&#xff09;社会经济指数 从考虑最大的MI开始&#xff0c;卡…

模拟 ADC 的前端

ADC 的 SPICE 模拟 反复试验的方法将信号发送到 ADC 非常耗时&#xff0c;而且可能有效也可能无效。如果转换器捕获电压信息的关键时刻模拟输入引脚不稳定&#xff0c;则无法获得正确的输出数据。SPICE 模型允许您执行的步是验证所有模拟输入是否稳定&#xff0c;以便没有错误…

全网最详细金融APP测试功能点-测试用例,详细整理(全)

2024软件测试面试刷题&#xff0c;这个小程序&#xff08;永久刷题&#xff09;&#xff0c;靠它快速找到工作了&#xff01;&#xff08;刷题APP的天花板&#xff09;-CSDN博客跳槽涨薪的朋友们有福了&#xff0c;今天给大家推荐一个软件测试面试的刷题小程序。https://blog.c…

mov文件怎么转换成mp4格式?这四种转换方法超级好用!

mov文件怎么转换成mp4格式&#xff1f;在数字娱乐的世界中&#xff0c;你是否曾遇到过MOV格式的视频&#xff1f;也许&#xff0c;对于许多人来说&#xff0c;这并不是一个常见的格式&#xff0c;但这并非偶然&#xff0c;首先&#xff0c;我们来谈谈MOV的兼容性问题&#xff0…

「漏洞复现」时空智友ERP系统updater.uploadStudioFile 任意文件上传漏洞

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

Python处理异常用操作介绍

Python中的异常处理主要用于捕获和处理程序运行过程中出现的错误。 在编写Python程序时&#xff0c;我们经常会遇到各种错误&#xff0c;如语法错误、运行时错误等。为了确保程序的稳定性和健壮性&#xff0c;我们需要对可能出现的错误进行捕获和处理。本文将介绍Python中常用的…

Python入门 2024/7/3

目录 for循环的基础语法 遍历字符串 练习&#xff1a;数一数有几个a range语句 三个语法 语法1 语法2 语法3 练习&#xff1a;有几个偶数 变量作用域 for循环的嵌套使用 打印九九乘法表 发工资案例 continue和break语句 函数的基础定义语法 函数声明 函数调用 …

MLLM QLoRA微调实战:基于最新的袖珍Mini-InternVL模型

引言 大型语言模型&#xff08;LLM&#xff09;的世界正在不断发展&#xff0c;新的进步正在迅速出现。一个令人兴奋的领域是多模态LLM&#xff08;MLLMs&#xff09;的发展&#xff0c;这种模型既能够理解文本又能够理解图像&#xff0c;并与之进行交互。因此&#xff0c;这种…

ICCV2023鲁棒性相关论文速览

Paper1 Towards Better Robustness against Common Corruptions for Unsupervised Domain Adaptation 摘要原文: Recent studies have investigated how to achieve robustness for unsupervised domain adaptation (UDA). While most efforts focus on adversarial robustnes…

udp发送数据如果超过1个mtu时,抓包所遇到的问题记录说明

最近在测试Syslog udp发送相关功能&#xff0c;测试环境是centos udp头部的数据长度是2个字节&#xff0c;最大传输长度理论上是65535&#xff0c;除去头部这些字节&#xff0c;可以大概的说是64k。 写了一个超过64k的数据(随便用了一个7w字节的buffer)发送demo&#xff0c;打…

Geotools系列说明之LineString仿高德航路截取说明

需求分析 我们在做webgl的时候经常会遇到这样的需求&#xff0c;计算给定航路的拥堵情况&#xff0c;不同的拥堵显示不同的颜色&#xff0c;航路截取计算等等。基于这类问题统一都可以使用LineString进行处理 实现思路 如上图所示&#xff0c;航路是几个关键的点然后练成线&a…

MySql Innodb 索引有哪些与详解

概述 对于MYSQL的INNODB存储引擎的索引&#xff0c;大家是不陌生的&#xff0c;都能想到是 B树结构&#xff0c;可以加速SQL查询。但对于B树索引&#xff0c;它到底“长”得什么样子&#xff0c;它具体如何由一个个字节构成的&#xff0c;这些的基础知识鲜有人深究。本篇文章从…

2本Top,4本纯正刊,25天即录!7月刊源表已更新!

本周投稿推荐 SCI • 能源技术类&#xff0c;1.5-2.0&#xff08;来稿即录25天&#xff09; • 计算机类&#xff0c;2.0-3.0&#xff08;纯正刊29天录用&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&a…

【微信小程序开发实战项目】——如何制作一个属于自己的花店微信小程序(2)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

Python基于决策树回归模型、多元线性回归模型、随机森林回归模型和LightGBM回归模型实现波士顿房价预测项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 城市住房市场的稳定与健康发展是衡量一个地区经济活力和社会福祉的重要指标之一。波士顿&#xff0c;作…

Three-pass authentication

7.2.3 Mechanism MUT.CR — Three-pass authentication # 参考符号 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/7aed1610e49e48729933f8160e5228af.png)

研发驱动 再谱新篇丨美格智能南通研发中心正式成立

近日&#xff0c;美格智能全资设立的众格智能技术&#xff08;南通&#xff09;有限公司&#xff0c;正式在江苏省南通市紫琅科技城揭牌成立&#xff0c;此举也标志着继上海、西安、深圳之后&#xff0c;美格智能研发力量布局再谱新篇&#xff1a;美格智能南通研发中心正式成立…

工商业光伏项目如何快速开发?

一、前期调研与规划 1、屋顶资源评估&#xff1a;详细测量屋顶面积、承重能力及朝向&#xff0c;利用光伏业务管理软件进行日照分析和发电量预测&#xff0c;确保项目可行性。 2、政策与补贴研究&#xff1a;深入了解当地政府对工商业光伏项目的政策支持和补贴情况&#xff0…

KES数据库实践指南:探索KES数据库的事务隔离级别

并发控制 并发控制的重要性 并发控制是数据库管理系统中的一个核心概念&#xff0c;它确保在多用户环境中&#xff0c;对数据库的并发访问不会破坏数据的完整性和一致性。 当多个用户同时对数据库进行读写操作时&#xff0c;如果缺乏有效的并发控制机制&#xff0c;可能会导致数…