Machen Sie Betrieb und Wartung sorgenfrei mit der praktischen Analyse des Implementierungsplans für die Inspektionsberichtsfunktion

Mit der Entwicklung der Big-Data-Technologie und der steigenden Nachfrage nach Informationssicherheit hat die kontinuierliche Ausweitung des Datenumfangs den Datenbetrieb und die Datenwartung vor große Herausforderungen gestellt. Angesichts des starken Managementdrucks, der durch riesige Datenmengen verursacht wird, sieht sich das Betriebs- und Wartungspersonal mit Effizienzengpässen konfrontiert, und steigende Arbeitskosten machen es nicht mehr praktikabel, sich bei der Lösung von Problemen ausschließlich auf die Erweiterung des Betriebs- und Wartungsteams zu verlassen.

Es ist ersichtlich, dass Intelligenz, Effizienz und Komfort die unvermeidlichen Richtungen für die Entwicklung von Betrieb und Wartung sind. Die von Kangaroo Cloud eingeführte Inspektionsberichtsfunktion dient genau diesem Ziel und ist bestrebt, optimierte Lösungen bereitzustellen.

Was ist ein Inspektionsbericht?

Unter Inspektionsbericht versteht man den Prozess der Durchführung einer umfassenden Inspektion eines bestimmten Systems oder einer bestimmten Ausrüstung und der Zusammenfassung der Inspektionsergebnisse und Vorschläge in einem Bericht. Inspektionsberichte werden normalerweise verwendet, um den Betriebsstatus und die Leistung von Systemen oder Geräten zu bewerten. Sie dienen als Referenz für die Identifizierung von Problemen, die Optimierung von Systemen, die Verbesserung der Effizienz und die Reduzierung von Ausfallraten.

Datei

In diesem Artikel werden die verschiedenen Funktionsmerkmale des Inspektionsberichts und sein Implementierungsplan näher erläutert und Benutzern mit solchen Anforderungen eine praktische Referenz geboten.

Funktion zur Implementierung des Inspektionsberichts

● Benutzerdefiniertes Layout

· Die Panels im Bericht können per Drag & Drop verschoben werden, um das Layout zu ändern

· Begrenzen Sie den Ziehbereich während des Ziehvorgangs. Das Ziehen ist nur innerhalb derselben übergeordneten Ebene zulässig. Änderungen auf Verzeichnisebene sind nicht zulässig, z. B. das Verschieben eines Verzeichnisses der ersten Ebene. Werden Sie ein sekundäres Verzeichnis

● Das Verzeichnis kann verkleinert und erweitert werden

· Das Verzeichnis unterstützt das Verkleinern und Erweitern. Beim Verkleinern werden alle Unterpanels ausgeblendet und beim Erweitern werden alle Unterpanels angezeigt.

· Beim Verschieben eines Verzeichnisses folgt das Unterpanel der Verschiebung

· Nach dem Ändern des Verzeichnisses wird gleichzeitig das Verzeichnisfeld auf der rechten Seite aktualisiert.

· Katalognummer generieren

Datei

● Verzeichnisbaum rechts

· Katalognummer generieren

· Unterstützen Sie das Scrollen mit Ankern

· Unterstützen Sie die Expansion und Kontraktion

· Verlinkt mit dem Bericht links

Datei

● Datenbereich

· Erhalten Sie Indikatordaten basierend auf dem Datumsbereich

· Anzeige von Indikatorinformationen in Form von Diagrammen

· Details anzeigen, löschen

· Fordern Sie für jedes Panel ein Design an, um Aktualisierungsanfragen zu unterstützen

Datei

Datei

●Panel-Import

· Zählen Sie die Anzahl der im Katalog ausgewählten Panels

· Beim Importieren eines neuen Panels kann das bestehende Layout nicht zerstört werden und das neue Panel kann nur dem alten Panel folgen.

· Beim Importieren eines vorhandenen Panels muss ein Datenvergleich durchgeführt werden . Bei Datenänderungen müssen die neuesten Daten erneut abgerufen werden.

