瀏覽代碼

feat: merge

xieyonghong 3 年之前
父節點
當前提交
9a769516ec
共有 60 個文件被更改,包括 2441 次插入475 次删除
  1. 11 0
      config/routes.ts
  2. 二進制
      public/images/channel/connect.png
  3. 二進制
      public/images/channel/disconnect.png
  4. 二進制
      public/images/firmware/error.png
  5. 二進制
      public/images/firmware/finish.png
  6. 二進制
      public/images/firmware/loading.png
  7. 二進制
      public/images/firmware/waiting.png
  8. 二進制
      public/images/init-home/background.png
  9. 9 4
      src/app.tsx
  10. 3 1
      src/components/BindParentDevice/index.tsx
  11. 129 0
      src/components/FIndicators/index.tsx
  12. 8 1
      src/components/Upload/Image/index.tsx
  13. 1 0
      src/pages/Northbound/AliCloud/index.tsx
  14. 1 0
      src/pages/Northbound/DuerOS/index.tsx
  15. 1 0
      src/pages/cloud/DuerOS/index.tsx
  16. 0 0
      src/pages/device/Firmware/Detail copy/History/index.tsx
  17. 0 0
      src/pages/device/Firmware/Detail copy/Task/Detail/index.tsx
  18. 0 0
      src/pages/device/Firmware/Detail copy/Task/Release/index.tsx
  19. 0 0
      src/pages/device/Firmware/Detail copy/Task/Save/index.tsx
  20. 3 3
      src/pages/device/Firmware/Detail/Task/index.tsx
  21. 1 1
      src/pages/device/Firmware/Detail/index.tsx
  22. 191 9
      src/pages/device/Firmware/Save/index.tsx
  23. 25 0
      src/pages/device/Firmware/Task/Detail/index.less
  24. 170 0
      src/pages/device/Firmware/Task/Detail/index.tsx
  25. 279 0
      src/pages/device/Firmware/Task/Save/index.tsx
  26. 192 0
      src/pages/device/Firmware/Task/index.tsx
  27. 71 22
      src/pages/device/Firmware/index.tsx
  28. 92 35
      src/pages/device/Instance/Detail/Diagnose/Status/index.tsx
  29. 1 0
      src/pages/device/Instance/index.tsx
  30. 1 1
      src/pages/device/Product/Detail/PropertyImport/index.tsx
  31. 1 0
      src/pages/device/Product/index.tsx
  32. 143 102
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  33. 15 0
      src/pages/init-home/index.less
  34. 13 0
      src/pages/init-home/index.tsx
  35. 2 0
      src/pages/link/Channel/Modbus/index.tsx
  36. 55 0
      src/pages/link/Channel/channelCard.tsx
  37. 81 0
      src/pages/link/Channel/index.less
  38. 390 0
      src/pages/link/Channel/new.tsx
  39. 1 0
      src/pages/link/Protocol/index.tsx
  40. 1 0
      src/pages/link/Type/index.tsx
  41. 1 0
      src/pages/media/Cascade/index.tsx
  42. 1 0
      src/pages/media/Device/index.tsx
  43. 1 0
      src/pages/notice/Config/index.tsx
  44. 1 0
      src/pages/notice/Template/index.tsx
  45. 118 0
      src/pages/rule-engine/Alarm/Config/Save/input.tsx
  46. 155 0
      src/pages/rule-engine/Alarm/Config/Save/output.tsx
  47. 77 175
      src/pages/rule-engine/Alarm/Config/index.tsx
  48. 1 0
      src/pages/rule-engine/Alarm/Configuration/index.tsx
  49. 1 0
      src/pages/rule-engine/Instance/index.tsx
  50. 1 0
      src/pages/rule-engine/Scene/index.tsx
  51. 76 107
      src/pages/system/Basis/index.tsx
  52. 90 0
      src/pages/system/Basis/init.tsx
  53. 1 0
      src/pages/system/Department/Assets/deivce/bind.tsx
  54. 1 0
      src/pages/system/Department/Assets/deivce/index.tsx
  55. 1 0
      src/pages/system/Department/Assets/product/bind.tsx
  56. 1 0
      src/pages/system/Department/Assets/product/index.tsx
  57. 4 4
      src/pages/user/Login/index.less
  58. 12 1
      src/pages/user/Login/index.tsx
  59. 3 0
      src/utils/menu/index.ts
  60. 4 9
      src/utils/menu/router.ts

+ 11 - 0
config/routes.ts

@@ -31,6 +31,17 @@
       },
     ],
   },
+  {
+    path: '/init-home',
+    layout: false,
+    routes: [
+      {
+        name: '初始化',
+        path: '/init-home',
+        component: './init-home',
+      },
+    ],
+  },
   // {
   //   path: '/analysis',
   //   name: 'analysis',

二進制
public/images/channel/connect.png


二進制
public/images/channel/disconnect.png


二進制
public/images/firmware/error.png


二進制
public/images/firmware/finish.png


二進制
public/images/firmware/loading.png


二進制
public/images/firmware/waiting.png


二進制
public/images/init-home/background.png


+ 9 - 4
src/app.tsx

