검사 보고서 기능 구현 계획에 대한 실질적인 분석으로 운영 및 유지 관리에 대한 걱정이 없습니다.

빅데이터 기술의 발전과 정보보안에 대한 수요 증가로 인해 데이터 규모가 지속적으로 확대되면서 데이터 운영 및 유지관리 업무에 심각한 어려움이 발생하고 있습니다. 방대한 데이터로 인한 과도한 관리 부담으로 인해 운영 및 유지 관리 담당자는 효율성 병목 현상에 직면하고 인건비 상승으로 인해 문제 해결을 위해 운영 및 유지 관리 팀 확대에만 의존하는 것은 더 이상 현실적이지 않습니다.

지능, 효율성 및 편의성은 운영 및 유지 관리 개발의 불가피한 방향임을 알 수 있습니다. Kangaroo Cloud가 출시한 검사 보고서 기능은 바로 이러한 목표를 준수하고 최적화된 솔루션을 제공하기 위해 최선을 다하고 있습니다.

검사 보고서란 무엇입니까?

점검 보고서란 특정 시스템이나 장비에 대해 종합적인 점검을 실시하고, 점검 결과 및 제안사항을 정리하여 보고서로 작성하는 과정을 말합니다. 검사 보고서는 일반적으로 시스템이나 장비의 작동 상태와 성능을 평가하고 문제 식별, 시스템 최적화, 효율성 향상 및 고장률 감소를 위한 참고 자료를 제공하는 데 사용됩니다.

파일

이 기사에서는 검사 보고서의 다양한 기능적 특징과 구현 계획을 자세히 설명하고 그러한 요구가 있는 사용자에게 실용적인 참고 자료를 제공합니다.

검사보고서 실행 기능

● 맞춤 레이아웃

· 보고서의 패널을 드래그 앤 드롭하여 레이아웃을 변경할 수 있습니다.

· 끌기 프로세스 중 끌기 영역을 제한합니다. 끌기는 동일한 상위 수준 내에서만 허용됩니다. 디렉터리 간 이동은 허용되지 않습니다. 첫 번째 수준 디렉터리를 다른 첫 번째 수준 디렉터리로 이동하는 등의 작업은 허용되지 않습니다. 보조 디렉터리가 됩니다

● 디렉토리를 축소 및 확장할 수 있습니다.

· 디렉토리는 축소 및 확장을 지원합니다. 축소 시 모든 하위 패널이 숨겨지고 확장 시 모든 하위 패널이 표시됩니다.

· 디렉토리를 이동할 때 하위 패널도 이동을 따릅니다.

· 디렉토리 변경 후 오른쪽 디렉토리 패널도 동시에 업데이트됩니다.

· 카탈로그 번호 생성

파일

● 오른쪽의 디렉토리 트리

· 카탈로그 번호 생성

· 앵커 스크롤 지원

· 확장 및 축소 지원

· 좌측 보고서와 연동

파일

● 데이터 패널

· 날짜 범위를 기준으로 지표 데이터를 가져옵니다.

· 지표정보를 차트 형태로 표시

· 세부정보 보기, 삭제

· 새로 고침 요청을 지원하기 위해 각 패널에 대한 디자인 요청

파일

파일

●패널 수입

· 카탈로그에서 선택된 패널 수를 계산합니다.

· 새 패널을 가져올 때 기존 레이아웃을 삭제할 수 없으며, 새 패널은 기존 패널을 따를 수만 있습니다.

· 기존 패널을 가져올 때 데이터 비교를 수행해야 합니다 . 데이터 변경이 있는 경우 최신 데이터를 다시 가져와야 합니다.

파일

● 저장

저장하기 전에 패널 가져오기를 포함하여 레이아웃과 관련된 모든 작업은 일시적입니다. 저장을 클릭한 후에만 현재 데이터가 저장을 위해 백엔드에 제출됩니다.

● PDF 및 Word 내보내기 지원

파일

검사보고서 실시계획