Datei

● Speichern

Vor dem Speichern sind alle Vorgänge im Zusammenhang mit dem Layout temporär, einschließlich des Importierens von Panels. Erst nach einem Klick auf „Speichern“ werden die aktuellen Daten zum Speichern an das Backend übermittelt.

● Unterstützt PDF- und Word-Export

Datei

Umsetzungsplan für den Inspektionsbericht

Wie werden diese Inspektionsberichtsfunktionen implementiert? Im Folgenden werden alle Aspekte des Datenstrukturdesigns , des Komponentendesigns , des Verzeichnisses, des Panels usw. vorgestellt.

Design der Datenstruktur

Schauen wir uns zunächst das Diagramm anhand einer flachen Struktur an:

Datei

In einer flachen Struktur müssen Sie nur das nächste Zeilenfeld finden, um das untergeordnete Verzeichnis zu bestimmen . Das Gleiche gilt für mehrstufige Verzeichnisse, aber das Verzeichnis der ersten Ebene erfordert zusätzliche Verarbeitung.

Obwohl die flache Struktur relativ einfach zu implementieren ist, um bestimmte Anforderungen zu erfüllen, dh das Ziehen und Ablegen von Verzeichnissen einzuschränken. Die Einschränkung des Verzeichnisses erfordert eine relativ klare hierarchische Beziehung zwischen Panels. Offensichtlich kann die Baumdatenstruktur die hierarchische Struktur von Daten sehr angemessen und klar beschreiben.

Datei

Komponentendesign

Es unterscheidet sich von der herkömmlichen Komponentenprogrammierung. Hinsichtlich der Implementierung sind Rendering und Datenverarbeitung getrennt und in zwei Teile unterteilt:

· React-Komponente: Hauptverantwortlich für das Rendern der Seite

· Klasse: Verantwortlicher für die Datenverarbeitung

Datei

DashboardModel

class DashboardModel {
    id: string | number;
    panels: PanelModel[]; // 各个面板
    // ...
}

PanelModel

class PanelModel {
    key?: string;
    id!: number;
    gridPos!: GridPos; // 位置信息
    title?: string;
    type: string;
    panels: PanelModel[]; // 目录面板需要维护当前目录下的面板信息
    // ...
}

Jede Dashboard-Komponente entspricht einem DashboardModel und jede Panel-Komponente entspricht einem PanelModel .

Reaktionskomponenten werden basierend auf den Daten in der Klasseninstanz gerendert. Nachdem die Instanz erstellt wurde, kann sie nicht einfach zerstört oder die Referenzadresse geändert werden. Dadurch wird verhindert, dass React-Komponenten , die zum Rendern auf Instanzdaten angewiesen sind , das Update-Rendering auslösen.

Wir benötigen eine Möglichkeit, das Update-Rendering der Komponente manuell auszulösen, nachdem sich die Daten in der Instanz geändert haben.

● Steuerung des Komponenten-Renderings

Da wir zuvor die Hooks-Komponente verwendet haben, kann die Komponente im Gegensatz zur Class-Komponente durch Aufrufen der Methode „forceUpdate“ ausgelöst werden.

In React18 gibt es eine neue Funktion, useSyncExternalStore , die es uns ermöglicht, externe Daten zu abonnieren. Wenn sich die Daten ändern, wird das Rendern der Komponente ausgelöst.

Tatsächlich besteht das Prinzip des Auslösens des Komponentenrenderings durch useSyncExternalStore darin, einen Status intern beizubehalten. Wenn der Statuswert geändert wird, wird das Rendern externer Komponenten veranlasst.

Basierend auf dieser Idee haben wir einfach eine useForceUpdate-Methode implementiert, die das Rendern von Komponenten auslösen kann .

export function useForceUpdate() {
    const [_, setValue] = useState(0);
    return debounce(() => setValue((prevState) => prevState + 1), 0);
}