@@ -241,10 +241,15 @@ const MenuItemIcon = (
 // ProLayout 支持的api https://procomponents.ant.design/components/layout
 export const layout: RunTimeLayoutConfig = ({ initialState }) => {
   // console.log({ ...initialState });
-  // let ico:any = document.querySelector('link[rel="icon"]')
-  // if(ico!==null){
-  //   ico.href='https://t9.baidu.com/it/u=2615841454,1925018474&fm=218&app=126&size=f242,150&n=0&f=JPEG&fmt=auto?s=A0B46032495267E15A7D35CE000070A2&sec=1657645200&t=b98ebc7ac3073c248e57894e899d7ac1'
-  // }
+  const ico: any = document.querySelector('link[rel="icon"]');
+  if (ico !== null) {
+    Service.settingDetail('basis').then((res) => {
+      if (res.status === 200) {
+        // console.log(res.result.ico)
+        ico.href = res.result.ico;
+      }
+    });
+  }
   return {
     navTheme: 'light',
     headerTheme: 'light',

+ 3 - 1
src/components/BindParentDevice/index.tsx

@@ -48,7 +48,9 @@ const BindParentDevice = (props: Props) => {
       ellipsis: true,
       width: '200px',
       valueType: 'dateTime',
-      render: (text: any) => (!!text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : '/'),
+      renderText: (text: any) => {
+        return !!text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : '';
+      },
       sorter: true,
     },
     {

+ 129 - 0
src/components/FIndicators/index.tsx

@@ -0,0 +1,129 @@
+import { Checkbox, InputNumber, Space, DatePicker, Input } from 'antd';
+import moment from 'moment';
+
+interface Props {
+  value: any;
+  type: any;
+  onChange: (value: any) => void;
+}
+
+const FIndicators = (props: Props) => {
+  const { value, onChange, type } = props;
+  const DatePicker1: any = DatePicker;
+
+  const renderComponent = () => {
+    if (['int', 'long', 'double', 'float'].includes(type)) {
+      return (
+        <>
+          <InputNumber
+            value={value?.value ? value?.value[0] : ''}
+            onChange={(val) => {
+              onChange({
+                ...value,
+                value: [
+                  value?.range && val < value?.value[1] ? val : value?.value[0],
+                  value?.value[1],
+                ],
+              });
+            }}
+          />
+          {value.range && (
+            <>
+              ~
+              <InputNumber
+                value={value?.value ? value?.value[1] : ''}
+                onChange={(val) => {
+                  onChange({
+                    ...value,
+                    value: [value?.value && value?.value[0], val],
+                  });
+                }}
+              />
+            </>
+          )}
+        </>
+      );
+    } else if (type === 'date') {
+      if (value.range) {
+        return (
+          <DatePicker1.RangePicker
+            allowClear={false}
+            showTime
+            value={
+              value?.value && [
+                !!value?.value[0] && moment(value.value[0], 'YYYY-MM-DD HH:mm:ss'),
+                !!value?.value[1] && moment(value.value[1], 'YYYY-MM-DD HH:mm:ss'),
+              ]
+            }
+            onChange={(_: any, date: string[]) => {
+              onChange({
+                ...value,
+                value: [...date],
+              });
+            }}
+          />
+        );
+      } else {
+        return (
+          <DatePicker1
+            showTime
+            allowClear={false}
+            value={value?.value ? moment(value.value[0], 'YYYY-MM-DD HH:mm:ss') : ''}
+            onChange={(_: any, date: string) => {
+              onChange({
+                ...value,
+                value: [date],
+              });
+            }}
+          />
+        );
+      }
+    } else {
+      return (
+        <>
+          <Input
+            value={value?.value ? value?.value[0] : ''}
+            onChange={(val) => {
+              onChange({
+                ...value,
+                value: [val, value?.value && value?.value[1]],
+              });
+            }}
+          />
+          {value.range && (
+            <>
+              ~
+              <Input
+                value={value?.value ? value?.value[1] : ''}
+                onChange={(val) => {
+                  onChange({
+                    ...value,
+                    value: [value?.value && value?.value[0], val],
+                  });
+                }}
+              />
+            </>
+          )}
+        </>
+      );
+    }
+  };
+  return (
+    <Space align="baseline">
+      {renderComponent()}
+      <Checkbox
+        style={{ minWidth: 60 }}
+        checked={value?.range}
+        onChange={(e) => {
+          onChange({
+            ...value,
+            range: e.target.checked,
+          });
+        }}
+      >
+        范围
+      </Checkbox>
+    </Space>
+  );
+};
+export default FIndicators;

+ 8 - 1
src/components/Upload/Image/index.tsx

@@ -20,6 +20,7 @@ interface UploadImageProps {
    */
   size?: number;
   style?: React.CSSProperties;
+  backgroundSize?: string;
 }
 
 export default ({ onChange, value, ...extraProps }: UploadImageProps) => {
@@ -76,7 +77,13 @@ export default ({ onChange, value, ...extraProps }: UploadImageProps) => {
             {values ? (
               <>
                 {/*<img width={120} height={120} src={values} />*/}
-                <div className={'upload-image'} style={{ backgroundImage: `url(${values})` }} />
+                <div
+                  className={'upload-image'}
+                  style={{
+                    backgroundImage: `url(${values})`,
+                    backgroundSize: extraProps.backgroundSize,
+                  }}
+                />
                 <div className={'upload-image-mask'}>点击修改</div>
               </>
             ) : (

+ 1 - 0
src/pages/Northbound/AliCloud/index.tsx

@@ -206,6 +206,7 @@ const AliCloud = () => {
         search={false}
         scroll={{ x: 1366 }}
         columns={columns}
+        columnEmptyText={''}
         actionRef={actionRef}
         params={searchParams}
         options={{ fullScreen: true }}

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

@@ -241,6 +241,7 @@ export default () => {
         columns={columns}
         actionRef={actionRef}
         params={searchParams}
+        columnEmptyText={''}
         scroll={{ x: 1366 }}
         options={{ fullScreen: true }}
         request={(params) =>

+ 1 - 0
src/pages/cloud/DuerOS/index.tsx

@@ -102,6 +102,7 @@ const DuerOS = () => {
         search={false}
         params={param}
         columns={columns}
+        columnEmptyText={''}
         request={(params) =>
           service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
         }

src/pages/device/Firmware/Detail/History/index.tsx → src/pages/device/Firmware/Detail copy/History/index.tsx


src/pages/device/Firmware/Detail/Task/Detail/index.tsx → src/pages/device/Firmware/Detail copy/Task/Detail/index.tsx


src/pages/device/Firmware/Detail/Task/Release/index.tsx → src/pages/device/Firmware/Detail copy/Task/Release/index.tsx


src/pages/device/Firmware/Detail/Task/Save/index.tsx → src/pages/device/Firmware/Detail copy/Task/Save/index.tsx


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

@@ -11,10 +11,10 @@ import {
   PlusOutlined,
 } from '@ant-design/icons';
 import { useIntl, useParams } from 'umi';
-import Save from '@/pages/device/Firmware/Detail/Task/Save';
+import Save from './Save';
 import { observer } from '@formily/react';
-import Release from '@/pages/device/Firmware/Detail/Task/Release';
-import Detail from '@/pages/device/Firmware/Detail/Task/Detail';
+import Release from './Release';
+import Detail from './Detail';
 
 const Task = observer(() => {
   const intl = useIntl();

+ 1 - 1
src/pages/device/Firmware/Detail/index.tsx

@@ -5,7 +5,7 @@ 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 '@/pages/device/Firmware/Detail/Task';
+import Task from './Task';
 import { observer } from '@formily/react';
 
 const Detail = observer(() => {

+ 191 - 9
src/pages/device/Firmware/Save/index.tsx

@@ -1,7 +1,7 @@
 import { Modal } from 'antd';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import { createSchemaField } from '@formily/react';
-import { Form, FormGrid, FormItem, Input, Select } from '@formily/antd';
+import { Form, FormGrid, FormItem, Input, Select, ArrayTable } from '@formily/antd';
 import type { Field } from '@formily/core';
 import { createForm } from '@formily/core';
 import type { ISchema } from '@formily/json-schema';
@@ -46,6 +46,7 @@ const Save = (props: Props) => {
       Input,
       FUpload,
       Select,
+      ArrayTable,
     },
   });
 
@@ -71,26 +72,90 @@ const Save = (props: Props) => {
           maxColumns: 2,
         },
         properties: {
-          productId: {
-            title: '产品',
-            'x-decorator': 'FormItem',
-            'x-component': 'Select',
-            'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
-          },
           name: {
             title: '名称',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入固件名称',
+            },
+            required: true,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入固件名称',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
+          },
+          productId: {
+            title: '所属产品',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
+            'x-component-props': {
+              placeholder: '请选择所属产品',
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择所属产品',
+              },
+            ],
           },
           version: {
             title: '版本号',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入版本号',
+            },
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入版本号',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
           },
           versionOrder: {
             title: '版本序号',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入版本序号',
+            },
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入版本号',
+              },
+              {
+                maximum: 99999,
+                minimum: 1,
+              },
+            ],
           },
           signMethod: {
             title: '签名方式',
@@ -100,11 +165,38 @@ const Save = (props: Props) => {
               { label: 'MD5', value: 'MD5' },
               { label: 'SHA256', value: 'SHA256' },
             ],
+            'x-component-props': {
+              placeholder: '请选择签名方式',
+            },
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择签名方式',
+              },
+            ],
           },
           sign: {
             title: '签名',
             'x-decorator': 'FormItem',
             'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入签名',
+            },
+            'x-decorator-props': {
+              tooltip: '请输入本地文件进行签名加密后的值',
+              gridSpan: 1,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入签名',
+              },
+            ],
           },
           '{url,size}': {
             title: '文件上传',
@@ -113,6 +205,96 @@ const Save = (props: Props) => {
             'x-component-props': {
               type: 'file',
             },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请上传文件',
+              },
+            ],
+          },
+          array: {
+            type: 'array',
+            'x-decorator': 'FormItem',
+            'x-component': 'ArrayTable',
+            title: '其他配置',
+            'x-component-props': {
+              pagination: { pageSize: 10 },
+              scroll: { x: '100%' },
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            items: {
+              type: 'object',
+              properties: {
+                column1: {
+                  type: 'void',
+                  'x-component': 'ArrayTable.Column',
+                  'x-component-props': { title: 'KEY' },
+                  properties: {
+                    a1: {
+                      type: 'string',
+                      'x-decorator': 'Editable',
+                      'x-component': 'Input',
+                    },
+                  },
+                },
+                column2: {
+                  type: 'void',
+                  'x-component': 'ArrayTable.Column',
+                  'x-component-props': { title: 'VALUE' },
+                  properties: {
+                    a2: {
+                      type: 'string',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Input',
+                    },
+                  },
+                },
+                column3: {
+                  type: 'void',
+                  'x-component': 'ArrayTable.Column',
+                  'x-component-props': {
+                    title: '操作',
+                    dataIndex: 'operations',
+                  },
+                  properties: {
+                    item: {
+                      type: 'void',
+                      'x-component': 'FormItem',
+                      properties: {
+                        remove: {
+                          type: 'void',
+                          'x-component': 'ArrayTable.Remove',
+                        },
+                      },
+                    },
+                  },
+                },
+              },
+            },
+            properties: {
+              add: {
+                type: 'void',
+                'x-component': 'ArrayTable.Addition',
+                title: '添加条目',
+              },
+            },
+          },
+        },
+        description: {
+          title: '说明',
+          'x-decorator': 'FormItem',
+          'x-component': 'Input.TextArea',
+          'x-component-props': {
+            rows: 3,
+            showCount: true,
+            maxLength: 200,
+            placeholder: '请输入说明',
           },
         },
       },
@@ -123,12 +305,12 @@ const Save = (props: Props) => {
     <Modal
       maskClosable={false}
       width="50vw"
-      title="新增固件版本"
+      title="新增"
       onCancel={() => close()}
       onOk={() => save()}
       visible={visible}
     >
-      <Form form={form} labelCol={5} wrapperCol={16}>
+      <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
         <SchemaField schema={schema} scope={{ useAsyncDataSource, loadData }} />
       </Form>
     </Modal>

+ 25 - 0
src/pages/device/Firmware/Task/Detail/index.less

@@ -0,0 +1,25 @@
+.firmwareDetailCard {
+  position: relative;
+  height: 100px;
+  padding-top: 15px;
+  padding-left: 15px;
+  background: linear-gradient(135.62deg, #f6f7fd 22.27%, rgba(255, 255, 255, 0.86) 91.82%);
+  border-radius: 2px;
+  box-shadow: 0 4px 18px #efefef;
+
+  .firmwareDetailCardTitle {
+    color: rgba(0, 0, 0, 0.64);
+    font-size: 14px;
+  }
+
+  .firmwareDetailCardNum {
+    font-size: 36px;
+  }
+
+  .firmwareDetailCardImg {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    width: 120px;
+  }
+}

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

@@ -0,0 +1,170 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { observer } from '@formily/react';
+import { Badge, Card, Col, Row } from 'antd';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { Tooltip } from 'antd';
+import { useRef, useState } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { EyeOutlined } from '@ant-design/icons';
+// import { useHistory } from 'umi';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import useDomFullHeight from '@/hooks/document/useDomFullHeight';
+// import usePermissions from '@/hooks/permission';
+import SearchComponent from '@/components/SearchComponent';
+import { service } from '@/pages/device/Firmware';
+import styles from './index.less';
+import { model } from '@formily/reactive';
+
+const colorMap = new Map();
+colorMap.set('waiting', '#FF9000');
+colorMap.set('loading', '#4293FF');
+colorMap.set('finish', '#24B276');
+colorMap.set('error', '#F76F5D');
+
+const state = model<{
+  waiting: number;
+  loading: number;
+  finish: number;
+  error: number;
+}>({
+  waiting: 0,
+  loading: 2,
+  finish: 4,
+  error: 0,
+});
+
+const Detail = observer(() => {
+  const actionRef = useRef<ActionType>();
+  const intl = useIntl();
+  const { minHeight } = useDomFullHeight(`.firmware-task-detail`, 24);
+  // const { permission } = usePermissions('device/Firmware');
+  const [param, setParam] = useState({});
+
+  const arr = [
+    {
+      key: 'waiting',
+      name: '等待升级',
+      img: require('/public/images/firmware/waiting.png'),
+    },
+    {
+      key: 'loading',
+      name: '升级中',
+      img: require('/public/images/firmware/loading.png'),
+    },
+    {
+      key: 'finish',
+      name: '升级完成',
+      img: require('/public/images/firmware/finish.png'),
+    },
+    {
+      key: 'error',
+      name: '升级失败',
+      img: require('/public/images/firmware/error.png'),
+    },
+  ];
+
+  const columns: ProColumns<FirmwareItem>[] = [
+    {
+      title: '设备名称',
+      ellipsis: true,
+      dataIndex: 'name',
+    },
+    {
+      title: '所属产品',
+      ellipsis: true,
+      dataIndex: 'version',
+    },
+    {
+      title: '创建时间',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: '完成时间',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: '进度',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: '状态',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: () => [
+        <a onClick={() => {}} key="link">
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.detail',
+              defaultMessage: '查看',
+            })}
+            key={'detail'}
+          >
+            <EyeOutlined />
+          </Tooltip>
+        </a>,
+      ],
+    },
+  ];
+  return (
+    <PageContainer>
+      <Card style={{ marginBottom: 20 }}>
+        <Row gutter={24}>
+          {arr.map((item) => (
+            <Col span={6} key={item.key}>
+              <div className={styles.firmwareDetailCard}>
+                <div className={styles.firmwareDetailCardTitle}>
+                  <Badge color={colorMap.get(item.key)} />
+                  {item.name}
+                </div>
+                <div
+                  className={styles.firmwareDetailCardNum}
+                  style={{ color: colorMap.get(item.key) }}
+                >
+                  {state[item.key]}
+                </div>
+                <div className={styles.firmwareDetailCardImg}>
+                  <img style={{ width: '100%' }} src={item.img} />
+                </div>
+              </div>
+            </Col>
+          ))}
+        </Row>
+      </Card>
+      <SearchComponent<FirmwareItem>
+        field={columns}
+        target="firmware-task-detail"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
+      <ProTable<FirmwareItem>
+        scroll={{ x: 1366 }}
+        tableClassName={'firmware-task-detail'}
+        tableStyle={{ minHeight }}
+        search={false}
+        params={param}
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
+        columns={columns}
+        actionRef={actionRef}
+      />
+    </PageContainer>
+  );
+});
+export default Detail;

+ 279 - 0
src/pages/device/Firmware/Task/Save/index.tsx

@@ -0,0 +1,279 @@
+import { Modal } from 'antd';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import { createSchemaField } from '@formily/react';
+import {
+  Form,
+  FormGrid,
+  FormItem,
+  Input,
+  Select,
+  ArrayTable,
+  NumberPicker,
+  Radio,
+} from '@formily/antd';
+import { createForm, onFieldValueChange } from '@formily/core';
+import type { ISchema } from '@formily/json-schema';
+import FUpload from '@/components/Upload';
+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';
+import FSelectDevices from '@/components/FSelectDevices';
+
+interface Props {
+  data?: FirmwareItem;
+  close: () => void;
+  visible: boolean;
+}
+
+const Save = (props: Props) => {
+  const { data, close, visible } = props;
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+    effects() {
+      onFieldValueChange('mode', async (field) => {
+        field
+          .query('timeoutSeconds1')
+          .take()
+          .setDecoratorProps({
+            gridSpan: field.value === 'push' ? 1 : 2,
+          });
+        field
+          .query('timeoutSeconds')
+          .take()
+          .setDecoratorProps({
+            gridSpan: field.value === 'push' ? 1 : 2,
+          });
+      });
+      onFieldValueChange('releaseType', async (field) => {
+        field.setDecoratorProps({
+          gridSpan: field.value === 'all' ? 2 : 1,
+        });
+      });
+    },
+  });
+
+  const products = useRef<ProductItem[]>([]);
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      FUpload,
+      Select,
+      ArrayTable,
+      NumberPicker,
+      Radio,
+      FSelectDevices,
+    },
+  });
+
+  const save = async () => {
+    const values: FirmwareItem = 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>;
+    if (resp.status === 200) {
+      onlyMessage('保存成功!');
+    } else {
+      onlyMessage('保存失败!', 'error');
+    }
+  };
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      grid: {
+        type: 'void',
+        'x-component': 'FormGrid',
+        'x-component-props': {
+          minColumns: 2,
+          maxColumns: 2,
+        },
+        properties: {
+          name: {
+            title: '任务名称',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入任务名称',
+            },
+            required: true,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入任务名称',
+              },
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+            ],
+          },
+          mode: {
+            title: '推送方式',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            enum: [
+              { label: '平台推送', value: 'push' },
+              { label: '设备拉取', value: 'pull' },
+            ],
+            'x-component-props': {
+              placeholder: '请选择推送方式',
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择推送方式',
+              },
+            ],
+          },
+          timeoutSeconds: {
+            title: '响应超时时间',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入响应超时时间(秒)',
+            },
+            'x-visible': false,
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入响应超时时间',
+              },
+              {
+                maximum: 99999,
+                minimum: 1,
+              },
+            ],
+            'x-reactions': {
+              dependencies: ['.mode'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="push"}}',
+                },
+              },
+            },
+          },
+          timeoutSeconds1: {
+            title: '升级超时时间',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入升级超时时间(秒)',
+            },
+            'x-visible': false,
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入升级超时时间',
+              },
+              {
+                maximum: 99999,
+                minimum: 1,
+              },
+            ],
+            'x-reactions': {
+              dependencies: ['.mode'],
+              fulfill: {
+                state: {
+                  visible: '{{!!$deps[0]}}',
+                },
+              },
+            },
+          },
+          releaseType: {
+            type: 'number',
+            title: '升级设备',
+            default: 'all',
+            'x-visible': false,
+            enum: [
+              { label: '所有设备', value: 'all' },
+              { label: '选择设备', value: 'part' },
+            ],
+            'x-decorator': 'FormItem',
+            'x-component': 'Radio.Group',
+            required: true,
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择升级设备',
+              },
+            ],
+            'x-reactions': {
+              dependencies: ['.mode'],
+              fulfill: {
+                state: {
+                  visible: '{{!!$deps[0]}}',
+                },
+              },
+            },
+          },
+          part: {
+            title: '选择设备',
+            'x-decorator': 'FormItem',
+            'x-component': 'FSelectDevices',
+            'x-visible': false,
+            required: true,
+            'x-reactions': {
+              dependencies: ['.releaseType'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="part"}}',
+                },
+              },
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择设备',
+              },
+            ],
+          },
+          description: {
+            title: '说明',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input.TextArea',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              rows: 3,
+              showCount: true,
+              maxLength: 200,
+              placeholder: '请输入说明',
+            },
+          },
+        },
+      },
+    },
+  };
+
+  return (
+    <Modal
+      maskClosable={false}
+      width="50vw"
+      title="新增任务"
+      onCancel={() => close()}
+      onOk={() => save()}
+      visible={visible}
+    >
+      <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+export default Save;

+ 192 - 0
src/pages/device/Firmware/Task/index.tsx

@@ -0,0 +1,192 @@
+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 { 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 { model } from '@formily/reactive';
+import { observer } from '@formily/react';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import Save from './Save';
+import { onlyMessage } from '@/utils/util';
+import { PermissionButton } from '@/components';
+import useDomFullHeight from '@/hooks/document/useDomFullHeight';
+import usePermissions from '@/hooks/permission';
+import SearchComponent from '@/components/SearchComponent';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { service } from '@/pages/device/Firmware';
+
+export const state = model<{
+  current?: FirmwareItem;
+  visible: boolean;
+}>({
+  visible: false,
+});
+const Task = observer(() => {
+  const actionRef = useRef<ActionType>();
+  const intl = useIntl();
+  const { minHeight } = useDomFullHeight(`.firmware-task`, 24);
+  const { permission } = usePermissions('device/Firmware');
+  const [param, setParam] = useState({});
+  const history = useHistory<Record<string, string>>();
+
+  const columns: ProColumns<FirmwareItem>[] = [
+    {
+      title: '任务名称',
+      ellipsis: true,
+      dataIndex: 'name',
+    },
+    {
+      title: '推送方式',
+      ellipsis: true,
+      dataIndex: 'version',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.table.description',
+        defaultMessage: '说明',
+      }),
+      ellipsis: true,
+      align: 'center',
+      dataIndex: 'description',
+    },
+    {
+      title: '完成比例',
+      ellipsis: true,
+      dataIndex: 'signMethod',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+
+      render: (text, record) => [
+        <Link
+          onClick={() => {
+            state.current = record;
+          }}
+          to={`/device/firmware/detail/${record.id}`}
+          key="link"
+        >
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.detail',
+              defaultMessage: '查看',
+            })}
+            key={'detail'}
+          >
+            <EyeOutlined />
+          </Tooltip>
+        </Link>,
+        <a
+          key="editable"
+          onClick={() => {
+            state.visible = true;
+          }}
+        >
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '编辑',
+            })}
+          >
+            <EditOutlined />
+          </Tooltip>
+        </a>,
+        <a key="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();
+            }}
+          >
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.data.option.remove',
+                defaultMessage: '删除',
+              })}
+            >
+              <MinusOutlined />
+            </Tooltip>
+          </Popconfirm>
+        </a>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <SearchComponent<FirmwareItem>
+        field={columns}
+        target="firmware-task"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
+      <ProTable<FirmwareItem>
+        scroll={{ x: 1366 }}
+        tableClassName={'firmware-task'}
+        tableStyle={{ minHeight }}
+        search={false}
+        params={param}
+        headerTitle={
+          <div>
+            <PermissionButton
+              onClick={() => {
+                state.visible = true;
+              }}
+              isPermission={permission.add}
+              key="button"
+              icon={<PlusOutlined />}
+              type="primary"
+            >
+              {intl.formatMessage({
+                id: 'pages.data.option.add',
+                defaultMessage: '新增',
+              })}
+            </PermissionButton>
+            <Button
+              onClick={() => {
+                const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task/Detail'], '123');
+                history.push(url);
+              }}
+            >
+              详情
+            </Button>
+          </div>
+        }
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
+        columns={columns}
+        actionRef={actionRef}
+      />
+      <Save
+        data={state.current}
+        visible={state.visible}
+        close={() => {
+          state.visible = false;
+        }}
+      />
+    </PageContainer>
+  );
+});
+export default Task;

