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 => { if (_realSensors[sensor.name]) { sensor.name = _realSensors[sensor.name] } }); // 获取数据后,将原始数据中的dataModel这部分替换掉 ((await getStatisticsInfo(_params))?.data?.list?.[0].dNameDataList ?? [])?.forEach(obj => { if (_realSensorsMap[obj.dName]) { 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;