Angular组件(一) 分割面板ShrinkSplitter

news2024/9/30 17:34:50

Angular组件(一) 分割面板ShrinkSplitter

前言

分割面板在日常开发中经常使用,可将一片区域,分割为可以拖拽整宽度或高度的两部分区域。模仿iview的分割面板组件,用angular实现该功能,支持拖拽和[(ngModel)]双向绑定的方式控制区域的展示收起和拖拽功能。

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.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 = 0
    oldOffset: number | string = 0
    isMoving = false
    initOffset = 0

    _value: number | string = 0.5
    isOpen = true

    @Input()
    tlZIndex = 10

    // @Input()
    // tlMode: "horizontal" | "vertical" = "horizontal"

    /** 是否展示收起icon */
    @Input()
    tlShowExpandIcon = true

    /** 收起容器模式,上下左右哪一个容器应用收起展开的状态 */
    @Input()
    tlColsedMode: "left" | "right" | "top" | "bottom" = "left"

    @Input()
    tlMin = "40px"

    @Input()
    tlMax = "40px"

    @Input()
    tlExpandTooltipContent = ""

    @Input()
    tlContractTooltipContent = ""

    get value() {
        return this._value
    }

    set value(val: number | string) {
        this._value = val
        this.onChange(val)
        this.computeOffset()
    }

    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.value
            if (this.tlColsedMode === "left") {
                this.value = 0
            } else if (this.tlColsedMode === "right") {
                this.value = 1
            } else if (this.tlColsedMode === "top") {
                this.value = 0
            } else if (this.tlColsedMode === "bottom") {
                this.value = 1
            }
        } else {
            // 展开
            this.value = this.expandValueCache
            this.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 = null

    rightTemplate?: TemplateRef<void> | null = null

    topTemplate?: TemplateRef<void> | null = null

    bottomTemplate?: TemplateRef<void> | null = null

    @ViewChild('outerWrapper')
    outerWrapper: ElementRef;


    get isHorizontal() {
        //return this.tlMode === 'horizontal';
        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`] = true
        classes[`${this.prefix}-pane-moving`] = this.isMoving
        return 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() {
        this.offset = (this.valueIsPx ? this.px2percent(this.value as string, this.outerWrapper.nativeElement[this.offsetSize]) : this.value) 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) {
            value = `${parseFloat(this.oldOffset as string) + offset}px`
        } 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 = value
        this.onMoving.emit(e)
    }

    handleUp = (e) => {
        this.isMoving = false;
        off(document, 'mousemove', this.handleMove);
        off(document, 'mouseup', this.handleUp);
        this.onMoveEnd.emit()
    }

    onTriggerMouseDown(e) {
        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 = val
            this.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)" [tTooltip]="tooltipContent" [tooltipPosition]="tooltipPosition" *ngIf="tlShowExpandIcon"></span> -->
                <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)" [tTooltip]="tooltipContent" [tooltipPosition]="tooltipPosition" *ngIf="tlShowExpandIcon"></span> -->
                    <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: #f8f8f9;
@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: 20px;
.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;
        }
    }

    &-trigger{
        border: 1px solid #dcdee2;
        &-con {
            position: absolute;
            transform: translate(-50%, -50%);
            z-index: 10;
        }
        &-bar-con {
            position: absolute;
            overflow: hidden;
            &:hover{
                color: #000 !important;
            }
            &.vertical {
                top: 50%;
                left: -6px;
                width: 20px;
                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: 20px;
                //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.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 = true

  value = 0.3

  expandChange(){
    this.expand = !this.expand
  }

  triggerMoveHnadle(e){
    console.log(e);
    console.log(this.value);  
  }
}

image.png

image.png

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

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

相关文章

为什么 FPGA 比 CPU 和 GPU 快?

FPGA、GPU 与 CPU——AI 应用的硬件选择 现场可编程门阵列 (FPGA) 为人工智能 (AI) 应用带来许多优势。图形处理单元 (GPU) 和传统中央处理单元 (CPU) 相比如何&#xff1f; 人工智能&#xff08;AI&#xff09;一词是指能够以类似于人类的方式做出决策的非人类机器智能。这包…

Excel 2019 for Mac/Win:商务数据分析与处理的终极工具

在当今快节奏的商业环境中&#xff0c;数据分析已经成为一项至关重要的技能。从市场趋势预测到财务报告&#xff0c;再到项目管理&#xff0c;数据无处不在。而作为数据分析的基石&#xff0c;Microsoft Excel 2019 for Mac/Win正是一个强大的工具&#xff0c;帮助用户高效地处…

77 C++对象模型探索。虚函数- 从静态联编,动态联编出发,分析 虚函数调用问题探究

什么叫做单纯的类&#xff1a; 比较简单的类&#xff0c;尤其不包括 虚函数 和虚基类。 什么叫不单纯的类&#xff1a; 从上一章的学习我们知道&#xff0c;在某些情况下&#xff0c;编译器会往类内部增加一些我们看不见但是真实存在的成员变量&#xff0c;例如vptr&#xff…

matlab appdesigner系列-图窗工具2-工具栏

工具栏&#xff0c;就是一般在任意软件界面上方的工具菜单栏 示例&#xff1a;工具菜单绘制正弦函数 操作步骤如下&#xff1a; 1&#xff09;将坐标区和工具栏拖拽到画布上 2)点击工具栏的号&#xff0c;可以看到可以添加2种工具&#xff0c;按钮工具和切换工具&#xff0c…

【JavaScript权威指南第七版】读书笔记速度

JavaScript权威指南第七版 序正文前言&#xff1a;图中笔记重点知识第1章 JavaScript简介第一章总结 第2章 词法结构注释字面量标识符和保留字Unicode可选的分号第二章总结 第3章 类型、值和变量【重要】原始类型特殊类型第三章总结 第4章 表达式与操作符表达式操作符条件式调用…

【量化交易】股市舞者:小明的撮合交易之旅

马西森AES撮合交易系统 在繁华的都市中&#xff0c;小明&#xff0c;一个普通的青年&#xff0c;刚刚赚到了人生的第一桶金——20万。这笔意外的财富&#xff0c;点燃了他对股市的强烈兴趣。他开始如饥似渴地学习金融知识&#xff0c;钻研各种交易策略。 一天&#xff0c;小…

基于 java+springboot+mybatis电影售票网站管理系统前台+后台设计和实现

基于 javaspringbootmybatis电影售票网站管理系统前台后台设计和实现 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承…

微软 Power Apps Canvas App 画布应用将上传的附件转化为base64编码操作

微软 Power Apps Canvas App 画布应用将上传的附件结合Power Automate转化为base64编码操作 在使用canvas app的过程中&#xff0c;我们有时需要将上传的文件转换为base64存入数据库或者&#xff0c;调用外部接口传参&#xff0c;那么看下如何将文件转化为base64编码格式。 首先…

金智易表通构建学生缴费数据查询+帆软构建缴费大数据报表并整合到微服务

使用金智易表通挂接外部数据,快速建设查询类服务,本次构建学生欠费数据查询,共有3块设计,规划如下: 1、欠费明细查询:学校领导和财务处等部门可查询全校欠费学生明细数据;各二级学院教职工可查询本二级学院欠费学生明细数据。 2、大数据统计报表:从应收总额、欠费总额…

C语言编程中的陷阱与规避策略

一、引言 C语言作为一门历史悠久且广泛应用的编程语言&#xff0c;其强大的功能和灵活性深受开发者喜爱。然而&#xff0c;这种灵活性也带来了许多潜在的陷阱和难点&#xff0c;特别是对于新手来说&#xff0c;可能会在编程过程中遇到各种预料之外的问题。本文将深入探讨C语言…

自动验证码解析器:CapSolver的Chrome扩展程序自动解析器

自动验证码解析器&#xff1a;CapSolver的Chrome扩展程序自动解析器 验证码是网站实施的一种安全措施&#xff0c;通常对用户构成挑战。然而&#xff0c;随着技术的进步&#xff0c;验证码解析器已经出现&#xff0c;以简化这一过程。在本文中&#xff0c;我们将探讨专为Googl…

【华为 ICT HCIA eNSP 习题汇总】——题目集9

1、缺省情况下&#xff0c;广播网络上 OSPF 协议 Hello 报文发送的周期和无效周期分别为&#xff08;&#xff09;。 A、10s&#xff0c;40s B、40s&#xff0c;10s C、30s&#xff0c;20s D、20s&#xff0c;30s 考点&#xff1a;①路由技术原理 ②OSPF 解析&#xff1a;&…

【Unity3D日常开发】Unity3D中UGUI的Text、Dropdown输入特殊符号

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中会遇到需要显示特殊符号的情况&#xff0c;比如上标、…

机房及设备安全智慧监管AI+视频方案的设计和应用

一、背景分析 随着互联网的迅猛发展&#xff0c;机房及其配套设施的数量持续攀升&#xff0c;它们的运行状况对于企业运营效率和服务质量的影响日益显著。作为企业信息化的基石&#xff0c;机房的安全监测与管理的重要性不容忽视。它不仅关乎企业的稳定运营&#xff0c;同时也…

[docker] Docker的私有仓库部署——Harbor

一、Docker原生私有仓库—— Registry 1.1 Registry的简单了解 关于Docker的仓库分为私有库和公有仓库&#xff0c;共有仓库只要在官方注册用户&#xff0c;登录即可使用。但对于仓库的使用&#xff0c;企业还是会有自己的专属镜像&#xff0c;所以私有库的搭建也是很有必要的…

Java复习系列之阶段三:框架原理

1. Spring 1.1 核心功能 1. IOC容器 IOC&#xff0c;全称为控制反转&#xff08;Inversion of Control&#xff09;&#xff0c;是一种软件设计原则&#xff0c;用于减少计算机代码之间的耦合度。控制反转的核心思想是将传统程序中对象的创建和绑定由程序代码直接控制转移到…

Android SharedPreferences源码分析

文章目录 Android SharedPreferences源码分析概述基本使用源码分析获取SP对象初始化和读取数据写入数据MemoryCommitResultcommitToMemory()commit()apply()enqueueDiskWrite()writeToFile() 主动等待写回任务结束 总结 Android SharedPreferences源码分析 概述 SharedPrefer…

《Vue3 基础知识》 Vue2+ElementUI 自动转 Vue3+ElementPlus(GoGoCode)

前言 GoGoCode 一个基于 AST 的 JavaScript/Typescript/HTML 代码转换工具。 AST abstract syntax code 抽象语法树。 实现 第一步&#xff1a;安装 GoGoCode 插件 全局安装最新的 gogocode-cli 即可 npm i gogocode-cli -g查看版本 gogocode-cli -V相关插件说明 插件描述…

qt 坦克大战游戏 GUI绘制

关于本章节中使用的图形绘制类&#xff0c;如QGraphicsView、QGraphicsScene等的详细使用说明请参见我的另一篇文章&#xff1a; 《图形绘制QGraphicsView、QGraphicsScene、QGraphicsItem、Qt GUI-CSDN博客》 本文将模仿坦克大战游戏&#xff0c;目前只绘制出一辆坦克&#…

看懂linux内核详解实现分解

一、linux的内核管理&#xff1a;对内核的基本认识 我们所谈到的操作系统主要指内核 以上功能据没有涉及实现文本编辑、实现字处理&#xff0c;也没有服务等等。 故&#xff0c;操作系统是一种通用软件&#xff0c;是平台类软件&#xff0c;自己并不做任何工作&#xff0c;只…