瀏覽代碼

fix: 合并冲突

sun-chaochao 3 年之前
父節點
當前提交
78fdf7500e
共有 34 個文件被更改,包括 1100 次插入300 次删除
  1. 7 7
      package.json
  2. 二進制
      public/images/alarm/alarm1.png
  3. 二進制
      public/images/alarm/alarm2.png
  4. 二進制
      public/images/alarm/alarm3.png
  5. 二進制
      public/images/alarm/alarm4.png
  6. 二進制
      public/images/alarm/alarm5.png
  7. 0 1
      src/components/BaseCrud/model.ts
  8. 0 8
      src/components/BaseCrud/save/index.tsx
  9. 33 0
      src/components/FLevelInput/index.tsx
  10. 0 1
      src/components/FRuleEditor/Editor/index.tsx
  11. 0 2
      src/components/FSelectDevice/index.tsx
  12. 0 1
      src/pages/device/Instance/Detail/Config/index.tsx
  13. 1 0
      src/pages/device/Instance/Detail/Diagnose/Status/index.tsx
  14. 35 6
      src/pages/device/Instance/Detail/index.tsx
  15. 0 1
      src/pages/device/Product/Detail/PropertyImport/index.tsx
  16. 32 4
      src/pages/device/Product/Detail/index.tsx
  17. 1 1
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  18. 18 9
      src/pages/device/components/Metadata/Base/columns.ts
  19. 0 1
      src/pages/device/components/Metadata/index.tsx
  20. 49 101
      src/pages/link/Type/Detail/index.tsx
  21. 3 0
      src/pages/notice/Config/Detail/index.tsx
  22. 184 0
      src/pages/notice/Config/SyncUser/index.tsx
  23. 96 53
      src/pages/notice/Config/index.tsx
  24. 38 0
      src/pages/notice/Config/service.ts
  25. 23 18
      src/pages/notice/Template/Detail/index.tsx
  26. 256 0
      src/pages/rule-engine/Alarm/Config/index.tsx
  27. 26 0
      src/pages/rule-engine/Alarm/Config/service.ts
  28. 25 0
      src/pages/rule-engine/Alarm/Config/typing.d.ts
  29. 134 0
      src/pages/system/User/ResetPassword/index.tsx
  30. 32 12
      src/pages/system/User/Save/index.tsx
  31. 27 0
      src/pages/system/User/index.tsx
  32. 6 0
      src/pages/system/User/serivce.ts
  33. 1 0
      src/utils/menu/router.ts
  34. 73 74
      yarn.lock

+ 7 - 7
package.json

@@ -62,13 +62,13 @@
     "@ant-design/pro-descriptions": "^1.6.8",
     "@ant-design/pro-form": "^1.18.3",
     "@ant-design/pro-layout": "^6.27.2",
-    "@formily/antd": "2.0.0-rc.17",
-    "@formily/core": "2.0.0-rc.17",
-    "@formily/json-schema": "2.0.0-rc.17",
-    "@formily/react": "2.0.0-rc.17",
-    "@formily/reactive": "2.0.0-rc.17",
-    "@formily/reactive-react": "2.0.0-rc.17",
-    "@formily/shared": "2.0.0-rc.17",
+    "@formily/antd": "2.0.19",
+    "@formily/core": "2.0.19",
+    "@formily/json-schema": "2.0.19",
+    "@formily/react": "2.0.19",
+    "@formily/reactive": "2.0.19",
+    "@formily/reactive-react": "2.0.19",
+    "@formily/shared": "2.0.19",
     "@jetlinks/pro-list": "^1.10.8",
     "@jetlinks/pro-table": "^2.63.11",
     "@liveqing/liveplayer": "^2.6.4",

二進制
public/images/alarm/alarm1.png


二進制
public/images/alarm/alarm2.png


二進制
public/images/alarm/alarm3.png


二進制
public/images/alarm/alarm4.png


二進制
public/images/alarm/alarm5.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);
           }}
         >

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