Obwohl useForceUpdate implementiert ist, muss das Ereignis im tatsächlichen Gebrauch entfernt werden, wenn die Komponente zerstört wird. UseSyncExternalStore wurde intern implementiert und kann direkt verwendet werden.

useSyncExternalStore(dashboard?.subscribe ?? (() => {}), dashboard?.getSnapshot ?? (() => 0));

useSyncExternalStore(panel?.subscribe ?? (() => {}), panel?.getSnapshot ?? (() => 0));

Je nach Verwendung von useSyncExternalStore werden die Methoden subscribe und getSnapshot hinzugefügt .

class DashboardModel {  // PanelModel 一样 
    count = 0;

    forceUpdate() {
        this.count += 1;
        eventEmitter.emit(this.key);
    }

    /**
     * useSyncExternalStore 的第一个入参,执行 listener 可以触发组件的重渲染
     * @param listener
     * @returns
     */
    subscribe = (listener: () => void) => {
        eventEmitter.on(this.key, listener);
        return () => {
            eventEmitter.off(this.key, listener);
        };
    };

    /**
     * useSyncExternalStore 的第二个入参,count 在这里改变后触发diff的通过。
     * @param listener
     * @returns
     */
    getSnapshot = () => {
        return this.count;
    };
}

Wenn sich die Daten ändern und das Rendern der Komponente ausgelöst werden muss, führen Sie einfach forceUpdate aus.

Panel

●Panel-Ziehen

Zu den beliebtesten Drag-and-Drop-Plug-ins auf dem Markt gehören die folgenden:

· reagieren-schön-dnd

· reagieren-dnd

· React-Grid-Layout

Nach dem Vergleich wurde festgestellt, dass das React-Grid-Layout sehr gut für die Drag-and-Drop-Funktion des Panels geeignet ist. React-Grid-Layout selbst ist einfach zu verwenden und es gibt im Grunde keine Schwelle, um loszulegen. Schließlich habe ich mich für React-Grid-Layout entschieden. Detaillierte Anweisungen finden Sie unter diesem Link: https://github.com/react-grid-layout/react-grid-layout

Nachdem das Panel-Layout geändert wurde, wird die onLayoutChange-Methode von React-Grid-Layout ausgelöst , um die neuesten Positionsdaten aller Panels nach dem Layout abzurufen.

const onLayoutChange = (newLayout: ReactGridLayout.Layout[]) => {
    for (const newPos of newLayout) {
        panelMap[newPos.i!].updateGridPos(newPos);
    }
    dashboard!.sortPanelsByGridPos();
};

PanelMap ist eine Karte, der Schlüssel ist Panel.key und der Wert ist das Panel, das bereit ist, wenn unsere Komponente gerendert wird.

const panelMap: Record<PanelModel['key'], PanelModel> = {};

Um die Panel-Layout-Daten zu aktualisieren, können Sie das entsprechende Panel über PanelMap genau lokalisieren und anschließend dessen updateGridPos-Methode aufrufen , um den Layout-Aktualisierungsvorgang durchzuführen.

Zu diesem Zeitpunkt haben wir nur die Datenaktualisierung des Panels selbst abgeschlossen und müssen außerdem die sortPanelsByGridPos-Methode des Dashboards ausführen, um alle Panels zu sortieren.

class DashboardModel {
    sortPanelsByGridPos() {
        this.panels.sort((panelA, panelB) => {
            if (panelA.gridPos.y === panelB.gridPos.y) {
                return panelA.gridPos.x - panelB.gridPos.x;
            } else {
                return panelA.gridPos.y - panelB.gridPos.y;
            }
        });
    }
    // ...
}

●Panel-Ziehbereich

Der aktuelle Ziehbereich ist das gesamte Dashboard, das nach Belieben gezogen werden kann. Grün ist der ziehbare Bereich des Dashboards und Grau ist das Panel. wie folgt:

Datei

Wenn Einschränkungen erforderlich sind, muss die Struktur wie folgt geändert werden:

Datei

