wzyyy před 3 roky
rodič
revize
02aae5b204
54 změnil soubory, kde provedl 1816 přidání a 201 odebrání
  1. binární
      public/images/media/dashboard-1.png
  2. binární
      public/images/media/dashboard-2.png
  3. binární
      public/images/media/dashboard-3.png
  4. binární
      public/images/media/dashboard-4.png
  5. 6 2
      src/components/DashBoard/baseCard.tsx
  6. 3 3
      src/components/DashBoard/echarts.tsx
  7. 83 1
      src/components/DashBoard/index.less
  8. 1 0
      src/components/DashBoard/index.tsx
  9. 4 4
      src/components/DashBoard/timePicker.tsx
  10. 72 2
      src/components/DashBoard/topCard.tsx
  11. 6 2
      src/components/ProTableCard/CardItems/noticeTemplate.tsx
  12. 16 13
      src/components/SearchComponent/index.tsx
  13. 29 0
      src/pages/Northbound/DuerOS/index.tsx
  14. 7 1
      src/pages/account/NotificationSubscription/save/index.tsx
  15. 0 2
      src/pages/device/Instance/Detail/MetadataLog/Property/index.tsx
  16. 1 1
      src/pages/device/Instance/Detail/Running/Property/PropertyCard.tsx
  17. 2 0
      src/pages/device/Instance/Detail/Running/Property/index.tsx
  18. 4 0
      src/pages/device/Product/Save/index.tsx
  19. 10 1
      src/pages/device/Product/index.tsx
  20. 36 0
      src/pages/home/components/Guide.tsx
  21. 29 0
      src/pages/home/components/Statistics.tsx
  22. 68 0
      src/pages/home/components/Steps.tsx
  23. 4 0
      src/pages/home/comprehensive/index.tsx
  24. 100 0
      src/pages/home/device/index.tsx
  25. 34 0
      src/pages/home/index.tsx
  26. 47 0
      src/pages/home/init/index.tsx
  27. 4 0
      src/pages/home/ops/index.tsx
  28. 17 0
      src/pages/home/service.ts
  29. 8 1
      src/pages/link/Certificate/index.tsx
  30. 30 0
      src/pages/link/DashBoard/index.less
  31. 351 38
      src/pages/link/DashBoard/index.tsx
  32. 3 3
      src/pages/media/Cascade/Channel/index.tsx
  33. 168 90
      src/pages/media/DashBoard/index.tsx
  34. 11 3
      src/pages/media/DashBoard/service.ts
  35. 10 1
      src/pages/media/Device/index.tsx
  36. 154 0
      src/pages/media/Home/deviceModal.tsx
  37. 38 0
      src/pages/media/Home/index.less
  38. 150 0
      src/pages/media/Home/index.tsx
  39. 11 0
      src/pages/media/Home/service.tsx
  40. 17 0
      src/pages/media/Home/typings.d.ts
  41. 50 0
      src/pages/notice/Template/index.tsx
  42. 4 4
      src/pages/rule-engine/Alarm/Config/index.tsx
  43. 27 10
      src/pages/rule-engine/Alarm/Configuration/Save/index.tsx
  44. 2 1
      src/pages/rule-engine/Alarm/Configuration/service.ts
  45. 135 3
      src/pages/rule-engine/DashBoard/index.tsx
  46. 24 0
      src/pages/rule-engine/DashBoard/service.ts
  47. 1 1
      src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx
  48. 1 1
      src/pages/rule-engine/Scene/TriggerTerm/index.tsx
  49. 1 1
      src/pages/system/DataSource/index.tsx
  50. 17 6
      src/pages/system/Platforms/Api/base.tsx
  51. 6 2
      src/pages/system/Platforms/Api/basePage.tsx
  52. 11 0
      src/pages/system/Platforms/Setting/index.tsx
  53. 1 4
      src/pages/system/Role/Detail/UserManage/index.tsx
  54. 2 0
      src/utils/menu/router.ts

binární
public/images/media/dashboard-1.png


binární
public/images/media/dashboard-2.png


binární
public/images/media/dashboard-3.png


binární
public/images/media/dashboard-4.png


+ 6 - 2
src/components/DashBoard/baseCard.tsx

@@ -4,11 +4,12 @@ import type { EchartsProps } from './echarts';
 import Echarts from './echarts';
 import Style from './index.less';
 import classNames from 'classnames';
-import { forwardRef } from 'react';
+import React, { forwardRef } from 'react';
 
 interface BaseCardProps extends HeaderProps, EchartsProps {
   height: number;
   className?: string;
+  echartsAfter?: React.ReactNode;
 }
 
 export default forwardRef((props: BaseCardProps, ref) => {
@@ -22,7 +23,10 @@ export default forwardRef((props: BaseCardProps, ref) => {
       }}
     >
       <Header ref={ref} {...formProps} />
-      <Echarts options={options} className={Style['echarts']} />
+      <div className={Style['echarts-content']}>
+        <Echarts options={options} className={Style['echarts']} />
+        {props.echartsAfter}
+      </div>
     </div>
   );
 });

+ 3 - 3
src/components/DashBoard/echarts.tsx

@@ -1,4 +1,4 @@
-import { useEffect, useRef } from 'react';
+import { useCallback, useEffect, useRef } from 'react';
 import * as echarts from 'echarts/core';
 import type { ECharts, EChartsOption } from 'echarts';
 import {
@@ -72,11 +72,11 @@ export default (props: EchartsProps) => {
     }
   };
 
