xujunjie 1 سال پیش
والد
کامیت
efd2b23d11

+ 24 - 3
.umirc.ts

@@ -15,11 +15,12 @@ export default defineConfig({
     { 'http-equiv': 'cache-control', content: 'no-cache' },
     { 'http-equiv': 'expires', content: '0' },
     { 'http-equiv': 'X-UA-Compatible', content: 'IE=EmulateIE9' },
+    { name: 'transparent', content: 'true' },
   ],
   proxy: {
     '/api': {
       // target: 'http://47.96.12.136:8888/',
-      target: 'http://47.96.12.136:8888/',
+      target: 'http://47.96.12.136:8788/',
       // target: 'https://work.greentech.com.cn/',
       changeOrigin: true,
     },
@@ -27,13 +28,33 @@ export default defineConfig({
   routes: [
     {
       path: '/',
-      redirect: '/home',
+      redirect: '/home/92',
     },
     {
       name: '首页',
-      path: '/home',
+      path: '/home/:projectId',
       component: './Home',
     },
+    {
+      name: '水量监测',
+      path: '/home/water-amt-mng/:projectId',
+      component: './Home/WaterAmtMng',
+    },
+    {
+      name: '水质监测',
+      path: '/home/water-quality-mng/:projectId',
+      component: './Home/QualityMng',
+    },
+    {
+      name: '能耗监测',
+      path: '/home/energy/:projectId',
+      component: './Home/EnergyCostComparison',
+    },
+    {
+      name: '药耗监测',
+      path: '/home/chem-cost/:projectId',
+      component: './Home/ChemCostComparison',
+    },
     {
       name: '工况管理',
       path: '/smart/work/:projectId',

+ 3 - 3
src/app.ts

@@ -1,9 +1,9 @@
 // 运行时配置
 
 // import { RequestConfig, history } from '@umijs/max';
-import { getToken } from '@/utils/utils';
+import { getToken, GetTokenFromUrl } from '@/utils/utils';
+import { history, RequestConfig } from '@umijs/max';
 import { message } from 'antd';
-import { RequestConfig, history } from '@umijs/max';
 
 // 全局初始化数据配置,用于 Layout 用户信息和权限初始化
 // 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
@@ -47,7 +47,7 @@ export const request: RequestConfig = {
   requestInterceptors: [
     (config: any) => {
       if (!config.headers) config.headers = {};
-      config.headers['JWT-TOKEN'] = getToken();
+      config.headers['JWT-TOKEN'] = GetTokenFromUrl() || getToken();
       return config;
     },
   ],

+ 133 - 0
src/components/ManagementPage/BarChartModule.js

@@ -0,0 +1,133 @@
+/*
+//y轴显示数据
+Data:{       
+  name:string,                       
+  data:string[],
+}
+
+props:{
+  xData:string[],  //x轴时间数据
+  dataList:Data[],  //数据列表
+}
+*/
+
+import { useEffect, useRef, useState } from 'react';
+import echarts from 'echarts';
+import styles from './index.less';
+import dayjs from 'dayjs';
+import { Empty } from 'antd';
+
+//图表模块
+const BarChartModule = props => {
+  const chartDomRef = useRef();
+  const chartRef = useRef();
+  const { xData, dataList } = props;
+  useEffect(() => {
+    chartRef.current = echarts.init(chartDomRef.current);
+    window.addEventListener('resize', resetChart);
+    return () => window.removeEventListener('resize', resetChart);
+  }, []);
+
+  useEffect(() => {
+    if (!chartRef.current || !dataList) return;
+    const option = { ...defaultOption };
+    option.xAxis.data = xData;
+    option.series = dataList.map((item, idx) => {
+      return {
+        ...option.series[idx],
+        ...item,
+      };
+    });
+    chartRef.current.clear();
+    chartRef.current.setOption(option);
+    chartRef.current.resize();
+  }, [xData, dataList]);
+
+  const resetChart = () => {
+    if (chartRef.current) chartRef.current.resize();
+  };
+
+  const getStyle = () => {
+    if (dataList && dataList.length != 0) return { width: '100%', height: '100%' };
+    else return { width: '100%', height: '100%', display: 'none' };
+  };
+
+  return (
+    <div className={styles.content}>
+      <div style={getStyle()} ref={chartDomRef} />
+      {(!dataList || dataList.length == 0) && <Empty />}
+    </div>
+  );
+};
+export default BarChartModule;
+const colors = [
+  '#5470c6',
+  '#91cc75',
+  '#fac858',
+  '#ee6666',
+  '#73c0de',
+  '#3ba272',
+  '#fc8452',
+  '#9a60b4',
+  '#ea7ccc',
+];
+const defaultOption = {
+  color: colors,
+  grid: {
+    bottom: 30,
+    left: 60,
+    right: 30,
+  },
+  xAxis: {
+    type: 'category',
+    axisTick: { show: false },
+    axisLine: {
+      lineStyle: {
+        color: '#c9d2d2',
+      },
+    },
+    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
+  },
+  yAxis: {
+    type: 'value',
+    splitLine: {
+      lineStyle: {
+        type: 'dashed',
+      },
+    },
+    axisTick: { show: false },
+    axisLine: {
+      show: false,
+    },
+
+  },
+  legend: {
+    icon: 'circle',
+    right: '20%',
+   
+  },
+  series: [
+    {
+      name: '2022',
+      data: [120, 200, 150, 80, 70, 110, 130],
+      type: 'bar',
+      label: {
+        show: true,
+        position: 'top',
+      },
+      barGap: '0',
+      barMaxWidth: '10%',
+    },
+    {
+      name: '2023',
+      data: [120, 200, 150, 80, 70, 110, 130],
+      type: 'bar',
+      label: {
+        show: true,
+        position: 'top',
+      },
+      barGap: '0',
+      barMaxWidth: '10%',
+    },
+  ],
+};

+ 57 - 0
src/components/ManagementPage/ManagementPage.less

@@ -0,0 +1,57 @@
+.searchWrapper1,
+.searchWrapper2,
+.searchWrapper3 {
+  width: 100%;
+  // padding: 0px 20px;
+}
+
+.searchWrapper2,
+.searchWrapper3 {
+  display: flex;
+}
+
+.searchDate1,
+.searchDate3 {
+  width: 100%;
+  flex-grow: 1;
+}
+
+.searchDate1,
+.searchDate3 {
+  .datePicker {
+    width: 180px;
+    min-width: 0px !important;
+  }
+}
+
+.searchBtnWrapper1,
+.searchBtnWrapper2,
+.searchBtnWrapper3 {
+  display: flex;
+}
+
+.searchBtnWrapper1 {
+  width: 100%;
+  justify-content: space-between;
+}
+
+.searchBtnWrapper2 {
+  flex-grow: 1;
+  justify-content: space-between;
+}
+
+.searchBtnWrapper1 {
+  margin-top: 20px;
+}
+
+.searchBtnWrapper2,
+.searchBtnWrapper3 {
+  margin-left: 20px;
+}
+
+.searchBtn {
+  margin-right: 20px;
+}
+
+.exportBtn {
+}

+ 98 - 0
src/components/ManagementPage/PieChartModule.js

@@ -0,0 +1,98 @@
+/*
+data: [
+        { value: 1048, name: 'Search Engine' },
+        { value: 735, name: 'Direct' },
+        { value: 580, name: 'Email' },
+        { value: 484, name: 'Union Ads' },
+        { value: 300, name: 'Video Ads' },
+      ],
+*/
+
+import { useEffect, useRef, useState } from 'react';
+import echarts from 'echarts';
+import styles from './index.less';
+import dayjs from 'dayjs';
+import { Empty } from 'antd';
+
+//图表模块
+const PieChartModule = props => {
+  const chartDomRef = useRef();
+  const chartRef = useRef();
+  const { data } = props;
+  useEffect(() => {
+    chartRef.current = echarts.init(chartDomRef.current);
+    window.addEventListener('resize', resetChart);
+    return () => window.removeEventListener('resize', resetChart);
+  }, []);
+
+  useEffect(() => {
+    if (!chartRef.current || !data) return;
+    const option = { ...defaultOption };
+    option.series[0].data = data;
+    chartRef.current.clear();
+    chartRef.current.setOption(option);
+    chartRef.current.resize();
+  }, [data]);
+
+  const resetChart = () => {
+    if (chartRef.current) chartRef.current.resize();
+  };
+
+  const getStyle = () => {
+    if (data && data.length != 0) return { width: '100%', height: '100%' };
+    else return { width: '100%', height: '100%', display: 'none' };
+  };
+
+  return (
+    <div className={styles.content}>
+      <div style={getStyle()} ref={chartDomRef} />
+      {(!data || data.length == 0) && <Empty />}
+    </div>
+  );
+};
+export default PieChartModule;
+const colors = [
+  '#5470c6',
+  '#91cc75',
+  '#fac858',
+  '#ee6666',
+  '#73c0de',
+  '#3ba272',
+  '#fc8452',
+  '#9a60b4',
+  '#ea7ccc',
+];
+const defaultOption = {
+  color: colors,
+  tooltip: {
+    trigger: 'item',
+    formatter: '{b} : {d}% ({c})',
+  },
+ 
+  series: [
+    {
+      type: 'pie',
+      radius: '70%',
+      data: [
+        { value: 1048, name: 'Search Engine' },
+        { value: 735, name: 'Direct' },
+        { value: 580, name: 'Email' },
+        { value: 484, name: 'Union Ads' },
+        { value: 300, name: 'Video Ads' },
+      ],
+      label: {
+        fontSize: 18,
+      },
+      emphasis: {
+        itemStyle: {
+          shadowBlur: 10,
+          shadowOffsetX: 0,
+          shadowColor: 'rgba(0, 0, 0, 0.5)',
+        },
+        label: {
+          fontSize: 24,
+        },
+      },
+    },
+  ],
+};

+ 136 - 0
src/components/ManagementPage/RadarChartModule.js

@@ -0,0 +1,136 @@
+/*
+//y轴显示数据
+Data:{
+  type:number, 0(实线) 1虚线(预测)  2(填充)
+  name:string,
+  yIndex:number, //yName的下标索引(第几个y坐标轴)
+  data:string[],
+}
+
+props:{
+  yName:string || string[], //y轴名称  一个或多个  
+  xData:string[],  //x轴时间数据
+  dataList:Data[],  //折线的数据列表
+  currentType:string, //当前选择那个tabs 需要外部更新当前选中type时传入
+  typeList:string[],  //图表下的选择类型列表
+  onChange:(id:number)=>{}
+}
+*/
+
+import { useEffect, useRef, useState } from 'react';
+import echarts from 'echarts';
+import styles from './index.less';
+import dayjs from 'dayjs';
+import { Empty } from 'antd';
+
+//图表模块
+const RadarChartModule = props => {
+  const chartDomRef = useRef();
+  const chartRef = useRef();
+  const { indicator, data, name = '' } = props;
+  useEffect(() => {
+    chartRef.current = echarts.init(chartDomRef.current);
+    window.addEventListener('resize', resetChart);
+    return () => window.removeEventListener('resize', resetChart);
+  }, []);
+
+  useEffect(() => {
+    if (!chartRef.current) return;
+    const option = { ...defaultOption };
+    option.radar.indicator = indicator;
+    option.series = [{ ...option.series[0], data: data, name }];
+    console.log(JSON.stringify(option));
+    chartRef.current.clear();
+    chartRef.current.setOption(option);
+    chartRef.current.resize();
+  }, [indicator, data]);
+
+  const resetChart = () => {
+    if (chartRef.current) chartRef.current.resize();
+  };
+
+  const getStyle = () => {
+    if (data && data.length != 0) return { width: '100%', height: '100%' };
+    else return { width: '100%', height: '100%', display: 'none' };
+  };
+
+  return (
+    <div className={styles.content}>
+      <div style={getStyle()} ref={chartDomRef} />
+      {(!data || data.length == 0) && <Empty />}
+    </div>
+  );
+};
+export default RadarChartModule;
+const colors = [
+  // '#666600',
+  // '#91cc75',
+  '#fac858',
+  '#ee6666',
+  '#73c0de',
+  '#3ba272',
+  '#fc8452',
+  '#9a60b4',
+  '#ea7ccc',
+];
+const defaultOption = {
+  color: colors,
+  tooltip: {
+    trigger: 'item',
+  },
+  radar: {
+    name: {
+      textStyle: {
+        fontSize: 18,
+      },
+    },
+    splitArea: {
+      areaStyle: {
+        color: [
+          'rgba(27, 133, 168, 0.8)',
+          'rgba(27, 133, 168, 0.7)',
+          'rgba(27, 133, 168, 0.6)',
+          'rgba(27, 133, 168, 0.4)',
+          'rgba(27, 133, 168, 0.3)',
+        ],
+        // shadowColor: 'rgba(0, 0, 0, 0.2)',
+        // shadowBlur: 10
+      },
+    },
+    axisLine: {
+      lineStyle: {
+        color: '#238BB6',
+      },
+    },
+    splitLine: {
+      show: false,
+    },
+    indicator: [
+      { name: 'Sales' },
+      { name: 'Administration' },
+      { name: 'Information Technology' },
+      { name: 'Customer Support' },
+      { name: 'Development' },
+      { name: 'Marketing' },
+    ],
+  },
+  series: [
+    {
+      name: '',
+      type: 'radar',
+      tooltip: {
+        trigger: 'item',
+      },
+      symbol: 'none',
+      // areaStyle: {},
+      data: [
+        {
+          value: [2, 3, 0, 100, 0, 1],
+        },
+        {
+          value: [3, 5, 10, 90, 0, 1],
+        },
+      ],
+    },
+  ],
+};

+ 42 - 0
src/components/ManagementPage/TypeSelct.js

@@ -0,0 +1,42 @@
+import React from 'react';
+import router from 'umi/router';
+import { Form, Select, Button } from 'antd';
+
+const { Option } = Select;
+
+function TypeSelect(props) {
+  const { type, processId, projectId, name } = props;
+
+  const onChangeType = value => {
+    switch (value) {
+      case 1:
+        router.replace(
+          `/unity/energy-consumption/detail/${projectId}/${processId}?showType=1&name=${name}`
+        );
+        break;
+      case 2:
+        router.replace(`/unity/Chem-cost-mng/${projectId}?processId=${processId}&name=${name}`);
+        break;
+      case 3:
+        router.replace(`/unity/water-amt-manage/${projectId}?processId=${processId}&name=${name}`);
+        break;
+    }
+  };
+
+  return (
+    <Form layout="inline" style={{ margin: '20px 0' }}>
+      <Form.Item label="成本类型">
+        <Select style={{ width: 200 }} value={type} onChange={onChangeType}>
+          <Option value={1}>能耗</Option>
+          <Option value={2}>药耗</Option>
+          <Option value={3}>水量</Option>
+        </Select>
+      </Form.Item>
+      {/* <Button type="primary" style={{ float: 'right' }} onClick={() => router.goBack()}>
+        返回
+      </Button> */}
+    </Form>
+  );
+}
+
+export default TypeSelect;

+ 90 - 0
src/components/ManagementPage/chartModal.js

@@ -0,0 +1,90 @@
+//图表弹窗
+import React, { useState } from 'react';
+import { Button, DatePicker, Modal, Form, Row, Col } from 'antd';
+import dayjs from 'dayjs';
+import ChartModule from '@/components/ManagementPage/chartModule';
+const FormItem = Form.Item;
+function chartModal(props) {
+  const {
+    visible,
+    form,
+    yName,
+    xData,
+    dataList,
+    typeList,
+    onChange,
+    onSearch,
+    title = '详情',
+    onCancel,
+    sTimeInitialValue,
+    eTimeInitialValue,
+    type = true //true 显示 false 隐藏时间查询
+  } = props;
+  const handleSearch = () => {
+    form.validateFields((err, fieldsValue) => {
+      if (err) return;
+      console.log(fieldsValue);
+      onSearch?.({
+        s_time: dayjs(fieldsValue.start_date).format('YYYY-MM-DD'),
+        e_time: dayjs(fieldsValue.end_date).format('YYYY-MM-DD'),
+      });
+    });
+  };
+  return (
+    <Modal
+      visible={visible}
+      destroyOnClose
+      footer={false}
+      onCancel={onCancel}
+      title={title}
+      width="90%"
+    >
+      {type &&
+        <Form
+          layout="inline"
+          // labelAlign="left"
+          labelCol={{ span: 8 }}
+          wrapperCol={{ span: 16 }}
+        >
+          <Row gutter={24}>
+            <Col span={10}>
+              <FormItem label="开始时间">
+                {form.getFieldDecorator('start_date', {
+                  initialValue: sTimeInitialValue || dayjs().subtract(1, 'M'),
+                })(<DatePicker allowClear={false} placeholder="选择开始日期" />)}
+              </FormItem>
+            </Col>
+            <Col span={10}>
+              <FormItem label="结束时间">
+                {form.getFieldDecorator('end_date', {
+                  initialValue: eTimeInitialValue || dayjs(),
+                })(<DatePicker allowClear={false} placeholder="选择结束日期" />)}
+              </FormItem>
+            </Col>
+            <Col span={2}>
+              <Button
+                onClick={() => {
+                  handleSearch();
+                }}
+                type="primary"
+              >
+                查询
+              </Button>
+            </Col>
+          </Row>
+        </Form>
+      }
+      <div style={{ height: 400, marginTop: 20 }}>
+        <ChartModule
+          yName={yName}
+          xData={xData}
+          dataList={dataList}
+          typeList={typeList}
+          onChange={onChange}
+        ></ChartModule>
+      </div>
+    </Modal>
+  );
+}
+
+export default Form.create()(chartModal);

+ 422 - 0
src/components/ManagementPage/chartModule.js

@@ -0,0 +1,422 @@
+/*
+//y轴显示数据
+Data:{
+  type:number, 0(实线) 1虚线(预测)  2(填充)
+  name:string,
+  yIndex:number, //yName的下标索引(第几个y坐标轴)
+  data:string[],
+}
+
+props:{
+  closeTime:boolen   //是否超过7天展示时间
+  yName:string || string[], //y轴名称  一个或多个
+  xData:string[],  //x轴时间数据
+  dataList:Data[],  //折线的数据列表
+  currentType:string, //当前选择那个tabs 需要外部更新当前选中type时传入
+  typeList:string[],  //图表下的选择类型列表
+  onChange:(id:number)=>{}
+}
+*/
+
+import { Tabs } from 'antd';
+import dayjs from 'dayjs';
+import * as echarts from 'echarts';
+import { useEffect, useRef, useState } from 'react';
+import styles from './index.less';
+
+const { TabPane } = Tabs;
+
+export const handleExportClick = () => {
+  const canvas = document.getElementsByTagName('canvas');
+  if (canvas && canvas.length > 0) {
+    return canvas[0].toDataURL('image/png');
+  }
+  return null;
+};
+
+// 图表模块
+const ChartModule = (props) => {
+  const chartDomRef = useRef();
+  const chartRef = useRef();
+
+  const {
+    yName,
+    closeTime = false,
+    xData,
+    dataList,
+    typeList = [],
+    onChange,
+    currentType,
+    chartType = 'line',
+  } = props;
+
+  const [curType, setCurType] = useState(currentType);
+
+  useEffect(() => {
+    chartRef.current = echarts.init(chartDomRef.current);
+    window.addEventListener('resize', resetChart);
+    return () => window.removeEventListener('resize', resetChart);
+  }, []);
+
+  useEffect(() => {
+    if (!chartRef.current || !dataList || !chartType === 'gauge') {
+      return;
+    }
+    const option = { ...defaultOption };
+    let series = [];
+    switch (chartType) {
+      case 'gauge':
+        series = option.series[dataList.type];
+        series.data = [dataList];
+        for (const key in option) {
+          delete option[key];
+        }
+        option.series = [series];
+        break;
+      default:
+        // 超过7天x轴显示年月日,不到7天显示时分
+        if (!closeTime && xData && xData.length > 1) {
+          const timeFormat =
+            Math.abs(
+              dayjs(xData[xData?.length - 1]).diff(dayjs(xData[0]), 'days'),
+            ) + 1;
+          option.xAxis.data = xData.map((item) => {
+            if (timeFormat > 150 || chartType === 'bar') {
+              return `${dayjs(item).format('MM')}月 `;
+            }
+            if (timeFormat >= 7) {
+              return dayjs(item).format('YYYY-MM-DD');
+            }
+            return dayjs(item).format('HH:mm');
+          });
+        } else {
+          option.xAxis.data = xData || [];
+        }
+
+        if (Array.isArray(yName)) {
+          if (yName.length > 2) {
+            option.grid.right = 120;
+          }
+
+          option.yAxis = yName.map((item, index) => {
+            return { ...option.yAxis[index], name: item };
+          });
+
+          option.series = dataList.map((item) => {
+            return {
+              ...option.series[item.type],
+              name: item.name,
+              data: item.data,
+              yAxisIndex: item.yIndex || 0,
+            };
+          });
+        } else {
+          option.grid.right = 30;
+          option.yAxis = { ...option.yAxis[0], name: yName };
+          option.series = dataList.map((item) => {
+            return {
+              ...option.series[item.type],
+              name: item.name,
+              data: item.data,
+              barWidth: dataList.length >= 4 ? 8 : 20,
+            };
+          });
+        }
+        // 柱状图需要开启boundaryGap避免和Y轴重合
+        if (chartType === 'bar') {
+          option.xAxis.boundaryGap = true;
+          delete option.legend.itemHeight;
+        } else {
+          option.xAxis.boundaryGap = false;
+          option.legend.itemHeight = 0;
+        }
+
+        option.legend.data = dataList.map((item) => item.name);
+        break;
+    }
+
+    // console.log(props, JSON.stringify(option));
+    chartRef.current.clear();
+    chartRef.current.setOption(option);
+    chartRef.current.resize();
+    // if (typeList?.length > 0) setCurType(typeList[0]);
+  }, [yName, xData, dataList, typeList, chartType]);
+
+  useEffect(() => {
+    setCurType(currentType);
+  }, [currentType]);
+
+  const resetChart = () => {
+    if (chartRef.current) chartRef.current.resize();
+  };
+
+  return (
+    <div className={styles.content}>
+      <div
+        style={{
+          width: chartType === 'gauge' ? '60%' : '100%',
+          marginLeft: chartType === 'gauge' ? '20%' : '0',
+          height: typeList?.length <= 0 ? '100%' : 'calc(100% - 57px)',
+        }}
+        ref={chartDomRef}
+      />
+      {typeList?.length > 0 && (
+        <Tabs
+          activeKey={curType || typeList[0]}
+          onChange={(type) => {
+            setCurType(type);
+            onChange(type);
+          }}
+        >
+          {typeList.map((item) => (
+            <TabPane tab={item} key={item} />
+          ))}
+        </Tabs>
+      )}
+    </div>
+  );
+};
+export default ChartModule;
+const colors = [
+  '#5470c6',
+  '#91cc75',
+  '#fac858',
+  '#ee6666',
+  '#73c0de',
+  '#3ba272',
+  '#fc8452',
+  '#9a60b4',
+  '#ea7ccc',
+];
+const defaultOption = {
+  color: colors,
+  tooltip: {
+    trigger: 'axis',
+  },
+  grid: {
+    bottom: 30,
+    left: 70,
+    right: 30,
+  },
+  xAxis: {
+    type: 'category',
+    boundaryGap: false,
+    axisTick: { show: false },
+
+    data: [
+      '00:00',
+      '01:15',
+      '02:30',
+      '03:45',
+      '05:00',
+      '06:15',
+      '07:30',
+      '08:45',
+    ],
+  },
+  yAxis: [
+    {
+      type: 'value',
+      name: '000',
+      top: 20,
+      nameTextStyle: {
+        fontSize: 16,
+        // align: 'left',
+        padding: [0, 0, 20, 0],
+      },
+      axisLine: {
+        show: false,
+        // lineStyle: {
+        //   color: colors[0],
+        // },
+      },
+      splitLine: {
+        lineStyle: {
+          type: 'dashed',
+        },
+      },
+    },
+    {
+      type: 'value',
+      name: '111',
+      top: 20,
+      position: 'right',
+      nameTextStyle: {
+        fontSize: 16,
+        // align: 'left',
+        padding: [0, 0, 20, 0],
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: colors[7],
+        },
+      },
+      splitLine: {
+        lineStyle: {
+          type: 'dashed',
+        },
+      },
+    },
+    {
+      type: 'value',
+      name: '222',
+      top: 20,
+      position: 'right',
+      offset: 80,
+      nameTextStyle: {
+        fontSize: 16,
+        // align: 'left',
+        padding: [0, 0, 20, 0],
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: colors[8],
+        },
+      },
+      splitLine: {
+        lineStyle: {
+          type: 'dashed',
+        },
+      },
+    },
+  ],
+  series: [
+    {
+      data: [820, 932, 901, 934, 1290, 1330, 1320],
+      type: 'line',
+      name: '进水水量',
+      yAxisIndex: 0,
+      smooth: 'true',
+      // itemStyle:{
+      //   color:'#be7bbe',
+      // },
+      showSymbol: false, // 不展示拐点圆圈
+    },
+    {
+      data: [130, 125, 828, 743, 1100],
+      name: '预测出水量',
+      yAxisIndex: 0,
+      type: 'line',
+      smooth: 'true',
+      lineStyle: {
+        normal: {
+          width: 4,
+          type: 'dashed',
+        },
+      },
+      itemStyle: {
+        color: '#be7bbe',
+      },
+      showSymbol: false, // 不展示拐点圆圈
+    },
+    {
+      data: [820, 772, 901, 934, 1290, 1120, 1320],
+      name: '实际出水量',
+      yAxisIndex: 0,
+      type: 'line',
+      smooth: 'true',
+      showSymbol: false, // 不展示拐点圆圈
+      itemStyle: {
+        color: '#50A3C8',
+      },
+      areaStyle: {
+        origin: 'number',
+        color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
+          {
+            offset: 0,
+            color: 'rgba(31,114,150,1)',
+          },
+          {
+            offset: 1,
+            color: 'rgba(31,114,150,0.3)',
+          },
+        ]),
+      },
+    },
+    {
+      data: [120, 200, 150, 80, 70, 110, 130],
+      name: '实际出水量',
+      type: 'bar',
+      barGap: 0.2,
+      barWidth: 30,
+    },
+    {
+      data: [
+        {
+          value: 19,
+          name: '负荷率',
+        },
+      ],
+      name: '负荷率',
+      type: 'gauge',
+      startAngle: 180,
+      endAngle: 0,
+      radius: '90%',
+      min: 0,
+      max: 100,
+      splitNumber: 10,
+      center: ['50%', '65%'],
+      axisLine: {
+        lineStyle: {
+          width: 40,
+          color: [
+            [0.33, '#a4f428'],
+            [0.66, '#f6a842'],
+            [1, '#e02b42'],
+          ],
+        },
+      },
+      axisPointer: {
+        show: true,
+      },
+      pointer: {
+        icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
+        length: '50%',
+        width: 8,
+        offsetCenter: [0, 0],
+        itemStyle: {
+          color: 'auto',
+        },
+      },
+      axisTick: {
+        show: false,
+      },
+      splitLine: {
+        show: false,
+      },
+      axisLabel: {
+        show: false,
+      },
+      title: {
+        offsetCenter: [0, '25%'],
+        fontSize: 18,
+      },
+      detail: {
+        show: false,
+      },
+    },
+  ],
+  legend: {
+    // 图例配置
+    // icon:'arrow',
+    // width:'2',
+    itemHeight: 0, // 远点宽度为0不显示原点
+    // right: '10%',
+    data: ['进水水量', '预测出水量', '实际出水量'],
+    lineStyle: {},
+  },
+  // toolbox: {
+  //   show: true,
+  //   feature: {
+  //     // dataZoom: {
+  //     //   yAxisIndex: 'none'
+  //     // },
+  //     // dataView: { readOnly: false },
+  //     // magicType: { type: ['line', 'bar'] },
+  //     restore: {},
+  //     saveAsImage: {},
+  //   },
+  // },
+};

+ 66 - 0
src/components/ManagementPage/index.less

@@ -0,0 +1,66 @@
+.icon {
+  float: left;
+  width: 8px;
+  height: 30px;
+  background-color: #366cda;
+}
+.title {
+  color: #c9d2d2;
+  font-size: 22px;
+  padding-left: 14px;
+}
+.right {
+  color: #366cda;
+  float: right;
+  font-size: 20px;
+  cursor: default;
+}
+
+.leftArrow {
+  border: solid 20px;
+  border-color: transparent #366cda transparent transparent;
+}
+.rightArrow {
+  border: solid 20px;
+  border-color: transparent transparent transparent #366cda;
+}
+.typeList {
+  flex-grow: 1;
+  display: flex;
+}
+
+.content {
+  height: 100%;
+  :global {
+    .ant-tabs-nav-wrap {
+      background: none;
+    }
+    .ant-tabs-nav .ant-tabs-tab {
+      padding: 2px 16px;
+      background-color: #2196f330;
+      border: none;
+      margin: 0 6px;
+    }
+    .ant-tabs-tab-active {
+      background-color: #366cda !important;
+    }
+    .ant-tabs-tab-active .ant-tabs-tab-btn {
+      color: #fff !important;
+    }
+    .ant-tabs-ink-bar {
+      display: none;
+    }
+
+    .ant-tabs-bar {
+      margin: 0;
+    }
+    // .ant-tabs-tab-prev-icon{
+    //   border: solid 20px ;
+    //   border-color: transparent #366CDA transparent  transparent ;
+    //   i{
+    //     width: 0;
+    //     height: 0;
+    //   }
+    // }
+  }
+}

+ 28 - 0
src/components/ManagementPage/moduleTitle.js

@@ -0,0 +1,28 @@
+//模块标题
+/*
+style //样式不传默认上面10px
+icon  dom节点
+title //名称
+rightText  //右侧文字
+handleRightClick   //右侧点击方法
+
+//使用实例
+<ModuleTitle title='数据曲线' icon={<img src={require('@/assets/newUI/icon_in.png')} />} />
+<ModuleTitle title='数据曲线' icon={<Icon style={{ color: 'yellow' }} type="bulb" />} />
+*/
+import styles from './index.less';
+const ModuleTitle = ({ style, icon, title, rightText, handleRightClick }) => {
+  return (
+    <div style={style ? style : { margin: '10px 0', paddingRight: '10px' }}>
+      {icon ? icon : <div className={styles.icon} />}
+      <span className={styles.title}>{title}</span>
+      {rightText && (
+        <span className={styles.right} onClick={() => handleRightClick?.()}>
+          {rightText}
+        </span>
+      )}
+    </div>
+  );
+};
+
+export default ModuleTitle;

+ 149 - 0
src/components/ManagementPage/searchModule.js

@@ -0,0 +1,149 @@
+//搜索模块
+//入参:
+//onSearch({s_time, e_time}) 搜索函数
+//onExport 导出函数
+//defaultValue dayjs[] 默认值, 不传则为1天
+//type 分类样式: 1为分行展示, 2为一行展示, 3不包含"近一月"、"近一周"、"近一天"按钮
+import { Button, DatePicker, Form } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useState } from 'react';
+import styles from './ManagementPage.less';
+
+function SearchModule(props) {
+  const {
+    onSearch,
+    onExport,
+    style,
+    defaultValue = [dayjs().subtract(1, 'day'), dayjs()],
+    type,
+    backBtn,
+  } = props;
+
+  const [form] = Form.useForm();
+  const [searchType, setSearchType] = useState(type);
+
+  useEffect(() => {
+    if (type == 1 || type == 2) window.addEventListener('resize', resize);
+    resize();
+    return () => {
+      window.removeEventListener('resize', resize);
+    };
+  });
+
+  const handleSearch = () => {
+    form.validateFields((err, fieldsValue) => {
+      if (err) return;
+      onSearch?.({
+        s_time: dayjs(fieldsValue.start_date).format('YYYY-MM-DD 00:00:00'),
+        e_time: dayjs(fieldsValue.end_date).format('YYYY-MM-DD 23:59:59'),
+      });
+    });
+  };
+
+  const searchTime = (type) => {
+    form.setFieldsValue({
+      start_date: dayjs().subtract(1, type),
+      end_date: dayjs(),
+    });
+    onSearch?.({
+      s_time: dayjs().subtract(1, type).format('YYYY-MM-DD 00:00:00'),
+      e_time: dayjs().format('YYYY-MM-DD 23:59:59'),
+    });
+  };
+
+  const resize = () => {
+    if (document.documentElement.clientWidth > 1200) setSearchType(2);
+    else setSearchType(1);
+  };
+
+  return (
+    <div className={styles[`searchWrapper${searchType}`]} style={style}>
+      <Form
+        layout="inline"
+        labelCol={{ span: 8 }}
+        wrapperCol={{ span: 16 }}
+        className={styles[`searchDate${searchType}`]}
+      >
+        <Form.Item
+          label="开始时间"
+          name="start_date"
+          initialValue={defaultValue[0]}
+        >
+          <DatePicker
+            allowClear={false}
+            placeholder="选择开始日期"
+            className={styles.datePicker}
+          />
+        </Form.Item>
+        <Form.Item
+          label="结束时间"
+          name="end_date"
+          initialValue={defaultValue[1]}
+        >
+          <DatePicker
+            allowClear={false}
+            placeholder="选择结束日期"
+            className={styles.datePicker}
+          />
+        </Form.Item>
+      </Form>
+      <div className={styles[`searchBtnWrapper${searchType}`]}>
+        <div>
+          {type != 3 && (
+            <>
+              <Button
+                type="primary"
+                onClick={() => searchTime('day')}
+                className={styles.searchBtn}
+              >
+                近一天
+              </Button>
+              <Button
+                type="primary"
+                onClick={() => searchTime('week')}
+                className={styles.searchBtn}
+              >
+                近一周
+              </Button>
+              <Button
+                type="primary"
+                onClick={() => searchTime('month')}
+                className={styles.searchBtn}
+              >
+                近一月
+              </Button>
+            </>
+          )}
+          <Button
+            type="primary"
+            onClick={handleSearch}
+            className={styles.searchBtn}
+          >
+            查询
+          </Button>
+        </div>
+        {onExport && (
+          <div className={styles.exportBtn}>
+            <Button
+              type="primary"
+              onClick={() => {
+                form.validateFields((err, fieldsValue) => {
+                  if (err) return;
+                  onExport(
+                    dayjs(fieldsValue.start_date).format('YYYY-MM-DD 00:00:00'),
+                    dayjs(fieldsValue.end_date).format('YYYY-MM-DD 23:59:59'),
+                  );
+                });
+              }}
+            >
+              导出
+            </Button>
+            {backBtn}
+          </div>
+        )}
+      </div>
+    </div>
+  );
+}
+
+export default SearchModule;

+ 0 - 0
src/pages/EqSelfInspection/models/eqSelfInspection.js → src/models/eqSelfInspection.js


+ 0 - 0
src/pages/HardwareController/AirConditioner.js


+ 7 - 0
src/pages/HardwareController/AirConditioner.less

@@ -0,0 +1,7 @@
+.desc {
+  position: absolute;
+  top: 30px;
+  left: 50%;
+  transform: translateX(-50%);  
+  font-size: 22px;
+}

+ 0 - 0
src/pages/HardwareController/Light.js


+ 317 - 0
src/pages/Home/ChemCostComparison.js

@@ -0,0 +1,317 @@
+// 药耗监测
+import ChartModule from '@/components/ManagementPage/chartModule';
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import {
+  getChemicalAgents,
+  getComparisonData,
+} from '@/services/OperationManagement';
+import { useParams } from '@umijs/max';
+import { Tabs, message } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useState } from 'react';
+import styles from './manage.less';
+
+const { TabPane } = Tabs;
+
+const typeParams = [
+  {
+    // 计划吨水药耗
+    type: '3',
+    flag: '0',
+  },
+  {
+    // 实际吨水药耗
+    type: '3',
+    flag: '1',
+  },
+  {
+    // 计划用药量
+    type: '4',
+    flag: '0',
+  },
+  {
+    // 实际用药量
+    type: '4',
+    flag: '1',
+  },
+];
+
+const CostComparison = (props) => {
+  const { projectId } = useParams();
+
+  const [open, setOpen] = useState(false);
+  const [chartData, setChartData] = useState([]);
+  const [chemList, setChemList] = useState([]);
+  const [currentChem, setCurrentChem] = useState();
+
+  const [topValues, setTopValues] = useState({
+    chemPer: 0,
+    chemUser: 0,
+  });
+  const curMonth = dayjs().format('YYYY-MM');
+
+  const defaultTime = {
+    s_time: `${dayjs().format('YYYY')}-${dayjs().startOf('year').format('MM')}`,
+    e_time: `${dayjs().format('YYYY')}-${dayjs().endOf('year').format('MM')}`,
+  };
+
+  const defaultParams = {
+    project_id: projectId,
+    start: defaultTime.s_time,
+    end: defaultTime.e_time,
+  };
+
+  const getChartData = () => {
+    // 构建请求列表
+    const queryList = [];
+    for (let index = 0; index < 4; index++) {
+      queryList.push(
+        getComparisonData({ ...defaultParams, ...typeParams[index] }),
+      );
+    }
+    // 获取四组数据
+    return Promise.all(queryList).catch(() => {
+      message.error('请求数据失败');
+    });
+  };
+
+  const getFixed = (maxValue) => {
+    // 如果小于1,则保留最后两位不为0的数字
+    // 如果大于1小于10,则保留三位
+    // 大于10,保留两位
+    // 大于100,保留一位
+    // 大于1000,不保留
+    let fixed = 0;
+    if (maxValue < 1) {
+      const decimal = maxValue.toFixed(100).toString().split('.')[1];
+      const decimalArr = decimal.split('');
+      for (let index = 0; index < decimalArr.length; index++) {
+        if (decimalArr[index] === '0') {
+          fixed++;
+        } else {
+          break;
+        }
+      }
+      fixed += 2;
+    } else if (maxValue < 10) {
+      fixed = 3;
+    } else if (maxValue < 100) {
+      fixed = 2;
+    } else if (maxValue < 1000) {
+      fixed = 1;
+    }
+    return fixed;
+  };
+
+  const createChartData = async () => {
+    const result = await getChartData().catch(() => {
+      message.error('获取数据失败');
+    });
+    if (result && result.length) {
+      const [planChemPerCost, actualChemPerCost, planChem, actualChem] = result;
+      const chemPerCost = { yName: '吨水药耗(kg/t)' };
+      const chemUsed = { yName: '药量(kg)' };
+      chemPerCost.xData = [
+        ...new Set(
+          [
+            ...planChemPerCost.map((item) => item.month),
+            ...actualChemPerCost.map((item) => item.month),
+          ].map((item) => item),
+        ),
+      ].sort();
+
+      let year = `${dayjs(chemPerCost.xData[0]).year()}`;
+      chemPerCost.xData = [];
+      for (let index = 0; index < 12; index++) {
+        chemPerCost.xData.push(`${year}-${dayjs().month(index).format('MM')}`);
+      }
+
+      let topVals = { ...topValues };
+
+      // 确定保留的小数点
+      const chemPerCostMaxValue = [...planChemPerCost, ...actualChemPerCost]
+        .map((item) => item.value)
+        .reduce((a, b) => Math.max(a, b));
+      const chemPerCostFixed = getFixed(chemPerCostMaxValue);
+      console.log(chemPerCostFixed);
+      chemPerCost.dataList = [
+        {
+          type: 0,
+          yIndex: 1,
+          name: '计划吨水药耗',
+          data: chemPerCost.xData.map((month) => {
+            const pItem = planChemPerCost.find((item) => item.month === month);
+            if (pItem) {
+              return pItem.value?.toFixed(chemPerCostFixed);
+            }
+            return 0;
+          }),
+        },
+        {
+          type: 0,
+          yIndex: 1,
+          name: '实际吨水药耗',
+          data: chemPerCost.xData.map((month) => {
+            const aItem = actualChemPerCost.find(
+              (item) => item.month === month,
+            );
+            if (aItem) {
+              if (month == curMonth)
+                topVals.chemPer = aItem.value.toFixed(chemPerCostFixed);
+              return aItem.value.toFixed(chemPerCostFixed);
+            }
+            return 0;
+          }),
+        },
+      ];
+      // 合并+去重+排序 两组数据中所有月份
+      chemUsed.xData = [
+        ...new Set(
+          [
+            ...planChem.map((item) => item.month),
+            ...actualChem.map((item) => item.month),
+          ].map((item) => item),
+        ),
+      ].sort();
+
+      year = `${dayjs(chemUsed.xData[0]).year()}`;
+      chemUsed.xData = [];
+      for (let index = 0; index < 12; index++) {
+        chemUsed.xData.push(`${year}-${dayjs().month(index).format('MM')}`);
+      }
+
+      // 确定保留的小数点
+      const chemUsedMaxValue = [...planChem, ...actualChem]
+        .map((item) => item.value)
+        .reduce((a, b) => Math.max(a, b));
+      const chemUsedFixed = getFixed(chemUsedMaxValue);
+      chemUsed.dataList = [
+        {
+          type: 3,
+          yIndex: 1,
+          name: '计划用药量',
+          // 根据月份是否在xData内返回数据
+          data: chemUsed.xData.map((month) => {
+            const pItem = planChem.find((item) => item.month === month);
+            if (pItem) {
+              return pItem.value.toFixed(chemUsedFixed);
+            }
+            return 0;
+          }),
+        },
+        {
+          type: 3,
+          yIndex: 1,
+          name: '实际用药量',
+          data: chemUsed.xData.map((month) => {
+            const aItem = actualChem.find((item) => item.month === month);
+            if (aItem) {
+              if (month == curMonth)
+                topVals.chemUser = aItem.value.toFixed(chemUsedFixed);
+              return aItem.value.toFixed(chemUsedFixed);
+            }
+            return 0;
+          }),
+        },
+      ];
+      chemUsed.chartType = 'bar';
+      setTopValues(topVals);
+      setChartData([chemPerCost, chemUsed]);
+    } else {
+      setChartData([]);
+    }
+  };
+
+  const getChemList = async () => {
+    const list = await getChemicalAgents(projectId).catch(() => {
+      message.error('获取数据失败');
+    });
+    setChemList([...list]);
+    typeParams.forEach((item) => {
+      item.chemical_agents = list[0];
+    });
+  };
+
+  const handleChemChange = (type) => {
+    typeParams.forEach((item) => {
+      item.chemical_agents = type;
+    });
+    createChartData();
+  };
+
+  useEffect(() => {
+    (async () => {
+      await getChemList();
+      await createChartData();
+    })();
+  }, []);
+
+  return (
+    <PageContent closeable={false}>
+      <PageTitle returnable>能耗数据</PageTitle>
+
+      <div className="card-box" style={{ padding: 20, marginTop: 40 }}>
+        <div
+          onClick={(e) => {
+            e.stopPropagation();
+            setOpen(!open);
+          }}
+          className={`password-eye ${open ? 'open' : ''}`}
+        ></div>
+        <div className={styles.curEnergyCost}>
+          <div className={styles.item}>
+            <div className={styles.value}>
+              {open ? topValues.chemPer : '***'}
+              <span className={styles.unit}>kg/t</span>
+            </div>
+            <div className={styles.name}>当月吨水药耗</div>
+          </div>
+          <div className={styles.item}>
+            <div className={styles.value}>
+              {open ? topValues.chemUser : '***'}
+              <span className={styles.unit}>kg</span>
+            </div>
+            <div className={styles.name}>当月药量</div>
+          </div>
+        </div>
+        {chartData.length !== 0 && (
+          <div
+            style={{
+              height: '800px',
+              display: 'flex',
+              flexDirection: 'column',
+              justifyContent: 'space-evenly',
+            }}
+          >
+            <div style={{ height: '300px' }}>
+              <ChartModule {...chartData[0]} />
+            </div>
+
+            <div style={{ height: '300px' }}>
+              <ChartModule {...chartData[1]} />
+            </div>
+            {/* 使用Tabs来展示所有药的标签 */}
+            {chemList.length > 0 && (
+              <div className={styles.content}>
+                <Tabs
+                  activeKey={currentChem || chemList[0]}
+                  onChange={(type) => {
+                    setCurrentChem(type);
+                    handleChemChange(type);
+                  }}
+                >
+                  {chemList.map((item) => (
+                    <TabPane tab={item} key={item} />
+                  ))}
+                </Tabs>
+              </div>
+            )}
+          </div>
+        )}
+      </div>
+    </PageContent>
+  );
+};
+
+export default CostComparison;

+ 274 - 0
src/pages/Home/EnergyCostComparison.js

@@ -0,0 +1,274 @@
+// 能耗监测
+import ChartModule from '@/components/ManagementPage/chartModule';
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import { getComparisonData } from '@/services/OperationManagement';
+import { useParams } from '@umijs/max';
+import { message } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useState } from 'react';
+import styles from './manage.less';
+
+const typeParams = [
+  {
+    // 计划吨水电耗
+    type: '5',
+    flag: '0',
+  },
+  {
+    // 实际吨水电耗
+    type: '5',
+    flag: '1',
+  },
+  {
+    // 计划用电量
+    type: '6',
+    flag: '0',
+  },
+  {
+    // 实际用电量
+    type: '6',
+    flag: '1',
+  },
+];
+
+const CostComparison = () => {
+  const { projectId } = useParams();
+
+  const [open, setOpen] = useState(false);
+  const [chartData, setChartData] = useState([]);
+  const [curElecPerCost, setElecPerCost] = useState(0); // 当前月实际吨水电耗
+  const [curElecUsed, setCurElecUsed] = useState(0); // 当前月实际用电量
+
+  const defaultTime = {
+    s_time: `${dayjs().format('YYYY')}-${dayjs().startOf('year').format('MM')}`,
+    e_time: `${dayjs().format('YYYY')}-${dayjs().endOf('year').format('MM')}`,
+  };
+
+  const defaultParams = {
+    project_id: projectId,
+    start: defaultTime.s_time,
+    end: defaultTime.e_time,
+  };
+
+  const getChartData = () => {
+    // 构建请求列表
+    const queryList = [];
+    for (let index = 0; index < 4; index++) {
+      queryList.push(
+        getComparisonData({ ...defaultParams, ...typeParams[index] }),
+      );
+    }
+    // 获取四组数据
+    return Promise.all(queryList).catch(() => {
+      message.error('请求数据失败');
+    });
+  };
+
+  const getFixed = (maxValue) => {
+    // 如果小于1,则保留最后两位不为0的数字
+    // 如果大于1小于10,则保留三位
+    // 大于10,保留两位
+    // 大于100,保留一位
+    // 大于1000,不保留
+    let fixed = 0;
+    if (maxValue < 1) {
+      const decimal = maxValue.toFixed(100).toString().split('.')[1];
+      const decimalArr = decimal.split('');
+      for (let index = 0; index < decimalArr.length; index++) {
+        if (decimalArr[index] === '0') {
+          fixed++;
+        } else {
+          break;
+        }
+      }
+      fixed += 2;
+    } else if (maxValue < 10) {
+      fixed = 3;
+    } else if (maxValue < 100) {
+      fixed = 2;
+    } else if (maxValue < 1000) {
+      fixed = 1;
+    }
+    return fixed;
+  };
+
+  const createChartData = async () => {
+    const result = await getChartData().catch(() => {
+      message.error('获取数据失败');
+    });
+    if (result && result.length) {
+      const [planElecPerCost, actualElecPerCost, planElecUsed, actualElecUsed] =
+        result;
+      const elecPerCost = { yName: '吨水电耗(kWh/t)' };
+      const elecUsed = { yName: '电量(kWh)' };
+      elecPerCost.xData = [
+        ...new Set(
+          [
+            ...planElecPerCost.map((item) => item.month),
+            ...actualElecPerCost.map((item) => item.month),
+          ].map((item) => item),
+        ),
+      ].sort();
+
+      let year = `${dayjs(elecPerCost.xData[0]).year()}`;
+      elecPerCost.xData = [];
+      for (let index = 0; index < 12; index++) {
+        elecPerCost.xData.push(`${year}-${dayjs().month(index).format('MM')}`);
+      }
+
+      // 确定保留的小数点
+      const elecPerCostValue = [...planElecPerCost, ...actualElecPerCost]
+        .map((item) => item.value)
+        .reduce((a, b) => Math.max(a, b));
+      const elecPerCostFixed = getFixed(elecPerCostValue);
+      elecPerCost.dataList = [
+        {
+          type: 0,
+          yIndex: 1,
+          name: '计划吨水电耗',
+          data: elecPerCost.xData.map((month) => {
+            const pItem = planElecPerCost.find((item) => item.month === month);
+            if (pItem) {
+              return pItem.value.toFixed(elecPerCostFixed);
+            }
+            return 0;
+          }),
+        },
+        {
+          type: 0,
+          yIndex: 1,
+          name: '实际吨水电耗',
+          data: elecPerCost.xData.map((month) => {
+            const aItem = actualElecPerCost.find(
+              (item) => item.month === month,
+            );
+            if (aItem) {
+              return aItem.value.toFixed(elecPerCostFixed);
+            }
+            return 0;
+          }),
+        },
+      ];
+
+      elecUsed.xData = [
+        ...new Set(
+          [
+            ...planElecUsed.map((item) => item.month),
+            ...actualElecUsed.map((item) => item.month),
+          ].map((item) => item),
+        ),
+      ].sort();
+
+      year = `${dayjs(elecUsed.xData[0]).year()}`;
+      elecUsed.xData = [];
+      for (let index = 0; index < 12; index++) {
+        elecUsed.xData.push(`${year}-${dayjs().month(index).format('MM')}`);
+      }
+
+      // 确定保留的小数点
+      const elecUsedMaxValue = [...planElecUsed, ...actualElecUsed]
+        .map((item) => item.value)
+        .reduce((a, b) => Math.max(a, b));
+      const elecUsedFixed = getFixed(elecUsedMaxValue);
+      elecUsed.dataList = [
+        {
+          type: 3,
+          yIndex: 1,
+          name: '计划用电量',
+          data: elecUsed.xData.map((month) => {
+            const pItem = planElecUsed.find((item) => item.month === month);
+            if (pItem) {
+              return pItem.value.toFixed(elecUsedFixed);
+            }
+            return 0;
+          }),
+        },
+        {
+          type: 3,
+          yIndex: 1,
+          name: '实际用电量',
+          data: elecUsed.xData.map((month) => {
+            const aItem = actualElecUsed.find((item) => item.month === month);
+            if (aItem) {
+              return aItem.value.toFixed(elecUsedFixed);
+            }
+            return 0;
+          }),
+        },
+      ];
+      elecUsed.chartType = 'bar';
+      setChartData([elecPerCost, elecUsed]);
+
+      var curElecPerCost = actualElecPerCost?.find((item) =>
+        dayjs().isSame(item?.month, 'month'),
+      );
+      if (curElecPerCost)
+        setElecPerCost(curElecPerCost?.value.toFixed(elecPerCostFixed));
+
+      var curElecUsed = actualElecUsed?.find((item) =>
+        dayjs().isSame(item?.month, 'month'),
+      );
+      if (curElecUsed)
+        setCurElecUsed(curElecUsed?.value.toFixed(elecUsedFixed));
+    } else {
+      setChartData([]);
+    }
+  };
+
+  useEffect(() => {
+    createChartData();
+  }, []);
+
+  return (
+    <PageContent closeable={false}>
+      <PageTitle returnable>能耗数据</PageTitle>
+
+      <div className="card-box" style={{ padding: 20, marginTop: 40 }}>
+        <div
+          onClick={(e) => {
+            e.stopPropagation();
+            setOpen(!open);
+          }}
+          className={`password-eye ${open ? 'open' : ''}`}
+        ></div>
+        <div className={styles.curEnergyCost}>
+          <div className={styles.item}>
+            <div className={styles.value}>
+              {open ? curElecPerCost : '***'}
+              <span className={styles.unit}>kWh/t</span>
+            </div>
+            <div className={styles.name}>当月吨水电耗</div>
+          </div>
+          <div className={styles.item}>
+            <div className={styles.value}>
+              {open ? curElecUsed : '***'}
+              <span className={styles.unit}>kWh</span>
+            </div>
+            <div className={styles.name}>当月实际用电量</div>
+          </div>
+        </div>
+        {chartData.length !== 0 && (
+          <div
+            style={{
+              height: '800px',
+              display: 'flex',
+              flexDirection: 'column',
+              justifyContent: 'space-evenly',
+            }}
+          >
+            <div style={{ height: '300px' }}>
+              <ChartModule {...chartData[0]} />
+            </div>
+
+            <div style={{ height: '300px' }}>
+              <ChartModule {...chartData[1]} />
+            </div>
+          </div>
+        )}
+      </div>
+    </PageContent>
+  );
+};
+
+export default CostComparison;

