wzyyy 3 éve
szülő
commit
e355693c58
60 módosított fájl, 2954 hozzáadás és 1730 törlés
  1. 5 5
      .github/workflows/docker.yml
  2. 1 1
      package.json
  3. 0 0
      public/images/diagnose copy/back.png
  4. 0 0
      public/images/diagnose copy/message-error.png
  5. 0 0
      public/images/diagnose copy/status-error.png
  6. 0 0
      public/images/diagnose copy/status-success-active.png
  7. 0 0
      public/images/diagnose copy/status-success.png
  8. BIN
      public/images/diagnose copy/status/error.png
  9. BIN
      public/images/diagnose copy/status/loading.png
  10. BIN
      public/images/diagnose copy/status/success.png
  11. BIN
      public/images/diagnose copy/status/warning.png
  12. 0 0
      public/images/diagnose copy/waiting.png
  13. BIN
      public/images/diagnose/error.png
  14. BIN
      public/images/diagnose/loading-1.png
  15. BIN
      public/images/diagnose/loading-2.png
  16. BIN
      public/images/diagnose/success.png
  17. 3 0
      src/app.tsx
  18. 3 0
      src/pages/device/Instance/Detail/Diagnose/Message/index.less
  19. 152 132
      src/pages/device/Instance/Detail/Diagnose/Message/index.tsx
  20. 0 136
      src/pages/device/Instance/Detail/Diagnose/Status/DiagnosticAdvice.tsx
  21. 70 121
      src/pages/device/Instance/Detail/Diagnose/Status/ManualInspection.tsx
  22. 0 7
      src/pages/device/Instance/Detail/Diagnose/Status/index.less
  23. 1195 725
      src/pages/device/Instance/Detail/Diagnose/Status/index.tsx
  24. 324 74
      src/pages/device/Instance/Detail/Diagnose/Status/model.ts
  25. 69 25
      src/pages/device/Instance/Detail/Diagnose/index.less
  26. 106 183
      src/pages/device/Instance/Detail/Diagnose/index.tsx
  27. 4 2
      src/pages/device/Instance/Detail/Running/Property/PropertyCard.tsx
  28. 23 13
      src/pages/device/Instance/Detail/Running/Property/index.tsx
  29. 1 0
      src/pages/device/Instance/typings.d.ts
  30. 10 1
      src/pages/home/index.tsx
  31. 13 9
      src/pages/link/AccessConfig/Detail/Access/index.tsx
  32. 5 0
      src/pages/link/AccessConfig/service.ts
  33. 4 1
      src/pages/media/Device/Save/index.tsx
  34. 33 3
      src/pages/rule-engine/Alarm/Config/index.tsx
  35. 31 13
      src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx
  36. 5 1
      src/pages/rule-engine/Scene/Save/action/VariableItems/org.tsx
  37. 33 20
      src/pages/rule-engine/Scene/Save/action/VariableItems/user.tsx
  38. 102 36
      src/pages/rule-engine/Scene/Save/action/action.tsx
  39. 223 0
      src/pages/rule-engine/Scene/Save/action/device/ConditionalFiltering.tsx
  40. 96 36
      src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx
  41. 7 1
      src/pages/rule-engine/Scene/Save/action/device/index.tsx
  42. 8 1
      src/pages/rule-engine/Scene/Save/action/messageContent.tsx
  43. 2 2
      src/pages/rule-engine/Scene/Save/action/service.ts
  44. 37 0
      src/pages/rule-engine/Scene/Save/components/BuiltInParams.tsx
  45. 1 0
      src/pages/rule-engine/Scene/Save/components/ItemGroup/index.less
  46. 32 0
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/RangePicker.tsx
  47. 27 0
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/TimePicker.tsx
  48. 213 0
      src/pages/rule-engine/Scene/Save/components/TimingTrigger/refactor.tsx
  49. 1 1
      src/pages/rule-engine/Scene/Save/components/index.ts
  50. 14 10
      src/pages/rule-engine/Scene/Save/index.less
  51. 22 101
      src/pages/rule-engine/Scene/Save/index.tsx
  52. 47 52
      src/pages/rule-engine/Scene/Save/trigger/index.tsx
  53. 1 2
      src/pages/rule-engine/Scene/TriggerTerm/index.tsx
  54. 0 7
      src/pages/system/Department/Assets/deivce/bind.tsx
  55. 9 8
      src/pages/system/Department/Assets/deivce/index.tsx
  56. 6 0
      src/pages/system/Department/Assets/product/index.tsx
  57. 5 0
      src/pages/system/Department/Member/index.tsx
  58. 3 0
      src/pages/system/Relationship/index.tsx
  59. 3 1
      src/pages/system/Role/index.tsx
  60. 5 0
      yarn.lock

+ 5 - 5
.github/workflows/docker.yml

@@ -9,11 +9,11 @@ jobs:
         uses: actions/checkout@v2
         with:
           persist-credentials: false
-      - name: Cache npm Repository
-        uses: actions/cache@v1
-        with:
-          path: ./node_modules
-          key: jetlinks-ui-pro-repository
+#      - name: Cache npm Repository
+#        uses: actions/cache@v1
+#        with:
+#          path: ./node_modules
+#          key: jetlinks-ui-pro-repository
       - name: Install 🔧
         run: |
           yarn install

+ 1 - 1
package.json

@@ -87,7 +87,7 @@
     "isomorphic-form-data": "^2.0.0",
     "jetlinks-store": "^0.0.3",
     "lodash": "^4.17.11",
-    "moment": "^2.25.3",
+    "moment": "^2.29.3",
     "monaco-editor": "^0.30.0",
     "monaco-editor-webpack-plugin": "^6.0.0",
     "omit.js": "^2.0.2",

public/images/diagnose/back.png → public/images/diagnose copy/back.png


public/images/diagnose/message-error.png → public/images/diagnose copy/message-error.png


public/images/diagnose/status-error.png → public/images/diagnose copy/status-error.png


public/images/diagnose/status-success-active.png → public/images/diagnose copy/status-success-active.png


public/images/diagnose/status-success.png → public/images/diagnose copy/status-success.png


BIN
public/images/diagnose copy/status/error.png


BIN
public/images/diagnose copy/status/loading.png


BIN
public/images/diagnose copy/status/success.png


BIN
public/images/diagnose copy/status/warning.png


public/images/diagnose/waiting.png → public/images/diagnose copy/waiting.png


BIN
public/images/diagnose/error.png


BIN
public/images/diagnose/loading-1.png


BIN
public/images/diagnose/loading-2.png


BIN
public/images/diagnose/success.png


+ 3 - 0
src/app.tsx

@@ -22,6 +22,9 @@ import getRoutes, {
 } from '@/utils/menu';
 import { AIcon } from '@/components';
 import React from 'react';
+import 'moment/dist/locale/zh-cn';
+import moment from 'moment';
+moment.locale('zh-cn');
 
 const isDev = process.env.NODE_ENV === 'development';
 const loginPath = '/user/login';

+ 3 - 0
src/pages/device/Instance/Detail/Diagnose/Message/index.less

@@ -1,3 +1,6 @@
+.message-status {
+  padding: 8px 24px;
+}
 .content {
   width: 100%;
 }

+ 152 - 132
src/pages/device/Instance/Detail/Diagnose/Message/index.tsx

@@ -1,8 +1,8 @@
 import TitleComponent from '@/components/TitleComponent';
 import './index.less';
 import Dialog from './Dialog';
-import { Button, Col, DatePicker, Empty, Input, InputNumber, Row, Select } from 'antd';
-import { useEffect, useState } from 'react';
+import { Badge, Button, Col, DatePicker, Empty, Input, InputNumber, Row, Select } from 'antd';
+import { useEffect, useMemo, useState } from 'react';
 import { InstanceModel, service } from '@/pages/device/Instance';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { map } from 'rxjs/operators';
@@ -20,19 +20,13 @@ import {
 } from '@formily/antd';
 import { randomString } from '@/utils/util';
 import Log from './Log';
-import { Store } from 'jetlinks-store';
-
-interface Props {
-  onChange: (type: string) => void;
-}
+import { DiagnoseStatusModel, messageStatusMap, messageStyleMap } from '../Status/model';
+import { observer } from '@formily/reactive-react';
 
 const DatePicker1: any = DatePicker;
 
