Procházet zdrojové kódy

feat(merge): merge xyh

lind před 3 roky
rodič
revize
b84001537e

+ 12 - 14
src/components/DashBoard/echarts.tsx

@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useRef } from 'react';
+import { useEffect, useRef } from 'react';
 import * as echarts from 'echarts/core';
 import type { ECharts, EChartsOption } from 'echarts';
 import {
@@ -57,11 +57,13 @@ export default (props: EchartsProps) => {
   const chartsRef = useRef<any>(null);
 
   const initEcharts = (dom: HTMLDivElement) => {
-    chartsRef.current = echarts.init(dom);
-    if (props.options) {
-      chartsRef.current.setOption(props.options);
-    } else {
-      chartsRef.current.setOption(DefaultOptions);
+    if (!chartsRef.current) {
+      chartsRef.current = echarts.init(dom);
+      if (props.options) {
+        chartsRef.current.setOption(props.options);
+      } else {
+        chartsRef.current.setOption(DefaultOptions);
+      }
     }
   };
 
@@ -72,12 +74,6 @@ export default (props: EchartsProps) => {
     }
   };
 
-  const updateOptions = useCallback(() => {
-    if (chartsRef.current && props.options) {
-      chartsRef.current.setOption(props.options);
-    }
-  }, [props.options]);
-
   useEffect(() => {
     (window as Window).addEventListener('resize', updateSize);
 
@@ -87,8 +83,10 @@ export default (props: EchartsProps) => {
   }, []);
 
   useEffect(() => {
-    updateOptions();
-  }, [props.options, chartsRef.current]);
+    if (chartsRef.current && props.options) {
+      chartsRef.current.setOption(props.options);
+    }
+  }, [props.options]);
 
   return (
     <div

+ 1 - 1
src/components/DashBoard/topCard.tsx

@@ -17,7 +17,7 @@ interface FooterItem {
 
 interface CardItemProps {
   span: number;
-  title: string;
+  title: string | React.ReactNode;
   value: any;
   footer: false | FooterItem[];
   showValue?: boolean;

+ 34 - 31
src/pages/device/Instance/Detail/Diagnose/index.less

@@ -1,37 +1,40 @@
-.header {
-  width: 100%;
-}
-.header-message {
-  width: 100%;
-  background: url('/images/diagnose/back.png') no-repeat;
-  background-size: 100% 100%;
-}
+.diagnose {
+  .header {
+    width: 100%;
+  }
 
-.container {
-  margin-top: 20px;
-}
+  .header-message {
+    width: 100%;
+    background: url('/images/diagnose/back.png') no-repeat;
+    background-size: 100% 100%;
+  }
 
-.item-box {
-  width: 100%;
-  padding: 10px;
-  background-repeat: no-repeat;
-  background-size: 100% 100%;
-  cursor: pointer;
-}
+  .container {
+    margin-top: 20px;
+  }
 
-.item-title {
-  font-weight: 700;
-  font-size: 14px;
-}
+  .item-box {
+    width: 100%;
+    padding: 10px;
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+    cursor: pointer;
+  }
 
-.item-context {
-  height: 40px;
-  font-weight: 700;
-  font-size: 24px;
-}
+  .item-title {
+    font-weight: 700;
+    font-size: 14px;
+  }
+
+  .item-context {
+    height: 40px;
+    font-weight: 700;
+    font-size: 24px;
+  }
 
-.item-message {
-  color: rgba(0, 0, 0, 0.85);
-  font-weight: 400;
-  font-size: 14px;
+  .item-message {
+    color: rgba(0, 0, 0, 0.85);
+    font-weight: 400;
+    font-size: 14px;
+  }
 }

+ 2 - 2
src/pages/device/Instance/Detail/Diagnose/index.tsx

@@ -131,9 +131,9 @@ const Diagnose = () => {
     };
   }, []);
   return (
-    <Card>
+    <Card className="diagnose">
       <div className={current === 'message' ? 'header-message' : 'header'}>
-        <Row gutter={24} style={{ padding: 10 }}>
+        <Row gutter={24} style={{ padding: 10, width: '100%' }}>
           {list.map((item: ListProps) => (
             <Col
               span={8}

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

@@ -70,9 +70,11 @@ const Property = (props: Props) => {
                 />
               </Tooltip>
             )}
-          <Tooltip placement="top" title="获取最新属性值">
-            <SyncOutlined onClick={refreshProperty} />
-          </Tooltip>
+          {data.expands?.type.includes('read') && (
+            <Tooltip placement="top" title="获取最新属性值">
+              <SyncOutlined onClick={refreshProperty} />
+            </Tooltip>
+          )}
           <Tooltip placement="top" title="详情">
             <UnorderedListOutlined
               onClick={() => {

+ 9 - 1
src/pages/device/Instance/Detail/index.tsx

@@ -26,6 +26,7 @@ import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { PermissionButton } from '@/components';
 import { QuestionCircleOutlined } from '@ant-design/icons';
 import Service from '@/pages/device/Instance/service';
+import useLocation from '@/hooks/route/useLocation';
 
 export const deviceStatus = new Map();
 deviceStatus.set('online', <Badge status="success" text={'在线'} />);
@@ -38,6 +39,7 @@ const InstanceDetail = observer(() => {
   const params = useParams<{ id: string }>();
   const service = new Service('device-instance');
   const { permission } = PermissionButton.usePermission('device/Instance');
+  const location = useLocation();
 
   // const resetMetadata = async () => {
   //   const resp = await service.deleteMetadata(params.id);
@@ -155,7 +157,6 @@ const InstanceDetail = observer(() => {
 
   const getDetail = (id: string) => {
     service.detail(id).then((response) => {
-      console.log(response.result);
       InstanceModel.detail = response?.result;
       const datalist = [...baseList];
       if (response.result.protocol === 'modbus-tcp') {
@@ -244,6 +245,13 @@ const InstanceDetail = observer(() => {
     return () => subscription.unsubscribe();
   }, []);
 
+  useEffect(() => {
+    const { state } = location;
+    if (state && state?.tab) {
+      setTab(state?.tab);
+    }
+  }, [location]);
+
   return (
     <PageContainer
       className={'page-title-show'}

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

@@ -93,6 +93,8 @@ const Instance = () => {
       if (location.state && location.state?.save) {
         setVisible(true);
         setCurrent({});
+      } else if (location.state && location.state?.import) {
+        setImportVisible(true);
       }
     }
   }, [location]);

+ 9 - 2
src/pages/device/Product/Detail/index.tsx

@@ -1,11 +1,11 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { useIntl, useLocation, useParams } from 'umi';
+import { useIntl, useParams } from 'umi';
 import { Badge, Card, Descriptions, message, Popconfirm, Space, Spin, Switch, Tooltip } from 'antd';
 import BaseInfo from '@/pages/device/Product/Detail/BaseInfo';
 import { observer } from '@formily/react';
 import { productModel, service } from '@/pages/device/Product';
 import { useCallback, useEffect, useState } from 'react';
-import { useHistory } from '@/hooks';
+import { useHistory, useLocation } from '@/hooks';
 import Metadata from '@/pages/device/components/Metadata';
 import Access from '@/pages/device/Product/Detail/Access';
 import type { DeviceMetadata } from '@/pages/device/Product/typings';
@@ -217,6 +217,13 @@ const ProductDetail = observer(() => {
     return () => subscription.unsubscribe();
   }, []);
 
+  useEffect(() => {
+    const { state } = location;
+    if (state && state?.tab) {
+      setMode(state?.tab);
+    }
+  }, [location]);
+
   return (
     <PageContainer
       className={'page-title-show'}

+ 163 - 0
src/pages/home/components/DeviceChoose.tsx

@@ -0,0 +1,163 @@
+import { message, Modal } from 'antd';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { service } from '@/pages/device/Instance';
+import SearchComponent from '@/components/SearchComponent';
+import type { DeviceItem } from '@/pages/media/Home/typings';
+import { useEffect, useRef, useState } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { BadgeStatus, PermissionButton } from '@/components';
+import { StatusColorEnum } from '@/components/BadgeStatus';
+import useHistory from '@/hooks/route/useHistory';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+
+interface DeviceModalProps {
+  visible: boolean;
+  url?: string;
+  onCancel: () => void;
+}
+
+export default (props: DeviceModalProps) => {
+  const intl = useIntl();
+  const history = useHistory();
+  const permission = PermissionButton.usePermission('device/Instance').permission;
+
+  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 columns: ProColumns<DeviceItem>[] = [
+    {
+      dataIndex: 'id',
+      title: '设备ID',
+      width: 220,
+    },
+    {
+      dataIndex: 'name',
+      title: '设备名称',
+    },
+    {
+      dataIndex: 'productName',
+      title: '产品名称',
+    },
+    {
+      dataIndex: 'modifyTime',
+      title: '注册时间',
+      valueType: 'dateTime',
+      width: 200,
+    },
+    {
+      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={() => {
+        if (deviceItem?.id) {
+          if (!!permission.update) {
+            history.push(
+              `${getMenuPathByParams(MENUS_CODE['device/Instance/Detail'], deviceItem.id)}`,
+              {
+                tab: 'diagnose',
+              },
+            );
+          } else {
+            message.warning('暂无权限,请联系管理员');
+            cancel();
+          }
+        } else {
+          message.warning('请选择设备');
+        }
+      }}
+      destroyOnClose={true}
+      maskClosable={false}
+      visible={props.visible}
+      width={1000}
+    >
+      <SearchComponent<DeviceItem>
+        field={columns}
+        enableSave={false}
+        model="simple"
+        onSearch={async (data) => {
+          setSearchParam(data);
+        }}
+        target="choose-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) => {
+            setDeviceItem(record);
+          },
+        }}
+        tableAlertOptionRender={() => false}
+        params={searchParam}
+      />
+    </Modal>
+  );
+};

+ 0 - 1
src/pages/home/components/Pie.tsx

@@ -17,7 +17,6 @@ const Pie = (props: Props) => {
           type: 'pie',
           radius: ['100%', '50%'],
           center: ['50%', '50%'],
-          width: 80,
           label: {
             show: false,
           },

+ 119 - 0
src/pages/home/components/ProductChoose.tsx

@@ -0,0 +1,119 @@
+import { FormItem, FormLayout, Select } from '@formily/antd';
+import { createForm } from '@formily/core';
+import { createSchemaField, FormProvider } from '@formily/react';
+import { Button, message, Modal } from 'antd';
+import 'antd/lib/tree-select/style/index.less';
+import { useEffect, useState } from 'react';
+import { service } from '@/pages/device/Instance';
+import encodeQuery from '@/utils/encodeQuery';
+import { PermissionButton } from '@/components';
+import useHistory from '@/hooks/route/useHistory';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+
+interface Props {
+  visible: boolean;
+  close: () => void;
+}
+
+const ProductChoose = (props: Props) => {
+  const productPermission = PermissionButton.usePermission('device/Product').permission;
+  const { visible, close } = props;
+  const [productList, setProductList] = useState<any[]>([]);
+
+  const SchemaField = createSchemaField({
+    components: {
+      Select,
+      FormItem,
+      FormLayout,
+    },
+  });
+
+  useEffect(() => {
+    service.getProductList(encodeQuery({ paging: false, terms: { state: 1 } })).then((resp) => {
+      if (resp.status === 200) {
+        const list = resp.result.map((item: { name: any; id: any }) => ({
+          label: item.name,
+          value: item.id,
+        }));
+        setProductList(list);
+      }
+    });
+  }, []);
+
+  const form = createForm({});
+
+  const schema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-component': 'FormLayout',
+        'x-component-props': {
+          layout: 'vertical',
+        },
+        properties: {
+          product: {
+            type: 'string',
+            title: '产品',
+            required: true,
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            enum: [...productList],
+            'x-component-props': {
+              showSearch: true,
+              showArrow: true,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+            },
+          },
+        },
+      },
+    },
+  };
+
+  const history = useHistory();
+
+  return (
+    <Modal
+      maskClosable={false}
+      visible={visible}
+      onCancel={() => close()}
+      width="35vw"
+      title="选择产品"
+      onOk={() => close()}
+      footer={[
+        <Button key="cancel" onClick={() => close()}>
+          取消
+        </Button>,
+        <Button
+          key="ok"
+          type="primary"
+          onClick={async () => {
+            const data: any = await form.submit();
+            const path = getMenuPathByParams(`device/Product/Detail`);
+            if (path && !!productPermission.update) {
+              history.push(
+                `${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], data.product)}`,
+                {
+                  tab: 'access',
+                },
+              );
+            } else {
+              message.warning('暂无权限,请联系管理员');
+              close();
+            }
+          }}
+        >
+          确认
+        </Button>,
+      ]}
+    >
+      <div style={{ marginTop: '20px' }}>
+        <FormProvider form={form}>
+          <SchemaField schema={schema} />
+        </FormProvider>
+      </div>
+    </Modal>
+  );
+};
+export default ProductChoose;

+ 6 - 6
src/pages/home/components/Statistics.tsx

@@ -25,13 +25,13 @@ const Statistics = (props: StatisticsProps) => {
           <div className={'home-guide-item'} key={item.name}>
             <div className={'item-english'}>{item.name}</div>
             <div className={'item-title'}>{item.value}</div>
-            <div className={`item-index`}>
-              {typeof item.children === 'string' ? (
+            {typeof item.children === 'string' ? (
+              <div className={`item-index`}>
                 <img src={item.children || defaultImage} />
-              ) : (
-                item.children
-              )}
-            </div>
+              </div>
+            ) : (
+              <div className={'item-index-echarts'}>{item.children}</div>
+            )}
           </div>
         ))}
       </div>

+ 1 - 1
src/pages/home/components/Steps.tsx

@@ -17,7 +17,7 @@ interface StepsProps {
 const ItemDefaultImg = require('/public/images/home/bottom-1.png');
 const StepsItem = (props: StepItemProps) => {
   return (
-    <div className={'step-item step-bar arrow-1'} onClick={() => props.onClick()}>
+    <div className={'step-item step-bar arrow-1'} onClick={props.onClick}>
       <div className={'step-item-title'}>
         <div className={'step-item-img'}>
           <img src={props.url || ItemDefaultImg} />

+ 15 - 1
src/pages/home/components/index.less

@@ -43,6 +43,14 @@
     right: 10%;
     bottom: 0;
   }
+
+  .item-index-echarts {
+    .item-index;
+
+    right: 12px;
+    bottom: 5%;
+    width: 50%;
+  }
 }
 
 .home-title {
@@ -78,8 +86,8 @@
 .home-body {
   .home-base;
 
-  height: 500px;
   margin-bottom: @margin;
+  padding-bottom: 26.5%;
   overflow: hidden;
 
   .home-body-img {
@@ -87,7 +95,13 @@
     top: 0;
     left: 0;
     z-index: 1;
+    width: 100%;
     height: 100%;
+
+    > img {
+      width: 100%;
+      height: 100%;
+    }
   }
 }
 

+ 169 - 7
src/pages/home/comprehensive/index.tsx

@@ -1,16 +1,19 @@
 import { PermissionButton } from '@/components';
 import useHistory from '@/hooks/route/useHistory';
 import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
-import { Col, message, Row } from 'antd';
+import { Col, message, Row, Tooltip } from 'antd';
 import Body from '../components/Body';
 import Guide from '../components/Guide';
 import Statistics from '../components/Statistics';
-// import Steps from '../components/Steps';
+import Steps from '../components/Steps';
 import { service } from '..';
 import { useEffect, useState } from 'react';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { map } from 'rxjs';
 import Pie from '../components/Pie';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import ProductChoose from '../components/ProductChoose';
+import DeviceChoose from '../components/DeviceChoose';
 
 const Comprehensive = () => {
   const [subscribeTopic] = useSendWebsocketMessage();
@@ -25,6 +28,8 @@ const Comprehensive = () => {
   const [deviceCount, setDeviceCount] = useState<number>(0);
   const [cpuValue, setCpuValue] = useState<number>(0);
   const [jvmValue, setJvmValue] = useState<number>(0);
+  const [productVisible, setProductVisible] = useState<boolean>(false);
+  const [deviceVisible, setDeviceVisible] = useState<boolean>(false);
 
   const getProductCount = async () => {
     const resp = await service.productCount({});
@@ -222,12 +227,169 @@ const Comprehensive = () => {
       <Col span={24}>
         <Body title={'平台架构图'} english={'PLATFORM ARCHITECTURE DIAGRAM'} />
       </Col>
-      {/* <Col span={24}>
-        <Steps />
-      </Col>
       <Col span={24}>
-        <Steps />
-      </Col> */}
+        <Steps
+          title={
+            <span>
+              设备接入推荐步骤
+              <Tooltip title={'不同的设备因为通信协议的不用,存在接入步骤的差异'}>
+                <QuestionCircleOutlined style={{ paddingLeft: 12 }} />
+              </Tooltip>
+            </span>
+          }
+          data={[
+            {
+              title: '创建产品',
+              content:
+                '产品是设备的集合,通常指一组具有相同功能的设备。物联设备必须通过产品进行接入方式配置。',
+              onClick: () => {
+                const path = getMenuPathByCode('device/Product');
+                if (path && !!productPermission.add) {
+                  history.push(`${path}`, {
+                    save: true,
+                  });
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
+            },
+            {
+              title: '配置产品接入方式',
+              content:
+                '通过产品对同一类型的所有设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
+              onClick: () => {
+                setProductVisible(true);
+              },
+            },
+            {
+              title: '添加测试设备',
+              content: '添加单个设备,用于验证产品模型是否配置正确。',
+              onClick: () => {
+                const path = getMenuPathByCode('device/Instance');
+                if (path && !!devicePermission.add) {
+                  history.push(`${path}`, {
+                    save: true,
+                  });
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
+            },
+            {
+              title: '功能调试',
+              content: '对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。',
+              onClick: () => {
+                setDeviceVisible(true);
+              },
+            },
+            {
+              title: '批量添加设备',
+              content: '批量添加同一产品下的设备',
+              onClick: () => {
+                const path = getMenuPathByCode('device/Instance');
+                if (path && !!devicePermission.import) {
+                  history.push(`${path}`, {
+                    import: true,
+                  });
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
+            },
+          ]}
+        />
+      </Col>
+      <Col span={24} style={{ marginTop: 24 }}>
+        <Steps
+          title={
+            <span>
+              运维管理推荐步骤
+              <Tooltip title="请根据业务需要对下述步骤进行选择性操作。">
+                <QuestionCircleOutlined style={{ paddingLeft: 12 }} />
+              </Tooltip>
+            </span>
+          }
+          data={[
+            {
+              title: '协议管理',
+              content: '根据业务需求自定义开发对应的产品(设备模型)接入协议,并上传到平台。',
+              url: require('/public/images/home/bottom-1.png'),
+              onClick: () => {
+                const url = getMenuPathByCode(MENUS_CODE['link/Protocol']);
+                if (!!url) {
+                  history.push(url);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
+            },
+            {
+              title: '证书管理',
+              content: '统一维护平台内的证书,用于数据通信加密。',
+              url: require('/public/images/home/bottom-6.png'),
+              onClick: () => {
+                const url = getMenuPathByCode(MENUS_CODE['link/Certificate']);
+                if (!!url) {
+                  history.push(url);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
+            },
+            {
+              title: '网络组件',
+              content: '根据不同的传输类型配置平台底层网络组件相关参数。',
+              url: require('/public/images/home/bottom-3.png'),
+              onClick: () => {
+                const url = getMenuPathByCode(MENUS_CODE['link/Type']);
+                if (!!url) {
+                  history.push(url);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
+            },
+            {
+              title: '设备接入网关',
+              content: '根据不同的传输类型,关联消息协议,配置设备接入网关相关参数。',
+              url: require('/public/images/home/bottom-4.png'),
+              onClick: () => {
+                const url = getMenuPathByCode(MENUS_CODE['link/Gateway']);
+                if (!!url) {
+                  history.push(url);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
+            },
+            {
+              title: '日志管理',
+              content: '监控系统日志,及时处理系统异常。',
+              url: require('/public/images/home/bottom-5.png'),
+              onClick: () => {
+                const url = getMenuPathByCode(MENUS_CODE['Log']);
+                if (!!url) {
+                  history.push(url);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
+            },
+          ]}
+        />
+      </Col>
+      <ProductChoose
+        visible={productVisible}
+        close={() => {
+          setProductVisible(false);
+        }}
+      />
+      <DeviceChoose
+        visible={deviceVisible}
+        onCancel={() => {
+          setDeviceVisible(false);
+        }}
+      />
     </Row>
   );
 };

+ 53 - 5
src/pages/home/device/index.tsx

@@ -8,6 +8,8 @@ import { useHistory } from '@/hooks';
 import { service } from '..';
 import { useEffect, useState } from 'react';
 import { QuestionCircleOutlined } from '@ant-design/icons';
+import ProductChoose from '../components/ProductChoose';
+import DeviceChoose from '../components/DeviceChoose';
 
 const Device = () => {
   const productPermission = PermissionButton.usePermission('device/Product').permission;
@@ -17,6 +19,9 @@ const Device = () => {
   const [productCount, setProductCount] = useState<number>(0);
   const [deviceCount, setDeviceCount] = useState<number>(0);
 
+  const [productVisible, setProductVisible] = useState<boolean>(false);
+  const [deviceVisible, setDeviceVisible] = useState<boolean>(false);
+
   const getProductCount = async () => {
     const resp = await service.productCount({});
     if (resp.status === 200) {
@@ -134,32 +139,75 @@ const Device = () => {
               title: '创建产品',
               content:
                 '产品是设备的集合,通常指一组具有相同功能的设备。物联设备必须通过产品进行接入方式配置。',
-              onClick: () => {},
+              onClick: () => {
+                const path = getMenuPathByCode('device/Product');
+                if (path && !!productPermission.add) {
+                  history.push(`${path}`, {
+                    save: true,
+                  });
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
             },
             {
               title: '配置产品接入方式',
               content:
                 '通过产品对同一类型的所有设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
-              onClick: () => {},
+              onClick: () => {
+                setProductVisible(true);
+              },
             },
             {
               title: '添加测试设备',
               content: '添加单个设备,用于验证产品模型是否配置正确。',
-              onClick: () => {},
+              onClick: () => {
+                const path = getMenuPathByCode('device/Instance');
+                if (path && !!devicePermission.add) {
+                  history.push(`${path}`, {
+                    save: true,
+                  });
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
             },
             {
               title: '功能调试',
               content: '对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。',
-              onClick: () => {},
+              onClick: () => {
+                setDeviceVisible(true);
+              },
             },
             {
               title: '批量添加设备',
               content: '批量添加同一产品下的设备',
-              onClick: () => {},
+              onClick: () => {
+                const path = getMenuPathByCode('device/Instance');
+                if (path && !!devicePermission.import) {
+                  history.push(`${path}`, {
+                    import: true,
+                  });
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
+              },
             },
           ]}
         />
       </Col>
+      <ProductChoose
+        visible={productVisible}
+        close={() => {
+          setProductVisible(false);
+        }}
+      />
+      <DeviceChoose
+        visible={deviceVisible}
+        onCancel={() => {
+          setDeviceVisible(false);
+        }}
+      />
     </Row>
   );
 };

+ 1 - 1
src/pages/home/index.tsx

@@ -24,7 +24,7 @@ const Home = () => {
         if (resp.result.length == 0) {
           setCurrent('init');
         } else {
-          setCurrent(resp.result[0]?.content);
+          // setCurrent(resp.result[0]?.content);
         }
         // setCurrent('ops');
       }

+ 95 - 71
src/pages/media/Home/index.tsx

@@ -1,5 +1,5 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { Card, Col, message, Row, Tooltip, Typography } from 'antd';
+import { Col, message, Row, Tooltip } from 'antd';
 import { PermissionButton } from '@/components';
 import { getMenuPathByCode } from '@/utils/menu';
 import useHistory from '@/hooks/route/useHistory';
@@ -9,6 +9,8 @@ import Service from './service';
 import { useState } from 'react';
 import DeviceModal from './deviceModal';
 import './index.less';
+import { Body, Guide, Statistics } from '@/pages/home/components';
+import Steps from '@/pages/home/components/Steps';
 
 const permissionTip = '暂无权限,请联系管理员';
 
@@ -19,7 +21,6 @@ export default () => {
   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);
 
@@ -54,14 +55,6 @@ export default () => {
     }
   };
 
-  const jumpCascade = () => {
-    if (cascadeUrl) {
-      history.push(cascadeUrl);
-    } else {
-      message.warning(permissionTip);
-    }
-  };
-
   const jumpChannel = () => {
     if (channelUrl) {
       setVisible(true);
@@ -70,6 +63,31 @@ export default () => {
     }
   };
 
+  const guideList = [
+    {
+      key: 'EQUIPMENT',
+      name: '添加视频设备',
+      english: 'ADD VIDEO EQUIPMENT',
+      auth: !!devicePermission.add,
+      url: deviceUrl,
+      param: { save: true },
+    },
+    {
+      key: 'SCREEN',
+      name: '分屏展示',
+      english: 'SPLIT SCREEN DISPLAY',
+      auth: !!splitScreenUrl,
+      url: splitScreenUrl,
+    },
+    {
+      key: 'CASCADE',
+      name: '国标级联',
+      english: 'GB CASCADE',
+      auth: !!channelUrl,
+      url: channelUrl,
+    },
+  ];
+
   return (
     <PageContainer>
       <DeviceModal
@@ -79,72 +97,78 @@ export default () => {
           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}
+      <Row gutter={24}>
+        <Col span={14}>
+          <Guide title={'视频中心引导'} data={guideList} />
+        </Col>
+        <Col span={10}>
+          <Statistics
+            title={'基础统计'}
+            data={[
+              {
+                name: '设备数量',
+                value: deviceTotal,
+                children: require('/public/images/home/top-1.png'),
+              },
+              {
+                name: '通道数量',
+                value: channelTotal || 0,
+                children: require('/public/images/home/top-2.png'),
+              },
+            ]}
+            extra={
+              <div style={{ fontSize: 14, fontWeight: 400 }}>
+                <a
                   onClick={() => {
-                    history.push(dashBoardUrl);
+                    if (!!dashBoardUrl) {
+                      history.push(`${dashBoardUrl}`);
+                    } else {
+                      message.warning('暂无权限,请联系管理员');
+                    }
                   }}
-                  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>
+                </a>
               </div>
-            </div>
-          </Col>
-        </Row>
-      </Card>
+            }
+          />
+        </Col>
+        <Col span={24}>
+          <Body title={'平台架构图'} english={'PLATFORM ARCHITECTURE DIAGRAM'} />
+        </Col>
+        <Col span={24}>
+          <Steps
+            title={
+              <span>
+                设备接入推荐步骤
+                <Tooltip title={'不同的设备因为通信协议的不用,存在接入步骤的差异'}>
+                  <QuestionCircleOutlined style={{ paddingLeft: 12 }} />
+                </Tooltip>
+              </span>
+            }
+            data={[
+              {
+                title: '添加视频设备',
+                content: '根据视频设备的传输协议,在已创建的产品下添加对应的设备。',
+                onClick: addDevice,
+                url: require('/public/images/home/bottom-6.png'),
+              },
+              {
+                title: '查看通道',
+                content: '查看设备下的通道数据,可以进行直播、录制等操作。',
+                onClick: jumpChannel,
+                url: require('/public/images/home/bottom-7.png'),
+              },
+              {
+                title: '分屏展示',
+                content: '对多个通道的视频流数据进行分屏展示。',
+                onClick: jumpSplitScreen,
+                url: require('/public/images/home/bottom-8.png'),
+              },
+            ]}
+          />
+        </Col>
+      </Row>
     </PageContainer>
   );
 };

+ 5 - 0
src/pages/system/Platforms/Api/base.tsx

@@ -68,6 +68,11 @@ export default observer((props: ApiPageProps) => {
     const code = param.get('code');
 
     if (props.isOpenGranted === false) {
+      service.apiOperations().then((resp: any) => {
+        if (resp.status === 200) {
+          setGrantKeys(resp.result);
+        }
+      });
     } else {
       service.getApiGranted(code!).then((resp: any) => {
         if (resp.status === 200) {

+ 15 - 7
src/pages/system/Platforms/Api/basePage.tsx

@@ -7,7 +7,7 @@ import { ApiModel } from '@/pages/system/Platforms/Api/base';
 interface TableProps {
   data: any;
   operations: string[];
-  // 是否只暂时已授权的接口
+  // 是否只展示已授权的接口
   isShowGranted?: boolean;
   //
   isOpenGranted?: boolean;
@@ -99,13 +99,21 @@ export default (props: TableProps) => {
     grantCache.current = addGrant;
 
     setLoading(true);
-    const resp = await service.addApiGrant(code!, { operations: addOperations });
-    const resp2 = await service.removeApiGrant(code!, { operations: removeOperations });
-    setLoading(false);
-    if (resp.status === 200 || resp2.status === 200) {
-      message.success('操作成功');
+    if (props.isOpenGranted === false) {
+      const resp = await service.apiOperationsAdd(addGrant);
+      const resp2 = removeGrant.length ? await service.apiOperationsRemove(removeGrant) : {};
+      if (resp.status === 200 || resp2.status === 200) {
+        message.success('操作成功');
+      }
+    } else {
+      const resp = await service.addApiGrant(code!, { operations: addOperations });
+      const resp2 = await service.removeApiGrant(code!, { operations: removeOperations });
+      if (resp.status === 200 || resp2.status === 200) {
+        message.success('操作成功');
+      }
     }
-  }, [selectKeys, location, dataSource]);
+    setLoading(false);
+  }, [selectKeys, location, dataSource, props.isOpenGranted]);
 
   return (
     <div className={'platforms-api-table'}>

+ 1 - 1
src/pages/system/Platforms/Setting/index.tsx

@@ -4,7 +4,7 @@ import ApiPage from '../Api/base';
 export default () => {
   return (
     <PageContainer>
-      <div>配置系统支持API赋权的范围</div>
+      <div style={{ padding: 24, background: '#fff' }}>配置系统支持API赋权的范围</div>
       <ApiPage showDebugger={true} isOpenGranted={false} />
     </PageContainer>
   );

+ 18 - 0
src/pages/system/Platforms/service.ts

@@ -68,6 +68,24 @@ class Service extends BaseService<platformsType> {
     request(`/${SystemConst.API_BASE}/api-client/operations`, { method: 'GET' });
 
   /**
+   * 新增可授权的接口ID
+   */
+  apiOperationsAdd = (data?: any) =>
+    request(`/${SystemConst.API_BASE}/api-client/operations/_batch`, {
+      method: 'PATCH',
+      data: data || [],
+    });
+
+  /**
+   * 删除可授权的接口ID
+   */
+  apiOperationsRemove = (data?: any) =>
+    request(`/${SystemConst.API_BASE}/api-client/operations/_batch`, {
+      method: 'DELETE',
+      data: data || [],
+    });
+
+  /**
    * 获取可授权的接口ID
    */
   getSdk = () => request(`${this.uri}/sdk`, { method: 'GET' });