Basierend auf dem Original ist es in Verzeichnisse unterteilt. Grün ist der gesamte verschiebbare Bereich, Gelb ist der Verzeichnisblock der ersten Ebene, der in den grünen Bereich gezogen werden kann. und Lila ist das Verzeichnis der zweiten Ebene. Blöcke können innerhalb des aktuellen gelben Bereichs gezogen werden und können nicht vom aktuellen gelben Block getrennt werden. Graue Felder können nur in das aktuelle Verzeichnis gezogen werden.

Muss basierend auf der ursprünglichen Datenstruktur transformiert werden:

Datei

class PanelModel {
    dashboard?: DashboardModel; // 当前目录下的 dashboard
    // ...
}

● Design des Panels importieren

Datei

Die vom Backend zurückgegebenen Daten sind ein Baum mit drei Ebenen. Nachdem wir sie erhalten haben, verwalten wir die Daten in drei Karten: ModuleMap, DashboardMap und PanelMap.

import { createContext } from 'react';

export interface Module { // 一级目录
    key: string;
    label: string;
    dashboards?: string[];
    sub_module?: Dashboard[];
}

export interface Dashboard { // 二级目录
    key: string;
    dashboard_key: string;
    label: string;
    panels?: number[];
    selectPanels?: number[];
    metrics?: Panel[];
}

export interface Panel {
    expr: Expr[]; // 数据源语句信息
    label: string;
    panel_id: number;
}

type Expr = {
    expr: string;
    legendFormat: string;
};

export const DashboardContext = createContext({
    moduleMap: new Map<string, Module>(),
    dashboardMap: new Map<string, Dashboard>(),
    panelMap: new Map<number, Panel>(),
});

Wenn wir das Modul rendern, durchsuchen wir die ModuleMap und finden das sekundäre Verzeichnis über die Dashboard-Informationen im Modul.

Legen Sie fest, dass das Verzeichnis der ersten Ebene in der Interaktion nicht auswählbar ist. Wenn das Verzeichnis der zweiten Ebene ausgewählt wird, werden die relevanten Panels über die Panels des Dashboards des sekundären Verzeichnisses gefunden und im rechten Bereich gerendert.

Für die Operationen dieser drei Maps werden sie in useHandleData verwaltet und exportiert:

{
    ...map, // moduleMap、dashboardMap、panelMap
    getData, // 生成巡检报告的数据结构
    init: initData, // 初始化 Map
}

●Panel-Auswahl-Hinterfüllung

Beim Aufrufen der Panelverwaltung müssen die ausgewählten Panels aufgefüllt werden. Über getSaveModel können wir die Informationen des aktuellen Inspektionsberichts abrufen und die entsprechenden ausgewählten Informationen in selectPanels speichern.

Jetzt müssen wir nur noch den Wert in selectPanels ändern, um das entsprechende Panel auszuwählen.

● Bedienfeldauswahl zurückgesetzt

Durchqueren Sie die DashboardMap direkt und setzen Sie jedes ausgewählte Panel zurück.

dashboardMap.forEach((dashboard) => {
    dashboard.selectPanels = [];
});

● Panel-Einfügung

Nachdem wir das Panel ausgewählt haben, gibt es beim Einfügen des ausgewählten Panels mehrere Situationen:

· Diesmal wird auch das Panel ausgewählt, das ursprünglich im Inspektionsbericht vorhanden war . Die Daten werden beim Einfügen verglichen. Wenn sich die Daten ändern, müssen sie basierend auf den neuesten Datenquelleninformationen angefordert und gerendert werden.

· Die Panels, die ursprünglich im Inspektionsbericht vorhanden waren, werden dieses Mal nicht ausgewählt. Beim Einfügen müssen die nicht ausgewählten Panels gelöscht werden.

· Das neu ausgewählte Panel wird beim Einfügen am Ende des entsprechenden Verzeichnisses eingefügt

Das Hinzufügen eines neuen Panels erfordert, ähnlich wie beim Verkleinern von Verzeichnissen , außer:

· Die Verzeichnisverkleinerung zielt nur auf ein Verzeichnis ab, während das Einfügen auf das gesamte Verzeichnis abzielt.

