xieyonghong 3 年 前
コミット
16076ccfc7
32 ファイル変更753 行追加172 行削除
  1. 1 2
      src/app.tsx
  2. 73 30
      src/components/FSelectDevices/index.tsx
  3. 81 2
      src/components/Metadata/JsonParam/index.tsx
  4. 11 2
      src/pages/Log/System/index.tsx
  5. 12 0
      src/pages/Log/service.ts
  6. 3 3
      src/pages/Northbound/DuerOS/index.tsx
  7. 2 6
      src/pages/device/Category/index.tsx
  8. 25 2
      src/pages/device/Firmware/Save/index.tsx
  9. 16 17
      src/pages/device/Firmware/Task/Save/index.tsx
  10. 229 0
      src/pages/device/Firmware/Task/Save/index1.tsx
  11. 19 15
      src/pages/device/Firmware/Task/index.tsx
  12. 5 0
      src/pages/device/Firmware/service.ts
  13. 4 4
      src/pages/device/Instance/Detail/Diagnose/Status/index.tsx
  14. 5 5
      src/pages/device/Instance/Detail/Diagnose/Status/model.ts
  15. 3 1
      src/pages/device/Product/Detail/Access/AccessConfig/index.tsx
  16. 5 1
      src/pages/device/Product/Detail/PropertyImport/index.tsx
  17. 6 0
      src/pages/device/Product/service.ts
  18. 5 0
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  19. 2 2
      src/pages/device/components/Metadata/Base/index.tsx
  20. 12 0
      src/pages/device/components/Metadata/Import/index.tsx
  21. 8 6
      src/pages/home/index.tsx
  22. 3 3
      src/pages/home/init/accountInit.tsx
  23. 9 1
      src/pages/home/service.ts
  24. 35 34
      src/pages/link/Channel/Modbus/index.tsx
  25. 10 0
      src/pages/link/Channel/Modbus/service.ts
  26. 25 18
      src/pages/link/Type/Detail/index.tsx
  27. 1 0
      src/pages/rule-engine/Alarm/Configuration/Save/index.tsx
  28. 120 11
      src/pages/rule-engine/Alarm/Log/TabComponent/index.tsx
  29. 6 6
      src/pages/rule-engine/Alarm/Log/model.ts
  30. 15 0
      src/pages/rule-engine/Alarm/Log/service.ts
  31. 1 1
      src/pages/system/DataSource/Management/RemoveData.tsx
  32. 1 0
      src/pages/system/Role/Detail/UserManage/BindUser.tsx

+ 1 - 2
src/app.tsx

@@ -314,6 +314,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
         ]
       : [],
     menuHeaderRender: undefined,
+    ...initialState?.settings,
     // 自定义 403 页面
     // unAccessible: <div>unAccessible</div>,
     pageTitleRender: (_props, _, info) => {
@@ -336,8 +337,6 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
         {initialState?.settings?.title}
       </div>
     ),
-    ...initialState?.settings,
-    // logo:''
   };
 };
 

+ 73 - 30
src/components/FSelectDevices/index.tsx

@@ -1,28 +1,38 @@
-import { Input, Modal } from 'antd';
+import { Badge, Input, Modal } from 'antd';
 import { EditOutlined } from '@ant-design/icons';
 import type { Key } from 'react';
 import { useRef, useState } from 'react';
-import { connect } from '@formily/react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import type { DeviceInstance } from '@/pages/device/Instance/typings';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import Service from '@/pages/device/Instance/service';
 import SearchComponent from '../SearchComponent';
-import _ from 'lodash';
+import { observer } from '@formily/react';
+import { model } from '@formily/reactive';
 
 interface Props {
-  value: Partial<DeviceInstance>[];
-  onChange: (data: Partial<DeviceInstance>[]) => void;
+  value?: Partial<DeviceInstance>[];
+  onChange?: (data: Partial<DeviceInstance>[]) => void;
   productId?: string;
 }
 
