Angular组件(一) 分割面板ShrinkSplitter

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.mfbz.cn/a/344145.html

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

相关文章

Dock的安装部署和基础命令

1 Docker基础 1.1 Docker概述 Docker是一个开源的应用容器引擎&#xff0c;用来运行容器里的运用&#xff0c;可以用来管理容器和镜像的一种工具&#xff0c;基于go语言开发并遵循了apache2.0协议开源。 Docker是在Linux容器里运行应用的开源工具&#xff0c;是一种轻量级的…

Java(TM) Platform SE binary (Process Id: 4412)

Java™ Platform SE binary (Process Id: 4412&#xff09;JAVA8安装过程中出现上述问题win10解决方法 打开任务管理器 在任务管理器中找到详细信息&#xff0c;然后根据上边的进程id找到对应的程序&#xff0c;右键结束任务即可。 在安装jdk17时候&#xff0c;同时出现了上…

05 双向链表

目录 1.双向链表 2.实现 3.OJ题 4.链表和顺序表对比 1. 双向链表 前面写了单向链表&#xff0c;复习一下 无头单向非循环链表&#xff1a;结构简单&#xff0c;一般不会单独用来存数据。实际中更多作为其他数据结构的子结构&#xff0c;如哈希桶、图的邻接等。另外这种结构在…

Pycharm中出现Comparison with None performed with equality operators

此图中警告翻译过来是 &#xff1a;与使用相等运算符执行的None进行比较 这里不应该使用 或者 &#xff01; 而应改为 is 或者 is not

聚观早报 | 华为P70 Art细节曝光;红魔9 Pro龙年限定版官宣

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 1月24日消息 华为P70 Art细节曝光 红魔9 Pro龙年限定版官宣 努比亚Z60 Ultra龙年限定版 小米14 Ultra或没有双版…

创新医疗服务:宠物在线问诊系统的搭建与应用

随着科技的不断进步&#xff0c;创新的医疗服务方式也日渐成为宠物主人关心爱宠健康的首选。本文将深入介绍如何搭建一套创新的宠物在线问诊系统&#xff0c;并展示其应用的技术代码。 1. 系统架构与技术选择 在开始搭建之前&#xff0c;我们需要设计系统的架构并选择合适的…

【Linux工具篇】编辑器vim

目录 vim的基本操作 进入vim(正常模式&#xff09; 正常模式->插入模式 插入模式->正常模式 正常模式->底行模式 底行模式->正常模式 底行模式->退出vim vim正常模式命令集 vim插入模式命令集 vim末行模式命令集 vim操作总结 vim配置 Linux编译器…

网络安全的使命:守护数字世界的稳定和信任

在数字化时代&#xff0c;网络安全的角色不仅仅是技术系统的守护者&#xff0c;更是数字社会的信任保卫者。网络安全的使命是保护、维护和巩固数字世界的稳定性、可靠性以及人们对互联网的信任。本文将深入探讨网络安全是如何履行这一使命的。 第一部分&#xff1a;信息资产的…

使用Sobel算子把视频转换为只剩边缘部分

效果展示 原始视频 修改后的视频 整体代码 import cv2vc cv2.VideoCapture(test.mp4)if vc.isOpened():open, frame vc.read() else:open Falsei 0 while open:ret, frame vc.read()if frame is None:breakif ret True:i 1# 转换为灰度图gray cv2.cvtColor(frame, cv…

复合机器人颠覆传统上下料,实现高效精准生产

在追求高效、精准生产的现代制造业中&#xff0c;传统的上下料方式已经无法满足企业的需求。复合机器人的出现&#xff0c;为制造业带来了革命性的变革。它不仅提高了生产效率&#xff0c;降低了生产成本&#xff0c;还为企业创造了更大的竞争优势。复合机器人的广泛应用&#…

多维时序 | Matlab实现CNN-BiGRU-Mutilhead-Attention卷积双向门控循环单元融合多头注意力机制多变量时间序列预测

多维时序 | Matlab实现CNN-BiGRU-Mutilhead-Attention卷积双向门控循环单元融合多头注意力机制多变量时间序列预测 目录 多维时序 | Matlab实现CNN-BiGRU-Mutilhead-Attention卷积双向门控循环单元融合多头注意力机制多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一…

GD32E230C8T6《调试篇》之 FMC(闪存)的读写 + USART打印

实验&#xff1a;按键DIG4&#xff08;保存键&#xff09;&#xff0c;任意按下一个数字后&#xff0c;再按保存键写入flash;断电后重新上电&#xff0c;从 flash里读值&#xff0c;显示到数码管 实验工具GD32E230C8T6查看GPIO查看Datasheet 2.6.7章节GPIO 复用 查看用户手册代…

Python Django的学生选课管理系统,实现多用户登录注册,可选课可评课

学生选课管理系统是一个基于Python Django开发的教务管理系统&#xff0c;旨在提供方便快捷的选课服务和学籍管理功能。该系统分为教师端和学生端两个角色&#xff0c;为教师和学生提供了不同的功能和权限。 教师端功能&#xff1a; 教师可以登录系统后&#xff0c;进行课程管…

如何在 Ubuntu 20.04 上安装 Nginx

前些天发现了一个人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;最重要的屌图甚多&#xff0c;忍不住分享一下给大家。点击跳转到网站。 如何在 Ubuntu 20.04 上安装 Nginx 介绍 Nginx是世界上最受欢迎的 Web 服务器之一&#xff0c;负责托管互联网…

【LeetCode-406】根据身高重建队列(贪心)

LeetCode406.根据身高重建队列 题目描述 题目链接 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &#xff0c;前面 正好 有 ki 个身高大于…

[C++]使用yolov8的onnx模型仅用opencv和bytetrack实现目标追踪

【官方框架地址】 yolov8: https://github.com/ultralytics/ultralytics bytetrack: https://github.com/ifzhang/ByteTrack 【算法介绍】 随着人工智能技术的不断发展&#xff0c;目标追踪已成为计算机视觉领域的重要研究方向。Yolov8和ByTetrack作为当前先进的算法&…

设计模式——1_6 代理(Proxy)

诗有可解不可解&#xff0c;若镜花水月勿泥其迹可也 —— 谢榛 文章目录 定义图纸一个例子&#xff1a;图片搜索器图片加载搜索器直接在Image添加组合他们 各种各样的代理远程代理&#xff1a;镜中月&#xff0c;水中花保护代理&#xff1a;对象也该有隐私引用代理&#xff1a;…

成熟的内外网数据交换方案,如何实现跨网传输?

网络迅速发展&#xff0c;我们可以从网络上查找到各式各样的信息&#xff0c;但是同时网络安全问题也随之严重。近几年&#xff0c;各种有关网络安全的新闻不断被报道&#xff0c;数据泄露给很多企业带来了严重打击&#xff0c;不仅是经济损失&#xff0c;严重者还会对企业的声…

二进制计算

二进制的引入 十进制规则:满10进1&#xff0c;由数字0到9组成。 而所谓十六进制&#xff0c;八进制&#xff0c;二进制的规则也是类似。 这里为了区分十六进制和八进制&#xff0c;十六进制前面会加上0x&#xff0c;八进制前面会加个0作为区分 而二进制的规则类似于十进制&…

【时间序列篇】基于LSTM的序列分类-Pytorch实现 part2 自有数据集构建

系列文章目录 【时间序列篇】基于LSTM的序列分类-Pytorch实现 part1 案例复现 【时间序列篇】基于LSTM的序列分类-Pytorch实现 part2 自有数据集构建 【时间序列篇】基于LSTM的序列分类-Pytorch实现 part3 化为己用 在一个人体姿态估计的任务中&#xff0c;需要用深度学习模型…
最新文章