Lind пре 4 година
родитељ
комит
5b5bfc8aea

+ 13 - 5
src/app.tsx

@@ -138,12 +138,20 @@ export const request: RequestConfig = {
             message: JSON.parse(resp).message,
           });
         } else {
-          response.json().then((res: any) => {
-            notification.error({
-              key: 'error',
-              message: `请求错误:${res.message}`,
+          response
+            .json()
+            .then((res: any) => {
+              notification.error({
+                key: 'error',
+                message: `请求错误:${res.message}`,
+              });
+            })
+            .catch(() => {
+              notification.error({
+                key: 'error',
+                message: '系统错误',
+              });
             });
-          });
         }
       });
       return response;

+ 3 - 0
src/components/BaseCrud/index.tsx

@@ -38,6 +38,7 @@ export type Props<T> = {
   toolBar?: React.ReactNode[];
   pagination?: false | TablePaginationConfig;
   search?: false | SearchConfig;
+  formEffect?: () => void;
 };
 
 const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
@@ -57,6 +58,7 @@ const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
     toolBar,
     pagination,
     search,
+    formEffect,
   } = props;
 
   return (
@@ -114,6 +116,7 @@ const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
         schema={schema}
         schemaConfig={schemaConfig}
         modelConfig={modelConfig}
+        formEffect={formEffect}
       />
     </>
   );

+ 9 - 1
src/components/BaseCrud/save/index.tsx

@@ -14,6 +14,9 @@ import {
   ArrayTable,
   Switch,
   FormGrid,
+  ArrayItems,
+  Space,
+  Radio,
 } from '@formily/antd';
 import { createForm } from '@formily/core';
 import { createSchemaField } from '@formily/react';
@@ -35,11 +38,12 @@ interface Props<T> {
   reload: () => void;
   schemaConfig?: ISchemaFieldProps;
   modelConfig?: ModalProps;
+  formEffect?: () => void;
 }
 
 const Save = <T extends Record<string, any>>(props: Props<T>) => {
   const intl = useIntl();
-  const { service, schema, reload, schemaConfig, modelConfig } = props;
+  const { service, schema, reload, schemaConfig, modelConfig, formEffect } = props;
 
   const [visible, setVisible] = useState<boolean>(false);
   const [current, setCurrent] = useState<T>();
@@ -59,6 +63,7 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
   const form = createForm({
     validateFirst: true,
     initialValues: current,
+    effects: formEffect,
   });
 
   const SchemaField = createSchemaField({
@@ -76,6 +81,9 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
       NumberPicker,
       FUpload,
       FMonacoEditor,
+      ArrayItems,
+      Space,
+      Radio,
     },
     scope: {
       icon(name: any) {

+ 1 - 1
src/locales/zh-CN/pages.ts

@@ -280,7 +280,7 @@ export default {
   'pages.link.component': '网络组件',
   'pages.link.gateway': '设备网关',
   'pages.link.opcua': 'OPC UA',
-  'pages.link.type': 'Type',
+  'pages.link.type': '类型',
   'pages.link.option.debug': '调试',
   // 通知管理
   'pages.notice.config': '通知配置',

+ 297 - 63
src/pages/link/Gateway/index.tsx

@@ -1,28 +1,40 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import type { GatewayItem } from '@/pages/link/Gateway/typings';
-import { useEffect, useRef } from 'react';
+import { useRef } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Tooltip } from 'antd';
+import { message, Popconfirm, Tooltip } from 'antd';
 import {
-  ArrowDownOutlined,
-  BarsOutlined,
-  BugOutlined,
   EditOutlined,
   MinusOutlined,
+  PauseCircleOutlined,
+  PlayCircleOutlined,
+  RedoOutlined,
+  StopOutlined,
 } from '@ant-design/icons';
 import BaseCrud from '@/components/BaseCrud';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { ISchema } from '@formily/json-schema';
+import type { ISchema } from '@formily/json-schema';
 import Service from '@/pages/link/Gateway/service';
-import linkService from '@/pages/link/service';
-import GatewayModel from '@/pages/link/Gateway/model';
+import { CurdModel } from '@/components/BaseCrud/model';
+import type { Field, FieldDataSource, FormPathPattern } from '@formily/core';
+import { action } from '@formily/reactive';
+import { onFieldReact, onFieldValueChange } from '@formily/core';
 
-export const service = new Service('network/config');
+export const service = new Service('gateway/device');
 
 const Gateway = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
 
+  const handleAction = async (id: string, type: 'shutdown' | 'startup' | 'pause') => {
+    const resp = await service.action(id, type);
+    if (resp.status === 200) {
+      message.success('操作成功!');
+    } else {
+      message.error('操作失败');
+    }
+    actionRef.current?.reload();
+  };
   const columns: ProColumns<GatewayItem>[] = [
     {
       dataIndex: 'index',
@@ -31,25 +43,36 @@ const Gateway = () => {
     },
     {
       dataIndex: 'name',
+      sorter: 'true',
+      defaultSortOrder: 'ascend',
       title: intl.formatMessage({
         id: 'pages.table.name',
         defaultMessage: '名称',
       }),
     },
     {
-      dataIndex: 'type',
+      dataIndex: 'provider',
       title: intl.formatMessage({
         id: 'pages.link.type',
         defaultMessage: '类型',
       }),
     },
     {
+      dataIndex: 'networkId',
+      title: '网络组件',
+    },
+    {
       dataIndex: 'state',
       title: intl.formatMessage({
         id: 'pages.searchTable.titleStatus',
         defaultMessage: '状态',
       }),
-      render: (text, record) => record.state.value,
+      render: (text, record) => record.state.text,
+    },
+    {
+      dataIndex: 'createTime',
+      title: '创建时间',
+      valueType: 'dateTime',
     },
     {
       title: intl.formatMessage({
@@ -60,7 +83,12 @@ const Gateway = () => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a onClick={() => console.log(record)}>
+        <a
+          key="edit"
+          onClick={() => {
+            CurdModel.update(record);
+          }}
+        >
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.edit',
@@ -70,71 +98,272 @@ const Gateway = () => {
             <EditOutlined />
           </Tooltip>
         </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.remove',
-              defaultMessage: '删除',
-            })}
-          >
-            <MinusOutlined />
-          </Tooltip>
-        </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.download',
-              defaultMessage: '下载配置',
-            })}
-          >
-            <ArrowDownOutlined />
-          </Tooltip>
-        </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.notice.option.debug',
-              defaultMessage: '调试',
-            })}
-          >
-            <BugOutlined />
-          </Tooltip>
-        </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.record',
-              defaultMessage: '通知记录',
-            })}
-          >
-            <BarsOutlined />
-          </Tooltip>
-        </a>,
+        record.state.value === 'disabled' && (
+          <a key="startup" onClick={() => handleAction(record.id, 'startup')}>
+            <Tooltip title="启动">
+              <PlayCircleOutlined />
+            </Tooltip>
+          </a>
+        ),
+        record.state.value === 'enabled' && (
+          <a key="shutdown" onClick={() => handleAction(record.id, 'shutdown')}>
+            <Tooltip title="停止">
+              <StopOutlined />
+            </Tooltip>
+          </a>
+        ),
+        record.state.value === 'enabled' && (
+          <a key="pause" onClick={() => handleAction(record.id, 'pause')}>
+            <Tooltip title="暂停">
+              <PauseCircleOutlined />
+            </Tooltip>
+          </a>
+        ),
+        record.state.value === 'paused' && (
+          <a key="restart" onClick={() => handleAction(record.id, 'startup')}>
+            <Tooltip title="恢复">
+              <RedoOutlined />
+            </Tooltip>
+          </a>
+        ),
+        record.state.value === 'disabled' && (
+          <a key="delete">
+            <Popconfirm
+              onConfirm={async () => {
+                await service.remove(record.id);
+                message.success(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }}
+              title={'确认删除?'}
+            >
+              <Tooltip
+                title={intl.formatMessage({
+                  id: 'pages.data.option.remove',
+                  defaultMessage: '删除',
+                })}
+              >
+                <MinusOutlined />
+              </Tooltip>
+            </Popconfirm>
+          </a>
+        ),
       ],
     },
   ];
 