+ 71 - 22
src/pages/device/Firmware/index.tsx

@@ -3,16 +3,21 @@ import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import { Button, Popconfirm, Tooltip } from 'antd';
 import moment from 'moment';
-import { useRef } from 'react';
+import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { EditOutlined, EyeOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons';
-import { Link } from 'umi';
+import { Link, useHistory } from 'umi';
 import { model } from '@formily/reactive';
 import { observer } from '@formily/react';
 import type { FirmwareItem, TaskItem } 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';
+import SearchComponent from '@/components/SearchComponent';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 
 export const service = new Service('firmware');
 
@@ -35,18 +40,18 @@ export const state = model<{
 const Firmware = observer(() => {
   const actionRef = useRef<ActionType>();
   const intl = useIntl();
+  const { minHeight } = useDomFullHeight(`.firmware`, 24);
+  const { permission } = usePermissions('device/Firmware');
+  const [param, setParam] = useState({});
+  const history = useHistory<Record<string, string>>();
 
   const columns: ProColumns<FirmwareItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: intl.formatMessage({
         id: 'pages.device.firmware.name',
         defaultMessage: '固件名称',
       }),
+      ellipsis: true,
       dataIndex: 'name',
     },
     {
@@ -54,6 +59,7 @@ const Firmware = observer(() => {
         id: 'pages.device.firmware.version',
         defaultMessage: '固件版本',
       }),
+      ellipsis: true,
       dataIndex: 'version',
     },
     {
@@ -61,6 +67,7 @@ const Firmware = observer(() => {
         id: 'pages.device.firmware.productName',
         defaultMessage: '所属产品',
       }),
+      ellipsis: true,
       dataIndex: 'productName',
     },
     {
@@ -68,6 +75,7 @@ const Firmware = observer(() => {
         id: 'pages.device.firmware.signMethod',
         defaultMessage: '签名方式',
       }),
+      ellipsis: true,
       dataIndex: 'signMethod',
     },
     {
@@ -78,9 +86,20 @@ const Firmware = observer(() => {
       dataIndex: 'createTime',
       width: '200px',
       align: 'center',
+      ellipsis: true,
+      valueType: 'dateTime',
       render: (text: any) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
-      sorter: true,
-      defaultSortOrder: 'descend',
+      // sorter: true,
+      // defaultSortOrder: 'descend',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.table.description',
+        defaultMessage: '说明',
+      }),
+      ellipsis: true,
+      align: 'center',
+      dataIndex: 'description',
     },
     {
       title: intl.formatMessage({
@@ -157,20 +176,50 @@ const Firmware = observer(() => {
 
   return (
     <PageContainer>
+      <SearchComponent<FirmwareItem>
+        field={columns}
+        target="firmware"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
+        }}
+      />
       <ProTable<FirmwareItem>
-        toolBarRender={() => [
-          <Button
-            onClick={() => {
-              state.visible = true;
-            }}
-            key="button"
-            icon={<PlusOutlined />}
-            type="primary"
-          >
-            新增
-          </Button>,
-        ]}
-        request={async (params) => service.query(params)}
+        scroll={{ x: 1366 }}
+        tableClassName={'firmware'}
+        tableStyle={{ minHeight }}
+        search={false}
+        params={param}
+        headerTitle={
+          <div>
+            <PermissionButton
+              onClick={() => {
+                state.visible = true;
+              }}
+              isPermission={permission.add}
+              key="button"
+              icon={<PlusOutlined />}
+              type="primary"
+            >
+              {intl.formatMessage({
+                id: 'pages.data.option.add',
+                defaultMessage: '新增',
+              })}
+            </PermissionButton>
+            <Button
+              onClick={() => {
+                const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task'], '123');
+                history.push(url);
+              }}
+            >
+              升级任务
+            </Button>
+          </div>
+        }
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
         columns={columns}
         actionRef={actionRef}
       />

+ 92 - 35
src/pages/device/Instance/Detail/Diagnose/Status/index.tsx

@@ -112,7 +112,6 @@ const Status = observer((props: Props) => {
             text: '正常',
             info: null,
           });
-          DiagnoseStatusModel.count++;
           resolve({});
         }, time);
       } else {
@@ -205,7 +204,6 @@ const Status = observer((props: Props) => {
                 if (item) {
                   DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, item);
                 }
-                DiagnoseStatusModel.count++;
                 resolve({});
               }, time);
             } else {
@@ -235,7 +233,6 @@ const Status = observer((props: Props) => {
             text: '正常',
             info: null,
           });
-          DiagnoseStatusModel.count++;
           resolve({});
         }, time);
       } else {
@@ -371,7 +368,6 @@ const Status = observer((props: Props) => {
                   if (item) {
                     DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, item);
                   }
-                  DiagnoseStatusModel.count++;
                   resolve({});
                 }, time);
               } else {
@@ -503,7 +499,6 @@ const Status = observer((props: Props) => {
           setTimeout(() => {
             if (item) {
               DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, item);
-              DiagnoseStatusModel.count++;
             }
             resolve({});
           }, time);
@@ -527,7 +522,6 @@ const Status = observer((props: Props) => {
             text: '正常',
             info: null,
           });
-          DiagnoseStatusModel.count++;
           resolve({});
         }, time);
       } else {
@@ -562,7 +556,6 @@ const Status = observer((props: Props) => {
                 </div>
               ),
             });