@@ -40,7 +40,6 @@ const Config = () => {
   };
 
   useEffect(() => {
-    console.log(id);
     if (id) {
       service.getConfigMetadata(id).then((config) => {
         setMetadata(config?.result);

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

@@ -10,6 +10,7 @@ import { DiagnoseStatusModel } from './model';
 import { PermissionButton } from '@/components';
 import DiagnosticAdvice from './DiagnosticAdvice';
 import ManualInspection from './ManualInspection';
+
 interface Props {
   onChange: (type: string) => void;
   flag: boolean;

+ 35 - 6
src/pages/device/Instance/Detail/index.tsx

@@ -1,7 +1,7 @@
 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 { Badge, Card, Descriptions, Divider, message, Tooltip } from 'antd';
 import type { ReactNode } from 'react';
 import { useEffect, useState } from 'react';
 import { observer } from '@formily/react';
@@ -22,6 +22,7 @@ import SystemConst from '@/utils/const';
 import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 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) => {

+ 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>),
   },
 ]);
 

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

@@ -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}>

+ 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 - 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: '请输入播放次数',
                     },
                   },
                 },

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

@@ -0,0 +1,256 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { Button, Card, Col, Divider, message, 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, onFieldReact, onFormInit } from '@formily/core';
+import FLevelInput from '@/components/FLevelInput';
+import { IOConfigItem } from '@/pages/rule-engine/Alarm/Config/typing';
+import Service from '@/pages/rule-engine/Alarm/Config/service';
+
+const service = new Service('alarm/config');
+const Config = () => {
+  const [tab, setTab] = useState<'io' | 'config' | string>('config');
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      ArrayItems,
+      FormGrid,
+      FLevelInput,
+    },
+  });
+
+  const levelForm = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+        effects() {
+          onFormInit(async (form) => {
+            const resp: any = await service.queryLevel();
+            if (resp.status === 200) {
+              const _level = resp.result.levels.map(
+                (item: { level: number; title: string }) => item.title,
+              );
+              form.setValuesIn('level', _level);
+            }
+          });
+          onFieldReact('level.0.remove', (state, f1) => {
+            state.setState({ display: 'none' });
+            f1.setFieldState('level.add', (state1) => {
+              const length = f1.values.level?.length;
+              if (length > 4) {
+                state1.display = 'none';
+              } else {
+                state1.display = 'visible';
+              }
+            });
+          });
+        },
+      }),
+    [],
+  );
+
+  const inputForm = useMemo(() => createForm(), []);
+  const outputForm = useMemo(() => createForm(), []);
+
+  const levelSchema: ISchema = {
+    type: 'object',
+    properties: {
+      level: {
+        type: 'array',
+        'x-component': 'ArrayItems',
+        'x-decorator': 'FormItem',
+        maxItems: 5,
+        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',
+              title: <div style={{ width: '20px' }} />,
+              'x-decorator': 'FormItem',
+              'x-component': 'ArrayItems.Remove',
+              'x-decorator-props': {
+                gridSpan: 1,
+              },
+              'x-component-props': {
+                style: { marginTop: '40px' },
+              },
+            },
+          },
+        },
+        properties: {
+          add: {
+            type: 'void',
+            title: '添加级别',
+            'x-component': 'ArrayItems.Addition',
+          },
+        },
+      },
+    },
+  };
+
+  const ioSchema: ISchema = {
+    type: 'object',
+    properties: {
+      kafka: {
+        title: 'kafka地址',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入kafka地址',
+        },
+      },
+      topic: {
+        title: 'topic',
+        type: 'string',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          placeholder: '请输入topic',
+        },
+      },
+      layout2: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
+        'x-decorator-props': {
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
+        },
+        properties: {
+          username: {
+            title: '用户名',
+            type: 'string',
+            required: true,
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入用户名',
+            },
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+          },
+          password: {
+            title: '密码',
+            type: 'string',
+            required: true,
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入密码',
+            },
+          },
+        },
+      },
+    },
+  };
+
+  const handleSaveIO = async () => {
+    const inputConfig: IOConfigItem = await inputForm.submit();
+    const outputConfig: IOConfigItem = await outputForm.submit();
+    const inputResp = await service.saveOutputData({
+      config: {
+        type: 'kafka',
+        config: inputConfig,
+      },
+      exchangeType: 'producer',
+    });
+    const outputResp = await service.saveOutputData({
+      config: {
+        type: 'kafka',
+        config: outputConfig,
+      },
+      exchangeType: 'consume',
+    });
+
+    if (inputResp.status === 200 && outputResp.status === 200) {
+      message.success('操作成功');
+    }
+  };
+
+  const handleSaveLevel = async () => {
+    const values: { level: string[] } = await levelForm.submit();
+    const _level = values?.level.map((l: string, i: number) => ({ level: i + 1, title: l }));
+    const resp = await service.saveLevel(_level);
+    if (resp.status === 200) {
+      message.success('操作成功');
+    }
+  };
+
+  const level = (
+    <Card>
+      <TitleComponent data="告警级别配置" />
+      <Form form={levelForm}>
+        <SchemaField schema={levelSchema} />
+        <FormButtonGroup.Sticky>
+          <FormButtonGroup.FormItem>
+            <Button type="primary" onClick={handleSaveLevel}>
+              保存
+            </Button>
+          </FormButtonGroup.FormItem>
+        </FormButtonGroup.Sticky>
+      </Form>
+    </Card>
+  );
+  const io = (
+    <div>
+      <Card>
+        <TitleComponent data="告警数据输出" />
+        <Form form={outputForm} layout="vertical">
+          <SchemaField schema={ioSchema} />
+        </Form>
+        <Divider />
+        <TitleComponent data="告警处理结果输入" />
+        <Form form={inputForm} layout="vertical">
+          <SchemaField schema={ioSchema} />
+          <FormButtonGroup.Sticky>
+            <FormButtonGroup.FormItem>
+              <Button type="primary" onClick={handleSaveIO}>
+                保存
+              </Button>
+            </FormButtonGroup.FormItem>
+          </FormButtonGroup.Sticky>
+        </Form>
+      </Card>
+    </div>
+  );
+
+  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;

