Просмотр исходного кода

Merge branch 'next' into next-xyh

xieyonghong 3 лет назад
Родитель
Сommit
2710c559ce

+ 3 - 0
src/components/FUpload/index.less

@@ -0,0 +1,3 @@
+.ant-upload.ant-upload-select {
+  display: block;
+}

+ 52 - 0
src/components/FUpload/index.tsx

@@ -0,0 +1,52 @@
+import { UploadOutlined } from '@ant-design/icons';
+import SystemConst from '@/utils/const';
+import Token from '@/utils/token';
+import { useState } from 'react';
+import { connect } from '@formily/react';
+import { Input, Upload } from 'antd';
+import type { UploadChangeParam } from 'antd/lib/upload/interface';
+import './index.less';
+
+interface Props {
+  value: any;
+  onChange: (value: any) => void;
+  placeholder: string;
+  beforeUpload: any;
+}
+
+const FUpload = connect((props: Props) => {
+  const [url, setUrl] = useState<any>(props?.value?.url);
+
+  const handleChange = (info: UploadChangeParam) => {
+    if (info.file.status === 'done') {
+      const result = info.file.response?.result;
+      const f = {
+        ...result,
+        url: `${location.protocol}://${SystemConst.API_BASE}/file/${result?.id}?accessKey=${result?.others?.accessKey}`,
+      };
+      setUrl(f.url);
+      props.onChange(f);
+    }
+  };
+
+  return (
+    <Upload
+      beforeUpload={props.beforeUpload}
+      action={`/${SystemConst.API_BASE}/file/upload`}
+      headers={{
+        'X-Access-Token': Token.get(),
+      }}
+      multiple={false}
+      onChange={handleChange}
+      progress={{}}
+    >
+      <Input
+        placeholder={props.placeholder}
+        value={url || ''}
+        readOnly
+        addonAfter={<UploadOutlined />}
+      />
+    </Upload>
+  );
+});
+export default FUpload;

+ 0 - 66
src/pages/device/Firmware/Detail copy/History/index.tsx

@@ -1,66 +0,0 @@
-import type { ProColumns } from '@jetlinks/pro-table';
-import ProTable from '@jetlinks/pro-table';
-import { service, state } from '@/pages/device/Firmware';
-import type { HistoryItem } from '@/pages/device/Firmware/typings';
-import { useParams } from 'umi';
-import { useEffect, useState } from 'react';
-
-const History = () => {
-  const param = useParams<{ id: string }>();
-
-  const [defaultParams, setParams] = useState<Record<string, unknown>>();
-  useEffect(() => {
-    if (state.historyParams) {
-      setParams({ ...state.historyParams });
-    }
-    return () => {
-      state.historyParams = undefined;
-      state.taskItem = undefined;
-    };
-  }, []);
-  const columns: ProColumns<HistoryItem>[] = [
-    {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
-      title: '设备名称',
-      dataIndex: 'deviceName',
-    },
-    {
-      title: '任务名称',
-      dataIndex: 'taskName',
-    },
-    {
-      title: '版本',
-      dataIndex: 'version',
-    },
-    {
-      title: '状态',
-      dataIndex: 'state',
-      renderText: (text) => text.text,
-    },
-    {
-      title: '进度(%)',
-      dataIndex: 'progress',
-    },
-    {
-      title: '创建时间',
-      dataIndex: 'createTime',
-      valueType: 'dateTime',
-    },
-  ];
-  return (
-    <ProTable
-      columns={columns}
-      defaultParams={{
-        ...defaultParams,
-        firmwareId: param.id,
-      }}
-      request={(params) => service.history(params)}
-      rowKey="id"
-    />
-  );
-};
-export default History;

+ 0 - 114
src/pages/device/Firmware/Detail copy/Task/Detail/index.tsx

@@ -1,114 +0,0 @@
-import { Badge, Col, Modal, Row, Statistic } from 'antd';
-import { useEffect, useState } from 'react';
-import { service, state } from '@/pages/device/Firmware';
-import encodeQuery from '@/utils/encodeQuery';
-
-interface Props {
-  visible: boolean;
-  close: () => void;
-}
-
-type TaskState = 'waiting' | 'processing' | 'success' | 'failed';
-const map = {
-  waiting: {
-    status: 'warning',
-    text: '等待升级',
-  },
-  processing: {
-    status: 'processing',
-    text: '升级中',
-  },
-  success: {
-    status: 'success',
-    text: '完成',
-  },
-  failed: {
-    status: 'error',
-    text: '失败',
-  },
-};
-const Detail = (props: Props) => {
-  const [count, setCount] = useState<{
-    waiting: number;
-    processing: number;
-    success: number;
-    failed: number;
-  }>({
-    waiting: 0,
-    processing: 0,
-    success: 0,
-    failed: 0,
-  });
-
-  const getStateCount = (status: TaskState) =>
-    service
-      .historyCount(
-        encodeQuery({
-          terms: {
-            taskId: state.taskItem?.id,
-            state: status,
-          },
-        }),
-      )
-      .then((resp) => {
-        count[`${status}`] = resp.result;
-        setCount({ ...count });
-      });
-
-  useEffect(() => {
-    (['waiting', 'processing', 'success', 'failed'] as TaskState[]).forEach((s) => {
-      if (state.taskItem?.id) {
-        getStateCount(s);
-      }
-    });
-  }, [state.taskItem]);
-
-  return (
-    <Modal
-      maskClosable={false}
-      width="30vw"
-      visible={props.visible}
-      onCancel={() => props.close()}
-      title="任务详情"
-    >
-      <Row gutter={16}>
-        {Object.keys(count)
-          .reduce((previousValue: any[], currentValue) => {
-            previousValue.push({
-              key: currentValue,
-              value: count[currentValue],
-              ...map[currentValue],
-            });
-            return previousValue;
-          }, [])
-          .map((item) => (
-            <Col span={6} key={item.key}>
-              <Statistic
-                title={
-                  <Badge
-                    status={item.status}
-                    text={
-                      <a
-                        onClick={() => {
-                          state.taskDetail = false;
-                          state.tab = 'history';
-                          state.historyParams = {
-                            taskId: state.taskItem?.id,
-                            state: item.key,
-                          };
-                        }}
-                      >
-                        {item.text}
-                      </a>
-                    }
-                  />
-                }
-                value={item.value}
-              />
-            </Col>
-          ))}
-      </Row>
-    </Modal>
-  );
-};
-export default Detail;

+ 0 - 92
src/pages/device/Firmware/Detail copy/Task/Release/index.tsx

@@ -1,92 +0,0 @@
-import { Modal } from 'antd';
-import { createForm } from '@formily/core';
-import { createSchemaField } from '@formily/react';
-import { Form, FormItem, Select } from '@formily/antd';
-import type { ISchema } from '@formily/json-schema';
-import FSelectDevices from '@/components/FSelectDevices';
-import { service, state } from '@/pages/device/Firmware';
-import type { DeviceInstance } from '@/pages/device/Instance/typings';
-import { onlyMessage } from '@/utils/util';
-
-interface Props {
-  close: () => void;
-  visible: boolean;
-}
-
-const Release = (props: Props) => {
-  const form = createForm({
-    validateFirst: true,
-  });
-
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Select,
-      FSelectDevices,
-    },
-  });
-
-  const save = async () => {
-    const values: { releaseType: 'all' | 'part'; part: DeviceInstance[] } = await form.submit();
-    if (!(values.part?.length && values.part?.length <= 0)) {
-      values.releaseType = 'all';
-    }
-    const resp = await service.deploy(
-      state.taskItem!.id,
-      values?.releaseType,
-      values?.part?.map((i) => i.id),
-    );
-    if (resp.status === 200) {
-      onlyMessage('操作成功');
-    } else {
-      onlyMessage('操作失败', 'error');
-    }
-    props.close();
-  };
-
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      releaseType: {
-        title: '发布方式',
-        'x-component': 'Select',
-        'x-decorator': 'FormItem',
-        default: 'all',
-        enum: [
-          { label: '所有设备', value: 'all' },
-          { label: '选择设备', value: 'part' },
-        ],
-      },
-      part: {
-        title: '选择设备',
-        'x-decorator': 'FormItem',
-        'x-component': 'FSelectDevices',
-        'x-visible': false,
-        'x-reactions': {
-          dependencies: ['.releaseType'],
-          fulfill: {
-            state: {
-              visible: '{{$deps[0]==="part"}}',
-            },
-          },
-        },
-      },
-    },
-  };
-  return (
-    <Modal
-      maskClosable={false}
-      title="发布任务"
-      onOk={save}
-      visible={props.visible}
-      onCancel={() => {
-        props.close();
-      }}
-    >
-      <Form form={form}>
-        <SchemaField schema={schema} />
-      </Form>
-    </Modal>
-  );
-};
-export default Release;

+ 0 - 86
src/pages/device/Firmware/Detail copy/Task/Save/index.tsx