-            DiagnoseStatusModel.count++;
             resolve({});
           }, time);
         } else {
@@ -651,7 +644,6 @@ const Status = observer((props: Props) => {
             setTimeout(() => {
               if (item) {
                 DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, item);
-                DiagnoseStatusModel.count++;
               }
               resolve({});
             }, time);
@@ -679,7 +671,6 @@ const Status = observer((props: Props) => {
             text: '正常',
             info: null,
           });
-          DiagnoseStatusModel.count++;
           resolve({});
         }, time);
       } else {
@@ -744,7 +735,6 @@ const Status = observer((props: Props) => {
                 if (item) {
                   DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, item);
                 }
-                DiagnoseStatusModel.count++;
                 resolve({});
               }, time);
             } else {
@@ -771,7 +761,6 @@ const Status = observer((props: Props) => {
             text: '正常',
             info: null,
           });
-          DiagnoseStatusModel.count++;
           resolve({});
         }, time);
       } else {
@@ -840,7 +829,6 @@ const Status = observer((props: Props) => {
           if (item) {
             DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, item);
           }
-          DiagnoseStatusModel.count++;
           resolve({});
         }, time);
       }
@@ -884,7 +872,6 @@ const Status = observer((props: Props) => {
                   text: '正常',
                   info: null,
                 });
-                DiagnoseStatusModel.count++;
                 resolve({});
               }, time);
             } else if (
@@ -941,7 +928,6 @@ const Status = observer((props: Props) => {
                     </div>
                   ),
                 });
-                DiagnoseStatusModel.count++;
                 resolve({});
               }, time);
             } else {
@@ -1002,7 +988,6 @@ const Status = observer((props: Props) => {
                     </div>
                   ),
                 });
-                DiagnoseStatusModel.count++;
                 resolve({});
               }, time);
             }
@@ -1051,7 +1036,6 @@ const Status = observer((props: Props) => {
                   text: '正常',
                   info: null,
                 });
-                DiagnoseStatusModel.count++;
                 resolve({});
               }, time);
             } else if (
@@ -1108,7 +1092,6 @@ const Status = observer((props: Props) => {
                     </div>
                   ),
                 });
-                DiagnoseStatusModel.count++;
                 resolve({});
               }, time);
             } else {
@@ -1169,7 +1152,6 @@ const Status = observer((props: Props) => {
                     </div>
                   ),
                 });
-                DiagnoseStatusModel.count++;
                 resolve({});
               }, time);
             }