· Das Verkleinern des Verzeichnisses erfolgt direkt von den untergeordneten Knoten aus, während die Einfügung vom Wurzelknoten aus beginnt und nach unten eingefügt wird. Nach Abschluss der Einfügung wird das Layout auf der Grundlage der neuesten Verzeichnisdaten aktualisiert.

class DashboardModel {
    update(panels: PanelData[]) {
        this.updatePanels(panels); // 更新面板
        this.resetDashboardGridPos(); // 重新布局
        this.forceUpdate();
    }

    /**
     * 以当前与传入的进行对比,以传入的数据为准,并在当前的顺序上进行修改
     * @param panels
     */
    updatePanels(panels: PanelData[]) {
        const panelMap = new Map();
        panels.forEach((panel) => panelMap.set(panel.id, panel));

        this.panels = this.panels.filter((panel) => {
            if (panelMap.has(panel.id)) {
                panel.update(panelMap.get(panel.id));
                panelMap.delete(panel.id);
                return true;
            }
            return false;
        });

        panelMap.forEach((panel) => {
            this.addPanel(panel);
        });
    }

    addPanel(panelData: any) {
        this.panels = [...this.panels, new PanelModel({ ...panelData, top: this })];
    }

    resetDashboardGridPos(panels: PanelModel[] = this.panels) {
        let sumH = 0;
        panels?.forEach((panel: any | PanelModel) => {
            let h = ROW_HEIGHT;
            if (isRowPanel(panel)) {
                h += this.resetDashboardGridPos(panel.dashboard.panels);
            } else {
                h = panel.getHeight();
            }

            const gridPos = {
                ...panel.gridPos,
                y: sumH,
                h,
            };
            panel.updateGridPos({ ...gridPos });
            sumH += h;
        });

        return sumH;
    }
}

class PanelModel {
    /**
     * 更新
     * @param panel
     */
    update(panel: PanelData) {
        // 数据源语句发生变化需要重新获取数据
        if (this.target !== panel.target) {
            this.needRequest = true;
        }

        this.restoreModel(panel);

        if (this.dashboard) {
            this.dashboard.updatePanels(panel.panels ?? []);
        }

        this.needRequest && this.forceUpdate();
    }
}

● Panel-Anfrage

needRequest steuert, ob das Panel eine Anfrage stellen muss. Wenn dies wahr ist, wird die Anfrage beim nächsten Rendern des Panels gestellt und die Verarbeitung der Anfrage wird auch im PanelModel platziert.

import { Params, params as fetchParams } from '../../components/useParams';

class PanelModel {
    target: string; // 数据源信息

    getParams() {
        return {
            targets: this.target,
            ...fetchParams,
        } as Params;
    }

    request = () => {
        if (!this.needRequest) return;
        this.fetchData(this.getParams());
    };

    fetchData = async (params: Params) => {
        const data = await this.fetch(params);
        this.data = data;
        this.needRequest = false;
        this.forceUpdate();
    };
    
    fetch = async (params: Params) => { /* ... */ }
}

Unsere Datenrendering-Komponenten verfügen im Allgemeinen über eine tiefe Ebene, und bei der Anforderung sind externe Parameter wie Zeitintervalle erforderlich. Diese Parameter werden in Form von globalen Variablen und useParams verwaltet. Die obere Komponente verwendet Change, um Parameter zu ändern, und die Datenrendering-Komponente stellt Anfragen basierend auf den ausgegebenen Parametern.

export let params: Params = {
    decimal: 1,
    unit: null,
};

function useParams() {
    const change = (next: (() => Params) | Params) => {
        if (typeof next === 'function') params = next();
        params = { ...params, ...next } as Params;
    };

    return { params, change };
}

export default useParams;

● Panel-Aktualisierung

Suchen Sie vom Wurzelknoten nach unten, um den Blattknoten zu finden und die entsprechende Anfrage auszulösen.

Datei