@@ -1,86 +0,0 @@
-import { Modal } from 'antd';
-import { service, state } from '../../..';
-import { createForm } from '@formily/core';
-import { createSchemaField } from '@formily/react';
-import { Form, FormItem, Input, NumberPicker, Select } from '@formily/antd';
-import type { ISchema } from '@formily/json-schema';
-import { onlyMessage } from '@/utils/util';
-
-interface Props {
-  visible: boolean;
-  close: () => void;
-}
-
-const Save = (props: Props) => {
-  const form = createForm({
-    validateFirst: true,
-  });
-
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Input,
-      Select,
-      NumberPicker,
-    },
-  });
-
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      name: {
-        title: '名称',
-        'x-component': 'Input',
-        'x-decorator': 'FormItem',
-      },
-      timeoutSeconds: {
-        title: '超时时间',
-        'x-component': 'NumberPicker',
-        'x-decorator': 'FormItem',
-      },
-      mode: {
-        title: '推送方式',
-        'x-component': 'Select',
-        'x-decorator': 'FormItem',
-        enum: [
-          { label: '平台推送', value: 'push' },
-          { label: '设备拉取', value: 'pull' },
-        ],
-      },
-      description: {
-        title: '名称',
-        'x-component': 'Input.TextArea',
-        'x-decorator': 'FormItem',
-      },
-    },
-  };
-
-  const save = async () => {
-    const values: Record<string, unknown> = await form.submit();
-    // 判断current 没有数据应该回退上一页
-    values.productId = state.current?.productId;
-    values.firmwareId = state.current?.id;
-    const resp = await service.saveTask(values);
-    if (resp.status === 200) {
-      onlyMessage('操作成功');
-    } else {
-      onlyMessage('操作失败', 'error');
-    }
-    props.close();
-  };
-  return (
-    <Modal
-      maskClosable={false}
-      onOk={save}
-      width="40vw"
-      visible={props.visible}
-      onCancel={() => props.close()}
-      title="新建任务"
-    >
-      <Form form={form} labelCol={5} wrapperCol={16}>
-        <SchemaField schema={schema} />
-      </Form>
-    </Modal>
-  );
-};
-export default Save;

+ 0 - 136
src/pages/device/Firmware/Detail copy/Task/index.tsx

@@ -1,136 +0,0 @@
-import type { ProColumns } from '@jetlinks/pro-table';
-import ProTable from '@jetlinks/pro-table';
-import type { TaskItem } from '@/pages/device/Firmware/typings';
-import { service, state } from '@/pages/device/Firmware';
-import { Button, Tooltip } from 'antd';
-import {
-  CloudDownloadOutlined,
-  DeleteOutlined,
-  EyeOutlined,
-  PieChartOutlined,
-  PlusOutlined,
-} from '@ant-design/icons';
-import { useIntl, useParams } from 'umi';
-import Save from './Save';
-import { observer } from '@formily/react';
-import Release from './Release';
-import Detail from './Detail';
-
-const Task = observer(() => {
-  const intl = useIntl();
-  const param = useParams<{ id: string }>();
-  const columns: ProColumns<TaskItem>[] = [
-    {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
-      dataIndex: 'id',
-      title: 'id',
-      width: 200,
-    },
-    {
-      dataIndex: 'name',
-      title: '任务名称',
-    },
-    {
-      dataIndex: 'mode',
-      title: '升级方式',
-      renderText: (text) => text.text,
-    },
-    {
-      dataIndex: 'timeoutSeconds',
-      title: '超时时间(秒)',
-    },
-    {
-      dataIndex: 'createTime',
-      valueType: 'dateTime',
-      title: '创建时间',
-    },
-    {
-      title: '操作',
-      valueType: 'option',
-      align: 'center',
-      render: (text, record) => [
-        <a
-          key="cat"
-          onClick={() => {
-            state.task = true;
-          }}
-        >
-          <Tooltip title="查看">
-            <EyeOutlined />
-          </Tooltip>
-        </a>,
-        <a
-          key="task"
-          onClick={() => {
-            state.release = true;
-            state.taskItem = record;
-          }}
-        >
-          <Tooltip title="下发任务">
-            <CloudDownloadOutlined />
-          </Tooltip>
-        </a>,
-        <a
-          key="detail"
-          onClick={() => {
-            state.taskDetail = true;
-            state.taskItem = record;
-          }}
-        >
-          <Tooltip title="任务详情">
-            <PieChartOutlined />
-          </Tooltip>
-        </a>,
-        <a key="remove">
-          <Tooltip title="删除">
-            <DeleteOutlined />
-          </Tooltip>
-        </a>,
-      ],
-    },
-  ];
-  return (
-    <>
-      <ProTable
-        columns={columns}
-        rowKey="id"
-        defaultParams={{
-          firmwareId: param.id,
-        }}
-        toolBarRender={() => [
-          <Button onClick={() => {}} key="button" icon={<PlusOutlined />} type="primary">
-            {intl.formatMessage({
-              id: 'pages.data.option.add',
-              defaultMessage: '新增',
-            })}
-          </Button>,
-        ]}
-        request={async (params) => service.task(params)}
-      />
-      <Save
-        close={() => {
-          state.task = false;
-        }}
-        visible={state.task}
-      />
-      <Release
-        close={() => {
-          state.release = false;
-        }}
-        visible={state.release}
-      />
-      <Detail
-        visible={state.taskDetail}
-        close={() => {
-          state.taskDetail = false;
-          state.taskItem = undefined;
-        }}
-      />
-    </>
-  );
-});
-export default Task;

+ 0 - 68
src/pages/device/Firmware/Detail copy/index.tsx

@@ -1,68 +0,0 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import { history, useParams } from 'umi';
-import { Descriptions } from 'antd';
-import { service, state } from '@/pages/device/Firmware';
-import History from './History';
-import { useEffect, useState } from 'react';
-import type { FirmwareItem } from '@/pages/device/Firmware/typings';
-import Task from './Task';
-import { observer } from '@formily/react';
-
-const Detail = observer(() => {
-  const [data, setData] = useState<FirmwareItem | undefined>(state.current);
-  const param = useParams<{ id: string }>();
-  useEffect(() => {
-    if (!state.current) {
-      service.detail(param.id).then((resp) => {
-        if (resp.status === 200) {
-          setData(resp.result);
-        }
-      });
-    }
-  }, [param.id]);
-
-  const list = [
-    {
-      key: 'task',
-      tab: '升级任务',
-      component: <Task />,
-    },
-    {
-      key: 'history',
-      tab: '升级记录',
-      component: <History />,
-    },
-  ];
-  return (
-    <PageContainer
-      tabActiveKey={state.tab}
-      onBack={history.goBack}
-      onTabChange={(key) => {
-        state.tab = key as 'task' | 'history';
-      }}
-      content={
-        <>
-          <Descriptions size="small" column={3}>
-            {[
-              { key: 'ID', value: data?.id },
-              { key: '所属产品', value: data?.productName },
-              { key: '版本号', value: data?.version },
-              { key: '版本序号', value: data?.versionOrder },
-              { key: '签名方式', value: data?.signMethod },
-              { key: '签名', value: data?.sign },
-            ].map((item) => (
-              <Descriptions.Item key={item.key} label={item.key}>
-                {item.value}
-              </Descriptions.Item>
-            ))}
-          </Descriptions>
-        </>
-      }
-      title={<>固件: {state.current?.name}</>}
-      tabList={list}
-    >
-      {list.find((k) => k.key === state.tab)?.component}
-    </PageContainer>
-  );
-});
-export default Detail;

+ 72 - 23
src/pages/device/Firmware/Save/index.tsx

@@ -3,12 +3,12 @@ import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import { createSchemaField } from '@formily/react';
 import { Form, FormGrid, FormItem, Input, Select, ArrayTable } from '@formily/antd';
 import type { Field } from '@formily/core';
+import { onFieldValueChange, onFormInit } from '@formily/core';
 import { createForm } from '@formily/core';
 import type { ISchema } from '@formily/json-schema';
-import FUpload from '@/components/Upload';
+import FUpload from '@/components/FUpload';
 import { action } from '@formily/reactive';
 import { service } from '@/pages/device/Firmware';
-import type { Response } from '@/utils/typings';
 import { useRef } from 'react';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import { onlyMessage } from '@/utils/util';
