import React, {useContext, useEffect, useMemo, useState, useCallback, useRef} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Checkbox, ConfigProvider, DatePicker, Radio, Select, Spin, Tabs, Tooltip, Button, message, Progress, } from 'antd'; import { CloseCircleFilled, PlusCircleOutlined, QuestionCircleFilled, DownloadOutlined, } from '@ant-design/icons'; import moment from 'moment'; import _ from 'lodash'; import TimeRangePicker from '@wisdom-components/timerangepicker'; import PandaEmpty from '@wisdom-components/empty'; import BasicTable from '@wisdom-components/basictable'; import {getHistoryInfo, getDeviceAlarmScheme, getExportDeviceHistoryUrl, getDictionaryInfoAll} from './apis'; import SingleChart from './SingleChart'; import GridChart from './GridChart'; import './index.less'; import {globalConfig} from 'antd/lib/config-provider'; const {RangePicker} = DatePicker; const {Option} = Select; const startFormat = 'YYYY-MM-DD 00:00:00'; const endFormat = 'YYYY-MM-DD 23:59:59'; const timeFormat = 'YYYY-MM-DD HH:mm:ss'; const dateFormat = 'YYYYMMDD'; const timeList = [ { key: 'twelveHours', name: '近12小时', }, { key: 'roundClock', name: '近24小时', }, { key: 'yesterday', name: '昨日' }, { key: 'oneWeek', name: '近1周', }, { key: 'oneMonth', name: '近1月', }, ]; // 同期对比 日 快捷按钮 const shortcutsForDay = [ { label: '近3天', value: '近3天' }, { label: '近7天', value: '近7天' }, /* { label: '去年同期', value: '去年同期', }*/ ]; // 同期对比 月 快捷按钮 const shortcutsForMonth = [ { label: '近3月', value: '近3月' }, { label: '近6月', value: '近6月' }, /* { label: '去年同期', value: '去年同期', }*/ ] const CheckboxData = [ { key: 'curveCenter', label: '曲线居中', checked: false, showInCurve: true, showInTable: false, }, { key: 'chartGrid', label: '图表网格', checked: true, showInCurve: true, showInTable: false, }, { key: 'ignoreOutliers', label: '曲线降噪', type: 'updateIgnoreOutliers', checked: false, showInCurve: true, showInTable: true, tooltip: '本算法采用递推平均滤波法(滑动平均滤波法)对采样数据进行均值化平滑处理。', hasSub: true }, { key: 'dataThin', label: '数据抽稀', type: 'updateDataThin', checked: true, showInCurve: false, showInTable: true, }, ]; const timeIntervalList = [ { key: '5', zoom: '5', unit: 'min', name: '5分钟', }, { key: '10', zoom: '10', unit: 'min', name: '10分钟', }, { key: '30', zoom: '30', unit: 'min', name: '30分钟', }, { key: '1', zoom: '1', unit: 'h', name: '1小时', }, { key: '2', zoom: '2', unit: 'h', name: '2小时', }, { key: '4', zoom: '4', unit: 'h', name: '4小时', }, { key: '6', zoom: '6', unit: 'h', name: '6小时', }, { key: '12', zoom: '12', unit: 'h', name: '12小时', }, ]; const updateTime = (key) => { let start = ''; let end = ''; if (Array.isArray(key)) { start = moment(key[0]).format(timeFormat); end = moment(key[1]).format(timeFormat); } else { switch (key) { case 'twelveHours': start = moment().subtract(12, 'hour').format(timeFormat); end = moment().format(timeFormat); break; case 'roundClock': start = moment().subtract(24, 'hour').format(timeFormat); end = moment().format(timeFormat); break; case 'yesterday': start = moment().subtract(1, 'days').format('YYYY-MM-DD 00:00:00'); end = moment().subtract(1, 'days').format('YYYY-MM-DD 23:59:59'); break; case 'oneWeek': start = moment().subtract(7, 'day').format(timeFormat); end = moment().format(timeFormat); break; case 'oneMonth': start = moment().subtract(30, 'day').format(timeFormat); end = moment().format(timeFormat); break; } } return [ { dateFrom: start, dateTo: end, }, ]; }; const DefaultDatePicker = (value) => [ { key: 1, value: moment(), }, { key: 2, value: moment().subtract(1, value), }, ]; const handleBatchTime = (arr, cOption) => { let newArr = []; arr.forEach((child) => { if (child.value) { newArr.push({ dateFrom: moment(child.value).startOf(cOption).format(startFormat), dateTo: moment(child.value).endOf(cOption).format(endFormat), }); } }); newArr = _.uniqWith(newArr, _.isEqual); // 去掉重复日期时间 return newArr; }; const timeColumn = { title: '采集时间', dataIndex: 'time', key: 'time', width: 170, fixed: 'left', ellipsis: true, align: 'center', sorter: true, // sortOrder:['descend','ascend'] }; const OriginMaxDays = 31; // 原始曲线请求数据的最大天数 const CharacteristicMaxDays = null; // 特征曲线或者其他曲线的最大天数 const HistoryView = (props) => { const [completeInit, setCompleteInit] = useState(false); const {getPrefixCls} = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('history-view'); const { title, grid, defaultChecked, tableProps, deviceParams, defaultModel, showModels, needMarkLine, defaultDate, } = props; const isBoxPlots = deviceParams?.length === 1 && deviceParams?.[0]?.sensors?.split(',').length === 1; const [loading, setLoading] = useState(null); const [activeTabKey, setActiveTabKey] = useState(defaultModel); // 时间模式: 自定义模式/同期对比模式 const [timeValue, setTimeValue] = useState('customer'); // 自定义模式 const [customerChecked, setCustomerChecked] = useState(defaultChecked); // 时间快速选择类型值 const [customerTime, setCustomerTime] = useState(); // 自定义时间选择值 // 同期对比模式 const [contrastOption, setContrastOption] = useState('day'); // 对比时间类型: 日/月 const [datePickerArr, setDatePickerArr] = useState(DefaultDatePicker(defaultDate)); // 对比时间段配置值 const [checkboxData, setCheckboxData] = useState(() => [...CheckboxData]); // 曲线设置项 const [dataThinKey, setDataThinKey] = useState(timeIntervalList[0].key); // 曲线抽稀时间设置 const [algorithmValue, setAlgorithmValue] = useState(1); const [columns, setColumns] = useState([]); const [tableData, setTableData] = useState([]); const [chartDataSource, setChartDataSource] = useState([]); const [timeOrder, setTimeOrder] = useState('descend'); const [chartType, setChartType] = useState('lineChart'); const [showBoxOption, setShowBoxOption] = useState(true); const [lineDataType, setLineDataType] = useState('特征曲线'); // 同期对比快捷键 // shortcutsValue // onShortcutsChange const [shortcutsValue, setShortcutsValue] = useState(''); const [shortcutsDatePickerArr, setShortcutsDatePickerArr] = useState([]); const [percent, setPercent] = useState(0); // 选择的时间范围值 const dateRange = useMemo(() => { if (timeValue === 'customer') { return updateTime(customerChecked || customerTime); } else { let _dateArr = shortcutsValue ? shortcutsDatePickerArr : datePickerArr; return handleBatchTime(_dateArr, contrastOption); } }, [contrastOption, customerChecked, customerTime, datePickerArr, timeValue, shortcutsValue]); const [dates, setDates] = useState(null); const configDependence = checkboxData .filter((item) => ['curveCenter', 'chartGrid'].indexOf(item.key) === -1) .map((item) => item.checked) .join(','); // 数据配置 const dataConfig = useMemo(() => { const initial = { ignoreOutliers: false, dataThin: false, zoom: '', // 数据抽稀时间 unit: '', // 数据抽稀时间单位 }; // 曲线居中,过滤异常值,数据抽稀 const config = checkboxData.reduce( (pre, item) => (item.key !== 'curveCenter' && (pre[item.key] = item.checked), pre), initial, ); // 数据抽稀时间单位 const dataThin = timeIntervalList.find((item) => item.key === dataThinKey); config.zoom = activeTabKey === 'curve' ? '' : dataThin?.zoom ?? ''; config.unit = activeTabKey === 'curve' ? '' : dataThin?.unit ?? ''; config.dataThin = activeTabKey === 'curve' ? true : config.dataThin; // 曲线强制抽稀 return config; }, [configDependence, dataThinKey, activeTabKey]); // 图表居中 const [curveCenter, chartGrid] = useMemo(() => { const curveCenter = checkboxData.find((item) => item.key === 'curveCenter')?.checked; const chartGrid = checkboxData.find((item) => item.key === 'chartGrid')?.checked; return [curveCenter, chartGrid]; }, [checkboxData]); // 自定义模式: 快速选择 const onCustomerTimeChange = (key) => { /* if (key === 'oneMonth' && lineDataType === '原始曲线') { setLineDataType('特征曲线'); message.info('月模式数据量较大,不支持原始曲线模式,已切换为特征曲线') }*/ setCustomerChecked(key); !!customerTime && setCustomerTime(null); }; // 自定义模式: 自定义时间选择 const onCustomerRangeChange = (value) => { if (!value) { // 时间清空,回到默认时间选择 setCustomerChecked(defaultChecked); setCustomerTime(value); } else { setCustomerChecked(null); let diffDays = moment(value[1]).diff(moment(value[0]), 'days'); if (diffDays > OriginMaxDays && lineDataType === '原始曲线') { setLineDataType('特征曲线'); message.info('时间区间超过7天,已切换为特征曲线'); } setCustomerTime(value); } }; // 同期对比模式: 选择(日/月) const onContrastChange = (value) => { if (value === 'month') { if (lineDataType === '原始曲线') message.info('月模式数据量较大,不支持原始曲线模式,已切换为特征曲线') setLineDataType('特征曲线'); } setShortcutsValue(''); setContrastOption(value); // 模式为日时,默认对比时间根据defaultDate判断 是昨天、上月、还是去年 setDatePickerArr([...DefaultDatePicker(value === 'day' && defaultDate ? defaultDate : value)]); }; // 同期对比模式: 时间段选择 const onContrastPickerChange = (date, dateString, item) => { // 操作时间就清除掉快捷键选用状态 setShortcutsValue(''); const arr = [...datePickerArr]; arr.forEach((child) => { if (child.key === item.key) { child.value = date; } }); setDatePickerArr(arr); }; // 同期对比模式: 新增日期选择组件 const handleAddDatePicker = () => { // 操作时间就清除掉快捷键选用状态 setShortcutsValue(''); setDatePickerArr([ ...datePickerArr, { key: datePickerArr[datePickerArr.length - 1].key + 1, value: '', }, ]); }; // 同期对比模式: 删除日期选择组件 const handleDeleteDatePicker = (index) => { // 操作时间就清除掉快捷键选用状态 setShortcutsValue(''); const arr = [...datePickerArr]; arr.splice(index, 1); setDatePickerArr(arr); }; // 时间设置切换(自定义/同期对比) const onTimeSetChange = (e) => { // 操作时间就清除掉快捷键选用状态 setShortcutsValue(''); if (e.target.value === 'customer') { setLineDataType('特征曲线'); setShortcutsValue(''); } setTimeValue(e.target.value); if (e.target.value === 'contrast') { // 同期对比 onContrastChange(contrastOption); setShowBoxOption(false); setChartType('lineChart'); onCheckboxChange({target: {value: false}}, 'chartType'); onCheckboxChange({target: {value: false}}, 'ignoreOutliers'); } else { // 自定义 // 不需要处理 setShowBoxOption(true); onCheckboxChange({target: {value: true}}, 'chartType'); } }; const onShortcutsChange = (e) => { let _val = e.target.value; setShortcutsValue(_val); let _arr = []; switch (_val) { case '近3天': _arr = [ {key: 1, value: moment()}, {key: 2, value: moment().subtract(1, 'days')}, {key: 3, value: moment().subtract(2, 'days')}, ] break; case '近7天': _arr = [ {key: 1, value: moment()}, {key: 2, value: moment().subtract(1, 'days')}, {key: 3, value: moment().subtract(2, 'days')}, {key: 4, value: moment().subtract(3, 'days')}, {key: 5, value: moment().subtract(4, 'days')}, {key: 6, value: moment().subtract(5, 'days')}, {key: 7, value: moment().subtract(6, 'days')}, ] break; case '近3月': _arr = [ {key: 1, value: moment()}, {key: 2, value: moment().subtract(1, 'months')}, {key: 3, value: moment().subtract(2, 'months')}, ] break; case '近6月': _arr = [ {key: 1, value: moment()}, {key: 2, value: moment().subtract(1, 'months')}, {key: 3, value: moment().subtract(2, 'months')}, {key: 4, value: moment().subtract(3, 'months')}, {key: 5, value: moment().subtract(4, 'months')}, {key: 6, value: moment().subtract(5, 'months')}, ] break; } setShortcutsDatePickerArr(_arr); }; const renderTimeOption = useMemo(() => { return ( <div className={classNames(`${prefixCls}-date`)}> <div className={classNames(`${prefixCls}-label`)}>时间选择</div> <Radio.Group value={timeValue} onChange={onTimeSetChange}> <Radio.Button value="customer">自定义</Radio.Button> { !grid ? <Radio.Button value="contrast">同期对比</Radio.Button> : '' } </Radio.Group> {timeValue === 'customer' && ( // 自定义 <> <TimeRangePicker format={'YYYY-MM-DD HH:mm'} onChange={onCustomerTimeChange} value={customerChecked} /* dataSource={timeList.filter(item => { if (lineDataType === '原始曲线') return item.key !== 'oneMonth'; return true })}*/ dataSource={timeList} /> <RangePicker format={'YYYY-MM-DD HH:mm'} className={classNames(`${prefixCls}-custime-customer`)} onChange={onCustomerRangeChange} value={dates || customerTime} onCalendarChange={(val) => { setDates(val); }} onOpenChange={(open) => { if (open) { setDates([null, null]); } else { setDates(null); } }} disabledDate={(current) => { if (timeValue !== 'customer') return false; let _days = lineDataType === '原始曲线' ? OriginMaxDays : CharacteristicMaxDays; if (!dates) { return false; } if (!_days) return false; const tooLate = dates[0] && current.diff(dates[0], 'days') > _days; const tooEarly = dates[1] && dates[1].diff(current, 'days') > _days; return !!tooEarly || !!tooLate; }} showTime={{ format: 'YYYY-MM-DD HH:mm', minuteStep: 10 }} /> </> )} {timeValue === 'contrast' && ( // 同期对比 <> <Select value={contrastOption} style={{width: 60}} onChange={onContrastChange}> <Option value="day">日</Option> <Option value="month" disabled={lineDataType === '原始曲线'}>月</Option> </Select> {/*增加快捷日期*/} { deviceParams?.length === 1 && deviceParams?.[0]?.sensors?.split(',').length === 1 ? <Radio.Group value={shortcutsValue} onChange={onShortcutsChange}> { (contrastOption === 'day' ? shortcutsForDay : shortcutsForMonth).map(item => { return <Radio.Button value={item.value}> {item.label} </Radio.Button> }) } </Radio.Group> : '' } {datePickerArr.map((child, index) => ( <div key={child.key} className={classNames(`${prefixCls}-contrast-list`)}> <div className={classNames(`${prefixCls}-contrast-wrap`)}> <DatePicker picker={contrastOption} value={child.value} onChange={(date, dateString) => onContrastPickerChange(date, dateString, child)} style={{width: 130, border: !shortcutsValue ? '1px solid #1890ff' : ''}} /> {datePickerArr.length > 2 && ( <div className={classNames(`${prefixCls}-contrast-delete`)} onClick={() => handleDeleteDatePicker(index)} > <CloseCircleFilled/> </div> )} </div> {index < datePickerArr.length - 1 && ( <div className={classNames(`${prefixCls}-contrast-connect`)}>与</div> )} </div> ))} {datePickerArr.length < 4 && <PlusCircleOutlined onClick={handleAddDatePicker}/>} </> )} </div> ); }, [timeValue, customerChecked, lineDataType, datePickerArr, deviceParams, dates, customerTime, chartDataSource]); // 曲线设置项选择/取消 const onCheckboxChange = (e, key, showJustLine) => { let data = [...checkboxData]; // let _index = data.findIndex(item => item.key === 'justLine'); // 仅查看曲线会在勾选了数据滤波后展示 let _index1 = data.findIndex((item) => item.key === 'ignoreOutliers'); // 仅查看曲线会在勾选了数据滤波后展示 data.forEach((item) => { if (item.key === key) { item.checked = e.target.checked; } }); if (key === 'ignoreOutliers') { // 需求变更,仅查看曲线剔除 /* if (showJustLine) { data[_index].showInCurve = e.target.checked; data[_index].checked = e.target.checked; } else {*/ data[_index1].showInCurve = true; // data[_index1].checked = false; // } } if (key === 'chartType') { data[_index1].showInCurve = e.target.value; data[_index1].checked = false; // data[_index].showInCurve = false; // data[_index].checked = false; } setCheckboxData(data); }; // 数据抽稀时间间隔 const onTimeIntervalChange = (value) => { setDataThinKey(value); }; // 切换数据类型 const switchLineDataType = (e) => { let _val = e.target.value let _startDate = dateRange[0]?.dateFrom; let _endDate = dateRange[0]?.dateTo; let diffDays = moment(_endDate).diff(moment(_startDate), 'days'); if (_val === '原始曲线' && diffDays > OriginMaxDays) { message.info('查阅原始曲线时,需选择小于或等于31天的时间间隔,已自动切换为近一月'); setCustomerChecked('oneMonth'); } if (_val === '原始曲线') { setContrastOption('day'); } setLineDataType(_val) }; const renderCheckbox = (child, showJustLine) => { const curveAccess = activeTabKey === 'curve' && child.showInCurve; const tableAccess = activeTabKey === 'table' && child.showInTable; const gridOptions = ['curveCenter']; if (grid && curveAccess && gridOptions.indexOf(child.key) === -1) return null; return ( (curveAccess || tableAccess) && ( <> <Checkbox checked={child.checked} onChange={(e) => onCheckboxChange(e, child.key)}> {child.label} </Checkbox> {child.tooltip && ( <Tooltip title={child.tooltip}> <QuestionCircleFilled className={`${prefixCls}-question`}/> </Tooltip> )} { child.hasSub && child.checked && false ? <Select style={{width: 80, marginLeft: 10}} value={algorithmValue} onChange={(e) => setAlgorithmValue(e)}> <Option value={1}>低</Option> <Option value={5}>中</Option> <Option value={10}>高</Option> </Select> : '' } </> ) ); }; const renderCurveOption = (isChart, isSingle) => { return ( <div className={classNames(`${prefixCls}-cover`)} style={isChart && isSingle ? {width: '100%'} : {}} > { isChart ? <> <div className={classNames(`${prefixCls}-label`)}>曲线选择</div> <div className={`${prefixCls}-cover-item`}> <Radio.Group value={lineDataType} onChange={switchLineDataType} > <Radio.Button value={'特征曲线'}>特征曲线</Radio.Button> <Radio.Button value={'原始曲线'}>原始曲线</Radio.Button> </Radio.Group> {/*<Segmented value={lineDataType} options={['特征曲线', '原始曲线']} onChange={switchLineDataType}/>*/} <Tooltip title={'原始曲线数据量较大,单次查询最多展示1万条数据'}> <QuestionCircleFilled style={{marginLeft: 6}} className={`${prefixCls}-question`}/> </Tooltip> </div> </> : '' } {isChart && isSingle && showBoxOption ? ( <> { lineDataType !== '原始曲线' ? <> <div style={{marginLeft: 7}} className={classNames(`${prefixCls}-label`)}>曲线形态</div> <Radio.Group value={chartType} style={{marginRight: 16}} onChange={(e) => { let _value = e.target.value; setChartType(_value); onCheckboxChange({target: {value: _value !== 'boxChart'}}, 'chartType'); }} > <Radio.Button value={'lineChart'}>线形图</Radio.Button> <Radio.Button value={'boxChart'}>箱线图</Radio.Button> </Radio.Group> </> : '' } </> ) : ( '' )} <div className={classNames(`${prefixCls}-label`)}> {activeTabKey !== 'table' ? '曲线设置' : '表格设置'} </div> {checkboxData.map((child) => { const box = renderCheckbox(child, isChart && isSingle); if (!box) return null; return ( <div key={child.key} className={`${prefixCls}-cover-item`}> {box} </div> ); })} {activeTabKey === 'table' && ( <Select value={dataThinKey} style={{width: 90}} onChange={onTimeIntervalChange} disabled={!dataConfig.dataThin} > {timeIntervalList.map((child) => ( <Option key={child.key} unit={child.unit} value={child.key}> {child.name} </Option> ))} </Select> )} </div> ); }; const exportExcelBtn = () => { deviceParams.forEach((i, r) => { let timeFrom = dateRange[r]?.dateFrom || moment().format(startFormat); let timeTo = dateRange[r]?.dateTo || moment().format(timeFormat); let fileName = `数据报表-${i.deviceType}-${i.deviceCode}-${moment(timeFrom).format( dateFormat, )}至${moment(timeTo).format(dateFormat)}`; let _quotas = i.sensors.split(',').filter(item => item !== '是否在线').join(',') getExportDeviceHistoryUrl({ deviceType: i.deviceType, deviceCode: i.deviceCode, quotas: _quotas, startTime: timeFrom, endTime: timeTo, fileName: fileName, }) .then((res) => { if (res && res.code === -1) return message.error(res.msg); const url = `${window.location.origin}/PandaCore/GCK/FileHandleContoller/Download/name?name=${res.data}&_site=${globalConfig?.userInfo?.site}`; const aDom = document.createElement('a'); aDom.href = url; aDom.click(); aDom.remove(); }) .catch((err) => { }); }); }; const handleTableData = useCallback((data) => { // eslint-disable-next-line no-param-reassign data = data.filter(item => item.sensorName !== '是否在线'); const ignoreOutliers = checkboxData.find((item) => item.key === 'ignoreOutliers').checked; const dataIndexAccess = (dataItem, index) => { const {stationCode, sensorName} = dataItem; return `${stationCode}-${sensorName}-${index}`; }; let format = timeFormat; if (timeValue === 'contrast') { format = contrastOption === 'day' ? '2020-01-01 HH:mm:00' : '2020-01-DD HH:mm:00'; } // 处理表头数据 const columnsData = data.map((item, index) => { const {stationCode, equipmentName, sensorName, unit, dataModel} = item; const dataIndex = dataIndexAccess(item, index); let col = { title: `${equipmentName}-${sensorName}${unit ? `(${unit})` : ''}`, dataIndex: dataIndex, key: dataIndex, ellipsis: true, align: 'center', }; // 同期对比 if (timeValue === 'contrast' && dataModel[0]) { const time = item.dataModel[0].pt .slice(0, contrastOption === 'day' ? 10 : 7) .replace(/-/g, ''); col.title = `${equipmentName}-${sensorName}-${time}`; } return col; }); // 格式化时间对齐数据, 生成行数 const timeData = {}; const buildDefaultData = (time) => { const obj = {key: time, time: time}; data.forEach((item, index) => { const dataIndex = dataIndexAccess(item, index); obj[dataIndex] = ''; }); return obj; }; data.forEach((item, index) => { const {stationCode, sensorName, dataModel} = item; dataModel && dataModel.forEach((data) => { const formatTime = moment(data.pt).format(format); let time = formatTime; if (timeValue === 'contrast') { time = time.slice(contrastOption === 'day' ? 11 : 8, 16); } timeData[formatTime] = timeData[formatTime] || buildDefaultData(time); }); }); // 处理表格数据 data.forEach((child, index) => { const {dataModel} = child; const dataIndex = dataIndexAccess(child, index); dataModel && dataModel.forEach((value, j) => { const formatTime = moment(value.pt).format(format); const dataRow = timeData[formatTime]; if (dataRow) { dataRow[dataIndex] = value.pv === null || value.pv === undefined ? '' : value.pv; } }); }); const timeSort = (a, b) => { let aa = a, bb = b; if (timeValue === 'contrast') { aa = a.slice(contrastOption === 'day' ? 11 : 8, 16); bb = b.slice(contrastOption === 'day' ? 11 : 8, 16); } return timeOrder === 'descend' ? -aa.localeCompare(bb) : aa.localeCompare(bb); }; const times = Object.keys(timeData).sort(timeSort); const tableData = times.map((time) => timeData[time]); setColumns([timeColumn, ...columnsData]); setTableData(tableData); }, [timeOrder, timeValue, contrastOption]); const [deviceAlarmSchemes, setDeviceAlarmSchemes] = useState([]); const beforChangeParams = (value = {}) => { if (!needMarkLine) return Promise.resolve(); return getDeviceAlarmScheme({ data: deviceParams.map((item) => ({ deviceType: item.deviceType, deviceCode: item.deviceCode, pointAddressID: item.pointAddressID, sensorName: item.sensors, })), }) .then((res) => { if (res.code === 0) setDeviceAlarmSchemes(res.data || []); else setDeviceAlarmSchemes([]); return Promise.resolve(); }) .catch((err) => { setDeviceAlarmSchemes([]); return Promise.resolve(); }); }; const handleDataThinKey = (diffYears, diffDays, diffHours, lineDataType) => { if (lineDataType === '原始曲线') { return {unit: '', zoom: ''} } // edit by zy 根据选择的时长控制抽稀频度 if (diffYears > 0) { if (diffYears === 1) return {unit: 'h', zoom: '24'} return {unit: 'h', zoom: '48'} } else if (diffYears === 0 && diffDays > 0) { if (diffDays > 90) return {unit: 'h', zoom: '24'} if (diffDays > 30) return {unit: 'h', zoom: '6'} if (diffDays > 15) return {unit: 'h', zoom: '4'} if (diffDays > 7) return {unit: 'h', zoom: '1'} if (diffDays > 3) return {unit: 'min', zoom: '30'} if (diffDays > 1) return {unit: 'min', zoom: '15'} if (diffDays === 1) return {unit: 'min', zoom: '5'} } else if (diffYears === 0 && diffDays === 0 && diffHours > 0) { if (diffHours > 12) return {unit: 'min', zoom: '5'} if (diffHours > 4) return {unit: 'min', zoom: '1'} if (diffHours > 1) return {unit: 's', zoom: '30'} if (diffHours > 0) return {unit: 's', zoom: '5'} return {unit: 's', zoom: '5'} } else { return {unit: '', zoom: ''} } }; // 处理接口服务参数的变化 const onChangeParams = (value = {}) => { const {dateRange, isDilute, ignoreOutliers, zoom, unit} = value; const requestArr = []; const acrossTables = []; const zoomArray = []; deviceParams .map((item) => { let _item = {...item}; _item.sensors = item.sensors && !item.sensors.includes('是否在线') ? item.sensors + ',是否在线' : item.sensors; return _item; }) .forEach((i) => { if (i.sensors && i.deviceCode && i.deviceCode) acrossTables.push(_.omit(i, ['pointAddressID'])); }); if (!acrossTables?.length || !dateRange.length) { handleTableData([]); setChartDataSource([]); return; } dateRange.forEach((item) => { const param = { isDilute, zoom, unit, ignoreOutliers, dateFrom: item.dateFrom, dateTo: item.dateTo, acrossTables, isBoxPlots: isBoxPlots, }; // if (ignoreOutliers) param.algorithmValue = algorithmValue; // param.algorithmValue = algorithmValue; let diffYears = moment(item.dateTo).diff(moment(item.dateFrom), 'years'); let diffDays = moment(item.dateTo).diff(moment(item.dateFrom), 'days'); let diffHours = moment(item.dateTo).diff(moment(item.dateFrom), 'hours'); let zoomParam = activeTabKey === 'curve' ? handleDataThinKey(diffYears, diffDays, diffHours, lineDataType) : {}; zoomArray.push(zoomParam); requestArr.push(getHistoryInfo({...param, ...zoomParam})); }); setLoading(true); Promise.all(requestArr).then((results) => { setLoading(false); if (results.length) { let data = []; results.forEach((res, index) => { const {dateFrom, dateTo} = dateRange?.[index] ?? {}; if (res.code === 0 && res.data.length) { res.data.forEach((d) => { d.dateFrom = dateFrom || ''; d.dateTo = dateTo || ''; }); deviceParams.forEach((p) => { // 返回数据按查询指标顺序排序 const sensors = p.sensors?.split(',') ?? []; const list = sensors.map((s) => { const dataItem = res.data.find( (d) => d.stationCode === p.deviceCode && d.sensorName === s, ); if (dataItem) { dataItem.dateFrom = dateFrom || ''; dataItem.dateTo = dateTo || ''; // 抽稀情况下,剔除掉为null的点 /* if (zoomArray[index]?.unit !== '' && zoomArray[index]?.zoom !== '') { dataItem.dataModel = dataItem.dataModel.filter(item => item.pv !== null) }*/ return dataItem; } else { return {}; } }).filter(item => item.sensorName); data = data.concat(list); }); } }); setLoading(false); handleTableData(data); setChartDataSource(data); } }).catch(err => { message.info('未查询到数据,请重试~'); setLoading(false) }); }; useEffect(() => { if (!completeInit) return; const {dataThin, ignoreOutliers, zoom, unit} = dataConfig; beforChangeParams().finally(() => { onChangeParams({ isDilute: dataThin, ignoreOutliers, zoom, unit, dateRange, isBoxPlots: isBoxPlots, }); }); }, [dateRange, dataConfig, deviceParams, chartType, lineDataType, completeInit, algorithmValue]); const handleChange = (pagination, filter, sort) => { if (sort.field === 'time') { setTimeOrder(sort.order) } }; const tableMemo = useMemo(() => { return <> <div className={`${prefixCls}-options`}> {renderTimeOption} {renderCurveOption()} </div> <div className={`${prefixCls}-content`}> {chartDataSource.length > 0 ? ( <BasicTable dataSource={tableData.sort((a, b) => { let _a = a.time; let _b = b.time if (timeValue === 'contrast') { if (contrastOption === 'day') { _a = `2000-01-01 ${a.time}:00`; _b = `2000-01-01 ${b.time}:00`; } if (contrastOption === 'month') { _a = `2000-01-${a.time}:00`; _b = `2000-01-${b.time}:00`; } } return timeOrder === 'ascend' ? moment(_a) - moment(_b) : moment(_b) - moment(_a); })} columns={columns} {...tableProps} pagination={false} onChange={handleChange} /> ) : ( <PandaEmpty/> )} </div> </> }, [timeOrder, chartDataSource, columns, tableProps, tableData]) const returnLongestPeriod = (data) => { let _earliest = '' let _latest = ''; data.forEach(item => { let _length = item.dataModel.length; let _tempFirst = item.dataModel[0].pt; let _tempLast = item.dataModel[_length - 1].pt; if (_earliest) { _earliest = moment(_earliest) > moment(_tempFirst) ? _tempFirst : _earliest } else { _earliest = _tempFirst } if (_latest) { _latest = moment(_latest) < moment(_tempLast) ? _tempLast : _latest } else { _latest = _tempLast; } }) return `${_earliest} - ${_latest}`; }; const renderPanel = (model) => { if (model === 'curve') { return ( <> <div className={`${prefixCls}-options`}> {renderTimeOption} {renderCurveOption( true, deviceParams?.length === 1 && deviceParams?.[0]?.sensors?.split(',').length === 1, )} </div> { lineDataType === '原始曲线' && false ? <div style={{marginTop: 10}}>展示区间:{returnLongestPeriod(chartDataSource)}</div> : '' } <div className={`${prefixCls}-content`}> {!chartDataSource.length ? ( <PandaEmpty/> ) : grid === true ? ( <GridChart curveCenter={curveCenter} prefixCls={prefixCls} dataSource={chartDataSource} contrast={timeValue === 'contrast'} contrastOption={contrastOption} deviceAlarmSchemes={deviceAlarmSchemes} /> ) : ( <SingleChart showBoxOption={showBoxOption} lineDataType={lineDataType} curveCenter={curveCenter} showGridLine={chartGrid} prefixCls={prefixCls} dataSource={chartDataSource} // justLine={!!checkboxData.find(item => item.key === 'justLine' && item.checked)} chartType={isBoxPlots ? chartType : null} contrast={timeValue === 'contrast'} contrastOption={contrastOption} deviceAlarmSchemes={deviceAlarmSchemes} /> )} </div> </> ); } if (model === 'table') { return tableMemo; } }; // 获取字段配置 const getDefaultOptions = async () => { // 非单曲线、单指标不执行 if (deviceParams?.length !== 1 || (deviceParams?.length === 1 && deviceParams?.[0]?.sensors?.split(',')?.length > 1)) return setCompleteInit(true); await getDictionaryInfoAll({ level: '组件_ec_historyview' }).then(res => { if (res.code === 0) { let _opt = res.data.reduce((final, cur) => { final[cur.fieldName] = cur.fieldValue return final }, {}); let _checkboxData = [...checkboxData].map(item => { let _item = {...item}; if (_opt[item.label] !== undefined) { _item.checked = _opt[item.label] === 'true' } return _item; }); setCheckboxData(_checkboxData); } }) setCompleteInit(true); }; useEffect(() => { getDefaultOptions(); }, []) let percentTimer = useRef({ timer: null }); useEffect(() => { if (loading === null) return; if (loading) { let _percent = percent; percentTimer.current.timer = setInterval(() => { _percent += 5; if (_percent > 95) return clearInterval(percentTimer.current.timer); setPercent(_percent); }, 100) } else { clearInterval(percentTimer.current.timer); setPercent(100); setTimeout(() => { setPercent(0); }, lineDataType === '原始曲线' ? 500 : 0) } }, [loading]) return ( <div className={classNames(prefixCls, 'wkt-scroll-light')}> {/*<Spin spinning={loading} wrapperClassName={classNames(`${prefixCls}-spin`)}>*/} <div className={classNames(`${prefixCls}-spin`)} style={{position: "relative"}}> { (loading || percent !== 0) ? <div className={classNames(`${prefixCls}-progressWrapper`)}> <div className={classNames(`${prefixCls}-header`)}/> <div className={classNames(`${prefixCls}-contentWrapper`)}> { lineDataType === '原始曲线' ? <><Progress percent={percent} steps={20} className={classNames(`${prefixCls}-progress`, `${prefixCls}-blink-2`)} showInfo={false}/> <div className={classNames(`${prefixCls}-tip`)}>加载中...</div> </> : <Spin spinning={loading || false}/> } </div> </div> : '' } {showModels.length === 1 && ( <div className={`${prefixCls}-single-panel`}>{renderPanel(showModels[0])}</div> )} {showModels.length > 1 && ( <Tabs activeKey={activeTabKey} onChange={(key) => setActiveTabKey(key)} centered tabBarExtraContent={{ left: <h3>{title}</h3>, right: ( <div className={`${prefixCls}-extra-right`}> {activeTabKey === 'table' && ( <Button type="link" onClick={exportExcelBtn}> <DownloadOutlined/> 下载 </Button> )} </div> ), }} > <Tabs.TabPane key="curve" tab="曲线" forceRender={true}> { activeTabKey === 'curve' ? renderPanel('curve') : '' } </Tabs.TabPane> <Tabs.TabPane key="table" tab="表格"> {renderPanel('table')} </Tabs.TabPane> </Tabs> )} </div> {/*</Spin>*/} </div> ); }; HistoryView.propTypes = { grid: PropTypes.bool, title: PropTypes.string, defaultChecked: PropTypes.oneOf(['twelveHours', 'roundClock', 'oneWeek', 'oneMonth']), tableProps: PropTypes.object, deviceParams: PropTypes.arrayOf( PropTypes.objectOf({ deviceCode: PropTypes.string, sensors: PropTypes.string, deviceType: PropTypes.string, pointAddressID: PropTypes.number, // 可选,配置了将会查询相关报警方案配置 }), ), defaultModel: PropTypes.oneOf(['curve', 'table']), showModels: PropTypes.arrayOf(PropTypes.oneOf(['curve', 'table'])), defaultDate: PropTypes.string, }; HistoryView.defaultProps = { grid: false, title: '指标曲线', defaultChecked: 'roundClock', tableProps: {}, defaultModel: 'curve', showModels: ['curve', 'table'], needMarkLine: true, defaultDate: 'day', }; export default HistoryView;