-const Message = (props: Props) => {
+const Message = observer(() => {
   const [subscribeTopic] = useSendWebsocketMessage();
-  const [dialogList, setDialogList] = useState<any[]>([]);
-  const [tempList, setTempList] = useState<any[]>([]);
-  const [logList, setLogList] = useState<any[]>([]);
   const [type, setType] = useState<'property' | 'function'>('function');
   const [input, setInput] = useState<any>({});
   const [inputs, setInputs] = useState<any[]>([]);
@@ -50,17 +44,19 @@ const Message = (props: Props) => {
       ?.pipe(map((res) => res.payload))
       .subscribe((payload: any) => {
         if (payload.type === 'log') {
-          logList.push({
-            key: randomString(),
-            ...payload,
-          });
-          setLogList([...logList]);
+          DiagnoseStatusModel.logList = [
+            ...DiagnoseStatusModel.logList,
+            {
+              key: randomString(),
+              ...payload,
+            },
+          ];
         } else {
-          tempList.push({
-            key: randomString(),
-            ...payload,
-          });
-          const flag = [...tempList]
+          DiagnoseStatusModel.allDialogList = [
+            ...DiagnoseStatusModel.logList,
+            { key: randomString(), ...payload },
+          ];
+          const flag = [...DiagnoseStatusModel.allDialogList]
             .filter(
               (i) =>
                 i.traceId === payload.traceId &&
@@ -69,21 +65,26 @@ const Message = (props: Props) => {
             .every((item) => {
               return !item.error;
             });
-          if (!flag) {
-            props.onChange(!payload.upstream ? 'down-error' : 'up-error');
+          if (!payload.upstream) {
+            DiagnoseStatusModel.message.down = {
+              text: !flag ? '下行消息通信异常' : '下行消息通信正常',
+              status: !flag ? 'error' : 'success',
+            };
           } else {
-            props.onChange(!payload.upstream ? 'down-success' : 'up-success');
+            DiagnoseStatusModel.message.up = {
+              text: !flag ? '上行消息通信异常' : '上行消息通信正常',
+              status: !flag ? 'error' : 'success',
+            };
           }
-          setTempList([...tempList]);
-          Store.set('temp', tempList);
-          const t = dialogList.find(
+          const list = [...DiagnoseStatusModel.dialogList];
+          const t = list.find(
             (item) =>
               item.traceId === payload.traceId &&
               payload.downstream === item.downstream &&
               payload.upstream === item.upstream,
           );
           if (t) {
-            dialogList.map((item) => {
+            list.map((item) => {
               if (item.key === payload.traceId) {
                 item.list.push({
                   key: randomString(),
@@ -92,7 +93,7 @@ const Message = (props: Props) => {
               }
             });
           } else {
-            dialogList.push({
+            list.push({
               key: randomString(),
               traceId: payload.traceId,
               downstream: payload.downstream,
@@ -105,8 +106,7 @@ const Message = (props: Props) => {
               ],
             });
           }
-          setDialogList([...dialogList]);
-          Store.set('diagnose', dialogList);
+          DiagnoseStatusModel.dialogList = [...list];
         }
         const chatBox = document.getElementById('dialog');
         if (chatBox) {
@@ -166,44 +166,46 @@ const Message = (props: Props) => {
     }
   };
   useEffect(() => {
-    subscribeLog();
-    const arr = Store.get('diagnose') || [];
-    setDialogList(arr);
-    const temp = Store.get('temp') || [];
-    setTempList(temp);
-  }, []);
+    if (DiagnoseStatusModel.state === 'success') {
+      subscribeLog();
+    }
+  }, [DiagnoseStatusModel.state]);
 
-  const form = createForm({
-    initialValues: {
-      data: [...inputs],
-    },
-    effects() {
-      onFieldReact('data.*.valueType.type', (field) => {
-        const value = (field as Field).value;
-        const format = field.query('..value').take() as any;
-        if (format) {
-          switch (value) {
-            case 'date':
-              format.setComponent(FDatePicker);
-              break;
-            case 'boolean':
-              format.setComponent(FSelect);
-              format.setDataSource([
-                { label: '是', value: true },
-                { label: '否', value: false },
-              ]);
-              break;
-            case 'int':
-              format.setComponent(NumberPicker);
-              break;
-            default:
-              format.setComponent(FInput);
-              break;
-          }
-        }
-      });
-    },
-  });
+  const form = useMemo(
+    () =>
+      createForm({
+        initialValues: {
+          data: [...inputs],
+        },
+        effects() {
+          onFieldReact('data.*.valueType.type', (field) => {
+            const value = (field as Field).value;
+            const format = field.query('..value').take() as any;
+            if (format) {
+              switch (value) {
+                case 'date':
+                  format.setComponent(FDatePicker);
+                  break;
+                case 'boolean':
+                  format.setComponent(FSelect);
+                  format.setDataSource([
+                    { label: '是', value: true },
+                    { label: '否', value: false },
+                  ]);
+                  break;
+                case 'int':
+                  format.setComponent(NumberPicker);
+                  break;
+                default:
+                  format.setComponent(FInput);
+                  break;
+              }
+            }
+          });
+        },
+      }),
+    [],
+  );
 
   const dataRender = () => {
     switch (type) {
@@ -352,69 +354,87 @@ const Message = (props: Props) => {
   return (
     <Row gutter={24}>
       <Col span={16}>
-        <TitleComponent data="调试" />
-        <div className="content">
-          <div className="dialog" id="dialog">
-            {dialogList.map((item) => (
-              <Dialog data={item} key={item.key} />
-            ))}
+        <div>
+          <div style={{ marginBottom: 20 }}>
+            <Row gutter={24}>
+              {Object.keys(DiagnoseStatusModel.message).map((key) => {
+                const obj = DiagnoseStatusModel.message[key];
+                return (
+                  <Col key={key} span={12}>
+                    <div style={messageStyleMap.get(obj.status)} className="message-status">
+                      <Badge status={messageStatusMap.get(obj.status)} />
+                      {obj.text}
+                    </div>
+                  </Col>
+                );
+              })}
+            </Row>
           </div>
-        </div>
-        <div className="function">
-          <Row gutter={24}>
-            <Col span={5}>
-              <Select
-                value={type}
-                placeholder="请选择"
-                style={{ width: '100%' }}
-                onChange={(value) => {
-                  setType(value);
-                  setInputs([]);
-                  setInput({});
-                }}
-              >
-                <Select.Option value="function">调用功能</Select.Option>
-                <Select.Option value="property">操作属性</Select.Option>
-              </Select>
-            </Col>
-            {dataRender()}
-            <Col span={3}>
-              <Button
-                type="primary"
-                onClick={async () => {
-                  props.onChange('waiting');
-                  if (type === 'function') {
-                    const list = (form?.values?.data || []).filter((it) => !!it.value);
-                    const obj = {};
-                    list.map((it) => {
-                      obj[it.id] = it.value;
-                    });
-                    await service.executeFunctions(InstanceModel.detail?.id || '', input.id, {
-                      ...obj,
-                    });
-                  } else {
-                    if (propertyType === 'read') {
-                      await service.readProperties(InstanceModel.detail?.id || '', [property]);
-                    } else {
-                      await service.settingProperties(InstanceModel.detail?.id || '', {
-                        [property]: propertyValue,
-                      });
-                    }
-                  }
-                }}
-              >
-                发送
-              </Button>
-            </Col>
-          </Row>
-          {inputs.length > 0 && (
-            <div className="parameter">
-              <h4>功能参数</h4>
-              <FormProvider form={form}>
-                <SchemaField schema={schema} />
-              </FormProvider>
+          <div>
+            <TitleComponent data="调试" />
+            <div className="content">
+              <div className="dialog" id="dialog">
+                {DiagnoseStatusModel.dialogList.map((item) => (
+                  <Dialog data={item} key={item.key} />
+                ))}
+              </div>
             </div>
-          )}
+            <div className="function">
+              <Row gutter={24}>
+                <Col span={5}>
+                  <Select
+                    value={type}
+                    placeholder="请选择"
+                    style={{ width: '100%' }}
+                    onChange={(value) => {
+                      setType(value);
+                      setInputs([]);
+                      setInput({});
+                    }}
+                  >
+                    <Select.Option value="function">调用功能</Select.Option>
+                    <Select.Option value="property">操作属性</Select.Option>
+                  </Select>
+                </Col>
+                {dataRender()}
+                <Col span={3}>
+                  <Button
+                    type="primary"
+                    onClick={async () => {
+                      if (type === 'function') {
+                        const list = (form?.values?.data || []).filter((it) => !!it.value);
+                        const obj = {};
+                        list.map((it) => {
+                          obj[it.id] = it.value;
+                        });
+                        await service.executeFunctions(InstanceModel.detail?.id || '', input.id, {
+                          ...obj,
+                        });
+                      } else {
+                        if (propertyType === 'read') {
+                          await service.readProperties(InstanceModel.detail?.id || '', [property]);
+                        } else {
+                          await service.settingProperties(InstanceModel.detail?.id || '', {
+                            [property]: propertyValue,
+                          });
+                        }
+                      }
+                    }}
+                  >
+                    发送
+                  </Button>
+                </Col>
+              </Row>
+              {inputs.length > 0 && (
+                <div className="parameter">
+                  <h4>功能参数</h4>
+                  <FormProvider form={form}>
+                    <SchemaField schema={schema} />
+                  </FormProvider>
+                </div>
+              )}
+            </div>
+          </div>
         </div>
       </Col>
       <Col span={8}>
@@ -430,8 +450,8 @@ const Message = (props: Props) => {
         >
           <TitleComponent data={'日志'} />
           <div style={{ marginTop: 10 }}>
-            {logList.length > 0 ? (
-              logList.map((item) => <Log data={item} key={item.key} />)
+            {DiagnoseStatusModel.logList.length > 0 ? (
+              DiagnoseStatusModel.logList.map((item) => <Log data={item} key={item.key} />)
             ) : (
               <Empty />
             )}
@@ -440,6 +460,6 @@ const Message = (props: Props) => {
       </Col>
     </Row>
   );
-};
+});
 
 export default Message;

+ 0 - 136
src/pages/device/Instance/Detail/Diagnose/Status/DiagnosticAdvice.tsx

@@ -1,136 +0,0 @@
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { InfoCircleOutlined } from '@ant-design/icons';
-import { Badge, Modal } from 'antd';
-import styles from './index.less';
-
-interface Props {
-  close: () => void;
-  data: any;
-}
-
-const DiagnosticAdvice = (props: Props) => {
-  const { data } = props;
-  const nameMap = new Map();
-  nameMap.set('mqtt-client-gateway', 'topic');
-  nameMap.set('websocket-server', 'URL');
-  nameMap.set('http-server-gateway', 'URL');
-  nameMap.set('coap-server-gateway', 'URL');
-  nameMap.set('udp-device-gateway', '地址');
-  nameMap.set('tcp-server-gateway', '地址');
-
-  const jumpUrl = () => {
-    const url = getMenuPathByParams(MENUS_CODE['device/Product/Detail'], data.id);
-    const tab: any = window.open(`${origin}/#${url}?key=access`);
-    tab!.onTabSaveSuccess = (value: any) => {
-      if (value) {
-        props.close();
-      }
-    };
-  };
-
-  return (
-    <Modal
-      title="诊断建议"
-      onCancel={() => {
-        props.close();
-      }}
-      onOk={() => {
-        props.close();
-      }}
-      width={700}
-      visible
-    >
-      <div className={styles.advice}>
-        <div className={styles.alert}>
-          <InfoCircleOutlined style={{ marginRight: 10 }} />
-          所有诊断均无异常但设备任未上线,请检查以下内容
-        </div>
-        {(data?.product || []).map((item: any) => (
-          <div className={styles.infoItem} key={item.name}>
-            <Badge
-              status="default"
-              text={
-                <span>
-                  产品-{item.name}规则可能有加密处理,请认真查看
-                  <a
-                    onClick={() => {
-                      jumpUrl();
-                    }}
-                  >
-                    设备接入配置
-                  </a>
-                  中【消息协议】说明
-                </span>
-              }
-            />
-          </div>
-        ))}
-        {(data?.device || []).map((item: any) => (
-          <div className={styles.infoItem} key={item.name}>
-            <Badge
-              status="default"
-              text={
-                <span>
-                  设备-{item.name}规则可能有加密处理,请认真查看
-                  <a
-                    onClick={() => {
-                      jumpUrl();
-                    }}
-                  >
-                    设备接入配置
-                  </a>
-                  中【消息协议】说明
-                </span>
-              }
-            />
-          </div>
-        ))}
-        {!!data?.provider && (
-          <div>
-            {data.routes.length > 0 ? (
-              <div className={styles.infoItem}>
-                <Badge
-                  status="default"
-                  text={
-                    <span>
-                      请根据
-                      <a
-                        onClick={() => {
-                          jumpUrl();
-                        }}
-                      >
-                        设备接入配置
-                      </a>
-                      中{nameMap.get(data.provider)}
-                      信息,任意上报一条数据(无设备接入配置查看权限时:请联系管理员根据设备接入配置中
-                      {URL}信息,任意上报一条数据)。 变量说明:{nameMap.get(data.provider)}
-                      变量根据网关详情中provider类型判断。
-                    </span>
-                  }
-                />
-              </div>
-            ) : (
-              <div className={styles.infoItem}>
-                <Badge
-                  status="default"
-                  text={
-                    <span>
-                      请联系管理员提供{nameMap.get(data.provider)}
-                      信息,并根据URL信息任意上报一条数据 变量说明:{nameMap.get(data.provider)}
-                      变量根据网关详情中provider类型判断。
-                    </span>
-                  }
-                />
-              </div>
-            )}
-          </div>
-        )}
-        <div className={styles.infoItem}>
-          <Badge status="default" text={'请检查设备是否已开机'} />
-        </div>
-      </div>
-    </Modal>
-  );
-};
-
-export default DiagnosticAdvice;

+ 70 - 121
src/pages/device/Instance/Detail/Diagnose/Status/ManualInspection.tsx

@@ -1,99 +1,23 @@
-import { createForm } from '@formily/core';
-import { createSchemaField } from '@formily/react';
-import type { ISchema } from '@formily/json-schema';
-import { Form, FormGrid, FormItem, Input, Password, PreviewText } from '@formily/antd';
-import { Modal } from 'antd';
+import { Button, Descriptions, Modal } from 'antd';
 import styles from './index.less';
 import { InfoCircleOutlined } from '@ant-design/icons';
-
-const componentMap = {
-  string: 'Input',
-  password: 'Password',
-};
-
+import { useHistory } from 'umi';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { InstanceModel } from '@/pages/device/Instance';
 interface Props {
   close: () => void;
   data: any;
-  ok: (data: any) => void;
+  save: (data: any) => void;
 }
 
 const ManualInspection = (props: Props) => {
   const { data } = props;
 
-  const form = createForm({
-    validateFirst: true,
-    initialValues: {},
-  });
-
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Input,
-      Password,
-      FormGrid,
-      PreviewText,
-    },
-  });
-
-  const configToSchema = (list: any[]) => {
-    const config = {};
-    list.forEach((item) => {
-      config[item.property] = {
-        type: 'string',
-        title: item.name,
-        require: true,
-        'x-decorator': 'FormItem',
-        'x-component': componentMap[item.type.type],
-        'x-decorator-props': {
-          tooltip: item.description,
-        },
-        'x-component-props': {
-          value: '',
-          placeholder: `请输入${item.name}`,
-        },
-      };
-    });
-    return config;
-  };
-
-  const renderConfigCard = () => {
-    const itemSchema: ISchema = {
-      type: 'object',
-      properties: {
-        grid: {
-          type: 'void',
-          'x-component': 'FormGrid',
-          'x-component-props': {
-            minColumns: [1],
-            maxColumns: [1],
-          },
-          properties: configToSchema(data?.data?.properties),
-        },
-      },
-    };
+  const history = useHistory<Record<string, string>>();
 
-    return (
-      <>
-        <PreviewText.Placeholder value="-">
-          <Form form={form} layout="vertical">
-            <SchemaField schema={itemSchema} />
-          </Form>
-        </PreviewText.Placeholder>
-      </>
-    );
+  const okBtn = () => {
+    props.save(data);
   };
-  const renderComponent = () => (
-    <div style={{ backgroundColor: '#f6f6f6', padding: 10 }}>
-      {(data?.data?.properties || []).map((item: any) => (
-        <div key={item?.property}>
-          <span>{item?.name}</span>:{' '}
-          <span>
-            {data?.check && data?.check[item?.property] ? data?.check[item?.property] : '--'}
-          </span>
-        </div>
-      ))}
-    </div>
-  );
 
   return (
     <Modal
@@ -101,47 +25,72 @@ const ManualInspection = (props: Props) => {
       onCancel={() => {
         props.close();
       }}
-      width={600}
-      onOk={async () => {
-        const values = (await form.submit()) as any;
-        if (!data?.check) {
-          props.ok({
-            status: 'error',
-            data: data,
-          });
-        } else {
-          let flag = true;
-          Object.keys(values).forEach((key) => {
-            if (values[key] !== data?.check[key]) {
-              flag = false;
+      width={800}
+      footer={[
+        <Button
+          key="back"
+          onClick={() => {
+            if (data.type === 'device') {
+              InstanceModel.active = 'detail';
+            } else {
+              history.push(
+                `${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], data.productId)}`,
+                {
+                  tab: 'access',
+                },
+              );
             }
-          });
-          if (flag) {
-            props.ok({
-              status: 'success',
-              data: data,
-            });
-          } else {
-            props.ok({
-              status: 'error',
-              data: data,
-            });
-          }
-        }
-      }}
+            props.close();
+          }}
+        >
+          去修改
+        </Button>,
+        <Button
+          key="submit"
+          onClick={() => {
+            okBtn();
+          }}
+        >
+          确认无误
+        </Button>,
+      ]}
       visible
     >
-      <div className={styles.alert}>
-        <InfoCircleOutlined style={{ marginRight: 10 }} />
-        {data.type === 'product'
-          ? `当前填写的数据将与产品-设备接入配置中的${data.data.name}数据进行比对`
-          : `当前填写的数据将与设备-实例信息配置中的${data.data.name}数据进行比对`}
-      </div>
-      <div style={{ marginTop: 10 }}>
-        已配置参数
-        {renderComponent()}
+      <div style={{ display: 'flex' }}>
+        <div style={{ flex: 1 }}>
+          <div className={styles.alert}>
+            <InfoCircleOutlined style={{ marginRight: 10 }} />
+            请检查配置项是否填写正确,若您确定该项无需诊断可
+            <a
+              onClick={() => {
+                okBtn();
+              }}
+            >
+              忽略
+            </a>
+          </div>
+          <div style={{ marginTop: 10 }}>
+            <Descriptions title={data?.data?.name} layout="vertical" bordered>
+              {(data?.data?.properties || []).map((item: any) => (
+                <Descriptions.Item
+                  key={item.property}
+                  label={`${item.name}${item?.description ? `(${item.description})` : ''}`}
+                >
+                  {data?.configuration[item.property] || ''}
+                </Descriptions.Item>
+              ))}
+            </Descriptions>
+          </div>
+        </div>
+        {data?.data?.description ? (
+          <div style={{ width: '50%', border: '1px solid #f0f0f0' }}>
+            <h4>诊断项说明</h4>
+            <p>{data?.data?.description}</p>
+          </div>
+        ) : (
+          ''
+        )}
       </div>
-      <div style={{ marginTop: 10 }}>{renderConfigCard()}</div>
     </Modal>
   );
 };

+ 0 - 7
src/pages/device/Instance/Detail/Diagnose/Status/index.less

@@ -88,10 +88,3 @@
   line-height: 40px;
   background-color: #f6f6f6;
 }
-
-.advice {
-  .infoItem {
-    width: 100%;
-    margin: 10px;
-  }
-}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1195 - 725
src/pages/device/Instance/Detail/Diagnose/Status/index.tsx


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

@@ -1,92 +1,342 @@
+import type { ProductItem } from '@/pages/device/Product/typings';
 import { model } from '@formily/reactive';
 import type { ReactNode } from 'react';
 
-type StatusProps = {
-  status: 'loading' | 'error' | 'success' | 'warning';
-  text: string;
-  info: null | ReactNode;
-};
+export const StatusMap = new Map();
+StatusMap.set('error', require('/public/images/diagnose/status/error.png'));
+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'));
 
-type ListProps = {
+export type ListProps = {
   key: string;
   name: string;
-  data: string;
-  desc: string;
+  desc?: string;
+  status: 'loading' | 'error' | 'success' | 'warning';
+  text?: string;
+  info?: ReactNode;
 };
 
+export const list = [
+  { key: 'status', text: '连接状态' },
+  { key: 'message', text: '消息通信' },
+];
+
+export const textColorMap = new Map();
+textColorMap.set('loading', 'black');
+textColorMap.set('error', 'red');
+textColorMap.set('success', 'green');
+textColorMap.set('warning', 'red');
+
+export const networkInitList: ListProps[] = [
+  // {
+  //   key: 'access',
+  //   name: '设备接入配置',
+  //   desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
+  //   status: 'loading',
+  //   text: '正在诊断中...',
+  //   info: null,
+  // },
+  {
+    key: 'network',
+    name: '网络组件',
+    desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'gateway',
+    name: '设备接入网关',
+    desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'product',
+    name: '产品状态',
+    desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'device',
+    name: '设备状态',
+    desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+];
+
+export const childInitList: ListProps[] = [
+  // {
+  //   key: 'access',
+  //   name: '设备接入配置',
+  //   desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
+  //   status: 'loading',
+  //   text: '正在诊断中...',
+  //   info: null,
+  // },
+  {
+    key: 'network',
+    name: '网络组件',
+    desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'gateway',
+    name: '设备接入网关',
+    desc: '诊断设备接入网关状态是否正常,网关配置是否正确',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'parent-device',
+    name: '网关父设备',
+    desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'product',
+    name: '产品状态',
+    desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'device',
+    name: '设备状态',
+    desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+];
+
+export const cloudInitList: ListProps[] = [
+  // {
+  //   key: 'access',
+  //   name: '设备接入配置',
+  //   desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
+  //   status: 'loading',
+  //   text: '正在诊断中...',
+  //   info: null,
+  // },
+  {
+    key: 'gateway',
+    name: '设备接入网关',
+    desc: '诊断设备接入网关状态是否正常,网关配置是否正确',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'product',
+    name: '产品状态',
+    desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'device',
+    name: '设备状态',
+    desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+];
+
+export const channelInitList: ListProps[] = [
+  // {
+  //   key: 'access',
+  //   name: '设备接入配置',
+  //   desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
+  //   status: 'loading',
+  //   text: '正在诊断中...',
+  //   info: null,
+  // },
+  {
+    key: 'gateway',
+    name: '设备接入网关',
+    desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'product',
+    name: '产品状态',
+    desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'device',
+    name: '设备状态',
+    desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+];
+
+export const mediaInitList: ListProps[] = [
+  // {
+  //   key: 'access',
+  //   name: '设备接入配置',
+  //   desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
+  //   status: 'loading',
+  //   text: '正在诊断中...',
+  //   info: null,
+  // },
+  {
+    key: 'gateway',
+    name: '设备接入网关',
+    desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'product',
+    name: '产品状态',
+    desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+  {
+    key: 'device',
+    name: '设备状态',
+    desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
+    status: 'loading',
+    text: '正在诊断中...',
+    info: null,
+  },
+];
+
 export const DiagnoseStatusModel = model<{
-  status: {
-    config?: StatusProps;
-    network?: StatusProps;
-    product?: StatusProps;
-    device?: StatusProps;
-    productAuth?: StatusProps;
-    deviceAuth?: StatusProps;
-    deviceAccess?: StatusProps;
-    other?: StatusProps;
-  };
   list: ListProps[];
-  model: boolean;
+  product: Partial<ProductItem>;
   gateway: any;
+  configuration: {
+    product: any[];
+    device: any[];
+  };
+  percent: number;
+  state: 'loading' | 'success' | 'error'; // 上面的状态
+  status: 'loading' | 'finish'; // 检验是否完成检验过程
+  count: number;
+  logList: any[];
+  dialogList: any[];
+  allDialogList: any[];
+  message: {
+    up: {
+      text: string;
+      status: 'loading' | 'success' | 'error';
+    };
+    down: {
+      text: string;
+      status: 'loading' | 'success' | 'error';
+    };
+  };
 }>({
-  status: {
-    config: {
-      status: 'loading',
-      text: '正在诊断中...',
-      info: null,
-    },
-    network: {
-      status: 'loading',
-      text: '正在诊断中...',
-      info: null,
-    },
-    product: {
-      status: 'loading',
-      text: '正在诊断中...',
-      info: null,
-    },
-    device: {
-      status: 'loading',
-      text: '正在诊断中...',
-      info: null,
-    },
-    deviceAccess: {
+  list: [],
+  product: {},
+  gateway: {},
+  configuration: {
+    product: [],
+    device: [],
+  },
+  state: 'loading',
+  status: 'loading',
+  percent: 0,
+  count: 0,
+  logList: [],
+  dialogList: [],
+  allDialogList: [],
+  message: {
+    up: {
+      text: '上行消息诊断中',
       status: 'loading',
-      text: '正在诊断中...',
-      info: null,
     },
-    other: {
+    down: {
+      text: '下行消息诊断中',
       status: 'loading',
-      text: '正在诊断中...',
-      info: null,
     },
   },
-  list: [
-    {
-      key: 'config',
-      name: '设备接入配置',
-      data: 'config',
-      desc: '诊断设备接入配置是否正确,配置错误将导致连接失败',
-    },
-    {
-      key: 'network',
-      name: '网络信息',
-      data: 'network',
-      desc: '诊断网络组件配置是否正确,配置错误将导致连接失败',
-    },
-    {
-      key: 'product',
-      name: '产品状态',
-      data: 'product',
-      desc: '诊断产品状态是否已发布,未发布的状态将导致连接失败',
-    },
-    {
-      key: 'device',
-      name: '设备状态',
-      data: 'device',
-      desc: '诊断设备状态是否已启用,未启用的状态将导致连接失败',
-    },
-  ],
-  model: true,
-  gateway: {},
 });
+
+export const gatewayList = [
+  'websocket-server',
+  'http-server-gateway',
+  'udp-device-gateway',
+  'coap-server-gateway',
+  'mqtt-client-gateway',
+  'tcp-server-gateway',
+];
+
+export const headerColorMap = new Map();
+headerColorMap.set('loading', 'linear-gradient(89.95deg, #E6F5FF 0.03%, #E9EAFF 99.95%)');
+headerColorMap.set(
+  'error',
+  'linear-gradient(89.95deg, rgba(231, 173, 86, 0.1) 0.03%, rgba(247, 111, 93, 0.1) 99.95%)',
+);
+headerColorMap.set('success', 'linear-gradient(89.95deg, #E8F8F7 0.03%, #EBEFFA 99.95%)');
+
+export const headerTextMap = new Map();
+headerTextMap.set('loading', '正在诊断中');
+headerTextMap.set('error', '发现连接问题');
+headerTextMap.set('success', '连接状态正常');
+
+export const headerDescMap = new Map();
+headerDescMap.set('loading', '已诊断XX个');
+headerDescMap.set('error', '请处理连接异常');
+headerDescMap.set('success', '现在可调试消息通信');
+
+export const headerImgMap = new Map();
+headerImgMap.set('loading', require('/public/images/diagnose/loading-2.png'));
+headerImgMap.set('error', require('/public/images/diagnose/error.png'));
+headerImgMap.set('success', require('/public/images/diagnose/success.png'));
+
+export const progressMap = new Map();
+progressMap.set('loading', '#597EF7');
+progressMap.set('error', '#FAB247');
+progressMap.set('success', '#32D4A4');
+
+export const messageStyleMap = new Map();
+messageStyleMap.set('loading', {
+  background: 'linear-gradient(0deg, rgba(30, 165, 241, 0.03), rgba(30, 165, 241, 0.03)), #FFFFFF',
+  boxShadow: '-2px 0px 0px #1EA5F1',
+});
+messageStyleMap.set('error', {
+  background: 'linear-gradient(0deg, rgba(255, 77, 79, 0.03), rgba(255, 77, 79, 0.03)), #FFFFFF',
+  boxShadow: '-2px 0px 0px #FF4D4F',
+});
+messageStyleMap.set('success', {
+  background: 'linear-gradient(0deg, rgba(50, 212, 164, 0.03), rgba(50, 212, 164, 0.03)), #FFFFFF',
+  boxShadow: '-2px 0px 0px #32D4A4',
+});
+
+export const messageStatusMap = new Map();
+messageStatusMap.set('loading', 'processing');
+messageStatusMap.set('error', 'error');
+messageStatusMap.set('success', 'success');
+
+export const urlMap = new Map();
+urlMap.set('mqtt-client-gateway', 'topic');
+urlMap.set('http-server-gateway', 'url');
+urlMap.set('websocket-server', 'url');
+urlMap.set('coap-server-gateway', 'url');

+ 69 - 25
src/pages/device/Instance/Detail/Diagnose/index.less

@@ -1,40 +1,84 @@
 .diagnose {
-  .header {
+  .diagnose-header {
+    position: relative;
     width: 100%;
-  }
+    height: 150px;
+    margin-bottom: 20px;
+    padding: 15px 25px;
 
-  .header-message {
-    width: 100%;
-    background: url('/images/diagnose/back.png') no-repeat;
-    background-size: 100% 100%;
+    .diagnose-top {
+      display: flex;
+      width: 100%;
+
+      .diagnose-img {
+        width: 65px;
+        height: 65px;
+        margin-right: 20px;
+      }
+
+      .diagnose-text {
+        .diagnose-title {
+          color: #000c;
+          font-weight: 700;
+          font-size: 25px;
+        }
+
+        .diagnose-desc {
+          color: rgba(0, 0, 0, 0.65);
+          font-size: 14px;
+        }
+      }
+    }
+
+    .diagnose-progress {
+      width: 100%;
+    }
+
+    .diagnose-radio {
+      position: absolute;
+      bottom: 0;
+      display: flex;
+
+      .diagnose-radio-item {
+        width: 150px;
+        height: 35px;
+        margin-right: 8px;
+        color: #00000073;
+        line-height: 35px;
+        text-align: center;
+        background: #f2f2f2;
+        border-radius: 2px 2px 0 0;
+        cursor: pointer;
+        &.disabled {
+          cursor: not-allowed;
+        }
+      }
+    }
   }
+}
 
-  .container {
-    margin-top: 20px;
+.diagnose-loading {
+  animation: diagnose-loading 2s linear infinite;
+}
+
+@keyframes diagnose-loading {
+  0% {
+    transform: rotate(0deg);
   }
 
-  .item-box {
-    width: 100%;
-    padding: 10px;
-    background-repeat: no-repeat;
-    background-size: 100% 100%;
-    cursor: pointer;
+  25% {
+    transform: rotate(90deg);
   }
 
-  .item-title {
-    font-weight: 700;
-    font-size: 14px;
+  50% {
+    transform: rotate(180deg);
   }
 
-  .item-context {
-    height: 40px;
-    font-weight: 700;
-    font-size: 24px;
+  75% {
+    transform: rotate(270deg);
   }
 
-  .item-message {
-    color: rgba(0, 0, 0, 0.85);
-    font-weight: 400;
-    font-size: 14px;
+  100% {
+    transform: rotate(360deg);
   }
 }

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

@@ -1,208 +1,131 @@
-import { Badge, Card, Col, Row, Tooltip } from 'antd';
-import type { ReactNode } from 'react';
+import { Card, Progress } from 'antd';
 import { useEffect, useState } from 'react';
 import Message from './Message';
 import Status from './Status';
 import './index.less';
-import classNames from 'classnames';
-import { Store } from 'jetlinks-store';
-import { DiagnoseStatusModel } from './Status/model';
 import { useDomFullHeight } from '@/hooks';
+import { InstanceModel } from '@/pages/device/Instance';
+import { observer } from '@formily/reactive-react';
+import {
+  DiagnoseStatusModel,
+  headerColorMap,
+  headerDescMap,
+  headerImgMap,
+  headerTextMap,
+  list,
+  progressMap,
+} from './Status/model';
+import classNames from 'classnames';
 
-interface ListProps {
-  key: string;
-  tab: string;
-  component: ReactNode;
-}
-
-const bImageMap = new Map();
-bImageMap.set('m-error', require('/public/images/diagnose/message-error.png'));
-bImageMap.set('s-error', require('/public/images/diagnose/status-error.png'));
-bImageMap.set('s-success-active', require('/public/images/diagnose/status-success-active.png'));
-bImageMap.set('s-success', require('/public/images/diagnose/status-success.png'));
-bImageMap.set('waiting', require('/public/images/diagnose/waiting.png'));
-
-const statusColor = new Map();
-statusColor.set('m-error', '#E50012');
-statusColor.set('s-error', '#E50012');
-statusColor.set('error', '#E50012');
-statusColor.set('s-success-active', '#24B276');
-statusColor.set('s-success', '#24B276');
-statusColor.set('success', '#24B276');
-statusColor.set('waiting', '#FF9000');
-statusColor.set('disabled', 'rgba(0, 0, 0, .8)');
-
-const statusText = new Map();
-statusText.set('s-error', '连接失败');
-statusText.set('s-success-active', '连接成功');
-statusText.set('s-success', '连接成功');
-statusText.set('waiting', '诊断中');
-statusText.set('disabled', '诊断中');
-
-const Diagnose = () => {
+const Diagnose = observer(() => {
+  const { minHeight } = useDomFullHeight(`.diagnose`, 12);
   const [current, setCurrent] = useState<string>('status');
-  const [status, setStatus] = useState<string>('waiting');
-  const [message, setMessage] = useState<string>('waiting');
-  const [active, setActive] = useState<boolean>(false);
+  const [providerType, setProviderType] = useState<
+    'network' | 'child-device' | 'media' | 'cloud' | 'channel'
+  >('network');
 
-  const [up, setUp] = useState<'success' | 'error' | 'waiting'>('waiting');
-  const [down, setDown] = useState<'success' | 'error' | 'waiting'>('waiting');
-
-  const { minHeight } = useDomFullHeight(`.diagnose`, 12);
+  const ViewMap = {
+    status: <Status providerType={providerType} />,
+    message: <Message />,
+  };
 
-  const list = [
-    {
-      key: 'status',
-      tab: '连接状态',
-      component: (
-        <div
-          style={{ backgroundImage: `url(${bImageMap.get(status)}`, backgroundSize: '100% 100%' }}
-          className="item-box"
-        >
-          <div className="item-title">连接状态</div>
-          <div style={{ color: statusColor.get(status) }} className="item-context">
-            <Badge color={statusColor.get(status)} /> {statusText.get(status)}
-          </div>
-        </div>
-      ),
-    },
-    {
-      key: 'message',
-      tab: '消息通信',
-      component: (
-        <div
-          style={
-            message !== 'disabled'
-              ? {
-                  backgroundImage: `url(${bImageMap.get(message)})`,
-                  backgroundSize: '100% 100%',
-                }
-              : {
-                  backgroundColor: 'rgba(0, 0, 0, .08)',
-                  borderLeft: '2px solid rgba(0, 0, 0, .8)',
-                  cursor: 'not-allowed',
-                }
-          }
-          className="item-box"
-        >
-          <div className="item-title">消息通信</div>
-          <div
-            className={classNames('item-context', message !== 'disabled' ? 'item-message' : '')}
-            style={{ fontWeight: 400 }}
-          >
-            {message === 'disabled' ? (
-              <Tooltip title={'设备未上线时消息通信功不能使用'}>
-                <span style={{ color: statusColor.get(message) }}>
-                  <Badge color={statusColor.get(message)} />
-                  {status === 's-error' || status === 'waiting' ? '等待设备连接' : '连接中'}
-                </span>
-              </Tooltip>
-            ) : (
-              <>
-                <div>
-                  <Badge
-                    color={statusColor.get(up)}
-                    text={
-                      up === 'waiting'
-                        ? `上行消息通信诊断中`
-                        : `上行消息通信${up === 'error' ? '异常' : '正常'}`
-                    }
-                  />
-                </div>
-                <div>
-                  <Badge
-                    color={statusColor.get(down)}
-                    text={
-                      down === 'waiting'
-                        ? `下行消息通信诊断中`
-                        : `下行消息通信${down === 'error' ? '异常' : '正常'}`
-                    }
-                  />
-                </div>
-              </>
-            )}
-          </div>
-        </div>
-      ),
-    },
-  ];
   useEffect(() => {
+    setCurrent('status');
+    const provider = InstanceModel.detail?.accessProvider;
+    if (provider === 'fixed-media' || provider === 'gb28181-2016') {
+      setProviderType('media');
+    } else if (provider === 'OneNet' || provider === 'Ctwing') {
+      setProviderType('media');
+    } else if (provider === 'modbus-tcp' || provider === 'opc-ua') {
+      setProviderType('channel');
+    } else if (provider === 'child-device') {
+      setProviderType('child-device');
+    } else {
+      setProviderType('network');
+    }
+    DiagnoseStatusModel.state = 'loading';
+
     return () => {
-      Store.set('diagnose', []);
-      Store.set('diagnose-status', []);
-      DiagnoseStatusModel.model = true;
+      DiagnoseStatusModel.list = [];
+      DiagnoseStatusModel.count = 0;
     };
   }, []);
+
+  const activeStyle = {
+    background: '#FFFFFF',
+    border: '1px solid rgba(0, 0, 0, 0.09)',
+    borderRadius: '2px 2px 0px 0px',
+    color: '#000000BF',
+  };
+
   return (
     <Card className="diagnose" style={{ minHeight }}>
-      <div className={current === 'message' ? 'header-message' : 'header'}>
-        <Row gutter={24} style={{ padding: 10, width: '100%' }}>
-          {list.map((item: ListProps) => (
-            <Col
-              span={8}
-              key={item.key}
+      <div
+        className="diagnose-header"
+        style={{
+          background: headerColorMap.get(DiagnoseStatusModel.state),
+        }}
+      >
+        <div className="diagnose-top">
+          <div className="diagnose-img">
+            {DiagnoseStatusModel.state === 'loading' ? (
+              <div style={{ height: '100%', width: '100%', position: 'relative' }}>
+                <img
+                  style={{ height: '100%', position: 'absolute', zIndex: 2 }}
+                  src={headerImgMap.get(DiagnoseStatusModel.state)}
+                />
+                <img
+                  src={require('/public/images/diagnose/loading-1.png')}
+                  className={'diagnose-loading'}
+                  style={{ height: '100%' }}
+                />
+              </div>
+            ) : (
+              <img style={{ height: '100%' }} src={headerImgMap.get(DiagnoseStatusModel.state)} />
+            )}
+          </div>
+          <div className="diagnose-text">
+            <div className="diagnose-title">{headerTextMap.get(DiagnoseStatusModel.state)}</div>
+            <div className="diagnose-desc">
+              {DiagnoseStatusModel.state !== 'loading'
+                ? headerDescMap.get(DiagnoseStatusModel.state)
+                : `已诊断${DiagnoseStatusModel.count}个`}
+            </div>
+          </div>
+        </div>
+        <div className="diagnose-progress">
+          <Progress
+            strokeColor={progressMap.get(DiagnoseStatusModel.state)}
+            showInfo={false}
+            style={{ width: '100%' }}
+            size="small"
+            percent={DiagnoseStatusModel.percent}
+          />
+        </div>
+        <div className="diagnose-radio">
+          {list.map((i) => (
+            <div
+              key={i.key}
+              className={classNames(
+                'diagnose-radio-item',
+                i.key === 'message' && DiagnoseStatusModel.state !== 'success' ? 'disabled' : '',
+              )}
+              style={current === i.key ? { ...activeStyle } : {}}
               onClick={() => {
-                if (current === item.key) {
-                  return;
-                }
-                if (item.key === 'message' && status === 's-success-active') {
-                  setCurrent(item.key);
-                  setMessage('waiting');
-                }
-                if (item.key === 'status') {
-                  setActive(true);
-                  setCurrent(item.key);
+                if (DiagnoseStatusModel.state === 'success') {
+                  setCurrent(i.key);
                 }
               }}
             >
-              {item.component}
-            </Col>
+              {i.text}
+              {current === i.key ? '(诊断中)' : ''}
+            </div>
           ))}
-        </Row>
-      </div>
-      <div className="container">
-        {current === 'status' ? (
-          <Status
-            flag={active}
-            onChange={(type: string) => {
-              if (type === 'success') {
-                setStatus('s-success-active');
-                setMessage('waiting');
-              } else if (type === 'error') {
-                setStatus('s-error');
-                setMessage('disabled');
-              } else if (type === 'loading') {
-                setStatus('waiting');
-                setMessage('disabled');
-              }
-            }}
-          />
-        ) : (
-          <Message
-            onChange={(data: string) => {
-              if (data === 'waiting') {
-                setMessage('waiting');
-                setDown('waiting');
-                setUp('waiting');
-              } else if (data === 'down-error') {
-                setMessage('m-error');
-                setDown('error');
-              } else if (data === 'down-success') {
-                setMessage('s-success-active');
-                setDown('success');
-              } else if (data === 'up-success') {
-                setMessage('s-success-active');
-                setUp('success');
-              } else if (data === 'up-error') {
-                setMessage('m-error');
-                setUp('error');
-              }
-            }}
-          />
-        )}
+        </div>
       </div>
+      <div>{ViewMap[current]}</div>
     </Card>
   );
-};
+});
 
 export default Diagnose;

+ 4 - 2
src/pages/device/Instance/Detail/Running/Property/PropertyCard.tsx

@@ -41,7 +41,7 @@ const Property = (props: Props) => {
   const [visible, setVisible] = useState<boolean>(false);
   const [editVisible, setEditVisible] = useState<boolean>(false);
   const [indicatorVisible, setIndicatorVisible] = useState<boolean>(false);
-  const [dataValue, setDataValue] = useState<any>(null);
+  const [dataValue, setDataValue] = useState<any>(value);
 
   const renderTitle = (title: string) => {
     return (
@@ -107,7 +107,9 @@ const Property = (props: Props) => {
           <div style={{ marginTop: 10 }}>
             <div style={{ color: 'rgba(0, 0, 0, .65)', fontSize: 12 }}>更新时间</div>
             <div style={{ marginTop: 5, fontSize: 16, color: 'black' }} className="value">
-              {value?.timestamp ? moment(value?.timestamp).format('YYYY-MM-DD HH:mm:ss') : ''}
+              {dataValue?.timestamp
+                ? moment(dataValue?.timestamp).format('YYYY-MM-DD HH:mm:ss')
+                : ''}
             </div>
           </div>
         </div>

+ 23 - 13
src/pages/device/Instance/Detail/Running/Property/index.tsx

@@ -53,6 +53,8 @@ const Property = (props: Props) => {
   const [indicatorVisible, setIndicatorVisible] = useState<boolean>(false);
   const [loading, setLoading] = useState<boolean>(true);
 
+  const [loading1, setLoading1] = useState<boolean>(true); // 使valueChange里面能拿到最新的propertyValue
+
   const [check, setCheck] = useState<boolean>(true);
 
   const refreshProperty = async (id: string) => {
@@ -147,19 +149,21 @@ const Property = (props: Props) => {
 
   const subRef = useRef<any>(null);
 
-  const valueChange = (payload: any) => {
-    (payload || [])
-      .sort((a: any, b: any) => a.timestamp - b.timestamp)
-      .forEach((item: any) => {
-        const { value } = item;
-        propertyValue[value?.property] = { ...item, ...value };
-      });
-    setPropertyValue({ ...propertyValue });
-    list.current = [];
-  };
+  const valueChange = useCallback(
+    (payload: any) => {
+      (payload || [])
+        .sort((a: any, b: any) => a.timestamp - b.timestamp)
+        .forEach((item: any) => {
+          const { value } = item;
+          propertyValue[value?.property] = { ...item, ...value };
+        });
+      setPropertyValue({ ...propertyValue });
+      list.current = [];
+    },
+    [propertyValue],
+  );
 
-  // eslint-disable-next-line react-hooks/exhaustive-deps
-  const throttleFn = useCallback(throttle(valueChange, 500), []);
+  const throttleFn = throttle(valueChange, 500);
 
   /**
    * 订阅属性数据
@@ -219,15 +223,21 @@ const Property = (props: Props) => {
             });
           setPropertyValue({ ...propertyValue, ...obj });
         }
+        setLoading1(false);
         setLoading(false);
       },
     });
   };
 
   useEffect(() => {
+    if (!loading1) {
+      subscribeProperty();
+    }
+  }, [loading1]);
+
+  useEffect(() => {
     if (dataSource.data.length > 0) {
       getDashboard();
-      subscribeProperty();
     } else {
       setLoading(false);
     }

+ 1 - 0
src/pages/device/Instance/typings.d.ts

@@ -43,6 +43,7 @@ export type DeviceInstance = {
   accessProvider?: string;
   accessId?: string;
   features?: any[];
+  parentId?: string;
 };
 
 type Unit = {

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

@@ -5,11 +5,13 @@ import Device from './device';
 import Init from './init';
 import Ops from './ops';
 import Service from './service';
+import { Skeleton } from 'antd';
 
 export const service = new Service();
 const Home = () => {
   type ViewType = keyof typeof ViewMap;
   const [current, setCurrent] = useState<ViewType>('init'); // 默认为初始化
+  const [loading, setLoading] = useState(true);
 
   const ViewMap = {
     init: <Init changeView={(value: ViewType) => setCurrent(value)} />,
@@ -20,6 +22,7 @@ const Home = () => {
 
   useEffect(() => {
     service.queryView().then((resp) => {
+      setLoading(false);
       if (resp.status === 200) {
         if (resp.result.length == 0) {
           setCurrent('init');
@@ -29,6 +32,12 @@ const Home = () => {
       }
     });
   }, []);
-  return <PageContainer>{ViewMap[current]}</PageContainer>;
+  return (
+    <PageContainer>
+      <Skeleton loading={loading} active>
+        {ViewMap[current]}
+      </Skeleton>
+    </PageContainer>
+  );
 };
 export default Home;

+ 13 - 9
src/pages/link/AccessConfig/Detail/Access/index.tsx

@@ -101,13 +101,21 @@ const Access = (props: Props) => {
       if (!procotolCurrent) {
         onlyMessage('请选择消息协议!', 'error');
       } else {
-        service
-          .getConfigView(procotolCurrent, ProcotoleMapping.get(props.provider?.id))
-          .then((resp) => {
+        if (props.provider?.channel !== 'child-device') {
+          service
+            .getConfigView(procotolCurrent, ProcotoleMapping.get(props.provider?.id))
+            .then((resp) => {
+              if (resp.status === 200) {
+                setConfig(resp.result);
+              }
+            });
+        } else {
+          service.getChildConfigView(procotolCurrent).then((resp) => {
             if (resp.status === 200) {
               setConfig(resp.result);
             }
           });
+        }
         setCurrent(current + 1);
       }
     }
@@ -440,13 +448,9 @@ const Access = (props: Props) => {
                         borderColor:
                           procotolCurrent === item.id ? 'var(--ant-primary-color-active)' : '',
                       }}
-                      hoverable={!props.data.id}
+                      hoverable
                       onClick={() => {
-                        if (!props.data.id) {
-                          setProcotolCurrent(item.id);
-                        } else {
-                          onlyMessage('消息协议不可修改', 'warning');
-                        }
+                        setProcotolCurrent(item.id);
                       }}
                     >
                       <div style={{ height: '45px' }}>

+ 5 - 0
src/pages/link/AccessConfig/service.ts

@@ -37,6 +37,11 @@ class Service extends BaseService<AccessItem> {
       params,
     });
   };
+  public getChildConfigView = (id: string) => {
+    return request(`/${SystemConst.API_BASE}/protocol/${id}/transports`, {
+      method: 'GET',
+    });
+  };
   public getConfigView = (id: string, transport: string) =>
     request(`/${SystemConst.API_BASE}/protocol/${id}/transport/${transport}`, {
       method: 'GET',

+ 4 - 1
src/pages/media/Device/Save/index.tsx

@@ -52,6 +52,7 @@ const Save = () => {
           const _accessType = res.result?.provider || DefaultAccessType;
           setAccessType(_accessType);
           queryProduct(_accessType);
+          setOldPassword(res.result.password);
         }
       });
     } else {
@@ -73,6 +74,9 @@ const Save = () => {
       if (formData.id === '') {
         delete extraFormData.id;
       }
+      // if (formData.password === oldPassword) {
+      //   delete extraFormData.password;
+      // }
       const resp =
         provider === DefaultAccessType
           ? await service.saveGB(extraFormData)
@@ -249,7 +253,6 @@ const Save = () => {
                             form.setFieldsValue({
                               password: pwd,
                             });
-                            setOldPassword(pwd);
                           }}
                         />
                       </Form.Item>

+ 33 - 3
src/pages/rule-engine/Alarm/Config/index.tsx

@@ -266,7 +266,7 @@ const Config = () => {
     },
   };
 
-  const ioSchema: ISchema = {
+  const outputSchema: ISchema = {
     type: 'object',
     properties: {
       id: {
@@ -333,6 +333,36 @@ const Config = () => {
     },
   };
 
+  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();
@@ -443,7 +473,7 @@ const Config = () => {
               }
             />
             <Form form={outputForm} layout="vertical">
-              <SchemaField schema={ioSchema} />
+              <SchemaField schema={outputSchema} />
             </Form>
             <Divider />
             <TitleComponent
@@ -457,7 +487,7 @@ const Config = () => {
               }
             />
             <Form form={inputForm} layout="vertical">
-              <SchemaField schema={ioSchema} />
+              <SchemaField schema={inputSchema} />
               <FormButtonGroup.Sticky>
                 <FormButtonGroup.FormItem>
                   <Button type="primary" onClick={handleSaveIO}>

+ 31 - 13
src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx

@@ -1,8 +1,10 @@
 import { DatePicker, Input, InputNumber, Select } from 'antd';
 import { useCallback, useEffect, useState } from 'react';
-import { useRequest } from 'umi';
+import type { FormInstance } from 'antd';
 import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
 import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
+import { BuiltInParamsHandleTreeData } from '@/pages/rule-engine/Scene/Save/components/BuiltInParams';
+
 import moment from 'moment';
 
 type ChangeType = {
@@ -18,6 +20,9 @@ interface BuiltInProps {
   notifyType?: string;
   onChange?: (value: ChangeType) => void;
   trigger?: any;
+  parallel?: boolean;
+  form: FormInstance;
+  name: number;
 }
 
 export default (props: BuiltInProps) => {
@@ -25,22 +30,35 @@ export default (props: BuiltInProps) => {
   const [value, setValue] = useState(props.value?.value);
   const [upperKey, setUpperKey] = useState(props.value?.upperKey);
 
-  const [builtInList, setBuiltInList] = useState([]);
-
-  const { run: getBuiltInList } = useRequest(queryBuiltInParams, {
-    manual: true,
-    formatResult: (res) => res.result,
-    onSuccess: (res) => {
-      setBuiltInList(res);
-    },
-  });
+  const [builtInList, setBuiltInList] = useState<any[]>([]);
 
   useEffect(() => {
     console.log(props.trigger);
-    if (source === 'upper' && props.trigger) {
-      getBuiltInList({ ...props.trigger });
+    // if (source === 'upper' && props.trigger) {
+    //   getBuiltInList({ ...props.trigger });
+    // }
+    if (source === 'upper') {
+      if (props.parallel === false) {
+        const data = props.form.getFieldsValue();
+        const params = props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
+        queryBuiltInParams(data, params).then((res: any) => {
+          if (res.status === 200) {
+            const actionParams = res.result.filter(
+              (item: any) => item.id === `action_${props.name}`,
+            );
+            const _data = props.name === 0 ? res.result : BuiltInParamsHandleTreeData(actionParams);
+            setBuiltInList(_data);
+          }
+        });
+      } else {
+        queryBuiltInParams({ ...props.trigger }).then((res: any) => {
+          if (res.status === 200) {
+            setBuiltInList(BuiltInParamsHandleTreeData(res.result));
+          }
+        });
+      }
     }
-  }, [source, props.trigger]);
+  }, [source, props.trigger, props.parallel]);
 
   useEffect(() => {
     setSource(props.value?.source);

+ 5 - 1
src/pages/rule-engine/Scene/Save/action/VariableItems/org.tsx

@@ -6,6 +6,7 @@ import {
 } from '@/pages/rule-engine/Scene/Save/action/service';
 
 type ChangeType = {
+  source?: string;
   value?: string[];
 };
 
@@ -54,7 +55,10 @@ export default (props: OrgProps) => {
       }}
       onChange={(key) => {
         if (props.onChange) {
-          props.onChange({ value: key });
+          props.onChange({
+            source: 'fixed',
+            value: key,
+          });
         }
       }}
       placeholder={'请选择部门'}

+ 33 - 20
src/pages/rule-engine/Scene/Save/action/VariableItems/user.tsx

@@ -8,7 +8,6 @@ import {
   queryRelationUsers,
   queryWechatUsers,
 } from '@/pages/rule-engine/Scene/Save/action/service';
-import { defer, filter, forkJoin, from, map } from 'rxjs';
 
 type ChangeType = {
   source?: string;
@@ -30,7 +29,6 @@ export default (props: UserProps) => {
   const [relationList, setRelationList] = useState([]);
   const [treeData, setTreeData] = useState([
     { name: '平台用户', id: 'p1', selectable: false, children: [] },
-    { name: '关系用户', id: 'p2', selectable: false, children: [] },
   ]);
 
   useEffect(() => {
@@ -52,25 +50,15 @@ export default (props: UserProps) => {
   }, [props.value]);
 
   const getPlatformUser = async () => {
-    forkJoin(
-      defer(() => from(queryPlatformUsers())).pipe(
-        filter((item) => item.status === 200),
-        map((resp) => resp.result),
-      ),
-      defer(() => from(queryRelationUsers())).pipe(
-        filter((item) => item.status === 200),
-        map((resp) => resp.result),
-      ),
-    ).subscribe((res) => {
-      const newTree = [...treeData];
-      res.forEach((item, index) => {
-        newTree[index].children = item;
-      });
-      setTreeData(newTree);
-    });
-  };
+    const newTree = [...treeData];
+    const platformResp = await queryPlatformUsers();
 
-  console.log('treeData', treeData);
+    if (platformResp.status === 200) {
+      newTree[0].children = platformResp.result;
+    }
+
+    setTreeData(newTree);
+  };
 
   const getRelationUsers = async (notifyType: string, configId: string) => {
     if (notifyType === 'dingTalk') {
@@ -175,6 +163,31 @@ export default (props: UserProps) => {
     }
   };
 
+  useEffect(() => {
+    if (props.type) {
+      const newTree = [...treeData];
+      if (props.type === 'device') {
+        queryRelationUsers().then((relationResp) => {
+          if (relationResp.status === 200) {
+            newTree.push({
+              name: '关系用户',
+              id: 'p2',
+              selectable: false,
+              children: relationResp.result,
+            });
+            setTreeData(newTree);
+          }
+        });
+      } else {
+        if (newTree.length > 1) {
+          newTree.splice(1, 1);
+          setTreeData(newTree);
+        }
+      }
+      onchange(props.value?.source, '');
+    }
+  }, [props.type]);
+
   const filterOption = (input: string, option: any) => {
     return option.name ? option.name.toLowerCase().includes(input.toLowerCase()) : false;
   };

+ 102 - 36
src/pages/rule-engine/Scene/Save/action/action.tsx

@@ -1,5 +1,5 @@
 import type { FormInstance } from 'antd';
-import { Button, Col, Form, Row, Select } from 'antd';
+import { Button, Checkbox, Col, Form, Row, Select } from 'antd';
 import { useCallback, useEffect, useState } from 'react';
 import { useRequest } from 'umi';
 import {
@@ -16,6 +16,7 @@ import FunctionCall from './device/functionCall';
 import { InputNumber } from '../components';
 import { DeleteOutlined } from '@ant-design/icons';
 import { observer } from '@formily/reactive-react';
+import ConditionalFiltering from './device/ConditionalFiltering';
 
 interface ActionProps {
   restField: any;
@@ -26,6 +27,7 @@ interface ActionProps {
   onRemove: () => void;
   actionItemData?: any;
   trigger?: any;
+  parallel?: boolean;
 }
 
 export default observer((props: ActionProps) => {
@@ -41,6 +43,8 @@ export default observer((props: ActionProps) => {
   const [properties, setProperties] = useState([]); // 物模型-属性
   const [functionList, setFunctionList] = useState([]); // 物模型-功能
 
+  const [isFiltering, setIsFiltering] = useState(false);
+
   const { data: messageType, run: queryMessageTypes } = useRequest(queryMessageType, {
     manual: true,
     formatResult: (res) => res.result,
@@ -119,6 +123,11 @@ export default observer((props: ActionProps) => {
       if (data.executor) {
         setType1(data.executor);
       }
+      console.log(data.terms);
+      if (data.terms) {
+        // 显示过滤条件
+        setIsFiltering(true);
+      }
 
       if (data.notify) {
         // 消息通知
@@ -200,6 +209,21 @@ export default observer((props: ActionProps) => {
     </>
   );
 
+  const parallelNode = (
+    <Col span={2}>
+      {!props.parallel ? (
+        <Checkbox
+          checked={isFiltering}
+          onChange={(e) => {
+            setIsFiltering(e.target.checked);
+          }}
+        >
+          条件过滤
+        </Checkbox>
+      ) : null}
+    </Col>
+  );
+
   useEffect(() => {
     if (type1 === 'notify') {
       queryMessageTypes();
@@ -250,6 +274,7 @@ export default observer((props: ActionProps) => {
             onMessageTypeChange={setDeviceMessageType}
             onFunctionChange={setFunctionList}
             restField={props.restField}
+            parallel={props.parallel}
           />
         )}
         {type1 === 'delay' && (
@@ -270,54 +295,95 @@ export default observer((props: ActionProps) => {
           notifyType={notifyType}
           triggerType={props.triggerType}
           configId={configId}
+          parallel={props.parallel}
         />
       ) : null}
       {type1 === 'device' &&
       deviceMessageType === MessageTypeEnum.WRITE_PROPERTY &&
       properties.length ? (
-        <Form.Item
-          name={[name, 'device', 'message', 'properties']}
-          rules={[
-            {
-              validator: async (_: any, value: any) => {
-                if (value) {
-                  if (!Object.values(value)[0]) {
-                    return Promise.reject(new Error('请输入属性值'));
-                  }
-                } else {
-                  return Promise.reject(new Error('请选择属性'));
-                }
-                return Promise.resolve();
-              },
-            },
-          ]}
-        >
-          <WriteProperty properties={properties} type={props.triggerType} form={props.form} />
-        </Form.Item>
+        <>
+          <Row gutter={24}>
+            <Col span={18}>
+              <Form.Item
+                name={[name, 'device', 'message', 'properties']}
+                rules={[
+                  {
+                    validator: async (_: any, value: any) => {
+                      if (value) {
+                        if (!Object.values(value)[0]) {
+                          return Promise.reject(new Error('请输入属性值'));
+                        }
+                      } else {
+                        return Promise.reject(new Error('请选择属性'));
+                      }
+                      return Promise.resolve();
+                    },
+                  },
+                ]}
+              >
+                <WriteProperty
+                  name={name}
+                  properties={properties}
+                  type={props.triggerType}
+                  form={props.form}
+                  parallel={props.parallel}
+                />
+              </Form.Item>
+            </Col>
+            {parallelNode}
+          </Row>
+          {!props.parallel && isFiltering && (
+            <Row gutter={24}>
+              <ConditionalFiltering
+                name={name}
+                form={props.form}
+                data={props.actionItemData.terms}
+              />
+            </Row>
+          )}
+        </>
       ) : null}
       {type1 === 'device' &&
       deviceMessageType === MessageTypeEnum.READ_PROPERTY &&
       properties.length ? (
-        <Row gutter={24}>
-          <Col span={4}>
-            <Form.Item
-              name={[name, 'device', 'message', 'properties']}
-              rules={[{ required: true, message: '请选择属性' }]}
-            >
-              <ReadProperty properties={properties} />
-            </Form.Item>
-          </Col>
-        </Row>
+        <>
+          <Row gutter={24}>
+            <Col span={4}>
+              <Form.Item
+                name={[name, 'device', 'message', 'properties']}
+                rules={[{ required: true, message: '请选择属性' }]}
+              >
+                <ReadProperty properties={properties} />
+              </Form.Item>
+            </Col>
+            {parallelNode}
+          </Row>
+          {!props.parallel && isFiltering && (
+            <Row gutter={24}>
+              <ConditionalFiltering
+                name={name}
+                form={props.form}
+                data={props.actionItemData.terms}
+              />
+            </Row>
+          )}
+        </>
       ) : null}
       {type1 === 'device' &&
       deviceMessageType === MessageTypeEnum.INVOKE_FUNCTION &&
       functionList.length ? (
-        <Form.Item
-          name={[name, 'device', 'message', 'inputs']}
-          rules={[{ required: true, message: '请输入功能值' }]}
-        >
-          <FunctionCall functionData={functionList} />
-        </Form.Item>
+        <>
+          <Form.Item
+            name={[name, 'device', 'message', 'inputs']}
+            rules={[{ required: true, message: '请输入功能值' }]}
+          >
+            <FunctionCall functionData={functionList} />
+          </Form.Item>
+          <Row gutter={24}>
+            {parallelNode}
+            <ConditionalFiltering name={name} form={props.form} data={props.actionItemData.terms} />
+          </Row>
+        </>
       ) : null}
     </div>
   );

+ 223 - 0
src/pages/rule-engine/Scene/Save/action/device/ConditionalFiltering.tsx

@@ -0,0 +1,223 @@
+import { Col, Form, Select, InputNumber, Input, DatePicker, Space, TreeSelect } from 'antd';
+import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
+import type { FormInstance } from 'antd';
+import { useEffect, useState, useCallback } from 'react';
+import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
+
+interface ConditionalFilteringProps {
+  name: number;
+  form: FormInstance;
+  data?: any;
+}
+
+export default (props: ConditionalFilteringProps) => {
+  const [builtInList, setBuiltInList] = useState<any[]>([]);
+  const [source, setSource] = useState<any>('fixed');
+  const [type, setType] = useState('string');
+  const [termTypes, setTermTypes] = useState([]);
+
+  const valueTypeMap = (_type: string) => {
+    switch (_type) {
+      case 'init':
+      case 'float':
+      case 'double':
+      case 'long':
+        return <InputNumber />;
+      case 'date':
+        return (
+          <>
+            {
+              // @ts-ignore
+              <DatePicker />
+            }
+          </>
+        );
+      case 'boolean':
+        return (
+          <Select
+            options={[
+              { label: '是', value: 'true' },
+              { label: '否', value: 'false' },
+            ]}
+          />
+        );
+      default:
+        return <Input />;
+    }
+  };
+
+  const handleName = (data: any) => {
+    return (
+      <Space>
+        {data.name}
+        {data.description && (
+          <div style={{ color: 'grey', marginLeft: '5px' }}>({data.description})</div>
+        )}
+      </Space>
+    );
+  };
+
+  const handleTreeData = (data: any): any[] => {
+    if (data.length > 0) {
+      return data.map((item: any) => {
+        const name = handleName(item);
+        if (item.children) {
+          return { ...item, name, disabled: true, children: handleTreeData(item.children) };
+        }
+        return { ...item, name };
+      });
+    }
+    return [];
+  };
+
+  const getItemByChildren = (id: string, data: any[]) => {
+    let BuiltItem = undefined;
+    data.forEach((_item) => {
+      if (_item.id === id) {
+        BuiltItem = _item;
+      } else if (_item.children) {
+        BuiltItem = getItemByChildren(id, _item.children);
+      }
+    });
+    return BuiltItem;
+  };
+
+  const getBuiltItemById = useCallback(
+    (id: string) => {
+      const builtItem: any = getItemByChildren(id, builtInList);
+      console.log(builtItem, id);
+      if (builtItem) {
+        setType(builtItem.type);
+        setTermTypes(builtItem.termTypes);
+      }
+      return builtItem ? builtItem : {};
+    },
+    [builtInList],
+  );
+
+  useEffect(() => {
+    const data = props.form.getFieldsValue();
+    queryBuiltInParams(data, { action: props.name }).then((res: any) => {
+      if (res.status === 200) {
+        const actionParams = res.result.filter(
+          (item: any) => item.id === `action_${props.name + 1}`,
+        );
+        setBuiltInList(handleTreeData(actionParams));
+      }
+    });
+  }, []);
+
+  useEffect(() => {
+    console.log('Conditional', builtInList);
+    if (props.data[0] && props.data[0].column && builtInList && builtInList.length) {
+      getBuiltItemById(props.data[0].column);
+    }
+  }, [props.data, builtInList]);
+  return (
+    <>
+      <Col span={4}>
+        <Form.Item name={[props.name, 'terms', 0, 'column']}>
+          <TreeSelect
+            placeholder={'请选择参数'}
+            fieldNames={{
+              value: 'id',
+              label: 'name',
+            }}
+            treeData={builtInList}
+            onSelect={() => {
+              props.form.setFields([
+                {
+                  name: [props.name, 'terms', 0, 'termType'],
+                  value: undefined,
+                },
+                {
+                  name: [props.name, 'terms', 0, 'value', 'source'],
+                  value: 'fixed',
+                },
+                {
+                  name: [props.name, 'terms', 0, 'value', 'value'],
+                  value: undefined,
+                },
+                {
+                  name: [props.name, 'terms', 0, 'value', 'value', 0],
+                  value: undefined,
+                },
+                {
+                  name: [props.name, 'terms', 0, 'value', 'value', 1],
+                  value: undefined,
+                },
+              ]);
+            }}
+          />
+        </Form.Item>
+      </Col>
+      <Col span={2}>
+        <Form.Item name={[props.name, 'terms', 0, 'termType']}>
+          <Select
+            style={{ width: '100%' }}
+            options={termTypes}
+            fieldNames={{ value: 'id', label: 'name' }}
+            placeholder={'操作符'}
+          />
+        </Form.Item>
+      </Col>
+      <Col span={7}>
+        <Form.Item noStyle>
+          <ItemGroup>
+            <Form.Item name={[props.name, 'terms', 0, 'value', 'source']} initialValue={'fixed'}>
+              <Select
+                options={[
+                  { label: '手动输入', value: 'fixed' },
+                  { label: '内置参数', value: 'upper' },
+                ]}
+                style={{ width: 120 }}
+                onSelect={(v: any) => {
+                  setSource(v);
+                }}
+              />
+            </Form.Item>
+            {['nbtw', 'btw'].includes(props.data[0] && props.data[0].termType) ? (
+              <>
+                <Form.Item name={[props.name, 'terms', 0, 'value', 'value', 0]}>
+                  {source === 'fixed' ? (
+                    valueTypeMap(type)
+                  ) : (
+                    <TreeSelect
+                      placeholder={'请选择参数'}
+                      fieldNames={{ value: 'id', label: 'name' }}
+                      treeData={builtInList}
+                    />
+                  )}
+                </Form.Item>
+                <Form.Item name={[props.name, 'terms', 0, 'value', 'value', 1]}>
+                  {source === 'fixed' ? (
+                    valueTypeMap(type)
+                  ) : (
+                    <TreeSelect
+                      placeholder={'请选择参数'}
+                      fieldNames={{ value: 'id', label: 'name' }}
+                      treeData={builtInList}
+                    />
+                  )}
+                </Form.Item>
+              </>
+            ) : (
+              <Form.Item name={[props.name, 'terms', 0, 'value', 'value']}>
+                {source === 'fixed' ? (
+                  valueTypeMap(type)
+                ) : (
+                  <TreeSelect
+                    placeholder={'请选择参数'}
+                    fieldNames={{ value: 'id', label: 'name' }}
+                    treeData={builtInList}
+                  />
+                )}
+              </Form.Item>
+            )}
+          </ItemGroup>
+        </Form.Item>
+      </Col>
+      <Col>不执行后续动作</Col>
+    </>
+  );
+};

+ 96 - 36
src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx

@@ -1,9 +1,10 @@
-import { Col, DatePicker, FormInstance, Input, InputNumber, Row, Select } from 'antd';
+import { Col, DatePicker, Input, InputNumber, Row, Select, TreeSelect } from 'antd';
+import type { FormInstance } from 'antd';
 import { useCallback, useEffect, useState } from 'react';
-import { useRequest } from '@@/plugin-request/request';
 import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
 import moment from 'moment';
 import { ItemGroup } from '@/pages/rule-engine/Scene/Save/components';
+import { Space } from '@formily/antd';
 
 interface WritePropertyProps {
   properties: any[];
@@ -11,40 +12,90 @@ interface WritePropertyProps {
   form: FormInstance;
   value?: any;
   onChange?: (value?: any) => void;
+  parallel?: boolean;
+  name: number;
 }
 
 export default (props: WritePropertyProps) => {
   const [source, setSource] = useState('fixed');
-  const [builtInList, setBuiltInList] = useState([]);
+  const [builtInList, setBuiltInList] = useState<any[]>([]);
   const [propertiesKey, setPropertiesKey] = useState<string | undefined>(undefined);
   const [propertiesValue, setPropertiesValue] = useState(undefined);
   const [propertiesType, setPropertiesType] = useState('');
 
-  const { run: getBuiltInList } = useRequest(queryBuiltInParams, {
-    manual: true,
-    formatResult: (res) => res.result,
-    onSuccess: (res) => {
-      setBuiltInList(res);
-    },
-  });
+  const handleName = (data: any) => {
+    return (
+      <Space>
+        {data.name}
+        {data.description && (
+          <div style={{ color: 'grey', marginLeft: '5px' }}>({data.description})</div>
+        )}
+      </Space>
+    );
+  };
+
+  const handleTreeData = (data: any): any[] => {
+    if (data.length > 0) {
+      return data.map((item: any) => {
+        const name = handleName(item);
+        if (item.children) {
+          return { ...item, name, disabled: true, children: handleTreeData(item.children) };
+        }
+        return { ...item, name };
+      });
+    }
+    return [];
+  };
+
+  const onChange = (key?: string, value?: any, _source: string = 'fixed') => {
+    if (props.onChange) {
+      props.onChange({
+        [key || 0]: {
+          value,
+          source: _source,
+        },
+      });
+    }
+  };
 
   useEffect(() => {
     if (source === 'upper') {
-      getBuiltInList({
-        trigger: { type: props.type },
-      });
+      onChange(propertiesKey, undefined, source);
+      if (props.parallel === false) {
+        // 串行
+        const params = props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
+        const data = props.form.getFieldsValue();
+        queryBuiltInParams(data, params).then((res: any) => {
+          if (res.status === 200) {
+            const actionParams = res.result.filter(
+              (item: any) => item.id === `action_${props.name}`,
+            );
+            const _data = props.name === 0 ? res.result : handleTreeData(actionParams);
+            setBuiltInList(_data);
+          }
+        });
+      } else {
+        // 并行
+        queryBuiltInParams({
+          trigger: { type: props.type },
+        }).then((res: any) => {
+          if (res.status === 200) {
+            setBuiltInList(handleTreeData(res.result));
+          }
+        });
+      }
     }
-  }, [source, props.type]);
+  }, [source, props.type, props.parallel]);
 
   useEffect(() => {
-    console.log('writeProperty', props.value);
     if (props.value && props.properties && props.properties.length) {
       if (0 in props.value) {
         setPropertiesValue(props.value[0]);
       } else {
         Object.keys(props.value).forEach((key: string) => {
           setPropertiesKey(key);
-          setPropertiesValue(props.value[key]);
+          setPropertiesValue(props.value[key].value);
+          setSource(props.value[key].source);
           const propertiesItem = props.properties.find((item: any) => item.id === key);
           if (propertiesItem) {
             setPropertiesType(propertiesItem.valueType.type);
@@ -54,14 +105,6 @@ export default (props: WritePropertyProps) => {
     }
   }, [props.value, props.properties]);
 
-  const onChange = (key?: string, value?: any) => {
-    if (props.onChange) {
-      props.onChange({
-        [key || 0]: value,
-      });
-    }
-  };
-
   const inputNodeByType = useCallback(
     (type: string) => {
       switch (type) {
@@ -76,7 +119,7 @@ export default (props: WritePropertyProps) => {
               ]}
               placeholder={'请选择'}
               onChange={(value) => {
-                onChange(propertiesKey, value);
+                onChange(propertiesKey, value, source);
               }}
             />
           );
@@ -90,7 +133,7 @@ export default (props: WritePropertyProps) => {
               value={propertiesValue}
               placeholder={'请输入'}
               onChange={(value) => {
-                onChange(propertiesKey, value);
+                onChange(propertiesKey, value, source);
               }}
             />
           );
@@ -101,7 +144,11 @@ export default (props: WritePropertyProps) => {
               style={{ width: '100%' }}
               value={propertiesValue ? moment(propertiesValue, 'YYYY-MM-DD HH:mm:ss') : undefined}
               onChange={(date) => {
-                onChange(propertiesKey, date ? date.format('YYYY-MM-DD HH:mm:ss') : undefined);
+                onChange(
+                  propertiesKey,
+                  date ? date.format('YYYY-MM-DD HH:mm:ss') : undefined,
+                  source,
+                );
               }}
             />
           );
@@ -111,17 +158,17 @@ export default (props: WritePropertyProps) => {
               style={{ width: '100%' }}
               value={propertiesValue}
               placeholder={'请输入'}
-              onChange={(e) => onChange(propertiesKey, e.target.value)}
+              onChange={(e) => onChange(propertiesKey, e.target.value, source)}
             />
           );
       }
     },
-    [propertiesKey, propertiesValue],
+    [propertiesKey, propertiesValue, source],
   );
 
   return (
     <Row gutter={24}>
-      <Col span={4}>
+      <Col span={6}>
         <Select
           value={propertiesKey}
           options={props.properties.filter((item) => {
@@ -133,12 +180,12 @@ export default (props: WritePropertyProps) => {
           fieldNames={{ label: 'name', value: 'id' }}
           style={{ width: '100%' }}
           onSelect={(key: any) => {
-            onChange(key, undefined);
+            onChange(key, undefined, source);
           }}
           placeholder={'请选择属性'}
         ></Select>
       </Col>
-      <Col span={7}>
+      <Col span={16}>
         <ItemGroup compact>
           <Select
             value={source}
@@ -149,18 +196,31 @@ export default (props: WritePropertyProps) => {
             style={{ width: 120 }}
             onChange={(key) => {
               setSource(key);
+              onChange(propertiesKey, propertiesValue, key);
             }}
           />
           {source === 'upper' ? (
-            <Select
-              options={builtInList}
-              fieldNames={{ label: 'name', value: 'id' }}
+            <TreeSelect
               placeholder={'请选择参数'}
+              fieldNames={{
+                value: 'id',
+                label: 'name',
+              }}
+              value={propertiesValue}
+              treeData={builtInList}
               onSelect={(value: any) => {
-                onChange(propertiesKey, value);
+                onChange(propertiesKey, value, source);
               }}
             />
           ) : (
+            // <Select
+            //   options={builtInList}
+            //   fieldNames={{ label: 'name', value: 'id' }}
+            //   placeholder={'请选择参数'}
+            //   onSelect={(value: any) => {
+            //     onChange(propertiesKey, value);
+            //   }}
+            // />
             <div>{inputNodeByType(propertiesType)}</div>
           )}
         </ItemGroup>

+ 7 - 1
src/pages/rule-engine/Scene/Save/action/device/index.tsx

@@ -16,6 +16,7 @@ interface DeviceProps {
   onProperties: (data: any) => void;
   onMessageTypeChange: (type: string) => void;
   onFunctionChange: (functionItem: any) => void;
+  parallel?: boolean;
 }
 
 enum SourceEnum {
@@ -77,7 +78,6 @@ export default (props: DeviceProps) => {
   };
 
   useEffect(() => {
-    props.form?.resetFields([['actions', name, 'device', 'selector']]);
     if (props.triggerType === 'device') {
       setSourceList([
         ...DefaultSourceOptions,
@@ -87,6 +87,12 @@ export default (props: DeviceProps) => {
     } else {
       setSourceList(DefaultSourceOptions);
     }
+    props.form?.setFields([
+      {
+        name: ['actions', name, 'device', 'selector'],
+        value: SourceEnum.fixed,
+      },
+    ]);
   }, [props.triggerType]);
 
   useEffect(() => {

+ 8 - 1
src/pages/rule-engine/Scene/Save/action/messageContent.tsx

@@ -17,6 +17,7 @@ interface MessageContentProps {
   triggerType: string;
   configId: string;
   trigger?: any;
+  parallel?: boolean;
 }
 
 const rowGutter = 12;
@@ -148,7 +149,13 @@ export default (props: MessageContentProps) => {
                       ) : type === 'link' ? (
                         <Input placeholder={'请输入' + item.name} />
                       ) : (
-                        <BuiltIn type={props.triggerType} trigger={props.trigger} data={item} />
+                        <BuiltIn
+                          form={props.form}
+                          name={props.name}
+                          type={props.triggerType}
+                          trigger={props.trigger}
+                          data={item}
+                        />
                       )}
                     </Form.Item>
                   </Col>

+ 2 - 2
src/pages/rule-engine/Scene/Save/action/service.ts

@@ -31,8 +31,8 @@ export const queryDeviceSelector = () =>
   request(`${SystemConst.API_BASE}/scene/device-selectors`, { method: 'GET' });
 
 // 内置参数
-export const queryBuiltInParams = (data: any) =>
-  request(`${SystemConst.API_BASE}/scene/parse-variables`, { method: 'POST', data });
+export const queryBuiltInParams = (data: any, params?: any) =>
+  request(`${SystemConst.API_BASE}/scene/parse-variables`, { method: 'POST', data, params });
 
 // 平台用户
 export const queryPlatformUsers = () =>

+ 37 - 0
src/pages/rule-engine/Scene/Save/components/BuiltInParams.tsx

@@ -0,0 +1,37 @@
+import { Space } from 'antd';
+
+interface BuiltInParamsProps {
+  name: string;
+  description?: string;
+}
+
+const BuiltInParamsTitle = (props: BuiltInParamsProps) => {
+  return (
+    <Space>
+      {props.name}
+      {props.description && (
+        <div style={{ color: 'grey', marginLeft: '5px' }}>({props.description})</div>
+      )}
+    </Space>
+  );
+};
+
+export const BuiltInParamsHandleTreeData = (data: any): any[] => {
+  if (data.length > 0) {
+    return data.map((item: any) => {
+      const name = <BuiltInParamsTitle {...item} />;
+      if (item.children) {
+        return {
+          ...item,
+          name,
+          disabled: true,
+          children: BuiltInParamsHandleTreeData(item.children),
+        };
+      }
+      return { ...item, name };
+    });
+  }
+  return [];
+};
+
+export default BuiltInParamsTitle;

+ 1 - 0
src/pages/rule-engine/Scene/Save/components/ItemGroup/index.less

@@ -6,6 +6,7 @@
   > * {
     &:last-child {
       flex: 1;
+      width: 0;
     }
   }
 

+ 32 - 0
src/pages/rule-engine/Scene/Save/components/TimingTrigger/RangePicker.tsx

@@ -0,0 +1,32 @@
+import { TimePicker } from 'antd';
+import moment from 'moment';
+
+type RangePickerValue = {
+  from: string;
+  to: string;
+};
+
+interface RangePickerProps {
+  value?: RangePickerValue;
+  onChange?: (value: RangePickerValue) => void;
+}
+export default (props: RangePickerProps) => {
+  return (
+    <TimePicker.RangePicker
+      style={{ width: '100%' }}
+      format={'HH:mm:ss'}
+      value={[
+        moment(props.value?.from || new Date(), 'HH:mm:ss'),
+        moment(props.value?.to || new Date(), 'hh:mm:ss'),
+      ]}
+      onChange={(_, dateString) => {
+        if (props.onChange) {
+          props.onChange({
+            from: dateString[0],
+            to: dateString[1],
+          });
+        }
+      }}
+    />
+  );
+};

+ 27 - 0
src/pages/rule-engine/Scene/Save/components/TimingTrigger/TimePicker.tsx

@@ -0,0 +1,27 @@
+import { TimePicker } from 'antd';
+import moment from 'moment';
+
+type TimePickerValue = {
+  time: string;
+};
+
+interface TimePickerProps {
+  value?: TimePickerValue;
+  onChange?: (value: TimePickerValue) => void;
+}
+export default (props: TimePickerProps) => {
+  return (
+    <TimePicker
+      style={{ width: '100%' }}
+      format={'HH:mm:ss'}
+      value={moment(props.value?.time || new Date(), 'HH:mm:ss')}
+      onChange={(_, dateString) => {
+        if (props.onChange) {
+          props.onChange({
+            time: dateString,
+          });
+        }
+      }}
+    />
+  );
+};

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 213 - 0
src/pages/rule-engine/Scene/Save/components/TimingTrigger/refactor.tsx


+ 1 - 1
src/pages/rule-engine/Scene/Save/components/index.ts

@@ -1,5 +1,5 @@
 export { default as TimeSelect } from './TimeSelect';
-export { default as TimingTrigger } from './TimingTrigger';
+export { default as TimingTrigger } from './TimingTrigger/refactor';
 export { default as TriggerWay } from './TriggerWay';
 export { default as ItemGroup } from './ItemGroup';
 export { default as InputFile } from './InputUpload';

+ 14 - 10
src/pages/rule-engine/Scene/Save/index.less

@@ -3,18 +3,22 @@
 .scene-save {
   .trigger-type-content,
   & .scene-actions {
-    padding: 24px;
+    padding: 24px 24px 0 24px;
     background-color: @bgColor;
   }
 
-  .trigger-type-content {
-    > .ant-row {
-      margin-bottom: 24px;
-
-      &:last-child,
-      &:first-child {
-        margin-bottom: 0;
-      }
-    }
+  .ant-form-item-with-help .ant-form-item-explain {
+    height: 0 !important;
   }
+
+  //.trigger-type-content {
+  //  > .ant-row {
+  //    margin-bottom: 24px;
+  //
+  //    &:last-child,
+  //    &:first-child {
+  //      margin-bottom: 0;
+  //    }
+  //  }
+  //}
 }

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 22 - 101
src/pages/rule-engine/Scene/Save/index.tsx


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 47 - 52
src/pages/rule-engine/Scene/Save/trigger/index.tsx


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

@@ -301,8 +301,7 @@ const TriggerTerm = (props: Props, ref: any) => {
                     type: 'void',
                     'x-component': 'FormGrid',
                     'x-decorator-props': {
-                      maxColumns: 24,
-                      minColumns: 24,
+                      columns: 12,
                     },
                     properties: {
                       // columns

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

@@ -73,13 +73,6 @@ const Bind = observer((props: Props) => {
       dataIndex: 'state',
       valueType: 'select',
       valueEnum: {
-        all: {
-          text: intl.formatMessage({
-            id: 'pages.searchTable.titleStatus.all',
-            defaultMessage: '全部',
-          }),
-          status: 'Default',
-        },
         onLine: {
           text: intl.formatMessage({
             id: 'pages.device.instance.status.onLine',

+ 9 - 8
src/pages/system/Department/Assets/deivce/index.tsx

@@ -82,6 +82,7 @@ export default observer((props: { parentId: string }) => {
       dataIndex: 'id',
       title: 'ID',
       width: 220,
+      fixed: 'left',
     },
     {
       dataIndex: 'name',
@@ -89,6 +90,7 @@ export default observer((props: { parentId: string }) => {
         id: 'pages.table.name',
         defaultMessage: '名称',
       }),
+      width: 200,
     },
     {
       title: intl.formatMessage({
@@ -99,6 +101,7 @@ export default observer((props: { parentId: string }) => {
       render: (_, row) => {
         return row.productName;
       },
+      width: 200,
     },
     {
       title: '资产权限',
@@ -107,6 +110,7 @@ export default observer((props: { parentId: string }) => {
       render: (_, row) => {
         return handlePermissionsMap(row.grantedPermissions);
       },
+      width: 80,
     },
     {
       title: intl.formatMessage({
@@ -115,6 +119,7 @@ export default observer((props: { parentId: string }) => {
       }),
       dataIndex: 'registryTime',
       valueType: 'dateTime',
+      width: 160,
     },
     {
       title: intl.formatMessage({
@@ -126,13 +131,6 @@ export default observer((props: { parentId: string }) => {
       // onFilter: true,
       valueType: 'select',
       valueEnum: {
-        all: {
-          text: intl.formatMessage({
-            id: 'pages.searchTable.titleStatus.all',
-            defaultMessage: '全部',
-          }),
-          status: 'Default',
-        },
         onLine: {
           text: intl.formatMessage({
             id: 'pages.device.instance.status.onLine',
@@ -150,13 +148,14 @@ export default observer((props: { parentId: string }) => {
         notActive: {
           text: intl.formatMessage({
             id: 'pages.device.instance.status.notActive',
-            defaultMessage: '未启用',
+            defaultMessage: '用',
           }),
           status: 'notActive',
         },
       },
       render: (_, row) => <DeviceBadge type={row.state.value} text={row.state.text} />,
       search: false,
+      width: 80,
     },
     {
       title: intl.formatMessage({
@@ -166,6 +165,7 @@ export default observer((props: { parentId: string }) => {
       valueType: 'option',
       align: 'center',
       width: 200,
+      fixed: 'right',
       render: (text, record) => [
         <Popconfirm
           title={intl.formatMessage({
@@ -277,6 +277,7 @@ export default observer((props: { parentId: string }) => {
         search={false}
         params={searchParam}
         gridColumn={2}
+        scroll={{ x: 1366 }}
         request={async (params) => {
           if (!props.parentId) {
             return {

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

@@ -69,6 +69,7 @@ export default observer((props: { parentId: string }) => {
       dataIndex: 'id',
       title: 'ID',
       width: 220,
+      fixed: 'left',
     },
     {
       dataIndex: 'name',
@@ -79,6 +80,7 @@ export default observer((props: { parentId: string }) => {
       search: {
         transform: (value) => ({ name$LIKE: value }),
       },
+      width: 200,
     },
     {
       title: '资产权限',
@@ -87,6 +89,7 @@ export default observer((props: { parentId: string }) => {
       render: (_, row) => {
         return handlePermissionsMap(row.grantedPermissions);
       },
+      width: 80,
     },
     {
       title: intl.formatMessage({
@@ -95,6 +98,7 @@ export default observer((props: { parentId: string }) => {
       }),
       dataIndex: 'describe',
       hideInSearch: true,
+      width: 200,
     },
     {
       title: intl.formatMessage({
@@ -104,6 +108,7 @@ export default observer((props: { parentId: string }) => {
       valueType: 'option',
       align: 'center',
       width: 200,
+      fixed: 'right',
       render: (text, record) => [
         <Popconfirm
           title={intl.formatMessage({
@@ -232,6 +237,7 @@ export default observer((props: { parentId: string }) => {
             status: resp.status,
           };
         }}
+        scroll={{ x: 1366 }}
         rowSelection={{
           selectedRowKeys: Models.unBindKeys,
           onChange: (selectedRowKeys, selectedRows) => {

+ 5 - 0
src/pages/system/Department/Member/index.tsx

@@ -52,6 +52,8 @@ const Member = observer((props: { parentId: string }) => {
       search: {
         transform: (value) => ({ name$LIKE: value }),
       },
+      width: 120,
+      fixed: 'left',
     },
     {
       dataIndex: 'username',
@@ -62,6 +64,7 @@ const Member = observer((props: { parentId: string }) => {
       search: {
         transform: (value) => ({ username$LIKE: value }),
       },
+      width: 120,
     },
     {
       title: intl.formatMessage({
@@ -111,6 +114,7 @@ const Member = observer((props: { parentId: string }) => {
           }
         />
       ),
+      width: 80,
     },
     {
       title: intl.formatMessage({
@@ -119,6 +123,7 @@ const Member = observer((props: { parentId: string }) => {
       }),
       valueType: 'option',
       width: 200,
+      fixed: 'right',
       render: (text, record) => [
         <Popconfirm
           title={intl.formatMessage({

+ 3 - 0
src/pages/system/Relationship/index.tsx

@@ -10,6 +10,7 @@ import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
 import Save from './Save';
 import { useDomFullHeight } from '@/hooks';
 import { onlyMessage } from '@/utils/util';
+import { message } from 'antd';
 
 export const service = new Service('relation');
 
@@ -85,6 +86,8 @@ const Relationship = () => {
                   }),
                 );
                 actionRef.current?.reload();
+              } else {
+                message.error(resp.message);
               }
             },
           }}

+ 3 - 1
src/pages/system/Role/index.tsx

@@ -197,14 +197,16 @@ const Role: React.FC = observer(() => {
       CurdModel.add();
     }
     const subscription = Store.subscribe(SystemConst.BASE_UPDATE_DATA, (data) => {
-      console.log('订阅数据');
       if ((window as any).onTabSaveSuccess) {
         (window as any).onTabSaveSuccess(data);
         setTimeout(() => window.close(), 300);
+      } else {
+        history.push(`${getMenuPathByParams(MENUS_CODE['system/Role/Detail'], data.id)}`);
       }
     });
     return () => subscription.unsubscribe();
   }, []);
+
   return (
     <PageContainer>
       <BaseCrud<RoleItem>

+ 5 - 0
yarn.lock

@@ -14192,6 +14192,11 @@ moment@^2.25.3, moment@^2.27.0, moment@^2.29.1:
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
   integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
 
+moment@^2.29.3:
+  version "2.29.3"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3"
+  integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==
+
 monaco-editor-webpack-plugin@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-6.0.0.tgz#628956ce1851afa2a5f6c88d0ecbb24e9a444898"