Browse Source

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/gt_client_pad into develop
解决冲突
# Conflicts:
# .umirc.ts

Renxy 1 year ago
parent
commit
afbfb2671c
41 changed files with 3617 additions and 122 deletions
  1. 1 0
      .gitignore
  2. 42 5
      .umirc.ts
  3. 1 0
      package.json
  4. 3 3
      src/app.ts
  5. BIN
      src/assets/air-conditioner.png
  6. 133 0
      src/components/ManagementPage/BarChartModule.js
  7. 57 0
      src/components/ManagementPage/ManagementPage.less
  8. 98 0
      src/components/ManagementPage/PieChartModule.js
  9. 136 0
      src/components/ManagementPage/RadarChartModule.js
  10. 42 0
      src/components/ManagementPage/TypeSelct.js
  11. 90 0
      src/components/ManagementPage/chartModal.js
  12. 422 0
      src/components/ManagementPage/chartModule.js
  13. 66 0
      src/components/ManagementPage/index.less
  14. 28 0
      src/components/ManagementPage/moduleTitle.js
  15. 149 0
      src/components/ManagementPage/searchModule.js
  16. 0 8
      src/components/ThresholdDetail/ThresholdModal.js
  17. 0 0
      src/models/eqSelfInspection.js
  18. 1 33
      src/pages/EqSelfInspection/components/Detail.js
  19. 55 0
      src/pages/HardwareController/AirConditioner.js
  20. 54 0
      src/pages/HardwareController/Light.js
  21. 57 0
      src/pages/HardwareController/index.less
  22. 317 0
      src/pages/Home/ChemCostComparison.js
  23. 274 0
      src/pages/Home/EnergyCostComparison.js
  24. 141 0
      src/pages/Home/QualityMng.js
  25. 112 0
      src/pages/Home/WaterAmtMng.js
  26. 189 13
      src/pages/Home/index.js
  27. 79 1
      src/pages/Home/index.less
  28. 90 0
      src/pages/Home/manage.less
  29. 3 3
      src/pages/Smart/components/SimulateDetail.js
  30. 143 0
      src/pages/TaskManage/Detail/TaskDetail.tsx
  31. 56 0
      src/pages/TaskManage/Detail/detail.types.ts
  32. 126 0
      src/pages/TaskManage/constent.ts
  33. 2 26
      src/pages/TaskManage/index.less
  34. 89 27
      src/pages/TaskManage/index.tsx
  35. 23 2
      src/pages/TaskManage/index.types.ts
  36. 28 0
      src/pages/TaskManage/models/user.js
  37. 391 0
      src/services/OperationManagement.js
  38. 11 1
      src/services/SmartOps.js
  39. 79 0
      src/services/TaskManage.js
  40. 5 0
      src/services/user.js
  41. 24 0
      yarn.lock

+ 1 - 0
.gitignore

@@ -12,3 +12,4 @@
 /.mfsu
 .swc
 /.idea
+dist.zip

+ 42 - 5
.umirc.ts