그렇다면 이러한 검사 보고서 기능 세트는 어떻게 구현됩니까? 다음은 데이터 구조 설계 , 컴포넌트 설계 , 디렉토리, 패널 등 의 각 측면을 소개 합니다.

데이터 구조 설계

먼저 평면 구조를 사용한 다이어그램을 살펴보겠습니다.

파일

평면 구조에서는 다음 행 패널 만 찾아 자식을 결정하면 됩니다 . 다중 레벨 디렉토리의 경우에도 마찬가지이지만 첫 번째 레벨 디렉토리에는 추가 처리가 필요합니다.

플랫 구조는 구현하기가 상대적으로 간단하지만 특정 요구 사항을 충족하기 위해, 즉 디렉토리의 드래그 앤 드롭을 제한합니다. 디렉토리를 제한하려면 상대적으로 명확한 패널 계층 관계가 필요합니다. 트리 데이터 구조는 데이터의 계층 구조를 매우 적절하고 명확하게 설명할 수 있습니다.

파일

부품 설계

이는 전통적인 컴포넌트 프로그래밍과 다릅니다. 구현 측면에서 렌더링과 데이터 처리는 분리되어 두 부분으로 나뉩니다.

· React 구성 요소: 주로 페이지 렌더링을 담당합니다.

· 직급 : 데이터 처리를 담당

파일

대시보드모델

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

패널모델

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

각 Dashboard 구성 요소는 DashboardModel 에 해당 하고 각 Panel 구성 요소는 PanelModel 에 해당합니다 .

React 구성 요소는 클래스 인스턴스의 데이터를 기반으로 렌더링됩니다. 인스턴스가 생성된 후에는 쉽게 파괴되지 않거나 참조 주소가 변경되지 않습니다. 이렇게 하면 렌더링을 위해 인스턴스 데이터에 의존하는 React 구성 요소가 업데이트 렌더링을 트리거하는 것을 방지할 수 있습니다.

인스턴스의 데이터가 변경된 후 구성 요소의 업데이트 렌더링을 수동으로 트리거하는 방법이 필요합니다.

● 구성요소 렌더링 제어

이전에는 Hooks 컴포넌트를 사용했기 때문에 Class 컴포넌트와 달리 forceUpdate 메소드를 호출하여 컴포넌트를 트리거할 수 있습니다.

React18에는 외부 데이터를 구독할 수 있는 새로운 기능인 useSyncExternalStore가 있습니다 . 데이터가 변경되면 구성 요소 렌더링이 트리거됩니다.

실제로 컴포넌트 렌더링을 트리거하는 useSyncExternalStore의 원칙은 상태 값이 변경되면 외부 컴포넌트의 렌더링을 발생시키는 것입니다.

이 아이디어를 바탕으로 우리는 컴포넌트 렌더링을 트리거할 수 있는 useForceUpdate 메소드를 구현했습니다 .

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

useForceUpdate가 구현되어 있지만 실제 사용 시에는 컴포넌트가 소멸될 때 이벤트를 제거해야 합니다. UseSyncExternalStore는 내부적으로 구현되었으며 직접 사용할 수 있습니다.

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

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

useSyncExternalStore 사용법에 따라 subscribe 메소드와 getSnapshot 메소드가 각각 추가됩니다 .

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;
    };
}

데이터가 변경되고 구성 요소 렌더링을 트리거해야 하는 경우 forceUpdate를 실행하면 됩니다.

패널

●패널 드래그

시장에서 가장 널리 사용되는 드래그 앤 드롭 플러그인은 다음과 같습니다.

· 반응-아름다운-dnd

· 반응-dnd

· 반응 그리드 레이아웃

비교해본 결과, 패널의 드래그 앤 드롭 기능에는 React-Grid-layout이 매우 적합한 것으로 나타났습니다 . React-grid-layout 자체는 사용이 간편하고 기본적으로 시작하는 데 한계가 없습니다. 마침내 저는 React-grid-layout을 사용하기로 결정했습니다. 자세한 지침은 다음 링크에서 확인할 수 있습니다: https://github.com/react-grid-layout/react-grid-layout