+ 141 - 0
src/pages/Home/QualityMng.js

@@ -0,0 +1,141 @@
+//水质管理
+import ChartModule from '@/components/ManagementPage/chartModule';
+// import SearchModule from '@/components/ManagementPage/searchModule';
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import {
+  queryChartListByCode,
+  queryProcessSection,
+  querySectionCode,
+} from '@/services/OperationManagement';
+import { useParams, useRequest } from '@umijs/max';
+import { Empty, Spin, Table } from 'antd';
+import dayjs from 'dayjs';
+import { useEffect, useMemo, useRef, useState } from 'react';
+
+function Quality() {
+  const { projectId } = useParams();
+  const [currentCode, setCode] = useState(null);
+  // const [processId, setProcessId] = useState(null);
+
+  const timerRef = useRef({
+    s_time: dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss'),
+    e_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  });
+
+  const { data: codeList, run: queryCodeList } = useRequest(querySectionCode, {
+    manual: true,
+    onSuccess(res) {
+      if (res && res.length > 0) {
+        setCode(res[0]);
+      }
+    },
+  });
+
+  // 获取工艺段列表
+  useRequest(queryProcessSection, {
+    defaultParams: [projectId],
+    onSuccess(res) {
+      // setProcessId(res.data[0].id);
+      queryCodeList(res[0].id, 2, projectId * 1);
+    },
+  });
+  const mainRes = useRequest(
+    () => {
+      return queryChartListByCode(
+        {
+          project_id: Number(projectId),
+          start_time: timerRef.current.s_time,
+          end_time: timerRef.current.e_time,
+        },
+        codeList?.map((item) => item.metric_code),
+      );
+    },
+    {
+      manual: true,
+    },
+  );
+  const column = useMemo(() => {
+    const col = { title: '时间', dataIndex: 'time', width: '250px' };
+    let other = codeList?.map((item) => {
+      return {
+        title: item.metric,
+        dataIndex: item.metric_code,
+        width: '200px',
+      };
+    });
+    return other && other.length > 0 ? [col, ...other] : [];
+  }, [codeList]);
+
+  //图表配置
+  const chartProps = useMemo(() => {
+    let xData = [],
+      dataList = [];
+    if (!mainRes.data || !currentCode) return;
+    let yName = currentCode.metric;
+    let data = [...mainRes.data].reverse();
+    xData = data.map((item) => item.time);
+    // dataList.push({
+    //   type: 0,
+    //   name: '出水水量',
+    //   data: data.map((item) => item.permeate),
+    // });
+    // dataList.push({
+    //   type: 1,
+    //   name: '进水水量',
+    //   data: data.map((item) => item.feed),
+    // });
+    dataList.push({
+      type: 2,
+      name: currentCode.metric,
+      data: data.map((item) => item[currentCode.metric_code]),
+    });
+    return {
+      yName,
+      xData,
+      dataList,
+      typeList: codeList?.map((item) => item.metric),
+      currentType: currentCode.metric,
+    };
+  }, [mainRes.data, currentCode]);
+
+  useEffect(() => {
+    if (codeList) {
+      mainRes.run();
+    }
+  }, [codeList]);
+
+  const onChange = (name) => {
+    const code = codeList.find((item) => item.metric == name);
+    if (code) setCode(code);
+  };
+
+  return (
+    <PageContent closeable={false}>
+      <PageTitle returnable>水质监测</PageTitle>
+      <div className="card-box" style={{ padding: 20, marginTop: 40 }}>
+        <PageTitle>数据曲线</PageTitle>
+        <Spin spinning={mainRes.loading}>
+          <div style={{ height: 500, marginTop: 20 }}>
+            {mainRes?.data ? (
+              <ChartModule {...chartProps} onChange={onChange} />
+            ) : (
+              <Empty />
+            )}
+          </div>
+        </Spin>
+        <div style={{ marginTop: 30 }}>
+          <PageTitle>数据列表</PageTitle>
+          <Table
+            columns={column}
+            style={{ marginTop: 20 }}
+            dataSource={mainRes?.data}
+            scroll={{ x: 2500 }}
+          />
+        </div>
+      </div>
+    </PageContent>
+  );
+}
+
+export default Quality;