-  const getProviders = () => {
-    linkService.getProviders().subscribe((data) => {
-      GatewayModel.provider = data;
+  const getProviders = async () =>
+    service
+      .getProviders()
+      .then((resp) =>
+        resp.result.map((item: any) => ({ label: item.name, value: item.networkType?.value })),
+      );
+
+  const getProtocol = async () =>
+    service.getProtocol().then((resp) =>
+      resp.result.map((item: any) => ({
+        label: item.name,
+        value: item.id,
+      })),
+    );
+
+  const getNetwork = async (id: string) =>
+    service.getNetwork(id).then((resp) =>
+      resp.result.map((item: any) => ({
+        label: item.name,
+        value: item.id,
+      })),
+    );
+
+  const getAsyncData = (pattern: FormPathPattern, service2: (field: Field) => Promise<any>) => {
+    onFieldReact(pattern, (field: any) => {
+      field.loading = true;
+      service2(field).then(
+        action.bound!((resp) => {
+          field.dataSource = resp;
+          field.loading = false;
+        }),
+      );
     });
   };
 
-  useEffect(() => {
-    getProviders();
-  }, []);
+  const formEffect = () => {
+    getAsyncData('networkId', async (field) => {
+      const value = field.query('provider').get('value');
+      if (!value) return [];
+      return getNetwork(value);
+    });
+    onFieldValueChange('provider', async (field) => {
+      const network = field.query('networkId').take() as Field;
+      network.value = undefined;
+    });
+  };
+
+  const useAsyncDataSource =
+    (services: (arg0: Field) => Promise<FieldDataSource>) => (field: Field) => {
+      field.loading = true;
+      services(field).then(
+        action.bound!((resp: any) => {
+          field.dataSource = resp;
+          field.loading = false;
+        }),
+      );
+    };
 
   const schema: ISchema = {
     type: 'object',
     properties: {
-      name: {},
-      type: {
+      name: {
+        title: '名称',
+        'x-component': 'Input',
+        'x-decorator': 'FormItem',
+      },
+      provider: {
         title: '类型',
         'x-component': 'Select',
-        enum: GatewayModel.provider,
+        'x-decorator': 'FormItem',
+        'x-reactions': ['{{useAsyncDataSource(getProviders)}}'],
+      },
+
+      configuration: {
+        type: 'object',
+        properties: {
+          routes: {
+            title: '协议路由',
+            type: 'array',
+            'x-component': 'ArrayItems',
+            'x-decorator': 'FormItem',
+            'x-visible': false,
+            items: {
+              type: 'object',
+              properties: {
+                space: {
+                  type: 'void',
+                  'x-component': 'Space',
+                  properties: {
+                    sort: {
+                      type: 'void',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'ArrayItems.SortHandle',
+                    },
+                    url: {
+                      type: 'string',
+                      title: 'url',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Input',
+                      'x-component-props': {
+                        placeholder: '/**',
+                      },
+                    },
+                    protocol: {
+                      type: 'string',
+                      title: '协议',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Select',
+                      'x-reactions': ['{{useAsyncDataSource(getProtocol)}}'],
+                      'x-component-props': {
+                        style: {
+                          width: '250px',
+                        },
+                      },
+                    },
+                    remove: {
+                      type: 'void',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'ArrayItems.Remove',
+                    },
+                  },
+                },
+              },
+            },
+            properties: {
+              add: {
+                type: 'void',
+                title: '添加条目',
+                'x-component': 'ArrayItems.Addition',
+              },
+            },
+            'x-reactions': {
+              dependencies: ['..provider'],
+              fulfill: {
+                state: {
+                  visible: '{{["WEB_SOCKET_SERVER","HTTP_SERVER"].includes($deps[0])}}',
+                },
+              },
+            },
+          },
+          protocol: {
+            type: 'string',
+            title: '消息协议',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-reactions': [
+              '{{useAsyncDataSource(getProtocol)}}',
+              {
+                dependencies: ['..provider'],
+                fulfill: {
+                  state: {
+                    visible:
+                      '{{["UDP","COAP_SERVER","TCP_SERVER","MQTT_SERVER"].includes($deps[0])}}',
+                    title: '{{$deps[0]==="MQTT_SERVER"?"认证协议":"消息协议"}}',
+                  },
+                },
+              },
+            ],
+          },
+          topics: {
+            type: 'string',
+            title: 'Topics',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input.TextArea',
+            'x-reactions': [
+              {
+                dependencies: ['..provider'],
+                fulfill: {
+                  state: {
+                    visible: '{{["MQTT_CLIENT"].includes($deps[0])}}',
+                  },
+                },
+              },
+            ],
+          },
+          qos: {
+            type: 'string',
+            title: 'Qos',
+            'x-decorator': 'FormItem',
+            'x-component': 'Radio.Group',
+            enum: [0, 1, 2],
+            'x-reactions': [
+              {
+                dependencies: ['..provider'],
+                fulfill: {
+                  state: {
+                    visible: '{{["MQTT_CLIENT"].includes($deps[0])}}',
+                  },
+                },
+              },
+            ],
+          },
+        },
+      },
+
+      networkId: {
+        title: '网络组件',
+        'x-component': 'Select',
+        'x-decorator': 'FormItem',
+      },
+      describe: {
+        title: '描述',
+        'x-component': 'Input.TextArea',
+        'x-decorator': 'FormItem',
+        'x-component-props': {
+          rows: 3,
+        },
       },
-      network: {},
-      description: {},
     },
   };
 
@@ -147,7 +376,12 @@ const Gateway = () => {
           id: 'pages.link.gateway',
           defaultMessage: '设备网关',
         })}
+        modelConfig={{
+          width: '50vw',
+        }}
         schema={schema}
+        formEffect={formEffect}
+        schemaConfig={{ scope: { getProviders, useAsyncDataSource, getProtocol, getNetwork } }}
         actionRef={actionRef}
       />
     </PageContainer>

+ 17 - 2
src/pages/link/Gateway/service.ts

@@ -1,6 +1,21 @@
 import BaseService from '@/utils/BaseService';
-import { GatewayItem } from '@/pages/link/Gateway/typings';
+import type { GatewayItem } from '@/pages/link/Gateway/typings';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
 
-class Service extends BaseService<GatewayItem> {}
+class Service extends BaseService<GatewayItem> {
+  public action = (id: string, type: 'shutdown' | 'startup' | 'pause') =>
+    request(`${this.uri}/${id}/_${type}`, { method: 'POST' });
+
+  public getProviders = () => request(`${this.uri}/providers`, { method: 'GET' });
+
+  public getProtocol = () =>
+    request(`/${SystemConst.API_BASE}/protocol/supports`, { method: 'GET' });
+
+  public getNetwork = (id: string) =>
+    request(`/${SystemConst.API_BASE}/network/config/${id}/_detail`, {
+      method: 'GET',
+    });
+}
 
 export default Service;