패널 레이아웃이 변경되면 React-grid-layout의 onLayoutChange 메서드가 트리거되어 레이아웃 후 모든 패널의 최신 위치 데이터를 가져옵니다.

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

PanelMap은 맵이고, 키는 Panel.key이고, 값은 컴포넌트가 렌더링될 때 준비된 패널입니다.

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

패널 레이아웃 데이터를 업데이트하려면 PanelMap을 통해 해당 패널을 정확하게 찾고, 추가로 updateGridPos 메서드를 호출하여 레이아웃 업데이트 작업을 수행하면 됩니다.

현재 패널 자체의 데이터 업데이트만 완료했으며, 모든 패널을 정렬하려면 대시보드의 sortPanelsByGridPos 메서드도 실행해야 합니다.

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;
            }
        });
    }
    // ...
}

●패널 드래그 범위

현재 드래그 범위는 대시보드 전체이며, 마음대로 드래그할 수 있는 영역입니다. 녹색은 대시보드 의 드래그 가능한 영역 이고, 회색은 패널입니다. 다음과 같이:

파일

제한이 필요한 경우 아래와 같은 구조로 변경해야 합니다.

파일

원본을 기준으로 디렉터리로 나누어져 있으며, 녹색은 전체 이동 가능한 영역이고, 노란색은 녹색 영역에서 드래그할 수 있는 1단계 디렉터리 블록으로, 드래그 시 노란색 블록 전체가 드래그에 사용됩니다. 보라색은 두 번째 수준 디렉터리입니다. 블록은 현재 노란색 영역 내에서 끌 수 있으며 현재 노란색 블록과 분리될 수 없습니다. 회색 패널은 현재 디렉터리에서만 끌 수 있습니다.

원래 데이터 구조를 기반으로 변환해야 합니다.

파일

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

● 패널 디자인 가져오기

파일

백엔드에서 반환된 데이터는 세 가지 수준의 트리입니다. 데이터를 얻은 후에는 ModuleMap, DashboardMap 및 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>(),
});

모듈을 렌더링할 때 ModuleMap을 탐색 하고 모듈의 대시보드 정보를 통해 보조 디렉터리를 찾습니다.

상호 작용에서 첫 번째 수준 디렉터리를 선택할 수 없도록 설정합니다. 두 번째 수준 디렉터리를 선택하면 보조 디렉터리 대시보드의 패널을 통해 관련 패널이 검색되어 올바른 영역에 렌더링됩니다.

이 세 가지 맵의 작업을 위해 useHandleData에서 유지 관리되고 내보내집니다.

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

●패널 선택 백필

패널 관리에 들어가면 선택한 패널을 다시 채워야 합니다. getSaveModel을 통해 현재 검사 보고서의 정보를 얻고 해당 선택된 정보를 selectPanels에 저장할 수 있습니다.

이제 해당 패널을 선택하려면 selectPanels의 값만 변경하면 됩니다.

● 패널 선택 재설정

DashboardMap을 직접 탐색하고 각 selectPanel을 재설정하세요.

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

● 패널 삽입

패널을 선택한 후 선택한 패널을 삽입할 때 여러 가지 상황이 있습니다.

· 이번에도 검사보고서에 원래 있던 패널을 선택하는데, 데이터 삽입 시 비교를 통해 데이터가 변경될 경우 최신 데이터 소스 정보를 기준으로 요청하여 렌더링해야 합니다.

· 검사보고서에 원래 있던 패널은 이번에는 선택되지 않습니다. 삽입 시 선택되지 않은 패널은 삭제해야 합니다.

· 새로 선택한 패널은 삽입시 해당 디렉토리의 마지막에 삽입됩니다.

새 패널을 추가하려면 다음을 제외하고 디렉터리 축소 와 유사해야 합니다 .

· 디렉터리 축소는 하나의 디렉터리만 대상으로 하는 반면, 삽입은 전체 디렉터리를 대상으로 합니다.