@@ -21,13 +21,30 @@ interface Props {
 
 const Save = (props: Props) => {
   const { data, close, visible } = props;
+  const fileInfo = useRef<any>({});
+  const signMethod = useRef<'md5' | 'sha256'>('md5');
 
   const form = createForm({
     validateFirst: true,
     initialValues: data,
+    effects: () => {
+      onFormInit(async (form1) => {
+        if (!data?.id) return;
+        form1.setInitialValues({ ...data, upload: { url: data?.url } });
+      });
+      onFieldValueChange('signMethod', (field) => {
+        const value = (field as Field).value;
+        signMethod.current = value;
+      });
+      onFieldValueChange('upload', (field) => {
+        const value = (field as Field).value;
+        fileInfo.current = value;
+      });
+    },
   });
 
   const products = useRef<ProductItem[]>([]);
+
   const useAsyncDataSource = (services: (arg0: Field) => Promise<any>) => (field: Field) => {
     field.loading = true;
     services(field).then(
@@ -51,14 +68,19 @@ const Save = (props: Props) => {
   });
 
   const save = async () => {
-    const values: FirmwareItem = await form.submit();
+    const values: any = await form.submit();
     const product = products.current?.find((item) => item.id === values.productId);
     values.productName = product?.name || '';
-    const resp = (await service.save(values)) as Response<FirmwareItem>;
+    const { upload, ...extra } = values;
+    const params = {
+      ...extra,
+      url: upload.url || data?.url,
+      size: upload.length || data?.size,
+    };
+    const resp = (await service.update(params)) as any;
     if (resp.status === 200) {
       onlyMessage('保存成功!');
-    } else {
-      onlyMessage('保存失败!', 'error');
+      close();
     }
   };
   const schema: ISchema = {
@@ -162,8 +184,8 @@ const Save = (props: Props) => {
             'x-decorator': 'FormItem',
             'x-component': 'Select',
             enum: [
-              { label: 'MD5', value: 'MD5' },
-              { label: 'SHA256', value: 'SHA256' },
+              { label: 'MD5', value: 'md5' },
+              { label: 'SHA256', value: 'sha256' },
             ],
             'x-component-props': {
               placeholder: '请选择签名方式',
@@ -196,14 +218,38 @@ const Save = (props: Props) => {
                 required: true,
                 message: '请输入签名',
               },
+              // {
+              //   validator: (value: string) => {
+              //     return new Promise((resolve, reject) => {
+              //       if (value !== '' && signMethod.current && fileInfo.current[signMethod.current]) {
+              //         if (value !== fileInfo.current[signMethod.current]) {
+              //           return reject(new Error('签名不一致,请检查文件是否上传正确'));
+              //         }
+              //       }
+              //       return resolve('');
+              //     });
+              //   },
+              // },
+            ],
+            'x-reactions': [
+              {
+                dependencies: ['.upload', 'signMethod'],
+                fulfill: {
+                  state: {
+                    selfErrors:
+                      '{{$deps[0] && $deps[1] && $deps[0][$deps[1]] && $self.value && $self.value !== $deps[0][$deps[1]] ? "签名不一致,请检查文件是否上传正确" : ""}}',
+                  },
+                },
+              },
             ],
           },
-          '{url,size}': {
+          upload: {
             title: '文件上传',
             'x-decorator': 'FormItem',
             'x-component': 'FUpload',
             'x-component-props': {
               type: 'file',
+              placeholder: '请上传文件',
             },
             'x-decorator-props': {
               gridSpan: 2,
@@ -216,7 +262,7 @@ const Save = (props: Props) => {
               },
             ],
           },
-          array: {
+          properties: {
             type: 'array',
             'x-decorator': 'FormItem',
             'x-component': 'ArrayTable',
@@ -236,7 +282,7 @@ const Save = (props: Props) => {
                   'x-component': 'ArrayTable.Column',
                   'x-component-props': { title: 'KEY' },
                   properties: {
-                    a1: {
+                    id: {
                       type: 'string',
                       'x-decorator': 'Editable',
                       'x-component': 'Input',
@@ -248,7 +294,7 @@ const Save = (props: Props) => {
                   'x-component': 'ArrayTable.Column',
                   'x-component-props': { title: 'VALUE' },
                   properties: {
-                    a2: {
+                    value: {
                       type: 'string',
                       'x-decorator': 'FormItem',
                       'x-component': 'Input',
@@ -285,16 +331,19 @@ const Save = (props: Props) => {
               },
             },
           },
-        },
-        description: {
-          title: '说明',
-          'x-decorator': 'FormItem',
-          'x-component': 'Input.TextArea',
-          'x-component-props': {
-            rows: 3,
-            showCount: true,
-            maxLength: 200,
-            placeholder: '请输入说明',
+          description: {
+            title: '说明',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input.TextArea',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              rows: 3,
+              showCount: true,
+              maxLength: 200,
+              placeholder: '请输入说明',
+            },
           },
         },
       },
@@ -305,7 +354,7 @@ const Save = (props: Props) => {
     <Modal
       maskClosable={false}
       width="50vw"
-      title="新增"
+      title={data?.id ? '编辑' : '新增'}
       onCancel={() => close()}
       onOk={() => save()}
       visible={visible}

+ 3 - 10
src/pages/device/Firmware/Task/index.tsx

@@ -1,7 +1,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { Button, Popconfirm, Tooltip } from 'antd';
+import { Popconfirm, Tooltip } from 'antd';
 import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { EditOutlined, EyeOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons';
@@ -69,7 +69,8 @@ const Task = observer(() => {
       render: (text, record) => [
         <Link
           onClick={() => {
-            state.current = record;
+            const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task/Detail'], '123');
+            history.push(url);
           }}
           to={`/device/firmware/detail/${record.id}`}
           key="link"
@@ -163,14 +164,6 @@ const Task = observer(() => {
                 defaultMessage: '新增',
               })}
             </PermissionButton>
-            <Button
-              onClick={() => {
-                const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task/Detail'], '123');
-                history.push(url);
-              }}
-            >
-              详情
-            </Button>
           </div>
         }
         request={async (params) =>

+ 40 - 67
src/pages/device/Firmware/index.tsx

@@ -1,18 +1,17 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { Button, Popconfirm, Tooltip } from 'antd';
+import { Popconfirm } from 'antd';
 import moment from 'moment';
 import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { EditOutlined, EyeOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons';
-import { Link, useHistory } from 'umi';
+import { DeleteOutlined, EditOutlined, NodeExpandOutlined, PlusOutlined } from '@ant-design/icons';
+import { useHistory } from 'umi';
 import { model } from '@formily/reactive';
 import { observer } from '@formily/react';
-import type { FirmwareItem, TaskItem } from '@/pages/device/Firmware/typings';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import Service from '@/pages/device/Firmware/service';
 import Save from '@/pages/device/Firmware/Save';
-import { onlyMessage } from '@/utils/util';
 import { PermissionButton } from '@/components';
 import useDomFullHeight from '@/hooks/document/useDomFullHeight';
 import usePermissions from '@/hooks/permission';
@@ -24,18 +23,8 @@ export const service = new Service('firmware');
 export const state = model<{
   current?: FirmwareItem;
   visible: boolean;
-  task: boolean;
-  release: boolean;
-  taskItem?: TaskItem;
-  taskDetail: boolean;
-  tab: 'task' | 'history';
-  historyParams?: Record<string, unknown>;
 }>({
   visible: false,
-  task: false,
-  release: false,
-  taskDetail: false,
-  tab: 'task',
 });
 const Firmware = observer(() => {
   const actionRef = useRef<ActionType>();
@@ -109,67 +98,56 @@ const Firmware = observer(() => {
       valueType: 'option',
       align: 'center',
       width: 200,
-
       render: (text, record) => [
-        <Link
+        <PermissionButton
+          style={{ padding: 0 }}
+          type="link"
+          isPermission={permission.action}
+          key="upgrade"
           onClick={() => {
-            state.current = record;
+            const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task'], record?.id);
+            history.push(url);
+          }}
+          tooltip={{
+            title: '升级任务',
           }}
-          to={`/device/firmware/detail/${record.id}`}
-          key="link"
         >
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.detail',
-              defaultMessage: '查看',
-            })}
-            key={'detail'}
-          >
-            <EyeOutlined />
-          </Tooltip>
-        </Link>,
-        <a
+          <NodeExpandOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          style={{ padding: 0 }}
+          type="link"
+          isPermission={permission.update}
           key="editable"
           onClick={() => {
             state.visible = true;
+            state.current = record;
           }}
-        >
-          <Tooltip
-            title={intl.formatMessage({
+          tooltip={{
+            title: intl.formatMessage({
               id: 'pages.data.option.edit',
               defaultMessage: '编辑',
-            })}
-          >
-            <EditOutlined />
-          </Tooltip>
-        </a>,
-        <a key="delete">
+            }),
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          type="link"
+          key="delete"
+          style={{ padding: 0 }}
+          isPermission={permission.delete}
+        >
           <Popconfirm
-            title={intl.formatMessage({
-              id: 'pages.data.option.remove.tips',
-              defaultMessage: '确认删除?',
-            })}
             onConfirm={async () => {
               await service.remove(record.id);
-              onlyMessage(
-                intl.formatMessage({
-                  id: 'pages.data.option.success',
-                  defaultMessage: '操作成功!',
-                }),
-              );
               actionRef.current?.reload();
             }}
+            title="确认删除?"
           >
-            <Tooltip
-              title={intl.formatMessage({
-                id: 'pages.data.option.remove',
-                defaultMessage: '删除',
-              })}
-            >
-              <MinusOutlined />
-            </Tooltip>
+            <DeleteOutlined />
           </Popconfirm>
-        </a>,
+        </PermissionButton>,
       ],
     },
   ];
@@ -191,11 +169,13 @@ const Firmware = observer(() => {
         tableStyle={{ minHeight }}
         search={false}
         params={param}
+        columnEmptyText={''}
         headerTitle={
           <div>
             <PermissionButton
               onClick={() => {
                 state.visible = true;
+                state.current = undefined;
               }}
               isPermission={permission.add}
               key="button"
@@ -207,14 +187,6 @@ const Firmware = observer(() => {
                 defaultMessage: '新增',
               })}
             </PermissionButton>
-            <Button
-              onClick={() => {
-                const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task'], '123');
-                history.push(url);
-              }}
-            >
-              升级任务
-            </Button>
           </div>
         }
         request={async (params) =>
@@ -228,6 +200,7 @@ const Firmware = observer(() => {
         visible={state.visible}
         close={() => {
           state.visible = false;
+          actionRef.current?.reload();
         }}
       />
     </PageContainer>

+ 303 - 0
src/pages/device/Instance/Detail/Modbus/channelList.tsx

@@ -0,0 +1,303 @@
+import {
+  FormItem,
+  Input,
+  ArrayTable,
+  Editable,
+  FormButtonGroup,
+  Submit,
+  Select,
+  NumberPicker,
+} from '@formily/antd';
+import { createForm, Field, onFieldReact, FormPath } from '@formily/core';
+import { FormProvider, createSchemaField } from '@formily/react';
+import { Button, Badge } from 'antd';
+
+const Render = (props: any) => <>{props.value}</>;
+const ActionButton = () => {
+  const record = ArrayTable.useRecord?.();
+  const index = ArrayTable.useIndex?.();
+  return (
+    <Button
+      onClick={() => {
+        console.log(record(index));
+      }}
+    >
+      启用
+    </Button>
+  );
+};
+const StatusRender = (props: any) => {
+  switch (props.value?.value) {
+    case 'enabled':
+      return <Badge status="success" text={props.value?.text} />;
+    case 'disabled':
+      return <Badge status="error" text={props.value?.text} />;
+    case 'connect':
+      return <Badge status="success" text={props.value?.text} />;
+    case 'disconnect':
+      return <Badge status="warning" text={props.value?.text} />;
+    default:
+      return <></>;
+  }
+};
+
+const SchemaField = createSchemaField({
+  components: {
+    FormItem,
+    Editable,
+    Input,
+    ArrayTable,
+    Select,
+    NumberPicker,
+    Render,
+    ActionButton,
+    StatusRender,
+  },
+});
+
+const form = createForm({
+  initialValues: {
+    array: [
+      {
+        // a2: '111',
+        a1: 'wendu',
+        // a3: '1111',
+        id: '0718',
+        state: {
+          text: '正常',
+          value: 'enabled',
+        },
+      },
+      {
+        // a2: '2',
+        a1: 'sudu',
+        // a3: '3',
+        id: '0718-1',
+        state: {
+          text: '禁用',
+          value: 'disabled',
+        },
+      },
+    ],
+  },
+  effects: () => {
+    onFieldReact('array.*.a2', (field, f) => {
+      const value = (field as Field).value;
+      const path = FormPath.transform(field.path, /\d+/, (index) => `array.${parseInt(index)}.a3`);
+      console.log(value);
+      f.setFieldState(path, (state) => {
+        if (value) {
+          state.required = true;
+          form.validate();
+        } else {
+          state.required = false;
+        }
+      });
+    });
+  },
+});
+
+const schema = {
+  type: 'object',
+  properties: {
+    array: {
+      type: 'array',
+      'x-decorator': 'FormItem',
+      'x-component': 'ArrayTable',
+      'x-component-props': {
+        pagination: { pageSize: 10 },
+        scroll: { x: '100%' },
+      },
+      items: {
+        type: 'object',
+        properties: {
+          column1: {
+            type: 'void',
+            'x-component': 'ArrayTable.Column',
+            'x-component-props': { width: 120, title: '属性' },
+            properties: {
+              a1: {
+                type: 'string',
+                'x-component': 'Render',
+              },
+            },
+          },
+          column2: {
+            type: 'void',
+            'x-component': 'ArrayTable.Column',
+            'x-component-props': { width: 200, title: '通道' },
+            properties: {
+              a2: {
+                type: 'string',
+                'x-decorator': 'FormItem',
+                'x-component': 'Select',
+                'x-component-props': {
+                  placeholder: '请选择',
+                  showSearch: true,
+                  allowClear: true,
+                  showArrow: true,
+                  filterOption: (input: string, option: any) =>
+                    option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                  onBlur: () => {
+                    const value = form.validate();
+                    console.log(value.array?.value);
+                  },
+                },
+                enum: [
+                  {
+                    label: '通道1',
+                    value: 'channel1',
+                  },
+                  {
+                    label: '通道2',
+                    value: 'channel2',
+                  },
+                ],
+              },
+            },
+          },
+          column3: {
+            type: 'void',
+            'x-component': 'ArrayTable.Column',
+            'x-component-props': { width: 200, title: '数据点名称' },
+            properties: {
+              a3: {
+                type: 'string',
+                'x-decorator': 'FormItem',
+                'x-component': 'Select',
+                'x-validator': {
+                  triggerType: 'onBlur',
+                },
+                'x-component-props': {
+                  placeholder: '请选择',
+                  showSearch: true,
+                  allowClear: true,
+                  showArrow: true,
+                  filterOption: (input: string, option: any) =>
+                    option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                },
+                enum: [
+                  {
+                    label: '名称1',
+                    value: 'name1',
+                  },
+                  {
+                    label: '名称2',
+                    value: 'name2',
+                  },
+                ],
+              },
+            },
+          },
+          column4: {
+            type: 'void',
+            'x-component': 'ArrayTable.Column',
+            'x-component-props': { width: 200, title: '数据点类型' },
+            properties: {
+              a4: {
+                type: 'string',
+                'x-decorator': 'FormItem',
+                'x-component': 'Select',
+                'x-component-props': {
+                  placeholder: '请选择',
+                  showSearch: true,
+                  allowClear: true,
+                  showArrow: true,
+                  filterOption: (input: string, option: any) =>
+                    option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                },
+                enum: [
+                  {
+                    label: '类型1',
+                    value: 'type1',
+                  },
+                  {
+                    label: '类型2',
+                    value: 'type2',
+                  },
+                ],
+              },
+            },
+          },
+          column5: {
+            type: 'void',
+            'x-component': 'ArrayTable.Column',
+            'x-component-props': { width: 200, title: '缩放因子' },
+            properties: {
+              a5: {
+                type: 'string',
+                default: 1,
+                'x-decorator': 'FormItem',
+                'x-component': 'NumberPicker',
+                'x-component-props': {
+                  min: 1,
+                },
+              },
+            },
+          },
+          column6: {
+            type: 'void',
+            'x-component': 'ArrayTable.Column',
+            'x-component-props': { width: 200, title: '数据点说明' },
+            properties: {
+              a6: {
+                type: 'string',
+                'x-decorator': 'FormItem',
+                'x-component': 'Render',
+              },
+            },
+          },
+          column7: {
+            type: 'void',
+            'x-component': 'ArrayTable.Column',
+            'x-component-props': {
+              width: 200,
+              title: '状态',
+              sorter: (a: any, b: any) => a.state.value.length - b.state.value.length,
+            },
+            properties: {
+              state: {
+                type: 'string',
+                'x-decorator': 'FormItem',
+                'x-component': 'StatusRender',
+              },
+            },
+          },
+          column8: {
+            type: 'void',
+            'x-component': 'ArrayTable.Column',
+            'x-component-props': {
+              title: '操作',
+              dataIndex: 'operations',
+              width: 100,
+              fixed: 'right',
+            },
+            properties: {
+              item: {
+                type: 'void',
+                'x-component': 'FormItem',
+                properties: {
+                  remove: {
+                    type: 'void',
+                    'x-component': 'ActionButton',
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  },
+};
+
+export default () => {
+  return (
+    <FormProvider form={form}>
+      <SchemaField schema={schema} />
+      <FormButtonGroup>
+        <Submit onSubmit={console.log}>提交</Submit>
+      </FormButtonGroup>
+    </FormProvider>
+  );
+};

+ 70 - 0
src/pages/device/Instance/Detail/Modbus/editTable.tsx

@@ -0,0 +1,70 @@
+import { Card, Input, Switch } from 'antd';
+// import { useEffect } from 'react';
+import './index.less';
+import { useDomFullHeight } from '@/hooks';
+import ChannelList from './channelList';
+
+const Editable = () => {
+  const { minHeight } = useDomFullHeight('.modbus');
+
+  // const data = [
+  //   {
+  //     "id": "1657787131289",
+  //     "title": "",
+  //     "decs": "这个活动真好玩",
+  //     "state": "open",
+  //     "created_at": "2020-05-26T09:42:56Z"
+  //   },
+  //   {
+  //     "id": "1657787131290",
+  //     "title": "活动名称1",
+  //     "decs": "这个活动真好玩",
+  //     "state": "open",
+  //     "created_at": "2020-05-26T09:42:56Z"
+  //   },
+  // ]
+  // useEffect(() => {
+  //   const id = data.map((item) => item.id)
+  //   // setEditableRowKeys(id)
+  // }, [])
+  return (
+    <Card className="modbus" style={{ minHeight }}>
+      <div className="edit-top">
+        <Switch checkedChildren={'正常采集'} unCheckedChildren={'停止采集'} defaultChecked />
+        <Input.Search
+          placeholder="请输入属性"
+          allowClear
+          style={{ width: 190 }}
+          onSearch={(value) => {
+            console.log(value);
+          }}
+        />
+      </div>
+      <div className="edit-table">
+        <ChannelList></ChannelList>
+        {/* <EditableProTable
+          rowKey="id"
+          columns={columns}
+          recordCreatorProps={false}
+          value={data}
+          editable={{
+            type: 'multiple',
+            editableKeys,
+            onChange: setEditableRowKeys,
+            onValuesChange: (record) => {
+              console.log(record)
+            },
+            actionRender: (row, config, defaultDoms) => {
+              return [
+                <Button onClick={() => {
+                  console.log(row, config)
+                }}>启用</Button>
+              ];
+            },
+          }}
+        /> */}
+      </div>
+    </Card>
+  );
+};
+export default Editable;

+ 10 - 0
src/pages/device/Instance/Detail/Modbus/index.less

@@ -0,0 +1,10 @@
+.edit-top {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.edit-table {
+  .ant-card-body {
+    padding: 12px 0 0 0 !important;
+  }
+}

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

@@ -29,6 +29,7 @@ import Service from '@/pages/device/Instance/service';
 import useLocation from '@/hooks/route/useLocation';
 import { onlyMessage } from '@/utils/util';
 import Parsing from './Parsing';
+// import Editable from './Modbus/editTable';
 
 export const deviceStatus = new Map();
 deviceStatus.set('online', <Badge status="success" text={'在线'} />);
@@ -193,6 +194,7 @@ const InstanceDetail = observer(() => {
           key: 'modbus',
           tab: 'Modbus',
           component: <Modbus />,
+          // component: <Editable />,
         });
       }
       if (response.result.protocol === 'opc-ua') {

+ 28 - 1
src/pages/init-home/index.less

@@ -1,7 +1,7 @@
 .init {
   width: 100%;
   height: 100vh;
-  padding: 64px 128px;
+  padding: 32px 128px 64px 128px;
   overflow: hidden;
   background-image: url('/images/init-home/background.png');
   background-repeat: no-repeat;
@@ -10,6 +10,33 @@
   .box {
     width: 100%;
     height: 100%;
+    padding: 24px;
     background: white;
+
+    .container {
+      display: flex;
+      justify-content: space-between;
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+
+      .left {
+        width: 30px;
+        height: 100%;
+      }
+
+      .right {
+        width: calc(100% - 50px);
+        height: 1200px;
+
+        // .collapseTitle {
+
+        // }
+
+        // .collapseDesc {
+
+        // }
+      }
+    }
   }
 }

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

@@ -1,11 +1,44 @@
 import { TitleComponent } from '@/components';
+import { Collapse, Steps } from 'antd';
 import styles from './index.less';
 
 const InitHome = () => {
+  const text = `
+        A dog is a type of domesticated animal.
+        Known for its loyalty and faithfulness,
+        it can be found as a welcome guest in many households across the world.
+    `;
   return (
     <div className={styles.init}>
       <TitleComponent data={'系统初始化'} />
-      <div className={styles.box}>123</div>
+      <div className={styles.box}>
+        <div className={styles.container}>
+          <div className={styles.left}>
+            <Steps direction="vertical" current={1} percent={60} style={{ height: '100%' }}>
+              <Steps.Step />
+              <Steps.Step />
+              <Steps.Step />
+              <Steps.Step />
+            </Steps>
+          </div>
+          <div className={styles.right}>
+            <Collapse defaultActiveKey={['1', '2', '3', '4']}>
+              <Collapse.Panel header={<div>基本信息</div>} key="1">
+                <p>{text}</p>
+              </Collapse.Panel>
+              <Collapse.Panel header="This is panel header 2" key="2">
+                <p>{text}</p>
+              </Collapse.Panel>
+              <Collapse.Panel header="This is panel header 3" key="3">
+                <p>{text}</p>
+              </Collapse.Panel>
+              <Collapse.Panel header="This is panel header 3" key="4">
+                <p>{text}</p>
+              </Collapse.Panel>
+            </Collapse>
+          </div>
+        </div>
+      </div>
     </div>
   );
 };

+ 274 - 156
src/pages/link/Channel/Modbus/index.tsx

@@ -1,56 +1,96 @@
-import { PageContainer } from '@ant-design/pro-layout';
+import { Badge, Button, Card, Divider, Dropdown, Input, Menu } from 'antd';
+import { useDomFullHeight } from '@/hooks';
+import './index.less';
+import SearchComponent from '@/components/SearchComponent';
 import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Badge, Card, Col, Row } from 'antd';
-import styles from './index.less';
-import { PermissionButton } from '@/components';
-import { history, useIntl } from 'umi';
+import PermissionButton from '@/components/PermissionButton';
 import {
-  ControlOutlined,
   DeleteOutlined,
   EditOutlined,
+  ExportOutlined,
+  ImportOutlined,
   PlayCircleOutlined,
   PlusOutlined,
   StopOutlined,
 } from '@ant-design/icons';
 import { useRef, useState } from 'react';
-import SearchComponent from '@/components/SearchComponent';
+import { useIntl } from 'umi';
+import ChannelCard from '../channelCard';
+import { PageContainer } from '@ant-design/pro-layout';
 import Service from './service';
-import Save from './Save';
-import { getMenuPathByCode } from '@/utils/menu';
-import { useDomFullHeight } from '@/hooks';
-import { onlyMessage } from '@/utils/util';
-// import NewModbus from '../new'
+import SaveChannel from './saveChannel';
+import SavePoint from './savePoint';
 
-export const service = new Service('modbus/master');
+export const service = new Service('');
 
-const Modbus = () => {
+const NewModbus = () => {
+  const { minHeight } = useDomFullHeight(`.modbus`);
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
-  const [param, setParam] = useState({});
   const { permission } = PermissionButton.usePermission('link/Channel/Modbus');
+  const [param, setParam] = useState({});
+  const [activeKey, setActiveKey] = useState<any>('');
   const [visible, setVisible] = useState<boolean>(false);
-  const [current, setCurrent] = useState<Partial<OpaUa>>({});
-  const { minHeight } = useDomFullHeight(`.modbus`, 24);
-
-  const iconMap = new Map();
-  iconMap.set('1', require('/public/images/channel/1.png'));
-  iconMap.set('2', require('/public/images/channel/2.png'));
-  iconMap.set('3', require('/public/images/channel/3.png'));
-  iconMap.set('4', require('/public/images/channel/4.png'));
+  const [visiblePoint, setVisiblePoint] = useState<boolean>(false);
+  const [current, setCurrent] = useState<any>({});
+  const [pointDetail, setPointDetail] = useState<any>({});
+  const data = [
+    {
+      id: 1,
+      status: 'connect',
+      state: {
+        text: '正常',
+        value: 'enabled',
+      },
+    },
+    {
+      id: 2,
+      status: 'disconnect',
+      state: {
+        text: '禁用',
+        value: 'disabled',
+      },
+    },
+  ];
 
-  const columns: ProColumns<OpaUa>[] = [
+  const columns: ProColumns<any>[] = [
     {
-      title: '通道名称',
+      title: '名称',
       dataIndex: 'name',
       ellipsis: true,
+      width: 200,
       fixed: 'left',
     },
     {
-      title: 'IP',
+      title: '功能码',
       dataIndex: 'host',
     },
     {
-      title: '端口',
+      title: '从站ID',
+      dataIndex: 'port',
+      search: false,
+      valueType: 'digit',
+    },
+    {
+      title: '寄存器数量',
+      dataIndex: 'port',
+      search: false,
+      valueType: 'digit',
+    },
+    {
+      title: '地址',
+      dataIndex: 'port',
+      search: false,
+      valueType: 'digit',
+    },
+    {
+      title: '当前数据',
+      dataIndex: 'port',
+      search: false,
+      valueType: 'digit',
+    },
+    {
+      title: '采集状态',
       dataIndex: 'port',
       search: false,
       valueType: 'digit',
@@ -81,15 +121,15 @@ const Modbus = () => {
       title: '操作',
       valueType: 'option',
       align: 'center',
-      width: 200,
+      width: 120,
       fixed: 'right',
       render: (text, record) => [
         <PermissionButton
           isPermission={permission.update}
           key="edit"
           onClick={() => {
-            setVisible(true);
-            setCurrent(record);
+            // setVisible(true);
+            // setCurrent(record);
           }}
           type={'link'}
           style={{ padding: 0 }}
@@ -114,24 +154,24 @@ const Modbus = () => {
               defaultMessage: '确认禁用?',
             }),
             onConfirm: async () => {
-              if (record.state.value === 'disabled') {
-                await service.edit({
-                  ...record,
-                  state: 'enabled',
-                });
-              } else {
-                await service.edit({
-                  ...record,
-                  state: 'disabled',
-                });
-              }
-              onlyMessage(
-                intl.formatMessage({
-                  id: 'pages.data.option.success',
-                  defaultMessage: '操作成功!',
-                }),
-              );
-              actionRef.current?.reload();
+              //   if (record.state.value === 'disabled') {
+              //     await service.edit({
+              //       ...record,
+              //       state: 'enabled',
+              //     });
+              //   } else {
+              //     await service.edit({
+              //       ...record,
+              //       state: 'disabled',
+              //     });
+              //   }
+              //   onlyMessage(
+              //     intl.formatMessage({
+              //       id: 'pages.data.option.success',
+              //       defaultMessage: '操作成功!',
+              //     }),
+              //   );
+              //   actionRef.current?.reload();
             },
           }}
           isPermission={permission.action}
@@ -145,20 +185,6 @@ const Modbus = () => {
           {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         <PermissionButton
-          isPermission={permission.view}
-          style={{ padding: 0 }}
-          key="link"
-          type="link"
-          tooltip={{
-            title: '数据点绑定',
-          }}
-          onClick={() => {
-            history.push(`${getMenuPathByCode('link/Channel/Modbus/Access')}?id=${record.id}`);
-          }}
-        >
-          <ControlOutlined />
-        </PermissionButton>,
-        <PermissionButton
           isPermission={permission.delete}
           style={{ padding: 0 }}
           disabled={record.state.value === 'enabled'}
@@ -166,16 +192,16 @@ const Modbus = () => {
             title: '确认删除',
             disabled: record.state.value === 'enabled',
             onConfirm: async () => {
-              const resp: any = await service.remove(record.id);
-              if (resp.status === 200) {
-                onlyMessage(
-                  intl.formatMessage({
-                    id: 'pages.data.option.success',
-                    defaultMessage: '操作成功!',
-                  }),
-                );
-                actionRef.current?.reload();
-              }
+              //   const resp: any = await service.remove(record.id);
+              //   if (resp.status === 200) {
+              //     onlyMessage(
+              //       intl.formatMessage({
+              //         id: 'pages.data.option.success',
+              //         defaultMessage: '操作成功!',
+              //       }),
+              //     );
+              //     actionRef.current?.reload();
+              //   }
             },
           }}
           key="delete"
@@ -187,93 +213,176 @@ const Modbus = () => {
     },
   ];
 
-  const topCard = [
-    {
-      numeber: '1',
-      title: 'Modbus通道',
-      text: '配置Modbus通道',
-    },
-    {
-      numeber: '2',
-      title: '设备接入网关',
-      text: '创建Modbus设备接入网关',
-    },
-    {
-      numeber: '3',
-      title: '创建产品',
-      text: '创建产品,并选择接入方式为Modbus',
-    },
-    {
-      numeber: '4',
-      title: '添加设备',
-      text: '添加设备,单独为每一个设备进行数据点绑定',
-    },
-  ];
+  const menu = (
+    <Menu>
+      <Menu.Item key="1">
+        <PermissionButton
+          isPermission={permission.export}
+          icon={<ExportOutlined />}
+          type="default"
+          onClick={() => {
+            // setExportVisible(true);
+          }}
+        >
+          批量导出设备
+        </PermissionButton>
+      </Menu.Item>
+      <Menu.Item key="2">
+        <PermissionButton
+          isPermission={permission.import}
+          icon={<ImportOutlined />}
+          onClick={() => {
+            // setImportVisible(true);
+          }}
+        >
+          批量导入设备
+        </PermissionButton>
+      </Menu.Item>
+    </Menu>
+  );
+
   return (
     <PageContainer>
-      {/* <NewModbus/> */}
-      <Card style={{ marginBottom: 10 }}>
-        <Row gutter={[24, 24]}>
-          {topCard.map((item) => (
-            <Col span={6} key={item.numeber}>
-              <Card>
-                <div className={styles.topCard}>
-                  <div>
-                    <img src={iconMap.get(item.numeber)} />
-                  </div>
-                  <div className={styles.text}>
-                    <p className={styles.p1}>{item.title}</p>
-                    <p className={styles.p2}>{item.text}</p>
-                  </div>
-                </div>
-              </Card>
-            </Col>
-          ))}
-        </Row>
+      <Card className="modbus" style={{ minHeight }}>
+        <div className="item">
+          <div className="item-left">
+            <div style={{ width: 220 }}>
+              <Input.Search
+                placeholder="请输入名称"
+                allowClear
+                onSearch={(value) => {
+                  console.log(value);
+                }}
+              />
+              <PermissionButton
+                onClick={() => {
+                  setVisible(true);
+                  setCurrent({});
+                }}
+                isPermission={permission.add}
+                key="add"
+                icon={<PlusOutlined />}
+                type="default"
+                style={{ width: '100%', marginTop: 16 }}
+              >
+                新增
+              </PermissionButton>
+              <div className="item-left-list">
+                {data.map((item) => (
+                  <ChannelCard
+                    active={activeKey === item.id}
+                    data={item}
+                    onClick={() => {
+                      setActiveKey(item.id);
+                    }}
+                    actions={
+                      <>
+                        <PermissionButton
+                          isPermission={permission.update}
+                          key="edit"
+                          onClick={() => {
+                            // setVisible(true);
+                            // setCurrent(record);
+                          }}
+                          type={'link'}
+                          style={{ padding: 0 }}
+                        >
+                          <EditOutlined />
+                          编辑
+                        </PermissionButton>
+                        <Divider type="vertical" />
+                        <PermissionButton
+                          isPermission={permission.update}
+                          key="enbale"
+                          type={'link'}
+                          style={{ padding: 0 }}
+                          popConfirm={{
+                            title: intl.formatMessage({
+                              id: `pages.data.option.${
+                                item.state.value !== 'disabled' ? 'disabled' : 'enabled'
+                              }.tips`,
+                              defaultMessage: '确认禁用?',
+                            }),
+                            onConfirm: async () => {},
+                          }}
+                        >
+                          {item.state.value === 'enabled' ? (
+                            <StopOutlined />
+                          ) : (
+                            <PlayCircleOutlined />
+                          )}
+                          {item.state.value === 'enabled' ? '禁用' : '启用'}
+                        </PermissionButton>
+                        <Divider type="vertical" />
+                        <PermissionButton
+                          isPermission={permission.delete}
+                          style={{ padding: 0 }}
+                          disabled={item.state.value === 'enabled'}
+                          popConfirm={{
+                            title: '确认删除',
+                            disabled: item.state.value === 'enabled',
+                            onConfirm: async () => {},
+                          }}
+                          key="delete"
+                          type="link"
+                        >
+                          <DeleteOutlined />
+                        </PermissionButton>
+                      </>
+                    }
+                  />
+                ))}
+              </div>
+            </div>
+          </div>
+          <div className="item-right">
+            <SearchComponent<any>
+              field={columns}
+              target="modbus"
+              onSearch={(value) => {
+                actionRef.current?.reset?.();
+                setParam(value);
+              }}
+            />
+            <ProTable
+              actionRef={actionRef}
+              params={param}
+              columns={columns}
+              rowKey="id"
+              // scroll={{ x: 1000 }}
+              search={false}
+              headerTitle={
+                <>
+                  <PermissionButton
+                    onClick={() => {
+                      setPointDetail({});
+                      setVisiblePoint(true);
+                    }}
+                    isPermission={permission.add}
+                    key="add"
+                    icon={<PlusOutlined />}
+                    type="primary"
+                    style={{ marginRight: 10 }}
+                  >
+                    {intl.formatMessage({
+                      id: 'pages.data.option.add',
+                      defaultMessage: '新增',
+                    })}
+                  </PermissionButton>
+                  <Dropdown key={'more'} overlay={menu} placement="bottom">
+                    <Button>批量操作</Button>
+                  </Dropdown>
+                </>
+              }
+              // request={async (params) =>
+              //     service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+              // }
+            />
+          </div>
+        </div>
       </Card>
-
-      <SearchComponent<any>
-        field={columns}
-        target="opcua"
-        onSearch={(data) => {
-          // 重置分页数据
-          actionRef.current?.reset?.();
-          setParam(data);
-        }}
-      />
-      <ProTable<OpaUa>
-        actionRef={actionRef}
-        params={param}
-        columns={columns}
-        rowKey="id"
-        scroll={{ x: 1366 }}
-        search={false}
-        tableClassName={'modbus'}
-        tableStyle={{ minHeight }}
-        headerTitle={
-          <PermissionButton
-            onClick={() => {
-              // setMode('add');
-              setVisible(true);
-              setCurrent({});
-            }}
-            isPermission={permission.add}
-            key="add"
-            icon={<PlusOutlined />}
-            type="primary"
-          >
-            {intl.formatMessage({
-              id: 'pages.data.option.add',
-              defaultMessage: '新增',
-            })}
-          </PermissionButton>
-        }
-        request={async (params) =>
-          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
-        }
-      />
       {visible && (
-        <Save
+        <SaveChannel
           data={current}
           close={() => {
             setVisible(false);
@@ -281,7 +390,16 @@ const Modbus = () => {
           }}
         />
       )}
+      {visiblePoint && (
+        <SavePoint
+          data={pointDetail}
+          close={() => {
+            setVisiblePoint(false);
+            actionRef.current?.reload();
+          }}
+        />
+      )}
     </PageContainer>
   );
 };
-export default Modbus;
+export default NewModbus;

+ 152 - 0
src/pages/link/Channel/Modbus/saveChannel.tsx

@@ -0,0 +1,152 @@
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Form, FormGrid, FormItem, Input, NumberPicker, Select } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+// import { service } from '@/pages/link/Channel/Modbus';
+import { Modal } from '@/components';
+import { useEffect } from 'react';
+// import { onlyMessage } from '@/utils/util';
+
+interface Props {
+  data: any;
+  close: () => void;
+  device?: any;
+}
+
+const SaveChannel = (props: Props) => {
+  const form = createForm({
+    validateFirst: true,
+    initialValues: props.data,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      FormGrid,
+      NumberPicker,
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
+        'x-decorator-props': {
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
+        },
+        properties: {
+          name: {
+            title: '名称',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入名称',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入名称',
+              },
+            ],
+          },
+          host: {
+            title: 'IP',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入IP',
+            },
+            'x-validator': ['ipv4'],
+            name: 'host',
+            required: true,
+          },
+          port: {
+            title: '端口',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'NumberPicker',
+            'x-component-props': {
+              placeholder: '请输入端口',
+            },
+            default: 502,
+            'x-validator': [
+              {
+                min: 1,
+                max: 65535,
+                message: '请输入1~65535之间的正整数',
+              },
+            ],
+            name: 'host',
+            required: true,
+          },
+          description: {
+            title: '说明',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input.TextArea',
+            'x-component-props': {
+              rows: 5,
+              placeholder: '请输入说明',
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                max: 200,
+                message: '最多可输入200个字符',
+              },
+            ],
+          },
+        },
+      },
+    },
+  };
+
+  const save = async () => {
+    const value = await form.submit<any>();
+    console.log(value);
+  };
+
+  useEffect(() => {
+    console.log(props.data.id);
+  }, []);
+  return (
+    <Modal
+      title={props.data.id ? '编辑通道' : '新增通道'}
+      maskClosable={false}
+      visible
+      onCancel={props.close}
+      onOk={save}
+      width="35vw"
+      permissionCode={'link/Channel/Modbus'}
+      permission={['add', 'edit']}
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+export default SaveChannel;

+ 159 - 0
src/pages/link/Channel/Modbus/savePoint.tsx

@@ -0,0 +1,159 @@
+import { Col, Form, Input, InputNumber, Modal, Row, Select } from 'antd';
+// import { useEffect, useState } from 'react';
+// import { service } from '@/pages/link/Channel/Modbus';
+// import { onlyMessage } from '@/utils/util';
+
+interface Props {
+  data: any;
+  close: Function;
+}
+
+const SavePoint = (props: Props) => {
+  const [form] = Form.useForm();
+
+  const handleSave = async () => {
+    const formData = await form.validateFields();
+    console.log(formData);
+  };
+
+  return (
+    <Modal
+      title={props.data.id ? '编辑点位' : '新增点位'}
+      visible
+      width="40vw"
+      destroyOnClose
+      onOk={handleSave}
+      onCancel={() => {
+        props.close();
+      }}
+    >
+      <Form
+        form={form}
+        layout="vertical"
+        // initialValues={{
+        //     ...props.data,
+        //     codecConfig: {
+        //         ...props.data?.codecConfig,
+        //         readIndex: props.data?.codecConfig?.readIndex || 0,
+        //         scaleFactor: props.data?.codecConfig?.scaleFactor || 1,
+        //         revertBytes: props.data?.codecConfig?.revertBytes || false,
+        //     },
+        // }}
+      >
+        <Row gutter={[24, 24]}>
+          <Col span={24}>
+            <Form.Item
+              label="名称"
+              name="name"
+              required
+              rules={[{ required: true, message: '名称必填' }]}
+            >
+              <Input placeholder="请输入名称" />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={[24, 24]}>
+          <Col span={12}>
+            <Form.Item
+              label="功能码"
+              name="function"
+              required
+              rules={[{ required: true, message: '功能码必选' }]}
+            >
+              <Select placeholder="请选择">
+                <Select.Option value={'Coils'}>线圈寄存器</Select.Option>
+                <Select.Option value={'HoldingRegisters'}>保存寄存器</Select.Option>
+                <Select.Option value={'InputRegisters'}>输入寄存器</Select.Option>
+              </Select>
+            </Form.Item>
+          </Col>
+          <Col span={12}>
+            <Form.Item
+              label="从站ID"
+              name="unitId"
+              required
+              rules={[
+                { required: true, message: '从站ID' },
+                ({}) => ({
+                  validator(_, value) {
+                    if (value !== 0 || /(^[1-9]\d*$)/.test(value)) {
+                      return Promise.resolve();
+                    }
+                    return Promise.reject(new Error('请输入非0正整数'));
+                  },
+                }),
+              ]}
+            >
+              <InputNumber style={{ width: '100%' }} placeholder="请输入" min={0} />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={[24, 24]}>
+          <Col span={24}>
+            <Form.Item
+              label="寄存器数量"
+              name="quantity"
+              rules={[
+                { required: true, message: '请输入寄存器数量' },
+                ({}) => ({
+                  validator(_, value) {
+                    if (/(^[0-9]\d*$)/.test(value)) {
+                      return Promise.resolve();
+                    }
+                    return Promise.reject(new Error('请输入正整数'));
+                  },
+                }),
+              ]}
+            >
+              <InputNumber style={{ width: '100%' }} placeholder="请输入" min={1} />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={[24, 24]}>
+          <Col span={24}>
+            <Form.Item
+              label="地址"
+              name="address"
+              tooltip="要获取的对象地址"
+              rules={[
+                { required: true, message: '请输入读取长度' },
+                ({}) => ({
+                  validator(_, value) {
+                    if (value > 1 && value < 255) {
+                      return Promise.resolve();
+                    }
+                    return Promise.reject(new Error('请输入1~255之间的数字'));
+                  },
+                }),
+              ]}
+            >
+              <InputNumber style={{ width: '100%' }} placeholder="请输入" min={1} />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row gutter={[24, 24]}>
+          <Col span={24}>
+            <Form.Item
+              label="采集频率"
+              name="interval"
+              tooltip="若不填写表示不定时拉取数据"
+              rules={[
+                ({}) => ({
+                  validator(_, value) {
+                    if (value !== 0 || /(^[1-9]\d*$)/.test(value)) {
+                      return Promise.resolve();
+                    }
+                    return Promise.reject(new Error('请输入正整数'));
+                  },
+                }),
+              ]}
+            >
+              <InputNumber style={{ width: '100%' }} placeholder="请输入" addonAfter={<>ms</>} />
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Modal>
+  );
+};
+export default SavePoint;

+ 118 - 137
src/pages/link/Channel/new.tsx

@@ -1,4 +1,4 @@
-import { Button, Card, Divider, Dropdown, Input, Menu } from 'antd';
+import { Badge, Button, Card, Divider, Dropdown, Input, Menu } from 'antd';
 import { useDomFullHeight } from '@/hooks';
 import './index.less';
 import SearchComponent from '@/components/SearchComponent';
@@ -48,6 +48,7 @@ const NewModbus = () => {
       title: '名称',
       dataIndex: 'name',
       ellipsis: true,
+      width: 200,
       fixed: 'left',
     },
     {
@@ -86,140 +87,120 @@ const NewModbus = () => {
     },
     {
       title: '状态',
-      dataIndex: 'port',
-      search: false,
-      valueType: 'digit',
+      dataIndex: 'state',
+      renderText: (state) => (
+        <Badge text={state?.text} status={state?.value === 'disabled' ? 'error' : 'success'} />
+      ),
+      valueType: 'select',
+      valueEnum: {
+        disabled: {
+          text: intl.formatMessage({
+            id: 'pages.data.option.disabled',
+            defaultMessage: '禁用',
+          }),
+          status: 'disabled',
+        },
+        enabled: {
+          text: '正常',
+          status: 'enabled',
+        },
+      },
+      filterMultiple: false,
+    },
+    {
+      title: '操作',
+      valueType: 'option',
+      align: 'center',
+      width: 120,
+      fixed: 'right',
+      render: (text, record) => [
+        <PermissionButton
+          isPermission={permission.update}
+          key="edit"
+          onClick={() => {
+            // setVisible(true);
+            // setCurrent(record);
+          }}
+          type={'link'}
+          style={{ padding: 0 }}
+          tooltip={{
+            title: intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '编辑',
+            }),
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          type="link"
+          key={'action'}
+          style={{ padding: 0 }}
+          popConfirm={{
+            title: intl.formatMessage({
+              id: `pages.data.option.${
+                record.state.value !== 'disabled' ? 'disabled' : 'enabled'
+              }.tips`,
+              defaultMessage: '确认禁用?',
+            }),
+            onConfirm: async () => {
+              //   if (record.state.value === 'disabled') {
+              //     await service.edit({
+              //       ...record,
+              //       state: 'enabled',
+              //     });
+              //   } else {
+              //     await service.edit({
+              //       ...record,
+              //       state: 'disabled',
+              //     });
+              //   }
+              //   onlyMessage(
+              //     intl.formatMessage({
+              //       id: 'pages.data.option.success',
+              //       defaultMessage: '操作成功!',
+              //     }),
+              //   );
+              //   actionRef.current?.reload();
+            },
+          }}
+          isPermission={permission.action}
+          tooltip={{
+            title: intl.formatMessage({
+              id: `pages.data.option.${record.state.value !== 'disabled' ? 'disabled' : 'enabled'}`,
+              defaultMessage: record.state.value !== 'disabled' ? '禁用' : '启用',
+            }),
+          }}
+        >
+          {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
+        </PermissionButton>,
+        <PermissionButton
+          isPermission={permission.delete}
+          style={{ padding: 0 }}
+          disabled={record.state.value === 'enabled'}
+          popConfirm={{
+            title: '确认删除',
+            disabled: record.state.value === 'enabled',
+            onConfirm: async () => {
+              //   const resp: any = await service.remove(record.id);
+              //   if (resp.status === 200) {
+              //     onlyMessage(
+              //       intl.formatMessage({
+              //         id: 'pages.data.option.success',
+              //         defaultMessage: '操作成功!',
+              //       }),
+              //     );
+              //     actionRef.current?.reload();
+              //   }
+            },
+          }}
+          key="delete"
+          type="link"
+        >
+          <DeleteOutlined />
+        </PermissionButton>,
+      ],
     },
-    // {
-    //   title: '状态',
-    //   dataIndex: 'state',
-    //   renderText: (state) => (
-    //     <Badge text={state?.text} status={state?.value === 'disabled' ? 'error' : 'success'} />
-    //   ),
-    //   valueType: 'select',
-    //   valueEnum: {
-    //     disabled: {
-    //       text: intl.formatMessage({
-    //         id: 'pages.data.option.disabled',
-    //         defaultMessage: '禁用',
-    //       }),
-    //       status: 'disabled',
-    //     },
-    //     enabled: {
-    //       text: '正常',
-    //       status: 'enabled',
-    //     },
-    //   },
-    //   filterMultiple: false,
-    // },
-    // {
-    //   title: '操作',
-    //   valueType: 'option',
-    //   align: 'center',
-    //   width: 200,
-    //   fixed: 'right',
-    //   render: (text, record) => [
-    //     <PermissionButton
-    //       isPermission={permission.update}
-    //       key="edit"
-    //       onClick={() => {
-    //         setVisible(true);
-    //         setCurrent(record);
-    //       }}
-    //       type={'link'}
-    //       style={{ padding: 0 }}
-    //       tooltip={{
-    //         title: intl.formatMessage({
-    //           id: 'pages.data.option.edit',
-    //           defaultMessage: '编辑',
-    //         }),
-    //       }}
-    //     >
-    //       <EditOutlined />
-    //     </PermissionButton>,
-    //     <PermissionButton
-    //       type="link"
-    //       key={'action'}
-    //       style={{ padding: 0 }}
-    //       popConfirm={{
-    //         title: intl.formatMessage({
-    //           id: `pages.data.option.${
-    //             record.state.value !== 'disabled' ? 'disabled' : 'enabled'
-    //           }.tips`,
-    //           defaultMessage: '确认禁用?',
-    //         }),
-    //         onConfirm: async () => {
-    //           if (record.state.value === 'disabled') {
-    //             await service.edit({
-    //               ...record,
-    //               state: 'enabled',
-    //             });
-    //           } else {
-    //             await service.edit({
-    //               ...record,
-    //               state: 'disabled',
-    //             });
-    //           }
-    //           onlyMessage(
-    //             intl.formatMessage({
-    //               id: 'pages.data.option.success',
-    //               defaultMessage: '操作成功!',
-    //             }),
-    //           );
-    //           actionRef.current?.reload();
-    //         },
-    //       }}
-    //       isPermission={permission.action}
-    //       tooltip={{
-    //         title: intl.formatMessage({
-    //           id: `pages.data.option.${record.state.value !== 'disabled' ? 'disabled' : 'enabled'}`,
-    //           defaultMessage: record.state.value !== 'disabled' ? '禁用' : '启用',
-    //         }),
-    //       }}
-    //     >
-    //       {record.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
-    //     </PermissionButton>,
-    //     <PermissionButton
-    //       isPermission={permission.view}
-    //       style={{ padding: 0 }}
-    //       key="link"
-    //       type="link"
-    //       tooltip={{
-    //         title: '数据点绑定',
-    //       }}
-    //       onClick={() => {
-    //         history.push(`${getMenuPathByCode('link/Channel/Modbus/Access')}?id=${record.id}`);
-    //       }}
-    //     >
-    //       <ControlOutlined />
-    //     </PermissionButton>,
-    //     <PermissionButton
-    //       isPermission={permission.delete}
-    //       style={{ padding: 0 }}
-    //       disabled={record.state.value === 'enabled'}
-    //       popConfirm={{
-    //         title: '确认删除',
-    //         disabled: record.state.value === 'enabled',
-    //         onConfirm: async () => {
-    //           const resp: any = await service.remove(record.id);
-    //           if (resp.status === 200) {
-    //             onlyMessage(
-    //               intl.formatMessage({
-    //                 id: 'pages.data.option.success',
-    //                 defaultMessage: '操作成功!',
-    //               }),
-    //             );
-    //             actionRef.current?.reload();
-    //           }
-    //         },
-    //       }}
-    //       key="delete"
-    //       type="link"
-    //     >
-    //       <DeleteOutlined />
-    //     </PermissionButton>,
-    //   ],
-    // },
   ];
 
   const menu = (
@@ -342,9 +323,9 @@ const NewModbus = () => {
           <SearchComponent<any>
             field={columns}
             target="modbus"
-            onSearch={(data1) => {
+            onSearch={(parms) => {
               actionRef.current?.reset?.();
-              setParam(data1);
+              setParam(parms);
             }}
           />
           <ProTable
@@ -352,7 +333,7 @@ const NewModbus = () => {
             params={param}
             columns={columns}
             rowKey="id"
-            scroll={{ x: '60%' }}
+            // scroll={{ x: 1000 }}
             search={false}
             headerTitle={
               <>

+ 9 - 8
src/pages/user/Login/index.tsx

@@ -18,6 +18,7 @@ import Footer from '@/components/Footer';
 const Login: React.FC = () => {
   const [captcha, setCaptcha] = useState<{ key?: string; base64?: string }>({});
   const [bindings, setBindings] = useState<any>([]);
+  const [basis, setBasis] = useState<any>({});
   const { initialState, setInitialState } = useModel('@@initialState');
   const intl = useIntl();
 
@@ -77,19 +78,19 @@ const Login: React.FC = () => {
   useEffect(getCode, []);
 
   useEffect(() => {
-    Service.settingDetail('basis').then((res) => {
-      if (res.status === 200) {
-        console.log(res.result);
-      }
-    });
-  }, []);
-  useEffect(() => {
     localStorage.clear();
     Service.bindInfo().then((res) => {
       if (res.status === 200) {
         setBindings(res.result);
       }
     });
+    Service.settingDetail('basis').then((res) => {
+      if (res.status === 200) {
+        console.log(res.result);
+        const ico: any = document.querySelector('link[rel="icon"]');
+        ico.href = res.result.ico + setBasis(res.result);
+      }
+    });
   }, []);
 
   const SchemaField = createSchemaField({
@@ -252,7 +253,7 @@ const Login: React.FC = () => {
         </div>
         <div className={styles.right}>
           <img
-            src={require('/public/images/login.png')}
+            src={basis.backgroud || require('/public/images/login.png')}
             style={{ width: '100%', height: '100%' }}
           />
           {/*<div className={styles.systemName}>{SystemConst.SYSTEM_NAME}</div>*/}