+ 112 - 0
src/pages/Home/WaterAmtMng.js

@@ -0,0 +1,112 @@
+// 水量监测
+import ChartModule from '@/components/ManagementPage/chartModule';
+// import SearchModule from '@/components/ManagementPage/searchModule';
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import { queryChartListByCode } from '@/services/OperationManagement';
+import { useParams, useRequest } from '@umijs/max';
+import { Spin, Table } from 'antd';
+import dayjs from 'dayjs';
+import { useMemo, useState } from 'react';
+
+const WaterAmtMng = () => {
+  const { projectId } = useParams();
+
+  const [currentPage, setPage] = useState(1);
+
+  const { data, loading, run } = useRequest(
+    (date) =>
+      queryChartListByCode(
+        {
+          start_time: date.s_time,
+          end_time: date.e_time,
+          project_id: Number(projectId),
+          order: 1,
+        },
+        ['plant_permeate_flow', 'plant_feed_flow'],
+      ),
+    {
+      defaultParams: [
+        {
+          s_time: dayjs().subtract(1, 'day').format('YYYY-MM-DD 00:00:00'),
+          e_time: dayjs().format('YYYY-MM-DD 23:59:59'),
+        },
+      ],
+    },
+  );
+
+  const columns = [
+    {
+      title: '时间',
+      dataIndex: 'time',
+    },
+    {
+      title: '进水水量(t)',
+      dataIndex: 'plant_feed_flow',
+    },
+    {
+      title: '实际出水水量(t)',
+      dataIndex: 'plant_permeate_flow',
+    },
+    {
+      title: '预测出水水量(t)',
+      // dataIndex: '',
+      render: () => '-',
+    },
+  ];
+
+  const { dataList, xData } = useMemo(() => {
+    const dataList = [];
+    let xData = [];
+    if (data) {
+      xData = data.map((item) => item.time);
+      dataList.push({
+        type: 0,
+        name: '实际出水水量',
+        data: data.map((item) => item.plant_permeate_flow),
+      });
+      dataList.push({
+        type: 1,
+        name: '进水水量',
+        data: data.map((item) => item.plant_feed_flow),
+      });
+    }
+    return { dataList, xData };
+  }, [data]);
+
+  const onChangePage = (pagination) => {
+    setPage(pagination.current);
+  };
+
+  const onSearch = (date) => {
+    run(date);
+    setPage(1);
+  };
+
+  return (
+    <PageContent closeable={false}>
+      <PageTitle returnable>水量监测</PageTitle>
+      <div className="card-box" style={{ padding: 20, marginTop: 40 }}>
+        <PageTitle>数据曲线</PageTitle>
+        <Spin spinning={loading}>
+          <div style={{ height: '300px', marginTop: 20 }}>
+            <ChartModule yName="水量(t)" xData={xData} dataList={dataList} />
+          </div>
+        </Spin>
+        <div style={{ marginTop: 30 }}>
+          <PageTitle>数据列表</PageTitle>
+          <Table
+            loading={loading}
+            columns={columns}
+            style={{ marginTop: 20 }}
+            pagination={{ current: currentPage }}
+            onChange={onChangePage}
+            dataSource={data?.sort((a, b) => b?.time?.localeCompare(a?.time))}
+          />
+        </div>
+      </div>
+    </PageContent>
+  );
+};
+
+export default WaterAmtMng;

