xieyonghong hace 4 años
padre
commit
81fb371176
Se han modificado 49 ficheros con 2091 adiciones y 792 borrados
  1. BIN
      public/images/alarm/alarm1.png
  2. BIN
      public/images/alarm/alarm2.png
  3. BIN
      public/images/alarm/alarm3.png
  4. BIN
      public/images/alarm/alarm4.png
  5. BIN
      public/images/alarm/alarm5.png
  6. BIN
      public/images/metadata-map.png
  7. BIN
      public/images/scene.png
  8. 0 1
      src/components/BaseCrud/model.ts
  9. 0 8
      src/components/BaseCrud/save/index.tsx
  10. 33 0
      src/components/FLevelInput/index.tsx
  11. 0 1
      src/components/FRuleEditor/Editor/index.tsx
  12. 0 2
      src/components/FSelectDevice/index.tsx
  13. 12 8
      src/pages/device/Instance/Detail/Config/index.tsx
  14. 131 0
      src/pages/device/Instance/Detail/Diagnose/Status/DiagnosticAdvice.tsx
  15. 122 0
      src/pages/device/Instance/Detail/Diagnose/Status/ManualInspection.tsx
  16. 15 0
      src/pages/device/Instance/Detail/Diagnose/Status/index.less
  17. 651 352
      src/pages/device/Instance/Detail/Diagnose/Status/index.tsx
  18. 47 44
      src/pages/device/Instance/Detail/Diagnose/Status/model.ts
  19. 19 0
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.less
  20. 69 71
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx
  21. 46 17
      src/pages/device/Instance/Detail/index.tsx
  22. 10 0
      src/pages/device/Instance/service.ts
  23. 30 54
      src/pages/device/Product/Detail/Access/index.tsx
  24. 0 1
      src/pages/device/Product/Detail/PropertyImport/index.tsx
  25. 32 4
      src/pages/device/Product/Detail/index.tsx
  26. 1 1
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  27. 18 9
      src/pages/device/components/Metadata/Base/columns.ts
  28. 14 14
      src/pages/device/components/Metadata/Base/index.tsx
  29. 1 0
      src/pages/device/components/Metadata/Import/index.tsx
  30. 8 9
      src/pages/device/components/Metadata/index.tsx
  31. 3 1
      src/pages/link/AccessConfig/Detail/Access/index.tsx
  32. 3 1
      src/pages/link/AccessConfig/Detail/Media/index.tsx
  33. 1 1
      src/pages/link/AccessConfig/index.tsx
  34. 5 0
      src/pages/link/AccessConfig/service.ts
  35. 49 101
      src/pages/link/Type/Detail/index.tsx
  36. 3 5
      src/pages/media/Cascade/Channel/BindChannel/index.tsx
  37. 4 4
      src/pages/media/Cascade/Channel/index.tsx
  38. 3 0
      src/pages/notice/Config/Detail/index.tsx
  39. 184 0
      src/pages/notice/Config/SyncUser/index.tsx
  40. 96 53
      src/pages/notice/Config/index.tsx
  41. 38 0
      src/pages/notice/Config/service.ts
  42. 23 18
      src/pages/notice/Template/Detail/index.tsx
  43. 177 0
      src/pages/rule-engine/Alarm/Config/index.tsx
  44. 37 0
      src/pages/rule-engine/Alarm/Log/index.tsx
  45. 7 0
      src/pages/rule-engine/Alarm/Log/model.ts
  46. 134 0
      src/pages/system/User/ResetPassword/index.tsx
  47. 32 12
      src/pages/system/User/Save/index.tsx
  48. 27 0
      src/pages/system/User/index.tsx
  49. 6 0
      src/pages/system/User/serivce.ts

BIN
public/images/alarm/alarm1.png


BIN
public/images/alarm/alarm2.png


BIN
public/images/alarm/alarm3.png


BIN
public/images/alarm/alarm4.png


BIN
public/images/alarm/alarm5.png


BIN
public/images/metadata-map.png


BIN
public/images/scene.png


+ 0 - 1
src/components/BaseCrud/model.ts

@@ -16,7 +16,6 @@ export const CurdModel = model<Option>({
   },
 
   update(current: any) {
-    console.log('触发编辑');
     Store.set(SystemConst.BASE_CURD_MODEL, 'edit');
     Store.set(SystemConst.BASE_CURD_MODAL_VISIBLE, true);
     Store.set(SystemConst.BASE_CURD_CURRENT, current);

+ 0 - 8
src/components/BaseCrud/save/index.tsx

@@ -107,15 +107,7 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
 
   const save = async () => {
     const values: T = await (customForm || form).submit();
-    // 特殊处理通知模版
-
-    if (service?.getUri().includes('/notifier/template')) {
-      (values as T & { template: Record<string, any> | string }).template = JSON.stringify(
-        values.template,
-      );
-    }
     const response = await service.update(values);
-
     if (response.status === 200) {
       Store.set(SystemConst.BASE_UPDATE_DATA, response.result);
     }

+ 33 - 0
src/components/FLevelInput/index.tsx

@@ -0,0 +1,33 @@
+import { ArrayItems } from '@formily/antd';
+import { Input } from 'antd';
+
+interface Props {
+  name?: string;
+  value: string;
+  onChange: () => void;
+}
+
+const LevelInput = (props: Props) => {
+  const alarm1 = require('/public/images/alarm/alarm1.png');
+  const alarm2 = require('/public/images/alarm/alarm2.png');
+  const alarm3 = require('/public/images/alarm/alarm3.png');
+  const alarm4 = require('/public/images/alarm/alarm4.png');
+  const alarm5 = require('/public/images/alarm/alarm5.png');
+
+  const imgMap = {
+    0: alarm1,
+    1: alarm2,
+    2: alarm3,
+    3: alarm4,
+    4: alarm5,
+  };
+  const index = ArrayItems.useIndex!();
+  return (
+    <div>
+      <img src={imgMap[index]} alt="" />
+      级别{index + 1}
+      <Input onChange={props.onChange} value={props.value} />
+    </div>
+  );
+};
+export default LevelInput;

+ 0 - 1
src/components/FRuleEditor/Editor/index.tsx

@@ -94,7 +94,6 @@ const Editor = (props: Props) => {
   };
 
   const handleInsertCode = (value: string) => {
-    console.log(value, 'values');
     const editor = editorRef.current;
     if (!editor || !value) return;
     const position = editor.getPosition()!;

+ 0 - 2
src/components/FSelectDevice/index.tsx

@@ -74,7 +74,6 @@ const FSelectDevice = connect((props: Props) => {
     },
     selectedRowKeys: [data?.id] as Key[],
   };
-  console.log(props?.value, 'de-name');
   return (
     <>
       <Input
@@ -92,7 +91,6 @@ const FSelectDevice = connect((props: Props) => {
           onCancel={() => setVisible(false)}
           onOk={() => {
             setVisible(false);
-            console.log(data, 'dd');
             props.onChange(data);
           }}
         >

+ 12 - 8
src/pages/device/Instance/Detail/Config/index.tsx

@@ -1,11 +1,16 @@
-import {Descriptions, message, Space, Tooltip} from 'antd';
-import {InstanceModel, service} from '@/pages/device/Instance';
-import {useEffect, useState} from 'react';
-import type {ConfigMetadata} from '@/pages/device/Product/typings';
-import {history, useParams} from 'umi';
-import {CheckOutlined, EditOutlined, QuestionCircleOutlined, UndoOutlined,} from '@ant-design/icons';
+import { Descriptions, message, Space, Tooltip } from 'antd';
+import { InstanceModel, service } from '@/pages/device/Instance';
+import { useEffect, useState } from 'react';
+import type { ConfigMetadata } from '@/pages/device/Product/typings';
+import { history, useParams } from 'umi';
+import {
+  CheckOutlined,
+  EditOutlined,
+  QuestionCircleOutlined,
+  UndoOutlined,
+} from '@ant-design/icons';
 import Edit from './Edit';
-import {PermissionButton} from '@/components';
+import { PermissionButton } from '@/components';
 
 const Config = () => {
   const params = useParams<{ id: string }>();
@@ -35,7 +40,6 @@ const Config = () => {
   };
 
   useEffect(() => {
-    console.log(id);
     if (id) {
       service.getConfigMetadata(id).then((config) => {
         setMetadata(config?.result);

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

@@ -0,0 +1,131 @@
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { ExclamationCircleFilled } 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) {
+        // diagnoseConfig();
+        // 没有权限怎么展示
+      }
+    };
+  };
+
+  return (
+    <Modal
+      title="诊断建议"
+      onCancel={() => {
+        props.close();
+      }}
+      width={700}
+      visible
+    >
+      <div className={styles.advice}>
+        <div className={styles.alert}>
+          <ExclamationCircleFilled 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>
+    </Modal>
+  );
+};
+
+export default DiagnosticAdvice;

+ 122 - 0
src/pages/device/Instance/Detail/Diagnose/Status/ManualInspection.tsx

@@ -0,0 +1,122 @@
+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';
+
+const componentMap = {
+  string: 'Input',
+  password: 'Password',
+};
+
+interface Props {
+  close: () => void;
+  metadata: any;
+  ok: (data: any) => void;
+}
+
+const ManualInspection = (props: Props) => {
+  const { metadata } = props;
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: {},
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Password,
+      FormGrid,
+      PreviewText,
+    },
+  });
+
+  const configToSchema = (data: any[]) => {
+    const config = {};
+    data.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: '',
+        },
+      };
+    });
+    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(metadata?.data?.properties),
+        },
+      },
+    };
+
+    return (
+      <>
+        <PreviewText.Placeholder value="-">
+          <Form form={form} layout="vertical">
+            <SchemaField schema={itemSchema} />
+          </Form>
+        </PreviewText.Placeholder>
+      </>
+    );
+  };
+  return (
+    <Modal
+      title="人工检查"
+      onCancel={() => {
+        props.close();
+      }}
+      onOk={async () => {
+        const values = (await form.submit()) as any;
+        if (metadata?.check) {
+          props.ok({
+            status: 'error',
+            data: metadata,
+          });
+        } else {
+          let flag = true;
+          Object.keys(values).forEach((key) => {
+            if (values[key] !== metadata?.check[key]) {
+              flag = false;
+            }
+          });
+          if (flag) {
+            props.ok({
+              status: 'success',
+              data: metadata,
+            });
+          } else {
+            props.ok({
+              status: 'error',
+              data: metadata,
+            });
+          }
+        }
+      }}
+      visible
+    >
+      {renderConfigCard()}
+    </Modal>
+  );
+};
+
+export default ManualInspection;

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