+ 26 - 0
src/pages/rule-engine/Alarm/Config/service.ts

@@ -0,0 +1,26 @@
+import BaseService from '@/utils/BaseService';
+import SystemConst from '@/utils/const';
+import { request } from 'umi';
+import { IOConfigItem, LevelItem } from '@/pages/rule-engine/Alarm/Config/typing';
+
+class Service extends BaseService<IOConfigItem> {
+  saveLevel = (data: LevelItem[]) =>
+    request(`/${SystemConst.API_BASE}/alarm/config/default/level`, {
+      method: 'PATCH',
+      data,
+    });
+
+  queryLevel = () =>
+    request(`/${SystemConst.API_BASE}/alarm/config/default/level`, {
+      method: 'GET',
+    });
+
+  // 保存告警数据输出
+  saveOutputData = (data: any) =>
+    request(`/${SystemConst.API_BASE}/alarm/config/data-exchange`, {
+      method: 'PATCH',
+      data,
+    });
+}
+
+export default Service;

+ 25 - 0
src/pages/rule-engine/Alarm/Config/typing.d.ts

@@ -0,0 +1,25 @@
+import { BaseItem } from '@/utils/typings';
+
+type LevelItem = {
+  level: number;
+  title: string;
+};
+
+type IOConfigItem = {
+  address: string;
+  topic: string;
+  username: string;
+  password: string;
+};
+type IOItem = {
+  alarmConfigId: string;
+  sourceType: string;
+  config: Partial<{
+    type: string;
+    dataSourceId: string;
+    config: Partial<IOConfigItem>;
+  }>;
+  exchangeType: 'consume' | 'producer'; //订阅|推送
+  state: 'disable' | 'enabled'; //禁用|正常
+  description: string;
+} & BaseItem;

+ 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;

+ 1 - 0
src/utils/menu/router.ts