· 디렉터리 축소는 하위 노드에서 직접 버블링되고, 삽입은 루트 노드에서 시작되어 아래쪽으로 삽입됩니다. 삽입이 완료된 후 레이아웃은 최신 디렉터리 데이터를 기반으로 업데이트됩니다.

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();
    }
}

● 패널 요청

needRequest는 패널이 요청해야 하는지 여부를 제어합니다. true인 경우 다음에 패널이 렌더링될 때 요청이 이루어지며 요청 처리도 PanelModel에 배치됩니다.

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) => { /* ... */ }
}

우리의 데이터 렌더링 구성 요소는 일반적으로 깊은 수준을 가지며 요청 시 시간 간격과 같은 외부 매개 변수가 필요하며 이러한 매개 변수는 전역 변수 및 useParams 의 형태로 유지 됩니다. 상위 구성 요소는 변경 사항을 사용하여 매개 변수를 수정하고, 데이터 렌더링 구성 요소는 던져진 매개 변수를 기반으로 요청을 수행합니다.

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;

● 패널 새로고침

루트 노드에서 아래쪽으로 검색하여 리프 노드를 찾고 해당 요청을 트리거합니다.

파일

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();
    }
}

● 패널 삭제

패널을 삭제하려면 해당 대시보드 아래에서만 패널을 제거하면 됩니다. 삭제 후 현재 대시보드 높이가 변경됩니다. 이 프로세스는 아래 디렉터리 축소와 일치합니다.

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));
    }
    // ...
}

● 패널 저장

백엔드와 통신한 후 현재 검사 보고서 데이터 구조를 프런트엔드에서 독립적으로 유지 관리하고 마지막으로 백엔드에 문자열을 제공합니다. 현재 패널 데이터를 가져와 JSON으로 변환합니다.

패널의 정보 획득 과정은 루트 노드에서 시작하여 리프 노드 로 이동한 다음 리프 노드에서 시작하여 계층별로 위쪽으로 돌아오는 역추적 프로세스입니다.

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;
    }
    // ...
}

● 패널 세부 정보 표시

파일

패널을 볼 때 시간 등을 수정할 수 있습니다. 이러한 작업은 인스턴스의 데이터에 영향을 미치므로 원본 데이터와 세부 정보의 데이터를 구분해야 합니다.

원본 패널 데이터에서 PanelModel 인스턴스를 다시 생성하면 이 인스턴스에 대한 모든 작업이 원본 데이터에 영향을 주지 않습니다.

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

돔의 세부정보 페이지는 절대 위치 지정을 사용 하고 검사 보고서를 다룹니다.

목차

● 디렉토리 축소 및 확장

패널 숨기기 및 표시를 제어하려면 디렉터리 패널의 축소된 속성을 유지관리하세요 .

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

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

디렉터리를 축소 및 확장하면 높이가 변경됩니다. 이제 변경된 높이를 상위 수준 대시보드에 동기화해야 합니다.

상위 수준에서 수행해야 하는 작업은 제어 디렉터리를 처리하는 것과 유사합니다. 다음과 같이 첫 번째 보조 디렉터리 의 축소를 제어합니다 .

파일

패널에 변경 사항이 발생하면 상위 패널에 통보하고 해당 작업을 수행해야 합니다.

파일

상위 인스턴스를 얻으려면 상단을 추가하십시오 .

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();
    }
    // ...
}

최상위 대시보드까지 프로세스와 버블링 유형을 구성합니다. 팽창과 수축은 동일합니다.

파일

● 올바른 디렉토리 렌더링

앵커 포인트/일련번호

· 앵커 포인트는 Anchor + id를 사용하여 구성 요소를 선택합니다.

· 각 렌더링을 기반으로 일련번호가 생성됩니다.

게시 및 구독을 사용하여 렌더링 관리

대시보드가 ​​레이아웃을 변경할 때마다 오른쪽 디렉터리는 동기식으로 업데이트되어야 하며 모든 패널은 오른쪽 디렉터리 업데이트를 트리거해야 할 수 있습니다.

인스턴스 내에서 해당 구성 요소의 렌더링 이벤트를 유지하는 경우 두 가지 문제가 있습니다.