class DashboardModel {
    /**
     * 刷新子面板
     */
    reloadPanels() {
        this.panels.forEach((panel) => {
            panel.reload();
        });
    }
}

class PanelModel {
    /**
     * 刷新
     */
    reload() {
        if (isRowPanel(this)) {
            this.dashboard.reloadPanels();
        } else {
            this.reRequest();
        }
    }

    reRequest() {
        this.needRequest = true;
        this.request();
    }
}

● Löschen von Panels

Um ein Panel zu löschen, müssen wir es nur unter dem entsprechenden Dashboard entfernen. Nach dem Löschen wird die aktuelle Dashboard-Höhe geändert. Dieser Vorgang steht im Einklang mit der folgenden Verzeichnisverkleinerung.

class DashboardModel {
    /**
     * @param panel 删除的面板
     */
    removePanel(panel: PanelModel) {
        this.panels = this.filterPanelsByPanels([panel]);

        // 冒泡父容器,减少的高度
        const h = -panel.gridPos.h;
        this.top?.changeHeight(h);

        this.forceUpdate();
    }

    /**
     * 根据传入的面板进行过滤
     * @param panels 需要过滤的面板数组
     * @returns 过滤后的面板
     */
    filterPanelsByPanels(panels: PanelModel[]) {
        return this.panels.filter((panel) => !panels.includes(panel));
    }
    // ...
}

● Speichern Sie das Panel

Nach der Kommunikation mit dem Backend wird die aktuelle Datenstruktur des Inspektionsberichts unabhängig vom Frontend verwaltet und schließlich eine Zeichenfolge an das Backend übergeben. Holen Sie sich die aktuellen Paneldaten und konvertieren Sie sie mit JSON.

Der Informationserfassungsprozess des Panels beginnt am Wurzelknoten, verläuft zu den Blattknoten und beginnt dann an den Blattknoten und kehrt Schicht für Schicht nach oben zurück. Dies ist der Backtracking-Prozess.

class DashboardModel {
    /**
     * 获取所有面板数据
     * @returns
     */
    getSaveModel() {
        const panels: PanelData[] = this.panels.map((panel) => panel.getSaveModel());
        return panels;
    }
    // ...
}

// 最终保存时所需要的属性,其他的都不需要
const persistedProperties: { [str: string]: boolean } = {
    id: true,
    title: true,
    type: true,
    gridPos: true,
    collapsed: true,
    target: true,
};

class PanelModel {
    /**
     * 获取所有面板数据
     * @returns
     */
    getSaveModel() {
        const model: any = {};

        for (const property in this) {
            if (persistedProperties[property] && this.hasOwnProperty(property)) {
                model[property] = cloneDeep(this[property]);
            }
        }
        model.panels = this.dashboard?.getSaveModel() ?? [];

        return model;
    }
    // ...
}

● Anzeige der Paneldetails

Datei

Beim Anzeigen des Panels können Sie die Zeit usw. ändern. Diese Vorgänge wirken sich auf die Daten in der Instanz aus und Sie müssen die Originaldaten von den Daten in den Details unterscheiden.

Durch die Neugenerierung einer PanelModel-Instanz aus den ursprünglichen Paneldaten hat jeder Vorgang an dieser Instanz keinen Einfluss auf die Originaldaten.

const model = panel.getSaveModel();
const newPanel = new PanelModel({ ...model, top: panel.top }); // 创建一个新的实例
setEditPanel(newPanel); // 设置为详情

Auf dem Dom verwendet die Detailseite die absolute Positionierung und deckt den Inspektionsbericht ab.

Inhaltsverzeichnis

● Verzeichnis verkleinern und erweitern

Behalten Sie eine minimierte Eigenschaft für den Verzeichnisbereich bei, um das Ausblenden und Anzeigen des Bereichs zu steuern.

class PanelModel {
    collapsed?: boolean; // type = row
    // ...
}

// 组件渲染
{!collapsed && <DashBoard dashboard={panel.dashboard} serialNumber={serialNumber} />}

