import React, {useState, useEffect, useRef, useContext} from 'react'; import classNames from 'classnames'; import moment from 'moment'; import Empty from '@wisdom-components/empty'; import LoadBox from '@wisdom-components/loadbox'; import BasicTable from '@wisdom-components/basictable'; import {ExportExcel} from '@wisdom-components/exportexcel'; import {ConfigProvider, Select, DatePicker, Radio, Table, Button} from 'antd'; import {VerticalAlignBottomOutlined} from '@ant-design/icons'; import {BasicChart} from '@wisdom-components/basicchart'; import {getStatisticsInfo} from './apis'; import './index.less'; const StatisticalHistoryView = (props) => { const {getPrefixCls} = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('ec-statistical-history-view'); const chartRef = useRef(null); const defaultOptionRef = useRef({ title: { show: false, text: '', }, tooltip: false, grid: { containLabel: true, left: 20, right: 20, // top: 20, bottom: 10, }, toolbox: { show: false, }, xAxis: [ { type: 'time', axisTick: { alignWithLabel: true, }, boundaryGap: false, splitLine: { show: true, lineStyle: { type: 'dashed', }, }, }, ], yAxis: [ { type: 'value', max: 100, min: 0, position: 'left', alignTicks: true, axisLine: { show: true, }, axisLabel: { formatter: '{value}', }, }, ], series: [{ type: 'line', data: [ [moment(moment().format('YYYY-MM-DD 00:00:00')).valueOf(), null], [moment(moment().format('YYYY-MM-DD 23:59:59')).valueOf(), null], ] }] }); // 用来判定是否是初始化、是否时报错 const emptyOrError = useRef({ empty: true, error: true }) const [loading, setLoading] = useState(false); const [historyRender, setHistoryRender] = useState(true); const [historyParams, setHistoryParams] = useState({}); const [columns, setColumns] = useState([ { title: '时间', dataIndex: 'pt', align: 'center', width: 100, fixed: 'left', }, ]); const [dataSource, setDataSource] = useState([]); const [options, setOptions] = useState({}); const [dateValue, setDateValue] = useState('today'); const [picker, setPicker] = useState('day'); const [time, setTime] = useState({startTime: '', endTime: ''}); const [dateTime, setDateTime] = useState(moment().format('YYYY-MM-DD HH:mm:ss')); // 时间切换筛选 const onTimeChange = (e) => { setDateValue(e.target.value); }; // 自定义时间类型 const onPickerChange = (value) => { setPicker(value); let start = moment(dateTime).subtract(2, value).startOf(value).format('YYYY-MM-DD 00:00:00'); let end = moment(dateTime).endOf(value).format('YYYY-MM-DD 23:59:59'); setTime({startDate: start, endDate: end}); }; // 自定义时间选择 const onDateChange = (value, dateString) => { setDateTime(dateString); const start = moment(value).subtract(2, picker).startOf(picker).format('YYYY-MM-DD 00:00:00'); const end = moment(value).endOf(picker).format('YYYY-MM-DD 23:59:59'); setTime({startDate: start, endDate: end}); }; const exportExcelBtn = (e) => { if (!dataSource.length) return false; const sheetFilter = columns.map((item) => { return item.dataIndex; }); const sheetHeader = columns.map((item) => { return item.title; }); const sheetData = dataSource.map((item) => { return { pt: item.pt, value0: String(item.value0), value1: String(item.value1), value2: String(item.value2), }; }); ExportExcel({ name: `${props?.deviceParams?.sensors || ''}指标数据`, content: [ { sheetData: sheetData, sheetName: 'sheet1', sheetFilter: sheetFilter, sheetHeader: sheetHeader, columnWidths: [10, 10, 10, 10], }, ], }); }; // 生成对应的数据结构 const returnDataStructure = (dateFrom, dateTo, dateType, dateValue) => { // let length = new Array(3).fill(false); let finalData = []; if (dateType === 'day') { let days = length.map((item, index) => { return moment(dateTo).subtract(index, 'days').format('YYYY-MM-DD') }); let hours = new Array(24).fill(false).map((item, index) => { return index < 10 ? `0${index}` : index; }) days.forEach(day => { hours.forEach(hour => { finalData.push(`${day} ${hour}`); }) }) } if (dateType === 'month') { let months = length.map((item, index) => { return moment(dateTo).subtract(index, 'months').format('YYYY-MM') }); let days = new Array(31).fill(false).map((item, index) => { return index < 9 ? `0${index + 1}` : index + 1; }) months.forEach(month => { days.forEach(day => { finalData.push(`${month} ${day}`); }) }) } if (dateType === 'year') { let years = length.map((item, index) => { return moment(dateTo).subtract(index, 'years').format('YYYY') }); let months = new Array(12).fill(false).map((item, index) => { return index < 9 ? `0${index + 1}` : index + 1; }) years.forEach(year => { months.forEach(month => { finalData.push(`${year} ${month}`); }) }) } return finalData.map(item => { return { time: item, value: null, } }); }; const handleDataForEmptyOrError = () => { let _data = returnDataStructure(time.startDate, time.endDate, picker, dateValue); dataMenthod({nameDate: _data, dName: props?.deviceParams?.sensors ?? ''}); } // 获取历史统计数据 const getHistoryData = async () => { const {deviceCode = '', sensors = '', deviceType = '', statisticType = ''} = historyParams; const nameTypeList = []; if (!deviceCode || !sensors) return setOptions(null); nameTypeList.push({ name: sensors, type: statisticType || 'Sub', }); const params = { pageIndex: 1, pageSize: 999, nameTypeList: nameTypeList, accountName: deviceType, dateFrom: time.startDate, dateTo: time.endDate, deviceCode: deviceCode, dateType: picker, }; setLoading(true); let results = null; try { results = await getStatisticsInfo(params); emptyOrError.current.error = false; } catch (err) { if (historyParams.sensors) { handleDataForEmptyOrError() } return setLoading(false); } setLoading(false); const list = results?.data?.list || []; if (list.length === 0) { handleDataForEmptyOrError() } const dataList = list.length ? list[0] : {}; const dNameDataList = dataList.dNameDataList ? dataList.dNameDataList : []; const data = dNameDataList.length ? dNameDataList[0] : null; if (!data) return setOptions(null); dataMenthod(data); }; const dataMenthod = (data) => { const {nameDate} = data; nameDate.forEach(item => { if (item.value !== null) emptyOrError.current.empty = false; }) let time0 = '', time1 = '', time2 = ''; let data0 = [], data1 = [], data2 = []; let timeName = []; switch (dateValue) { case 'today': time0 = moment(time.endDate).subtract(0, 'day').format('YYYY-MM-DD'); time1 = moment(time.endDate).subtract(1, 'day').format('YYYY-MM-DD'); time2 = moment(time.endDate).subtract(2, 'day').format('YYYY-MM-DD'); timeName = [].concat(dayName); nameDate.forEach((item) => { const times = moment(item.time).format('YYYY-MM-DD'); const pt = moment(item.time).format('HH时'); if (times === time0) data0.push({...item, pt: pt}); if (times === time1) data1.push({...item, pt: pt}); if (times === time2) data2.push({...item, pt: pt}); }); break; case 'thisWeek': time0 = moment(time.endDate).subtract(0, 'week').format('WW'); time1 = moment(time.endDate).subtract(1, 'week').format('WW'); time2 = moment(time.endDate).subtract(2, 'week').format('WW'); timeName = [].concat(weekName); nameDate.forEach((item) => { const times = moment(item.time).format('WW'); const pt = getWeek(item.time); if (times === time0) data0.push({...item, pt: pt}); if (times === time1) data1.push({...item, pt: pt}); if (times === time2) data2.push({...item, pt: pt}); }); break; case 'thisMonth': time0 = moment(time.endDate).subtract(0, 'month').format('YYYY-MM'); time1 = moment(time.endDate).subtract(1, 'month').format('YYYY-MM'); time2 = moment(time.endDate).subtract(2, 'month').format('YYYY-MM'); timeName = [].concat(monthName); nameDate.forEach((item) => { const times = moment(item.time).format('YYYY-MM'); const pt = moment(item.time).format('DD日'); if (times === time0) data0.push({...item, pt: pt}); if (times === time1) data1.push({...item, pt: pt}); if (times === time2) data2.push({...item, pt: pt}); }); break; case 'thisYear': time0 = moment(time.endDate).subtract(0, 'year').format('YYYY'); time1 = moment(time.endDate).subtract(1, 'year').format('YYYY'); time2 = moment(time.endDate).subtract(2, 'year').format('YYYY'); timeName = [].concat(yearName); nameDate.forEach((item) => { const times = moment(item.time).format('YYYY'); const pt = moment(item.time).format('MM月'); if (times === time0) data0.push({...item, pt: pt}); if (times === time1) data1.push({...item, pt: pt}); if (times === time2) data2.push({...item, pt: pt}); }); break; case 'customer': const style = picker === 'day' ? 'HH时' : picker === 'month' ? 'DD日' : 'MM月'; const format = picker === 'day' ? 'YYYY-MM-DD' : picker === 'month' ? 'YYYY-MM' : 'YYYY'; time0 = moment(time.endDate).subtract(0, picker).format(format); time1 = moment(time.endDate).subtract(1, picker).format(format); time2 = moment(time.endDate).subtract(2, picker).format(format); timeName = [].concat([time0, time1, time2]); nameDate.forEach((item) => { const times = moment(item.time).format(format); const pt = moment(item.time).format(style); if (times === time0) data0.push({...item, pt: pt}); if (times === time1) data1.push({...item, pt: pt}); if (times === time2) data2.push({...item, pt: pt}); }); break; } const dataChart = { data: [data0, data1, data2], name: timeName, }; const _data = [data0, data1, data2].sort((a, b) => { return b.length - a.length; }) const dataTable = _data[0].map((item, index) => { return { ...item, value0: data0[index] ? data0[index]?.value === 0 || data0[index]?.value ? data0[index]?.value : '-' : '', value1: data1[index] ? data1[index]?.value === 0 || data1[index]?.value ? data1[index]?.value : '-' : '', value2: data2[index] ? data2[index]?.value === 0 || data2[index]?.value ? data2[index]?.value : '-' : '', }; }); renderChart(dataChart, data); renderTable(dataTable, timeName); }; const renderChart = (dataInfo, data) => { if (!dataInfo) return setOptions(null); const {unit, dName} = data; const series = []; let xData = []; dataInfo.data.forEach((item, index) => { const config = !index && dName.indexOf('流量') > -1 ? {areaStyle: {}} : {}; const style = index ? {lineStyle: {normal: {type: 'dashed'}}} : {}; const list = { name: dataInfo.name[index], type: 'line', smooth: true, ...config, ...style, data: item.map((arr) => { return { name: arr.pt, value: arr.value, }; }), }; series.push(list); xData = item.map((arr) => { return arr.pt; }); }); let showMinMax = emptyOrError.current.empty && emptyOrError.current.error; const option = { title: { show: false, text: '', }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross', }, }, grid: { containLabel: true, left: 20, right: 20, // top: 20, bottom: 10, }, toolbox: { show: false, }, legend: { left: 'center', }, xAxis: [ { type: 'category', axisTick: { alignWithLabel: true, }, boundaryGap: false, splitLine: { show: true, lineStyle: { type: 'dashed', }, }, data: xData, }, ], yAxis: [ { type: 'value', name: unit, position: 'left', alignTicks: true, axisLine: { show: true, }, axisLabel: { formatter: '{value}', }, ...(showMinMax ? {min: 0, max: 100} : {}) }, ], series: series, }; setOptions(option); }; const renderTable = (dataInfo, timeName) => { const dataIndex = ['value0', 'value1', 'value2']; const column1 = [ { title: '时间', dataIndex: 'pt', align: 'center', width: 100, fixed: 'left', }, ]; const column2 = timeName.map((item, index) => { return { title: item, dataIndex: dataIndex[index], align: 'center', }; }); const column = column1.concat(column2); setColumns(column); setDataSource(dataInfo); }; const getWeek = (date) => { const weekTime = moment(date).day(); switch (weekTime) { case 1: return '周一'; break; case 2: return '周二'; break; case 3: return '周三'; break; case 4: return '周四'; break; case 5: return '周五'; break; case 6: return '周六'; break; case 0: return '周日'; break; } }; const Summary = (currentData) => { return ( <Table.Summary.Row> {columns.map((item, index) => { let sum = 0; currentData.reduce((prev, next) => { sum += next[item.dataIndex] === '-' || next[item.dataIndex] === '' ? 0 : next[item.dataIndex]; }, 0); return ( <Table.Summary.Cell key={item.dataIndex} index={index} align={'center'}> {index === 0 ? '总计' : sum.toFixed(0)} </Table.Summary.Cell> ); })} </Table.Summary.Row> ); }; useEffect(() => { if (props?.deviceParams?.sensors) { handleDataForEmptyOrError() } }, []) useEffect(() => { setHistoryParams(props.deviceParams || {}); }, [props.deviceParams]); useEffect(() => { let start = '', end = ''; switch (dateValue) { case 'today': start = moment().subtract(2, 'day').format('YYYY-MM-DD 00:00:00'); end = moment().format('YYYY-MM-DD 23:59:59'); setPicker('day'); break; case 'thisWeek': start = moment().startOf('week').subtract(2, 'week').format('YYYY-MM-DD 00:00:00'); end = moment().endOf('week').format('YYYY-MM-DD 23:59:59'); setPicker('month'); break; case 'thisMonth': start = moment().startOf('month').subtract(2, 'month').format('YYYY-MM-DD 00:00:00'); end = moment().endOf('month').format('YYYY-MM-DD 23:59:59'); setPicker('month'); break; case 'thisYear': start = moment().startOf('year').subtract(2, 'year').format('YYYY-MM-DD 00:00:00'); end = moment().endOf('year').format('YYYY-MM-DD 23:59:59'); setPicker('year'); break; case 'customer': start = moment(dateTime).startOf(picker).subtract(2, picker).format('YYYY-MM-DD 00:00:00'); end = moment(dateTime).endOf(picker).format('YYYY-MM-DD 23:59:59'); break; } setTime({startDate: start, endDate: end}); // eslint-disable-next-line react-hooks/exhaustive-deps }, [dateValue]); useEffect(() => { getHistoryData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [time, historyParams, picker]); return ( <div className={classNames(prefixCls)}> <div className={classNames(`${prefixCls}-box`)}> <div className={classNames(`${prefixCls}-header`)}> <div className={classNames(`${prefixCls}-time`)}> <span>时间选择:</span> <Radio.Group value={dateValue} defaultValue={dateList[0].key} onChange={onTimeChange}> {dateList.map((item) => ( <Radio.Button key={item.key} value={item.key}> {item.name} </Radio.Button> ))} </Radio.Group> {dateValue === 'customer' && ( <> <Select defaultValue="day" value={picker} options={timeList} onChange={onPickerChange} className={classNames(`${prefixCls}-select`)} /> <DatePicker onChange={onDateChange} value={moment(dateTime)} picker={picker} allowClear={false}/> </> )} </div> <Button className={classNames(`${prefixCls}-down-load`)} type="primary" onClick={exportExcelBtn} disabled={!dataSource.length} > <VerticalAlignBottomOutlined/> 下载 </Button> </div> <div className={classNames(`${prefixCls}-content`)}> { <> <div className={classNames(`${prefixCls}-chart`)}> { <BasicChart ref={chartRef} option={(!options || !options?.series) ? defaultOptionRef.current : options} notMerge style={{width: '100%', height: '100%'}} /> } </div> <div className={classNames(`${prefixCls}-table`)}> <BasicTable dataSource={dataSource} columns={columns} pagination={false} bordered={true} onRow={(record, index) => { return { onMouseEnter: (event) => { let seriesIndex = 0; const dataIndex = ['value0', 'value1', 'value2']; seriesIndex = dataIndex.findIndex((item) => { return record[item] !== '-'; }); chartRef?.current?.getEchartsInstance() && chartRef?.current?.getEchartsInstance().dispatchAction({ type: 'showTip', seriesIndex: seriesIndex, dataIndex: index, }); }, // 鼠标移入行 onMouseLeave: (event) => { chartRef?.current?.getEchartsInstance() && chartRef?.current?.getEchartsInstance().dispatchAction({ type: 'hideTip', dataIndex: index, }); }, // 鼠标移出行 }; }} scroll={{x: '100%', y: 'calc(100% - 40px)'}} summary={Summary} /> </div> </> } </div> </div> {loading && ( <div className={classNames(`${prefixCls}-load-box`)}> <LoadBox spinning={loading}/> </div> )} </div> ); }; export default StatisticalHistoryView; const dateList = [ { key: 'today', name: '今日', }, // { // key: 'thisWeek', // name: '本周', // }, { key: 'thisMonth', name: '本月', }, { key: 'thisYear', name: '今年', }, { key: 'customer', name: '自定义', }, ]; const timeList = [ { value: 'day', label: '日', }, { value: 'month', label: '月', }, { value: 'year', label: '年', }, ]; const dayName = ['今日', '昨日', '前日']; const weekName = ['本周', '上周', '上上周']; const monthName = ['本月', '上月', '上上月']; const yearName = ['今年', '去年', '前年'];