@@ -1235,7 +1217,6 @@ const Status = observer((props: Props) => {
   //               DiagnoseStatusModel.list.length,
   //             );
   //           }
-  //           DiagnoseStatusModel.count++;
   //           resolve({});
   //         }, time);
   //       }
@@ -1291,7 +1272,6 @@ const Status = observer((props: Props) => {
   //       //     if (item) {
   //       //       DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, item, DiagnoseStatusModel.list.length);
   //       //     }
-  //       //     DiagnoseStatusModel.count++;
   //       //     resolve({});
   //       //   }, time);
   //       // }
@@ -1354,7 +1334,6 @@ const Status = observer((props: Props) => {
   //               DiagnoseStatusModel.list.length,
   //             );
   //           }
-  //           DiagnoseStatusModel.count++;
   //           resolve({});
   //         }, time);
   //       }
@@ -1488,7 +1467,6 @@ const Status = observer((props: Props) => {
               DiagnoseStatusModel.list.length,
             );
           }
-          DiagnoseStatusModel.count++;
           resolve({});
         }, time);
       } else {
@@ -1604,7 +1582,6 @@ const Status = observer((props: Props) => {
               DiagnoseStatusModel.list.length,
             );
           }
-          DiagnoseStatusModel.count++;
           resolve({});
         }, time);
       } else {
@@ -1777,6 +1754,7 @@ const Status = observer((props: Props) => {
 
   useEffect(() => {
     if (DiagnoseStatusModel.status === 'finish') {
+      DiagnoseStatusModel.count = 0;
       const list = _.uniq(_.map(DiagnoseStatusModel.list, 'status'));
       if (device.state?.value !== 'online') {
         DiagnoseStatusModel.state = 'error';
@@ -1786,6 +1764,9 @@ const Status = observer((props: Props) => {
       } else {
         DiagnoseStatusModel.state = 'success';
       }
+    } else if (DiagnoseStatusModel.status === 'loading') {
+      const arr = _.map(DiagnoseStatusModel.list, 'status').filter((i) => i !== 'loading');
+      DiagnoseStatusModel.count = arr.length;
     }
   }, [DiagnoseStatusModel.status, DiagnoseStatusModel.list]);
 
@@ -1802,7 +1783,6 @@ const Status = observer((props: Props) => {
       product: [],
       device: [],
     };
-    DiagnoseStatusModel.count = 0;
     DiagnoseStatusModel.status = 'loading';
     DiagnoseStatusModel.percent = 0;
     let arr: any[] = [];
@@ -2022,17 +2002,94 @@ const Status = observer((props: Props) => {
           onCancel={() => {
             setBindParentVisible(false);
           }}
-          onOk={(parentId: string) => {
-            DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, {
-              key: 'parent-device',
-              name: '网关父设备',
-              desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
-              status: 'success',
-              text: '正常',
-              info: null,
-            });
-            InstanceModel.detail.parentId = parentId;
-            setBindParentVisible(false);
+          onOk={async (parentId: string) => {
+            let item: ListProps | undefined = undefined;
+            const response = await service.detail(parentId);
+            if (response.status === 200) {
+              if (response?.result?.state?.value === 'notActive') {
+                item = {
+                  key: 'parent-device',
+                  name: '网关父设备',
+                  desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
+                  status: 'error',
+                  text: '异常',
+                  info: (
+                    <div>
+                      <div className={styles.infoItem}>
+                        <Badge
+                          status="default"
+                          text={
+                            productPermission.action ? (
+                              <span>
+                                网关父设备已禁用,请先
+                                <Popconfirm
+                                  title="确认启用"
+                                  onConfirm={async () => {
+                                    const resp = await service.deployDevice(
+                                      response?.result?.id || '',
+                                    );
+                                    if (resp.status === 200) {
+                                      onlyMessage('操作成功!');
+                                      DiagnoseStatusModel.list = modifyArrayList(
+                                        DiagnoseStatusModel.list,
+                                        {
+                                          key: 'parent-device',
+                                          name: '网关父设备',
+                                          desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
+                                          status: 'success',
+                                          text: '正常',
+                                          info: null,
+                                        },
+                                      );
+                                    }
+                                  }}
+                                >
+                                  <a>启用</a>
+                                </Popconfirm>
+                              </span>
+                            ) : (
+                              '暂无权限,请联系管理员处理'
+                            )
+                          }
+                        />
+                      </div>
+                    </div>
+                  ),
+                };
+              } else if (response?.state?.value === 'online') {
+                item = {
+                  key: 'parent-device',
+                  name: '网关父设备',
+                  desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
+                  status: 'success',
+                  text: '正常',
+                  info: null,
+                };
+              } else {
+                item = {
+                  key: 'parent-device',
+                  name: '网关父设备',
+                  desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
+                  status: 'error',
+                  text: '异常',
+                  info: (
+                    <div>
+                      <div className={styles.infoItem}>
+                        <Badge
+                          status="default"
+                          text={<span>网关父设备已离线,请先排查网关设备故障</span>}
+                        />
+                      </div>
+                    </div>
+                  ),
+                };
+              }
+              if (item) {
+                DiagnoseStatusModel.list = modifyArrayList(DiagnoseStatusModel.list, item);
+              }
+              InstanceModel.detail.parentId = parentId;
+              setBindParentVisible(false);
+            }
           }}
         />
       )}

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

@@ -558,6 +558,7 @@ const Instance = () => {
         actionRef={actionRef}
         params={searchParams}
         options={{ fullScreen: true }}
+        columnEmptyText={''}
         request={(params) =>
           service.query({
             ...params,

+ 1 - 1
src/pages/device/Product/Detail/PropertyImport/index.tsx

@@ -214,7 +214,7 @@ const PropertyImport = (props: Props) => {
             MetadataModel.importMetadata = false;
           }}
         >
-          确
+          确
         </Button>,
       ]}
     >

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

@@ -422,6 +422,7 @@ const Product = observer(() => {
       <ProTableCard<ProductItem>
         columns={columns}
         actionRef={actionRef}
+        columnEmptyText={''}
         options={{ fullScreen: true }}
         scroll={{ x: 1366 }}
         // request={async (params = {}) => {

+ 143 - 102
src/pages/device/components/Metadata/Base/Edit/index.tsx

@@ -50,6 +50,7 @@ import DB from '@/db';
 import _ from 'lodash';
 import { InstanceModel } from '@/pages/device/Instance';
 import FRuleEditor from '@/components/FRuleEditor';
+import FIndicators from '@/components/FIndicators';
 import { action } from '@formily/reactive';
 import { asyncUpdateMedata, updateMetadata } from '../../metadata';
 import { onlyMessage } from '@/utils/util';
@@ -160,6 +161,7 @@ const Edit = observer((props: Props) => {
       Checkbox,
       FormGrid,
       DatePicker,
+      FIndicators,
     },
     scope: {
       async asyncOtherConfig(field: Field) {
@@ -778,121 +780,160 @@ const Edit = observer((props: Props) => {
                         },
                       ],
                     },
-                    space: {
-                      type: 'void',
+                    '{value, range}': {
                       title: '指标值',
                       'x-decorator': 'FormItem',
-                      'x-component': 'FormGrid',
+                      'x-component': 'FIndicators',
                       'x-decorator-props': {
                         labelAlign: 'left',
                         layout: 'vertical',
                       },
+                      'x-reactions': {
+                        dependencies: ['valueType.type'],
+                        fulfill: {
+                          state: {
+                            componentProps: {
+                              type: '{{$deps[0]}}',
+                            },
+                          },
+                        },
+                      },
                       'x-validator': [
                         {
                           required: true,
                           message: '请输入指标值',
                         },
-                      ],
-                      'x-component-props': {
-                        maxColumns: 12,
-                        minColumns: 12,
-                      },
-                      properties: {
-                        'value[0]': {
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Input',
-                          'x-decorator-props': {
-                            gridSpan: 5,
-                          },
-                          'x-validator': [
-                            {
-                              required: true,
-                              message: '请输入',
-                            },
-                          ],
-                          'x-reactions': {
-                            dependencies: ['..range', 'valueType.type'],
-                            fulfill: {
-                              state: {
-                                decoratorProps: {
-                                  gridSpan: '{{!!$deps[0]?5:$deps[1]==="boolean"?12:10}}',
-                                },
-                                componentType:
-                                  '{{["int","long","double","float"].includes($deps[1])?"NumberPicker":["date"].includes($deps[1])?"DatePicker":"Input"}}',
-                              },
-                            },
-                          },
-                          // 根据数据类型来渲染不同的组件
-                        },
-                        'value[1]': {
-                          title: '~',
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Input',
-                          'x-decorator-props': {
-                            gridSpan: 5,
-                          },
-                          'x-validator': [
-                            {
-                              required: true,
-                              message: '请输入',
-                            },
-                          ],
-                          'x-reactions': [
-                            {
-                              dependencies: ['..range', 'valueType.type'],
-                              fulfill: {
-                                state: {
-                                  visible: '{{!!$deps[0]}}',
-                                  componentType:
-                                    '{{["int","long","double","float"].includes($deps[1])?"NumberPicker":["date"].includes($deps[1])?"DatePicker":"Input"}}',
-                                },
-                              },
-                            },
-                            {
-                              dependencies: ['valueType.type'],
-                              fulfill: {
-                                state: {
-                                  visible: '{{!$deps[0]==="boolean"}}',
-                                },
-                              },
-                            },
-                          ],
-                        },
-                        // 根据数据类型来渲染不同的组件
-                        range: {
-                          type: 'boolean',
-                          default: false,
-                          'x-decorator': 'FormItem',
-                          'x-component': 'Checkbox',
-                          'x-component-props': {
-                            children: '范围',
-                          },
-                          'x-decorator-props': {
-                            gridSpan: 2,
-                          },
-                          'x-reactions': {
-                            dependencies: ['valueType.type'],
-                            when: '{{$deps[0]==="boolean"}}',
-                            fulfill: {
-                              state: {
-                                visible: false,
-                                decoratorProps: {
-                                  gridSpan: 0,
-                                },
-                              },
-                            },
-                            otherwise: {
-                              state: {
-                                visible: true,
-                                decoratorProps: {
-                                  gridSpan: 2,
-                                },
-                              },
-                            },
+                        {
+                          validator: (value: any) => {
+                            if (value?.range) {
+                              if (!value?.value || !value?.value[0] || !value?.value[1]) {
+                                return Promise.reject(new Error('请输入指标值'));
+                              }
+                            } else {
+                              if (value?.value && !value?.value[0]) {
+                                return Promise.reject(new Error('请输入指标值'));
+                              }
+                            }
+                            return Promise.resolve();
                           },
                         },
-                      },
+                      ],
                     },
+                    // space: {
+                    //   type: 'void',
+                    //   title: '指标值',
+                    //   'x-decorator': 'FormItem',
+                    //   'x-component': 'FormGrid',
+                    //   'x-decorator-props': {
+                    //     labelAlign: 'left',
+                    //     layout: 'vertical',
+                    //   },
+                    //   'x-validator': [
+                    //     {
+                    //       required: true,
+                    //       message: '请输入指标值',
+                    //     },
+                    //   ],
+                    //   'x-component-props': {
+                    //     maxColumns: 12,
+                    //     minColumns: 12,
+                    //   },
+                    //   properties: {
+                    //     'value[0]': {
+                    //       'x-decorator': 'FormItem',
+                    //       'x-component': 'Input',
+                    //       'x-decorator-props': {
+                    //         gridSpan: 5,
+                    //       },
+                    //       'x-validator': [
+                    //         {
+                    //           required: true,
+                    //           message: '请输入',
+                    //         },
+                    //       ],
+                    //       'x-reactions': {
+                    //         dependencies: ['..range', 'valueType.type'],
+                    //         fulfill: {
+                    //           state: {
+                    //             decoratorProps: {
+                    //               gridSpan: '{{!!$deps[0]?5:$deps[1]==="boolean"?12:10}}',
+                    //             },
+                    //             componentType:
+                    //               '{{["int","long","double","float"].includes($deps[1])?"NumberPicker":["date"].includes($deps[1])?"DatePicker":"Input"}}',
+                    //           },
+                    //         },
+                    //       },
+                    //       // 根据数据类型来渲染不同的组件
+                    //     },
+                    //     'value[1]': {
+                    //       title: '~',
+                    //       'x-decorator': 'FormItem',
+                    //       'x-component': 'Input',
+                    //       'x-decorator-props': {
+                    //         gridSpan: 5,
+                    //       },
+                    //       'x-validator': [
+                    //         {
+                    //           required: true,
+                    //           message: '请输入',
+                    //         },
+                    //       ],
+                    //       'x-reactions': [
+                    //         {
+                    //           dependencies: ['..range', 'valueType.type'],
+                    //           fulfill: {
+                    //             state: {
+                    //               visible: '{{!!$deps[0]}}',
+                    //               componentType:
+                    //                 '{{["int","long","double","float"].includes($deps[1])?"NumberPicker":["date"].includes($deps[1])?"DatePicker":"Input"}}',
+                    //             },
+                    //           },
+                    //         },
+                    //         {
+                    //           dependencies: ['valueType.type'],
+                    //           fulfill: {
+                    //             state: {
+                    //               visible: '{{!$deps[0]==="boolean"}}',
+                    //             },
+                    //           },
+                    //         },
+                    //       ],
+                    //     },
+                    //     // 根据数据类型来渲染不同的组件
+                    //     range: {
+                    //       type: 'boolean',
+                    //       default: false,
+                    //       'x-decorator': 'FormItem',
+                    //       'x-component': 'Checkbox',
+                    //       'x-component-props': {
+                    //         children: '范围',
+                    //       },
+                    //       'x-decorator-props': {
+                    //         gridSpan: 2,
+                    //       },
+                    //       'x-reactions': {
+                    //         dependencies: ['valueType.type'],
+                    //         when: '{{$deps[0]==="boolean"}}',
+                    //         fulfill: {
+                    //           state: {
+                    //             visible: false,
+                    //             decoratorProps: {
+                    //               gridSpan: 0,
+                    //             },
+                    //           },
+                    //         },
+                    //         otherwise: {
+                    //           state: {
+                    //             visible: true,
+                    //             decoratorProps: {
+                    //               gridSpan: 2,
+                    //             },
+                    //           },
+                    //         },
+                    //       },
+                    //     },
+                    //   },
+                    // },
                   },
                 },
                 right: {
@@ -1084,7 +1125,7 @@ const Edit = observer((props: Props) => {
     if (params?.id) {
       const result1 = await DB.getDB().table(`${type}`).where('id').equals(params.id).toArray();
 
-      if (result1.length > 0) {
+      if (result1.length > 0 && MetadataModel.action === 'add') {
         message.error('标识已存在');
         setLoading(false);
         return;

+ 15 - 0
src/pages/init-home/index.less

@@ -0,0 +1,15 @@
+.init {
+  width: 100%;
+  height: 100vh;
+  padding: 64px 128px;
+  overflow: hidden;
+  background-image: url('/images/init-home/background.png');
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+
+  .box {
+    width: 100%;
+    height: 100%;
+    background: white;
+  }
+}

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

@@ -0,0 +1,13 @@
+import { TitleComponent } from '@/components';
+import styles from './index.less';
+
+const InitHome = () => {
+  return (
+    <div className={styles.init}>
+      <TitleComponent data={'系统初始化'} />
+      <div className={styles.box}>123</div>
+    </div>
+  );
+};
+
+export default InitHome;

+ 2 - 0
src/pages/link/Channel/Modbus/index.tsx

@@ -19,6 +19,7 @@ import Save from './Save';
 import { getMenuPathByCode } from '@/utils/menu';
 import { useDomFullHeight } from '@/hooks';
 import { onlyMessage } from '@/utils/util';
+// import NewModbus from '../new'
 
 export const service = new Service('modbus/master');
 
@@ -210,6 +211,7 @@ const Modbus = () => {
   ];
   return (
     <PageContainer>
+      {/* <NewModbus/> */}
       <Card style={{ marginBottom: 10 }}>
         <Row gutter={[24, 24]}>
           {topCard.map((item) => (

+ 55 - 0
src/pages/link/Channel/channelCard.tsx

@@ -0,0 +1,55 @@
+import { Badge } from 'antd';
+import classNames from 'classnames';
+import { useState } from 'react';
+import './index.less';
+interface Props {
+  actions: any;
+  onClick: Function;
+  active: boolean;
+  data: any;
+  defaultActiveKey?: any;
+}
+
+const ChannelCard = (props: Props) => {
+  const [actions, setActions] = useState<boolean>(false);
+  const connectImg = require('/public/images/channel/connect.png');
+  const disconnectImg = require('/public/images/channel/disconnect.png');
+
+  return (
+    <div
+      className={classNames('channel-card', {
+        active: props.active,
+        connect: props.data.status === 'connect',
+        disconnect: props.data.status === 'disconnect',
+      })}
+      onMouseEnter={() => {
+        setActions(true);
+      }}
+      onMouseLeave={() => {
+        setActions(false);
+      }}
+    >
+      <div
+        className="channel-card-top"
+        onClick={() => {
+          props.onClick();
+        }}
+      >
+        <div className="card-top-img">
+          {' '}
+          <img src={props.data.status === 'connect' ? connectImg : disconnectImg} />
+        </div>
+        <div className="card-top-name">这里是通道名称</div>
+        <div className="card-top-status">
+          {props.data.status === 'connect' ? (
+            <Badge status="processing" color={'green'} text={'正常'} />
+          ) : (
+            <Badge status="processing" color={'red'} text={'禁用'} />
+          )}
+        </div>
+      </div>
+      {actions && <div className="channel-card-actions">{props.actions}</div>}
+    </div>
+  );
+};
+export default ChannelCard;

+ 81 - 0
src/pages/link/Channel/index.less

@@ -0,0 +1,81 @@
+.item {
+  display: flex;
+
+  .item-left {
+    margin-right: 20px;
+    padding-right: 10px;
+    border-right: 1px #eee solid;
+    .item-left-list {
+      height: 628px;
+      overflow: overlay;
+    }
+    ::-webkit-scrollbar {
+      // display:none
+      width: 2px;
+      height: 2px;
+      scrollbar-arrow-color: #eee;
+    }
+    ::-webkit-scrollbar-thumb {
+      background: rgb(136, 136, 136);
+      border-radius: 5px;
+    }
+  }
+
+  .item-right {
+    .ant-card-body {
+      padding-top: 0;
+      padding-left: 0;
+    }
+  }
+}
+
+.channel-card {
+  width: 220px;
+  height: 112px;
+  margin-top: 16px;
+  border: 1px #e0e0e0 solid;
+
+  &.active {
+    &.connect {
+      background-color: #24b27614;
+      border: 1px solid #24b276;
+      border-radius: 2px;
+    }
+
+    &.disconnect {
+      background-color: rgba(255, 144, 0, 0.08);
+      border: 1px solid #ff9000;
+      border-radius: 2px;
+    }
+  }
+
+  .channel-card-top {
+    .card-top-img {
+      text-align: end;
+    }
+
+    .card-top-name {
+      position: relative;
+      bottom: 8px;
+      margin-left: 16px;
+      color: #323130;
+      font-weight: 600;
+      font-size: 16px;
+      line-height: 22px;
+    }
+
+    .card-top-status {
+      margin-bottom: 8px;
+      margin-left: 16px;
+    }
+  }
+
+  .channel-card-actions {
+    display: flex;
+    align-items: center;
+    justify-content: space-evenly;
+    // height: 40px;
+    background-color: #f6f6f6;
+    border-top: 1px #eee solid;
+  }
+}

+ 390 - 0
src/pages/link/Channel/new.tsx

@@ -0,0 +1,390 @@
+import { 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 PermissionButton from '@/components/PermissionButton';
+import {
+  DeleteOutlined,
+  EditOutlined,
+  ExportOutlined,
+  ImportOutlined,
+  PlayCircleOutlined,
+  PlusOutlined,
+  StopOutlined,
+} from '@ant-design/icons';
+import { useRef, useState } from 'react';
+import { useIntl } from 'umi';
+import ChannelCard from './channelCard';
+
+const NewModbus = () => {
+  const { minHeight } = useDomFullHeight(`.modbus`);
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const { permission } = PermissionButton.usePermission('link/Channel/Modbus');
+  const [param, setParam] = useState({});
+  const [activeKey, setActiveKey] = useState<any>('');
+  const data = [
+    {
+      id: 1,
+      status: 'connect',
+      state: {
+        text: '正常',
+        value: 'enabled',
+      },
+    },
+    {
+      id: 2,
+      status: 'disconnect',
+      state: {
+        text: '禁用',
+        value: 'disabled',
+      },
+    },
+  ];
+
+  const columns: ProColumns<any>[] = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+      ellipsis: true,
+      fixed: 'left',
+    },
+    {
+      title: '功能码',
+      dataIndex: 'host',
+    },
+    {
+      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',
+    },
+    {
+      title: '状态',
+      dataIndex: 'port',
+      search: false,
+      valueType: 'digit',
+    },
+    // {
+    //   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 = (
+    <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 (
+    <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={() => {
+                // setDeviceVisiable(true);
+              }}
+              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={(data1) => {
+              actionRef.current?.reset?.();
+              setParam(data1);
+            }}
+          />
+          <ProTable
+            actionRef={actionRef}
+            params={param}
+            columns={columns}
+            rowKey="id"
+            scroll={{ x: '60%' }}
+            search={false}
+            headerTitle={
+              <>
+                <PermissionButton
+                  onClick={() => {
+                    // setMode('add');
+                    // setVisible(true);
+                    // setCurrent({});
+                  }}
+                  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>
+  );
+};
+export default NewModbus;

+ 1 - 0
src/pages/link/Protocol/index.tsx

@@ -199,6 +199,7 @@ const Protocol = () => {
         actionRef={actionRef}
         scroll={{ x: 1366 }}
         params={searchParams}
+        columnEmptyText={''}
         options={{ fullScreen: true }}
         request={(params) =>
           service.query({

+ 1 - 0
src/pages/link/Type/index.tsx

@@ -254,6 +254,7 @@ const Network = () => {
         columns={columns}
         scroll={{ x: 1366 }}
         search={false}
+        columnEmptyText={''}
         headerTitle={
           <PermissionButton
             isPermission={networkPermission.add}

+ 1 - 0
src/pages/media/Cascade/index.tsx

@@ -326,6 +326,7 @@ const Cascade = () => {
         actionRef={actionRef}
         params={searchParams}
         scroll={{ x: 1366 }}
+        columnEmptyText={''}
         options={{ fullScreen: true }}
         request={async (params = {}) => {
           return await lastValueFrom(

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

@@ -318,6 +318,7 @@ const Device = () => {
         actionRef={actionRef}
         options={{ fullScreen: true }}
         params={queryParam}
+        columnEmptyText={''}
         scroll={{ x: 1366 }}
         request={(params = {}) =>
           service.query({

+ 1 - 0
src/pages/notice/Config/index.tsx

@@ -263,6 +263,7 @@ const Config = observer(() => {
         actionRef={actionRef}
         search={false}
         params={param}
+        columnEmptyText={''}
         columns={columns}
         scroll={{ x: 1366 }}
         headerTitle={

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

@@ -229,6 +229,7 @@ const Template = observer(() => {
         search={false}
         params={param}
         columns={columns}
+        columnEmptyText={''}
         headerTitle={
           <Space>
             <PermissionButton

+ 118 - 0
src/pages/rule-engine/Alarm/Config/Save/input.tsx

@@ -0,0 +1,118 @@
+import { Modal } from 'antd';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import { createSchemaField } from '@formily/react';
+import { Form, FormGrid, FormItem, Input, Switch } from '@formily/antd';
+import { createForm, onFormInit } from '@formily/core';
+import type { ISchema } from '@formily/json-schema';
+import { service } from '@/pages/rule-engine/Alarm/Config';
+import { onlyMessage } from '@/utils/util';
+import type { IOConfigItem } from '../typing';
+
+interface Props {
+  data?: FirmwareItem;
+  close: () => void;
+}
+
+const InputSave = (props: Props) => {
+  const { data, close } = props;
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+    effects() {
+      onFormInit(async (f) => {
+        const resp = await service.getDataExchange('consume');
+        if (resp.status === 200) {
+          f.setInitialValues(resp.result?.config.config);
+          f.setValuesIn('id', resp.result?.id);
+          f.setValuesIn('state', resp.result?.state?.value === 'enabled' ? true : false);
+        }
+      });
+    },
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      Switch,
+    },
+  });
+
+  const save = async () => {
+    form.validate();
+    const inputConfig: IOConfigItem = await form.submit();
+    const res = await service.saveOutputData({
+      config: {
+        sourceType: 'kafka',
+        config: inputConfig,
+      },
+      id: inputConfig.id,
+      sourceType: 'kafka',
+      exchangeType: 'consume',
+    });
+
+    if (res.status === 200) {
+      onlyMessage('操作成功');
+    }
+  };
+
+  const inputSchema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        'x-component': 'Input',
+        'x-hidden': true,
+      },
+      address: {
+        title: 'kafka地址',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入kafka地址',
+        },
+      },
+      topic: {
+        title: 'topic',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入topic',
+        },
+      },
+      state: {
+        title: '状态',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Switch',
+        default: false,
+        'x-component-props': {
+          checkedChildren: '禁用',
+          unCheckedChildren: '启用',
+        },
+      },
+    },
+  };
+
+  return (
+    <Modal
+      maskClosable={false}
+      width="45vw"
+      title="编辑"
+      onCancel={() => close()}
+      onOk={() => save()}
+      visible
+    >
+      <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
+        <SchemaField schema={inputSchema} />
+      </Form>
+    </Modal>
+  );
+};
+export default InputSave;

+ 155 - 0
src/pages/rule-engine/Alarm/Config/Save/output.tsx

@@ -0,0 +1,155 @@
+import { Modal } from 'antd';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import { createSchemaField } from '@formily/react';
+import { Form, FormGrid, FormItem, Input, Switch } from '@formily/antd';
+import { createForm, onFormInit } from '@formily/core';
+import type { ISchema } from '@formily/json-schema';
+import { service } from '@/pages/rule-engine/Alarm/Config';
+import { onlyMessage } from '@/utils/util';
+import type { IOConfigItem } from '../typing';
+
+interface Props {
+  data?: FirmwareItem;
+  close: () => void;
+}
+
+const OutputSave = (props: Props) => {
+  const { data, close } = props;
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+    effects() {
+      onFormInit(async (f) => {
+        const resp = await service.getDataExchange('producer');
+        if (resp.status === 200) {
+          f.setInitialValues(resp.result?.config.config);
+          f.setValuesIn('id', resp.result?.id);
+          f.setValuesIn('state', resp.result?.state?.value === 'enabled' ? true : false);
+        }
+      });
+    },
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      Switch,
+    },
+  });
+
+  const save = async () => {
+    form.validate();
+    const inputConfig: IOConfigItem = await form.submit();
+    const res = await service.saveOutputData({
+      config: {
+        sourceType: 'kafka',
+        config: inputConfig,
+      },
+      id: inputConfig.id,
+      sourceType: 'kafka',
+      exchangeType: 'consume',
+    });
+
+    if (res.status === 200) {
+      onlyMessage('操作成功');
+    }
+  };
+
+  const outputSchema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        'x-component': 'Input',
+        'x-hidden': true,
+      },
+      address: {
+        title: 'kafka地址',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入kafka地址',
+        },
+      },
+      topic: {
+        title: 'topic',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入topic',
+        },
+      },
+      state: {
+        title: '状态',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Switch',
+        default: false,
+        'x-component-props': {
+          checkedChildren: '禁用',
+          unCheckedChildren: '启用',
+        },
+      },
+      // layout2: {
+      //   type: 'void',
+      //   'x-decorator': 'FormGrid',
+      //   'x-decorator-props': {
+      //     maxColumns: 2,
+      //     minColumns: 2,
+      //     columnGap: 24,
+      //   },
+      //   properties: {
+      //     username: {
+      //       title: '用户名',
+      //       type: 'string',
+      //       // required: true,
+      //       'x-decorator': 'FormItem',
+      //       'x-component': 'Input',
+      //       'x-component-props': {
+      //         placeholder: '请输入用户名',
+      //       },
+      //       'x-decorator-props': {
+      //         gridSpan: 1,
+      //       },
+      //     },
+      //     password: {
+      //       title: '密码',
+      //       type: 'string',
+      //       // required: true,
+      //       'x-decorator': 'FormItem',
+      //       'x-component': 'Input',
+      //       'x-decorator-props': {
+      //         gridSpan: 1,
+      //       },
+      //       'x-component-props': {
+      //         placeholder: '请输入密码',
+      //       },
+      //     },
+      //   },
+      // },
+    },
+  };
+
+  return (
+    <Modal
+      maskClosable={false}
+      width="50vw"
+      title="编辑"
+      onCancel={() => close()}
+      onOk={() => save()}
+      visible
+    >
+      <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
+        <SchemaField schema={outputSchema} />
+      </Form>
+    </Modal>
+  );
+};
+export default OutputSave;

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

@@ -1,23 +1,29 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { Button, Card, Col, Divider, Image, Row, Table, Tooltip } from 'antd';
+import { Badge, Button, Card, Col, Descriptions, Divider, Image, Row, Table, Tooltip } from 'antd';
 import TitleComponent from '@/components/TitleComponent';
 import { createSchemaField } from '@formily/react';
 import { ArrayItems, Form, FormButtonGroup, FormGrid, FormItem, Input } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
-import { useMemo, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
 import { createForm, onFormInit } from '@formily/core';
 import FLevelInput from '@/components/FLevelInput';
-import type { IOConfigItem } from '@/pages/rule-engine/Alarm/Config/typing';
 import Service from '@/pages/rule-engine/Alarm/Config/service';
 import styles from './index.less';
 import ReactMarkdown from 'react-markdown';
-import { QuestionCircleOutlined } from '@ant-design/icons';
+import { EditOutlined, QuestionCircleOutlined } from '@ant-design/icons';
 import { onlyMessage } from '@/utils/util';
+import OutputSave from './Save/output';
+import InputSave from './Save/input';
 
 export const service = new Service('alarm/config');
 const ioImg = require('/public/images/alarm/io.png');
 const Config = () => {
   const [tab, setTab] = useState<'io' | 'config' | string>('config');
+  const [inputVisible, setInputVisible] = useState<boolean>(false);
+  const [outputVisible, setOutputVisible] = useState<boolean>(false);
+  const [input, setInput] = useState<any>({});
+  const [output, setOutput] = useState<any>({});
+
   const outputData = [
     {
       key: 'alarmName',
@@ -183,39 +189,6 @@ const Config = () => {
     [],
   );
 
-  const inputForm = useMemo(
-    () =>
-      createForm({
-        validateFirst: true,
-        effects() {
-          onFormInit(async (f) => {
-            const resp = await service.getDataExchange('consume');
-            if (resp.status === 200) {
-              f.setInitialValues(resp.result?.config.config);
-              f.setValuesIn('id', resp.result?.id);
-            }
-          });
-        },
-      }),
-    [],
-  );
-  const outputForm = useMemo(
-    () =>
-      createForm({
-        validateFirst: true,
-        effects() {
-          onFormInit(async (f) => {
-            const resp = await service.getDataExchange('producer');
-            if (resp.status === 200) {
-              f.setInitialValues(resp.result?.config.config);
-              f.setValuesIn('id', resp.result?.id);
-            }
-          });
-        },
-      }),
-    [],
-  );
-
   const levelSchema: ISchema = {
     type: 'object',
     properties: {
@@ -266,131 +239,6 @@ const Config = () => {
     },
   };
 
-  const outputSchema: ISchema = {
-    type: 'object',
-    properties: {
-      id: {
-        'x-component': 'Input',
-        'x-hidden': true,
-      },
-      address: {
-        title: 'kafka地址',
-        type: 'string',
-        required: true,
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        'x-component-props': {
-          placeholder: '请输入kafka地址',
-        },
-      },
-      topic: {
-        title: 'topic',
-        type: 'string',
-        required: true,
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        'x-component-props': {
-          placeholder: '请输入topic',
-        },
-      },
-      // layout2: {
-      //   type: 'void',
-      //   'x-decorator': 'FormGrid',
-      //   'x-decorator-props': {
-      //     maxColumns: 2,
-      //     minColumns: 2,
-      //     columnGap: 24,
-      //   },
-      //   properties: {
-      //     username: {
-      //       title: '用户名',
-      //       type: 'string',
-      //       // required: true,
-      //       'x-decorator': 'FormItem',
-      //       'x-component': 'Input',
-      //       'x-component-props': {
-      //         placeholder: '请输入用户名',
-      //       },
-      //       'x-decorator-props': {
-      //         gridSpan: 1,
-      //       },
-      //     },
-      //     password: {
-      //       title: '密码',
-      //       type: 'string',
-      //       // required: true,
-      //       'x-decorator': 'FormItem',
-      //       'x-component': 'Input',
-      //       'x-decorator-props': {
-      //         gridSpan: 1,
-      //       },
-      //       'x-component-props': {
-      //         placeholder: '请输入密码',
-      //       },
-      //     },
-      //   },
-      // },
-    },
-  };
-
-  const inputSchema: ISchema = {
-    type: 'object',
-    properties: {
-      id: {
-        'x-component': 'Input',
-        'x-hidden': true,
-      },
-      address: {
-        title: 'kafka地址',
-        type: 'string',
-        required: true,
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        'x-component-props': {
-          placeholder: '请输入kafka地址',
-        },
-      },
-      topic: {
-        title: 'topic',
-        type: 'string',
-        required: true,
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        'x-component-props': {
-          placeholder: '请输入topic',
-        },
-      },
-    },
-  };
-
-  const handleSaveIO = async () => {
-    outputForm.validate();
-    inputForm.validate();
-    const inputConfig: IOConfigItem = await inputForm.submit();
-    const outputConfig: IOConfigItem = await outputForm.submit();
-    const inputResp = await service.saveOutputData({
-      config: {
-        config: outputConfig,
-      },
-      id: outputConfig.id,
-      sourceType: 'kafka',
-      exchangeType: 'producer',
-    });
-    const outputResp = await service.saveOutputData({
-      config: {
-        sourceType: 'kafka',
-        config: inputConfig,
-      },
-      id: inputConfig.id,
-      sourceType: 'kafka',
-      exchangeType: 'consume',
-    });
-
-    if (inputResp.status === 200 && outputResp.status === 200) {
-      onlyMessage('操作成功');
-    }
-  };
-
   const handleSaveLevel = async () => {
     const values: { level: string[] } = await levelForm.submit();
     const _level = values?.level.map((l: string, i: number) => ({ level: i + 1, title: l }));
@@ -400,6 +248,19 @@ const Config = () => {
     }
   };
 
+  useEffect(() => {
+    service.getDataExchange('consume').then((resp) => {
+      if (resp.status === 200) {
+        setInput(resp.result);
+      }
+    });
+    service.getDataExchange('producer').then((resp) => {
+      if (resp.status === 200) {
+        setOutput(resp.result);
+      }
+    });
+  }, []);
+
   const outputText = `
   ~~~json
   {
@@ -469,12 +330,29 @@ const Config = () => {
                   <Tooltip title={'将告警数据输出到其他第三方系统'}>
                     <QuestionCircleOutlined style={{ marginLeft: 5 }} />
                   </Tooltip>
+                  <a
+                    style={{ marginLeft: 10 }}
+                    onClick={() => {
+                      setOutputVisible(true);
+                    }}
+                  >
+                    <EditOutlined />
+                  </a>
                 </span>
               }
             />
-            <Form form={outputForm} layout="vertical">
-              <SchemaField schema={outputSchema} />
-            </Form>
+            <Descriptions bordered column={2}>
+              <Descriptions.Item label="kafka地址">
+                {output?.config?.config?.kafka || ''}
+              </Descriptions.Item>
+              <Descriptions.Item label="topic">
+                {output?.config?.config?.topic || ''}
+              </Descriptions.Item>
+              <Descriptions.Item label="状态" span={2}>
+                <Badge status={output?.state?.value === 'enabled' ? 'success' : 'error'} />
+                {output?.state?.text || ''}
+              </Descriptions.Item>
+            </Descriptions>
             <Divider />
             <TitleComponent
               data={
@@ -483,19 +361,29 @@ const Config = () => {
                   <Tooltip title={'接收第三方系统处理的告警结果'}>
                     <QuestionCircleOutlined style={{ marginLeft: 5 }} />
                   </Tooltip>
+                  <a
+                    style={{ marginLeft: 10 }}
+                    onClick={() => {
+                      setInputVisible(true);
+                    }}
+                  >
+                    <EditOutlined />
+                  </a>
                 </span>
               }
             />
-            <Form form={inputForm} layout="vertical">
-              <SchemaField schema={inputSchema} />
-              <FormButtonGroup.Sticky>
-                <FormButtonGroup.FormItem>
-                  <Button type="primary" onClick={handleSaveIO}>
-                    保存
-                  </Button>
-                </FormButtonGroup.FormItem>
-              </FormButtonGroup.Sticky>
-            </Form>
+            <Descriptions bordered column={2}>
+              <Descriptions.Item label="kafka地址">
+                {input?.config?.config?.kafka || ''}
+              </Descriptions.Item>
+              <Descriptions.Item label="topic">
+                {input?.config?.config?.topic || ''}
+              </Descriptions.Item>
+              <Descriptions.Item label="状态" span={2}>
+                <Badge status={input?.state?.value === 'enabled' ? 'success' : 'error'} />
+                {input?.state?.text || ''}
+              </Descriptions.Item>
+            </Descriptions>
           </Card>
         </div>
       </Col>
@@ -541,6 +429,20 @@ const Config = () => {
   return (
     <PageContainer onTabChange={setTab} tabActiveKey={tab} tabList={list}>
       {list.find((k) => k.key === tab)?.component}
+      {inputVisible && (
+        <InputSave
+          close={() => {
+            setInputVisible(false);
+          }}
+        />
+      )}
+      {outputVisible && (
+        <OutputSave
+          close={() => {
+            setOutputVisible(false);
+          }}
+        />
+      )}
     </PageContainer>
   );
 };

+ 1 - 0
src/pages/rule-engine/Alarm/Configuration/index.tsx

@@ -276,6 +276,7 @@ const Configuration = () => {
         scroll={{ x: 1366 }}
         params={param}
         columns={columns}
+        columnEmptyText={''}
         request={(params) =>
           service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
         }

+ 1 - 0
src/pages/rule-engine/Instance/index.tsx

@@ -278,6 +278,7 @@ const Instance = () => {
         actionRef={actionRef}
         params={searchParams}
         scroll={{ x: 1366 }}
+        columnEmptyText={''}
         options={{ fullScreen: true }}
         request={(params) =>
           service.query({

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

@@ -263,6 +263,7 @@ const Scene = () => {
         actionRef={actionRef}
         scroll={{ x: 1366 }}
         params={searchParams}
+        columnEmptyText={''}
         options={{ fullScreen: true }}
         request={(params) =>
           service.query({

+ 76 - 107
src/pages/system/Basis/index.tsx

@@ -1,70 +1,30 @@
-import { Card, Form, Input, Select, Upload } from 'antd';
+import { UploadImage } from '@/components';
+import { Card, Col, Form, Input, Row, Select } from 'antd';
+import Service from './service';
 import { useModel } from '@@/plugin-model/useModel';
-import { useEffect, useState } from 'react';
 import usePermissions from '@/hooks/permission';
 import { PermissionButton } from '@/components';
-import { UploadProps } from 'antd/lib/upload';
-import Token from '@/utils/token';
+import { onlyMessage } from '@/utils/util';
 import SystemConst from '@/utils/const';
-import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
-import styles from './index.less';
-import { PageContainer } from '@ant-design/pro-layout';
-import Service from './service';
+import { useEffect } from 'react';
 import { useDomFullHeight } from '@/hooks';
-import { onlyMessage } from '@/utils/util';
+import { PageContainer } from '@ant-design/pro-layout';
 
 const Basis = () => {
-  const service = new Service();
-  const [form] = Form.useForm();
   const { initialState, setInitialState } = useModel('@@initialState');
   const { permission: userPermission } = usePermissions('system/Basis');
-  const [imageUrl, setImageUrl] = useState<string>('');
-  const [loading, setLoading] = useState(false);
+  const [form] = Form.useForm();
+  const service = new Service();
   const { minHeight } = useDomFullHeight(`.basis`);
 
-  const uploadProps: UploadProps = {
-    showUploadList: false,
-    listType: 'picture-card',
-    accept: 'image/jpeg,image/png',
-    action: `/${SystemConst.API_BASE}/file/static`,
-    headers: {
-      'X-Access-Token': Token.get(),
-    },
-    beforeUpload(file) {
-      const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
-      if (!isJpgOrPng) {
-        onlyMessage('请上传.png.jpg格式的文件', 'error');
-      }
-      return isJpgOrPng;
-    },
-    onChange(info) {
-      if (info.file.status === 'uploading') {
-        setLoading(true);
-      }
-      if (info.file.status === 'done') {
-        setImageUrl(info.file.response.result);
-        setLoading(false);
-      }
-    },
-  };
-  const uploadButton = (
-    <div>
-      {loading ? <LoadingOutlined /> : <PlusOutlined />}
-      <div style={{ marginTop: 8 }}>Upload</div>
-    </div>
-  );
-
   const detail = async (data: any) => {
     const res = await service.detail(data);
     if (res.status === 200) {
       const basis = res.result?.filter((item: any) => item.scope === 'basis');
       const api = res?.result.filter((item: any) => item.scope === 'api');
       localStorage.setItem(SystemConst.AMAP_KEY, api[0].properties.api);
-      // console.log(basis?.[0])
-      setImageUrl(basis[0].properties?.logo);
       form.setFieldsValue({
-        title: basis[0].properties.title,
-        headerTheme: basis[0].properties.headerTheme,
+        ...basis[0].properties,
         apikey: api[0].properties.api,
       });
       setInitialState({
@@ -77,15 +37,14 @@ const Basis = () => {
   };
   const save = async () => {
     const formData = await form.validateFields();
-    if (formData && imageUrl !== '') {
+    console.log(formData);
+    if (formData) {
       const item = [
         {
           scope: 'basis',
           properties: {
-            title: formData.title,
-            headerTheme: formData.headerTheme,
-            navTheme: formData.headerTheme,
-            logo: imageUrl,
+            ...formData,
+            apikey: '',
           },
         },
         {
@@ -100,32 +59,19 @@ const Basis = () => {
         onlyMessage('保存成功');
         detail(['basis', 'api']);
       }
-    } else {
-      onlyMessage('请上传图片', 'error');
     }
   };
 
   useEffect(() => {
     detail(['basis', 'api']);
   }, []);
-
   return (
     <PageContainer>
       <Card className="basis" style={{ minHeight }}>
-        <div
-          style={{
-            display: 'flex',
-            alignItems: 'flex-start',
-            justifyContent: 'flex-start',
-          }}
-        >
-          <div style={{ width: 400 }}>
-            <Form layout="vertical" form={form}>
-              <Form.Item
-                label="系统名称"
-                name="title"
-                // rules={[{ required: true, message: '请输入系统名称' }]}
-              >
+        <Form layout="vertical" form={form}>
+          <Row gutter={[24, 24]}>
+            <Col span={10}>
+              <Form.Item label="系统名称" name="title">
                 <Input />
               </Form.Item>
               <Form.Item
@@ -146,42 +92,65 @@ const Basis = () => {
               >
                 <Input />
               </Form.Item>
-            </Form>
-          </div>
-          <div className={styles.content}>
-            <div style={{ marginBottom: 8, display: 'flex' }}>
-              系统logo
-              <div className={styles.text}></div>
-            </div>
-            <Upload {...uploadProps}>
-              {imageUrl ? (
-                <img
-                  src={imageUrl}
-                  alt="avatar"
-                  onError={() => {
-                    setImageUrl(require('/public/images/img-miss.png'));
-                  }}
-                  style={{ width: '100%', height: '100%', objectFit: 'contain' }}
-                />
-              ) : (
-                uploadButton
-              )}
-            </Upload>
-          </div>
-          <div style={{ paddingTop: 215, color: '#a2a5a7' }}>推荐分辨率200*200</div>
-        </div>
-        <div>
-          <PermissionButton
-            type="primary"
-            key="basis"
-            onClick={async () => {
-              save();
-            }}
-            isPermission={userPermission.update}
-          >
-            保存
-          </PermissionButton>
-        </div>
+              <Row gutter={[24, 24]}>
+                <Col>
+                  <Form.Item
+                    name={'logo'}
+                    label="系统logo"
+                    extra={
+                      <>
+                        <div>推荐尺寸200*200</div>
+                        <div>支持jpg,png</div>
+                      </>
+                    }
+                  >
+                    <UploadImage />
+                  </Form.Item>
+                </Col>
+                <Col>
+                  <Form.Item
+                    name={'ico'}
+                    label="浏览器页签"
+                    tooltip="浏览器tab页中显示的图片元素"
+                    extra={
+                      <>
+                        <div>推荐尺寸64*64</div>
+                        <div>支持ico格式</div>
+                      </>
+                    }
+                  >
+                    <UploadImage size={1} types={['image/x-icon']} backgroundSize={'inherit'} />
+                  </Form.Item>
+                </Col>
+              </Row>
+            </Col>
+            <Col span={14}>
+              <Form.Item
+                name={'backgroud'}
+                label="登录背景图"
+                extra={
+                  <>
+                    <div>支持4M以内的图片:支持jpg、png</div>
+                    <div>建议尺寸1400x1080</div>
+                  </>
+                }
+                rules={[{ required: true, message: '请上传背景图' }]}
+              >
+                <UploadImage size={4} style={{ width: 570, height: 415 }} />
+              </Form.Item>
+            </Col>
+          </Row>
+        </Form>
+        <PermissionButton
+          type="primary"
+          key="basis"
+          onClick={() => {
+            save();
+          }}
+          isPermission={userPermission.update}
+        >
+          保存
+        </PermissionButton>
       </Card>
     </PageContainer>
   );

+ 90 - 0
src/pages/system/Basis/init.tsx

@@ -0,0 +1,90 @@
+import { UploadImage } from '@/components';
+import { Card, Col, Form, Input, Row, Select } from 'antd';
+import { useEffect } from 'react';
+
+interface Props {
+  getData: Function;
+}
+
+const Init = (props: Props) => {
+  const [form] = Form.useForm();
+
+  useEffect(() => {
+    props.getData(form);
+  }, []);
+
+  return (
+    <Card>
+      <Form layout="vertical" form={form}>
+        <Row gutter={[24, 24]}>
+          <Col span={10}>
+            <Form.Item label="系统名称" name="title">
+              <Input />
+            </Form.Item>
+            <Form.Item
+              label="主题色"
+              name="headerTheme"
+              initialValue="light"
+              rules={[{ required: true, message: '请选择主题色' }]}
+            >
+              <Select>
+                <Select.Option value="light">白色</Select.Option>
+                <Select.Option value="dark">黑色</Select.Option>
+              </Select>
+            </Form.Item>
+            <Form.Item label="高德API Key" name="apikey" tooltip="配置后平台可调用高德地图GIS服务">
+              <Input />
+            </Form.Item>
+            <Row gutter={[24, 24]}>
+              <Col>
+                <Form.Item
+                  name={'logo'}
+                  label="系统logo"
+                  extra={
+                    <>
+                      <div>推荐尺寸200*200</div>
+                      <div>支持jpg,png</div>
+                    </>
+                  }
+                >
+                  <UploadImage />
+                </Form.Item>
+              </Col>
+              <Col>
+                <Form.Item
+                  name={'ico'}
+                  label="浏览器页签"
+                  tooltip="浏览器tab页中显示的图片元素"
+                  extra={
+                    <>
+                      <div>推荐尺寸64*64</div>
+                      <div>支持ico格式</div>
+                    </>
+                  }
+                >
+                  <UploadImage size={1} types={['image/x-icon']} backgroundSize={'inherit'} />
+                </Form.Item>
+              </Col>
+            </Row>
+          </Col>
+          <Col span={14}>
+            <Form.Item
+              name={'backgroud'}
+              label="登录背景图"
+              extra={
+                <>
+                  <div>支持4M以内的图片:支持jpg、png</div>
+                  <div>建议尺寸1400x1080</div>
+                </>
+              }
+              rules={[{ required: true, message: '请上传背景图' }]}
+            >
+              <UploadImage size={4} style={{ width: 570, height: 415 }} />
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Card>
+  );
+};
+export default Init;

+ 1 - 0
src/pages/system/Department/Assets/deivce/bind.tsx

@@ -213,6 +213,7 @@ const Bind = observer((props: Props) => {
           rowKey="id"
           search={false}
           gridColumn={2}
+          columnEmptyText={''}
           cardRender={(record) => (
             <ExtraDeviceCard showBindBtn={false} showTool={false} {...record} cardType={'bind'} />
           )}

+ 1 - 0
src/pages/system/Department/Assets/deivce/index.tsx

@@ -328,6 +328,7 @@ export default observer((props: { parentId: string }) => {
         rowKey="id"
         search={false}
         params={searchParam}
+        columnEmptyText={''}
         gridColumn={2}
         height={'none'}
         scroll={{ x: 1366 }}

+ 1 - 0
src/pages/system/Department/Assets/product/bind.tsx

@@ -133,6 +133,7 @@ const Bind = observer((props: Props) => {
             rowKey="id"
             search={false}
             gridColumn={2}
+            columnEmptyText={''}
             rowSelection={{
               selectedRowKeys: Models.bindKeys,
               onChange: (selectedRowKeys, selectedRows) => {

+ 1 - 0
src/pages/system/Department/Assets/product/index.tsx

@@ -277,6 +277,7 @@ export default observer((props: { parentId: string }) => {
         search={false}
         gridColumn={2}
         params={searchParam}
+        columnEmptyText={''}
         height={'none'}
         request={async (params) => {
           params.sorts = [{ name: 'createTime', order: 'desc' }];

+ 4 - 4
src/pages/user/Login/index.less

@@ -130,10 +130,10 @@
   width: 73%;
   height: 100%;
   // padding: 112px 80px;
-  background-image: url('/images/login.png');
-  background-repeat: no-repeat;
-  background-position: center;
-  background-size: cover;
+  // background-image: url('/images/login.png');
+  // background-repeat: no-repeat;
+  // background-position: center;
+  // background-size: cover;
 
   .systemName {
     width: 100%;

+ 12 - 1
src/pages/user/Login/index.tsx

@@ -75,6 +75,14 @@ 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) => {
@@ -243,7 +251,10 @@ const Login: React.FC = () => {
           </div>
         </div>
         <div className={styles.right}>
-          {/* <img src={require('/public/images/login.png')}/> */}
+          <img
+            src={require('/public/images/login.png')}
+            style={{ width: '100%', height: '100%' }}
+          />
           {/*<div className={styles.systemName}>{SystemConst.SYSTEM_NAME}</div>*/}
           {/*<div className={styles.systemDesc}>OPEN SOURCE INTERNET OF THINGS BASIC PLATFORM</div>*/}
         </div>

+ 3 - 0
src/utils/menu/index.ts

@@ -37,6 +37,9 @@ const extraRouteObj = {
       { code: 'Save2', name: '测试详情' },
     ],
   },
+  'device/Firmware': {
+    children: [{ code: 'Task', name: '升级任务' }],
+  },
   'link/Channel': {
     children: [
       {

+ 4 - 9
src/utils/menu/router.ts

@@ -16,8 +16,6 @@ export enum MENUS_CODE {
   'device/Category' = 'device/Category',
   'device/Command' = 'device/Command',
   'device/DataSource' = 'device/DataSource',
-  'device/Firmware/Save' = 'device/Firmware/Save',
-  'device/Firmware' = 'device/Firmware',
   'device/Instance' = 'device/Instance',
   'device/Location' = 'device/Location',
   'device/Product/Save' = 'device/Product/Save',
@@ -102,12 +100,9 @@ export enum MENUS_CODE {
   'visualization/Category' = 'visualization/Category',
   'visualization/Configuration' = 'visualization/Configuration',
   'visualization/Screen' = 'visualization/Screen',
-  'device/Firmware/Detail/History' = 'device/Firmware/Detail/History',
-  'device/Firmware/Detail/Task/Detail' = 'device/Firmware/Detail/Task/Detail',
-  'device/Firmware/Detail/Task/Release' = 'device/Firmware/Detail/Task/Release',
-  'device/Firmware/Detail/Task/Save' = 'device/Firmware/Detail/Task/Save',
-  'device/Firmware/Detail/Task' = 'device/Firmware/Detail/Task',
-  'device/Firmware/Detail' = 'device/Firmware/Detail',
+  'device/Firmware' = 'device/Firmware',
+  'device/Firmware/Task' = 'device/Firmware/Task',
+  'device/Firmware/Task/Detail' = 'device/Firmware/Task/Detail',
   'device/Instance/Detail/Config/Tags' = 'device/Instance/Detail/Config/Tags',
   'device/Instance/Detail/Config' = 'device/Instance/Detail/Config',
   'device/Instance/Detail/Functions' = 'device/Instance/Detail/Functions',
@@ -169,7 +164,7 @@ export const getDetailNameByCode = {
   'system/Menu/Detail': '菜单详情',
   'device/Product/Detail': '产品详情',
   'device/Instance/Detail': '设备详情',
-  'device/Firmware/Detail': '固件详情',
+  'device/Firmware/Task/Detail': '详情',
   'system/Department/Detail': '部门详情',
   'system/Role/Detail': '权限配置',
   'link/Type/Detail': '网络组件详情',