-  const updateOptions = () => {
+  const updateOptions = useCallback(() => {
     if (chartsRef.current && props.options) {
       chartsRef.current.setOption(props.options);
     }
-  };
+  }, [props.options]);
 
   useEffect(() => {
     (window as Window).addEventListener('resize', updateSize);

+ 83 - 1
src/components/DashBoard/index.less

@@ -26,8 +26,90 @@
   height: 100%;
 }
 
-.echarts {
+.echarts-content {
+  display: flex;
   flex-grow: 1;
   height: 0;
   margin-top: 12px;
 }
+
+.echarts {
+  flex: 1;
+  height: 100%;
+}
+
+.dash-board-top {
+  margin-bottom: 24px;
+
+  .dash-board-top-item {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    padding: 24px;
+    background-color: #fff;
+    border: 1px solid #e0e4e8;
+    border-radius: 2px;
+
+    .top-item-content {
+      display: flex;
+      flex-direction: column;
+      flex-grow: 1;
+
+      .content-left {
+        width: 50%;
+
+        .content-left-title {
+          color: rgba(0, 0, 0, 0.64);
+        }
+
+        .content-left-value {
+          padding: 12px 0;
+          color: #323130;
+          font-weight: bold;
+          font-size: 36px;
+        }
+      }
+
+      .content-right {
+        display: flex;
+        flex-grow: 1;
+        align-items: flex-end;
+        justify-content: flex-end;
+        width: 100%;
+        height: 0;
+
+        > * {
+          max-width: 100%;
+          max-height: 100%;
+        }
+      }
+
+      &.show-value {
+        flex-direction: row;
+
+        .content-left {
+          height: 100%;
+        }
+
+        .content-right {
+          width: 0;
+          height: 100%;
+        }
+      }
+    }
+  }
+
+  .top-item-footer {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-top: 16px;
+    border-top: 1px solid #f0f0f0;
+
+    .footer-item-value {
+      color: #323130;
+      font-weight: bold;
+      font-size: 16px;
+    }
+  }
+}

+ 1 - 0
src/components/DashBoard/index.tsx

@@ -2,5 +2,6 @@ import BaseCard from './baseCard';
 
 export { default as DashBoardEcharts } from './echarts';
 export { default as DashBoardHeader } from './header';
+export { default as DashBoardTopCard } from './topCard';
 
 export default BaseCard;

+ 4 - 4
src/components/DashBoard/timePicker.tsx

@@ -23,11 +23,11 @@ interface ExtraTimePickerProps extends Omit<DatePickerProps, 'onChange' | 'value
 export const getTimeByType = (type: TimeType) => {
   switch (type) {
     case TimeKey.week:
-      return moment().subtract(6, 'days').valueOf();
+      return moment().subtract(6, 'days').startOf('day').valueOf();
     case TimeKey.month:
-      return moment().subtract(29, 'days').valueOf();
+      return moment().subtract(29, 'days').startOf('day').valueOf();
     case TimeKey.year:
-      return moment().subtract(365, 'days').valueOf();
+      return moment().subtract(365, 'days').startOf('day').valueOf();
     default:
       return moment().startOf('day').valueOf();
   }
@@ -49,7 +49,7 @@ export default (props: ExtraTimePickerProps) => {
   };
 
   const timeChange = (type: TimeType) => {
-    const endTime = moment(new Date()).valueOf();
+    const endTime = moment(new Date()).endOf('day').valueOf();
     const startTime: number = getTimeByType(type);
     setRadioValue(type);
     change(startTime, endTime, type);

+ 72 - 2
src/components/DashBoard/topCard.tsx

@@ -1,3 +1,73 @@
-export default () => {
-  return <div></div>;
+import React from 'react';
+import { Badge, Col, Row } from 'antd';
+import classNames from 'classnames';
+import './index.less';
+
+interface TopCardProps {
+  children: React.ReactNode;
+  className?: string;
+  style?: React.CSSProperties;
+}
+
+interface FooterItem {
+  status?: 'error' | 'success' | 'warning';
+  title: string;
+  value: string | number;
+}
+
+interface CardItemProps {
+  span: number;
+  title: string;
+  value: any;
+  footer: false | FooterItem[];
+  showValue?: boolean;
+  children: React.ReactNode;
+}
+
+const CardItem = (props: CardItemProps) => {
+  return (
+    <Col span={props.span}>
+      <div className={'dash-board-top-item'}>
+        <div
+          className={classNames('top-item-content', { 'show-value': props.showValue !== false })}
+        >
+          <div className={'content-left'}>
+            <div className={'content-left-title'}>{props.title}</div>
+            {props.showValue !== false && <div className={'content-left-value'}>{props.value}</div>}
+          </div>
+          <div className={'content-right'}>{props.children}</div>
+        </div>
+        {props.footer !== false && props.footer.length ? (
+          <div className={'top-item-footer'}>
+            {props.footer.map((item) => {
+              return (
+                <>
+                  <div>
+                    {item.status ? (
+                      <Badge status={item.status} text={item.title} />
+                    ) : (
+                      <span>{item.title}</span>
+                    )}
+                  </div>
+                  <div className={'footer-item-value'}>{item.value}</div>
+                </>
+              );
+            })}
+          </div>
+        ) : null}
+      </div>
+    </Col>
+  );
 };
+
+const TopCard = (props: TopCardProps) => {
+  return (
+    <div className={classNames('dash-board-top', props.className)} style={props.style}>
+      <Row gutter={24}>{props.children}</Row>
+    </div>
+  );
+};
+
+TopCard.Item = CardItem;
+
+export default TopCard;

+ 6 - 2
src/components/ProTableCard/CardItems/noticeTemplate.tsx

@@ -66,7 +66,9 @@ export default (props: NoticeCardProps) => {
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
             <span className={'card-item-header-name ellipsis'}>
-              <Tooltip title={props.name}>{props.name}</Tooltip>
+              <Tooltip placement="topLeft" title={props.name}>
+                {props.name}
+              </Tooltip>
             </span>
           </div>
           <div className={'card-item-content'}>
@@ -77,7 +79,9 @@ export default (props: NoticeCardProps) => {
             <div>
               <label>说明</label>
               <div className={'ellipsis'}>
-                <Tooltip title={props.description}>{props.description}</Tooltip>
+                <Tooltip placement="topLeft" title={props.description}>
+                  {props.description}
+                </Tooltip>
               </div>
             </div>
           </div>

+ 16 - 13
src/components/SearchComponent/index.tsx

@@ -64,7 +64,11 @@ interface Props<T> {
    *         ]}
    * */
   defaultParam?: SearchTermsServer | Term[];
-  // pattern?: 'simple' | 'advance';
+  /**
+   * @name "搜索组件模式"
+   * simple 限制只支持一组搜索条件,用于小弹窗搜索时使用
+   */
+  model?: 'simple' | 'advance';
   enableSave?: boolean;
   initParam?: SearchTermsServer;
 }
@@ -138,7 +142,7 @@ const sortField = (field: ProColumns[]) => {
 // 默认六组搜索条件。根据字段index排序
 
 const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
-  const { field, target, onSearch, defaultParam, enableSave = true, initParam } = props;
+  const { field, target, onSearch, defaultParam, enableSave = true, initParam, model } = props;
 
   /**
    * 过滤不参与搜索的数据 ?
@@ -187,7 +191,6 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
         effects() {
           onFieldReact('*.*.column', async (typeFiled, f) => {
             const _column = (typeFiled as Field).value;
-
             const _field = field.find((item) => item.dataIndex === _column);
             if (_column === 'id') {
               f.setFieldState(typeFiled.query('.termType'), async (state) => {
@@ -210,9 +213,7 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
                   });
                   f.setFieldState(typeFiled.query('.value'), async (state) => {
                     state.componentType = 'Select';
-                    // state.loading = true;
                     state.dataSource = __option;
-                    // state.loading = false;
                   });
                   break;
                 case 'treeSelect':
@@ -269,7 +270,7 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
           });
         },
       }),
-    [target],
+    [target, expand],
   );
 
   const historyForm = createForm();
@@ -303,7 +304,7 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
           'x-decorator': 'FormItem',
           'x-component': 'GroupNameControl',
           'x-decorator-props': {
-            gridSpan: 2,
+            gridSpan: 3,
           },
           default: 'or',
           'x-component-props': {
@@ -633,12 +634,14 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
                 重置
               </Button>
             </Space>
-            <div className={classnames(styles.more, !expand ? styles.simple : styles.advance)}>
-              <Button type="link" onClick={handleExpand}>
-                更多筛选
-                <DoubleRightOutlined style={{ marginLeft: 32 }} rotate={expand ? 90 : -90} />
-              </Button>
-            </div>
+            {model !== 'simple' && (
+              <div className={classnames(styles.more, !expand ? styles.simple : styles.advance)}>
+                <Button type="link" onClick={handleExpand}>
+                  更多筛选
+                  <DoubleRightOutlined style={{ marginLeft: 32 }} rotate={expand ? 90 : -90} />
+                </Button>
+              </div>
+            )}
           </div>
         </div>
       </Form>

+ 29 - 0
src/pages/Northbound/DuerOS/index.tsx

@@ -141,6 +141,15 @@ export default () => {
         defaultMessage: '产品',
       }),
       dataIndex: 'productName',
+      hideInSearch: true,
+      valueType: 'select',
+      request: async () => {
+        const res = await service.getProduct();
+        if (res.status === 200) {
+          return res.result.map((pItem: any) => ({ label: pItem.name, value: pItem.id }));
+        }
+        return [];
+      },
     },
     {
       title: intl.formatMessage({
@@ -149,10 +158,19 @@ export default () => {
       }),
       dataIndex: 'applianceType',
       renderText: (data) => data.text,
+      valueType: 'select',
+      request: async () => {
+        const res = await service.getTypes();
+        if (res.status === 200) {
+          return res.result.map((pItem: any) => ({ label: pItem.name, value: pItem.id }));
+        }
+        return [];
+      },
     },
     {
       title: '说明',
       dataIndex: 'description',
+      hideInSearch: true,
     },
     {
       title: '状态',
@@ -164,6 +182,17 @@ export default () => {
         };
         return map[data.value];
       },
+      valueType: 'select',
+      valueEnum: {
+        disabled: {
+          text: '禁用',
+          status: 'disabled',
+        },
+        enabled: {
+          text: '正常',
+          status: 'enabled',
+        },
+      },
     },
     {
       title: intl.formatMessage({

+ 7 - 1
src/pages/account/NotificationSubscription/save/index.tsx

@@ -185,7 +185,13 @@ const Save = (props: Props) => {
   };
 
   return (
-    <Modal title={'详情'} visible onCancel={props.close} onOk={() => handleSave()} width={'45vw'}>
+    <Modal
+      title={props.data.id ? '编辑' : '新增'}
+      visible
+      onCancel={props.close}
+      onOk={() => handleSave()}
+      width={'45vw'}
+    >
       <Form form={form} layout="vertical">
         <SchemaField
           schema={schema}

+ 0 - 2
src/pages/device/Instance/Detail/MetadataLog/Property/index.tsx

@@ -223,8 +223,6 @@ const PropertyLog = (props: Props) => {
   useEffect(() => {
     setRadioValue('today');
     setTab('table');
-    setStart(moment().startOf('day').valueOf());
-    setEnd(new Date().getTime());
   }, []);
 
   const renderComponent = (type: string) => {

+ 1 - 1
src/pages/device/Instance/Detail/Running/Property/PropertyCard.tsx

@@ -49,7 +49,7 @@ const Property = (props: Props) => {
           <Tooltip title={title}>{title}</Tooltip>
         </div>
         <Space style={{ fontSize: 12 }}>
-          {(data.expands?.readOnly === false || data.expands?.readOnly === 'false') && (
+          {data.expands?.type.includes('write') && (
             <Tooltip placement="top" title="设置属性至设备">
               <EditOutlined
                 onClick={() => {

+ 2 - 0
src/pages/device/Instance/Detail/Running/Property/index.tsx

@@ -196,6 +196,8 @@ const Property = (props: Props) => {
     if (dataSource.data.length > 0) {
       getDashboard();
       subscribeProperty();
+    } else {
+      setLoading(false);
     }
   }, [dataSource]);
 

+ 4 - 0
src/pages/device/Product/Save/index.tsx

@@ -87,6 +87,10 @@ const Save = (props: Props) => {
         }
         props.close();
         form.resetFields();
+        if ((window as any).onTabSaveSuccess) {
+          (window as any).onTabSaveSuccess(res);
+          setTimeout(() => window.close(), 300);
+        }
       }
     }
   };

+ 10 - 1
src/pages/device/Product/index.tsx

@@ -12,7 +12,7 @@ import {
 import Service from '@/pages/device/Product/service';
 import { observer } from '@formily/react';
 import { model } from '@formily/reactive';
-import { useHistory } from 'umi';
+import { useHistory, useLocation } from 'umi';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { useEffect, useRef, useState } from 'react';
@@ -78,6 +78,15 @@ const Product = observer(() => {
     ),
   };
 
+  const location = useLocation();
+
+  useEffect(() => {
+    if ((location as any).query?.save === 'true') {
+      setCurrent(undefined);
+      setVisible(true);
+    }
+  }, []);
+
   const deleteItem = async (id: string) => {
     const response: any = await service.remove(id);
     if (response.status === 200) {

+ 36 - 0
src/pages/home/components/Guide.tsx

@@ -0,0 +1,36 @@
+import { Card, Col, Row } from 'antd';
+
+interface Props {
+  title: string;
+  data: any[];
+  jump?: (auth: boolean, url: string, param: string) => void;
+}
+
+const Guide = (props: Props) => {
+  const { title, data, jump } = props;
+  return (
+    <Card>
+      <div style={{ marginBottom: 15 }}>
+        <h3>{title}</h3>
+      </div>
+      <Row gutter={24}>
+        {data.map((item) => (
+          <Col key={item.key} span={8}>
+            <Card
+              bordered
+              onClick={() => {
+                if (jump) {
+                  jump(item.auth, item.url, item.param);
+                }
+              }}
+            >
+              {item.name}
+            </Card>
+          </Col>
+        ))}
+      </Row>
+    </Card>
+  );
+};
+
+export default Guide;

+ 29 - 0
src/pages/home/components/Statistics.tsx

@@ -0,0 +1,29 @@
+import { Card, Col, Row } from 'antd';
+
+const Statistics = () => {
+  return (
+    <Card
+      title={'设备统计'}
+      extra={
+        <a
+          onClick={() => {
+            // pageJump(!!getMenuPathByCode('device/DashBoard'), 'device/DashBoard');
+          }}
+        >
+          详情
+        </a>
+      }
+    >
+      <Row gutter={24}>
+        <Col span={12}>
+          <Card bordered>产品数量</Card>
+        </Col>
+        <Col span={12}>
+          <Card bordered>设备数量</Card>
+        </Col>
+      </Row>
+    </Card>
+  );
+};
+
+export default Statistics;

+ 68 - 0
src/pages/home/components/Steps.tsx

@@ -0,0 +1,68 @@
+import { RightOutlined } from '@ant-design/icons';
+import { Card, Col, Row } from 'antd';
+
+const Steps = () => {
+  return (
+    <Card title={'设备接入推荐步骤'}>
+      <Row gutter={24}>
+        <Col span={4}>
+          <Card
+            bordered
+            title="创建产品"
+            onClick={() => {
+              // pageJump(!!devicePermission.add, 'device/Instance')
+            }}
+          >
+            产品是设备的集合,通常指一组具有相同功能的设备。物联设备必须通过产品进行接入方式配置。
+          </Card>
+        </Col>
+        <Col span={1}>
+          <RightOutlined />
+        </Col>
+        <Col span={4}>
+          <Card bordered title="配置产品接入方式" onClick={() => {}}>
+            通过产品对同一类型的所有设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。
+          </Card>
+        </Col>
+        <Col span={1}>
+          <RightOutlined />
+        </Col>
+        <Col span={4}>
+          <Card
+            bordered
+            title="添加测试设备"
+            onClick={() => {
+              // pageJump(!!devicePermission.add, 'device/Instance')
+            }}
+          >
+            添加单个设备,用于验证产品模型是否配置正确。
+          </Card>
+        </Col>
+        <Col span={1}>
+          <RightOutlined />
+        </Col>
+        <Col span={4}>
+          <Card bordered title="功能调试" onClick={() => {}}>
+            对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。
+          </Card>
+        </Col>
+        <Col span={1}>
+          <RightOutlined />
+        </Col>
+        <Col span={4}>
+          <Card
+            bordered
+            title="批量添加设备"
+            onClick={() => {
+              // pageJump(!!devicePermission.add, 'device/Instance')
+            }}
+          >
+            批量添加同一产品下的设备
+          </Card>
+        </Col>
+      </Row>
+    </Card>
+  );
+};
+
+export default Steps;

+ 4 - 0
src/pages/home/comprehensive/index.tsx

@@ -0,0 +1,4 @@
+const Comprehensive = () => {
+  return <div>综合管理视图</div>;
+};
+export default Comprehensive;

+ 100 - 0
src/pages/home/device/index.tsx

@@ -0,0 +1,100 @@
+import { Card, Col, message, Row } from 'antd';
+import { PermissionButton } from '@/components';
+import { getMenuPathByCode } from '@/utils/menu';
+import Guide from '../components/Guide';
+import Statistics from '../components/Statistics';
+import Steps from '../components/Steps';
+
+const Device = () => {
+  const productPermission = PermissionButton.usePermission('device/Product').permission;
+  const devicePermission = PermissionButton.usePermission('device/Instance').permission;
+  const rulePermission = PermissionButton.usePermission('rule-engine/Instance').permission;
+  // // 跳转
+  const pageJump = (auth: boolean, url: string, param: string) => {
+    if (auth) {
+      // 判断是否有权限
+      const path = getMenuPathByCode(url);
+      if (path) {
+        const tab: any = window.open(`${origin}/#${path}${param}`);
+        tab!.onTabSaveSuccess = () => {
+          // if (value.status === 200) {
+          // }
+        };
+      }
+    } else {
+      message.error('暂无权限,请联系管理员');
+    }
+  };
+
+  const guideList = [
+    {
+      key: 'product',
+      name: '1、创建产品',
+      auth: !!productPermission.add,
+      url: 'device/Product',
+      param: '?save=true',
+    },
+    {
+      key: 'device',
+      name: '2、创建设备',
+      auth: !!devicePermission.add,
+      url: 'device/Instance',
+      param: '?save=true',
+    },
+    {
+      key: 'rule-engine',
+      name: '3、规则引擎',
+      auth: !!rulePermission.add,
+      url: 'rule-engine/Instance',
+      param: '?save=true',
+    },
+  ];
+
+  // const statisticsList = [{
+  //   key: 'product',
+  //   name: '1、创建产品',
+  //   auth: !!productPermission.add,
+  //   url: 'device/Product',
+  //   param: "?save=true"
+  // }, {
+  //   key: 'device',
+  //   name: '2、创建设备',
+  //   auth: !!devicePermission.add,
+  //   url: 'device/Instance',
+  //   param: "?save=true"
+  // },
+  // {
+  //   key: 'rule-engine',
+  //   name: '3、规则引擎',
+  //   auth: !!rulePermission.add,
+  //   url: 'rule-engine/Instance',
+  //   param: "?save=true"
+  // }
+  // ];
+
+  return (
+    <Row gutter={24}>
+      <Col span={12}>
+        <Guide
+          title="物联网引导"
+          data={guideList}
+          jump={(auth: boolean, url: string, param: string) => {
+            pageJump(auth, url, param);
+          }}
+        />
+      </Col>
+      <Col span={12}>
+        <Statistics />
+      </Col>
+      <Col span={24}>
+        <Card style={{ margin: '20px 0' }} title="平台架构图">
+          <img style={{ height: 500 }} src={require('/public/images/login.png')} />
+        </Card>
+      </Col>
+      <Col span={24}>
+        <Steps />
+      </Col>
+    </Row>
+  );
+};
+export default Device;

+ 34 - 0
src/pages/home/index.tsx

@@ -0,0 +1,34 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { useEffect, useState } from 'react';
+import Comprehensive from './comprehensive';
+import Device from './device';
+import Init from './init';
+import Ops from './ops';
+import Service from './service';
+
+export const service = new Service();
+const Home = () => {
+  type ViewType = keyof typeof ViewMap;
+  const [current, setCurrent] = useState<ViewType>('init');
+
+  const ViewMap = {
+    init: <Init changeView={(value: ViewType) => setCurrent(value)} />,
+    device: <Device />,
+    ops: <Ops />,
+    comprehensive: <Comprehensive />,
+  };
+
+  useEffect(() => {
+    service.queryView().then((resp) => {
+      if (resp.status === 200) {
+        if (resp.result.length == 0) {
+          setCurrent('init');
+        } else {
+          setCurrent(resp.result[0]?.content);
+        }
+      }
+    });
+  }, []);
+  return <PageContainer>{ViewMap[current]}</PageContainer>;
+};
+export default Home;

+ 47 - 0
src/pages/home/init/index.tsx

@@ -0,0 +1,47 @@
+import { Button, Radio } from 'antd';
+import { useState } from 'react';
+import { service } from '..';
+
+interface Props {
+  changeView: (view: any) => void;
+}
+
+const Init = (props: Props) => {
+  const options = [
+    { label: '设备接入视图', value: 'device' },
+    { label: '运维管理视图', value: 'ops' },
+    { label: '综合管理视图', value: 'comprehensive' },
+  ];
+
+  const [value, setValue] = useState<string>('device');
+
+  return (
+    <div>
+      <Radio.Group
+        options={options}
+        value={value}
+        onChange={(e) => setValue(e.target.value)}
+        optionType="button"
+      />
+      <div>
+        <Button
+          onClick={() => {
+            service
+              .setView({
+                name: 'view',
+                content: value,
+              })
+              .then((resp) => {
+                if (resp.status === 200) {
+                  props.changeView(value);
+                }
+              });
+          }}
+        >
+          确定
+        </Button>
+      </div>
+    </div>
+  );
+};
+export default Init;

+ 4 - 0
src/pages/home/ops/index.tsx

@@ -0,0 +1,4 @@
+const Ops = () => {
+  return <div>运维管理视图</div>;
+};
+export default Ops;

+ 17 - 0
src/pages/home/service.ts

@@ -0,0 +1,17 @@
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service {
+  public queryView = () =>
+    request(`/${SystemConst.API_BASE}/user/settings/view`, {
+      method: 'GET',
+    });
+
+  public setView = (data: Record<string, any>) =>
+    request(`/${SystemConst.API_BASE}/user/settings/view`, {
+      method: 'POST',
+      data,
+    });
+}
+
+export default Service;

+ 8 - 1
src/pages/link/Certificate/index.tsx

@@ -24,7 +24,14 @@ const Certificate = () => {
     {
       dataIndex: 'type',
       title: '证书标准',
-      render: (text: any) => <span>{text?.text || '-'}</span>,
+      render: (_, record: any) => <span>{record.type?.text || '-'}</span>,
+      valueType: 'select',
+      valueEnum: {
+        common: {
+          text: '国际标准',
+          status: 'common',
+        },
+      },
     },
     {
       dataIndex: 'name',

+ 30 - 0
src/pages/link/DashBoard/index.less

@@ -0,0 +1,30 @@
+.link-dash-board {
+  background-color: #fff;
+
+  .echarts-items {
+    position: relative;
+    display: flex;
+    gap: 12px;
+    margin: 12px 0;
+
+    .echarts-item {
+      display: flex;
+      flex-grow: 1;
+      align-items: center;
+      justify-content: center;
+      width: 0;
+      height: 160px;
+
+      .echarts-item-title {
+        margin-bottom: 8px;
+        color: rgba(#000, 0.6);
+        font-size: 16px;
+      }
+
+      .echarts-item-value {
+        font-weight: bold;
+        font-size: 18px;
+      }
+    }
+  }
+}

+ 351 - 38
src/pages/link/DashBoard/index.tsx

@@ -1,10 +1,14 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import DashBoard from '@/components/DashBoard';
-import { Radio, Select } from 'antd';
+import { Progress, Radio, Select } from 'antd';
 import { useEffect, useRef, useState } from 'react';
 import type { EChartsOption } from 'echarts';
 import { useRequest } from 'umi';
 import Service from './service';
+import moment from 'moment';
+import './index.less';
+import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
+import { map } from 'rxjs/operators';
 
 type RefType = {
   getValues: Function;
@@ -13,23 +17,136 @@ type RefType = {
 const service = new Service('dashboard');
 
 export default () => {
-  const [networkOptions] = useState<EChartsOption | undefined>(undefined);
-  const [cpuOptions] = useState<EChartsOption | undefined>(undefined);
-  const [jvmOptions] = useState<EChartsOption | undefined>(undefined);
+  const [networkOptions, setNetworkOptions] = useState<EChartsOption | undefined>(undefined);
+  const [cpuOptions, setCpuOptions] = useState<EChartsOption | undefined>(undefined);
+  const [jvmOptions, setJvmOptions] = useState<EChartsOption | undefined>(undefined);
   const [serverId, setServerId] = useState(undefined);
 
+  const [topValues, setTopValues] = useState({
+    cpu: 0,
+    jvm: 0,
+    usage: 0,
+    systemUsage: 0,
+  });
+
   const NETWORKRef = useRef<RefType>(); // 网络流量
   const CPURef = useRef<RefType>(); // CPU使用率
   const JVMRef = useRef<RefType>(); // JVM内存使用率
 
+  const [subscribeTopic] = useSendWebsocketMessage();
+
   const { data: serverNode } = useRequest(service.serverNode, {
     formatResult: (res) => res.result.map((item: any) => ({ label: item.name, value: item.id })),
   });
 
-  const getNetworkEcharts = () => {
-    const data = NETWORKRef.current!.getValues();
-    if (data) {
-      service.queryMulti([
+  const handleNetworkOptions = (data: Record<string, any>) => {
+    setNetworkOptions({
+      xAxis: {
+        type: 'category',
+        data: Object.keys(data),
+      },
+      tooltip: {
+        trigger: 'axis',
+      },
+      yAxis: {
+        type: 'value',
+      },
+      grid: {
+        left: '3%',
+        right: '2%',
+      },
+      series: [
+        {
+          data: Object.values(data),
+          type: 'line',
+        },
+      ],
+    });
+  };
+
+  const handleJVMOptions = (data: Record<string, any>) => {
+    setJvmOptions({
+      xAxis: {
+        type: 'category',
+        data: Object.keys(data).map((item) => {
+          return moment(Number(item)).format('YYYY-MM-DD HH:mm:ss');
+        }),
+      },
+      tooltip: {
+        trigger: 'axis',
+      },
+      yAxis: {
+        type: 'value',
+      },
+      grid: {
+        left: '3%',
+        right: '2%',
+      },
+      dataZoom: [
+        {
+          type: 'inside',
+          start: 0,
+          end: 10,
+        },
+        {
+          start: 0,
+          end: 10,
+        },
+      ],
+      series: [
+        {
+          data: Object.values(data),
+          type: 'line',
+        },
+      ],
+    });
+  };
+
+  const handleCpuOptions = (data: Record<string, any>) => {
+    setCpuOptions({
+      xAxis: {
+        type: 'category',
+        data: Object.keys(data).map((item) => {
+          return moment(Number(item)).format('YYYY-MM-DD HH:mm:ss');
+        }),
+      },
+      tooltip: {
+        trigger: 'axis',
+      },
+      yAxis: {
+        type: 'value',
+      },
+      grid: {
+        left: '3%',
+        right: '2%',
+      },
+      dataZoom: [
+        {
+          type: 'inside',
+          start: 0,
+          end: 10,
+        },
+        {
+          start: 0,
+          end: 10,
+        },
+      ],
+      series: [
+        {
+          data: Object.values(data),
+          type: 'line',
+        },
+      ],
+    });
+  };
+
+  const getAllEcharts = () => {
+    const networkData = NETWORKRef.current!.getValues();
+    const cpuData = CPURef.current!.getValues();
+    const jvmData = JVMRef.current!.getValues();
+
+    service
+      .queryMulti([
         {
           dashboard: 'systemMonitor',
           object: 'network',
@@ -37,57 +154,195 @@ export default () => {
           dimension: 'agg',
           group: 'network',
           params: {
-            type: data.type,
-            from: data.time.start,
-            to: data.time.end,
+            type: networkData.type,
+            interval: networkData.time.type === 'today' ? '1h' : '1d',
+            from: networkData.time.start,
+            to: networkData.time.end,
           },
         },
-      ]);
-    }
-  };
-
-  const getCPUEcharts = () => {
-    const data = CPURef.current!.getValues();
-    if (data) {
-      service.queryMulti([
         {
           dashboard: 'systemMonitor',
           object: 'stats',
-          measurement: 'traffic',
-          dimension: 'agg',
+          measurement: 'info',
+          dimension: 'history',
           group: 'cpu',
           params: {
-            from: data.time.start,
-            to: data.time.end,
+            from: cpuData.time.start,
+            to: cpuData.time.end,
           },
         },
-      ]);
-    }
-  };
-
-  const getJVMEcharts = () => {
-    const data = CPURef.current!.getValues();
-    if (data) {
-      service.queryMulti([
         {
           dashboard: 'systemMonitor',
           object: 'stats',
-          measurement: 'traffic',
-          dimension: 'agg',
+          measurement: 'info',
+          dimension: 'history',
           group: 'jvm',
           params: {
-            from: data.time.start,
-            to: data.time.end,
+            from: jvmData.time.start,
+            to: jvmData.time.end,
           },
         },
-      ]);
+      ])
+      .then((res) => {
+        if (res.status === 200) {
+          const _networkOptions = {};
+          const _jvmOptions = {};
+          const _cpuOptions = {};
+
+          res.result.forEach((item: any) => {
+            const value = item.data.value;
+            if (item.group === 'network') {
+              value.forEach((networkItem: any) => {
+                _networkOptions[networkItem.timeString] = networkItem.value;
+              });
+            } else if (item.group === 'cpu') {
+              const memoryJvmHeapFree = value.memoryJvmHeapFree;
+              const memoryJvmHeapTotal = value.memoryJvmHeapTotal;
+              _jvmOptions[value.timestamp] = (
+                ((memoryJvmHeapTotal - memoryJvmHeapFree) / memoryJvmHeapTotal) *
+                100
+              ).toFixed(2);
+            } else {
+              _cpuOptions[value.timestamp] = value.cpuSystemUsage;
+            }
+          });
+          handleNetworkOptions(_networkOptions);
+          handleJVMOptions(_jvmOptions);
+          handleCpuOptions(_cpuOptions);
+        }
+      });
+  };
+
+  const getNetworkEcharts = () => {
+    const data = NETWORKRef.current!.getValues();
+    if (data) {
+      service
+        .queryMulti([
+          {
+            dashboard: 'systemMonitor',
+            object: 'network',
+            measurement: 'traffic',
+            dimension: 'agg',
+            group: 'network',
+            params: {
+              type: data.type,
+              interval: data.time.type === 'today' ? '1h' : '1d',
+              from: data.time.start,
+              to: data.time.end,
+            },
+          },
+        ])
+        .then((res) => {
+          if (res.status === 200) {
+            const _options = {};
+            res.result.forEach((item: any) => {
+              const value = item.data.value;
+              value.forEach((networkItem: any) => {
+                _options[networkItem.timeString] = networkItem.value;
+              });
+            });
+            handleNetworkOptions(_options);
+          }
+        });
+    }
+  };
+
+  const getCPUEcharts = () => {
+    const data = CPURef.current!.getValues();
+    if (data) {
+      service
+        .queryMulti([
+          {
+            dashboard: 'systemMonitor',
+            object: 'stats',
+            measurement: 'info',
+            dimension: 'history',
+            group: 'cpu',
+            params: {
+              from: data.time.start,
+              to: data.time.end,
+            },
+          },
+        ])
+        .then((res) => {
+          if (res.status === 200) {
+            const _options = {};
+            res.result.forEach((item: any) => {
+              const value = item.data.value;
+              _options[value.timestamp] = value.cpuSystemUsage;
+            });
+            handleCpuOptions(_options);
+          }
+        });
+    }
+  };
+
+  const getJVMEcharts = () => {
+    const data = JVMRef.current!.getValues();
+    if (data) {
+      service
+        .queryMulti([
+          {
+            dashboard: 'systemMonitor',
+            object: 'stats',
+            measurement: 'info',
+            dimension: 'history',
+            group: 'jvm',
+            params: {
+              from: data.time.start,
+              to: data.time.end,
+            },
+          },
+        ])
+        .then((res) => {
+          if (res.status === 200) {
+            const _options = {};
+            res.result.forEach((item: any) => {
+              const value = item.data.value;
+              const memoryJvmHeapFree = value.memoryJvmHeapFree;
+              const memoryJvmHeapTotal = value.memoryJvmHeapTotal;
+              _options[value.timestamp] = (
+                ((memoryJvmHeapTotal - memoryJvmHeapFree) / memoryJvmHeapTotal) *
+                100
+              ).toFixed(2);
+            });
+            handleJVMOptions(_options);
+          }
+        });
     }
   };
 
   useEffect(() => {
     if (serverId) {
-      getNetworkEcharts();
+      getAllEcharts();
     }
+
+    const id = 'operations-statistics-system-info-realTime';
+    const topic = '/dashboard/systemMonitor/stats/info/realTime';
+    const sub = subscribeTopic!(id, topic, {
+      type: 'all',
+      serverNodeId: serverId,
+      interval: '5s',
+      agg: 'avg',
+    })
+      ?.pipe(map((res) => res.payload))
+      .subscribe((plyload: any) => {
+        const value = plyload.value;
+        const cpu = value.cpu;
+        const memory = value.memory;
+        const disk = value.disk;
+
+        setTopValues({
+          cpu: cpu.systemUsage,
+          jvm: memory.jvmHeapUsage,
+          usage: disk.usage,
+          systemUsage: memory.systemUsage,
+        });
+      });
+
+    return () => {
+      sub?.unsubscribe();
+    };
   }, [serverId]);
 
   useEffect(() => {
@@ -98,7 +353,7 @@ export default () => {
 
   return (
     <PageContainer>
-      <div>
+      <div className={'link-dash-board'}>
         {serverNode && serverNode.length ? (
           <Select
             value={serverId}
@@ -109,6 +364,64 @@ export default () => {
             style={{ width: 300 }}
           />
         ) : null}
+        <div className={'echarts-items'}>
+          <div className={'echarts-item'}>
+            <Progress
+              type="circle"
+              strokeWidth={8}
+              width={160}
+              percent={topValues.cpu}
+              format={(percent) => (
+                <div>
+                  <div className={'echarts-item-title'}>CPU使用率</div>
+                  <div className={'echarts-item-value'}>{percent}%</div>
+                </div>
+              )}
+            />
+          </div>
+          <div className={'echarts-item'}>
+            <Progress
+              type="circle"
+              strokeWidth={8}
+              width={160}
+              percent={topValues.jvm}
+              format={(percent) => (
+                <div>
+                  <div className={'echarts-item-title'}>JVM内存</div>
+                  <div className={'echarts-item-value'}>{percent}%</div>
+                </div>
+              )}
+            />
+          </div>
+          <div className={'echarts-item'}>
+            <Progress
+              type="circle"
+              strokeWidth={8}
+              width={160}
+              percent={topValues.usage}
+              format={(percent) => (
+                <div>
+                  <div className={'echarts-item-title'}>磁盘占用率</div>
+                  <div className={'echarts-item-value'}>{percent}%</div>
+                </div>
+              )}
+            />
+          </div>
+          <div className={'echarts-item'}>
+            <Progress
+              type="circle"
+              strokeWidth={8}
+              width={160}
+              percent={topValues.systemUsage}
+              format={(percent) => (
+                <div>
+                  <div className={'echarts-item-title'}>系统内存</div>
+                  <div className={'echarts-item-value'}>{percent}%</div>
+                </div>
+              )}
+            />
+          </div>
+        </div>
         <div>
           <DashBoard
             title={'网络流量'}

+ 3 - 3
src/pages/media/Cascade/Channel/index.tsx

@@ -52,7 +52,7 @@ const Channel = () => {
           style={{ marginTop: 10, width: '100%' }}
           onClick={async () => {
             if (!!data) {
-              const resp: any = await service.editBindInfo(record.gbChannelId, {
+              const resp: any = await service.editBindInfo(record.id, {
                 gbChannelId: data,
               });
               if (resp.status === 200) {
@@ -88,7 +88,7 @@ const Channel = () => {
         <span>
           {text}
           <Popover
-            visible={popVisible === record.gbChannelId}
+            visible={popVisible === record.id}
             trigger="click"
             content={content(record)}
             title={
@@ -114,7 +114,7 @@ const Channel = () => {
               style={{ marginLeft: 10 }}
               onClick={() => {
                 setData('');
-                setPopvisible(record.gbChannelId);
+                setPopvisible(record.id);
               }}
             >
               <EditOutlined />

+ 168 - 90
src/pages/media/DashBoard/index.tsx

@@ -1,46 +1,57 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { Badge, Card, Select } from 'antd';
-import DashBoard from '@/components/DashBoard';
+import DashBoard, { DashBoardTopCard } from '@/components/DashBoard';
 import { useRequest } from 'umi';
-import React, { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
 import Service from './service';
 import './index.less';
 import encodeQuery from '@/utils/encodeQuery';
 import type { EChartsOption } from 'echarts';
+import moment from 'moment';
 
-interface TopCardProps {
-  url: string;
-  title: string;
-  total: number | string;
-  bottomRender: () => React.ReactNode;
-}
-
-const service = new Service('media/device');
-
-const TopCard = (props: TopCardProps) => {
-  return (
-    <div className={'top-card-item'}>
-      <div className={'top-card-top'}>
-        <div className={'top-card-top-left'}></div>
-        <div className={'top-card-top-right'}>
-          <div className={'top-card-title'}>{props.title}</div>
-          <div className={'top-card-total'}>{props.total}</div>
-        </div>
-      </div>
-      <div className={'top-card-bottom'}>{props.bottomRender()}</div>
-    </div>
-  );
-};
+const service = new Service('media');
 
 export default () => {
   const [deviceOnline, setDeviceOnline] = useState(0);
   const [deviceOffline, setDeviceOffline] = useState(0);
+  const [channelOnline, setChannelOnline] = useState(0);
+  const [channelOffline, setChannelOffline] = useState(0);
   const [options, setOptions] = useState<EChartsOption>({});
 
   const { data: deviceTotal } = useRequest(service.deviceCount, {
     formatResult: (res) => res.result,
   });
 
+  const { data: playObject } = useRequest(service.playingAgg, {
+    formatResult: (res) => res.result,
+  });
+
+  const { data: fileObject } = useRequest(service.fileAgg, {
+    formatResult: (res) => res.result,
+  });
+
+  const { data: channelTotal } = useRequest<any, any>(service.channelCount, {
+    formatResult: (res) => res.result,
+    defaultParams: {},
+  });
+
+  /**
+   * 通道数量
+   */
+  const channelStatus = async () => {
+    const onlineRes = await service.channelCount({
+      terms: [{ column: 'status', value: 'online' }],
+    });
+    if (onlineRes.status === 200) {
+      setChannelOnline(onlineRes.result);
+    }
+    const offlineRes = await service.channelCount({
+      terms: [{ column: 'status$not', value: 'online' }],
+    });
+    if (offlineRes.status === 200) {
+      setChannelOffline(offlineRes.result);
+    }
+  };
+
   /**
    * 设备数量
    */
@@ -56,90 +67,157 @@ export default () => {
   };
 
   const getEcharts = async (params: any) => {
-    // 请求数据
-    console.log(params);
-
-    setOptions({
-      xAxis: {
-        type: 'category',
-        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
-      },
-      yAxis: {
-        type: 'value',
+    const resp = await service.getMulti([
+      {
+        dashboard: 'media_stream',
+        object: 'play_count',
+        measurement: 'quantity',
+        dimension: 'agg',
+        group: 'playCount',
+        params: {
+          time: params.time.type === 'today' ? '1h' : '1d',
+          from: moment(params.time.start).format('YYYY-MM-DD HH:mm:ss'),
+          to: moment(params.time.end).format('YYYY-MM-DD HH:mm:ss'),
+          limit: 30,
+        },
       },
-      series: [
-        {
-          data: [150, 230, 224, 218, 135, 147, 260],
-          type: 'line',
+    ]);
+
+    if (resp.status === 200) {
+      const xData: string[] = [];
+      const sData: number[] = [];
+      resp.result.forEach((item: any) => {
+        xData.push(item.data.timeString);
+        sData.push(item.data.value);
+      });
+
+      setOptions({
+        xAxis: {
+          type: 'category',
+          data: xData,
         },
-      ],
-    });
+        yAxis: {
+          type: 'value',
+        },
+        grid: {
+          left: '2%',
+          right: '2%',
+          top: '2%',
+          bottom: '2%',
+        },
+        series: [
+          {
+            data: sData,
+            type: 'bar',
+          },
+        ],
+      });
+    }
+  };
+
+  const handleTimeFormat = (time: number) => {
+    let hour = 0;
+    let min = 0;
+    let sec = 0;
+    const timeStr = 'hh小时mm分钟ss秒';
+
+    if (time) {
+      if (time >= 6000) {
+        hour = Math.trunc(time / (60 * 60));
+      }
+
+      if (time >= 60) {
+        min = Math.trunc((time - hour * 60 * 60) / 60);
+      }
+
+      sec = time - hour * (60 * 60) - min * 60;
+    }
+
+    return timeStr
+      .replace('hh', hour.toString())
+      .replace('mm', min.toString())
+      .replace('ss', sec.toString());
   };
 
   useEffect(() => {
     deviceStatus();
+    channelStatus();
   }, []);
 
   return (
     <PageContainer>
       <div className={'media-dash-board'}>
-        <Card className={'top-card-items'} bodyStyle={{ display: 'flex', gap: 12 }}>
-          <TopCard
+        <DashBoardTopCard>
+          <DashBoardTopCard.Item
             title={'设备数量'}
-            total={deviceTotal}
-            url={''}
-            bottomRender={() => (
-              <>
-                <Badge status="error" text="在线" />{' '}
-                <span style={{ padding: '0 4px' }}>{deviceOnline}</span>
-                <Badge status="success" text="离线" />{' '}
-                <span style={{ padding: '0 4px' }}>{deviceOffline}</span>
-              </>
-            )}
-          />
-          <TopCard
+            value={deviceTotal}
+            footer={[
+              {
+                title: '在线',
+                value: deviceOnline,
+                status: 'success',
+              },
+              {
+                title: '离线',
+                value: deviceOffline,
+                status: 'error',
+              },
+            ]}
+            span={6}
+          >
+            <img src={require('/public/images/media/dashboard-1.png')} />
+          </DashBoardTopCard.Item>
+          <DashBoardTopCard.Item
             title={'通道数量'}
-            total={12}
-            url={''}
-            bottomRender={() => (
-              <>
-                <Badge status="error" text="在线" /> <span style={{ padding: '0 4px' }}>12</span>
-                <Badge status="success" text="离线" /> <span style={{ padding: '0 4px' }}>12</span>
-              </>
-            )}
-          />
-          <TopCard
+            value={channelTotal}
+            footer={[
+              {
+                title: '已连接',
+                value: channelOnline,
+                status: 'success',
+              },
+              {
+                title: '未连接',
+                value: channelOffline,
+                status: 'error',
+              },
+            ]}
+            span={6}
+          >
+            <img src={require('/public/images/media/dashboard-2.png')} />
+          </DashBoardTopCard.Item>
+          <DashBoardTopCard.Item
             title={'录像数量'}
-            total={12}
-            url={''}
-            bottomRender={() => <div>总时长: </div>}
-          />
-          <TopCard
+            value={fileObject ? fileObject.total : 0}
+            footer={[
+              {
+                title: '总时长',
+                value: handleTimeFormat(fileObject ? fileObject.duration : 0),
+              },
+            ]}
+            span={6}
+          >
+            <img src={require('/public/images/media/dashboard-3.png')} />
+          </DashBoardTopCard.Item>
+          <DashBoardTopCard.Item
             title={'播放中数量'}
-            total={12}
-            url={''}
-            bottomRender={() => <div>播放人数: </div>}
-          />
-        </Card>
+            value={playObject ? playObject.playerTotal : 0}
+            footer={[
+              {
+                title: '播放人数',
+                value: playObject ? playObject.playingTotal : 0,
+              },
+            ]}
+            span={6}
+          >
+            <img src={require('/public/images/media/dashboard-4.png')} />
+          </DashBoardTopCard.Item>
+        </DashBoardTopCard>
         <DashBoard
           className={'media-dash-board-body'}
           title={'播放数量(人次)'}
           options={options}
           height={500}
-          initialValues={{
-            test: '2',
-          }}
-          extraParams={{
-            key: 'test',
-            Children: (
-              <Select
-                options={[
-                  { label: '1', value: '1' },
-                  { label: '2', value: '2' },
-                ]}
-              ></Select>
-            ),
-          }}
           onParamsChange={getEcharts}
         />
       </div>

+ 11 - 3
src/pages/media/DashBoard/service.ts

@@ -1,12 +1,20 @@
 import BaseService from '@/utils/BaseService';
 import { request } from 'umi';
 import type { DeviceItem } from '@/pages/media/Device/typings';
+import SystemConst from '@/utils/const';
 
 class Service extends BaseService<DeviceItem> {
-  deviceCount = (data?: any) => request(`${this.uri}/_count`, { methods: 'GET', params: data });
+  deviceCount = (data?: any) =>
+    request(`${this.uri}/device/_count`, { methods: 'GET', params: data });
 
-  channelCount = (data?: any) =>
-    request(`${this.uri}/channel/_count`, { methods: 'POST', params: data });
+  channelCount = (data?: any) => request(`${this.uri}/channel/_count`, { method: 'POST', data });
+
+  playingAgg = () => request(`${this.uri}/channel/playing/agg`, { method: 'GET' });
+
+  fileAgg = () => request(`${this.uri}/record/file/agg`, { method: 'GET' });
+
+  getMulti = (data: any) =>
+    request(`/${SystemConst.API_BASE}/dashboard/_multi`, { method: 'POST', data });
 }
 
 export default Service;

+ 10 - 1
src/pages/media/Device/index.tsx

@@ -1,6 +1,6 @@
 // 视频设备列表
 import { PageContainer } from '@ant-design/pro-layout';
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { Button, message, Tooltip } from 'antd';
 import {
@@ -14,6 +14,7 @@ import {
 import type { DeviceItem } from '@/pages/media/Device/typings';
 import { useHistory, useIntl } from 'umi';
 import { BadgeStatus, PermissionButton, ProTableCard } from '@/components';
+import useLocation from '@/hooks/route/useLocation';
 import { StatusColorEnum } from '@/components/BadgeStatus';
 import SearchComponent from '@/components/SearchComponent';
 import MediaDevice from '@/components/ProTableCard/CardItems/mediaDevice';
@@ -41,6 +42,14 @@ const Device = () => {
   const [queryParam, setQueryParam] = useState({});
   const history = useHistory<Record<string, string>>();
   const { permission } = PermissionButton.usePermission('media/Device');
+  const location = useLocation();
+
+  useEffect(() => {
+    const { state } = location;
+    if (state && state.save) {
+      setVisible(true);
+    }
+  }, [location]);
 
   /**
    * table 查询参数

+ 154 - 0
src/pages/media/Home/deviceModal.tsx

@@ -0,0 +1,154 @@
+import { message, Modal } from 'antd';
+import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { service } from './index';
+import SearchComponent from '@/components/SearchComponent';
+import { DeviceItem } from '@/pages/media/Home/typings';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { BadgeStatus } from '@/components';
+import { StatusColorEnum } from '@/components/BadgeStatus';
+import useHistory from '@/hooks/route/useHistory';
+
+interface DeviceModalProps {
+  visible: boolean;
+  url?: string;
+  onCancel: () => void;
+}
+
+export default (props: DeviceModalProps) => {
+  const intl = useIntl();
+  const history = useHistory();
+
+  const actionRef = useRef<ActionType>();
+  const [searchParam, setSearchParam] = useState({});
+  const [deviceItem, setDeviceItem] = useState<any>({});
+
+  useEffect(() => {
+    if (!props.visible) {
+      setDeviceItem({});
+      setSearchParam({});
+    }
+  }, [props.visible]);
+
+  const cancel = () => {
+    if (props.onCancel) {
+      props.onCancel();
+    }
+  };
+
+  const jumpChannel = useCallback(() => {
+    if (deviceItem && deviceItem.id) {
+      history.push(`${props.url}?id=${deviceItem.id}&type=${deviceItem.provider}`);
+    } else {
+      message.warning('请选择设备');
+    }
+  }, [props.url, deviceItem]);
+
+  const columns: ProColumns<DeviceItem>[] = [
+    {
+      dataIndex: 'id',
+      title: 'ID',
+      width: 220,
+    },
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.table.name',
+        defaultMessage: '名称',
+      }),
+    },
+    {
+      dataIndex: 'channelNumber',
+      title: intl.formatMessage({
+        id: 'pages.media.device.channelNumber',
+        defaultMessage: '通道数量',
+      }),
+      valueType: 'digit',
+      hideInSearch: true,
+    },
+    {
+      dataIndex: 'state',
+      title: intl.formatMessage({
+        id: 'pages.searchTable.titleStatus',
+        defaultMessage: '状态',
+      }),
+      render: (_, record) => (
+        <BadgeStatus
+          status={record.state.value}
+          statusNames={{
+            online: StatusColorEnum.success,
+            offline: StatusColorEnum.error,
+            notActive: StatusColorEnum.processing,
+          }}
+          text={record.state.text}
+        />
+      ),
+      valueType: 'select',
+      valueEnum: {
+        offline: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.offLine',
+            defaultMessage: '离线',
+          }),
+          status: 'offline',
+        },
+        online: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.onLine',
+            defaultMessage: '在线',
+          }),
+          status: 'online',
+        },
+      },
+      filterMultiple: false,
+    },
+  ];
+
+  return (
+    <Modal
+      title={'选择设备'}
+      onCancel={cancel}
+      onOk={jumpChannel}
+      destroyOnClose={true}
+      maskClosable={false}
+      visible={props.visible}
+      width={800}
+    >
+      <SearchComponent<DeviceItem>
+        field={columns}
+        enableSave={false}
+        onSearch={async (data) => {
+          setSearchParam(data);
+        }}
+        target="media-home-device"
+      />
+      <ProTable<DeviceItem>
+        actionRef={actionRef}
+        columns={columns}
+        rowKey={'id'}
+        search={false}
+        request={(params) =>
+          service.query({
+            ...params,
+            sorts: [
+              {
+                name: 'createTime',
+                order: 'desc',
+              },
+            ],
+          })
+        }
+        rowSelection={{
+          type: 'radio',
+          selectedRowKeys: deviceItem.id ? [deviceItem.id] : undefined,
+          onSelect: (record) => {
+            console.log(record);
+            setDeviceItem(record);
+          },
+        }}
+        tableAlertOptionRender={() => false}
+        params={searchParam}
+      />
+    </Modal>
+  );
+};

+ 38 - 0
src/pages/media/Home/index.less

@@ -0,0 +1,38 @@
+.media-home {
+  .media-home-top {
+    & .media-guide,
+    & .media-statistics {
+      display: flex;
+      justify-content: space-between;
+      height: 150px;
+    }
+
+    .media-guide {
+      > div {
+        padding: 24px;
+        border: 1px solid rgba(#000, 0.4);
+      }
+    }
+
+    .media-statistics {
+      > div {
+        display: flex;
+        flex-grow: 1;
+      }
+    }
+  }
+
+  .media-home-content {
+    height: 350px;
+  }
+
+  .media-home-steps {
+    display: flex;
+    height: 150px;
+
+    > div {
+      padding: 24px;
+      border: 1px solid rgba(#000, 0.4);
+    }
+  }
+}

+ 150 - 0
src/pages/media/Home/index.tsx

@@ -0,0 +1,150 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { Card, Col, message, Row, Tooltip, Typography } from 'antd';
+import { PermissionButton } from '@/components';
+import { getMenuPathByCode } from '@/utils/menu';
+import useHistory from '@/hooks/route/useHistory';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import { useRequest } from 'umi';
+import Service from './service';
+import { useState } from 'react';
+import DeviceModal from './deviceModal';
+import './index.less';
+
+const permissionTip = '暂无权限,请联系管理员';
+
+export const service = new Service('media/device');
+
+export default () => {
+  const dashBoardUrl = getMenuPathByCode('media/DashBoard');
+  const deviceUrl = getMenuPathByCode('media/Device');
+  const channelUrl = getMenuPathByCode('media/Device/Channel');
+  const splitScreenUrl = getMenuPathByCode('media/SplitScreen');
+  const cascadeUrl = getMenuPathByCode('media/Cascade');
+
+  const [visible, setVisible] = useState(false);
+
+  const { permission: devicePermission } = PermissionButton.usePermission('media/Device');
+
+  const history = useHistory();
+
+  const { data: deviceTotal } = useRequest(service.deviceCount, {
+    formatResult: (res) => res.result,
+  });
+
+  const { data: channelTotal } = useRequest<any, any>(service.channelCount, {
+    formatResult: (res) => res.result,
+    defaultParams: {},
+  });
+
+  const addDevice = () => {
+    if (deviceUrl && devicePermission.add) {
+      history.push(deviceUrl, {
+        save: true,
+      });
+    } else {
+      message.warning(permissionTip);
+    }
+  };
+
+  const jumpSplitScreen = () => {
+    if (splitScreenUrl) {
+      history.push(splitScreenUrl);
+    } else {
+      message.warning(permissionTip);
+    }
+  };
+
+  const jumpCascade = () => {
+    if (cascadeUrl) {
+      history.push(cascadeUrl);
+    } else {
+      message.warning(permissionTip);
+    }
+  };
+
+  const jumpChannel = () => {
+    if (channelUrl) {
+      setVisible(true);
+    } else {
+      message.warning(permissionTip);
+    }
+  };
+
+  return (
+    <PageContainer>
+      <DeviceModal
+        visible={visible}
+        url={channelUrl}
+        onCancel={() => {
+          setVisible(false);
+        }}
+      />
+      <Card className={'media-home'}>
+        <Row gutter={[12, 12]}>
+          <Col span={14}>
+            <div className={'media-home-top'}>
+              <Typography.Title level={5}>视频中心引导</Typography.Title>
+              <div className={'media-guide'}>
+                <div onClick={addDevice}>添加视频设备</div>
+                <div onClick={jumpSplitScreen}>分屏展示</div>
+                <div onClick={jumpCascade}>国标级联</div>
+              </div>
+            </div>
+          </Col>
+          <Col span={10}>
+            <div className={'media-home-top'}>
+              <Typography.Title level={5}>
+                基础统计
+                <PermissionButton
+                  isPermission={!!dashBoardUrl}
+                  onClick={() => {
+                    history.push(dashBoardUrl);
+                  }}
+                  type={'link'}
+                >
+                  详情
+                </PermissionButton>
+              </Typography.Title>
+              <div className={'media-statistics'}>
+                <div>
+                  设备数量
+                  {deviceTotal}
+                </div>
+                <div>
+                  通道数量
+                  {channelTotal}
+                </div>
+              </div>
+            </div>
+          </Col>
+          <Col span={24}>
+            <Typography.Title level={5}>平台架构图</Typography.Title>
+            <div className={'media-home-content'}></div>
+          </Col>
+          <Col span={24}>
+            <Typography.Title level={5}>
+              <span style={{ paddingRight: 12 }}>视频设备管理推荐步骤</span>
+              <Tooltip title={'请根据业务需要对下述步骤进行选择性操作'}>
+                <QuestionCircleOutlined />
+              </Tooltip>
+            </Typography.Title>
+            <div className={'media-home-steps'}>
+              <div onClick={addDevice}>
+                添加视频设备
+                <div>根据视频设备的传输协议,在已创建的产品下添加对应的设备</div>
+              </div>
+              <div onClick={jumpChannel}>
+                查看通道
+                <div>查看设备下的通道数据,可以进行直播、录制等操作</div>
+              </div>
+              <div onClick={jumpSplitScreen}>
+                分屏展示
+                <div>对多个通道的视频流数据进行分屏展示</div>
+              </div>
+            </div>
+          </Col>
+        </Row>
+      </Card>
+    </PageContainer>
+  );
+};

+ 11 - 0
src/pages/media/Home/service.tsx

@@ -0,0 +1,11 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import type { DeviceItem } from '@/pages/media/Device/typings';
+
+class Service extends BaseService<DeviceItem> {
+  deviceCount = (data?: any) => request(`${this.uri}/_count`, { methods: 'GET', params: data });
+
+  channelCount = (data?: any) => request(`${this.uri}/channel/_count`, { method: 'POST', data });
+}
+
+export default Service;

+ 17 - 0
src/pages/media/Home/typings.d.ts

@@ -0,0 +1,17 @@
+import type { BaseItem, State } from '@/utils/typings';
+
+export type DeviceItem = {
+  photoUrl?: string;
+  channelNumber: number;
+  createTime: number;
+  firmware: string;
+  gatewayId: string;
+  host: string;
+  manufacturer: string;
+  model: string;
+  port: number;
+  provider: string;
+  state: State;
+  streamMode: string;
+  transport: string;
+} & BaseItem;

+ 50 - 0
src/pages/notice/Template/index.tsx

@@ -38,6 +38,54 @@ export const state = model<{
   debug: false,
   log: false,
 });
+
+const list = {
+  weixin: {
+    corpMessage: {
+      text: '企业消息',
+      status: 'corpMessage',
+    },
+    officialMessage: {
+      text: '服务号消息',
+      status: 'officialMessage',
+    },
+  },
+  dingTalk: {
+    dingTalkMessage: {
+      text: '钉钉消息',
+      status: 'dingTalkMessage',
+    },
+    dingTalkRobotWebHook: {
+      text: '群机器人消息',
+      status: 'dingTalkRobotWebHook',
+    },
+  },
+  voice: {
+    aliyun: {
+      text: '阿里云语音',
+      status: 'aliyun',
+    },
+  },
+  sms: {
+    aliyunSms: {
+      text: '阿里云短信',
+      status: 'aliyunSms',
+    },
+  },
+  email: {
+    embedded: {
+      text: '默认',
+      status: 'embedded',
+    },
+  },
+  webhook: {
+    http: {
+      text: 'Webhook',
+      status: 'http',
+    },
+  },
+};
+
 const Template = observer(() => {
   const intl = useIntl();
   const location = useLocation<{ id: string }>();
@@ -56,6 +104,8 @@ const Template = observer(() => {
       dataIndex: 'provider',
       title: '通知方式',
       renderText: (text, record) => typeList[record.type][record.provider],
+      valueType: 'select',
+      valueEnum: list[id],
     },
     {
       dataIndex: 'description',

+ 4 - 4
src/pages/rule-engine/Alarm/Config/index.tsx

@@ -355,18 +355,18 @@ const Config = () => {
     const outputConfig: IOConfigItem = await outputForm.submit();
     const inputResp = await service.saveOutputData({
       config: {
-        config: inputConfig,
+        config: outputConfig,
       },
-      id: inputConfig.id,
+      id: outputConfig.id,
       sourceType: 'kafka',
       exchangeType: 'producer',
     });
     const outputResp = await service.saveOutputData({
       config: {
         sourceType: 'kafka',
-        config: outputConfig,
+        config: inputConfig,
       },
-      id: outputConfig.id,
+      id: inputConfig.id,
       sourceType: 'kafka',
       exchangeType: 'consume',
     });

+ 27 - 10
src/pages/rule-engine/Alarm/Configuration/Save/index.tsx

@@ -1,6 +1,6 @@
 import { message, Modal, Typography } from 'antd';
 import { useMemo } from 'react';
-import { createForm, onFieldInit } from '@formily/core';
+import { createForm, Field, onFieldInit, onFieldValueChange } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import { Form, FormGrid, FormItem, Input, Radio, Select } from '@formily/antd';
 import { ISchema } from '@formily/json-schema';
@@ -11,6 +11,7 @@ import { useAsyncDataSource } from '@/utils/util';
 import styles from './index.less';
 import { service as ConfigService } from '../../Config';
 import { Store } from 'jetlinks-store';
+import encodeQuery from '@/utils/encodeQuery';
 
 interface Props {
   visible: boolean;
@@ -58,15 +59,6 @@ const Save = (props: Props) => {
     });
   };
 
-  const getScene = () => {
-    return service.getScene().then((resp) => {
-      Store.set('scene-data', resp.result);
-      return resp.result.map((item: { id: string; name: string }) => ({
-        label: item.name,
-        value: item.id,
-      }));
-    });
-  };
   const form = useMemo(
     () =>
       createForm({
@@ -80,11 +72,36 @@ const Save = (props: Props) => {
               disabled: resp.result > 0,
             });
           });
+          onFieldValueChange('targetType', async (field: Field, f) => {
+            if (field.modified) {
+              f.setFieldState(field.query('.sceneId'), (state) => {
+                state.value = undefined;
+              });
+            }
+          });
         },
       }),
     [props.data, props.visible],
   );
 
+  const getScene = () => {
+    return service
+      .getScene(
+        encodeQuery({
+          terms: {
+            triggerType: form.getValuesIn('targetType'),
+          },
+        }),
+      )
+      .then((resp) => {
+        Store.set('scene-data', resp.result);
+        return resp.result.map((item: { id: string; name: string }) => ({
+          label: item.name,
+          value: item.id,
+        }));
+      });
+  };
+
   const getSupports = () => service.getTargetTypes();
 
   const SchemaField = createSchemaField({

+ 2 - 1
src/pages/rule-engine/Alarm/Configuration/service.ts

@@ -13,9 +13,10 @@ class Service extends BaseService<ConfigItem> {
       }));
     });
 
-  public getScene = () =>
+  public getScene = (params: Record<string, any>) =>
     request(`/${SystemConst.API_BASE}/scene/_query/no-paging?paging=false`, {
       method: 'GET',
+      params,
     });
 
   public _enable = (id: string) =>

+ 135 - 3
src/pages/rule-engine/DashBoard/index.tsx

@@ -1,11 +1,14 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { EChartsOption } from 'echarts';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
 import { Statistic, StatisticCard } from '@ant-design/pro-card';
 import { Card, Select } from 'antd';
 import './index.less';
 import Header from '@/components/DashBoard/header';
 import Echarts from '@/components/DashBoard/echarts';
+import Service from './service';
+import { observer } from '@formily/react';
+import { model } from '@formily/reactive';
 
 const imgStyle = {
   display: 'block',
@@ -13,9 +16,137 @@ const imgStyle = {
   height: 42,
 };
 
-const Dashboard = () => {
+const service = new Service();
+export const state = model<{
+  today: number;
+  thisMonth: number;
+  config: number;
+  enabledConfig: number;
+  disabledConfig: number;
+  alarmList: any[];
+}>({
+  today: 0,
+  thisMonth: 0,
+  config: 0,
+  enabledConfig: 0,
+  disabledConfig: 0,
+  alarmList: [],
+});
+
+type DashboardItem = {
+  group: string;
+  data: Record<string, any>;
+};
+const Dashboard = observer(() => {
   const [options, setOptions] = useState<EChartsOption>({});
 
+  // 今日告警
+  const today = {
+    dashboard: 'alarm',
+    object: 'record',
+    measurement: 'trend',
+    dimension: 'agg',
+    group: 'today',
+    params: {
+      time: '1h',
+      targetType: 'device',
+      format: 'HH:mm:ss',
+      from: 'now-1d',
+      to: 'now',
+      limit: 24,
+    },
+  };
+  // 当月告警
+  const thisMonth = {
+    dashboard: 'alarm',
+    object: 'record',
+    measurement: 'trend',
+    dimension: 'agg',
+    group: 'thisMonth',
+    params: {
+      time: '1M',
+      targetType: 'device',
+      format: 'yyyy-MM',
+      limit: 1,
+      from: 'now-1M',
+    },
+  };
+  // 告警趋势
+  const chartData = {
+    dashboard: 'alarm',
+    object: 'record',
+    measurement: 'trend',
+    dimension: 'agg',
+    group: 'alarmTrend',
+    params: {
+      time: '1M',
+      targetType: 'device', // product、device、org、other
+      from: 'now-1y', // now-1d、now-1w、now-1M、now-1y
+      format: 'M月',
+      to: 'now',
+      limit: 12,
+    },
+  };
+
+  // 告警排名
+  const order = {
+    dashboard: 'alarm',
+    object: 'record',
+    measurement: 'rank',
+    dimension: 'agg',
+    group: 'alarmRank',
+    params: {
+      time: '1h',
+      targetType: 'device',
+      from: 'now-1w',
+      to: 'now',
+      limit: 10,
+    },
+  };
+
+  const getDashboard = async () => {
+    const resp = await service.dashboard([today, thisMonth, chartData, order]);
+    if (resp.status === 200) {
+      const _data = resp.result as DashboardItem[];
+      state.today = _data.find((item) => item.group === 'today')?.data.value;
+      state.thisMonth = _data.find((item) => item.group === 'thisMonth')?.data.value;
+    }
+  };
+
+  const getAlarmConfig = async () => {
+    const countResp = await service.getAlarmConfigCount({});
+    const enabledResp = await service.getAlarmConfigCount({
+      terms: [
+        {
+          column: 'state',
+          value: 'enabled',
+        },
+      ],
+    });
+    const disabledResp = await service.getAlarmConfigCount({
+      terms: [
+        {
+          column: 'state',
+          value: 'disabled',
+        },
+      ],
+    });
+    if (countResp.status === 200) {
+      state.config = countResp.result;
+    }
+    if (enabledResp.status === 200) {
+      state.enabledConfig = enabledResp.result;
+    }
+    if (disabledResp.status === 200) {
+      state.disabledConfig = disabledResp.result;
+    }
+  };
+
+  useEffect(() => {
+    getDashboard();
+    getAlarmConfig();
+  }, []);
+
   const getEcharts = async (params: any) => {
     // 请求数据
     console.log(params);
@@ -121,6 +252,7 @@ const Dashboard = () => {
                       <div>{item.name}</div>
                       <div>{item.product}</div>
                       <div>{item.level}</div>
+                      {JSON.stringify(state)}
                     </div>
                   </li>
                 ))}
@@ -156,5 +288,5 @@ const Dashboard = () => {
       </Card>
     </PageContainer>
   );
-};
+});
 export default Dashboard;

+ 24 - 0
src/pages/rule-engine/DashBoard/service.ts

@@ -0,0 +1,24 @@
+import { request } from '@@/plugin-request/request';
+import SystemConst from '@/utils/const';
+
+class Service {
+  dashboard = (data: Record<string, any>[]) =>
+    request(`/${SystemConst.API_BASE}/dashboard/_multi`, {
+      method: 'POST',
+      data,
+    });
+
+  getAlarm = (params: Record<string, any>) =>
+    request(`/${SystemConst.API_BASE}/alarm/record/_query`, {
+      method: 'GET',
+      params,
+    });
+
+  getAlarmConfigCount = (data: Record<string, any>) =>
+    request(`/${SystemConst.API_BASE}/alarm/config/_count`, {
+      method: 'POST',
+      data,
+    });
+}
+
+export default Service;

+ 1 - 1
src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx

@@ -134,7 +134,7 @@ export default (props: DeviceModelProps) => {
           <SearchComponent<DeviceItem>
             field={columns}
             enableSave={false}
-            // pattern={'simple'}
+            model={'simple'}
             onSearch={async (data) => {
               actionRef.current?.reset?.();
               setSearchParam(data);

+ 1 - 1
src/pages/rule-engine/Scene/TriggerTerm/index.tsx

@@ -219,7 +219,7 @@ const TriggerTerm = (props: Props, ref: any) => {
     const data = props.value;
     data?.trigger?.map((item: { terms: any[] }) =>
       item.terms?.map((j) => {
-        if (typeof j.value.value === 'string') {
+        if (typeof j.value.value !== 'object') {
           j.value.value = [j.value.value];
         }
         return j;

+ 1 - 1
src/pages/system/DataSource/index.tsx

@@ -130,7 +130,7 @@ const DataSource = observer(() => {
         <PermissionButton
           style={{ padding: 0 }}
           type="link"
-          isPermission={userPermission.update}
+          isPermission={userPermission.action}
           key="manage"
           onClick={() => {
             const url = getMenuPathByCode(MENUS_CODE[`system/DataSource/Management`]);

+ 17 - 6
src/pages/system/Platforms/Api/base.tsx

@@ -26,7 +26,14 @@ export const ApiModel = model<{
 
 interface ApiPageProps {
   showDebugger?: boolean;
+  /**
+   * true 只展示已赋权的接口
+   */
   isShowGranted?: boolean;
+  /**
+   * false:table暂时所有接口
+   */
+  isOpenGranted?: boolean;
 }
 
 export default observer((props: ApiPageProps) => {
@@ -60,12 +67,15 @@ export default observer((props: ApiPageProps) => {
     const param = new URLSearchParams(location.search);
     const code = param.get('code');
 
-    service.getApiGranted(code!).then((resp: any) => {
-      if (resp.status === 200) {
-        setGrantKeys(resp.result);
-      }
-    });
-  }, [location]);
+    if (props.isOpenGranted === false) {
+    } else {
+      service.getApiGranted(code!).then((resp: any) => {
+        if (resp.status === 200) {
+          setGrantKeys(resp.result);
+        }
+      });
+    }
+  }, [location, props.isOpenGranted]);
 
   useEffect(() => {
     initModel();
@@ -89,6 +99,7 @@ export default observer((props: ApiPageProps) => {
         <Table
           data={ApiModel.data}
           operations={operations}
+          isOpenGranted={props.isOpenGranted}
           isShowGranted={props.isShowGranted}
           grantKeys={GrantKeys}
         />

+ 6 - 2
src/pages/system/Platforms/Api/basePage.tsx

@@ -10,6 +10,8 @@ interface TableProps {
   // 是否只暂时已授权的接口
   isShowGranted?: boolean;
   //
+  isOpenGranted?: boolean;
+  //
   grantKeys: string[];
 }
 
@@ -48,14 +50,16 @@ export default (props: TableProps) => {
   }, [props.isShowGranted, selectKeys, props.data]);
 
   useEffect(() => {
-    if (!props.isShowGranted) {
+    if (props.isOpenGranted === false) {
+      setDataSource(props.data);
+    } else if (!props.isShowGranted) {
       if (props.data && props.data.length && props.operations) {
         getOperations(props.data, props.operations);
       } else {
         setDataSource([]);
       }
     }
-  }, [props.data, props.operations, props.isShowGranted]);
+  }, [props.data, props.operations, props.isShowGranted, props.isOpenGranted]);
 
   const save = useCallback(async () => {
     const param = new URLSearchParams(location.search);

+ 11 - 0
src/pages/system/Platforms/Setting/index.tsx

@@ -0,0 +1,11 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import ApiPage from '../Api/base';
+
+export default () => {
+  return (
+    <PageContainer>
+      <div>配置系统支持API赋权的范围</div>
+      <ApiPage showDebugger={true} isOpenGranted={false} />
+    </PageContainer>
+  );
+};

+ 1 - 4
src/pages/system/Role/Detail/UserManage/index.tsx

@@ -33,7 +33,6 @@ const UserManage = () => {
       title: '姓名',
       dataIndex: 'name',
       ellipsis: true,
-      align: 'center',
     },
     {
       title: intl.formatMessage({
@@ -42,20 +41,19 @@ const UserManage = () => {
       }),
       align: 'center',
       dataIndex: 'username',
+      ellipsis: true,
     },
     {
       title: '创建时间',
       dataIndex: 'createTime',
       ellipsis: true,
       width: '200px',
-      align: 'center',
       render: (text: any) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
     },
     {
       title: '状态',
       dataIndex: 'status',
       ellipsis: true,
-      align: 'center',
       render: (text, record) => (
         <Badge
           status={record?.status === 1 ? 'success' : 'error'}
@@ -69,7 +67,6 @@ const UserManage = () => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (text, record) => [
         <a key="delete">

+ 2 - 0
src/utils/menu/router.ts

@@ -1,5 +1,6 @@
 /** 路由Code */
 export enum MENUS_CODE {
+  'home' = 'home',
   'Analysis/CPU' = 'Analysis/CPU',
   'Analysis/DeviceChart' = 'Analysis/DeviceChart',
   'Analysis/DeviceMessage' = 'Analysis/DeviceMessage',
@@ -131,6 +132,7 @@ export enum MENUS_CODE {
   'system/Platforms' = 'system/Platforms',
   'system/Platforms/Api' = 'system/Platforms/Api',
   'system/Platforms/View' = 'system/Platforms/View',
+  'system/Platforms/Setting' = 'system/Platforms/Setting',
 }
 
 export type MENUS_CODE_TYPE = keyof typeof MENUS_CODE | string;