-export const service = new Service('device/instance');
-const FSelectDevices = connect((props: Props) => {
+const deviceStatus = new Map();
+deviceStatus.set('online', <Badge status="success" text={'在线'} />);
+deviceStatus.set('offline', <Badge status="error" text={'离线'} />);
+deviceStatus.set('notActive', <Badge status="processing" text={'禁用'} />);
+
+const service = new Service('device/instance');
+const State = model<{
+  visible: boolean;
+}>({
+  visible: false,
+});
+
+const FSelectDevices = observer((props: Props) => {
   // todo 考虑与单选设备合并
-  const [visible, setVisible] = useState<boolean>(false);
   const intl = useIntl();
-  const actionRef = useRef<ActionType>();
+  const actionRef1 = useRef<ActionType>();
   const [searchParam, setSearchParam] = useState({});
   const columns: ProColumns<DeviceInstance>[] = [
     {
@@ -53,50 +63,87 @@ const FSelectDevices = connect((props: Props) => {
       width: '200px',
       valueType: 'dateTime',
     },
+    {
+      title: '状态',
+      dataIndex: 'state',
+      render: (text: any, record: any) =>
+        record?.state?.value ? deviceStatus.get(record?.state?.value) : '',
+      ellipsis: true,
+    },
   ];
 
   const [data, setData] = useState<Partial<DeviceInstance>[]>(props?.value || []);
   const rowSelection = {
-    onChange: (selectedRowKeys: Key[], selectedRows: DeviceInstance[]) => {
-      const list = [...data];
-      selectedRows.map((item) => {
-        if (!_.map(data, 'id').includes(item.id)) {
-          list.push(item);
-        }
-      });
-      setData(list);
+    onSelect: (selectedRow: any, selected: any) => {
+      let newSelectKeys = [...data];
+      if (selected) {
+        newSelectKeys.push({ ...selectedRow });
+      } else {
+        newSelectKeys = newSelectKeys.filter((item) => item.id !== selectedRow.id);
+      }
+      setData(newSelectKeys);
+    },
+    onSelectAll: (selected: boolean, _: any, changeRows: any) => {
+      let newSelectKeys = [...data];
+      if (selected) {
+        changeRows.forEach((item: any) => {
+          newSelectKeys.push({ ...item });
+        });
+      } else {
+        newSelectKeys = newSelectKeys.filter((a) => {
+          return !changeRows.some((b: any) => b.id === a.id);
+        });
+      }
+      setData(newSelectKeys);
     },
     selectedRowKeys: data?.map((item) => item.id) as Key[],
   };
 
+  const reload = () => {
+    actionRef1.current?.reset?.();
+    setSearchParam({});
+  };
+
   return (
     <>
       <Input
         disabled
         value={props.value?.map((item) => item.name).join(',')}
-        addonAfter={<EditOutlined onClick={() => setVisible(true)} />}
+        addonAfter={
+          <EditOutlined
+            onClick={() => {
+              State.visible = true;
+            }}
+          />
+        }
       />
-      {visible && (
+      {State.visible && (
         <Modal
           maskClosable={false}
           visible
           title="选择设备"
           width="80vw"
-          onCancel={() => setVisible(false)}
+          onCancel={() => {
+            State.visible = false;
+            reload();
+          }}
           onOk={() => {
-            setVisible(false);
-            props.onChange(data);
+            State.visible = false;
+            reload();
+            if (props.onChange) {
+              props.onChange(data);
+            }
           }}
         >
           <SearchComponent<DeviceInstance>
             field={columns}
             enableSave={false}
             model="simple"
-            onSearch={async (data1) => {
+            onSearch={(data1) => {
+              actionRef1.current?.reset?.();
               setSearchParam(data1);
-              actionRef.current?.reset?.();
             }}
-            target="choose-device"
+            target="choose-devices"
           />
           <ProTable<DeviceInstance>
             tableAlertRender={false}
@@ -106,14 +153,10 @@ const FSelectDevices = connect((props: Props) => {
             }}
             search={false}
             columnEmptyText={''}
-            toolBarRender={false}
             rowKey="id"
-            pagination={{
-              pageSize: 10,
-            }}
             params={searchParam}
             columns={columns}
-            actionRef={actionRef}
+            actionRef={actionRef1}
             request={async (params) => {
               return service.queryDetailList({
                 context: {

+ 81 - 2
src/components/Metadata/JsonParam/index.tsx

@@ -9,11 +9,15 @@ import {
 } from '@formily/antd';
 import { createSchemaField, observer } from '@formily/react';
 import type { ISchema } from '@formily/json-schema';
-import { DataTypeList, DateTypeList } from '@/pages/device/data';
+import { DataTypeList, DateTypeList, FileTypeList } from '@/pages/device/data';
 import { Store } from 'jetlinks-store';
 import { useAsyncDataSource } from '@/utils/util';
 import { service } from '@/pages/device/components/Metadata';
 import MetadataModel from '@/pages/device/components/Metadata/Base/model';
+import BooleanEnum from '@/components/Metadata/BooleanParam';
+import EnumParam from '@/components/Metadata/EnumParam';
+import ArrayParam from '@/components/Metadata/ArrayParam';
+import { useIntl } from '@/.umi/plugin-locale/localeExports';
 
 // 不算是自定义组件。只是抽离了JSONSchema
 interface Props {
@@ -21,6 +25,7 @@ interface Props {
 }
 
 const JsonParam = observer((props: Props) => {
+  const intl = useIntl();
   const SchemaField = createSchemaField({
     components: {
       FormItem,
@@ -31,6 +36,9 @@ const JsonParam = observer((props: Props) => {
       Editable,
       FormLayout,
       NumberPicker,
+      BooleanEnum,
+      EnumParam,
+      ArrayParam,
     },
   });
   const getUnit = () =>
@@ -113,6 +121,71 @@ const JsonParam = observer((props: Props) => {
                           ? DataTypeList.filter((item) => item.value !== 'file')
                           : DataTypeList,
                     },
+                    booleanConfig: {
+                      title: '布尔值',
+                      type: 'void',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'BooleanEnum',
+                      'x-reactions': {
+                        dependencies: ['..valueType.type'],
+                        fulfill: {
+                          state: {
+                            visible: "{{['boolean'].includes($deps[0])}}",
+                          },
+                        },
+                      },
+                    },
+                    enumConfig: {
+                      title: intl.formatMessage({
+                        id: 'pages.device.productDetail.metadata.enum',
+                        defaultMessage: '枚举项',
+                      }),
+                      type: 'void',
+                      'x-decorator': 'FormItem',
+                      'x-component': 'EnumParam',
+                      'x-reactions': {
+                        dependencies: ['..valueType.type'],
+                        fulfill: {
+                          state: {
+                            visible: "{{['enum'].includes($deps[0])}}",
+                          },
+                        },
+                      },
+                    },
+                    elementType: {
+                      title: intl.formatMessage({
+                        id: 'pages.device.productDetail.metadata.elementConfiguration',
+                        defaultMessage: '元素配置',
+                      }),
+                      'x-decorator': 'FormItem',
+                      'x-component': 'ArrayParam',
+                      'x-reactions': {
+                        dependencies: ['..valueType.type'],
+                        fulfill: {
+                          state: {
+                            visible: "{{['array'].includes($deps[0])}}",
+                          },
+                        },
+                      },
+                    },
+                    fileType: {
+                      title: intl.formatMessage({
+                        id: 'pages.device.productDetail.metadata.fileType',
+                        defaultMessage: '文件类型',
+                      }),
+                      'x-decorator': 'FormItem',
+                      'x-component': 'Select',
+                      'x-visible': false,
+                      enum: FileTypeList,
+                      'x-reactions': {
+                        dependencies: ['..valueType.type'],
+                        fulfill: {
+                          state: {
+                            visible: "{{['file'].includes($deps[0])}}",
+                          },
+                        },
+                      },
+                    },
                     unit: {
                       title: '单位',
                       'x-decorator': 'FormItem',
@@ -160,11 +233,17 @@ const JsonParam = observer((props: Props) => {
                           title: '最大长度',
                           'x-decorator': 'FormItem',
                           'x-component': 'NumberPicker',
+                          'x-decorator-props': {
+                            tooltip: intl.formatMessage({
+                              id: 'pages.device.productDetail.metadata.maxLength.byte',
+                              defaultMessage: '字节',
+                            }),
+                          },
                           'x-reactions': {
                             dependencies: ['..type'],
                             fulfill: {
                               state: {
-                                visible: "{{['string'].includes($deps[0])}}",
+                                visible: "{{['string','password'].includes($deps[0])}}",
                               },
                             },
                           },

+ 11 - 2
src/pages/Log/System/index.tsx

@@ -5,13 +5,14 @@ import ProTable from '@jetlinks/pro-table';
 import type { SystemLogItem } from '@/pages/Log/System/typings';
 import { Tag, Tooltip } from 'antd';
 import moment from 'moment';
-import BaseService from '@/utils/BaseService';
+// import BaseService from '@/utils/BaseService';
+import Service from '../service';
 import { EyeOutlined } from '@ant-design/icons';
 import SearchComponent from '@/components/SearchComponent';
 import Detail from '@/pages/Log/System/Detail';
 import { useDomFullHeight } from '@/hooks';
 
-const service = new BaseService<SystemLogItem>('logger/system');
+const service = new Service('logger/system');
 const System = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
@@ -71,7 +72,15 @@ const System = () => {
       dataIndex: 'context.server',
       width: 150,
       ellipsis: true,
+      valueType: 'select',
       render: (text, record) => record?.context?.server || '',
+      request: async () => {
+        const res = await service.getServer();
+        if (res.status === 200) {
+          return res.result.map((item: any) => ({ label: item.name, value: item.id }));
+        }
+        return [];
+      },
     },
     {
       title: intl.formatMessage({

+ 12 - 0
src/pages/Log/service.ts

@@ -0,0 +1,12 @@
+import { request } from 'umi';
+import BaseService from '@/utils/BaseService';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<any> {
+  public getServer = () =>
+    request(`/${SystemConst.API_BASE}/network/resources/clusters`, {
+      method: 'GET',
+    });
+}
+
+export default Service;

+ 3 - 3
src/pages/Northbound/DuerOS/index.tsx

@@ -141,16 +141,16 @@ export default () => {
     {
       title: intl.formatMessage({
         id: 'page.cloud.duerOS.productName',
-        defaultMessage: '产品',
+        defaultMessage: '产品名称',
       }),
       dataIndex: 'productName',
-      hideInSearch: true,
+      // hideInSearch: true,
       ellipsis: true,
       valueType: 'select',
       request: async () => {
         const res = await service.getProduct();
         if (res.status === 200) {
-          return res.result.map((pItem: any) => ({ label: pItem.name, value: pItem.id }));
+          return res.result.map((pItem: any) => ({ label: pItem.name, value: pItem.name }));
         }
         return [];
       },

+ 2 - 6
src/pages/device/Category/index.tsx

@@ -70,13 +70,9 @@ const Category = observer(() => {
     {
       title: '分类排序',
       dataIndex: 'sortIndex',
-      // valueType: 'digit',
+      valueType: 'digit',
       sorter: true,
-      // render: (text) => (
-      //   <Space>{text}<EditOutlined onClick={() => {
-
-      //   }} /></Space>
-      // )
+      render: (_, record) => <>{record.sortIndex}</>,
     },
     {
       title: intl.formatMessage({

+ 25 - 2
src/pages/device/Firmware/Save/index.tsx

@@ -13,6 +13,7 @@ import { useRef } from 'react';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import { onlyMessage } from '@/utils/util';
 import RemoveData from './RemoveData';
+import _ from 'lodash';
 
 interface Props {
   data?: FirmwareItem;
@@ -41,6 +42,25 @@ const Save = (props: Props) => {
         const value = (field as Field).value;
         fileInfo.current = value;
       });
+      onFieldValueChange('productId', (field, form1) => {
+        if (field.modified) {
+          form1.setFieldState('versionOrder', (state) => {
+            state.value = undefined;
+          });
+        }
+      });
+      onFieldValueChange('versionOrder', async (field, f1) => {
+        const value = (field as Field).value;
+        const productId = (field.query('.productId').take() as Field).value;
+        if (field.modified && productId && value) {
+          const resp = await service.validateVersion(productId, value);
+          if (resp.status === 200) {
+            f1.setFieldState('versionOrder', (state) => {
+              state.selfErrors = resp.result ? ['版本序号已存在'] : undefined;
+            });
+          }
+        }
+      });
     },
   });
 
@@ -50,7 +70,10 @@ const Save = (props: Props) => {
     field.loading = true;
     services(field).then(
       action.bound!((list: any) => {
-        field.dataSource = list.result.map((item: any) => ({ label: item.name, value: item.id }));
+        const _data = list.result.filter((it: any) => {
+          return _.map(it?.features || [], 'id').includes('supportFirmware');
+        });
+        field.dataSource = _data.map((item: any) => ({ label: item.name, value: item.id }));
         products.current = list.result;
         field.loading = false;
       }),
@@ -65,7 +88,7 @@ const Save = (props: Props) => {
           value: 1,
         },
       ],
-      sorts: [{ name: 'name', order: 'desc' }],
+      sorts: [{ name: 'createTime', order: 'desc' }],
     });
 
   const SchemaField = createSchemaField({

+ 16 - 17
src/pages/device/Firmware/Task/Save/index.tsx

@@ -2,13 +2,12 @@ import { Modal } from 'antd';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import { createSchemaField } from '@formily/react';
 import { Form, FormGrid, FormItem, Input, Select, NumberPicker, Radio } from '@formily/antd';
-import { createForm, onFieldValueChange, onFormInit } from '@formily/core';
+import { createForm, onFieldValueChange } from '@formily/core';
 import type { ISchema } from '@formily/json-schema';
 import { service } from '@/pages/device/Firmware';
-import { useEffect, useMemo, useRef } from 'react';
+import { useEffect, useMemo } from 'react';
 import { onlyMessage } from '@/utils/util';
 import FSelectDevices from '@/components/FSelectDevices';
-import type { DeviceInstance } from '@/pages/device/Instance/typings';
 
 interface Props {
   ids: { id: string; productId: string };
@@ -27,10 +26,10 @@ const Save = (props: Props) => {
         validateFirst: true,
         initialValues: {},
         effects() {
-          onFormInit(async (form1) => {
-            if (!data?.id) return;
-            form1.setInitialValues({ ...data, upload: { url: data?.url } });
-          });
+          // onFormInit(async (form1) => {
+          //   if (!data?.id) return;
+          //   form1.setInitialValues({ ...data, upload: { url: data?.url } });
+          // });
           onFieldValueChange('mode', async (field) => {
             field
               .query('timeoutSeconds')
@@ -55,7 +54,7 @@ const Save = (props: Props) => {
     [],
   );
 
-  const devices = useRef<DeviceInstance[]>([]);
+  // const devices = useRef<DeviceInstance[]>([]);
 
   const SchemaField = createSchemaField({
     components: {
@@ -70,19 +69,19 @@ const Save = (props: Props) => {
   });
 
   useEffect(() => {
-    if (visible) {
-      service.queryDevice().then((resp) => {
-        if (resp.status === 200) {
-          devices.current = resp.result;
-        }
-      });
-    }
+    // if (visible) {
+    //   service.queryDevice().then((resp) => {
+    //     if (resp.status === 200) {
+    //       devices.current = resp.result;
+    //     }
+    //   });
+    // }
   }, [visible]);
 
   const save = async () => {
     const values: any = await form.submit();
     if (values?.releaseType !== 'all') {
-      values.deviceId = devices.current.map((item) => item.id);
+      // values.deviceId = devices.current.map((item) => item.id);
     } else {
       values.deviceId = undefined;
     }
@@ -211,7 +210,7 @@ const Save = (props: Props) => {
             },
           },
           releaseType: {
-            type: 'number',
+            type: 'string',
             title: '升级设备',
             default: 'all',
             'x-visible': false,

+ 229 - 0
src/pages/device/Firmware/Task/Save/index1.tsx

@@ -0,0 +1,229 @@
+import { Col, Form, Input, InputNumber, message, Modal, Radio, Row, Select } from 'antd';
+import type { FirmwareItem } from '@/pages/device/Firmware/typings';
+import FSelectDevices from '@/components/FSelectDevices';
+import { useEffect, useRef, useState } from 'react';
+import type { DeviceInstance } from '@/pages/device/Instance/typings';
+import { service } from '@/pages/device/Firmware';
+import { onlyMessage } from '@/utils/util';
+
+interface Props {
+  ids: { id: string; productId: string };
+  data?: FirmwareItem;
+  close: () => void;
+  save: () => void;
+}
+
+const Save = (props: Props) => {
+  const { data, close, ids } = props;
+  const [mode, setMode] = useState<'push' | 'pull' | undefined>(undefined);
+  const [releaseType, setReleaseType] = useState<'all' | 'part' | undefined>(undefined);
+
+  const [form] = Form.useForm();
+
+  const devices = useRef<DeviceInstance[]>([]);
+
+  useEffect(() => {
+    service.queryDevice().then((resp) => {
+      if (resp.status === 200) {
+        devices.current = resp.result;
+      }
+    });
+  }, []);
+
+  const save = async () => {
+    const values = await form.validateFields();
+    if (values?.releaseType !== 'all') {
+      values.deviceId = devices.current.map((item) => item.id);
+    } else {
+      values.deviceId = undefined;
+    }
+    const resp = await service.saveTask({
+      ...values,
+      firmwareId: ids?.id,
+      productId: ids?.productId,
+    });
+    if (resp.status === 200) {
+      onlyMessage('保存成功!');
+      props.save();
+      form.resetFields();
+      setMode(undefined);
+      setReleaseType(undefined);
+    } else {
+      message.error('保存失败!');
+    }
+  };
+
+  return (
+    <Modal
+      maskClosable={false}
+      width="50vw"
+      title={data?.id ? '编辑任务' : '新增任务'}
+      onCancel={() => {
+        form.resetFields();
+        close();
+        setMode(undefined);
+        setReleaseType(undefined);
+      }}
+      onOk={() => save()}
+      visible
+    >
+      <Form form={form} name="basic" layout="vertical">
+        <Row gutter={24}>
+          <Col span={24}>
+            <Form.Item
+              label="任务名称"
+              name="name"
+              rules={[
+                {
+                  required: true,
+                  message: '请输入任务名称',
+                },
+                {
+                  max: 64,
+                  message: '最多可输入64个字符',
+                },
+              ]}
+            >
+              <Input placeholder="请输入任务名称" />
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item
+              label="推送方式"
+              name="mode"
+              rules={[
+                {
+                  required: true,
+                  message: '请选择推送方式',
+                },
+              ]}
+            >
+              <Select
+                placeholder="请选择推送方式"
+                onChange={(value) => {
+                  setMode(value);
+                }}
+              >
+                <Select.Option value="push">平台推送</Select.Option>
+                <Select.Option value="pull">设备拉取</Select.Option>
+              </Select>
+            </Form.Item>
+          </Col>
+          {mode === 'push' && (
+            <>
+              <Col span={12}>
+                <Form.Item
+                  label="响应超时时间"
+                  name="responseTimeoutSeconds"
+                  rules={[
+                    {
+                      required: true,
+                      message: '请输入响应超时时间',
+                    },
+                    {
+                      type: 'number',
+                      max: 99999,
+                      min: 1,
+                      message: '请输入1~99999之间的数字',
+                    },
+                  ]}
+                >
+                  <InputNumber style={{ width: '100%' }} placeholder="请输入响应超时时间(秒)" />
+                </Form.Item>
+              </Col>
+              <Col span={12}>
+                <Form.Item
+                  label="升级超时时间"
+                  name="timeoutSeconds"
+                  rules={[
+                    {
+                      required: true,
+                      message: '请输入升级超时时间',
+                    },
+                    {
+                      type: 'number',
+                      max: 99999,
+                      min: 1,
+                      message: '请输入1~99999之间的数字',
+                    },
+                  ]}
+                >
+                  <InputNumber style={{ width: '100%' }} placeholder="请请输入升级超时时间(秒)" />
+                </Form.Item>
+              </Col>
+            </>
+          )}
+          {mode === 'pull' && (
+            <Col span={24}>
+              <Form.Item
+                label="升级超时时间"
+                name="timeoutSeconds"
+                rules={[
+                  {
+                    required: true,
+                    message: '请输入升级超时时间',
+                  },
+                  {
+                    type: 'number',
+                    max: 99999,
+                    min: 1,
+                    message: '请输入1~99999之间的数字',
+                  },
+                ]}
+              >
+                <InputNumber style={{ width: '100%' }} placeholder="请请输入升级超时时间(秒)" />
+              </Form.Item>
+            </Col>
+          )}
+          {!!mode && (
+            <>
+              <Col span={12}>
+                <Form.Item
+                  label="升级设备"
+                  name="releaseType"
+                  rules={[
+                    {
+                      required: true,
+                      message: '请选择升级设备',
+                    },
+                  ]}
+                >
+                  <Radio.Group
+                    onChange={(e) => {
+                      setReleaseType(e.target.value);
+                    }}
+                  >
+                    <Radio value="all"> 所有设备 </Radio>
+                    <Radio value="part"> 选择设备 </Radio>
+                  </Radio.Group>
+                </Form.Item>
+              </Col>
+              {releaseType === 'part' && (
+                <Col span={12}>
+                  <Form.Item
+                    label="选择设备"
+                    name="deviceId"
+                    rules={[
+                      {
+                        required: true,
+                        message: '请选择设备',
+                      },
+                    ]}
+                  >
+                    <FSelectDevices productId={ids?.productId || ''} />
+                  </Form.Item>
+                </Col>
+              )}
+            </>
+          )}
+          <Col span={24}>
+            <Form.Item label="说明" name="description">
+              <Input.TextArea rows={3} maxLength={200} showCount={true} placeholder="请输入说明" />
+            </Form.Item>
+          </Col>
+        </Row>
+      </Form>
+    </Modal>
+  );
+};
+export default Save;

+ 19 - 15
src/pages/device/Firmware/Task/index.tsx

@@ -15,7 +15,7 @@ import { useHistory, useLocation } 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 Save from './Save/index1';
 import { onlyMessage } from '@/utils/util';
 import { PermissionButton } from '@/components';
 import useDomFullHeight from '@/hooks/document/useDomFullHeight';
@@ -26,6 +26,9 @@ import { service } from '@/pages/device/Firmware';
 
 const UpgradeBtn = (props: { data: any; actions: any }) => {
   const { data, actions } = props;
+  if (data.progress === 100) {
+    return null;
+  }
   return (
     <a>
       <Tooltip title={data.waiting ? '停止' : '继续升级'}>
@@ -106,9 +109,9 @@ const Task = observer(() => {
     {
       title: '完成比例',
       ellipsis: true,
-      // hideInSearch: true,
+      hideInSearch: true,
       dataIndex: 'progress',
-      valueType: 'digit',
+      // valueType: 'digit',
     },
     {
       title: intl.formatMessage({
@@ -223,18 +226,19 @@ const Task = observer(() => {
         columns={columns}
         actionRef={actionRef}
       />
-      <Save
-        data={state.current}
-        ids={{ id: id, productId: productId }}
-        visible={state.visible}
-        save={() => {
-          state.visible = false;
-          actionRef.current?.reload?.();
-        }}
-        close={() => {
-          state.visible = false;
-        }}
-      />
+      {state.visible && (
+        <Save
+          data={state.current}
+          ids={{ id: id, productId: productId }}
+          save={() => {
+            state.visible = false;
+            actionRef.current?.reload?.();
+          }}
+          close={() => {
+            state.visible = false;
+          }}
+        />
+      )}
     </PageContainer>
   );
 });

+ 5 - 0
src/pages/device/Firmware/service.ts

@@ -63,6 +63,11 @@ class Service extends BaseService<FirmwareItem> {
 
   queryDevice = () =>
     request(`/${SystemConst.API_BASE}/device/instance/_query/no-paging?paging=false`);
+
+  validateVersion = (productId: string, versionOrder: number) =>
+    request(`/${SystemConst.API_BASE}/firmware/${productId}/${versionOrder}/exists`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

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

@@ -3,9 +3,6 @@ import { Badge, Button, message, Popconfirm, Space } from 'antd';
 import styles from './index.less';
 import { observer } from '@formily/reactive-react';
 import type { ListProps } from './model';
-import { urlMap } from './model';
-import { gatewayList } from './model';
-import { textColorMap } from './model';
 import {
   DiagnoseStatusModel,
   StatusMap,
@@ -13,6 +10,9 @@ import {
   childInitList,
   cloudInitList,
   mediaInitList,
+  TextColorMap,
+  gatewayList,
+  urlMap,
 } from './model';
 import type { ReactNode } from 'react';
 import { useEffect, useState } from 'react';
@@ -2006,7 +2006,7 @@ const Status = observer((props: Props) => {
                 <div className={styles.info}>{item?.info}</div>
               </div>
             </div>
-            <div className={styles.statusRight} style={{ color: textColorMap.get(item.status) }}>
+            <div className={styles.statusRight} style={{ color: TextColorMap.get(item.status) }}>
               {item?.text}
             </div>
           </div>

+ 5 - 5
src/pages/device/Instance/Detail/Diagnose/Status/model.ts

@@ -9,11 +9,11 @@ StatusMap.set('success', require('/public/images/diagnose/status/success.png'));
 StatusMap.set('warning', require('/public/images/diagnose/status/warning.png'));
 StatusMap.set('loading', require('/public/images/diagnose/status/loading.png'));
 
-export const textColorMap = new Map();
-textColorMap.set('loading', 'black');
-textColorMap.set('error', 'red');
-textColorMap.set('success', 'green');
-textColorMap.set('warning', 'red');
+export const TextColorMap = new Map();
+TextColorMap.set('loading', 'black');
+TextColorMap.set('error', 'red');
+TextColorMap.set('success', 'green');
+TextColorMap.set('warning', 'red');
 
 export type ListProps = {
   key: string;

+ 3 - 1
src/pages/device/Product/Detail/Access/AccessConfig/index.tsx

@@ -136,7 +136,9 @@ const AccessConfig = (props: Props) => {
         oldData.push(item);
       }
     });
-    return oldData;
+    return oldData.map((item, index) => {
+      return { ...item, sortsIndex: index };
+    });
   };
 
   return (

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

@@ -31,7 +31,11 @@ const NormalUpload = (props: any) => {
 
     const target = typeMap.get(props.type);
 
-    const _data = updateMetadata('properties', _metadata.properties, target) as ProductItem;
+    const properties = (_metadata?.properties || []).map((item, index) => {
+      return { ...item, sortsIndex: index };
+    });
+
+    const _data = updateMetadata('properties', properties, target) as ProductItem;
     // const resp = await service.update(_product);
     const resp = await asyncUpdateMedata(props.type, _data);
     if (resp.status === 200) {

+ 6 - 0
src/pages/device/Product/service.ts

@@ -157,6 +157,12 @@ class Service extends BaseService<ProductItem> {
       method: 'POST',
       data: {
         paging: false,
+        sorts: [
+          {
+            name: 'createTime',
+            order: 'desc',
+          },
+        ],
       },
     });
   //获取协议详情

+ 5 - 0
src/pages/device/components/Metadata/Base/Edit/index.tsx

@@ -1112,9 +1112,14 @@ const Edit = observer((props: Props) => {
 
     if (!typeMap.get(props.type)) return;
 
+    const list = await DB.getDB().table(`${type}`).toArray();
+
     const updateDB = (t: 'add' | 'update', item: MetadataItem) => {
       switch (t) {
         case 'add':
+          const dt = list.sort((a, b) => b?.sortsIndex - a?.sortsIndex) || [];
+          item.sortsIndex =
+            dt.length > 0 && dt[0]?.sortsIndex !== undefined ? dt[0].sortsIndex + 1 : 0;
           DB.getDB().table(`${type}`).add(item, item.id);
           return;
         case 'update':

+ 2 - 2
src/pages/device/components/Metadata/Base/index.tsx

@@ -121,7 +121,7 @@ const BaseMetadata = observer((props: Props) => {
 
   const initData = useCallback(async () => {
     const result = await DB.getDB().table(`${type}`).toArray();
-    setData(result);
+    setData(result.sort((a, b) => b?.sortsIndex - a?.sortsIndex));
   }, [param.id, type]);
 
   useEffect(() => {
@@ -146,7 +146,7 @@ const BaseMetadata = observer((props: Props) => {
         .where('id')
         .startsWithAnyOfIgnoreCase(name)
         .toArray();
-      setData(result);
+      setData(result.sort((a, b) => b?.sortsIndex - a?.sortsIndex));
     } else {
       await initData();
     }

+ 12 - 0
src/pages/device/components/Metadata/Import/index.tsx

@@ -197,6 +197,18 @@ const Import = (props: Props) => {
     if (fid.includes('propertyNotModifiable')) {
       obj.properties = old?.properties || [];
     }
+    obj.events.map((item, index) => {
+      return { ...item, sortsIndex: index };
+    });
+    obj.properties.map((item, index) => {
+      return { ...item, sortsIndex: index };
+    });
+    obj.functions.map((item, index) => {
+      return { ...item, sortsIndex: index };
+    });
+    obj.tags.map((item, index) => {
+      return { ...item, sortsIndex: index };
+    });
     return obj;
   };
 

+ 8 - 6
src/pages/home/index.tsx

@@ -24,7 +24,7 @@ const Home = () => {
 
   const adminView = () => {
     service
-      .setView({
+      .setViews({
         name: 'view',
         content: 'comprehensive',
       })
@@ -38,6 +38,7 @@ const Home = () => {
   useEffect(() => {
     service.userDetail().then((res) => {
       if (res.status === 200) {
+        //三方用户
         service
           .apiDetail({
             terms: [
@@ -50,17 +51,18 @@ const Home = () => {
           .then((response) => {
             if (response.status === 200) {
               setDetail(response.result?.data);
-              service.queryView().then((resp) => {
+              service.queryViews().then((resp) => {
                 setLoading(false);
                 if (resp.status === 200) {
-                  if (resp.result.length == 0) {
-                    if (response.result.username === 'admin') {
+                  if (resp.result) {
+                    setCurrent(resp.result?.content);
+                  } else {
+                    if (res.result.username === 'admin') {
+                      setCurrent('comprehensive');
                       adminView();
                     } else {
                       setCurrent('init');
                     }
-                  } else {
-                    setCurrent(resp.result[0]?.content);
                   }
                 }
               });

+ 3 - 3
src/pages/home/init/accountInit.tsx

@@ -26,9 +26,9 @@ const AccountInit = () => {
     },
   ];
   useEffect(() => {
-    service.queryView().then((res) => {
+    service.queryViews().then((res) => {
       if (res.status === 200) {
-        setValue(res.result[0]?.content);
+        setValue(res.result?.content);
       }
     });
   }, []);
@@ -56,7 +56,7 @@ const AccountInit = () => {
           type="primary"
           onClick={() => {
             service
-              .setView({
+              .setViews({
                 name: 'view',
                 content: value,
               })

+ 9 - 1
src/pages/home/service.ts

@@ -6,12 +6,20 @@ class Service {
     request(`/${SystemConst.API_BASE}/user/settings/view`, {
       method: 'GET',
     });
-
+  public queryViews = () =>
+    request(`/${SystemConst.API_BASE}/user/settings/view/user`, {
+      method: 'GET',
+    });
   public setView = (data: Record<string, any>) =>
     request(`/${SystemConst.API_BASE}/user/settings/view`, {
       method: 'POST',
       data,
     });
+  public setViews = (data: Record<string, any>) =>
+    request(`/${SystemConst.API_BASE}/user/settings/view/user`, {
+      method: 'PATCH',
+      data,
+    });
   // 设备数量
   deviceCount = (data?: any) =>
     request(`/${SystemConst.API_BASE}/device/instance/_count`, { methods: 'GET', params: data });

+ 35 - 34
src/pages/link/Channel/Modbus/index.tsx

@@ -94,22 +94,29 @@ const NewModbus = () => {
     },
     {
       title: '功能码',
-      render: (record: any) => <>{record.function?.text}</>,
+      valueType: 'select',
+      dataIndex: 'function',
+      valueEnum: {
+        Coils: { text: '线圈寄存器', status: 'Coils' },
+        HoldingRegisters: { text: '保存寄存器', status: 'HoldingRegisters' },
+        InputRegisters: { text: '输入寄存器', status: 'InputRegisters' },
+      },
+      render: (_, record: any) => <>{record.function?.text}</>,
     },
     {
       title: '从站ID',
+      valueType: 'digit',
       dataIndex: 'unitId',
-      search: false,
     },
     {
       title: '寄存器数量',
       search: false,
-      render: (record: any) => <>{record.parameter?.quantity}</>,
+      render: (_, record: any) => <>{record.parameter?.quantity}</>,
     },
     {
       title: '地址',
       dataIndex: 'address',
-      search: false,
+      valueType: 'digit',
     },
     {
       title: '当前数据',
@@ -142,29 +149,29 @@ const NewModbus = () => {
     },
     {
       title: '采集状态',
-      search: false,
-      render: (record: any) => (
+      dataIndex: 'collectState',
+      valueType: 'select',
+      valueEnum: {
+        running: { text: '采集中', status: 'running' },
+        error: { text: '失败', status: 'error' },
+        stopped: { text: '已停止', status: 'stopped' },
+      },
+      render: (_, record: any) => (
         <>
-          {record.state.value === 'disabled' ? (
-            ''
-          ) : (
-            <>
-              <Badge
-                status={collectMap.get(record.collectState?.value)}
-                text={record.collectState?.text}
-              />
-              {record.collectState?.value === 'error' && (
-                <SearchOutlined
-                  style={{ color: '#1d39c4', marginLeft: 3 }}
-                  onClick={() => {
-                    Modal.error({
-                      title: '失败原因',
-                      content: <div>{record.errorReason}</div>,
-                    });
-                  }}
-                />
-              )}
-            </>
+          <Badge
+            status={collectMap.get(record.collectState?.value)}
+            text={record.collectState?.text}
+          />
+          {record.collectState?.value === 'error' && (
+            <SearchOutlined
+              style={{ color: '#1d39c4', marginLeft: 3 }}
+              onClick={() => {
+                Modal.error({
+                  title: '失败原因',
+                  content: <div>{record.errorReason}</div>,
+                });
+              }}
+            />
           )}
         </>
       ),
@@ -229,15 +236,9 @@ const NewModbus = () => {
             }),
             onConfirm: async () => {
               if (record.state.value === 'disabled') {
-                await service.editPoint(record.id, {
-                  ...record,
-                  state: 'enabled',
-                });
+                await service.enablePoint([record.id]);
               } else {
-                await service.editPoint(record.id, {
-                  ...record,
-                  state: 'disabled',
-                });
+                await service.disablePoint([record.id]);
               }
               onlyMessage(
                 intl.formatMessage({

+ 10 - 0
src/pages/link/Channel/Modbus/service.ts

@@ -37,6 +37,16 @@ class Service extends BaseService<any> {
       method: 'PUT',
       data,
     });
+  enablePoint = (data: any) =>
+    request(`/${SystemConst.API_BASE}/modbus/point/_enable`, {
+      method: 'POST',
+      data,
+    });
+  disablePoint = (data: any) =>
+    request(`/${SystemConst.API_BASE}/modbus/point/_disable`, {
+      method: 'POST',
+      data,
+    });
   deletePoint = (id: string) =>
     request(`/${SystemConst.API_BASE}/modbus/point/${id}`, {
       method: 'DELETE',

+ 25 - 18
src/pages/link/Type/Detail/index.tsx

@@ -124,7 +124,14 @@ const Save = observer(() => {
           onFormInit(async (form1) => {
             if (param?.id && param.id !== ':id') {
               const resp = await service.detail(param.id);
-              form1.setInitialValues({ ...resp?.result });
+              const data = resp?.result || {};
+              if (data?.shareCluster === false) {
+                data.cluster = data.cluster?.map((item: any) => ({
+                  ...item.configuration,
+                  configuration: item,
+                }));
+              }
+              form1.setInitialValues({ ...data });
             }
           });
           onFieldValueChange('type', (field, f) => {
@@ -208,23 +215,23 @@ const Save = observer(() => {
     [],
   );
 
-  useEffect(() => {
-    console.log(Store.get('current-network-data'));
-    // const subscription = Store.subscribe('current-network-data', (data) => {
-    //   if (!data) return;
-    //   // form.readPretty = true;
-    //   const _data = _.cloneDeep(data);
-    //   // 处理一下集群模式数据
-    //   if (!_data.shareCluster) {
-    //     _data.cluster = _data.cluster?.map((item: any) => ({ ...item.configuration }));
-    //   }
-    //   form.setValues({ ..._data });
-    // });
-    // return () => {
-    //   subscription.unsubscribe();
-    //   // Store.set('current-network-data', undefined);
-    // };
-  }, []);
+  // useEffect(() => {
+  //   console.log(Store.get('current-network-data'));
+  //   // const subscription = Store.subscribe('current-network-data', (data) => {
+  //   //   if (!data) return;
+  //   //   // form.readPretty = true;
+  //   //   const _data = _.cloneDeep(data);
+  //   //   // 处理一下集群模式数据
+  //   //   if (!_data.shareCluster) {
+  //   //     _data.cluster = _data.cluster?.map((item: any) => ({ ...item.configuration }));
+  //   //   }
+  //   //   form.setValues({ ..._data });
+  //   // });
+  //   // return () => {
+  //   //   subscription.unsubscribe();
+  //   //   // Store.set('current-network-data', undefined);
+  //   // };
+  // }, []);
 
   const SchemaField = createSchemaField({
     components: {

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

@@ -96,6 +96,7 @@ const Save = (props: Props) => {
     return service
       .getScene(
         encodeQuery({
+          sorts: { createTime: 'desc' },
           terms: {
             triggerType: map[form.getValuesIn('targetType')],
           },

+ 120 - 11
src/pages/rule-engine/Alarm/Log/TabComponent/index.tsx

@@ -70,6 +70,57 @@ const TabComponent = observer((props: Props) => {
       },
     },
   ];
+  const productCol: ProColumns<any>[] = [
+    ...columns,
+    {
+      title: '产品名称',
+      dataIndex: 'targetName',
+      width: 200,
+      ellipsis: true,
+      valueType: 'select',
+      request: async () => {
+        const res = await service.getProductList();
+        if (res.status === 200) {
+          return res.result.map((item: any) => ({ label: item.name, value: item.name }));
+        }
+        return [];
+      },
+    },
+  ];
+  const deviceCol: ProColumns<any>[] = [
+    ...columns,
+    {
+      title: '设备名称',
+      dataIndex: 'targetName',
+      width: 200,
+      ellipsis: true,
+      valueType: 'select',
+      request: async () => {
+        const res = await service.getDeviceList();
+        if (res.status === 200) {
+          return res.result.map((item: any) => ({ label: item.name, value: item.name }));
+        }
+        return [];
+      },
+    },
+  ];
+  const orgCol: ProColumns<any>[] = [
+    ...columns,
+    {
+      title: '部门名称',
+      dataIndex: 'targetName',
+      width: 200,
+      ellipsis: true,
+      valueType: 'select',
+      request: async () => {
+        const res = await service.getOrgList();
+        if (res.status === 200) {
+          return res.result.map((item: any) => ({ label: item.name, value: item.name }));
+        }
+        return [];
+      },
+    },
+  ];
 
   const [param, setParam] = useState<any>({ pageSize: 10, terms: [] });
   const history = useHistory<Record<string, string>>();
@@ -107,6 +158,9 @@ const TabComponent = observer((props: Props) => {
 
   useEffect(() => {
     handleSearch(param);
+    if (props.type === 'prodcut') {
+    }
+    console.log(props.type);
   }, [props.type]);
 
   const tools = (record: any) => [
@@ -182,17 +236,72 @@ const TabComponent = observer((props: Props) => {
 
   return (
     <div className="alarm-log-card">
-      <SearchComponent<any>
-        field={columns}
-        target="alarm-log"
-        onSearch={(data) => {
-          const dt = {
-            pageSize: 10,
-            terms: [...data?.terms],
-          };
-          handleSearch(dt);
-        }}
-      />
+      {props.type === 'all' && (
+        <SearchComponent<any>
+          field={columns}
+          target="alarm-log"
+          onSearch={(data) => {
+            const dt = {
+              pageSize: 10,
+              terms: [...data?.terms],
+            };
+            handleSearch(dt);
+          }}
+        />
+      )}
+      {props.type === 'product' && (
+        <SearchComponent<any>
+          field={productCol}
+          target="alarm-log"
+          onSearch={(data) => {
+            const dt = {
+              pageSize: 10,
+              terms: [...data?.terms],
+            };
+            handleSearch(dt);
+          }}
+        />
+      )}
+      {props.type === 'device' && (
+        <SearchComponent<any>
+          field={deviceCol}
+          target="alarm-log"
+          onSearch={(data) => {
+            const dt = {
+              pageSize: 10,
+              terms: [...data?.terms],
+            };
+            handleSearch(dt);
+          }}
+        />
+      )}
+      {props.type === 'org' && (
+        <SearchComponent<any>
+          field={orgCol}
+          target="alarm-log"
+          onSearch={(data) => {
+            const dt = {
+              pageSize: 10,
+              terms: [...data?.terms],
+            };
+            handleSearch(dt);
+          }}
+        />
+      )}
+      {props.type === 'other' && (
+        <SearchComponent<any>
+          field={productCol}
+          target="alarm-log"
+          onSearch={(data) => {
+            const dt = {
+              pageSize: 10,
+              terms: [...data?.terms],
+            };
+            handleSearch(dt);
+          }}
+        />
+      )}
+
       <Card>
         <div className="alarmLog" style={{ minHeight, position: 'relative' }}>
           <div style={{ height: '100%', paddingBottom: 48 }}>

+ 6 - 6
src/pages/rule-engine/Alarm/Log/model.ts

@@ -19,19 +19,19 @@ export const AlarmLogModel = model<{
   defaultLevel: [],
   columns: [
     {
+      dataIndex: 'alarmConfigName',
+      title: '告警名称',
+      // hideInSearch: true,
+    },
+    {
       dataIndex: 'alarmTime',
       title: '告警时间',
       valueType: 'dateTime',
     },
     {
-      dataIndex: 'alarmConfigName',
-      title: '告警名称',
-      hideInSearch: true,
-    },
-    {
       dataIndex: 'description',
       title: '说明',
-      hideInSearch: true,
+      // hideInSearch: true,
     },
     {
       dataIndex: 'action',

+ 15 - 0
src/pages/rule-engine/Alarm/Log/service.ts

@@ -30,6 +30,21 @@ class Service extends BaseService<AlarmLogItem> {
       method: 'POST',
       data,
     });
+  getProductList = (params?: any) =>
+    request(`/${SystemConst.API_BASE}/device/product/_query/no-paging?paging=false`, {
+      method: 'GET',
+      params,
+    });
+  getDeviceList = (params?: any) =>
+    request(`/${SystemConst.API_BASE}/device-instance/_query/no-paging?paging=false`, {
+      method: 'GET',
+      params,
+    });
+  getOrgList = (params?: any) =>
+    request(`/${SystemConst.API_BASE}/organization/_query/no-paging?paging=false`, {
+      method: 'GET',
+      params,
+    });
 }
 
 export default Service;

+ 1 - 1
src/pages/system/DataSource/Management/RemoveData.tsx

@@ -12,7 +12,7 @@ interface Props {
 
 const RemoveData = (props: Props) => {
   const { type } = props;
-  const row = ArrayItems.useRecord!();
+  const row = ArrayItems.useRecord!()();
 
   const index = ArrayItems.useIndex!();
   const self = useField();

+ 1 - 0
src/pages/system/Role/Detail/UserManage/BindUser.tsx

@@ -119,6 +119,7 @@ const BindUser = (props: Props) => {
                 ],
               },
             ],
+            sorts: [{ name: 'createTime', order: 'desc' }],
           });
           return {
             result: {