GridChart.js 12.5 KB
import React, {memo, useEffect, useMemo, useState} from 'react';
import _, {cloneDeep} from 'lodash';
import {BasicChart} from '@wisdom-components/basicchart';
import PandaEmpty from '@wisdom-components/empty';
import optionGenerator from './utils';
import {getPointAddress, getPointAddressEntry, getSensorsRealName, getSensorType, getStatisticsInfo} from "./apis";
import moment from "moment";

const ChartTitle = ({prefixCls, title, unit}) => {
    const cls = `${prefixCls}-grid-item-title`;
    return (
        <div className={cls}>
            <span className={`${cls}_text`}>{title}</span>
            {unit && <span className={`${cls}_unit`}>(单位:{unit}</span>}
        </div>
    );
};
const GridChart = memo((props) => {
    const {
        dataSource,
        contrast = false,
        contrastOption = 'day',
        smooth = true,
        curveCenter,
        allPointAddress,
        allSensorType,
        dateRange
    } = props;
    const {prefixCls} = props;
    const [gridData, setGridData] = useState([]);
    const [pointAddressEntryData, setPointAddressEntryData] = useState(null);
    const [sensorType, setSensorType] = useState(null);
    const [isInit, setIsInit] = useState(true);
    // 新增逻辑:需要区分出哪些是统计值

    /**
     *  @param {array} dataSource
     */
    const handleDataSource = async (dataSource) => {
        props.setLoading(true);
        // 1. 统计设备
        try {
            let _deviceTypes = [];
            let _deviceCodes = dataSource.reduce((final, cur) => {
                if (!final.includes(cur.stationCode) && !_deviceTypes.includes(cur.deviceType)) {
                    final.push(cur.stationCode);
                    _deviceTypes.push(cur.deviceType);
                }
                return final;
            }, []);
            // 2. 获取对应的版本id
            let _ids = [];
            let _idRequest = await getPointAddress({code: _deviceCodes.join(',')});
            _ids = _idRequest?.data ?? [];
            // 3. 获取对应的点表
            let _map = {};
            for await (let item of _ids) {
                let _index = _deviceCodes.findIndex(code => code === item.code);
                if (pointAddressEntryData && pointAddressEntryData[item.id]) {
                    _map[_deviceTypes[_index]] = pointAddressEntryData[item.id];
                } else {
                    let _entry = await getPointAddressEntry({versionId: item.id});
                    _map[_deviceTypes[_index]] = _entry?.data ?? [];
                    setPointAddressEntryData({...pointAddressEntryData, [item.id]: _entry?.data})
                }
            }
            // 4. 获取点类型
            let _sensorType = []
            if (sensorType) {
                _sensorType = sensorType;
            } else {
                _sensorType = (await getSensorType())?.data ?? [];
            }
            //5. 找出统计值,合并
            let _dataSource = cloneDeep(dataSource);
            let _nameListMap = {};
            let _indexArr = [];
            let _tempValue = {};
            let _finalData = {};
            _dataSource.forEach((item, index) => {
                let _sensorTypeId = _map[item.deviceType].find(sensor => sensor.name === item.sensorName)?.sensorTypeID || 0;
                let _type = _sensorType.find(sensor => sensor.id === _sensorTypeId)?.type ?? '';
                if (_type === '统计值') {
                    // 移除掉,并存储
                    _tempValue[`needToReplace_${item.stationCode}_${item.sensorName}`] = _dataSource.splice(index, 1, `needToReplace_${item.stationCode}_${item.sensorName}`)?.[0];
                    if (!_nameListMap[item.stationCode]) {
                        _nameListMap[item.stationCode] = {
                            code: item.stationCode,
                            deviceType: item.deviceType,
                            sensors: [item.sensorName]
                        }
                    } else {
                        _nameListMap[item.stationCode].sensors.push(item.sensorName)
                    }
                }
            })
            //6. 请求数据并替换数据。grid模式下,请求的时间是一致的。
            let baseParam = {
                pageIndex: 1,
                pageSize: 999,
                dateFrom: dateRange[0].dateFrom,
                dateTo: dateRange[0].dateTo,
            }
            let _arr = Object.values(_nameListMap)
            for await (let item of _arr) {
                let _params = {
                    ...baseParam,
                    accountName: item.deviceType,
                    deviceCode: item.code,
                    nameTypeList: item.sensors.map(sensor => ({
                        name: sensor,
                        type: 'Sub'
                    })),
                    /*                nameTypeList: ['今日用电量', '今日供水量'].map(sensor => ({
                                        name: sensor,
                                        type: 'Sub'
                                    })),*/
                    dateType: returnDateType(dateRange[0])
                };
                // 虚拟点需要查出实际点后,进行查找
                let _realSensors = {};
                let _realSensorsMap = {};
                // 统计类的如果是虚拟点,那么需要查出实际数据来源的点,查出映射关系
                (await getSensorsRealName(_params))?.data?.forEach(sensor => {
                    // name 虚拟点 staticName实际的点
                    _realSensors[sensor.staticName] = sensor.name;
                    _realSensorsMap[sensor.name] = sensor.staticName;
                });
                // 请求统计数据时,需要使用实际点去获取
                _params.nameTypeList.forEach(sensor => {
                    sensor.name = _realSensors[sensor.name]
                });
                // 获取数据后,将原始数据中的dataModel这部分替换掉
                ((await getStatisticsInfo(_params))?.data?.list?.[0].dNameDataList ?? [])?.forEach(obj => {
                    let _v = _tempValue[`needToReplace_${item.code}_${_realSensorsMap[obj.dName]}`];
                    _v.dataModel = obj.nameDate.map(d => {
                        return {
                            pt: moment(d.time),
                            pv: d.value,
                            maxPV: d.value, minPV: d.value, firstPV: d.value, lastPV: d.value,
                        }
                    });
                    _finalData[`needToReplace_${item.code}_${_realSensorsMap[obj.dName]}`] = _v;
                });
                // 替换数据
                _dataSource.forEach((d, index) => {
                    if (_.isString(d) && d.includes('needToReplace') && _finalData[d]) {
                        _dataSource[index] = _finalData[d];
                    }
                })
                // 有不存在数据的,将原始数据替换回来
                _dataSource.forEach((d, index) => {
                    if (_.isString(d) && d.includes('needToReplace')) {
                        _dataSource[index] = dataSource[index];
                    }
                })
            }
            props.setLoading(false);
            return _dataSource
        } catch (e) {
            props.setLoading(false);
            return []
        }
    };
    const returnDateType = (date) => {
        let {dateFrom, dateTo} = date;
        let _duration = moment.duration(moment(dateTo) - moment(dateFrom), 'ms').days();
        if (_duration >= 7) return 'month';
        if (_duration >= 30) return 'year';
        return 'day';
    };
    useEffect(() => {
        async function handle() {
            let _data = isInit ? dataSource : (await handleDataSource(dataSource) ?? []);
            setIsInit(false);
            const grids = _data.reduce((pre, item, index) => {
                const {sensorName, deviceType} = item;
                const key = `${deviceType}_${sensorName}`; // 同设备类型同指标才在同一组
                let grid = pre.find((g) => g.key === key);
                if (!grid) {
                    const restProp = _.pick(item, ['equipmentName', 'sensorName', 'stationCode', 'unit']);
                    grid = {
                        key: key,
                        list: [],
                        ...restProp,
                    };
                    pre.push(grid);
                }
                grid.list.push(item);
                return pre;
            }, []);
            setGridData(grids);
        }

        handle();
    }, [dataSource])
    const options = useMemo(() => {
        let _options = gridData.map((item) => {
            const {key, list, equipmentName, sensorName, stationCode, unit} = item;
            let max = 300;
            // 5:左侧竖条的宽度;10:标题部分的左侧margin;
            // sensorName长度*单个宽度16.7;5:单位部分的左侧margin;
            // 91:单位部分的宽度(格式固定,宽度相对固定)
            let maxTitleLength = 5 + 10 + sensorName.length * 16.7 + 5 + 91;
            let finalLength = maxTitleLength > max ? max : maxTitleLength
            const cusOption = {
                title: {
                    show: true,
                    // text: `{prefix|}{t|${sensorName}}${unit ? '{suffix|(单位:' + unit + ')}' : ''}`,
                    text: ' ',
                    textStyle: {
                        width: finalLength,
                        overflow: 'truncate',
                    },
                },
                legend: {
                    // orient: 'vertical',
                    itemGap: 10,
                    padding: [0, 0, 0, finalLength],
                    textStyle: {
                        width: 120,
                        overflow: 'truncate',
                    },
                },
            };
            const option = optionGenerator(list, cusOption, contrast, contrastOption, smooth, {
                curveCenter,
                nameWithSensor: false,
                showGridLine: true,
                isMultiple: gridData.length > 1
            });
            // 无数据时,图表需要显示默认图形 2024年3月14日
            // 1. x轴
            let dataEmpty = [];
            option.series.forEach(item => {
                if (item.data.length === 0) {
                    dataEmpty.push(true)
                    item.data = [[moment(dataSource?.[0]?.dateFrom).valueOf(), null], [moment(dataSource?.[0]?.dateTo).valueOf(), null]]
                } else {
                    dataEmpty.push(false);
                }
            })
            // 2. y轴
            let allEmpty = dataEmpty.length ? dataEmpty.reduce((final, cur) => {
                if (!cur) final = false;
                return final
            }, true) : true;
            if (allEmpty) {
                option.yAxis.forEach(item => {
                    item.max = 100;
                    item.min = 0;
                });
                option.tooltip = false;
            }
            return {
                key,
                option: option,
            };
        });
        return _options;
    }, [gridData, smooth, curveCenter]);

    return (
        <div className={`${prefixCls}-grid`}>
            {options.map((item, index) => {
                const {sensorName, unit} = gridData[index];
                const isEmpty =
                    !item.option.series.length ||
                    !item.option.series.find((e) => e.data && e.data.length > 0);
                return (
                    <div
                        key={item.key}
                        className={`${prefixCls}-grid-item`}
                        style={{
                            height: gridData.length === 1 ? '100%' : '',
                            width: gridData.length === 1 ? '100%' : '',
                        }}
                    >
                        <div className={`${prefixCls}-grid-item-wrap`}>
                            <ChartTitle prefixCls={prefixCls} title={sensorName} unit={unit}/>
                            {isEmpty ? (
                                isInit ? '' : <PandaEmpty/>
                            ) : (
                                <BasicChart
                                    style={{width: '100%', height: '100%'}}
                                    option={item.option}
                                    notMerge
                                />
                            )}
                        </div>
                    </div>
                );
            })}
        </div>
    );
});

export default GridChart;