Wenn Sie das Verzeichnis verkleinern und erweitern, ändert es seine Höhe. Jetzt müssen Sie diese geänderte Höhe mit dem Dashboard der oberen Ebene synchronisieren.

Was die obere Ebene tun muss, ähnelt der Verarbeitung unseres Kontrollverzeichnisses. Steuern Sie die Verkleinerung des ersten sekundären Verzeichnisses wie folgt :

Datei

Bei Änderungen am Panel muss das übergeordnete Panel benachrichtigt und entsprechende Maßnahmen durchgeführt werden.

Datei

Fügen Sie ein Top hinzu, um die übergeordnete Instanz zu erhalten .

class DashboardModel {
    top?: null | PanelModel; // 最近的 panel 面板

    /**
     * 面板高度变更,同步修改其他面板进行对应高度 Y 轴的变更
     * @param row 变更高度的 row 面板
     * @param h 变更高度
     */
    togglePanelHeight(row: PanelModel, h: number) {
        const rowIndex = this.getIndexById(row.id);

        for (let panelIndex = rowIndex + 1; panelIndex < this.panels.length; panelIndex++) {
            this.panels[panelIndex].gridPos.y += h;
        }
        this.panels = [...this.panels];

        // 顶级 dashBoard 容器没有 top
        this.top?.changeHeight(h);
        this.forceUpdate();
    }
    // ...
}

class PanelModel {
    top: DashboardModel; // 最近的 dashboard 面板

    /**
     * @returns h 展开收起影响的高度
     */
    toggleRow() {
        this.collapsed = !this.collapsed;
        let h = this.dashboard?.getHeight();
        h = this.collapsed ? -h : h;
        this.changeHeight(h);
    }

    /**
     *
     * @param h 变更的高度
     */
    changeHeight(h: number) {
        this.updateGridPos({ ...this.gridPos, h: this.gridPos.h + h }); // 更改自身面板的高度
        this.top.togglePanelHeight(this, h); // 触发父级变更
        this.forceUpdate();
    }
    // ...
}

Organisieren Sie Prozesse und Bubbling-Typen bis hin zum Dashboard auf der obersten Ebene. Expansion und Kontraktion sind gleich.

Datei

● Richtiges Verzeichnis-Rendering

Ankerpunkt/Seriennummer

· Der Ankerpunkt verwendet Anker + ID, um die Komponente auszuwählen

· Seriennummern werden basierend auf jedem Rendering generiert

Verwenden Sie „Publish“ und „Subscribe“, um das Rendering zu verwalten

Immer wenn das Dashboard das Layout ändert, muss das Verzeichnis auf der rechten Seite synchron aktualisiert werden, und jedes Panel muss möglicherweise die Aktualisierung des Verzeichnisses auf der rechten Seite auslösen.

Wenn wir die Rendering-Ereignisse der entsprechenden Komponenten innerhalb der Instanz verwalten, gibt es zwei Probleme:

· Es ist eine Unterscheidung erforderlich, z. B. beim Aktualisieren des Panels ist es nicht erforderlich, das Rendern des Verzeichnisses auf der rechten Seite auszulösen

· Wie jedes Panel die Rendering-Ereignisse des Verzeichnisses auf der rechten Seite abonniert

Schließlich wurde das Publish-Subscriber-Modell zur Verwaltung von Ereignissen übernommen .

class EventEmitter {
    list: Record<string, any[]> = {};

    /**
     * 订阅
     * @param event 订阅事件
     * @param fn 订阅事件回调
     * @returns
     */
    on(event: string, fn: () => void) {}

    /**
     * 取消订阅
     * @param event 订阅事件
     * @param fn 订阅事件回调
     * @returns
     */
    off(event: string, fn: () => void) {}