@@ -62,6 +62,7 @@ export enum MENUS_CODE {
   'rule-engine/Scene' = 'rule-engine/Scene',
   'rule-engine/Alarm/Log' = 'rule-engine/Alarm/Log',
   'rule-engine/Alarm/Log/Detail' = 'rule-engine/Alarm/Log/Detail',
+  'rule-engine/Alarm/Config' = 'rule-engine/Alarm/Config',
   'simulator/Device' = 'simulator/Device',
   'system/DataSource' = 'system/DataSource',
   'system/Department/Assets' = 'system/Department/Assets',

+ 73 - 74
yarn.lock

@@ -2778,86 +2778,85 @@
   resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz#2dc8c57044de0340eb53a7ba602e59abf80dc799"
   integrity sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==
 
-"@formily/antd@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/antd/-/antd-2.0.0-rc.17.tgz#fe41ded1c387a2018c292cd37c17e13350ee1dfe"
-  integrity sha512-FLUnq56b43+va3NiH2bhLPSO2BhmAMMxRWvdMX2qfV7s6SG4TQ35hEQkxDPu3YUUKKDv/75kFdgAJIaIZwHcNA==
-  dependencies:
-    "@ant-design/icons" "^4.0.0"
-    "@formily/core" "2.0.0-rc.17"
-    "@formily/grid" "2.0.0-rc.17"
-    "@formily/json-schema" "2.0.0-rc.17"
-    "@formily/react" "2.0.0-rc.17"
-    "@formily/reactive" "2.0.0-rc.17"
-    "@formily/reactive-react" "2.0.0-rc.17"
-    "@formily/shared" "2.0.0-rc.17"
-    "@juggle/resize-observer" "^3.3.1"
+"@formily/antd@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/antd/-/antd-2.0.19.tgz#7419807965d5d1f39324b46e0be4f6aae04ca267"
+  integrity sha512-pxybyq2zWS4Ki56oY7227yjonVN7mnFiaIXSy/NVRD5wXxUBzOvrFA+4LiJuFGv0vzUkmSBbFCBkcDb/8TRZXQ==
+  dependencies:
+    "@formily/core" "2.0.19"
+    "@formily/grid" "2.0.19"
+    "@formily/json-schema" "2.0.19"
+    "@formily/react" "2.0.19"
+    "@formily/reactive" "2.0.19"
+    "@formily/reactive-react" "2.0.19"
+    "@formily/shared" "2.0.19"
     classnames "^2.2.6"
     react-sortable-hoc "^1.11.0"
     react-sticky-box "^0.9.3"
 
-"@formily/core@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/core/-/core-2.0.0-rc.17.tgz#06880aa6be6f6f822050998662654748bce1627b"
-  integrity sha512-O+iahZipqv1iwqQW9KDLTSo0USVwFrwjUs3v6ToHWgaJHbOFY3rHtUjUBB00QN4cQsc7tANErx8+MawoV/fH8Q==
-  dependencies:
-    "@formily/reactive" "2.0.0-rc.17"
-    "@formily/shared" "2.0.0-rc.17"
-    "@formily/validator" "2.0.0-rc.17"
-
-"@formily/grid@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/grid/-/grid-2.0.0-rc.17.tgz#46603919f435cbf71b65f039a4f7a4c98db8cf77"
-  integrity sha512-1boauZbcyKFC+0Pom3xeOzbH5DvCBXs2u9xHklDoaQrxZZLPggtZLlo7Qxyx7zXfyKQRn910WkbzlWFIt2VaDg==
-  dependencies:
-    "@formily/reactive" "2.0.0-rc.17"
-
-"@formily/json-schema@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/json-schema/-/json-schema-2.0.0-rc.17.tgz#5638ad56c7c44da89ae33b60b8b1f78f548ed9e4"
-  integrity sha512-x7kqGGdXXS40a3xf7LvbjPvMsCEhUZCshoEfHbQaxKOq4Y+mtUCpFYwJMBr3xsJG6+Yid3IDqflL0yBHn9/SDA==
-  dependencies:
-    "@formily/core" "2.0.0-rc.17"
-    "@formily/reactive" "2.0.0-rc.17"
-    "@formily/shared" "2.0.0-rc.17"
-
-"@formily/path@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/path/-/path-2.0.0-rc.17.tgz#40ea317fc8f46fa9908c4407720d8bcfd7e5fae6"
-  integrity sha512-BOFI38udFlYC/q9DYHehwu9FfKOdW1KgIjXp0t/wFlwfiVGQ+B/KyKVSkFPzEocdK5Q3fkujs8kyGLoyJLfSHQ==
-
-"@formily/react@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/react/-/react-2.0.0-rc.17.tgz#a88d9fa4b30d08a5ac19fda7ae09841fab64d493"
-  integrity sha512-PLBZYzKHNAb8PSGJrIFBEHA8kB3+j3WN0ls6weo89RXqbcnmkLCmjs6Xa7Cx0KYFLSLkmUZySmrI+Y51w0ASJA==
-  dependencies:
-    "@formily/core" "2.0.0-rc.17"
-    "@formily/json-schema" "2.0.0-rc.17"
-    "@formily/reactive" "2.0.0-rc.17"
-    "@formily/reactive-react" "2.0.0-rc.17"
-    "@formily/shared" "2.0.0-rc.17"
-    "@formily/validator" "2.0.0-rc.17"
+"@formily/core@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/core/-/core-2.0.19.tgz#359bef69964b623d8468934e4cc396de4eb03173"
+  integrity sha512-VsqWJKc2jhjzPgu4SKN5EVJeRrEwu+mAvsSo5bdDeKDTQ3b9+L9TTpUF8Q4t9NvZshK+gMAfvdCYNnb5hUqSnw==
+  dependencies:
+    "@formily/reactive" "2.0.19"
+    "@formily/shared" "2.0.19"
+    "@formily/validator" "2.0.19"
+
+"@formily/grid@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/grid/-/grid-2.0.19.tgz#b0b3aa45f027fd23c5918f1490597aeea7b2b90d"
+  integrity sha512-x2s1EVAkiGx6rdFr333gsNJjpwS9yLHIECvvStqWcTfBHlszrFxtPyAa1rYf0RCvjBMWq0EE6p2o6VIVqKVOtw==
+  dependencies:
+    "@formily/reactive" "2.0.19"
+    "@juggle/resize-observer" "^3.3.1"
+
+"@formily/json-schema@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/json-schema/-/json-schema-2.0.19.tgz#e14167060a07abd54759bb5ee17c6679156b866f"
+  integrity sha512-BTcEZwcGM/up6VKEVZ4wulD4hI5fYBb8n5SgRnaezSJbHECK23p8Yh13Qj4h1GFbQbnCWr6FVYFvqBSAc8tyOQ==
+  dependencies:
+    "@formily/core" "2.0.19"
+    "@formily/reactive" "2.0.19"
+    "@formily/shared" "2.0.19"
+
+"@formily/path@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/path/-/path-2.0.19.tgz#391abd170fd68048a4f59568b7b150c68fd36785"
+  integrity sha512-uiNyq0Vrls7ie8/odP7ZVybNBOFgwJVQ68XXIzq4ZPrki0uSyoVAn5CrCkNP94PdqOjN8/gjP4sQo6eSXvPnvQ==
+
+"@formily/react@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/react/-/react-2.0.19.tgz#44d0afeb4eff2b62f555664aa620cb9ecff1bec2"
+  integrity sha512-R6FE/pX1u06nORiWX7hNgb8idMcZdd+ozvZu1iupgDqAespWz6axl24OOKWH56+JU/uXDRXG8dvGKds5rjctvQ==
+  dependencies:
+    "@formily/core" "2.0.19"
+    "@formily/json-schema" "2.0.19"
+    "@formily/reactive" "2.0.19"
+    "@formily/reactive-react" "2.0.19"
+    "@formily/shared" "2.0.19"
+    "@formily/validator" "2.0.19"
     hoist-non-react-statics "^3.3.2"
 
-"@formily/reactive-react@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/reactive-react/-/reactive-react-2.0.0-rc.17.tgz#fa7d86c83170f183c8180bc6a798797609e8839b"
-  integrity sha512-7rHZ1Az0cpqjLccmrwASJ68b6QxPzJ2mpTLYKf5jbmIINPB5mG0zziFPJLymY15ljAQ6jIyX15viOkDBSkedJA==
+"@formily/reactive-react@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/reactive-react/-/reactive-react-2.0.19.tgz#0526cc22346d62c1809eefbdbac988a1845e581e"
+  integrity sha512-Laz3O/oSCIA4qKQ4fIMsyUQjS4XtD00nUvXSXIZhGdTkZW09Spq8zv7wd+0V6REEKIH6urTtC8htpBQN8W3fww==
   dependencies:
-    "@formily/reactive" "2.0.0-rc.17"
+    "@formily/reactive" "2.0.19"
     hoist-non-react-statics "^3.3.2"
 
-"@formily/reactive@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/reactive/-/reactive-2.0.0-rc.17.tgz#fcf752d2c6c14459580d08305efc0d56d9741278"
-  integrity sha512-xFLOFnd+O5t1TRmunlFJHpTTKObSjh7rxJW7IvO42OkrV1o2dUJ7TdDcsaZIHsHg9H/3tMFHzAtfGcprpIcYAA==
+"@formily/reactive@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/reactive/-/reactive-2.0.19.tgz#4498b4e70c466bfee9b9dda8639ffe6f10d5d7ca"
+  integrity sha512-gEpiEITdrRHGc+cf/0lalw4gTcES+8axdAxC0mZRMHfJ8iSZnFs369AGxiWdElUK9NNVLfEmSuU60op6XCQhrg==
 
-"@formily/shared@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/shared/-/shared-2.0.0-rc.17.tgz#c6e88df5652f376783130be908e428d3533093b5"
-  integrity sha512-+09L5mOP0MwOk5AOOiphNlZ1PPNYxPI/7pMthulyF3BIDSAJF9Odx3IGvGz+YthzD0fYpi3QX7ly2OuwAA3FhA==
+"@formily/shared@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/shared/-/shared-2.0.19.tgz#602ce0738fe39fb0773accc3345329ba3b0cbbac"
+  integrity sha512-1zKNZLKoEEH31Y9+rBXdByHVsUModWyshkPj7fsZv0KkaObn/wV2WUCKLQW4c4Hn1y+yojPH//8SD2oOZ4wZXw==
   dependencies:
-    "@formily/path" "2.0.0-rc.17"
+    "@formily/path" "2.0.19"
     camel-case "^4.1.1"
     lower-case "^2.0.1"
     no-case "^3.0.4"
@@ -2865,12 +2864,12 @@
     pascal-case "^3.1.1"
     upper-case "^2.0.1"
 
-"@formily/validator@2.0.0-rc.17":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@formily/validator/-/validator-2.0.0-rc.17.tgz#e11bf27a5f5b14bed92dcdc8e5dbf2430965018d"
-  integrity sha512-srjQrfY8ubKaFjldb75lcHhBVgXKNY6Q1R6BvFr2Xogslbkriv2ct752Bix0YC+cFZ4elFwWyiOknSaupnzZRg==
+"@formily/validator@2.0.19":
+  version "2.0.19"
+  resolved "https://registry.yarnpkg.com/@formily/validator/-/validator-2.0.19.tgz#4d14191b6ab92b0298a59b42964cab008ef7f551"
+  integrity sha512-KS9g0WXKR77ET+3blKGxDL2w4e8gp0z5kkd5BDm7bIUmfNb67rTuSaacs+8MbOuckt09B7qU1nzOekXkskaRNw==
   dependencies:
-    "@formily/shared" "2.0.0-rc.17"
+    "@formily/shared" "2.0.19"
 
 "@hapi/address@^2.1.2":
   version "2.1.4"