· 예를 들어 패널을 새로 고칠 때 오른쪽에 있는 디렉터리 렌더링을 트리거할 필요가 없다는 점을 구별해야 합니다.

· 각 패널이 오른쪽 디렉토리의 렌더링 이벤트를 구독하는 방법

마지막으로 이벤트 관리를 위해 게시-구독자 모델이 채택되었습니다 .

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/단어 내보내기

PDF 내보내기는 html2Canvas + jsPDF로 구현됩니다. 이미지가 너무 길면 PDF가 이미지를 분할하고 콘텐츠 영역이 분할될 수 있다는 점에 유의하세요. 패널의 높이가 현재 문서를 초과하는지 수동으로 계산해야 하며, 높이를 초과하는 경우 미리 분할하여 다음 페이지에 추가해야 합니다. 가능한 한 많이.

Word 내보내기는 html-docx-js로 구현됩니다. 디렉터리 구조를 유지하고 패널 아래에 요약을 추가해야 합니다. 이를 위해서는 각 패널의 이미지를 별도로 변환해야 합니다.

구현 아이디어는 패널을 탐색 하는 것입니다 . 디렉토리 패널을 찾으려면 h1 및 h2 태그를 사용하여 삽입하십시오. 데이터 패널의 경우 DOM 정보를 얻을 수 있도록 데이터 패널에 ref 속성을 유지하십시오. 현재 패널을 기반으로 이미지 변환을 수행하고, base64 형식으로 수행합니다(워드에서는 base64 이미지 삽입만 지원함).

마지막에 쓰세요

현재 버전의 검사 보고서는 아직 초기 단계이며 최종 형태가 아닙니다. 이후 반복적인 업그레이드를 통해 점차적으로 요약 설명을 포함한 여러 기능을 추가할 예정입니다.

현재 방식으로 구현한 후 향후 UI 인터페이스를 조정해야 하는 경우 원형 차트, 테이블 등을 추가하는 등 관련 UI 구성요소만 타겟 방식으로 수정하면 됩니다. 데이터 상호 작용 수준에서 변경하려면 DashboardModel 및 PanelModel만 입력하여 필요한 업데이트를 수행하면 됩니다. 또한 특정 시나리오의 경우 처리를 위한 특수 클래스를 유연하게 추출하여 전체 반복 프로세스가 보다 모듈화되고 효율적이도록 보장할 수도 있습니다.

"Dutstack 제품 백서" 다운로드 주소: https://www.dtstack.com/resources/1004?src=szsm

"데이터 거버넌스 산업 실무 백서" 다운로드 주소: https://www.dtstack.com/resources/1001?src=szsm

빅데이터 제품, 산업 솔루션, 고객 사례에 대해 더 알고 싶거나 상담하고 싶은 분들은 Kangaroo Cloud 공식 홈페이지( https://www.dtstack.com/?src=szkyzg )를 방문해 주세요.

Linus는 커널 개발자가 탭을 공백으로 대체하는 것을 막기 위해 스스로 노력했습니다. 그의 아버지는 코드를 작성할 수 있는 몇 안되는 리더 중 한 명이고, 둘째 아들은 오픈 소스 기술 부서의 책임자이며, 막내 아들은 오픈 소스 코어입니다. 기고자 Robin Li: 자연 언어 새로운 범용 프로그래밍 언어가 될 것입니다. 오픈 소스 모델은 Huawei에 비해 점점 더 뒤쳐질 것입니다 . 일반적으로 사용되는 5,000개의 모바일 애플리케이션을 Hongmeng으로 완전히 마이그레이션하는 데 1년이 걸릴 것입니다. 타사 취약점. 기능, 안정성 및 개발자의 경험이 크게 개선된 Quill 2.0 출시되었습니다. Ma Huateng과 Zhou Hongyi는 "원한을 제거하기 위해" 공식적으로 출시되었습니다. Laoxiangji의 소스는 코드가 아닙니다. Google이 대규모 구조 조정을 발표한 이유는 매우 훈훈합니다.
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/3869098/blog/11046131