    /**
     * 发布
     * @param event 订阅事件
     * @param arg 额外参数
     * @returns
     */
    emit(event: string, ...arg: any[]) {
}
eventEmitter.emit(this.key); // 触发面板的订阅事件

eventEmitter.emit(GLOBAL); // 触发顶级订阅事件,就包括右侧目录的更新

PDF-/Word-Export

Der PDF-Export wird durch html2Canvas + jsPDF implementiert. Es ist zu beachten, dass die PDF-Datei das Bild segmentiert und der Inhaltsbereich möglicherweise segmentiert wird, wenn das Bild zu lang ist. Wir müssen die Höhe des Panels manuell berechnen, um zu sehen, ob es die Höhe des aktuellen Dokuments überschreitet. Wenn es die Höhe überschreitet, müssen wir es im Voraus teilen und auf der nächsten Seite hinzufügen. Versuchen Sie, das Verzeichnispanel und das Datenpanel zu teilen so viel wie möglich.

Der Word-Export wird durch html-docx-js implementiert. Dabei muss die Struktur des Verzeichnisses beibehalten und eine Zusammenfassung unter dem Panel hinzugefügt werden. Dies erfordert, dass wir die Bilder für jedes Panel separat konvertieren.

Die Idee der Implementierung besteht darin , Panels zu durchlaufen . Um das Verzeichnispanel zu finden, fügen Sie es mit den Tags h1 und h2 ein. Wenn es sich um ein Datenpanel handelt, pflegen Sie ein Ref-Attribut im Datenpanel, das es uns ermöglicht, die DOM-Informationen abzurufen das aktuelle Panel und führen Sie eine Bildkonvertierung auf dieser Grundlage und im Base64-Format durch (Word unterstützt nur das Einfügen von Base64-Bildern).

schreibe am Ende

Die aktuelle Version des Inspektionsberichts befindet sich noch in den Kinderschuhen und ist nicht in seiner endgültigen Form. Mit nachfolgenden iterativen Upgrades werden wir nach und nach mehrere Funktionen einschließlich zusammenfassender Erläuterungen hinzufügen.

Wenn die UI-Oberfläche nach der aktuellen Implementierung in Zukunft angepasst werden muss, müssen nur relevante UI-Komponenten gezielt geändert werden, z. B. durch das Hinzufügen von Kreisdiagrammen, Tabellen usw. Für Änderungen auf der Ebene der Dateninteraktion müssen Sie lediglich DashboardModel und PanelModel eingeben, um die erforderlichen Aktualisierungen vorzunehmen. Darüber hinaus können wir für bestimmte Szenarien auch spezielle Klassen flexibel zur Verarbeitung extrahieren, um sicherzustellen, dass der gesamte Iterationsprozess modularer und effizienter ist.

Download-Adresse „Dutstack Product White Paper“: https://www.dtstack.com/resources/1004?src=szsm

Downloadadresse „Data Governance Industry Practice White Paper“: https://www.dtstack.com/resources/1001?src=szsm

Wenn Sie mehr über Big-Data-Produkte, Branchenlösungen und Kundenbeispiele erfahren oder sich beraten lassen möchten, besuchen Sie die offizielle Website von Kangaroo Cloud: https://www.dtstack.com/?src=szkyzg

Linus hat es sich zur Aufgabe gemacht, zu verhindern, dass Kernel-Entwickler Tabulatoren durch Leerzeichen ersetzen. Sein Vater ist einer der wenigen Führungskräfte, die Code schreiben können, sein zweiter Sohn ist Direktor der Open-Source-Technologieabteilung und sein jüngster Sohn ist ein Open-Source-Core Mitwirkender : Natürliche Sprache wird immer weiter hinter Huawei zurückfallen: Es wird 1 Jahr dauern, bis 5.000 häufig verwendete mobile Anwendungen vollständig auf Hongmeng migriert sind Der Rich - Text-Editor Quill 2.0 wurde mit einer deutlich verbesserten Erfahrung von Ma Huateng und „ Meta Llama 3 “ veröffentlicht Quelle von Laoxiangji ist nicht der Code, die Gründe dafür sind sehr herzerwärmend. Google hat eine groß angelegte Umstrukturierung angekündigt
{{o.name}}
{{m.name}}

Ich denke du magst

Origin my.oschina.net/u/3869098/blog/11046131
Empfohlen
Rangfolge