Angular组件(二) 分割面板ShrinkSplitter

Angular组件(二) 分割面板ShrinkSplitter

前言

在Angular组件(一) 分割面板ShrinkSplitter文章中我们实现了Splitter组件,后来在业务场景中发现在开关右侧容器和底部容器时,使用起来不方便,ngModel绑定的值始终是左侧容器和顶部容器的大小,然而有时我们关注的是右侧容器和底部容器的大小,让左侧自适应。于是修改组件代码,让ngmodel绑定的容器大小和tlColsedMode关联,举例: tlColsedMode = “right”,ngModel绑定的值就是右侧容器的大小。

组件Splitter

module.ts

import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { TlShrinkSplitterComponent } from "./shrink-splitter.component";
import{NzToolTipModule} from "ng-zorro-antd/tooltip"const COMMENT = [TlShrinkSplitterComponent];@NgModule({declarations: [...COMMENT],exports: [...COMMENT],imports: [CommonModule,NzToolTipModule,]
})
export class TlShrinkSplitterModule {}

component.ts

import { AfterContentInit, AfterViewInit, ChangeDetectorRef, Component, ContentChildren, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, QueryList, TemplateRef, ViewChild } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
import { TlTemplateDirective } from "topdsm-lib/common"
import { isFalsy } from "topdsm-lib/core/util";
import { off, on } from "./util";@Component({selector: "tl-shrink-splitter",templateUrl: "./shrink-splitter.component.html",providers: [{provide: NG_VALUE_ACCESSOR,useExisting: forwardRef(() => TlShrinkSplitterComponent),multi: true}],host: {class: "tl-shrink-splitter",'[class.expand]': 'tlExpand','[class.contract]': '!tlExpand','[class.dragable]': 'tlDragable','[class.contract-left]': 'tlColsedMode === "left"','[class.contract-right]': 'tlColsedMode === "right"','[class.contract-top]': 'tlColsedMode === "top"','[class.contract-bottom]': 'tlColsedMode === "bottom"','[style.z-index]': 'tlZIndex',}
})
export class TlShrinkSplitterComponent implements OnInit, AfterContentInit, AfterViewInit, ControlValueAccessor {prefix = "tl-shrink-splitter"offset = 0oldOffset: number | string = 0isMoving = falseinitOffset = 0_value: number | string = 0.5isOpen = trueviewRender = false@Input()tlZIndex = 10/** 是否展示收起icon */@Input()tlShowExpandIcon = true/** 收起容器模式,上下左右哪一个容器应用收起展开的状态 */@Input()tlColsedMode: "left" | "right" | "top" | "bottom" = "left"@Input()tlMin = "40px"@Input()tlMax = "40px"@Input()tlExpandTooltipContent = ""@Input()tlContractTooltipContent = ""/** 是否可拖拽调整大小 */@Input()tlDragable = trueget value() {return this._value}set value(val: number | string) {if(!this.viewRender && !this.tlExpand){           this.expandValueCache = valval = 0}this._value = valthis.onChange(val)this.computeOffset()setTimeout(() => {this.viewRender = true}, 0);}expandValueCache: string | number = 0/** 展开状态 */get tlExpand() {return this.isOpen;}@Input()set tlExpand(val: boolean) {if (val !== this.isOpen) {this.isOpen = val;this.tlExpandChange.emit(val);this.changeExpand(val)}}/** 容器展开状态切换 */changeExpand(status: boolean) {if (!status) {// 收起this.expandValueCache = this.valuethis.value = 0} else {// 展开this.value = this.expandValueCachethis.expandValueCache = 0}}/** 展开收缩切换事件 */@Output() readonly tlExpandChange = new EventEmitter<boolean>();@Output() readonly onMoveStart = new EventEmitter();@Output() readonly onMoving = new EventEmitter<MouseEvent>();@Output() readonly onMoveEnd = new EventEmitter();expandChange(e: MouseEvent) {e.stopPropagation();e.preventDefault()this.tlExpand = !this.isOpen}@ContentChildren(TlTemplateDirective)templates?: QueryList<TlTemplateDirective>leftTemplate?: TemplateRef<void> | null = nullrightTemplate?: TemplateRef<void> | null = nulltopTemplate?: TemplateRef<void> | null = nullbottomTemplate?: TemplateRef<void> | null = null@ViewChild('outerWrapper')outerWrapper: ElementRef;get isHorizontal() {return this.tlColsedMode === "left" || this.tlColsedMode === "right"}get computedMin() {return this.getComputedThresholdValue('tlMin');}get computedMax() {return this.getComputedThresholdValue('tlMax');}get anotherOffset() {return 100 - this.offset;}get valueIsPx() {return typeof this.value === 'string';}get offsetSize() {return this.isHorizontal ? 'offsetWidth' : 'offsetHeight';}get paneClasses() {let classes = {}classes[`${this.prefix}-pane`] = trueclasses[`${this.prefix}-pane-transition`] = this.viewRenderclasses[`${this.prefix}-pane-moving`] = this.isMovingreturn classes}/** 展开收起触发器icon */get triggrrClass() {let classes = {}if (this.tlColsedMode === "left" && this.isOpen) {classes["icon-caret-left"] = true} else if (this.tlColsedMode === "left" && !this.isOpen) {classes["icon-caret-right"] = true} else if (this.tlColsedMode === "right" && this.isOpen) {classes["icon-caret-right"] = true} else if (this.tlColsedMode === "right" && !this.isOpen) {classes["icon-caret-left"] = true} else if (this.tlColsedMode === "top" && this.isOpen) {classes["icon-caret-left"] = true} else if (this.tlColsedMode === "top" && !this.isOpen) {classes["icon-caret-right"] = true} else if (this.tlColsedMode === "bottom" && this.isOpen) {classes["icon-caret-right"] = true} else if (this.tlColsedMode === "bottom" && !this.isOpen) {classes["icon-caret-left"] = true}return classes}get tooltipPosition() {let position = "right"if (this.tlColsedMode === "right" && !this.isOpen) {position = "left"}return position}get tooltipContent() {let tooltip = ""if (this.tlColsedMode === "left" && this.isOpen) {tooltip = isFalsy(this.tlExpandTooltipContent) ? "收起左侧内容" : this.tlExpandTooltipContent} else if (this.tlColsedMode === "left" && !this.isOpen) {tooltip = isFalsy(this.tlContractTooltipContent) ? "展开左侧内容" : this.tlContractTooltipContent} else if (this.tlColsedMode === "right" && this.isOpen) {tooltip = isFalsy(this.tlExpandTooltipContent) ? "收起右侧内容" : this.tlExpandTooltipContent} else if (this.tlColsedMode === "right" && !this.isOpen) {tooltip = isFalsy(this.tlContractTooltipContent) ? "展开右侧内容" : this.tlContractTooltipContent} else if (this.tlColsedMode === "top" && this.isOpen) {tooltip = isFalsy(this.tlExpandTooltipContent) ? "收起顶部内容" : this.tlExpandTooltipContent} else if (this.tlColsedMode === "top" && !this.isOpen) {tooltip = isFalsy(this.tlContractTooltipContent) ? "展开顶部内容" : this.tlContractTooltipContent} else if (this.tlColsedMode === "bottom" && this.isOpen) {tooltip = isFalsy(this.tlExpandTooltipContent) ? "收起底部内容" : this.tlExpandTooltipContent} else if (this.tlColsedMode === "bottom" && !this.isOpen) {tooltip = isFalsy(this.tlContractTooltipContent) ? "展开底部内容" : this.tlContractTooltipContent}return tooltip}px2percent(numerator: string | number, denominator: string | number) {return parseFloat(numerator + "") / parseFloat(denominator + "");}computeOffset() {if(this.tlColsedMode === "left" || this.tlColsedMode === "top"){           this.offset = (this.valueIsPx ? this.px2percent(this.value as string, this.outerWrapper.nativeElement[this.offsetSize]) : this.value) as number * 10000 / 100}else{this.offset = (this.valueIsPx ? 1 - this.px2percent(this.value as string, this.outerWrapper.nativeElement[this.offsetSize]) : 1- (this.value as number)) as number * 10000 / 100}  }getComputedThresholdValue(type) {let size = this.outerWrapper.nativeElement[this.offsetSize];if (this.valueIsPx) return typeof this[type] === 'string' ? this[type] : size * this[type];else return typeof this[type] === 'string' ? this.px2percent(this[type], size) : this[type];}getMin(value1, value2) {if (this.valueIsPx) return `${Math.min(parseFloat(value1), parseFloat(value2))}px`;else return Math.min(value1, value2);}getMax(value1, value2) {if (this.valueIsPx) return `${Math.max(parseFloat(value1), parseFloat(value2))}px`;else return Math.max(value1, value2);}getAnotherOffset(value) {let res: string | number = 0;if (this.valueIsPx) res = `${this.outerWrapper.nativeElement[this.offsetSize] - parseFloat(value)}px`;else res = 1 - value;return res;}handleMove = (e) => {let pageOffset = this.isHorizontal ? e.pageX : e.pageY;let offset = pageOffset - this.initOffset;let outerWidth = this.outerWrapper.nativeElement[this.offsetSize];let value: string | number = ""if (this.valueIsPx) {           if(this.tlColsedMode === "left" || this.tlColsedMode === "top"){value = `${parseFloat(this.oldOffset as string) + offset}px`}else{value = `${parseFloat(this.oldOffset as string) - offset}px`}} else {if(this.tlColsedMode === "left" || this.tlColsedMode === "top"){value = this.px2percent(outerWidth * (this.oldOffset as number) + offset, outerWidth)}else{value = this.px2percent(outerWidth * (this.oldOffset as number) - offset, outerWidth)}         }let anotherValue = this.getAnotherOffset(value);if (parseFloat(value + "") <= parseFloat(this.computedMin + "")) value = this.getMax(value, this.computedMin);if (parseFloat(anotherValue + "") <= parseFloat(this.computedMax)) value = this.getAnotherOffset(this.getMax(anotherValue, this.computedMax));e.atMin = this.value === this.computedMin;e.atMax = this.valueIsPx ? this.getAnotherOffset(this.value) === this.computedMax : (this.getAnotherOffset(this.value) as number).toFixed(5) === this.computedMax.toFixed(5);this.value = valuethis.onMoving.emit(e)}handleUp = (e) => {this.isMoving = false;off(document, 'mousemove', this.handleMove);off(document, 'mouseup', this.handleUp);this.onMoveEnd.emit()}onTriggerMouseDown(e) {if(!this.tlDragable){return}this.initOffset = this.isHorizontal ? e.pageX : e.pageY;this.oldOffset = this.value;this.isMoving = true;on(document, 'mousemove', this.handleMove);on(document, 'mouseup', this.handleUp);this.onMoveStart.emit()}constructor(private cdr: ChangeDetectorRef) { }ngOnInit(): void {console.log("ngOnInit");}ngAfterViewInit(): void {console.log("ngAfterViewInit");this.computeOffset()}ngAfterContentInit() {this.templates?.forEach((item) => {switch (item.getType()) {case 'left':this.leftTemplate = item.template;break;case 'right':this.rightTemplate = item.template;break;case 'top':this.topTemplate = item.template;break;case 'bottom':this.bottomTemplate = item.template;break;default:this.leftTemplate = item.template;break;}});}// 输入框数据变化时onChange: (value: any) => void = () => null;onTouched: () => void = () => null;writeValue(val: number | string): void {if (val !== this.value) {this.value = valthis.computeOffset();this.cdr.markForCheck();}}// UI界面值发生更改,调用注册的回调函数registerOnChange(fn: any): void {this.onChange = fn;}// 在blur(等失效事件),调用注册的回调函数registerOnTouched(fn: any): void {this.onTouched = fn;}// 设置禁用状态setDisabledState?(isDisabled: boolean): void {}
}

TlTemplateDirective指令实现

import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
import { NzSafeAny } from "topdsm-lib/core/types";@Directive({selector: '[tlTemplate]'
})
export class TlTemplateDirective {@Input('tlTemplate')name: string = "default"// @Input()// type: string = ""constructor(private viewContainer: ViewContainerRef, public template: TemplateRef<NzSafeAny>) {//this.template = templateRef;}ngOnInit(): void {this.viewContainer.createEmbeddedView(this.template)}getType() {return this.name;}
}

事件绑定、解绑

export const on = (function() {if (document.addEventListener) {return function(element, event, handler) {if (element && event && handler) {element.addEventListener(event, handler, false);}};} else {return function(element, event, handler) {if (element && event && handler) {element.attachEvent('on' + event, handler);}};}
})();export const off = (function() {if (document.removeEventListener) {return function(element, event, handler) {if (element && event) {element.removeEventListener(event, handler, false);}};} else {return function(element, event, handler) {if (element && event) {element.detachEvent('on' + event, handler);}};}
})();

component.html

<div [ngClass]="prefix + '-wrapper'" #outerWrapper><div [ngClass]="prefix + '-horizontal'" *ngIf="isHorizontal; else verticalSlot"><div class="left-pane" [ngStyle]="{right: anotherOffset + '%'}" [ngClass]="paneClasses"><ng-container *ngTemplateOutlet="leftTemplate"></ng-container></div><div [ngClass]="prefix + '-trigger-con'" [ngStyle]="{left: offset + '%'}" (mousedown)="onTriggerMouseDown($event)"><div ngClass="tl-shrink-splitter-trigger tl-shrink-splitter-trigger-vertical" ><span class="tl-shrink-splitter-trigger-bar-con vertical" [ngClass]="triggrrClass" (mousedown)="expandChange($event)" nz-tooltip [nzTooltipTitle]="tooltipContent" [nzTooltipPlacement]="tooltipPosition" *ngIf="tlShowExpandIcon"></span></div></div><div class="right-pane" [ngStyle]="{left: offset + '%'}" [ngClass]="paneClasses"><ng-container *ngTemplateOutlet="rightTemplate"></ng-container></div></div><ng-template #verticalSlot><div [ngClass]="prefix + '-vertical'" ><div class="top-pane" [ngStyle]="{bottom: anotherOffset + '%'}" [ngClass]="paneClasses"><ng-container *ngTemplateOutlet="topTemplate"></ng-container></div><div [ngClass]="prefix + '-trigger-con'" [ngStyle]="{top: offset + '%'}" (mousedown)="onTriggerMouseDown($event)"><div ngClass="tl-shrink-splitter-trigger tl-shrink-splitter-trigger-horizontal" ><span class="tl-shrink-splitter-trigger-bar-con horizontal" [ngClass]="triggrrClass" (mousedown)="expandChange($event)" nz-tooltip [nzTooltipTitle]="tooltipContent" [nzTooltipPlacement]="tooltipPosition" *ngIf="tlShowExpandIcon"></span></div></div><div class="bottom-pane" [ngStyle]="{top: offset + '%'}" [ngClass]="paneClasses"><ng-container *ngTemplateOutlet="bottomTemplate"></ng-container></div></div></ng-template>
</div>

component.less

@split-prefix-cls: ~"tl-shrink-splitter";
@trigger-bar-background: rgba(23, 35, 61, 0.25);
@trigger-background: #f3f4f7;
@trigger-width: 8px;
@trigger-bar-width: 4px;
@trigger-bar-offset: (@trigger-width - @trigger-bar-width) / 2;
@trigger-bar-interval: 3px;
@trigger-bar-weight: 1px;
@trigger-bar-con-height: 24px;
@trigger-bar-con-width: 24px;
.tl-shrink-splitter{position: relative;height: 100%;width: 100%;
}
.tl-shrink-splitter-wrapper{position: relative;height: 100%;width: 100%;
}.@{split-prefix-cls}{background-color: #fff;border: 1px solid #dee2e6;&-pane{position: absolute;//transition: all .3s ease-in;padding: 8px;&.tl-shrink-splitter-pane-moving{transition: none;}&.left-pane, &.right-pane {top: 0;bottom: 0;}&.left-pane {left: 0;}&.right-pane {right: 0;padding-left: 16px;}&.top-pane, &.bottom-pane {left: 0;right: 0;}&.top-pane {top: 0;}&.bottom-pane {bottom: 0;padding-top: 16px;}&-moving{-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;}&-transition{transition: all .3s ease-in; }}&-trigger{border: 1px solid #dcdee2;&-con {position: absolute;transform: translate(-50%, -50%);z-index: 10;}&-bar-con {position: absolute;overflow: hidden;background-image: linear-gradient(90deg, #dcf3fc, #f8fdff);border: 1px solid #76b9d6;color: #76b9d6;&:hover{color: #000 !important;}&.vertical {top: 50%;left: -6px;width: @trigger-bar-con-width;height: @trigger-bar-con-height;//background-color: #fff;//border: 1px solid #ccc;border-radius: 50%;display: flex;align-items: center;justify-content: center;//color: #b2b2b2;font-size: 14px;cursor: pointer;}&.horizontal {left: 50%;top: -4px;width: @trigger-bar-con-height;height: @trigger-bar-con-width;//transform: translate(-50%, 0);background-color: #fff;border: 1px solid #ccc;border-radius: 50%;display: flex;align-items: center;justify-content: center;color: #b2b2b2;font-size: 14px;cursor: pointer;}}&-vertical {width: @trigger-width;height: 100%;background: @trigger-background;border-top: none;border-bottom: none;//cursor: col-resize;.@{split-prefix-cls}-trigger-bar {width: @trigger-bar-width;height: 1px;background: @trigger-bar-background;float: left;margin-top: @trigger-bar-interval;}}&-horizontal {height: @trigger-width;width: 100%;background: @trigger-background;border-left: none;border-right: none;//cursor: row-resize;.@{split-prefix-cls}-trigger-bar {height: @trigger-bar-width;width: 1px;background: @trigger-bar-background;float: left;margin-right: @trigger-bar-interval;}}}&-horizontal {.@{split-prefix-cls}-trigger-con {top: 50%;height: 100%;width: 0;}}&-vertical {.@{split-prefix-cls}-trigger-con {left: 50%;height: 0;width: 100%;}}
}.tl-shrink-splitter.dragable{.tl-shrink-splitter-trigger-vertical{cursor: col-resize;}.tl-shrink-splitter-trigger-horizontal{cursor: row-resize;}
}.tl-shrink-splitter.contract{.tl-shrink-splitter-trigger-vertical{width: 0;padding-left: 0;}.tl-shrink-splitter-trigger-horizontal{height: 0;padding-top: 0;}.tl-shrink-splitter-trigger{border: 0;}&.contract-left{.tl-shrink-splitter-pane.left-pane{width: 0;padding: 0;overflow: hidden;}.right-pane{padding-left: 8px;}}.tl-shrink-splitter-trigger-bar-con{&.vertical{left: -6px;}}&.contract-right{.tl-shrink-splitter-trigger-bar-con{&.vertical{left: -16px;}}}&.contract-top{.tl-shrink-splitter-pane.top-pane{overflow: hidden;height: 0;padding: 0;}.bottom-pane{padding-top: 8px;}.tl-shrink-splitter-trigger-bar-con.horizontal{transform: rotate(90deg);}}&.contract-bottom{.tl-shrink-splitter-trigger-bar-con{&.horizontal{top: -16px;}}.tl-shrink-splitter-pane.bottom-pane{overflow: hidden;height: 0;padding: 0;}.top-pane{padding-top: 8px;}.tl-shrink-splitter-trigger-bar-con.horizontal{transform: rotate(90deg);}}
}
.tl-shrink-splitter.expand{.tl-shrink-splitter-trigger-bar-con{&.vertical{left: -8px;}}&.contract-top{.tl-shrink-splitter-trigger-bar-con.horizontal{transform: rotate(90deg);}}&.contract-bottom{.tl-shrink-splitter-trigger-bar-con.horizontal{transform: rotate(90deg);}}
}

页面效果

左右容器和上下容器
image.png

demo

左侧容器

import { Component } from '@angular/core';@Component({selector: 'tl-demo-shrink-splitter-basic',template: `<button tButton type="button" label="切换伸缩状态" class="ui-plusWidth ui-button-primary" style="margin-right: 8px" (click)="expandChange()"></button><div class="split-box"><tl-shrink-splitter [(tlExpand)]="expand" [(ngModel)]="value" (onMoving)="triggerMoveHnadle($event)"><ng-template tlTemplate="left"><div>左侧区域自定义</div></ng-template><ng-template tlTemplate="right"><div>右侧区域自定义</div></ng-template></tl-shrink-splitter>  </div>`,styles: [`.split-box{height: 200px;display: flex;position: relative;overflow: hidden;}.split-right{margin-left: 10px;border: 1px solid #e3e3e3;flex:1;}`]
})
export class TlDemoShrinkSplitterBasicComponent {expand = truevalue = 0.3expandChange(){this.expand = !this.expand}triggerMoveHnadle(e){console.log(e);console.log(this.value);  }
}

image.png

image.png

右侧容器

import { Component } from '@angular/core';@Component({selector: 'tl-demo-shrink-splitter-right',template: `<button tButton type="button" label="切换伸缩状态" class="ui-plusWidth ui-button-primary" style="margin-right: 8px" (click)="expandChange()"></button><div class="split-box"><tl-shrink-splitter [(tlExpand)]="expand" [(ngModel)]="value" tlColsedMode="right"><ng-template tlTemplate="left"><div>左侧区域自定义</div></ng-template><ng-template tlTemplate="right"><div>右侧区域自定义</div></ng-template></tl-shrink-splitter>  </div>`,styles: [`.split-box{height: 300px;display: flex;position: relative;overflow: hidden;}.split-right{margin-left: 10px;border: 1px solid #e3e3e3;flex:1;}`]
})
export class TlDemoShrinkSplitterRightComponent {expand = truevalue = "200px"expandChange(){this.expand = !this.expand}
}

在这里插入图片描述

顶部容器

import { Component } from '@angular/core';@Component({selector: 'tl-demo-shrink-splitter-top',template: `<button tButton type="button" label="切换伸缩状态" class="ui-plusWidth ui-button-primary" style="margin-right: 8px" (click)="expandChange()"></button><div class="split-box"><tl-shrink-splitter [(tlExpand)]="expand" [(ngModel)]="value"  tlColsedMode="top"><ng-template tlTemplate="top"><div>顶部区域自定义</div></ng-template><ng-template tlTemplate="bottom"><div>底部区域自定义</div></ng-template></tl-shrink-splitter>  </div>`,styles: [`.split-box{height: 300px;display: flex;position: relative;overflow: hidden;}.split-right{margin-left: 10px;border: 1px solid #e3e3e3;flex:1;}`]
})
export class TlDemoShrinkSplitterTopComponent {expand = truevalue = "100px"expandChange(){this.expand = !this.expand}
}

在这里插入图片描述

底部容器

import { Component } from '@angular/core';@Component({selector: 'tl-demo-shrink-splitter-bottom',template: `<button tButton type="button" label="切换伸缩状态" class="ui-plusWidth ui-button-primary" style="margin-right: 8px" (click)="expandChange()"></button><div class="split-box"><tl-shrink-splitter [(tlExpand)]="expand" [(ngModel)]="value"  tlColsedMode="bottom"><ng-template tlTemplate="top"><div>顶部区域自定义</div></ng-template><ng-template tlTemplate="bottom"><div>底部区域自定义</div></ng-template></tl-shrink-splitter>  </div>`,styles: [`.split-box{height: 300px;display: flex;position: relative;overflow: hidden;}.split-right{margin-left: 10px;border: 1px solid #e3e3e3;flex:1;}`]
})
export class TlDemoShrinkSplitterBottomComponent {expand = truevalue = "100px"expandChange() {this.expand = !this.expand}
}

在这里插入图片描述

API

tl-shrink-splitter

参数说明类型默认值
[ngModel]面板位置,可以是 0~1 代表百分比,或具体数值的像素,可用 ngModel 双向绑定string | number0.5
[tlExpand]是否展开容器booleantrue
[tlDragable]是否可以拖拽拖拽容器大小booleantrue
[tlZIndex]组件z-indexnumber10
[tlShowExpandIcon]是否展示收起展开切换状态的按钮iconbooleantrue
[tlColsedMode]收起容器模式,上下左右哪一个容器应用收起展开的状态'left' | 'right' | 'top' | 'bottom'left
[tlMin]最小阈值,类型为number时,数值范围为0 ~ 1 表示宽度百分比string | number40px
[tlMax]最大阈值,类型为number时,数值范围为0 ~ 1 表示宽度百分比string| number40px
[tlExpandTooltipContent]展开状态时的tooltip
[tlContractTooltipContent]收起状态时的tooltip

事件

事件名参数描述
tlExpandChangeevt(Boolean)容器展开/收起 change
onMoveStart拖拽开始
onMovingevt(MouseEvent)拖拽中
onMoveEnd拖拽结束

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

顺序表的奥秘:高效数据存储与检索

&#x1f37f;顺序表 &#x1f9c0;1、顺序表的实现&#x1f365;1.1 创建顺序表类&#x1f365;1.2 插入操作&#x1f365;1.3 查找操作&#x1f365;1.4 删除操作&#x1f365;1.5 清空操作 &#x1f9c0;2、ArrayList的说明&#x1f9c0;3、ArrayList使用&#x1f365;3.1 A…

网络安全全栈培训笔记(59-服务攻防-中间件安全CVE复现lSApacheTomcataNginx)

第59天 服务攻防-中间件安全&CVE复现&lS&Apache&Tomcata&Nginx 知识点&#xff1a; 中间件及框架列表&#xff1a; lIS,Apache,Nginx,Tomcat,Docker,Weblogic,JBoos,WebSphere,Jenkins, GlassFish,Jira,Struts2,Laravel,Solr,Shiro,Thinkphp,Sprng,Flask,…

使用流服务器m7s对接gb28181

优&#xff1a;sip品牌兼容性比较好&#xff0c;大华&#xff0c;海康都稳定可以&#xff0c;srs的5.0 sip品牌兼容性大华没反应&#xff0c;akstream-sip 大华也有问题&#xff0c;wvp也还可以 缺&#xff1a;目前最新的4.7.4版本&#xff0c;&#xff0c;sip协议用udp正常&a…

从零开始 Linux(一):基础介绍与常用指令总结

从零开始 Linux 01. 概念理解 1.1 什么是 Linux&#xff1f; Linux 是一个开源免费的 操作系统&#xff0c;具有很好的稳定性、安全性&#xff0c;且有很强的处理高并发的能力 Linux 的应用场景&#xff1a; 可以在 Linux 下开发项目&#xff0c;比如 JavaEE、大数据、Python…

Flink中StateBackend(工作状态)与Checkpoint(状态快照)的关系

State Backends 由 Flink 管理的 keyed state 是一种分片的键/值存储&#xff0c;每个 keyed state 的工作副本都保存在负责该键的 taskmanager 本地中。另外&#xff0c;Operator state 也保存在机器节点本地。Flink 定期获取所有状态的快照&#xff0c;并将这些快照复制到持…

Adobe Photoshop 2024 v25.4.0 - 专业的图片设计软件

Adobe Photoshop 2024 v25.4.0更新了&#xff0c;从照片编辑和合成到数字绘画、动画和图形设计&#xff0c;任何您能想象到的内容都能通过PS2024轻松实现。 利用人工智能技术进行快速编辑。学习新技能并与社区分享您的工作。借助我们的最新版本&#xff0c;做令人惊叹的事情从未…

持续集成 CI/CD

CI和CD代表持续集成和持续交付/持续部署。简而言之&#xff0c;CI 是一种现代软件开发实践&#xff0c;其中频繁且可靠地进行增量代码更改。由 CI 触发的自动构建和测试步骤确保合并到存储库中的代码更改是可靠的。然后&#xff0c;作为 CD 流程的一部分&#xff0c;快速、无缝…

Vue打包Webpack源码及物理路径泄漏问题解决

修复前&#xff1a; 找到vue.config.js文件&#xff0c;在其中增加配置 module.exports {productionSourceMap: false,// webpack 配置configureWebpack: {devtool: false,}}修复后&#xff1a;

Docker核心教程

1. 概述 官网&#xff1a;https://docs.docker.com/ Docker Hub 网站&#xff1a;https://hub.docker.com/ 容器较为官方的解释&#xff1a; 一句话概括容器&#xff1a;容器就是将软件打包成标准化单元&#xff0c;以用于开发、交付和部署。 容器镜像是轻量的、可执行的独立…

大数据-Spark-关于Json数据格式的数据的处理与练习

上一篇&#xff1a; 大数据-MapReduce-关于Json数据格式的数据的处理与练习-CSDN博客 16.7 Json在Spark中的引用 依旧利用上篇的数据去获取每部电影的平均分 {"mid":1,"rate":6,"uid":"u001","ts":15632433243} {"m…

【Linux】命名管道

文章目录 命名管道一、命名管道的原理二、命名管道的创建命令行中创建程序中创建 - mkfifo函数&#xff1a; 三、命名管道的使用命名管道实现server&client通信 四、匿名管道与命名管道的区别和联系 命名管道 如果涉及到在文件系统中创建一个有名的管道&#xff0c;那么就…

HarmonyOS模拟器启动失败,电脑蓝屏解决办法

1、在Tool->Device Manager管理界面中&#xff0c;通过Wipe User Data清理模拟器用户数据&#xff0c;然后重启模拟器&#xff1b;如果该方法无效&#xff0c;需要Delete删除已创建的Local Emulater。 2、在Tool->SDK Manager管理界面的PlatForm选项卡中&#xff0c;取消…

C++11—— lambda表达式与包装器

C11—— lambda表达式与包装器 文章目录 C11—— lambda表达式与包装器一、 lambda表达式lambda表达式产生的意义lambda表达式语法函数对象与lambda表达式 二、 包装器functionfunction产生的意义function的用法function使用的例子 bind调整参数顺序固定绑定参数 一、 lambda表…

Redis常用数据结构与应用场景

常用数据结构 StringHashListSetZset String常用操作 String应用场景 Hash常用操作 hash应用场景 Hash结构优缺点 优点 同类数据归类整合存储,方便数据管理相比String操作消耗内存与spu更小相比string更节省空间 缺点 过期功能不能使用在field上,只用用在key上Redis集群…

TypeScript实战系列之合理运用类型

目录 介绍any 和 unknownerve 的用途断言type 和 interfacedeclare 关键字的作用联合类型 和 类型守卫交叉类型 介绍 这篇主要介绍下ts 常用的基本类型和一些常用的技巧性技能 any 和 unknow any 和 unknown 是两个类型关键字&#xff0c;它们用于处理类型不确定或未知的情况…

yolov8数据标注、模型训练到模型部署全过程

文章目录 一、数据标注&#xff08;x-anylabeling&#xff09;1. 安装方式1.1 直接通过Releases安装1.2 clone源码后采用终端运行 2. 如何使用 二、模型训练三、模型部署3.1 onnx转engine3.2 c调用engine模型3.2.1 main_tensorRT.cpp3.2.2 segmentationModel.cpp 一、数据标注&…

爱可声助听器参与南湖区价值百万公益助残捐赠活动成功举行

“声音大小合适吗&#xff1f;能听清楚吗&#xff1f;”今天下午&#xff0c;一场助残捐赠活动在南湖区凤桥镇悄然举行&#xff0c;杭州爱听科技有限公司带着验配团队和听力检测设备来到活动现场&#xff0c;为南湖区听障残疾人和老人适配助听器。 家住余新镇的75岁的周奶奶身体…

1.迭代与递归 - JS

迭代与递归是函数进阶的第一个门槛。迭代就是对已知变量反复赋值变换&#xff1b;递归就是函数体内调用自身。 迭代 一个迭代是就是一个循环&#xff0c;根据迭代式对变量反复赋值。 求近似根&#xff08;切线法&#xff09;&#xff1b; 迭代描述&#xff1a; x 0 x_0 x0…

C语言KR圣经笔记 6.6 表查询 6.7 typedef

6.6 表查询 为了说明结构体的更多方面&#xff0c;本节我们来写一个表查询功能包的内部代码。在宏处理器或编译器的符号表管理例程中&#xff0c;这个代码是很典型的。例如&#xff0c;考虑 #define 语句&#xff0c;当遇到如下行 #define IN 1 时&#xff0c;名称 IN 与其对…

微信小程序如何实现点击上传图片功能

如下所示,实际需求中常常存在需要点击上传图片的功能,上传前显示边框表面图片显示大小,上传后将图形缩放到边框大小。 实现如下: .wxml <view class="{{img_src==?blank-area:}}" style="width:100%;height:40%;display:flex;align-items: center;jus…