@@ -80,3 +80,18 @@
     transform: rotate(360deg);
   }
 }
+
+.alert {
+  height: 40px;
+  padding-left: 10px;
+  color: rgba(0, 0, 0, 0.55);
+  line-height: 40px;
+  background-color: #f6f6f6;
+}
+
+.advice {
+  .infoItem {
+    width: 100%;
+    margin: 10px;
+  }
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 651 - 352
src/pages/device/Instance/Detail/Diagnose/Status/index.tsx


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

@@ -7,79 +7,82 @@ type StatusProps = {
   info: null | ReactNode;
 };
 
-// export const initStatus = {
-//   product: {
-//     status: 'loading',
-//     text: '正在诊断中...',
-//     info: null,
-//   },
-//   config: {
-//     status: 'loading',
-//     text: '正在诊断中...',
-//     info: null,
-//   },
-//   device: {
-//     status: 'loading',
-//     text: '正在诊断中...',
-//     info: null,
-//   },
-//   deviceConfig: {
-//     status: 'loading',
-//     text: '正在诊断中...',
-//     info: null,
-//   },
-//   gateway: {
-//     status: 'loading',
-//     text: '正在诊断中...',
-//     info: null,
-//   },
-//   network: {
-//     status: 'loading',
-//     text: '正在诊断中...',
-//     info: null,
-//   },
-// };
+type ListProps = {
+  key: string;
+  name: string;
+  data: string;
+  desc: string;
+};
 
 export const DiagnoseStatusModel = model<{
   status: {
-    product: StatusProps;
-    config: StatusProps;
-    device: StatusProps;
-    deviceConfig: StatusProps;
-    gateway: StatusProps;
-    network: StatusProps;
+    config?: StatusProps;
+    network?: StatusProps;
+    product?: StatusProps;
+    device?: StatusProps;
+    productAuth?: StatusProps;
+    deviceAuth?: StatusProps;
+    deviceAccess?: StatusProps;
+    other?: StatusProps;
   };
+  list: ListProps[];
 }>({
   status: {
-    product: {
+    config: {
       status: 'loading',
       text: '正在诊断中...',
       info: null,
     },
-    config: {
+    network: {
       status: 'loading',
       text: '正在诊断中...',
       info: null,
     },
-    device: {
+    product: {
       status: 'loading',
       text: '正在诊断中...',
       info: null,
     },
-    deviceConfig: {
+    device: {
       status: 'loading',
       text: '正在诊断中...',
       info: null,
     },
-    gateway: {
+    deviceAccess: {
       status: 'loading',
       text: '正在诊断中...',
       info: null,
     },
-    network: {
+    other: {
       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: '诊断设备状态是否已启用,未启用的状态将导致连接失败。',
+    },
+  ],
 });

+ 19 - 0
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.less

@@ -0,0 +1,19 @@
+.map-desc {
+  padding: 16px;
+  border: 1px solid rgba(#000, 0.08);
+
+  h1 {
+    margin: 0 0 16px 0;
+    color: rgba(#000, 0.85);
+    font-size: 14px;
+  }
+
+  .text {
+    color: #666;
+    font-size: 14px;
+  }
+
+  .image {
+    margin: 24px 0;
+  }
+}

+ 69 - 71
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -1,7 +1,10 @@
 import React, { useContext, useEffect, useState } from 'react';
-import { Form, Input, message, Pagination, Select, Table } from 'antd';
+import { Badge, Col, Form, Input, message, Pagination, Row, Select, Table } from 'antd';
 import { service } from '@/pages/device/Instance';
 import _ from 'lodash';
+import './index.less';
+
+const defaultImage = require('/public/images/metadata-map.png');
 
 const EditableContext: any = React.createContext(null);
 
@@ -67,12 +70,13 @@ const EditableCell = ({
             (option?.children || '').toLowerCase()?.indexOf(input.toLowerCase()) >= 0
           }
         >
-          <Select.Option value={'use-origin-data'}>使用原始属性</Select.Option>
-          {list.map((item: any) => (
-            <Select.Option key={item?.id} value={item?.id}>
-              {item?.id}
-            </Select.Option>
-          ))}
+          <Select.Option value={record.metadataId}>使用原始属性</Select.Option>
+          {list.length > 0 &&
+            list.map((item: any) => (
+              <Select.Option key={item?.id} value={item?.id}>
+                {item?.name}({item?.id})
+              </Select.Option>
+            ))}
         </Select>
       </Form.Item>
     );
@@ -85,25 +89,17 @@ interface Props {
   type: 'device' | 'product';
 }
 
-const statusMap = new Map();
-statusMap.set('write', '写');
-statusMap.set('read', '读');
-statusMap.set('report', '上报');
-
 const EditableTable = (props: Props) => {
   const baseColumns = [
     {
-      title: '建模属性',
+      title: '物模型属性',
       dataIndex: 'name',
       render: (text: any, record: any) => <span>{`${record.name}(${record.id})`}</span>,
     },
     {
-      title: '映射属性',
+      title: '设备上报属性',
       dataIndex: 'metadataId',
       width: '30%',
-      render: (text: any, record: any) => (
-        <span>{`${record.metadataId}(${record.metadataName})`}</span>
-      ),
       editable: true,
     },
     {
@@ -112,23 +108,14 @@ const EditableTable = (props: Props) => {
       render: (text: any, record: any) => <span>{record.valueType?.type}</span>,
     },
     {
-      title: '读写类型',
-      dataIndex: 'expands',
-      render: (text: any, record: any) => (
+      title: '映射状态',
+      dataIndex: 'customMapping',
+      render: (text: any) => (
         <span>
-          {(record.expands?.type || [])
-            .map((i: string) => {
-              return statusMap.get(i);
-            })
-            .join(',')}
+          <Badge status={text ? 'success' : 'error'} text={text ? '已映射' : '未映射'} />
         </span>
       ),
     },
-    {
-      title: '映射状态',
-      dataIndex: 'customMapping',
-      render: (text: any) => <span>{String(text)}</span>,
-    },
   ];
   const metadata = JSON.parse(props?.data?.metadata || '{}');
   const [properties, setProperties] = useState<any[]>(metadata?.properties || []);
@@ -175,7 +162,6 @@ const EditableTable = (props: Props) => {
           };
         },
       );
-      console.log(list);
       setProperties([...list]);
       setDataSource({
         data: list.slice(
@@ -209,19 +195,14 @@ const EditableTable = (props: Props) => {
     const newData = [...dataSource.data];
     const index = newData.findIndex((item) => row.id === item.id);
     const item = newData[index];
-    // newData.splice(index, 1, { ...item, ...row });
-    // setDataSource({
-    //     ...dataSource,
-    //     data: [...newData],
-    // });
     if (item?.metadataId !== row?.metadataId) {
       const resp = await service[
         props.type === 'device' ? 'saveDeviceMetadata' : 'saveProductMetadata'
       ](props.data?.id, [
         {
           metadataType: 'property',
-          metadataId: row.metadataId === 'use-origin-data' ? row.metadataId : row.id,
-          originalId: row.metadataId === 'use-origin-data' ? row.id : '',
+          metadataId: row.metadataId === row.id ? row.metadataId : row.id,
+          originalId: row.metadataId === row.id ? row.id : '',
           others: {},
         },
       ]);
@@ -294,40 +275,57 @@ const EditableTable = (props: Props) => {
           }}
         />
       </div>
-      <Table
-        components={components}
-        rowClassName={() => 'editable-row'}
-        bordered
-        rowKey="id"
-        pagination={false}
-        dataSource={dataSource?.data || []}
-        columns={columns}
-      />
-      {dataSource.data.length > 0 && (
-        <div style={{ display: 'flex', marginTop: 20, justifyContent: 'flex-end' }}>
-          <Pagination
-            showSizeChanger
-            size="small"
-            className={'pro-table-card-pagination'}
-            total={dataSource?.total || 0}
-            current={dataSource?.pageIndex + 1}
-            onChange={(page, size) => {
-              handleSearch({
-                name: value,
-                pageIndex: page - 1,
-                pageSize: size,
-              });
-            }}
-            pageSizeOptions={[10, 20, 50, 100]}
-            pageSize={dataSource?.pageSize}
-            showTotal={(num) => {
-              const minSize = dataSource?.pageIndex * dataSource?.pageSize + 1;
-              const MaxSize = (dataSource?.pageIndex + 1) * dataSource?.pageSize;
-              return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
-            }}
+      <Row gutter={24}>
+        <Col span={16}>
+          <Table
+            components={components}
+            rowClassName={() => 'editable-row'}
+            bordered
+            rowKey="id"
+            pagination={false}
+            dataSource={dataSource?.data || []}
+            columns={columns}
           />
-        </div>
-      )}
+          {dataSource.data.length > 0 && (
+            <div style={{ display: 'flex', marginTop: 20, justifyContent: 'flex-end' }}>
+              <Pagination
+                showSizeChanger
+                size="small"
+                className={'pro-table-card-pagination'}
+                total={dataSource?.total || 0}
+                current={dataSource?.pageIndex + 1}
+                onChange={(page, size) => {
+                  handleSearch({
+                    name: value,
+                    pageIndex: page - 1,
+                    pageSize: size,
+                  });
+                }}
+                pageSizeOptions={[10, 20, 50, 100]}
+                pageSize={dataSource?.pageSize}
+                showTotal={(num) => {
+                  const minSize = dataSource?.pageIndex * dataSource?.pageSize + 1;
+                  const MaxSize = (dataSource?.pageIndex + 1) * dataSource?.pageSize;
+                  return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
+                }}
+              />
+            </div>
+          )}
+        </Col>
+        <Col span={8}>
+          <div className="map-desc">
+            <h1>功能说明</h1>
+            <div className="text">
+              该功能用于将<b>物模型属性</b>与<b>设备上报属性</b>
+              进行映射,当物模型属性与设备上报属性不一致时,可在当前页面直接修改映射关系,系统将以
+              <b>映射后</b>的<b>物模型属性</b>进行数据处理
+            </div>
+            <div className="image">
+              <img src={defaultImage} style={{ width: '100%' }} />
+            </div>
+          </div>
+        </Col>
+      </Row>
     </div>
   );
 };

+ 46 - 17
src/pages/device/Instance/Detail/index.tsx

@@ -1,10 +1,10 @@
-import {PageContainer} from '@ant-design/pro-layout';
-import {InstanceModel, service} from '@/pages/device/Instance';
-import {history, useParams} from 'umi';
-import {Badge, Card, Descriptions, Divider, message} from 'antd';
-import type {ReactNode} from 'react';
-import {useEffect, useState} from 'react';
-import {observer} from '@formily/react';
+import { PageContainer } from '@ant-design/pro-layout';
+import { InstanceModel, service } from '@/pages/device/Instance';
+import { history, useParams } from 'umi';
+import { Badge, Card, Descriptions, Divider, message, Tooltip } from 'antd';
+import type { ReactNode } from 'react';
+import { useEffect, useState } from 'react';
+import { observer } from '@formily/react';
 import Log from '@/pages/device/Instance/Detail/Log';
 // import Alarm from '@/pages/device/components/Alarm';
 import Info from '@/pages/device/Instance/Detail/Info';
@@ -13,15 +13,16 @@ import Running from '@/pages/device/Instance/Detail/Running';
 import ChildDevice from '@/pages/device/Instance/Detail/ChildDevice';
 import Diagnose from '@/pages/device/Instance/Detail/Diagnose';
 import MetadataMap from '@/pages/device/Instance/Detail/MetadataMap';
-import {useIntl} from '@@/plugin-locale/localeExports';
+import { useIntl } from '@@/plugin-locale/localeExports';
 import Metadata from '../../components/Metadata';
-import type {DeviceMetadata} from '@/pages/device/Product/typings';
+import type { DeviceMetadata } from '@/pages/device/Product/typings';
 import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
-import {Store} from 'jetlinks-store';
+import { Store } from 'jetlinks-store';
 import SystemConst from '@/utils/const';
-import {getMenuPathByCode, getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
+import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
-import {PermissionButton} from '@/components';
+import { PermissionButton } from '@/components';
+import { QuestionCircleOutlined } from '@ant-design/icons';
 
 export const deviceStatus = new Map();
 deviceStatus.set('online', <Badge status="success" text={'在线'} />);
@@ -63,10 +64,37 @@ const InstanceDetail = observer(() => {
     },
     {
       key: 'metadata',
-      tab: intl.formatMessage({
-        id: 'pages.device.instanceDetail.metadata',
-        defaultMessage: '物模型',
-      }),
+      tab: (
+        <>
+          {intl.formatMessage({
+            id: 'pages.device.instanceDetail.metadata',
+            defaultMessage: '物模型',
+          })}
+          <Tooltip
+            title={
+              <>
+                属性:
+                <br />
+                用于描述设备运行时具体信息和状态。
+                <br />
+                功能:
+                <br />
+                指设备可供外部调用的指令或方法。
+                <br />
+                事件:
+                <br />
+                设备运行时,主动上报给云端的信息。
+                <br />
+                标签:
+                <br />
+                统一为设备添加拓展字段,添加后将在设备信息页显示。
+              </>
+            }
+          >
+            <QuestionCircleOutlined style={{ marginLeft: 5 }} />
+          </Tooltip>
+        </>
+      ),
       component: (
         <Card>
           <Metadata
@@ -118,7 +146,8 @@ const InstanceDetail = observer(() => {
       component: <MetadataMap type="device" />,
     },
   ];
-  const [list, setList] = useState<{ key: string; tab: string; component: ReactNode }[]>(baseList);
+  const [list, setList] =
+    useState<{ key: string; tab: string | ReactNode; component: ReactNode }[]>(baseList);
 
   const getDetail = (id: string) => {
     service.detail(id).then((response) => {

+ 10 - 0
src/pages/device/Instance/service.ts

@@ -174,6 +174,11 @@ class Service extends BaseService<DeviceInstance> {
     request(`/${SystemConst.API_BASE}/device/product/${id}/config-metadata`, {
       method: 'GET',
     });
+  // 设备配置
+  public queryDeviceConfig = (id: string) =>
+    request(`/${SystemConst.API_BASE}/device-instance/${id}/config-metadata`, {
+      method: 'GET',
+    });
   // 设备接入网关状态
   public queryGatewayState = (id: string) =>
     request(`/${SystemConst.API_BASE}/gateway/device/${id}/detail`, {
@@ -238,6 +243,11 @@ class Service extends BaseService<DeviceInstance> {
     request(`/${SystemConst.API_BASE}/device/metadata/mapping/product/${productId}`, {
       method: 'GET',
     });
+  //
+  public queryProcotolDetail = (type: string, transport: string) =>
+    request(`/${SystemConst.API_BASE}/protocol/${type}/transport/${transport}`, {
+      method: 'GET',
+    });
 
   //接入方式
   public queryGatewayList = () => request(`/${SystemConst.API_BASE}/gateway/device/providers`);

+ 30 - 54
src/pages/device/Product/Detail/Access/index.tsx

@@ -24,8 +24,6 @@ const Access = () => {
   const [visible, setVisible] = useState<boolean>(true);
   const [config, setConfig] = useState<any>();
   const [access, setAccess] = useState<any>();
-  const [providers, setProviders] = useState<any[]>([]);
-  const [networkList, setNetworkList] = useState<any[]>([]);
   const { permission } = usePermissions('link/AccessConfig');
 
   const MetworkTypeMapping = new Map();
@@ -43,31 +41,29 @@ const Access = () => {
 
   const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
 
-  const queryNetworkList = (id: string) => {
-    service.getNetworkList(MetworkTypeMapping.get(id)).then((resp) => {
-      if (resp.status === 200) {
-        setNetworkList(resp.result);
-      }
-    });
+  const queryAccessDetail = (id: string) => {
+    service
+      .queryGatewayDetail({
+        terms: [
+          {
+            column: 'id',
+            value: id,
+          },
+        ],
+      })
+      .then((resp) => {
+        setAccess(resp.result.data[0]);
+      });
   };
 
-  const queryProviders = () => {
-    service.getProviders().then((resp) => {
+  const getConfigDetail = (messageProtocol: string, transportProtocol: string) => {
+    service.getConfigView(messageProtocol, transportProtocol).then((resp) => {
       if (resp.status === 200) {
-        setProviders(resp.result);
+        setConfig(resp.result);
       }
     });
   };
 
-  const queryAccess = (id: string) => {
-    service.queryList({ pageSize: 1000 }).then((resp) => {
-      const dt = resp.result?.data.find((i: any) => i.id === id);
-      setAccess(dt);
-      if (dt) {
-        queryNetworkList(dt?.provider);
-      }
-    });
-  };
   const columnsMQTT: any[] = [
     {
       title: '分组',
@@ -192,14 +188,6 @@ const Access = () => {
     },
   ];
 
-  const getDetail = (messageProtocol: string, transportProtocol: string) => {
-    service.getConfigView(messageProtocol, transportProtocol).then((resp) => {
-      if (resp.status === 200) {
-        setConfig(resp.result);
-      }
-    });
-  };
-
   const id = productModel.current?.id;
 
   useEffect(() => {
@@ -210,14 +198,13 @@ const Access = () => {
           setMetadata(resp.result);
         });
     }
-    queryProviders();
     setVisible(!!productModel.current?.accessId);
     if (productModel.current?.accessId) {
-      getDetail(
+      queryAccessDetail(productModel.current?.accessId);
+      getConfigDetail(
         productModel.current?.messageProtocol || '',
         productModel.current?.transportProtocol || '',
       );
-      queryAccess(productModel.current?.accessId);
     }
   }, [productModel.current]);
 
@@ -393,16 +380,8 @@ const Access = () => {
                     </span>
                   }
                 />
-                <div className={styles.context}>
-                  {providers.find((i) => i.id === access?.provider)?.name || '--'}
-                </div>
-                <div className={styles.context}>
-                  {providers.find((i) => i.id === access?.provider)?.description && (
-                    <span>
-                      {providers.find((i) => i.id === access?.provider)?.description || '--'}
-                    </span>
-                  )}
-                </div>
+                <div className={styles.context}>{access?.name || '--'}</div>
+                <div className={styles.context}>{access?.description || '--'}</div>
               </div>
 
               <div className={styles.item}>
@@ -414,21 +393,18 @@ const Access = () => {
                   </div>
                 )}
               </div>
-
               <div className={styles.item}>
                 <TitleComponent data={'连接信息'} />
-                {(networkList.find((i) => i.id === access?.channelId)?.addresses || []).length > 0
-                  ? (networkList.find((i) => i.id === access?.channelId)?.addresses || []).map(
-                      (item: any) => (
-                        <div key={item.address}>
-                          <Badge
-                            color={item.health === -1 ? 'red' : 'green'}
-                            text={item.address}
-                            style={{ marginLeft: '20px' }}
-                          />
-                        </div>
-                      ),
-                    )
+                {(access?.channelInfo?.addresses || []).length > 0
+                  ? (access?.channelInfo?.addresses || []).map((item: any) => (
+                      <div key={item.address}>
+                        <Badge
+                          color={item.health === -1 ? 'red' : 'green'}
+                          text={item.address}
+                          style={{ marginLeft: '20px' }}
+                        />
+                      </div>
+                    ))
                   : '暂无连接信息'}
               </div>
 

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

@@ -34,7 +34,6 @@ const NormalUpload = (props: any) => {
     const _data = updateMetadata('properties', _metadata.properties, target) as ProductItem;
     // const resp = await service.update(_product);
     const resp = await asyncUpdateMedata(props.type, _data);
-    console.log(resp);
     if (resp.status === 200) {
       message.success('操作成功');
       // 刷新物模型

+ 32 - 4
src/pages/device/Product/Detail/index.tsx

@@ -16,6 +16,7 @@ import encodeQuery from '@/utils/encodeQuery';
 import MetadataMap from '@/pages/device/Instance/Detail/MetadataMap';
 import SystemConst from '@/utils/const';
 import { PermissionButton } from '@/components';
+import { QuestionCircleOutlined } from '@ant-design/icons';
 
 export const ModelEnum = {
   base: 'base',
@@ -158,10 +159,37 @@ const ProductDetail = observer(() => {
     },
     {
       key: 'metadata',
-      tab: intl.formatMessage({
-        id: 'pages.device.productDetail.metadata',
-        defaultMessage: '物模型',
-      }),
+      tab: (
+        <>
+          {intl.formatMessage({
+            id: 'pages.device.instanceDetail.metadata',
+            defaultMessage: '物模型',
+          })}
+          <Tooltip
+            title={
+              <>
+                属性:
+                <br />
+                用于描述设备运行时具体信息和状态。
+                <br />
+                功能:
+                <br />
+                指设备可供外部调用的指令或方法。
+                <br />
+                事件:
+                <br />
+                设备运行时,主动上报给云端的信息。
+                <br />
+                标签:
+                <br />
+                统一为设备添加拓展字段,添加后将在设备信息页显示。
+              </>
+            }
+          >
+            <QuestionCircleOutlined style={{ marginLeft: 5 }} />
+          </Tooltip>
+        </>
+      ),
       component: <Metadata type="product" />,
     },
     {

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

@@ -582,7 +582,7 @@ const Edit = observer((props: Props) => {
             },
           },
           type: {
-            title: MetadataModel.type === 'tags' ? '标签类型' : '属性类型',
+            title: MetadataModel.type === 'tags' ? '标签类型' : '读写类型',
             required: true,
             'x-decorator': 'FormItem',
             'x-component': 'Select',

+ 18 - 9
src/pages/device/components/Metadata/Base/columns.ts

@@ -1,5 +1,6 @@
 import type { ProColumns } from '@jetlinks/pro-table';
 import type { MetadataItem } from '@/pages/device/Product/typings';
+import { Tag } from 'antd';
 
 const BaseColumns: ProColumns<MetadataItem>[] = [
   // {
@@ -22,6 +23,12 @@ const BaseColumns: ProColumns<MetadataItem>[] = [
   },
 ];
 
+const type = {
+  read: '读',
+  write: '写',
+  report: '上报',
+};
+
 const EventColumns: ProColumns<MetadataItem>[] = BaseColumns.concat([
   {
     title: '事件级别',
@@ -43,11 +50,11 @@ const FunctionColumns: ProColumns<MetadataItem>[] = BaseColumns.concat([
     dataIndex: 'async',
     render: (text) => (text ? '是' : '否'),
   },
-  {
-    title: '读写类型',
-    dataIndex: 'expands.readOnly',
-    render: (text) => (text ? '是' : '否'),
-  },
+  // {
+  //   title: '读写类型',
+  //   dataIndex: 'expands',
+  //   render: (text: any) => (text?.type || []).map((item: string | number) => <Tag>{type[item]}</Tag>),
+  // },
 ]);
 
 const PropertyColumns: ProColumns<MetadataItem>[] = BaseColumns.concat([
@@ -58,8 +65,9 @@ const PropertyColumns: ProColumns<MetadataItem>[] = BaseColumns.concat([
   },
   {
     title: '读写类型',
-    dataIndex: 'expands.readOnly',
-    render: (text) => (text === 'true' || text === true ? '是' : '否'),
+    dataIndex: 'expands',
+    render: (text: any) =>
+      (text?.type || []).map((item: string | number) => <Tag>{type[item]}</Tag>),
   },
 ]);
 
@@ -71,8 +79,9 @@ const TagColumns: ProColumns<MetadataItem>[] = BaseColumns.concat([
   },
   {
     title: '读写类型',
-    dataIndex: 'expands.readOnly',
-    render: (text) => (text === 'true' || text === true ? '是' : '否'),
+    dataIndex: 'expands',
+    render: (text: any) =>
+      (text?.type || []).map((item: string | number) => <Tag>{type[item]}</Tag>),
   },
 ]);
 

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

@@ -1,24 +1,24 @@
-import type {ProColumns} from '@jetlinks/pro-table';
+import type { ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import {useCallback, useEffect, useState} from 'react';
-import {useParams} from 'umi';
+import { useCallback, useEffect, useState } from 'react';
+import { useParams } from 'umi';
 import DB from '@/db';
-import type {MetadataItem, MetadataType} from '@/pages/device/Product/typings';
+import type { MetadataItem, MetadataType } from '@/pages/device/Product/typings';
 import MetadataMapping from './columns';
-import {message} from 'antd';
-import {DeleteOutlined, EditOutlined, ImportOutlined, PlusOutlined} from '@ant-design/icons';
+import { message } from 'antd';
+import { DeleteOutlined, EditOutlined, ImportOutlined, PlusOutlined } from '@ant-design/icons';
 import Edit from './Edit';
-import {observer} from '@formily/react';
+import { observer } from '@formily/react';
 import MetadataModel from './model';
-import {Store} from 'jetlinks-store';
+import { Store } from 'jetlinks-store';
 import SystemConst from '@/utils/const';
-import {useIntl} from '@@/plugin-locale/localeExports';
+import { useIntl } from '@@/plugin-locale/localeExports';
 import PropertyImport from '@/pages/device/Product/Detail/PropertyImport';
-import {productModel} from '@/pages/device/Product';
-import {InstanceModel} from '@/pages/device/Instance';
-import {asyncUpdateMedata, removeMetadata} from '../metadata';
-import type {permissionType} from '@/hooks/permission';
-import {PermissionButton} from '@/components';
+import { productModel } from '@/pages/device/Product';
+import { InstanceModel } from '@/pages/device/Instance';
+import { asyncUpdateMedata, removeMetadata } from '../metadata';
+import type { permissionType } from '@/hooks/permission';
+import { PermissionButton } from '@/components';
 
 interface Props {
   type: MetadataType;

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

@@ -36,6 +36,7 @@ const Import = (props: Props) => {
   const loadData = () => async (field: Field) => {
     field.loading = true;
     const product = (await service.queryNoPagingPost({
+      paging: false,
       terms: [{ column: 'id$not', value: param.id }],
     })) as any;
     field.dataSource = product.result.map((item: any) => ({

+ 8 - 9
src/pages/device/components/Metadata/index.tsx

@@ -1,16 +1,16 @@
-import {observer} from '@formily/react';
-import {Space, Tabs} from 'antd';
+import { observer } from '@formily/react';
+import { Space, Tabs } from 'antd';
 import BaseMetadata from './Base';
-import {useIntl} from '@@/plugin-locale/localeExports';
+import { useIntl } from '@@/plugin-locale/localeExports';
 import Import from './Import';
-import type {ReactNode} from 'react';
-import {useState} from 'react';
+import type { ReactNode } from 'react';
+import { useState } from 'react';
 import Cat from './Cat';
 import Service from '@/pages/device/components/Metadata/service';
-import {InfoCircleOutlined} from '@ant-design/icons';
+import { InfoCircleOutlined } from '@ant-design/icons';
 import styles from './index.less';
-import {InstanceModel} from '@/pages/device/Instance';
-import {PermissionButton} from '@/components';
+import { InstanceModel } from '@/pages/device/Instance';
+import { PermissionButton } from '@/components';
 
 interface Props {
   tabAction?: ReactNode;
@@ -26,7 +26,6 @@ const Metadata = observer((props: Props) => {
   const { permission } = PermissionButton.usePermission(
     props.type === 'device' ? 'device/Instance' : 'device/Product',
   );
-  console.log(InstanceModel.detail, 'test');
   return (
     <div style={{ position: 'relative' }}>
       <div className={styles.tips}>

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

@@ -510,7 +510,9 @@ const Access = (props: Props) => {
                   <Button
                     type="primary"
                     disabled={
-                      !props.data?.id ? getButtonPermission('link/AccessConfig', ['update']) : false
+                      !!props.data.id
+                        ? getButtonPermission('link/AccessConfig', ['update'])
+                        : getButtonPermission('link/AccessConfig', ['add'])
                     }
                     onClick={async () => {
                       try {

+ 3 - 1
src/pages/link/AccessConfig/Detail/Media/index.tsx

@@ -480,7 +480,9 @@ const Media = (props: Props) => {
               <Button
                 type="primary"
                 disabled={
-                  !!params.get('id') ? getButtonPermission('link/AccessConfig', ['update']) : false
+                  !!params.get('id')
+                    ? getButtonPermission('link/AccessConfig', ['update'])
+                    : getButtonPermission('link/AccessConfig', ['add'])
                 }
                 onClick={async () => {
                   const values = await form.validateFields();

+ 1 - 1
src/pages/link/AccessConfig/index.tsx

@@ -53,7 +53,7 @@ const AccessConfig = () => {
   const handleSearch = (params: any) => {
     setParam(params);
     service
-      .queryList({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+      .queryGatewayDetail({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
       .then((resp) => {
         if (resp.status === 200) {
           setDataSource(resp.result);

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

@@ -4,6 +4,11 @@ import SystemConst from '@/utils/const';
 import type { AccessItem } from './typings';
 
 class Service extends BaseService<AccessItem> {
+  public queryGatewayDetail = (data: any) =>
+    request(`/${SystemConst.API_BASE}/gateway/device/detail/_query`, {
+      method: 'POST',
+      data,
+    });
   public queryList = (data: any) =>
     request(`/${SystemConst.API_BASE}/gateway/device/detail/_query`, {
       method: 'POST',

+ 49 - 101
src/pages/link/Type/Detail/index.tsx

@@ -227,6 +227,9 @@ const Save = observer(() => {
           labelAlign: 'left',
           layout: 'vertical',
         },
+        'x-component-props': {
+          placeholder: '请输入节点名称',
+        },
         'x-reactions': [
           {
             dependencies: ['shareCluster'],
@@ -243,6 +246,9 @@ const Save = observer(() => {
         title: '本地地址',
         'x-decorator': 'FormItem',
         'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请选择本地地址',
+        },
         'x-decorator-props': {
           gridSpan: 1,
           labelAlign: 'left',
@@ -274,6 +280,9 @@ const Save = observer(() => {
         type: 'number',
         'x-decorator': 'FormItem',
         'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请输入本地端口',
+        },
         'x-reactions': {
           dependencies: ['type'],
           fulfill: {
@@ -307,6 +316,9 @@ const Save = observer(() => {
         'x-decorator': 'FormItem',
         'x-component': 'Input',
         'x-validator': ['ipv4'],
+        'x-component-props': {
+          placeholder: '请输入公网地址',
+        },
         'x-reactions': {
           dependencies: ['type'],
           fulfill: {
@@ -326,6 +338,9 @@ const Save = observer(() => {
           layout: 'vertical',
           labelAlign: 'left',
         },
+        'x-component-props': {
+          placeholder: '请输入公网端口',
+        },
         required: true,
         'x-decorator': 'FormItem',
         'x-component': 'NumberPicker',
@@ -369,6 +384,9 @@ const Save = observer(() => {
               layout: 'vertical',
               labelAlign: 'left',
             },
+            'x-component-props': {
+              placeholder: '请输入远程地址',
+            },
             required: true,
             'x-validator': ['ipv4'],
             'x-decorator': 'FormItem',
@@ -381,6 +399,9 @@ const Save = observer(() => {
               layout: 'vertical',
               labelAlign: 'left',
             },
+            'x-component-props': {
+              placeholder: '请输入远程端口',
+            },
             required: true,
             'x-decorator': 'FormItem',
             'x-component': 'NumberPicker',
@@ -392,6 +413,9 @@ const Save = observer(() => {
               layout: 'vertical',
               labelAlign: 'left',
             },
+            'x-component-props': {
+              placeholder: '请输入ClientId',
+            },
             required: true,
             'x-decorator': 'FormItem',
             'x-component': 'Input',
@@ -403,12 +427,18 @@ const Save = observer(() => {
               layout: 'vertical',
               labelAlign: 'left',
             },
+            'x-component-props': {
+              placeholder: '请输入用户名',
+            },
             required: true,
             'x-decorator': 'FormItem',
             'x-component': 'Input',
           },
           password: {
             title: '密码',
+            'x-component-props': {
+              placeholder: '请输入密码',
+            },
             'x-decorator-props': {
               gridSpan: 1,
               layout: 'vertical',
@@ -426,12 +456,18 @@ const Save = observer(() => {
               layout: 'vertical',
               labelAlign: 'left',
             },
+            'x-component-props': {
+              placeholder: '请输入最大消息长度',
+            },
             required: true,
             'x-decorator': 'FormItem',
             'x-component': 'Input',
           },
           topicPrefix: {
             title: '订阅前缀',
+            'x-component-props': {
+              placeholder: '请输入订阅前缀',
+            },
             'x-decorator-props': {
               gridSpan: 1,
               tooltip: '当连接的服务为EMQ时,可能需要使用共享的订阅前缀,如:$queue或$share',
@@ -453,6 +489,9 @@ const Save = observer(() => {
           tooltip: '对外提供访问的地址,内网环境是填写服务器的内网IP地址',
           layout: 'vertical',
         },
+        'x-component-props': {
+          placeholder: '请输入最大消息长度',
+        },
         'x-reactions': {
           dependencies: ['type'],
           fulfill: {
@@ -463,25 +502,6 @@ const Save = observer(() => {
           },
         },
       },
-      // topicPrefix: {
-      //   title: '订阅前缀',
-      //   'x-decorator': 'FormItem',
-      //   'x-component': 'Input',
-      //   'x-decorator-props': {
-      //     gridSpan: 1,
-      //     labelAlign: 'left',
-      //     layout: 'vertical',
-      //   },
-      //   'x-reactions': {
-      //     dependencies: ['type'],
-      //     fulfill: {
-      //       state: {
-      //         // visible: '{{$deps[0]==="UDP"}}',
-      //         visible: '{{["MQTT_CLIENT"].includes($deps[0])}}',
-      //       },
-      //     },
-      //   },
-      // },
       parserType: {
         // TCP
         required: true,
@@ -495,6 +515,9 @@ const Save = observer(() => {
         'x-visible': false,
         'x-decorator': 'FormItem',
         'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请选择粘拆包规则',
+        },
         enum: [
           { value: 'DIRECT', label: '不处理' },
           { value: 'delimited', label: '分隔符' },
@@ -533,6 +556,9 @@ const Save = observer(() => {
             'x-decorator-props': {
               gridSpan: 1,
             },
+            'x-component-props': {
+              placeholder: '请输入名称',
+            },
             'x-validator': [
               {
                 max: 64,
@@ -551,6 +577,9 @@ const Save = observer(() => {
             'x-decorator-props': {
               gridSpan: 1,
             },
+            'x-component-props': {
+              placeholder: '请选择类型',
+            },
             'x-validator': [
               {
                 required: true,
@@ -655,88 +684,6 @@ const Save = observer(() => {
               },
             },
           },
-
-          // parserType: {
-          //   // TCP
-          //   title: '粘拆包规则',
-          //   'x-decorator-props': {
-          //     gridSpan: 3,
-          //     tooltip: '',
-          //   },
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'Select',
-          //   enum: [
-          //     { label: 'DIRECT', value: '不处理' },
-          //     { label: 'delimited', value: '分隔符' },
-          //     { label: 'script', value: '自定义脚本' },
-          //     { label: 'fixed_length', value: '固定长度' },
-          //   ],
-          // },
-          // // MQTT_C
-          // remoteAddress: {
-          //   title: '远程地址',
-          //   'x-validator': ['ipv4'],
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'Input',
-          // },
-          // remotePort: {
-          //   title: '远程端口',
-          //   type: 'number',
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'NumberPicker',
-          //   'x-validator': [
-          //     {
-          //       max: 65535,
-          //       message: '请输入1-65535之间的整整数',
-          //     },
-          //     {
-          //       min: 1,
-          //       message: '请输入1-65535之间的整整数',
-          //     },
-          //
-          //   ],
-          // },
-          // client: {
-          //   title: 'client',
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'Input',
-          // },
-          // username: {
-          //   title: '用户名',
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'Input',
-          // },
-          // password: {
-          //   title: '密码',
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'Password',
-          // },
-          // // MQTT-S
-          // maxMessageSize: {
-          //   title: '最大消息长度',
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'NumberPicker',
-          //   'x-decorator-props': {
-          //     tooltip: '单次收发消息的最大长度,单位:字节。设置过大可能会影响性能',
-          //   },
-          // },
-          // topicPrefix: {
-          //   title: '订阅前缀',
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'Input',
-          // },
-          // // MQTT_C end
-          // enableDtls: {
-          //   title: '开启DTLS',
-          //   'x-decorator': 'FormItem',
-          //   'x-component': 'Radio.Group',
-          //   required: true,
-          //   default: false,
-          //   enum: [
-          //     { label: '是', value: true },
-          //     { label: '否', value: false },
-          //   ],
-          // },
           description: {
             title: '说明',
             'x-component': 'Input.TextArea',
@@ -745,6 +692,7 @@ const Save = observer(() => {
               gridSpan: 3,
             },
             'x-component-props': {
+              placeholder: '请输入说明',
               showCount: true,
               maxLength: 200,
               rows: 5,

+ 3 - 5
src/pages/media/Cascade/Channel/BindChannel/index.tsx

@@ -14,8 +14,6 @@ interface Props {
 
 const BindChannel = (props: Props) => {
   const [param, setParam] = useState<any>({
-    pageIndex: 0,
-    pageSize: 10,
     terms: [
       {
         column: 'id',
@@ -124,9 +122,9 @@ const BindChannel = (props: Props) => {
         columns={columns}
         search={false}
         headerTitle={'通道列表'}
-        request={async (params) =>
-          service.queryChannel({ ...params, sorts: [{ name: 'name', order: 'desc' }] })
-        }
+        request={async (params) => {
+          return service.queryChannel({ ...params, sorts: [{ name: 'name', order: 'desc' }] });
+        }}
         rowKey="id"
         rowSelection={{
           selectedRowKeys: selectedRowKey,

+ 4 - 4
src/pages/media/Cascade/Channel/index.tsx

@@ -192,12 +192,12 @@ const Channel = () => {
         columns={columns}
         search={false}
         headerTitle={'通道列表'}
-        request={async (params) =>
-          service.queryBindChannel(id, {
+        request={async (params) => {
+          return service.queryBindChannel(id, {
             ...params,
             sorts: [{ name: 'createTime', order: 'desc' }],
-          })
-        }
+          });
+        }}
         rowKey="channelId"
         rowSelection={{
           selectedRowKeys: selectedRowKey,

+ 3 - 0
src/pages/notice/Config/Detail/index.tsx

@@ -124,6 +124,9 @@ const Detail = observer(() => {
         required: true,
         'x-component': 'Input',
         'x-decorator': 'FormItem',
+        'x-component-props': {
+          placeholder: '请输入名称',
+        },
       },
       type: {
         title: '分类',

+ 184 - 0
src/pages/notice/Config/SyncUser/index.tsx

@@ -0,0 +1,184 @@
+import { Button, Col, Input, Modal, Row, Tree } from 'antd';
+import { observer } from '@formily/react';
+import { service, state } from '..';
+import ProTable, { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useEffect, useRef, useState } from 'react';
+import { history, useLocation } from 'umi';
+import { PermissionButton } from '@/components';
+import { DisconnectOutlined, EditOutlined } from '@ant-design/icons';
+
+const SyncUser = observer(() => {
+  const [dept, setDept] = useState<string>();
+  const location = useLocation<{ id: string }>();
+  const id = (location as any).query?.id;
+
+  const idMap = {
+    dingTalk: '钉钉',
+    weixin: '微信',
+  };
+  const columns: ProColumns<any>[] = [
+    {
+      dataIndex: 'id',
+      title: `${idMap[id]}ID`,
+    },
+    {
+      dataIndex: 'name',
+      title: `${idMap[id]}用户名`,
+    },
+    {
+      dataIndex: 'action',
+      title: '绑定状态',
+      render: () => [
+        <PermissionButton
+          tooltip={{
+            title: '绑定用户',
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          tooltip={{
+            title: '解绑用户',
+          }}
+        >
+          <DisconnectOutlined />
+        </PermissionButton>,
+      ],
+    },
+  ];
+  const actionRef = useRef<ActionType>();
+
+  const [treeData, setTreeData] = useState([]);
+
+  /**
+   * 获取部门列表
+   */
+  const getDepartment = async () => {
+    if (state.current?.id) {
+      if (id === 'dingTalk') {
+        service.syncUser.dingTalkDept(state.current?.id).then((resp) => {
+          if (resp.status === 200) {
+            setTreeData(resp.result);
+            setDept(resp.result[0].id);
+            console.log(resp.result[0].id, 'id');
+          }
+        });
+      } else if (id === 'weixin') {
+        service.syncUser.wechatDept(state.current?.id).then((resp) => {
+          if (resp.status === 200) {
+            setTreeData(resp.result);
+            setDept(resp.result[0].id);
+            console.log(resp.result[0].id, 'id~~');
+          }
+        });
+      }
+    }
+  };
+
+  useEffect(() => {
+    if (!state.current?.id) {
+      history.goBack();
+    }
+    getDepartment();
+  }, [id]);
+
+  // const updateTreeData = (list: any[], key: React.Key, children: any[]): any[] => {
+  //   return list.map((node) => {
+  //     if (node.id === key) {
+  //       return {
+  //         ...node,
+  //         children: node.children ? [...node.children, ...children] : children,
+  //       };
+  //     }
+  //
+  //     if (node.children) {
+  //       return {
+  //         ...node,
+  //         children: updateTreeData(node.children, key, children),
+  //       };
+  //     }
+  //     return node;
+  //   });
+  // };
+
+  // const getParentKey = (key: any, tree: string | any[]): any => {
+  //   let parentKey;
+  //   for (let i = 0; i < tree.length; i++) {
+  //     const node = tree[i];
+  //     if (node.children) {
+  //       if (node.children.some((item: { key: any; }) => item.key === key)) {
+  //         parentKey = node.key;
+  //       } else if (getParentKey(key, node.children)) {
+  //         parentKey = getParentKey(key, node.children);
+  //       }
+  //     }
+  //   }
+  //   return parentKey;
+  // };
+
+  return (
+    <Modal
+      title="同步用户"
+      bodyStyle={{ height: '600px', overflowY: 'auto' }}
+      visible={true}
+      onCancel={() => (state.syncUser = false)}
+      width="80vw"
+    >
+      <Row>
+        <Col span={4}>
+          <div style={{ borderRight: 'lightgray 1px solid', padding: '2px', height: '600px' }}>
+            <Input.Search style={{ marginBottom: 8 }} placeholder="请输入部门名称" />
+            <Tree
+              fieldNames={{
+                title: 'name',
+                key: 'id',
+              }}
+              onSelect={(key) => {
+                setDept(key[0] as string);
+              }}
+              treeData={treeData}
+              // loadData={onLoadData}
+            />
+          </div>
+        </Col>
+        <Col span={20}>
+          {dept && (
+            <ProTable
+              rowKey="id"
+              actionRef={actionRef}
+              search={false}
+              columns={columns}
+              params={{ dept: dept }}
+              request={async (params) =>
+                service.syncUser
+                  .getDeptUser(
+                    {
+                      dingTalk: 'dingtalk',
+                      weixin: 'wechat',
+                    }[id],
+                    state.current?.id || '',
+                    params.dept || '',
+                  )
+                  .then((resp) => {
+                    return {
+                      code: resp.message,
+                      result: {
+                        data: resp.result || [],
+                        pageIndex: 0,
+                        pageSize: 0,
+                        total: 0,
+                      },
+                      status: resp.status,
+                    };
+                  })
+              }
+              headerTitle={<Button>保存</Button>}
+            />
+          )}
+        </Col>
+      </Row>
+    </Modal>
+  );
+});
+
+export default SyncUser;

+ 96 - 53
src/pages/notice/Config/index.tsx

@@ -8,9 +8,10 @@ import {
   EditOutlined,
   EyeOutlined,
   PlusOutlined,
+  TeamOutlined,
   UnorderedListOutlined,
 } from '@ant-design/icons';
-import { message, Popconfirm, Space, Tooltip, Upload } from 'antd';
+import { message, Space, Upload } from 'antd';
 import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { downloadObject } from '@/utils/util';
@@ -27,6 +28,7 @@ import Debug from '@/pages/notice/Config/Debug';
 import Log from '@/pages/notice/Config/Log';
 import { typeList } from '@/components/ProTableCard/CardItems/noticeTemplate';
 import usePermissions from '@/hooks/permission';
+import SyncUser from '@/pages/notice/Config/SyncUser';
 
 export const service = new Service('notifier/config');
 
@@ -34,9 +36,11 @@ export const state = model<{
   current?: ConfigItem;
   debug?: boolean;
   log?: boolean;
+  syncUser: boolean;
 }>({
   debug: false,
   log: false,
+  syncUser: false,
 });
 const Config = observer(() => {
   const intl = useIntl();
@@ -69,24 +73,44 @@ const Config = observer(() => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a
+        (id === 'dingTalk' || id === 'weixin') && (
+          <PermissionButton
+            tooltip={{
+              title: '同步用户',
+            }}
+            style={{ padding: 0 }}
+            type="link"
+            onClick={() => {
+              state.syncUser = true;
+              state.current = record;
+            }}
+          >
+            <TeamOutlined />
+          </PermissionButton>
+        ),
+        <PermissionButton
           key="edit"
+          type="link"
+          style={{ padding: 0 }}
+          isPermission={configPermission.update}
           onClick={async () => {
             // setLoading(true);
             state.current = record;
             history.push(getMenuPathByParams(MENUS_CODE['notice/Config/Detail'], id));
           }}
-        >
-          <Tooltip
-            title={intl.formatMessage({
+          tooltip={{
+            title: intl.formatMessage({
               id: 'pages.data.option.edit',
               defaultMessage: '编辑',
-            })}
-          >
-            <EditOutlined />
-          </Tooltip>
-        </a>,
-        <a
+            }),
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          type="link"
+          style={{ padding: 0 }}
+          isPermission={configPermission.export}
           onClick={() =>
             downloadObject(
               record,
@@ -94,50 +118,55 @@ const Config = observer(() => {
             )
           }
           key="download"
-        >
-          <Tooltip
-            title={intl.formatMessage({
+          tooltip={{
+            title: intl.formatMessage({
               id: 'pages.data.option.download',
               defaultMessage: '下载配置',
-            })}
-          >
-            <ArrowDownOutlined />
-          </Tooltip>
-        </a>,
-        <a
+            }),
+          }}
+        >
+          <ArrowDownOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          type="link"
+          style={{ padding: 0 }}
+          isPermission={configPermission.debug}
           key="debug"
           onClick={() => {
             state.debug = true;
             state.current = record;
           }}
-        >
-          <Tooltip
-            title={intl.formatMessage({
+          tooltip={{
+            title: intl.formatMessage({
               id: 'pages.notice.option.debug',
               defaultMessage: '调试',
-            })}
-          >
-            <BugOutlined />
-          </Tooltip>
-        </a>,
-        <a
+            }),
+          }}
+        >
+          <BugOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          type="link"
+          style={{ padding: 0 }}
+          isPermission={configPermission.log}
           key="record"
           onClick={() => {
             state.log = true;
           }}
-        >
-          <Tooltip
-            title={intl.formatMessage({
+          tooltip={{
+            title: intl.formatMessage({
               id: 'pages.data.option.record',
               defaultMessage: '通知记录',
-            })}
-          >
-            <BarsOutlined />
-          </Tooltip>
-        </a>,
-        <a key="remove">
-          <Popconfirm
-            onConfirm={async () => {
+            }),
+          }}
+        >
+          <BarsOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          style={{ padding: 0 }}
+          type="link"
+          popConfirm={{
+            onConfirm: async () => {
               await service.remove(record.id);
               message.success(
                 intl.formatMessage({
@@ -146,19 +175,19 @@ const Config = observer(() => {
                 }),
               );
               actionRef.current?.reload();
-            }}
-            title="确认删除?"
-          >
-            <Tooltip
-              title={intl.formatMessage({
-                id: 'pages.data.option.remove',
-                defaultMessage: '删除',
-              })}
-            >
-              <DeleteOutlined />
-            </Tooltip>
-          </Popconfirm>
-        </a>,
+            },
+            title: '确认删除',
+          }}
+          tooltip={{
+            title: intl.formatMessage({
+              id: 'pages.data.option.remove',
+              defaultMessage: '删除',
+            }),
+          }}
+          key="remove"
+        >
+          <DeleteOutlined />
+        </PermissionButton>,
       ],
     },
   ];
@@ -267,6 +296,19 @@ const Config = observer(() => {
               </div>
             }
             actions={[
+              (id === 'dingTalk' || id === 'weixin') && (
+                <PermissionButton
+                  key="syncUser"
+                  isPermission={true}
+                  onClick={() => {
+                    state.syncUser = true;
+                    state.current = record;
+                  }}
+                >
+                  <TeamOutlined />
+                  同步用户
+                </PermissionButton>
+              ),
               <PermissionButton
                 isPermission={configPermission.update}
                 type={'link'}
@@ -338,6 +380,7 @@ const Config = observer(() => {
       />
       <Debug />
       {state.log && <Log />}
+      {state.syncUser && <SyncUser />}
     </PageContainer>
   );
 });

+ 38 - 0
src/pages/notice/Config/service.ts

@@ -33,6 +33,44 @@ class Service extends BaseService<ConfigItem> {
       method: 'POST',
       data,
     });
+
+  syncUser = {
+    dingTalkDept: (configId: string) =>
+      request(`${SystemConst.API_BASE}/notifier/dingtalk/corp/${configId}/departments/tree`),
+    dingTalkUser: (configId: string, departmentId: string) =>
+      request(`${SystemConst.API_BASE}/notifier/dingtalk/corp/${configId}/${departmentId}/users`),
+    wechatDept: (configId: string) =>
+      request(`${SystemConst.API_BASE}/notifier/wechat/corp/${configId}/departments`),
+    getDeptUser: (type: 'wechat' | 'dingTalk', configId: string, departmentId: string) =>
+      request(`${SystemConst.API_BASE}/notifier/${type}/corp/${configId}/${departmentId}/users`, {
+        method: 'GET',
+      }),
+    wechatUser: (configId: string, departmentId: string) =>
+      request(`${SystemConst.API_BASE}/notifier/wechat/corp/${configId}/${departmentId}/users`),
+    bindInfo: (type: string, provider: string, configId: string) =>
+      request(`${SystemConst.API_BASE}/user/third-party/${type}_${provider}/${configId}`),
+    noBindUser: (data: any) =>
+      request(`${SystemConst.API_BASE}/user/_query/no-paging`, {
+        method: 'POST',
+        data,
+      }),
+    bindUser: (
+      type: string,
+      provider: string,
+      configId: string,
+      data: { userId: string; providerName: string; thirdPartyUserId: string }[],
+    ) =>
+      request(`${SystemConst.API_BASE}/user/third-party/${type}_${provider}/${configId}`, {
+        method: 'PATCH',
+        data,
+      }),
+    getUserBindInfo: () =>
+      request(`${SystemConst.API_BASE}/user/third-party/me`, { method: 'GET' }),
+    unBindUser: (bindId: string) =>
+      request(`${SystemConst.API_BASE}/user/third-party/me/${bindId}`, {
+        method: 'DELETE',
+      }),
+  };
 }
 
 export default Service;

+ 23 - 18
src/pages/notice/Template/Detail/index.tsx

@@ -377,6 +377,7 @@ const Detail = observer(() => {
         'x-component': 'Radio.Group',
         'x-component-props': {
           optionType: 'button',
+          placeholder: '请选择类型',
         },
         required: true,
         'x-visible': typeList[id]?.length > 0,
@@ -388,12 +389,9 @@ const Detail = observer(() => {
         type: 'string',
         'x-decorator': 'FormItem',
         'x-component': 'Select',
-        // enum: [
-        //   {label: '测试配置1', value: 'test1'},
-        //   {label: '测试配置2', value: 'test2'},
-        //   {label: '测试配置3', value: 'test3'},
-        // ],
-        // 'x-reactions': '{{useAsyncDataSource(getConfig)}}',
+        'x-component-props': {
+          placeholder: '请选择绑定配置',
+        },
         'x-visible': id !== 'email',
       },
       template: {
@@ -597,12 +595,6 @@ const Detail = observer(() => {
                       placeholder: '这里是回显内容',
                     },
                   },
-                  // content: {
-                  //   title: '模版内容',
-                  //   type: 'string',
-                  //   'x-decorator': 'FormItem',
-                  //   'x-component': 'Input.TextArea',
-                  // },
                 },
                 'x-reactions': {
                   dependencies: ['provider'],
@@ -698,6 +690,9 @@ const Detail = observer(() => {
                     'x-component': 'Select',
                     'x-decorator': 'FormItem',
                     required: true,
+                    'x-component-props': {
+                      placeholder: '请选择消息类型',
+                    },
                     enum: [
                       { label: 'markdown', value: 'markdown' },
                       { label: 'text', value: 'text' },
@@ -712,6 +707,9 @@ const Detail = observer(() => {
                         title: '标题',
                         'x-component': 'Input',
                         'x-decorator': 'FormItem',
+                        'x-component-props': {
+                          placeholder: '请输入标题',
+                        },
                       },
                     },
                     'x-reactions': {
@@ -731,6 +729,9 @@ const Detail = observer(() => {
                         title: '标题',
                         'x-component': 'Input',
                         'x-decorator': 'FormItem',
+                        'x-component-props': {
+                          placeholder: '请输入标题',
+                        },
                       },
                       '{url:picUrl}': {
                         title: '图片链接',
@@ -738,12 +739,16 @@ const Detail = observer(() => {
                         'x-decorator': 'FormItem',
                         'x-component-props': {
                           type: 'file',
+                          placeholder: '请输入图片链接',
                         },
                       },
                       messageUrl: {
                         title: '内容链接',
                         'x-component': 'Input',
                         'x-decorator': 'FormItem',
+                        'x-component-props': {
+                          placeholder: '请输入内容链接',
+                        },
                       },
                     },
                     'x-reactions': {
@@ -803,11 +808,11 @@ const Detail = observer(() => {
                         'x-component': 'Input',
                         'x-decorator': 'FormItem',
                         'x-decorator-props': {
-                          tooltip: '请输入calledShowNumbers',
+                          tooltip: '请输入被叫号码',
                           gridSpan: 1,
                         },
                         'x-component-props': {
-                          placeholder: '请输入calledShowNumbers',
+                          placeholder: '请输入被叫号码',
                         },
                       },
                     },
@@ -817,10 +822,10 @@ const Detail = observer(() => {
                     'x-component': 'Input',
                     'x-decorator': 'FormItem',
                     'x-decorator-props': {
-                      tooltip: '请输入CalledNumber',
+                      tooltip: '请输入被叫显号',
                     },
                     'x-component-props': {
-                      placeholder: '请输入CalledNumber',
+                      placeholder: '请输入被叫显号',
                     },
                   },
                   PlayTimes: {
@@ -828,10 +833,10 @@ const Detail = observer(() => {
                     'x-component': 'Input',
                     'x-decorator': 'FormItem',
                     'x-decorator-props': {
-                      tooltip: '请输入PlayTimes',
+                      tooltip: '请输入播放次数',
                     },
                     'x-component-props': {
-                      placeholder: '请输入PlayTimes',
+                      placeholder: '请输入播放次数',
                     },
                   },
                 },

+ 177 - 0
src/pages/rule-engine/Alarm/Config/index.tsx

@@ -0,0 +1,177 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { Button, Card, Col, Row } from 'antd';
+import TitleComponent from '@/components/TitleComponent';
+import { createSchemaField } from '@formily/react';
+import { ArrayItems, Form, FormButtonGroup, FormGrid, FormItem, Input } from '@formily/antd';
+import { ISchema } from '@formily/json-schema';
+import { useMemo, useState } from 'react';
+import { createForm } from '@formily/core';
+import FLevelInput from '@/components/FLevelInput';
+
+const Config = () => {
+  const [tab, setTab] = useState<'io' | 'config' | string>('config');
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      ArrayItems,
+      FormGrid,
+      FLevelInput,
+    },
+  });
+
+  const form = useMemo(
+    () =>
+      createForm({
+        effects() {},
+      }),
+    [],
+  );
+  const schema1: ISchema = {
+    type: 'object',
+    properties: {
+      level: {
+        type: 'array',
+        'x-component': 'ArrayItems',
+        'x-decorator': 'FormItem',
+        maxItems: 3,
+        items: {
+          type: 'void',
+          'x-decorator': 'FormGrid',
+          'x-decorator-props': {
+            maxColumns: 24,
+            minColumns: 24,
+            columnGap: 2,
+          },
+          properties: {
+            input: {
+              type: 'string',
+              'x-decorator': 'FormItem',
+              'x-component': 'FLevelInput',
+              'x-decorator-props': {
+                gridSpan: 23,
+              },
+            },
+            remove: {
+              type: 'void',
+              'x-decorator': 'FormItem',
+              'x-component': 'ArrayItems.Remove',
+              'x-decorator-props': {
+                gridSpan: 1,
+              },
+            },
+          },
+        },
+        properties: {
+          add: {
+            type: 'void',
+            title: '添加级别',
+            'x-component': 'ArrayItems.Addition',
+          },
+        },
+      },
+    },
+  };
+
+  const schema2: ISchema = {
+    type: 'object',
+    properties: {
+      kafka: {
+        title: 'kafka地址',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      topic: {
+        title: 'topic',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      layout2: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
+        'x-decorator-props': {
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
+        },
+        properties: {
+          username: {
+            title: '用户名',
+            type: 'string',
+            required: true,
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+          },
+          password: {
+            title: '密码',
+            type: 'string',
+            required: true,
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+          },
+        },
+      },
+    },
+  };
+
+  const level = (
+    <Card>
+      <TitleComponent data="告警级别配置" />
+      <Form form={form}>
+        <SchemaField schema={schema1} />
+        <FormButtonGroup.Sticky>
+          <FormButtonGroup.FormItem>
+            <Button type="primary" onClick={() => {}}>
+              保存
+            </Button>
+          </FormButtonGroup.FormItem>
+        </FormButtonGroup.Sticky>
+      </Form>
+    </Card>
+  );
+
+  const io = (
+    <Card>
+      <TitleComponent data="告警数据输出" />
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema2} />
+      </Form>
+      <TitleComponent data="告警处理结果输入" />
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema2} />
+        <FormButtonGroup.Sticky>
+          <FormButtonGroup.FormItem>
+            <Button type="primary" onClick={() => {}}>
+              保存
+            </Button>
+          </FormButtonGroup.FormItem>
+        </FormButtonGroup.Sticky>
+      </Form>
+    </Card>
+  );
+
+  const list = [
+    { key: 'config', tab: '告警级别', component: level },
+    { key: 'io', tab: '数据流转', component: io },
+  ];
+
+  return (
+    <PageContainer onTabChange={setTab} tabActiveKey={tab} tabList={list}>
+      <Row>
+        <Col span={16}>{list.find((k) => k.key === tab)?.component}</Col>
+        <Col span={8}></Col>
+      </Row>
+    </PageContainer>
+  );
+};
+export default Config;

+ 37 - 0
src/pages/rule-engine/Alarm/Log/index.tsx

@@ -0,0 +1,37 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { observer } from '@formily/reactive-react';
+import { AlarmLogModel } from './model';
+
+const Log = observer(() => {
+  const list = [
+    {
+      key: 'product',
+      tab: '产品',
+    },
+    {
+      key: 'device',
+      tab: '设备',
+    },
+    {
+      key: 'department',
+      tab: '部门',
+    },
+    {
+      key: 'other',
+      tab: '其他',
+    },
+  ];
+  return (
+    <PageContainer
+      // onTabChange={(key: 'product' | 'device' | 'department' | 'other') => {
+      //     AlarmLogModel.tab = key
+      // }}
+      tabList={list}
+      tabActiveKey={AlarmLogModel.tab}
+    >
+      test
+    </PageContainer>
+  );
+});
+
+export default Log;

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

@@ -0,0 +1,7 @@
+import { model } from '@formily/reactive';
+
+export const AlarmLogModel = model<{
+  tab: 'product' | 'device' | 'department' | 'other';
+}>({
+  tab: 'product',
+});

+ 134 - 0
src/pages/system/User/ResetPassword/index.tsx

@@ -0,0 +1,134 @@
+import { message, Modal } from 'antd';
+import { createSchemaField } from '@formily/react';
+import { Form, FormItem, Password } from '@formily/antd';
+import { ISchema } from '@formily/json-schema';
+import { useIntl } from 'umi';
+import { useMemo } from 'react';
+import { createForm } from '@formily/core';
+import { service } from '@/pages/system/User';
+
+interface Props {
+  visible: boolean;
+  close: Function;
+  data: Partial<UserItem>;
+}
+
+const ResetPassword = (props: Props) => {
+  const intl = useIntl();
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Password,
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      password: {
+        type: 'string',
+        title: intl.formatMessage({
+          id: 'pages.system.password',
+          defaultMessage: '密码',
+        }),
+        'x-decorator': 'FormItem',
+        'x-component': 'Password',
+        'x-component-props': {
+          checkStrength: true,
+          placeholder: '请输入密码',
+        },
+        required: true,
+        'x-reactions': [
+          {
+            dependencies: ['.confirmPassword'],
+            fulfill: {
+              state: {
+                selfErrors:
+                  '{{$deps[0] && $self.value && $self.value !==$deps[0] ? "两次密码输入不一致" : ""}}',
+              },
+            },
+          },
+        ],
+        name: 'password',
+        'x-validator': [
+          {
+            max: 128,
+            message: '密码最多可输入128位',
+          },
+          {
+            message: '密码不能少于6位',
+          },
+          {
+            required: true,
+            message: '请输入密码',
+          },
+        ],
+      },
+      confirmPassword: {
+        type: 'string',
+        title: intl.formatMessage({
+          id: 'pages.system.confirmPassword',
+          defaultMessage: '确认密码?',
+        }),
+        'x-decorator': 'FormItem',
+        'x-component': 'Password',
+        'x-component-props': {
+          checkStrength: true,
+          placeholder: '请再次输入密码',
+        },
+        'x-validator': [
+          {
+            max: 128,
+            message: '密码最多可输入128位',
+          },
+          {
+            min: 6,
+            message: '密码不能少于6位',
+          },
+          {
+            required: true,
+            message: '请输入确认密码',
+          },
+        ],
+        'x-reactions': [
+          {
+            dependencies: ['.password'],
+            fulfill: {
+              state: {
+                selfErrors:
+                  '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "两次密码输入不一致" : ""}}',
+              },
+            },
+          },
+        ],
+        'x-decorator-props': {},
+        name: 'confirmPassword',
+      },
+    },
+  };
+
+  const form = useMemo(() => createForm({}), []);
+  return (
+    <Modal
+      title="重置密码"
+      visible={props.visible}
+      onCancel={() => props.close()}
+      onOk={async () => {
+        const value: { password: string; confirmPassword: string } = await form.submit();
+        if (props.data.id) {
+          const resp = await service.resetPassword(props.data.id, value.confirmPassword);
+          if (resp.status === 200) {
+            message.success('操作成功');
+          }
+        } else {
+          props.close();
+        }
+      }}
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+export default ResetPassword;

+ 32 - 12
src/pages/system/User/Save/index.tsx

@@ -153,8 +153,8 @@ const Save = (props: Props) => {
             },
             'x-validator': [
               {
-                max: 50,
-                message: '最多可输入50个字符',
+                max: 64,
+                message: '最多可输入64个字符',
               },
               {
                 required: true,
@@ -200,6 +200,7 @@ const Save = (props: Props) => {
           checkStrength: true,
           placeholder: '请输入密码',
         },
+        'x-visible': model === 'add',
         'x-reactions': [
           {
             dependencies: ['.confirmPassword'],
@@ -218,7 +219,7 @@ const Save = (props: Props) => {
             message: '密码最多可输入128位',
           },
           {
-            min: model === 'edit' ? 0 : 6,
+            min: 8,
             message: '密码不能少于6位',
           },
           {
@@ -239,15 +240,14 @@ const Save = (props: Props) => {
           checkStrength: true,
           placeholder: '请再次输入密码',
         },
-        maxLength: 128,
-        minLength: 6,
+        'x-visible': model === 'add',
         'x-validator': [
           {
             max: 128,
             message: '密码最多可输入128位',
           },
           {
-            min: model === 'edit' ? 0 : 6,
+            min: 8,
             message: '密码不能少于6位',
           },
           {
@@ -300,11 +300,15 @@ const Save = (props: Props) => {
                   onClick={() => {
                     const tab: any = window.open(`${origin}/#/system/role?save=true`);
                     tab!.onTabSaveSuccess = (value: any) => {
-                      form.setFieldState('roleIdList', (state) => {
-                        state.dataSource = state.dataSource?.concat([
-                          { label: value.name, value: value.id },
-                        ]);
-                        state.value = [...state.value, value.id];
+                      form.setFieldState('roleIdList', async (state) => {
+                        state.dataSource = await getRole().then((resp) =>
+                          resp.result?.map((item: Record<string, unknown>) => ({
+                            ...item,
+                            label: item.name,
+                            value: item.id,
+                          })),
+                        );
+                        state.value = [...(state.value || []), value.id];
                       });
                     };
                   }}
@@ -349,7 +353,7 @@ const Save = (props: Props) => {
                             value: item.id,
                           })),
                         );
-                        state.value = [...state.value, value.id];
+                        state.value = [...(state.value || []), value.id];
                       });
                     };
                   }}
@@ -360,6 +364,22 @@ const Save = (props: Props) => {
             },
             'x-reactions': ['{{useAsyncDataSource(getOrg)}}'],
           },
+          telephone: {
+            title: '手机号',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+          },
+          email: {
+            title: '邮箱',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+          },
         },
       },
     },

+ 27 - 0
src/pages/system/User/index.tsx

@@ -10,6 +10,7 @@ import {
   EditOutlined,
   PlayCircleOutlined,
   PlusOutlined,
+  SafetyOutlined,
 } from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { useRef, useState } from 'react';
@@ -17,6 +18,7 @@ import Save from './Save';
 import { observer } from '@formily/react';
 import { PermissionButton } from '@/components';
 import usePermissions from '@/hooks/permission';
+import ResetPassword from '@/pages/system/User/ResetPassword';
 
 export const service = new Service('user');
 
@@ -32,6 +34,7 @@ const User = observer(() => {
     setCurrent(record);
   };
 
+  const [reset, setReset] = useState<boolean>(false);
   const columns: ProColumns<UserItem>[] = [
     {
       title: intl.formatMessage({
@@ -108,6 +111,14 @@ const User = observer(() => {
       ),
     },
     {
+      dataIndex: 'telephone',
+      title: '手机号',
+    },
+    {
+      dataIndex: 'email',
+      title: '邮箱',
+    },
+    {
       title: intl.formatMessage({
         id: 'pages.data.option',
         defaultMessage: '操作',
@@ -165,6 +176,21 @@ const User = observer(() => {
         </PermissionButton>,
         <PermissionButton
           type="link"
+          key="password"
+          style={{ padding: 0 }}
+          tooltip={{
+            title: '重置密码',
+          }}
+          onClick={() => {
+            setReset(true);
+            setCurrent(record);
+          }}
+          isPermission={userPermission.update}
+        >
+          <SafetyOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          type="link"
           key="delete"
           style={{ padding: 0 }}
           isPermission={userPermission.delete}
@@ -231,6 +257,7 @@ const User = observer(() => {
         }}
         data={current}
       />
+      <ResetPassword data={current} visible={reset} close={() => setReset(false)} />
     </PageContainer>
   );
 });

+ 6 - 0
src/pages/system/User/serivce.ts

@@ -42,6 +42,12 @@ class Service extends BaseService<UserItem> {
       method: 'POST',
       data: name,
     });
+
+  resetPassword = (id: string, password: string) =>
+    request(`/${SystemConst.API_BASE}/user/${id}/password/_reset`, {
+      method: 'POST',
+      data: password,
+    });
 }
 
 export default Service;