@@ -8,18 +8,20 @@ export default defineConfig({
   request: {},
   dva: {},
   layout: false,
-  publicPath: process.env.NODE_ENV == 'development' ? '/' : '/gt-dig/',
+  // publicPath: process.env.NODE_ENV == 'development' ? '/' : '/gt-dig/',
+  publicPath: '/',
   metas: [
     // 配置html禁止缓存
     { 'http-equiv': 'pragma', content: 'no-cache' },
     { '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 +29,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',
@@ -54,6 +76,16 @@ export default defineConfig({
       path: '/smart/simulate/:projectId',
       component: './Smart/Simulate',
     },
+    {
+      name: '空调监控',
+      path: '/hardware-controller/air-conditioner/:projectId',
+      component: './HardwareController/AirConditioner',
+    },
+    {
+      name: '空调监控',
+      path: '/hardware-controller/light/:projectId',
+      component: './HardwareController/Light',
+    },
     {
       name: '系统自检',
       path: '/self-inspection/:projectId',
@@ -76,7 +108,7 @@ export default defineConfig({
     },
     {
       name: '任务管理',
-      path: '/task-manage/:projectId',
+      path: '/task-manage/:projectID',
       component: './TaskManage',
     },
     {
@@ -94,6 +126,11 @@ export default defineConfig({
       path: '/device/detail/:projectId/:type',
       component: './DeviceManager/detail',
     },
+    {
+      name: '任务管理-详情',
+      path: '/task-manage/detail',
+      component: './TaskManage/Detail/TaskDetail',
+    },
     // {
     //   name: '权限演示',
     //   path: '/access',

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "@umijs/max": "^4.0.75",
     "antd": "^5.4.0",
     "echarts": "^5.4.3",
+    "md5": "^2.3.0",
     "qs": "^6.11.2",
     "react-zmage": "^0.8.5"
   },

+ 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;
     },
   ],

BIN
src/assets/air-conditioner.png


+ 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 - 8
src/components/ThresholdDetail/ThresholdModal.js

@@ -3,7 +3,6 @@ import {
   Card,
   Col,
   Form,
-  Icon,
   Input,
   InputNumber,
   Modal,
@@ -29,13 +28,6 @@ export default function ThresholdModal(props) {
           size="small"
           style={{ marginBottom: 16 }}
           title={`阈值${index + 1}`}
-          extra={
-            <Icon
-              type="delete"
-              style={{ fontSize: 20, color: '#fff' }}
-              onClick={() => deleteThreshold(index, key)}
-            />
-          }
         >
           <Form.Item
             label="阈值类型"

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


+ 1 - 33
src/pages/EqSelfInspection/components/Detail.js

@@ -371,19 +371,9 @@ export function DeviceTable(props) {
         columns={columns}
         dataSource={items}
         rowKey="Id"
-        onRow={(data) => {
-          return {
-            onClick: () => {
-              handleClickItem(data);
-            },
-          };
-        }}
         locale={{
           emptyText: <Empty />,
         }}
-        rowClassName={(record) =>
-          `DeviceTable-${record.Id}` == select ? styles.select : null
-        }
       />
       <ThresholdModal
         open={visible}
@@ -447,21 +437,9 @@ function AalysisTable(props) {
         columns={columns}
         dataSource={FaultAnalysis}
         rowKey="device_code"
-        onRow={(data) => {
-          return {
-            onClick: () => {
-              onClickItem(`AalysisTable-${data.device_code}`, {
-                deviceCode: data.device_code,
-              });
-            }, // 点击行
-          };
-        }}
         locale={{
           emptyText: <Empty />,
         }}
-        rowClassName={(record) =>
-          `AalysisTable-${record.device_code}` == select ? styles.select : null
-        }
       />
     </div>
   );
@@ -704,24 +682,14 @@ export function WarningTable(props) {
 
   return (
     <div>
-      {/* <div className="table-total">当前列表总数 {Items?.length || 0}</div> */}
       <Table
         columns={columns}
         dataSource={items}
         rowKey="Id"
-        onRow={(data) => {
-          return {
-            onClick: () => {
-              handleClickItem(data);
-            },
-          };
-        }}
         locale={{
           emptyText: <Empty />,
         }}
-        rowClassName={(record) =>
-          `DeviceTable-${record.Id}` == select ? styles.select : null
-        }
+     
       />
       <ThresholdModal
         open={visible}

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

@@ -0,0 +1,55 @@
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import { queryConditionSnapshot } from '@/services/SmartOps';
+import { useParams, useRequest } from '@umijs/max';
+import styles from './index.less';
+
+const Work = (props) => {
+  const { projectId } = useParams();
+
+  const { data, loading } = useRequest(queryConditionSnapshot, {
+    defaultParams: [{ project_id: projectId }],
+    initialData: {},
+  });
+
+  return (
+    <PageContent>
+      <PageTitle>空调控制</PageTitle>
+      <div className={styles.desc}>当前空间&nbsp;&nbsp;温度:26 湿度:2.4%</div>
+      <div className={`card-box ${styles.top}`}>
+        <div className={styles.left}>
+          <img
+            className={styles.img}
+            src={require('@/assets/air-conditioner.png')}
+          />
+          <div>
+            <div className={styles.number}>23</div>
+            空调数量
+          </div>
+        </div>
+        <div className={styles.right}>
+          <div className={styles.row}>
+            <i className={styles.icon}></i> 在线:137
+          </div>
+          <div className={styles.row}>
+            <i className={`${styles.icon} ${styles.error}`}></i> 在线:137
+          </div>
+          <div className={styles.row}>
+            <i className={`${styles.icon} ${styles.offline}`}></i> 在线:137
+          </div>
+        </div>
+      </div>
+      <div className={`card-box ${styles.item}`}>
+        <i className={styles.icon}></i> 空调1
+      </div>
+      <div className={`card-box ${styles.item}`}>
+        <i className={styles.icon}></i> 空调2
+      </div>
+      <div className={`card-box ${styles.item}`}>
+        <i className={`${styles.icon} ${styles.error}`}></i> 空调3
+      </div>
+    </PageContent>
+  );
+};
+
+export default Work;

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

@@ -0,0 +1,54 @@
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import { queryConditionSnapshot } from '@/services/SmartOps';
+import { useParams, useRequest } from '@umijs/max';
+import styles from './index.less';
+
+const Work = (props) => {
+  const { projectId } = useParams();
+
+  const { data, loading } = useRequest(queryConditionSnapshot, {
+    defaultParams: [{ project_id: projectId }],
+    initialData: {},
+  });
+
+  return (
+    <PageContent>
+      <PageTitle>照明控制</PageTitle>
+      <div className={`card-box ${styles.top}`}>
+        <div className={styles.left}>
+          <img
+            className={styles.img}
+            src={require('@/assets/air-conditioner.png')}
+          />
+          <div>
+            <div className={styles.number}>23</div>
+            照明数量
+          </div>
+        </div>
+        <div className={styles.right}>
+          <div className={styles.row}>
+            <i className={styles.icon}></i> 在线:137
+          </div>
+          <div className={styles.row}>
+            <i className={`${styles.icon} ${styles.error}`}></i> 在线:137
+          </div>
+          <div className={styles.row}>
+            <i className={`${styles.icon} ${styles.offline}`}></i> 在线:137
+          </div>
+        </div>
+      </div>
+      <div className={`card-box ${styles.item}`}>
+        <i className={styles.icon}></i> 照明1
+      </div>
+      <div className={`card-box ${styles.item}`}>
+        <i className={styles.icon}></i> 照明2
+      </div>
+      <div className={`card-box ${styles.item}`}>
+        <i className={`${styles.icon} ${styles.error}`}></i> 照明3
+      </div>
+    </PageContent>
+  );
+};
+
+export default Work;

+ 57 - 0
src/pages/HardwareController/index.less

@@ -0,0 +1,57 @@
+.desc {
+  position: absolute;
+  top: 20px;
+  left: 50%;
+  transform: translateX(-50%);
+  font-size: 22px;
+}
+.top {
+  display: flex;
+  margin-top: 40px;
+  padding: 20px;
+  .left {
+    width: 50%;
+    font-size: 24px;
+    display: flex;
+    align-items: center;
+  }
+  .img {
+    width: 200px;
+    margin-right: 30px;
+  }
+  .number {
+    font-size: 48px;
+    font-weight: bold;
+    margin-bottom: 10px;
+    color: #4a90e2;
+  }
+  .right {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+  }
+  .row {
+    font-size: 24px;
+  }
+}
+.item {
+  padding: 20px;
+  font-size: 28px;
+  margin-top: 30px;
+  vertical-align: middle;
+}
+.icon {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  display: inline-block;
+  background-color: #12ceb3;
+  margin-right: 10px;
+  vertical-align: middle;
+  &.error {
+    background-color: #fe5850;
+  }
+  &.offline {
+    background-color: #9b9b9b;
+  }
+}

+ 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;
+    }
+  }
+}

+ 3 - 3
src/pages/Smart/components/SimulateDetail.js

@@ -12,8 +12,9 @@ import {
   queryMembraneList,
   queryProjectConfig,
 } from '@/services/SmartOps';
+import { AreaChartOutlined } from '@ant-design/icons';
 import { useRequest } from '@umijs/max';
-import { DatePicker, Icon, Modal, Select, Spin } from 'antd';
+import { DatePicker, Modal, Select, Spin } from 'antd';
 import dayjs from 'dayjs';
 import * as echarts from 'echarts';
 import { useEffect, useMemo, useRef, useState } from 'react';
@@ -249,8 +250,7 @@ const ChartContent = (props) => {
             ))}
           </div>
           {active == 1 && (
-            <Icon
-              type="area-chart"
+            <AreaChartOutlined
               style={{ float: 'right', lineHeight: '56px', marginRight: 20 }}
               onClick={() => setVisible(true)}
             />

+ 143 - 0
src/pages/TaskManage/Detail/TaskDetail.tsx

@@ -0,0 +1,143 @@
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import {
+  MandateChildType,
+  MandateDetailType,
+  PropsType,
+  UserType,
+  WorkOrderType,
+} from '@/pages/TaskManage/Detail/detail.types';
+import {
+  MandateClass,
+  MandateStatus,
+  MandateType,
+  OrderStatus,
+  OrderType,
+} from '@/pages/TaskManage/constent';
+import {
+  dispatchOrder,
+  getMandateChildList,
+  getMandateDetail,
+  ignoreTask,
+  setTaskAutomation,
+} from '@/services/TaskManage';
+import { connect, useLocation, useRequest } from '@umijs/max';
+import React, { useEffect, useState } from 'react';
+
+const TaskDetail: React.FC<PropsType> = (props) => {
+  const { userList, dispatch } = props;
+  const md5 = require('md5');
+  const location = useLocation();
+  const queryParams = new URLSearchParams(location.search);
+  const project_id = Number(queryParams.get('project_id'));
+  const mandate_id = Number(queryParams.get('mandate_id'));
+
+  const [mandateDetail, setMandateDetail] = useState<MandateDetailType>();
+  const [handledWorkOrders, setHandledWorkOrders] = useState<WorkOrderType[]>(
+    [],
+  );
+  const [subMandateList, setSubMandateList] = useState<MandateChildType[]>([]);
+
+  const { run: getDetail, refresh: refreshDetail } = useRequest(
+    getMandateDetail,
+    {
+      defaultParams: [
+        {
+          project_id,
+          mandate_id,
+        },
+      ],
+      formatResult: (result) => {
+        const mandate: MandateDetailType = {
+          ...result.data,
+          Status: MandateStatus.find((item) => item.value === result.data.Status),
+          MandateClass: MandateClass.find(
+            (item) => item.value === result.data.MandateClass,
+          ),
+          MandateType: MandateType.find(
+            (item) => item.value === result.data.MandateType,
+          ),
+        };
+        const workOrderList: WorkOrderType[] = result.data.Records.map(
+          (record: WorkOrderType) => {
+            return {
+              ...record,
+              Status: OrderStatus.find(
+                (item) => item.value === record.Status,
+              ),
+              RecordType: OrderType.find(
+                (item) => item.value === record.RecordType,
+              ),
+              Responsible: userList.find(
+                (item: UserType) => item.ID === record.Responsible,
+              ),
+            };
+          },
+        );
+        setMandateDetail(mandate);
+        setHandledWorkOrders(workOrderList);
+      },
+    },
+  );
+
+  const {
+    run: getMandateChild,
+    loading,
+    refresh: refreshMandateChild,
+  } = useRequest(getMandateChildList, {
+    defaultParams: [
+      {
+        mandateId: mandate_id,
+      },
+    ],
+    formatResult: (result) => {
+      setSubMandateList(result.data);
+    },
+  });
+
+  const { run: setAuto } = useRequest(setTaskAutomation, {
+    manual: true,
+    onSuccess: () => {
+      refreshDetail();
+      refreshMandateChild();
+    },
+  });
+
+  const { run: runIgnoreTask } = useRequest(ignoreTask, {
+    manual: true,
+    onSuccess: () => {
+      refreshDetail();
+      refreshMandateChild();
+    },
+  });
+
+  const { run: runDispatch } = useRequest(dispatchOrder, {
+    manual: true,
+    onSuccess: () => {
+      refreshDetail();
+      refreshMandateChild();
+    },
+  });
+
+  useEffect(() => {
+    if (userList.length === 0) {
+      dispatch({
+        type: 'taskUser/fetchUserList',
+        payload: { project_id },
+      });
+    }
+  }, []);
+
+  return (
+    <PageContent>
+      <PageTitle returnable>任务详情</PageTitle>
+
+    </PageContent>
+  );
+};
+
+export default connect(({ taskUser }: any): { userList: UserType[] } => {
+  return {
+    userList: taskUser.userList,
+  };
+})(TaskDetail);

+ 56 - 0
src/pages/TaskManage/Detail/detail.types.ts

@@ -0,0 +1,56 @@
+import User from "@/pages/TaskManage/models/user";
+
+export interface PropsType{
+  userList: UserType[],
+  dispatch: (args:{type:string, payload: object})=>{}
+}
+
+export interface MandateChildType {
+  Content: string;
+  CreateTime: string;
+  ExtendId: number;
+  Id: number;
+  MandateClass: number;
+  MandateId: number;
+  ProjectId: number;
+  Status: number;
+  Title: string;
+}
+
+export interface WorkOrderType {
+  ActualDoneTime: string;
+  CreateTime: string;
+  Detail: string;
+  Id: number;
+  Note: string;
+  PlanDoneTime: string;
+  RecordType: number;
+  Responsible: number;
+  Status: number;
+}
+
+export interface MandateDetailType {
+  Id: number;
+  ProjectId: number;
+  MandateType: number;
+  MandateClass: number;
+  ResponsiblePeople: number;
+  Detail: string;
+  MandateChild: MandateChildType[];
+  Records: WorkOrderType[];
+  Summary: string;
+  CreateTime: string;
+  Status: number;
+  Note: string;
+  OperationLog: string;
+  OperationPeople: number;
+  ExtendId: number;
+}
+
+export interface UserType {
+  CName: string;
+  ID: number;
+  RoleID: string;
+  RoleName: string;
+  UserName: string;
+}

+ 126 - 0
src/pages/TaskManage/constent.ts

@@ -0,0 +1,126 @@
+export const MandateClass = [
+  {
+    value: 1,
+    label: '生产调度类',
+  },
+  {
+    value: 2,
+    label: '成本节约类',
+  },
+  {
+    value: 3,
+    label: '设备自检',
+  },
+  {
+    value: 4,
+    label: '工艺自检',
+  },
+  {
+    value: 5,
+    label: '电气检测',
+  },
+  {
+    value: 6,
+    label: '环境检测',
+  },
+  {
+    value: 7,
+    label: '安防检测',
+  },
+  {
+    value: 8,
+    label: '密闭空间检测',
+  },
+  {
+    value: 9,
+    label: '设备保养',
+  },
+  {
+    value: 10,
+    label: '设备维修',
+  },
+  {
+    value: 11,
+    label: '故障上报',
+  },
+  {
+    value: 12,
+    label: '工艺数据',
+  },
+  {
+    value: 13,
+    label: '设备巡检',
+  },
+  {
+    value: 14,
+    label: '数据超限',
+  },
+];
+
+export const MandateType = [
+  {
+    value: 1,
+    label: '工况任务',
+  },
+  {
+    value: 2,
+    label: '系统自检',
+  },
+  {
+    value: 3,
+    label: '生产任务',
+  },
+];
+
+export const MandateStatus = [
+  {
+    value: 0,
+    label: '未处理',
+  },
+  {
+    value: 2,
+    label: '已完成',
+  },
+  {
+    value: 4,
+    label: '已忽略',
+  },
+  {
+    value: 5,
+    label: '已派遣',
+  },
+];
+
+export const OrderType = [
+  {
+    value: 1,
+    label: '工艺工单',
+  },
+  {
+    value: 2,
+    label: '维修工单',
+  },
+  {
+    value: 3,
+    label: '保养工单',
+  },
+];
+
+export const OrderStatus = [
+  {
+    value: 0,
+    label: '处理中',
+  },
+  {
+    value: 1,
+    label: '已提交',
+  },
+  {
+    value: 2,
+    label: '已完成',
+  },
+  {
+    value: 3,
+    label: '已拒绝',
+  },
+];

+ 2 - 26
src/pages/TaskManage/index.less

@@ -1,36 +1,12 @@
-.backgroundImage {
-}
-
-.container {
-  margin: 0;
-  padding: 0;
-  height: 100vh;
-  display: flex;
-  flex-direction: column;
-  background-color: rgba(255, 255, 255, 0.4);
-  backdrop-filter: blur(20px);
-}
-
-.title {
-  padding: 10px;
-  font-size: 20px;
-  font-weight: 500;
-
-  .delimiter {
-    margin: 0 10px 0 10px;
-    border-left: 5px solid #468ee1;
-  }
-}
-
 .taskList {
-  height: calc(100vh - 50px);
+  height: calc(100vh - 70px);
   overflow-y: scroll;
   border: none;
 
   .listItem {
     margin: 20px 10px;
     height: 10vh;
-    box-shadow: 0px 0px 6px 3px rgba(0, 150, 255, 0.1);
+    box-shadow: 0 0 6px 3px rgba(0, 150, 255, 10%);
 
     .itemCount {
       display: flex;

+ 89 - 27
src/pages/TaskManage/index.tsx

@@ -1,21 +1,71 @@
+import PageContent from '@/components/PageContent';
+import PageTitle from '@/components/PageTitle';
+import {
+  MandateClass,
+  MandateStatus,
+  MandateType,
+} from '@/pages/TaskManage/constent';
 import styles from '@/pages/TaskManage/index.less';
-import { PropTypes } from '@/pages/TaskManage/index.types';
-import { List } from 'antd';
-import React, { useEffect } from 'react';
+import { PropTypes, mandateType } from '@/pages/TaskManage/index.types';
+import { getMandateList } from '@/services/TaskManage';
+import { RightOutlined } from '@ant-design/icons';
+import { useParams, useRequest } from '@umijs/max';
+import { List, Spin } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'umi';
 
 const TaskManage: React.FC<PropTypes> = (props) => {
-  const { projectID } = props;
+  const { projectID } = useParams();
+  console.log(projectID);
   const project_id = Number(projectID === '' ? '0' : projectID);
-  const arr = [];
-  for (let i = 0; i < 20; i++) {
-    arr.push({
-      title: '任务标题',
-      desc: '任务详情任务详情任务详情任务详情任务详情任务详情任务详情任务详情任务详情',
-    });
-  }
 
-  const goToDetail = (item: number) => {
-    console.log(item);
+  const navigate = useNavigate();
+
+  const [pagination, setPagination] = useState({
+    total: 0,
+    pageSize: 5,
+    current: 1,
+  });
+  const [mandateList, setMandateList] = useState<mandateType[]>([]);
+
+  const {
+    run: runGetMandateList,
+    loading,
+    refresh: refreshMandateList,
+  } = useRequest(getMandateList, {
+    defaultParams: [
+      {
+        project_id,
+        pageSize: pagination.pageSize,
+        currentPage: pagination.current,
+      },
+    ],
+    onSuccess: () => {},
+    formatResult: (result) => {
+      const mandates = result.data.list;
+      mandates.forEach((mandate: mandateType) => {
+        mandate.Status =
+          MandateStatus.find((status) => status.value === mandate.Status) || 0;
+        mandate.MandateClass =
+          MandateClass.find(
+            (classItem) => classItem.value === mandate.MandateClass,
+          ) || 0;
+        mandate.MandateType =
+          MandateType.find((type) => type.value === mandate.MandateType) || 0;
+      });
+      setMandateList(result.data.list);
+      setPagination(result.data.pagination);
+    },
+  });
+
+  const goToDetail = (item: mandateType) => {
+    navigate(
+      `/task-manage/detail?project_id=${project_id}&mandate_id=${item.Id}`,
+    );
+  };
+
+  const onPageChange = (page: number, pageSize: number) => {
+    runGetMandateList({ project_id, currentPage: page, pageSize });
   };
 
   useEffect(() => {
@@ -26,7 +76,7 @@ const TaskManage: React.FC<PropTypes> = (props) => {
     };
   }, []);
 
-  const makeList = (item: any, index: number) => {
+  const makeList = (item: mandateType, index: number) => {
     return (
       <List.Item
         className={styles.listItem}
@@ -34,34 +84,46 @@ const TaskManage: React.FC<PropTypes> = (props) => {
           goToDetail(item);
         }}
       >
-        <List.Item.Meta title={item.title} description={item.desc} />
+        <List.Item.Meta
+          title={
+            typeof item.MandateType === 'number' ? '' : item.MandateType.label
+          }
+          description={item.Detail}
+        />
         <div className={styles.itemCount}>
           <div className={styles.countNumber}>10</div>
           <div>任务数量</div>
         </div>
-        <div style={{ fontSize: '48px', color: 'gray', fontWeight: '200' }}>
-          {'>'}
-        </div>
+        <RightOutlined />
       </List.Item>
     );
   };
 
   return (
-    <div className={styles.backgroundImage}>
-      <div className={styles.container}>
-        <div className={styles.title}>
-          <span className={styles.delimiter}></span>
-          任务管理
-        </div>
+    <PageContent>
+      <PageTitle>任务管理</PageTitle>
+      <Spin spinning={loading}>
         <List
           className={styles.taskList}
           bordered
           itemLayout="horizontal"
-          dataSource={arr}
+          dataSource={mandateList}
           renderItem={makeList}
+          pagination={
+            mandateList.length
+              ? {
+                  position: 'bottom',
+                  align: 'end',
+                  simple: true,
+                  showSizeChanger: false,
+                  onChange: onPageChange,
+                  ...pagination,
+                }
+              : false
+          }
         />
-      </div>
-    </div>
+      </Spin>
+    </PageContent>
   );
 };
 

+ 23 - 2
src/pages/TaskManage/index.types.ts

@@ -1,3 +1,24 @@
 export interface PropTypes {
-  projectID: string
-}
+  projectID: string;
+}
+
+export interface selectOption {
+  value: number;
+  label: string;
+}
+
+export interface mandateType {
+  Id: number;
+  ProjectId: number;
+  MandateType: number | selectOption;
+  MandateClass: number | selectOption;
+  ResponsiblePeople: number;
+  Detail: string;
+  Summary: string;
+  CreateTime: string;
+  Status: number | selectOption;
+  Note: string;
+  OperationLog: string;
+  OperationPeople: number;
+  ExtendId: string;
+}

+ 28 - 0
src/pages/TaskManage/models/user.js

@@ -0,0 +1,28 @@
+import { queryUserList } from "../../../services/user";
+
+export default {
+    namespace: 'taskUser',
+  state: {
+    userList: [],
+  },
+  effects: {
+    *fetchUserList({ payload }, { call, put }) {
+      const response = yield call(queryUserList, payload);
+      if (response) {
+        yield put({
+          type: 'userListHandler',
+          payload: response.data,
+        });
+      }
+    },
+  },
+
+  reducers:{
+    userListHandler(state, { payload }) {
+      return {
+        ...state,
+        userList: payload,
+      };
+    },
+  }
+};

+ 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) {

+ 79 - 0
src/services/TaskManage.js

@@ -0,0 +1,79 @@
+import {request} from "umi";
+import { stringify } from 'qs';
+
+const baseURL='/api'
+
+/**
+ * 获取任务列表
+ * @param data.project_id
+ * @returns {Promise<*>}
+ */
+export async function getMandateList(data) {
+  return request(`${baseURL}/v1/mandate/list?${stringify(data)}`);
+}
+
+/**
+ * 获取子任务列表
+ * @param params.project_id
+ * @param params.mandate_id
+ * @returns {Promise<*>}
+ */
+export async function getMandateChildList(params) {
+  return request(`${baseURL}/v1/mandate-child/list?${stringify(params)}`);
+}
+
+/**
+ * 获取任务详情
+ * @param params.mandate_idz
+ * @param params.project_id
+ * @returns {Promise<*>}
+ */
+export async function getMandateDetail(params) {
+  return request(`${baseURL}/v1/mandate/info?${stringify(params)}`);
+}
+
+/**
+ * 设置任务为自动
+ * @param params.mandate_id 任务id
+ * @param params.pw MD5加密的口令
+ * @returns {Promise<*>}
+ */
+export async function setTaskAutomation(params) {
+  return request(`${baseURL}/v1/mandate/automation`, {
+    method: 'POST',
+    body: params
+  });
+}
+
+/**
+ * 忽略任务
+ * @param params.Id 任务id
+ * @param params.Status 状态
+ * @param params.note 备注
+ * @returns {Promise<*>}
+ */
+export async function ignoreTask(params) {
+  const encodeParams = new URLSearchParams(params).toString()
+  return request(`${baseURL}/v1/mandate/edit`, {
+    method: 'POST',
+    headers: { ContentType: 'application/x-www-form-urlencoded' },
+    body: encodeParams
+  });
+}
+
+/**
+ * 派单
+ * @param params.m_id *主任务id
+ * @param params.mc_id *子任务id,多个以逗号分隔
+ * @param params.operator_id *操作员id
+ * @param params.plan_end_time *计划完成时间 YYYY:MM:DD HH:mm:ss
+ * @param params.type *工单类型:1:工艺工单,2:维修工单,3:保养工单
+ * @param params.note 备注
+ * @returns {Promise<*>}
+ */
+export async function dispatchOrder(params) {
+  return request(`${baseURL}/v1/work_order/save`, {
+    method: 'POST',
+    body: params
+  });
+}

+ 5 - 0
src/services/user.js

@@ -0,0 +1,5 @@
+import { request } from 'umi';
+
+export async function queryUserList(param) {
+  return request(`/api/v1/user/project/${param.project_id}`);
+}

+ 24 - 0
yarn.lock

@@ -3579,6 +3579,11 @@ chalk@^4.0.0, chalk@^4.1.2:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
+charenc@0.0.2:
+  version "0.0.2"
+  resolved "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
+  integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
+
 chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.3:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -3872,6 +3877,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+crypt@0.0.2:
+  version "0.0.2"
+  resolved "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
+  integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
+
 crypto-browserify@^3.11.0:
   version "3.12.0"
   resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -5526,6 +5536,11 @@ is-boolean-object@^1.1.0, is-boolean-object@^1.1.2:
     call-bind "^1.0.2"
     has-tostringtag "^1.0.0"
 
+is-buffer@~1.1.6:
+  version "1.1.6"
+  resolved "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+  integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+
 is-callable@^1.0.4, is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4, is-callable@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
@@ -6238,6 +6253,15 @@ md5.js@^1.3.4:
     inherits "^2.0.1"
     safe-buffer "^5.1.2"
 
+md5@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.npmmirror.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
+  integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
+  dependencies:
+    charenc "0.0.2"
+    crypt "0.0.2"
+    is-buffer "~1.1.6"
+
 mdn-data@2.0.14:
   version "2.0.14"
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"