2 Commits 9344e7dcad ... 41f5509cf4

Author SHA1 Message Date
  ZhaoJun 41f5509cf4 Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/gt_client_pad into develop 1 year ago
  ZhaoJun db3304f7ae feat: 预测图表 1 year ago

+ 32 - 2
src/pages/SmartOps/predictionAnalysis/PredictionAnalysis.less

@@ -33,7 +33,37 @@
     color: #4089ff;
     margin: 0.2rem 0;
   }
-  .detailDeviceRangeSelect > * {
-    margin-right: 0.1rem;
+  .detailDeviceCharts {
+    padding: 0.2rem;
+  }
+}
+
+.form {
+  margin-bottom: 0.14rem;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.dateTabs {
+  display: flex;
+  border: 0.01rem solid #d5d5d5;
+  height: 0.6rem;
+  .dateTabsItem {
+    width: 1.28rem;
+    text-align: center;
+    cursor: pointer;
+    border-right: 0.01rem solid #d5d5d5;
+    line-height: 0.6rem;
+    font-size: 0.24rem;
+    font-weight: 400;
+    color: #4a4a4a;
+    &:last-child {
+      border-right: none;
+    }
+    &.active {
+      background: #4a90e2;
+      color: #fff;
+    }
   }
 }

+ 533 - 37
src/pages/SmartOps/predictionAnalysis/PredictionDetail.js

@@ -1,12 +1,28 @@
 import PageContent from '@/components/PageContent';
 import PageTitle from '@/components/PageTitle';
-import { queryMembraneList, queryUFCondition } from '@/services/SmartOps';
+import { queryMembrane, queryMembraneList } from '@/services/SmartOps';
 import { useLocation, useParams, useRequest } from '@umijs/max';
-import { Button, DatePicker } from 'antd';
+import { DatePicker, Empty, Form, Spin } from 'antd';
 import dayjs from 'dayjs';
-import { useState } from 'react';
+import * as echarts from 'echarts';
+import { useEffect, useMemo, useRef, useState } from 'react';
 import styles from './PredictionAnalysis.less';
 
+const DateTab = [
+  {
+    value: 'day',
+    label: '今日',
+  },
+  {
+    value: 'week',
+    label: '本周',
+  },
+  {
+    value: 'month',
+    label: '本月',
+  },
+];
+
 const { RangePicker } = DatePicker;
 
 const PredictionDetail = () => {
@@ -14,13 +30,6 @@ const PredictionDetail = () => {
   const locationSearch = new URLSearchParams(useLocation().search);
   const code = locationSearch.get('code');
 
-  const defaultParams = {
-    project_id: projectId,
-    device_code: code,
-    s_time: dayjs().format('YYYY-MM-DD 00:00:00'),
-    e_time: dayjs().format('YYYY-MM-DD 23:59:59'),
-  };
-
   const [currentDevice, setCurrentDevice] = useState({});
 
   useRequest(queryMembraneList, {
@@ -36,18 +45,6 @@ const PredictionDetail = () => {
     },
   });
 
-  const { run } = useRequest(queryUFCondition, {
-    defaultParams: [defaultParams],
-  });
-
-  const handleRangeChange = ([s_time, e_time]) => {
-    run({
-      ...filter,
-      s_time: dayjs(s_time).format('YYYY-MM-DD 00:00:00'),
-      e_time: dayjs(e_time).format('YYYY-MM-DD 23:59:59'),
-    });
-  };
-
   return (
     <PageContent closeable={false}>
       <PageTitle returnable>预测分析</PageTitle>
@@ -57,25 +54,524 @@ const PredictionDetail = () => {
       <div className={styles.detailDevice}>
         <div className={styles.detailDeviceTitle}>跨膜压差</div>
         <div className="card-box">
-          <div
-            className={styles.detailDeviceRangeSelect}
-            style={{ padding: '0.2rem' }}
-          >
-            <RangePicker inputReadOnly onChange={handleRangeChange} />
-            <Button type="primary" shape="round">
-              今日
-            </Button>
-            <Button type="primary" shape="round">
-              本周
-            </Button>
-            <Button type="primary" shape="round">
-              本月
-            </Button>
+          <div className={styles.detailDeviceCharts}>
+            <ChartContent projectId={projectId} deviceCode={code} />
           </div>
-          <div className={styles.detailDeviceCharts}></div>
         </div>
       </div>
     </PageContent>
   );
 };
 export default PredictionDetail;
+
+function getOption(data = [], active) {
+  let formatter,
+    yAxisName = '',
+    y2AxisName = '',
+    series = [],
+    xAxis = [];
+  var data1 = [],
+    data2 = [],
+    data3 = [],
+    data4 = [];
+  switch (active) {
+    case 'tt_backwash':
+      yAxisName = '清洗周期/Min';
+      formatter = (params) => {
+        let item = data[params[0].dataIndex];
+        let content = '';
+        if (item.bw_type == 1) {
+          // PEB
+          content += 'PEB 反洗开始时间:' + item.peb_st;
+          content += '<br />PEB 反洗结束时间:' + item.peb_et;
+        } else {
+          // CEB
+          content += 'CEB 反洗开始时间:' + item.ceb_st;
+          content += '<br />CEB 反洗结束时间:' + item.ceb_et;
+          content += '<br />CEB 清洗剂浓度' + item.ceb_ppm;
+        }
+        return content;
+      };
+      data?.forEach((item) => {
+        let time = 0;
+        if (item.bw_type == 1) {
+          if (item.peb_et && item.peb_st) {
+            time = dayjs(item.peb_st).diff(dayjs(item.peb_et), 'minutes');
+          }
+          // 实际冲洗
+          data1.push(time);
+          data2.push(null);
+          xAxis.push(dayjs(item.peb_st).format('YYYY-MM-DD HH:mm:ss'));
+        } else {
+          if (item.ceb_st && item.ceb_et) {
+            time = dayjs(item.ceb_st).diff(dayjs(item.ceb_et), 'minutes');
+          }
+          data1.push(null);
+          data2.push(time);
+          xAxis.push(dayjs(item.ceb_st).format('YYYY-MM-DD HH:mm:ss'));
+        }
+      });
+      series = [
+        {
+          name: '物理实际冲洗',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data1,
+        },
+        {
+          name: '物理模拟冲洗',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data2,
+        },
+        {
+          name: '化学实际冲洗',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data3,
+        },
+        {
+          name: '化学模拟冲洗',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data4,
+        },
+      ];
+      break;
+    case 'tt_wash':
+      yAxisName = '清洗周期/Min';
+      formatter = (params) => {
+        let item = data[params[0].dataIndex];
+        let content = '';
+        content += '反洗开始时间:' + item.st;
+        content += '<br />反洗结束时间:' + item.et;
+        return content;
+      };
+      data?.forEach((item) => {
+        // 实际冲洗
+        data1.push(Math.ceil(item.interval / 60));
+        // TODO:模拟冲洗
+        data2.push(Math.ceil(item.interval / 60));
+        xAxis.push(dayjs(item.st).format('YYYY-MM-DD HH:mm:ss'));
+      });
+      series = [
+        {
+          name: '实际冲洗',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data1,
+        },
+        {
+          name: '模拟冲洗',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data2,
+        },
+      ];
+      break;
+    case 'tt_nob':
+      yAxisName = '杀菌周期/Min';
+      formatter = (params) => {
+        let item = data[params[0].dataIndex];
+        let content = '';
+        content += '杀菌开始时间:' + (item.st || '-');
+        content += '<br />杀菌结束时间:' + (item.et || '-');
+        return content;
+      };
+      var data1 = [],
+        data2 = [];
+      data?.forEach((item) => {
+        // 实际冲洗
+        data1.push(Math.ceil(item.interval / 60));
+        // TODO:模拟冲洗
+        data2.push(Math.ceil(item.interval / 60));
+        xAxis.push(dayjs(item.st).format('YYYY-MM-DD HH:mm:ss'));
+      });
+      series = [
+        {
+          name: '实际',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data1,
+        },
+        {
+          name: '模拟',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data2,
+        },
+      ];
+      break;
+    case 'tdr_hci':
+    case 'tdr_nob':
+    case 'tdr_pac':
+      yAxisName = '投加量';
+      data?.forEach((item) => {
+        // 实际冲洗
+        data1.push(Math.ceil(item.fr / 60));
+        // TODO:模拟冲洗
+        data2.push(Math.ceil(item.fcoa / 60));
+        xAxis.push(dayjs(item.c_time).format('YYYY-MM-DD HH:mm:ss'));
+      });
+      series = [
+        {
+          name: '实际物理投加量',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data1,
+        },
+        {
+          name: '理论物理投加量',
+          type: 'bar',
+          barMaxWidth: '0.2rem',
+          data: data2,
+        },
+      ];
+      break;
+
+    case 'td_uf':
+      yAxisName = '跨膜压差';
+      data?.forEach((item) => {
+        // 预测跨膜压差
+        data1.push(item.std_tmp);
+        // 实际跨膜压差
+        data2.push(item.tmp);
+        xAxis.push(dayjs(item.c_time).format('YYYY-MM-DD HH:mm:ss'));
+      });
+      series = [
+        {
+          name: '预测跨膜压差',
+          type: 'line',
+          data: data1,
+          lineStyle: {
+            color: '#F5A623',
+            type: 'dashed',
+          },
+          showSymbol: false,
+        },
+        {
+          name: '实际跨膜压差',
+          type: 'line',
+          data: data2,
+          lineStyle: {
+            color: '#4A90E2',
+          },
+          showSymbol: false,
+        },
+      ];
+      break;
+    case 'td_mf':
+    case 'td_nf':
+      yAxisName = '渗透率';
+      data?.forEach((item) => {
+        // 实际跨膜压差
+        data1.push(item.std_tmp);
+        // 模拟跨膜压差
+        data2.push(item.std_permeability);
+        xAxis.push(dayjs(item.c_time).format('YYYY-MM-DD HH:mm:ss'));
+      });
+      series = [
+        {
+          name: '标准跨膜压差',
+          type: 'line',
+          data: data1,
+          showSymbol: false,
+        },
+        {
+          name: '标准渗透率',
+          type: 'line',
+          data: data2,
+          showSymbol: false,
+        },
+      ];
+      break;
+    case 'td_ro':
+      yAxisName = '跨膜压差';
+      data?.forEach((item) => {
+        // 实际跨膜压差
+        data1.push(item.extend['1st_Stage_DP']);
+        data2.push(item.extend['2nd_Stage_DP']);
+        // 模拟跨膜压差
+        data3.push(item.stabilize_extend['1st_Stage_DP']);
+        data4.push(item.stabilize_extend['2nd_Stage_DP']);
+        xAxis.push(dayjs(item.c_time).format('YYYY-MM-DD HH:mm:ss'));
+      });
+      series = [
+        {
+          name: '实际一段跨膜压差',
+          type: 'line',
+          data: data1,
+          showSymbol: false,
+        },
+        {
+          name: '实际二段跨膜压差',
+          type: 'line',
+          data: data2,
+          showSymbol: false,
+        },
+        {
+          name: '模拟一段跨膜压差',
+          type: 'line',
+          data: data3,
+          showSymbol: false,
+        },
+        {
+          name: '模拟二段跨膜压差',
+          type: 'line',
+          data: data4,
+          showSymbol: false,
+        },
+      ];
+      break;
+    case 'td_pump_nf':
+    case 'td_pump_uf':
+    case 'td_pump_ro':
+    // case 'td_pump':
+    case 'td_pump_mf':
+    case 'td_pump_nf_drug':
+    case 'td_pump_mf_drug':
+    case 'td_pump_mf_drug':
+    case 'td_pump_mf_drug':
+      yAxisName = '频率 Hz';
+      y2AxisName = '电流 A';
+      data?.forEach((item) => {
+        // 实际跨膜压差
+        data1.push(item.frequency);
+        data2.push(item.current);
+        xAxis.push(dayjs(item.c_time).format('YYYY-MM-DD HH:mm:ss'));
+      });
+      series = [
+        {
+          name: '频率',
+          type: 'line',
+          data: data1,
+          showSymbol: false,
+        },
+        {
+          name: '电流',
+          type: 'line',
+          data: data2,
+          yAxisIndex: 1,
+          showSymbol: false,
+        },
+      ];
+      break;
+  }
+  // 过滤失效数据
+  xAxis.forEach((time, index) => {
+    if (time === 'Invalid date') {
+      xAxis[index] = null;
+      data1[index] = null;
+      data2[index] = null;
+      data3[index] = null;
+      data4[index] = null;
+    }
+  });
+  xAxis = xAxis.filter((item) => item);
+  data1 = data1.filter((item) => !isNaN(item));
+  data2 = data2.filter((item) => !isNaN(item));
+  data3 = data3.filter((item) => !isNaN(item));
+  data4 = data4.filter((item) => !isNaN(item));
+
+  const option = {
+    color: ['#FFC800', '#30EDFD', '#4096ff', '#ff4d4f', '#ffa940'],
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow',
+      },
+      textStyle: {
+        fontSize: 12,
+      },
+      formatter,
+    },
+    legend: {
+      textStyle: {
+        // color: '#fff',
+        fontSize: 14,
+      },
+      type: 'scroll',
+    },
+    grid: {
+      top: 80,
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true,
+    },
+    xAxis: {
+      type: 'category',
+      data: xAxis,
+      nameTextStyle: {
+        fontSize: 12,
+      },
+      axisLabel: {
+        fontSize: 12,
+      },
+    },
+    yAxis: {
+      name: yAxisName,
+      type: 'value',
+      boundaryGap: [0, 0.01],
+      nameTextStyle: {
+        fontSize: 14,
+      },
+      axisLabel: {
+        fontSize: 12,
+      },
+      splitNumber: 5,
+    },
+    series,
+  };
+  if (y2AxisName) {
+    let y1 = option.yAxis;
+    let y2 = JSON.parse(JSON.stringify(y1));
+    y1.max = getMax(data1) || 1;
+    y1.interval = y1.max / 5;
+
+    y2.name = y2AxisName;
+    y2.max = getMax(data2) || 1;
+    y2.interval = y2.max / 5;
+    option.yAxis = [y1, y2];
+  }
+  console.log(option);
+  return option;
+}
+
+const ChartContent = (props) => {
+  const { deviceCode, projectId } = props;
+
+  const [time, setTime] = useState([dayjs().subtract(10, 'minute'), dayjs()]);
+  const [dateActive, setDateActive] = useState();
+
+  const timerRef = useRef({
+    s_time: time[0].format('YYYY-MM-DD HH:mm:ss'),
+    e_time: time[1].format('YYYY-MM-DD HH:mm:ss'),
+  });
+
+  const domRef = useRef(null);
+  const chartRef = useRef(null);
+
+  const { data, loading, run } = useRequest(() => {
+    let params = {
+      device_code: deviceCode,
+      page: 1,
+      page_size: 9999,
+      project_id: projectId,
+      type: 'uf',
+      ...timerRef.current,
+    };
+    return queryMembrane(params);
+  });
+
+  const optimization = useMemo(() => {
+    const result = data?.list?.filter((item) => item.optimization);
+    if (result?.length > 0) {
+      const lastItem = result[result.length - 1];
+      const arr = [
+        'ro_wash_interval',
+        'peb_interval',
+        'pac_fr',
+        'ro_nob_interva',
+      ];
+      const valueList = Object.entries(lastItem.optimization)
+        .map(([key, item]) => {
+          if (!arr.includes(key)) return null;
+          return item.remark;
+        })
+        .filter((item) => item);
+      if (valueList.length == 0) return '';
+      return `【${lastItem.c_time}】${valueList.join(',')}`;
+    }
+    return '';
+  }, [data]);
+
+  const searchTime = (type) => {
+    setDateActive(type);
+    let time = [dayjs().startOf(type), dayjs()];
+    onSearch?.(time);
+  };
+
+  const onSearch = (time) => {
+    console.log(time);
+    if (time && time.length == 2) {
+      let s_time, e_time;
+      s_time = time[0].format('YYYY-MM-DD HH:mm:ss');
+      e_time = time[1].format('YYYY-MM-DD HH:mm:ss');
+
+      timerRef.current = { s_time, e_time };
+      // setTime(time);
+      run();
+    }
+  };
+
+  useEffect(() => {
+    if (data && data.list && domRef.current) {
+      chartRef.current = echarts.init(domRef.current);
+      chartRef.current.clear();
+      let options = getOption(data.list, 'td_uf');
+      chartRef.current.setOption(options, true);
+      chartRef.current.resize();
+    }
+    return () => {
+      if (chartRef.current) {
+        chartRef.current.dispose();
+      }
+    };
+  }, [data, domRef.current]);
+
+  return (
+    <div className={styles.chartBox}>
+      <Form layout="inline" className={styles.form}>
+        <RangePicker
+          inputReadOnly
+          style={{ width: '5.4rem' }}
+          allowClear={false}
+          defaultValue={time}
+          // value={time}
+          onChange={onSearch}
+          format="YYYY-MM-DD HH:mm:ss"
+        ></RangePicker>
+        <div className={styles.dateTabs}>
+          {DateTab.map((item) => (
+            <div
+              key={item.value}
+              className={`${styles.dateTabsItem} ${
+                item.value === dateActive ? styles.active : ''
+              }`}
+              onClick={() => searchTime(item.value)}
+            >
+              {item.label}
+            </div>
+          ))}
+        </div>
+      </Form>
+      <div>
+        <Spin spinning={loading}>{!data?.list && <Empty />}</Spin>
+        {data?.list && (
+          <div
+            ref={domRef}
+            style={{
+              height: '3rem',
+              width: '100%',
+            }}
+            // onResize={() => console.log(1)}
+          />
+        )}
+      </div>
+      {optimization && (
+        <div
+          style={{
+            fontSize: 22,
+            color: '#000',
+            textIndent: 30,
+            padding: '0.1rem 0',
+            flexShrink: 1,
+          }}
+        >
+          {optimization}
+        </div>
+      )}
+    </div>
+  );
+};