+ 189 - 13
src/pages/Home/index.js

@@ -1,30 +1,206 @@
+import { queryConditionSnapshot, queryEnergy } from '@/services/SmartOps';
+import { LoadingOutlined } from '@ant-design/icons';
+import { connect, history, useParams, useRequest } from '@umijs/max';
+import { useEffect } from 'react';
+import CircleScore from '../Smart/components/CircleScore';
+import styles from './index.less';
 
-import { useRequest,useParams } from '@umijs/max';
-import styles from "./index.less"
+const HomePage = (props) => {
+  const { projectId } = useParams();
 
-const HomePage= (props) => {
+  const { data } = useRequest(queryConditionSnapshot, {
+    defaultParams: [{ project_id: projectId }],
+  });
   return (
-    <div>
-      
+    <div className={styles.content}>
+      <LeftContent data={data} />
+      <RightContent data={data} />
     </div>
-    
   );
 };
 
 const LeftContent = (props) => {
+  const { data } = props;
   return (
-    <div>
-      <div className={styles.box}></div>
+    <div className={styles.left}>
+      <SmartWork data={data} />
+      <WaterAmt data={data} />
+      <WaterQuality data={data} />
     </div>
-    
   );
 };
-const rightContent = (props) => {
+const RightContent = (props) => {
+  const { data } = props;
   return (
-    <div>
-      
+    <div className={styles.right}>
+      <SelfInspection />
+      <Electric data={data} />
+      <Medicine />
+    </div>
+  );
+};
+
+// 水厂工况
+const SmartWork = (props) => {
+  const { projectId } = useParams();
+  const { data } = props;
+  return (
+    <Box
+      title="水厂工况"
+      onClick={() => history.push(`/smart/work/${projectId}`)}
+    >
+      <div className={styles.scoreBox}>
+        <CircleScore>
+          {data?.score}
+          <div className={styles.grade}>{data?.grade}</div>
+        </CircleScore>
+        <div className={styles.scoreTitle}>
+          当前运行{data?.grade},可继续优化
+        </div>
+        <div className={styles.time}>{data?.clac_time}</div>
+      </div>
+    </Box>
+  );
+};
+
+// 水量监测
+const WaterAmt = (props) => {
+  const { data } = props;
+  const { projectId } = useParams();
+  return (
+    <Box
+      title="水量监测"
+      onClick={() => history.push(`/home/water-amt-mng/${projectId}`)}
+    >
+      <ul>
+        <li>
+          <div className={styles.value}>{data?.fwa}</div>
+          <div className={styles.name}>进水量</div>
+        </li>
+        <li>
+          <div className={styles.value}>{data?.dwa}</div>
+          <div className={styles.name}>产水量</div>
+        </li>
+      </ul>
+    </Box>
+  );
+};
+
+// 水质监测
+const WaterQuality = (props) => {
+  const { data } = props;
+  const { projectId } = useParams();
+  return (
+    <Box
+      title="水质监测"
+      onClick={() => history.push(`/home/water-quality-mng/${projectId}`)}
+    >
+      <ul>
+        <li>
+          <div className={styles.value}>{data?.dsan}</div>
+          <div className={styles.name}>出水余氯</div>
+        </li>
+        <li>
+          <div className={styles.value}>{data?.dtur}</div>
+          <div className={styles.name}>出水浊度</div>
+        </li>
+      </ul>
+    </Box>
+  );
+};
+
+// 系统自检
+const SelfInspection = connect(({ eqSelfInspection, loading }) => ({
+  autoReport: eqSelfInspection.autoReport,
+  loading: loading.models['eqSelfInspection'],
+}))((props) => {
+  const { autoReport, dispatch, loading } = props;
+  const { projectId } = useParams();
+
+  const renderStatus = () => {
+    if (loading) return <LoadingOutlined />;
+    if (autoReport.warningTotalNum) {
+      return (
+        <>
+          系统自检发现
+          <span style={{ color: '#FE5850' }}>{autoReport.warningTotalNum}</span>
+          项异常
+        </>
+      );
+    }
+    return <span style={{ color: '#F5A623' }}>正常</span>;
+  };
+
+  useEffect(() => {
+    dispatch({
+      type: 'eqSelfInspection/getAutoPatrol',
+      payload: {
+        projectId,
+      },
+    });
+  }, []);
+
+  return (
+    <Box
+      title="系统自检"
+      onClick={() =>
+        history.push(`/self-inspection/detail/${projectId}/${autoReport?.Id}`)
+      }
+    >
+      <div className={styles.insTag}>自检中</div>
+
+      <div className={styles.insStatus}>{renderStatus()}</div>
+      <div className={styles.time}>{autoReport.CreatedTime}</div>
+    </Box>
+  );
+});
+
+// 能耗监测
+const Electric = (props) => {
+  const { data } = props;
+  const { projectId } = useParams();
+  return (
+    <Box
+      title="能耗监测"
+      onClick={() => history.push(`/home/energy/${projectId}`)}
+    >
+      <ul>
+        <li>
+          <div className={styles.value}>{data?.elec}</div>
+          <div className={styles.name}>用电量</div>
+        </li>
+      </ul>
+    </Box>
+  );
+};
+// 药耗监测
+const Medicine = () => {
+  const { projectId } = useParams();
+
+  const { data } = useRequest(queryEnergy, {
+    defaultParams: [Number(projectId)],
+  });
+  return (
+    <Box
+      title="药耗监测"
+      onClick={() => history.push(`/home/chem-cost/${projectId}`)}
+    >
+      <ul>
+        <li>
+          <div className={styles.value}>{data?.medicine}kg</div>
+          <div className={styles.name}>总药耗</div>
+        </li>
+      </ul>
+    </Box>
+  );
+};
+
+const Box = ({ title, children, onClick }) => {
+  return (
+    <div className={styles.box} onClick={onClick}>
+      <div className={styles.boxTitle}>{title}</div>
+      {children}
     </div>
-    
   );
 };
 

+ 79 - 1
src/pages/Home/index.less

@@ -1,4 +1,82 @@
+.content {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 100vh;
+  width: 100%;
+}
 .box {
-  background: url("@/assets/home-box-bg.png") no-repeat center;
+  background: url('@/assets/home-box-bg.png') no-repeat center;
   background-size: 100% 100%;
+  width: 400px;
+  padding: 20px;
+  position: relative;
+  margin-bottom: 40px;
+  cursor: pointer;
+  color: #fff;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+
+  .boxTitle {
+    font-size: 24px;
+    padding-left: 46px;
+    margin-bottom: 20px;
+  }
+  > ul {
+    display: flex;
+    justify-content: center;
+    > li {
+      width: 50%;
+    }
+  }
+  .name {
+    margin-top: 20px;
+    font-size: 18px;
+    text-align: center;
+  }
+  .value {
+    padding-top: 14px;
+    color: #3198fa;
+    font-size: 24px;
+    text-align: center;
+  }
+}
+
+.scoreBox {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+
+  .scoreTitle {
+    font-size: 20px;
+    margin-top: 10px;
+    margin-bottom: 14px;
+  }
+  .grade {
+    font-size: 18px;
+    margin-top: 10px;
+  }
+}
+
+.time {
+  font-size: 18px;
+  margin-bottom: 20px;
+  text-align: center;
+}
+.insTag {
+  position: absolute;
+  right: 30px;
+  top: 30px;
+  background: #4a90e2;
+  border-radius: 8px;
+  padding: 10px 20px;
+}
+
+.insStatus {
+  text-align: center;
+  font-size: 34px;
+  margin-bottom: 30px;
+  margin-top: 60px;
 }

+ 90 - 0
src/pages/Home/manage.less

@@ -0,0 +1,90 @@
+.title {
+  display: inline;
+  margin-bottom: 10px;
+  font-size: 24px;
+  color: #02a7f0;
+}
+.itemContent {
+  margin: 6px 6px;
+  text-align: center;
+  flex-grow: 1;
+  background-color: #2f62ae;
+  width: 20%;
+  padding: 10px 0;
+  .data {
+    color: palevioletred;
+    font-size: 24px;
+    margin-right: 6px;
+  }
+}
+
+.content {
+  :global {
+    .ant-tabs-nav-wrap {
+      background: none;
+    }
+    .ant-tabs-nav .ant-tabs-tab {
+      padding: 2px 16px;
+      background-color: #2196f330;
+      border: none;
+      margin: 0 6px;
+    }
+    .ant-tabs-tab-active {
+      background-color: #366cda !important;
+    }
+    .ant-tabs-tab-active .ant-tabs-tab-btn {
+      color: #fff !important;
+    }
+    .ant-tabs-ink-bar {
+      display: none;
+    }
+
+    .ant-tabs-bar {
+      margin: 0;
+    }
+    // .ant-tabs-tab-prev-icon{
+    //   border: solid 20px ;
+    //   border-color: transparent #366CDA transparent  transparent ;
+    //   i{
+    //     width: 0;
+    //     height: 0;
+    //   }
+    // }
+  }
+}
+.curEnergyCost {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin: 40px 40px 0 40px;
+  .item {
+    width: 50%;
+    letter-spacing: 2px;
+    position: relative;
+    font-size: 24px;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    .value {
+      display: flex;
+      justify-content: center;
+      align-items: flex-end;
+      line-height: 1;
+      margin-bottom: 8px;
+      color: #f6af3b;
+      font-size: 34px;
+    }
+    .name {
+      font-size: 16px;
+      margin-bottom: 4px;
+      max-width: 80%;
+    }
+    .unit {
+      font-size: 16px;
+      margin-left: 6px;
+    }
+  }
+}

+ 391 - 0
src/services/OperationManagement.js

@@ -0,0 +1,391 @@
+import dayjs from 'dayjs';
+import { stringify } from 'qs';
+import { request } from 'umi';
+
+// 能耗标牌
+export async function query() {
+  const res = await request(
+    `/api/energy/v1/energy-card/list?project_id=${projectId}`,
+  );
+  return res;
+}
+
+// 全场概况 查询能耗配置相关
+export async function queryEnergyConfig(projectId) {
+  const res = await request(
+    `/api/energy/v1/energy-config?project_id=${projectId}`,
+  );
+  return res;
+}
+
+// 当日累计能耗、吨水电耗、环比
+export async function queryAccumulativeEnergy(projectId) {
+  const res = await request(
+    `/api/energy/v1/accumulative-energy?project_id=${projectId}`,
+  );
+  return res;
+}
+
+// 工艺段下总电耗曲线
+export async function querySectionEnergy(params) {
+  const res = await request(
+    `/api/energy/v1/section-energy?${stringify(params)}`,
+  );
+  return res;
+}
+
+// 工艺段下各设备的用电情况
+export async function querySectionDevice(params) {
+  const res = await request(
+    `/api/energy/v1/section-device?${stringify(params)}`,
+  );
+  return res;
+}
+
+// 吨水电耗折线图
+export async function queryEnergyWaterChart(params) {
+  const res = await request(
+    `/api/energy/v1/energy-water-chart?${stringify(params)}`,
+  );
+  return res;
+}
+
+/**
+ * 根据工艺段id查询code
+ * @param {*} processId 工艺段id
+ * @param {*} type 0 能耗 1 药耗 2 水质 3 水量
+ * @param project_id
+ * @returns
+ */
+export async function querySectionCode(processId, type, project_id) {
+  const res = await request(
+    `/api/energy/v1/section-code?process_section_id=${processId}&type=${type}&project_id=${project_id}`,
+  );
+  return res.data;
+}
+
+/**
+ *
+ * @param {*} data
+ * @param {*} data.project_id
+ * @param {*} data.metric_code
+ * @param {*} data.start_time
+ * @param {*} data.end_time
+ * @returns
+ */
+export async function queryChartList(data) {
+  if (!data.order) data.order = 2;
+  const res = await request(`/api/energy/v1/chart/list`, {
+    method: 'POST',
+    data,
+  });
+  return res.data;
+}
+
+// 水检测折线图 参数同上
+export async function queryWaterCheckChartList(data) {
+  if (!data.order) data.order = 2;
+  const res = await request(`/api/energy/v1/mon-chart/list`, {
+    method: 'POST',
+    data,
+  });
+  return res.data;
+}
+
+/**
+ *  根据metric_code返回数据
+ * @param {*} data 同queryChartList的data
+ * @param {Array|string} codes metric_code
+ * @param {Boolean} isCheck 是否为水检测数据
+ * @returns [{ time:string, [code]:number}]
+ */
+export async function queryChartListByCode(data, codes, isCheck = false) {
+  let dataSource = [];
+  if (codes instanceof Array) {
+    const res = {};
+    let time = [];
+    for (let i = 0; i < codes.length; i++) {
+      const metric_code = codes[i];
+      let chartRes;
+      if (isCheck) {
+        chartRes = await queryWaterCheckChartList({ ...data, metric_code });
+      } else {
+        chartRes = await queryChartList({ ...data, metric_code });
+      }
+
+      if (i == 0) {
+        time = chartRes.data?.map((item) => item.data_time);
+      }
+      res[metric_code] = chartRes.data?.map((item) => item.value.toFixed(2));
+    }
+    console.log(res, codes);
+    dataSource = time.map((time, index) => {
+      const data = { time };
+      codes.forEach((code) => {
+        data[code] = res[code]?.[index];
+      });
+      return data;
+    });
+  } else if (typeof codes === 'string') {
+    const res = await queryChartList({ ...data, metric_code: codes });
+    dataSource = res.data.map((item) => ({
+      time: item.data_time,
+      [metric_code]: item.value,
+    }));
+  }
+  return { data: dataSource };
+}
+
+/**
+ *
+ *
+ * @param {*} metric_code //药耗code数组
+ * @param {*} water_code  //水质单个code
+ * @param {*} params.start_time
+ * @param {*} params.end_time
+ *  @param {*} params.projectId
+ * @returns
+ */
+export async function queryChartMedicineAndWater(
+  metric_code,
+  water_code,
+  params,
+) {
+  const data = {};
+  let time = [];
+  const waterRes = await queryChartList({
+    project_id: params.projectId,
+    start_time: params.start_time,
+    end_time: params.end_time,
+    metric_code: water_code?.metric_code,
+    order: 1,
+  });
+  data[water_code.metric_code] = {
+    data: waterRes.map((item) => item.value),
+    type: 2,
+    name: water_code?.metric,
+    yIndex: 0,
+  };
+  if (metric_code instanceof Array) {
+    for (let index = 0; index < metric_code.length; index++) {
+      const element = metric_code[index];
+      const res = await queryChartList({
+        project_id: params.projectId,
+        start_time: params.start_time,
+        end_time: params.end_time,
+        metric_code: element?.metric_code,
+        order: 1,
+      });
+      if (index == 0) {
+        time = res.map((item) => item.data_time);
+      }
+      // type	是	0 能耗 1 药耗 2 水质 3 水量
+      data[element.metric_code] = {
+        data: res.map((item) => item.value),
+        type: 0,
+        name: element?.metric,
+        yIndex: 1,
+      };
+    }
+  }
+  return { data: { data, time } };
+}
+
+export async function queryProcessSection(projectId) {
+  const res = await request(
+    `/api/v1/process-section/${projectId}?page_size=999`,
+  );
+  return { data: res?.data?.list };
+}
+
+export async function queryAnalysisDict() {
+  const res = await request(
+    `/api/analysis/v1/analysis-dict/list?page_size=9999`,
+    {
+      method: 'POST',
+    },
+  );
+  return { data: res?.data?.list };
+}
+
+export async function queryAnalysisDictByParams(params) {
+  const res = await request(
+    `/api/analysis/v1/analysis-dict/list?${stringify(params)}`,
+    {
+      method: 'POST',
+    },
+  );
+  return { data: res?.data?.list };
+}
+
+// 成本分析-能耗监测
+export async function queryEnergyChartList(data) {
+  if (!data.order) data.order = 2;
+  const res = await request(`/api/energy/v1/energy-chart/list`, {
+    method: 'POST',
+    data,
+  });
+  return res;
+}
+
+// 能耗管理导入列表
+export async function queryEnergyUpload(data) {
+  const res = await request(`/api/energy/v1/statday/list`, {
+    method: 'POST',
+    headers: { ContentType: 'application/json' },
+    data,
+  });
+  return res;
+}
+
+export async function deleteEnergyUpload(data) {
+  const res = await request(`/api/energy/v1/statday/del`, {
+    method: 'POST',
+    headers: { ContentType: 'application/json' },
+    data,
+  });
+  return res;
+}
+
+export async function createStat(data) {
+  await request(`/api/energy/v1/statday/create`, {
+    method: 'POST',
+    headers: { ContentType: 'application/json' },
+    data,
+  });
+}
+
+export async function modifySampleMetric(data) {
+  const res = await request(`/api/energy/v1/sample-metric`, {
+    method: 'POST',
+    data,
+  });
+  return res;
+}
+
+export async function deleteBreakerExp(data) {
+  const res = await request(`/api/energy/v1/beaker/del`, {
+    method: 'POST',
+    data,
+  });
+  return res;
+}
+
+export async function saveBreakerExp(data) {
+  const res = await request(`/api/energy/v1/beaker/save`, {
+    method: 'POST',
+    data,
+  });
+  return res;
+}
+
+export async function editBreakerExp(data) {
+  const res = await request(`/api/energy/v1/beaker/edit`, {
+    method: 'POST',
+    data,
+  });
+}
+
+export async function queryBreakerExpList(data) {
+  const res = await request(`/api/energy/v1/beaker/list`, {
+    method: 'POST',
+    data,
+  });
+  return res;
+}
+
+export async function querySampleMetric(params) {
+  const res = await request(
+    `/api/energy/v1/sample-metric-list?${stringify(params)}`,
+  );
+  return res;
+}
+
+// 水质异常
+export async function queryQualityAbnormal(params) {
+  const res = await request(`/api/task/v1/water-monitor/list`, {
+    method: 'POST',
+    headers: { ContentType: 'application/json' },
+    body: params,
+  });
+  return res;
+}
+
+export async function dealQualityAbnormal(params) {
+  await request(`/api/task/v1/set/water-monitor`, {
+    method: 'POST',
+    headers: { ContentType: 'application/json' },
+    body: params,
+  });
+}
+
+export async function getStatisticsChart(data_type) {
+  let s_time;
+  const e_time = dayjs().format('YYYY-MM-DD HH:mm:ss');
+  switch (data_type) {
+    case '1':
+      s_time = dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss');
+      break;
+    case '2':
+      s_time = dayjs().subtract(1, 'months').format('YYYY-MM-DD HH:mm:ss');
+      break;
+    case '3':
+      s_time = dayjs().subtract(1, 'years').format('YYYY-MM-DD HH:mm:ss');
+      break;
+    case '4':
+      s_time = dayjs().subtract(5, 'years').format('YYYY-MM-DD HH:mm:ss');
+      break;
+  }
+  const baseParams = {
+    start_time: s_time,
+    end_time: e_time,
+    data_type,
+    project_id: 65,
+  };
+  const params = [
+    {
+      ...baseParams,
+      name: '北侧流量计',
+      metric_code: '172_168_184_200.FIT111_TOT',
+    },
+    {
+      ...baseParams,
+      name: '南侧流量计',
+      metric_code: '172_168_184_200.FIT112_TOT',
+    },
+  ];
+  const dataList = [];
+  let xData = [];
+  for (let i = 0; i < params.length; i++) {
+    const item = params[i];
+    const res = await request(
+      `/api/energy/v1/statistics/chart?${stringify(item)}`,
+    );
+    if (i == 0) {
+      xData = res.data.data.map((item) =>
+        dayjs(item.data_time).format('YYYY-MM-DD HH:mm:ss'),
+      );
+    }
+    dataList.push({
+      name: item.name,
+      data: res.data.data.map((item) => item.value),
+    });
+  }
+  return { data: { xData, dataList } };
+}
+
+/**
+ * @param  {*} params
+ * @returns {Promise<null>}
+ */
+export async function getComparisonData(params) {
+  const res = await request(`/api/energy/v1/month/chart?${stringify(params)}`);
+  return res?.data?.list;
+}
+
+export async function getChemicalAgents(projectID) {
+  const res = await request(
+    `/api/energy/v1/chemical_agents?project_id=${projectID}`,
+  );
+  return res?.data?.list;
+}

+ 11 - 1
src/services/SmartOps.js

@@ -1,11 +1,21 @@
 import { stringify } from 'qs';
 import { request } from 'umi';
 
+export async function queryEnergy(projectId) {
+  const res = await request(`/api/energy/v1/analy-card/list`,{
+    method: 'POST',
+    data: {
+      project_id: projectId
+    }
+  });
+  return res;
+}
+
 export async function queryProcessSection(projectId) {
   const res = await request(
     `/api/v1/process-section/${projectId}?page_size=999`,
   );
-  return res?.data?.list;
+  return { data: res?.data?.list };
 }
 
 export async function queryUserList(param) {