Bläddra i källkod

fix: Merge branch next into next-hub

jackhoo_98 3 år sedan
förälder
incheckning
abee325c4b
32 ändrade filer med 594 tillägg och 249 borttagningar
  1. BIN
      public/images/apply/1.png
  2. BIN
      public/images/apply/2.png
  3. BIN
      public/images/apply/3.png
  4. BIN
      public/images/apply/4.png
  5. BIN
      public/images/apply/5.png
  6. 15 2
      src/components/FIndicators/index.tsx
  7. 8 0
      src/components/Metadata/ArrayParam/index.tsx
  8. 8 0
      src/components/Metadata/EnumParam/index.tsx
  9. 16 0
      src/components/Metadata/JsonParam/index.tsx
  10. 1 1
      src/components/ProTableCard/index.tsx
  11. 6 10
      src/pages/device/Instance/Detail/Running/Property/FileComponent/index.tsx
  12. 10 20
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  13. 47 31
      src/pages/device/components/Metadata/Cat/index.tsx
  14. 22 20
      src/pages/device/components/Metadata/Import/index.tsx
  15. 1 1
      src/pages/link/DataCollect/components/Point/index.tsx
  16. 31 9
      src/pages/media/Cascade/Channel/index.tsx
  17. 5 0
      src/pages/media/Cascade/service.ts
  18. 10 14
      src/pages/media/Device/Save/index.tsx
  19. 6 0
      src/pages/media/Device/service.ts
  20. 4 0
      src/pages/notice/Template/Detail/doc/index.less
  21. 1 1
      src/pages/notice/Template/Detail/index.tsx
  22. 14 8
      src/pages/rule-engine/Scene/Save/action/Delay/index.tsx
  23. 1 1
      src/pages/rule-engine/Scene/Save/action/DeviceOutput/actions/TypeModel.tsx
  24. 0 1
      src/pages/rule-engine/Scene/Save/action/DeviceOutput/device/Tag.tsx
  25. 20 11
      src/pages/rule-engine/Scene/Save/action/DeviceOutput/device/index.tsx
  26. 10 1
      src/pages/rule-engine/Scene/Save/action/DeviceOutput/index.tsx
  27. 2 2
      src/pages/rule-engine/Scene/Save/action/ListItem/Item.tsx
  28. 4 1
      src/pages/rule-engine/Scene/Save/action/notify/components/variableItem/buildIn.tsx
  29. 33 2
      src/pages/rule-engine/Scene/index.tsx
  30. 178 0
      src/pages/system/Apply/Save/doc.tsx
  31. 135 107
      src/pages/system/Apply/Save/index.tsx
  32. 6 6
      src/pages/system/DataSource/Save/index.tsx

BIN
public/images/apply/1.png


BIN
public/images/apply/2.png


BIN
public/images/apply/3.png


BIN
public/images/apply/4.png


BIN
public/images/apply/5.png


+ 15 - 2
src/components/FIndicators/index.tsx

@@ -13,7 +13,6 @@ const FIndicators = (props: Props) => {
   const { value, onChange, type } = props;
   const DatePicker1: any = DatePicker;
   const [list, setList] = useState<any[]>([]);
-
   useEffect(() => {
     const arr = [];
     if (!!props.enum?.falseText && props.enum?.falseValue !== undefined) {
@@ -119,6 +118,20 @@ const FIndicators = (props: Props) => {
           ))}
         </Select>
       );
+    } else if (type === 'string') {
+      return (
+        <Input
+          style={{ width: '100%' }}
+          value={value?.value}
+          placeholder={'请输入'}
+          onChange={(e) => {
+            onChange({
+              ...value,
+              value: [e.target.value],
+            });
+          }}
+        />
+      );
     } else {
       return (
         <>
@@ -152,7 +165,7 @@ const FIndicators = (props: Props) => {
   return (
     <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
       {renderComponent()}
-      {type !== 'boolean' && (
+      {type !== 'boolean' && type !== 'string' && (
         <Checkbox
           style={{ minWidth: 60, marginLeft: 5 }}
           checked={value?.range}

+ 8 - 0
src/components/Metadata/ArrayParam/index.tsx

@@ -66,6 +66,10 @@ const ArrayParam = () => {
             default: 2,
             'x-validator': [
               {
+                format: 'integer',
+                message: '请输入0-2147483647之间的正整数',
+              },
+              {
                 max: 2147483647,
                 message: '请输入0-2147483647之间的正整数',
               },
@@ -141,6 +145,10 @@ const ArrayParam = () => {
                 },
                 'x-validator': [
                   {
+                    format: 'integer',
+                    message: '请输入1-2147483647之间的正整数',
+                  },
+                  {
                     max: 2147483647,
                     message: '请输入1-2147483647之间的正整数',
                   },

+ 8 - 0
src/components/Metadata/EnumParam/index.tsx

@@ -39,6 +39,14 @@ const EnumParam = () => {
             },
           },
         ],
+        'x-reactions': {
+          dependencies: ['.type'],
+          fulfill: {
+            state: {
+              value: [{}],
+            },
+          },
+        },
         items: {
           type: 'void',
           'x-component': 'ArrayItems.Item',

+ 16 - 0
src/components/Metadata/JsonParam/index.tsx

@@ -64,6 +64,14 @@ const JsonParam = observer((props: Props) => {
         type: 'array',
         'x-component': 'ArrayItems',
         'x-decorator': 'FormItem',
+        'x-reactions': {
+          dependencies: ['.type'],
+          fulfill: {
+            state: {
+              value: [{}],
+            },
+          },
+        },
         'x-validator': [
           {
             triggerType: 'onBlur',
@@ -294,6 +302,10 @@ const JsonParam = observer((props: Props) => {
                           },
                           'x-validator': [
                             {
+                              format: 'integer',
+                              message: '请输入1-2147483647之间的正整数',
+                            },
+                            {
                               max: 2147483647,
                               message: '请输入1-2147483647之间的正整数',
                             },
@@ -323,6 +335,10 @@ const JsonParam = observer((props: Props) => {
                   default: 2,
                   'x-validator': [
                     {
+                      format: 'integer',
+                      message: '请输入0-2147483647之间的正整数',
+                    },
+                    {
                       max: 2147483647,
                       message: '请输入0-2147483647之间的正整数',
                     },

+ 1 - 1
src/components/ProTableCard/index.tsx

@@ -168,7 +168,7 @@ const ProTableCard = <
     if (pageSize !== size) {
       _current = 1;
     }
-    console.log(_current);
+    // console.log(_current);
     setCurrent(_current);
     setPageIndex(_current - 1);
     setPageSize(size);

+ 6 - 10
src/pages/device/Instance/Detail/Running/Property/FileComponent/index.tsx

@@ -116,18 +116,14 @@ const FileComponent = (props: Props) => {
         data?.valueType?.fileType === 'base64' ||
         data?.valueType?.fileType === 'Binary(二进制)'
       ) {
-        // if(data?.valueType?.fileType === 'base64') {
-        //   console.log(value?.formatValue.split(',')[0])
-        // }
-        // if(data?.valueType?.fileType === 'Binary(二进制)') {
-        //
-        // }
         return (
           <div className={props.type === 'card' ? styles.cardValue : styles.otherValue}>
-            <Tooltip placement="topLeft" title={String(value?.formatValue)}>
-              {String(value?.formatValue)}
-            </Tooltip>
-            {/*<img src={value?.formatValue} />*/}
+            <img
+              src={value?.formatValue}
+              onError={(e: any) => {
+                e.target.src = imgMap.get('other');
+              }}
+            />
           </div>
         );
       } else {

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

@@ -208,21 +208,6 @@ const Edit = observer((props: Props) => {
       const reg = new RegExp('^[0-9a-zA-Z_\\\\-]+$');
       return reg.exec(value) ? '' : 'ID只能由数字、字母、下划线、中划线组成';
     },
-    checkLength(value) {
-      if (String(value).length > 64) {
-        return {
-          type: 'error',
-          message: '最多可输入64个字符',
-        };
-      }
-      if (!(value % 1 === 0)) {
-        return {
-          type: 'error',
-          message: '请输入非0正整数',
-        };
-      }
-      return '';
-    },
   });
   const valueTypeConfig = {
     type: 'object',
@@ -293,6 +278,10 @@ const Edit = observer((props: Props) => {
         default: 2,
         'x-validator': [
           {
+            format: 'integer',
+            message: '请输入0-2147483647之间的正整数',
+          },
+          {
             max: 2147483647,
             message: '请输入0-2147483647之间的正整数',
           },
@@ -387,13 +376,12 @@ const Edit = observer((props: Props) => {
             'x-component-props': {
               min: 1,
             },
-            // 'x-validator': [
-            //   {
-            //     checkLength: true,
-            //   },
-            // ],
             'x-validator': [
               {
+                format: 'integer',
+                message: '请输入1-2147483647之间的正整数',
+              },
+              {
                 max: 2147483647,
                 message: '请输入1-2147483647之间的正整数',
               },
@@ -923,6 +911,8 @@ const Edit = observer((props: Props) => {
                               type: '{{$deps[0]}}',
                               enum: '{{$deps[1]}}',
                             },
+                            selfErrors:
+                              "{{$deps[0] === 'string' && $self?.value?.value?.[0]?.length > 64 ? '最多输入64个字符' : ''}}",
                           },
                         },
                       },

+ 47 - 31
src/pages/device/components/Metadata/Cat/index.tsx

@@ -1,8 +1,8 @@
-import { Button, Drawer, message, Space, Tabs } from 'antd';
+import { Button, Drawer, message, Space, Spin, Tabs } from 'antd';
 import { useEffect, useState } from 'react';
 import { productModel, service } from '@/pages/device/Product';
 import { observer } from '@formily/react';
-import { InstanceModel } from '@/pages/device/Instance';
+import { InstanceModel, service as deviceService } from '@/pages/device/Instance';
 import { useLocation } from 'umi';
 import InstanceService from '@/pages/device/Instance/service';
 import { downloadObject } from '@/utils/util';
@@ -24,14 +24,28 @@ const Cat = observer((props: Props) => {
   };
   const metadata = metadataMap[props.type];
   const [value, setValue] = useState(metadata);
+  const [loading, setLoading] = useState<boolean>(false);
   const _path = location.pathname.split('/');
   const id = _path[_path.length - 1];
 
   useEffect(() => {
     if (props.visible) {
-      setValue(metadata);
+      setLoading(true);
+      if (props.type === 'device') {
+        deviceService.detail(id).then((resp) => {
+          setLoading(false);
+          InstanceModel.current = resp.result;
+          setValue(resp.result.metadata);
+        });
+      } else {
+        service.detail(id).then((resp) => {
+          setLoading(false);
+          productModel.current = resp.result;
+          setValue(resp.result.metadata);
+        });
+      }
     }
-  }, [props.visible]);
+  }, [props.visible, props.type]);
 
   useEffect(() => {
     service.codecs().subscribe({
@@ -97,34 +111,36 @@ const Cat = observer((props: Props) => {
         </Space>
       }
     >
-      <div style={{ background: '#F6F6F6' }}>
-        <p style={{ padding: 10, color: 'rgba(0, 0, 0, 0.55)' }}>
-          物模型是对设备在云端的功能描述,包括设备的属性、服务和事件。物联网平台通过定义一种物的描述语言来描述物模型,称之为
-          TSL(即 Thing Specification Language),采用 JSON 格式,您可以根据 TSL
-          组装上报设备的数据。您可以导出完整物模型,用于云端应用开发。
-        </p>
-      </div>
-      <Tabs onChange={convertMetadata}>
-        {codecs?.map((item) => (
-          <Tabs.TabPane tab={item.name} tabKey={item.id} key={item.id}>
-            <div style={{ border: '1px solid #eee', height: 670, width: 650 }}>
-              <JMonacoEditor
-                height={'100%'}
-                theme="vs"
-                language="json"
-                key={item.id}
-                value={value}
-                editorDidMount={(editor: any) => {
-                  editor.getAction('editor.action.formatDocument').run();
-                  editor.onDidScrollChange?.(() => {
+      <Spin spinning={loading}>
+        <div style={{ background: '#F6F6F6' }}>
+          <p style={{ padding: 10, color: 'rgba(0, 0, 0, 0.55)' }}>
+            物模型是对设备在云端的功能描述,包括设备的属性、服务和事件。物联网平台通过定义一种物的描述语言来描述物模型,称之为
+            TSL(即 Thing Specification Language),采用 JSON 格式,您可以根据 TSL
+            组装上报设备的数据。您可以导出完整物模型,用于云端应用开发。
+          </p>
+        </div>
+        <Tabs onChange={convertMetadata}>
+          {codecs?.map((item) => (
+            <Tabs.TabPane tab={item.name} tabKey={item.id} key={item.id}>
+              <div style={{ border: '1px solid #eee', height: 670, width: 650 }}>
+                <JMonacoEditor
+                  height={'100%'}
+                  theme="vs"
+                  language="json"
+                  key={item.id}
+                  value={value}
+                  editorDidMount={(editor: any) => {
                     editor.getAction('editor.action.formatDocument').run();
-                  });
-                }}
-              />
-            </div>
-          </Tabs.TabPane>
-        ))}
-      </Tabs>
+                    editor.onDidScrollChange?.(() => {
+                      editor.getAction('editor.action.formatDocument').run();
+                    });
+                  }}
+                />
+              </div>
+            </Tabs.TabPane>
+          ))}
+        </Tabs>
+      </Spin>
     </Drawer>
   );
 });

+ 22 - 20
src/pages/device/components/Metadata/Import/index.tsx

@@ -17,6 +17,7 @@ import { InstanceModel } from '@/pages/device/Instance';
 import _ from 'lodash';
 import type { DeviceMetadata } from '@/pages/device/Product/typings';
 import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
+import { useMemo, useState } from 'react';
 interface Props {
   visible: boolean;
   close: () => void;
@@ -25,9 +26,14 @@ interface Props {
 
 const Import = (props: Props) => {
   const param = useParams<{ id: string }>();
-  const form = createForm({
-    initialValues: {},
-  });
+  const form = useMemo(
+    () =>
+      createForm({
+        initialValues: {},
+      }),
+    [props.visible],
+  );
+  const [loading, setLoading] = useState<boolean>(false);
 
   const SchemaField = createSchemaField({
     components: {
@@ -282,32 +288,22 @@ const Import = (props: Props) => {
 
   const handleImport = async () => {
     const data = (await form.submit()) as any;
-    const checkProperties = (metadataJson: string) => {
-      const metadata = JSON.parse(metadataJson.metadata);
-      return (
-        !!metadata &&
-        !!metadata?.properties &&
-        !!metadata?.events &&
-        !!metadata?.functions &&
-        !!metadata?.tags
-      );
-    };
-
+    setLoading(true);
     if (data.metadata === 'alink') {
       service.convertMetadata('from', 'alink', data.import).subscribe({
         next: async (meta) => {
           const metadata = JSON.stringify(operateLimits(meta));
-          // eslint-disable-next-line @typescript-eslint/no-throw-literal
-          if (!checkProperties(metadata)) throw 'error';
           if (props?.type === 'device') {
             await deviceService.saveMetadata(param.id, metadata);
           } else {
             await service.modify(param.id, { metadata: metadata });
           }
+          setLoading(false);
           MetadataAction.insert(JSON.parse(metadata || '{}'));
           onlyMessage('导入成功');
         },
         error: () => {
+          setLoading(false);
           onlyMessage('发生错误!', 'error');
         },
       });
@@ -317,6 +313,13 @@ const Import = (props: Props) => {
     } else {
       try {
         const _object = JSON.parse(data[props?.type === 'device' ? 'import' : data?.type] || '{}');
+        if (
+          !(!!_object?.properties || !!_object?.events || !!_object?.functions || !!_object?.tags)
+        ) {
+          onlyMessage('物模型数据不正确', 'error');
+          setLoading(false);
+          return;
+        }
         const params = {
           id: param.id,
           metadata: JSON.stringify(operateLimits(_object as DeviceMetadata)),
@@ -328,17 +331,14 @@ const Import = (props: Props) => {
         } else {
           resp = await service.modify(param.id, params);
         }
+        setLoading(false);
         if (resp.status === 200) {
           if (props?.type === 'device') {
             const metadata: DeviceMetadata = JSON.parse(paramsDevice || '{}');
-            // eslint-disable-next-line @typescript-eslint/no-throw-literal
-            if (!checkProperties(metadata)) throw 'error';
             MetadataAction.insert(metadata);
             onlyMessage('导入成功');
           } else {
             const metadata: DeviceMetadata = JSON.parse(params?.metadata || '{}');
-            // eslint-disable-next-line @typescript-eslint/no-throw-literal
-            if (!checkProperties(metadata)) throw 'error';
             MetadataAction.insert(metadata);
             onlyMessage('导入成功');
           }
@@ -347,6 +347,7 @@ const Import = (props: Props) => {
         Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
         props.close();
       } catch (e) {
+        setLoading(false);
         onlyMessage(e === 'error' ? '物模型数据不正确' : '上传json格式的物模型文件', 'error');
       }
     }
@@ -359,6 +360,7 @@ const Import = (props: Props) => {
       visible={props.visible}
       onCancel={() => props.close()}
       onOk={handleImport}
+      confirmLoading={loading}
     >
       <div style={{ background: 'rgb(236, 237, 238)' }}>
         <p style={{ padding: 10 }}>

+ 1 - 1
src/pages/link/DataCollect/components/Point/index.tsx

@@ -122,7 +122,7 @@ const PointCard = observer((props: PointCardProps) => {
             terms: [{ column: 'collectorId', value: props.data?.id }],
           },
         ],
-        sorts: [{ name: 'createTime', order: 'desc' }],
+        sorts: [{ name: 'id', order: 'desc' }],
       })
       .then((resp) => {
         if (resp.status === 200) {

+ 31 - 9
src/pages/media/Cascade/Channel/index.tsx

@@ -26,6 +26,8 @@ const Channel = () => {
   const [popVisible, setPopvisible] = useState<string>('');
   const { permission } = PermissionButton.usePermission('media/Cascade');
   const { minHeight } = useDomFullHeight(`.cascadeDevice`, 24);
+  const [pass, setPass] = useState<boolean>(true);
+  const [isnull, setIsnull] = useState<boolean>(false);
 
   const unbind = async (list: string[]) => {
     const resp = await service.unbindChannel(id, list);
@@ -43,27 +45,45 @@ const Channel = () => {
 
   const content = (record: any) => {
     return (
-      <div>
+      <div style={{ width: 250 }}>
         <Input
           value={data}
           placeholder="请输入国标ID"
-          onChange={(e) => {
+          onChange={async (e) => {
             setData(e.target.value);
+            if (e.target.value) {
+              if (/\s/.test(e.target.value)) {
+                setIsnull(true);
+                setPass(true);
+              } else {
+                setIsnull(false);
+                const res = await service.validateId(record.cascadeId, [e.target.value]);
+                if (res.status === 200) {
+                  setPass(res.result.passed);
+                }
+              }
+            }
           }}
         />
+        {!pass && <div style={{ color: 'red' }}>该国标ID在同一设备下已存在</div>}
+        {isnull && <div style={{ color: 'red' }}>国标ID不能含有空格</div>}
         <Button
           type="primary"
           style={{ marginTop: 10, width: '100%' }}
           onClick={async () => {
             if (!!data) {
               if (data.length <= 64) {
-                const resp: any = await service.editBindInfo(record.id, {
-                  gbChannelId: data,
-                });
-                if (resp.status === 200) {
-                  onlyMessage('操作成功');
-                  actionRef.current?.reload();
-                  setPopvisible('');
+                if (pass) {
+                  const resp: any = await service.editBindInfo(record.id, {
+                    gbChannelId: data,
+                  });
+                  if (resp.status === 200) {
+                    onlyMessage('操作成功');
+                    actionRef.current?.reload();
+                    setPopvisible('');
+                  }
+                } else {
+                  message.error('该国标ID在同一设备下已存在');
                 }
               } else {
                 message.error('最多可输入64个字符');
@@ -117,6 +137,8 @@ const Channel = () => {
                   onClick={() => {
                     setPopvisible('');
                     setData('');
+                    setPass(true);
+                    setIsnull(false);
                   }}
                 />
               </div>

+ 5 - 0
src/pages/media/Cascade/service.ts

@@ -72,6 +72,11 @@ class Service extends BaseService<CascadeItem> {
     request(`/${SystemConst.API_BASE}/network/resources/alive/_all`, {
       method: 'GET',
     });
+  validateId = (cascadeId: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/media/gb28181-cascade/${cascadeId}/gbChannelId/_validate`, {
+      method: 'POST',
+      data,
+    });
 }
 
 export default Service;

+ 10 - 14
src/pages/media/Device/Save/index.tsx

@@ -53,6 +53,7 @@ const Save = () => {
           form.setFieldsValue({
             ...res.result,
             photoUrl: res.result?.photoUrl || defaultImage,
+            channel: res.result?.provider,
           });
           const _accessType = res.result?.provider || DefaultAccessType;
           setAccessType(_accessType);
@@ -62,7 +63,7 @@ const Save = () => {
       });
     } else {
       form.setFieldsValue({
-        provider: DefaultAccessType,
+        channel: DefaultAccessType,
         photoUrl: defaultImage,
       });
       queryProduct(DefaultAccessType);
@@ -73,20 +74,16 @@ const Save = () => {
   const handleSave = useCallback(async () => {
     const formData = await form.validateFields();
     if (formData) {
-      const { provider, ...extraFormData } = formData;
-      if (formData.password === oldPassword && !id) {
-        delete extraFormData.password;
+      const { channel, ...extraFormData } = formData;
+      if (formData?.others?.access_pwd === oldPassword && !id) {
+        delete extraFormData.others?.access_pwd;
       }
       if (formData.id === '') {
         delete extraFormData.id;
       }
-      // if (formData.password === oldPassword) {
-      //   delete extraFormData.password;
-      // }
-      const resp =
-        provider === DefaultAccessType
-          ? await service.saveGB(extraFormData)
-          : await service.saveFixed(extraFormData);
+      const resp = id
+        ? await service.updateData(channel, id, { ...extraFormData, channel })
+        : await service.saveData(channel, { ...extraFormData, channel });
       if (resp.status === 200) {
         form.resetFields();
         onlyMessage('操作成功');
@@ -140,7 +137,7 @@ const Save = () => {
                 <Row>
                   <Col span={24}>
                     <Form.Item
-                      name={'provider'}
+                      name={'channel'}
                       label={'接入方式'}
                       required
                       rules={[{ required: true, message: '请选择接入方式' }]}
@@ -149,7 +146,6 @@ const Save = () => {
                         model={'singular'}
                         itemStyle={{ width: '50%' }}
                         onSelect={(key) => {
-                          console.log(key);
                           setAccessType(key);
                           queryProduct(key);
                           form.resetFields(['id', 'productId']);
@@ -301,7 +297,7 @@ const Save = () => {
                     <Col span={24}>
                       <Form.Item
                         label={'接入密码'}
-                        name={'password'}
+                        name={['others', 'access_pwd']}
                         required
                         rules={[
                           { required: true, message: '请输入接入密码' },

+ 6 - 0
src/pages/media/Device/service.ts

@@ -4,6 +4,12 @@ import SystemConst from '@/utils/const';
 import type { DeviceItem } from './typings';
 
 class Service extends BaseService<DeviceItem> {
+  saveData = (channelId: string, data?: any) =>
+    request(`${this.uri}/${channelId}`, { method: 'POST', data });
+
+  updateData = (channel: string, deviceId: string, data?: any) =>
+    request(`${this.uri}/${channel}/${deviceId}`, { method: 'PUT', data });
+
   // 新增GB28181接入的设备
   saveGB = (data?: any) => request(`${this.uri}/gb28181`, { method: 'PATCH', data });
 

+ 4 - 0
src/pages/notice/Template/Detail/doc/index.less

@@ -28,6 +28,10 @@
     color: rgba(0, 0, 0, 0.8);
     font-size: 14px;
   }
+  span {
+    color: rgba(0, 0, 0, 0.8);
+    font-weight: 600;
+  }
 
   .image {
     margin: 16px 0;

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

@@ -335,7 +335,7 @@ const Detail = observer(() => {
           });
           onFieldValueChange('template.body', (field, form1) => {
             const value = (field as Field).value;
-            console.log(value);
+            // console.log(value);
             const idList = value.match(pattern)?.filter((i: string) => i);
             form1.setFieldState('variableDefinitions', (state1) => {
               state1.visible = !!idList && idList.length > 0;

+ 14 - 8
src/pages/rule-engine/Scene/Save/action/Delay/index.tsx

@@ -1,6 +1,7 @@
 import { Modal, Select, InputNumber } from 'antd';
 import { useEffect, useState } from 'react';
 import { observer } from '@formily/react';
+import { onlyMessage } from '@/utils/util';
 
 export enum TimeUnit {
   'seconds' = 'seconds',
@@ -53,7 +54,7 @@ export default observer((props: Props) => {
 
   return (
     <Modal
-      title={'执行动作'}
+      title={'延迟执行'}
       open
       keyboard={false}
       maskClosable={false}
@@ -62,13 +63,18 @@ export default observer((props: Props) => {
         props.cancel();
       }}
       onOk={() => {
-        props.save(
-          {
-            time: value,
-            unit,
-          },
-          { name: `延迟 ${value} ${timeUnitEnum[unit]} 执行` },
-        );
+        console.log(value);
+        if (value || value === 0) {
+          props.save(
+            {
+              time: value,
+              unit,
+            },
+            { name: `${value} ${timeUnitEnum[unit]}后,执行后续动作` },
+          );
+        } else {
+          onlyMessage('请输入时间', 'error');
+        }
       }}
     >
       <InputNumber

+ 1 - 1
src/pages/rule-engine/Scene/Save/action/DeviceOutput/actions/TypeModel.tsx

@@ -65,7 +65,7 @@ export default observer((props: Props) => {
     const _params = {
       branch: props.thenName,
       branchGroup: props.branchGroup,
-      action: props.name,
+      action: props.name - 1,
     };
     queryBuiltInParams(FormModel.current, _params).then((res: any) => {
       if (res.status === 200) {

+ 0 - 1
src/pages/rule-engine/Scene/Save/action/DeviceOutput/device/Tag.tsx

@@ -38,7 +38,6 @@ export default (props: TagModalProps) => {
           value: item.value,
         };
       });
-    console.log();
     if (props.onChange) {
       props.onChange([{ value: newValue, name: '标签' }]);
     }

+ 20 - 11
src/pages/rule-engine/Scene/Save/action/DeviceOutput/device/index.tsx

@@ -14,7 +14,7 @@ import { observer } from '@formily/reactive-react';
 import { Form, TreeSelect } from 'antd';
 import '../index.less';
 import TopCard from './TopCard';
-import { ExclamationCircleOutlined } from '@ant-design/icons';
+// import { ExclamationCircleOutlined } from '@ant-design/icons';
 import { FormModel } from '../../..';
 import { BuiltInParamsHandleTreeData } from '@/pages/rule-engine/Scene/Save/components/BuiltInParams';
 import { queryBuiltInParams } from '@/pages/rule-engine/Scene/Save/action/service';
@@ -27,6 +27,7 @@ interface Props {
   parallel: boolean;
   thenName: number;
   branchGroup?: number;
+  formProductId: any;
 }
 
 export default observer((props: Props) => {
@@ -249,14 +250,19 @@ export default observer((props: Props) => {
     const _params = {
       branch: props.thenName,
       branchGroup: props.branchGroup,
-      action: props.name,
+      action: props.name - 1,
     };
     queryBuiltInParams(FormModel.current, _params).then((res: any) => {
       if (res.status === 200) {
         const _data = BuiltInParamsHandleTreeData(res.result);
         const array = filterTree(_data);
-        console.log('--------', array);
-        setBuiltInList(array);
+        // console.log('--------', array);
+        //判断相同产品才有按变量
+        if (props.formProductId === DeviceModel.productId) {
+          setBuiltInList(array);
+        } else {
+          setBuiltInList([]);
+        }
       }
     });
   };
@@ -272,7 +278,7 @@ export default observer((props: Props) => {
       }
       //标签
       const tag = JSON.parse(DeviceModel.productDetail?.metadata || '{}')?.tags;
-      if (!tag) {
+      if (tag && tag.length !== 0) {
         const array = TypeList.filter((item) => item.value === 'tag');
         _list.push(...array);
       }
@@ -426,7 +432,7 @@ export default observer((props: Props) => {
                 fieldNames={{ label: 'name', value: 'id' }}
                 placeholder={'请选择参数'}
                 onSelect={(value: any, node: any) => {
-                  console.log(value, node);
+                  // console.log(value, node);
                   DeviceModel.deviceId = value;
                   DeviceModel.deviceDetail = node;
                   DeviceModel.selectorValues = [{ value: value, name: node.description }];
@@ -471,13 +477,16 @@ export default observer((props: Props) => {
 
   return (
     <div>
-      <div className="device-title">
-        <ExclamationCircleOutlined className="device-title-icon" />
-        <span>自定义选择当前产品下的任意设备</span>
-      </div>
       <Form form={form} layout={'vertical'}>
         <Form.Item name="selector" label="选择方式" required hidden={list.length === 1}>
-          <TopCard typeList={list} />
+          <TopCard
+            typeList={list}
+            onChange={(value) => {
+              if (value) {
+                form.resetFields(['selectorValues']);
+              }
+            }}
+          />
         </Form.Item>
         {contentRender(selector)}
       </Form>

+ 10 - 1
src/pages/rule-engine/Scene/Save/action/DeviceOutput/index.tsx

@@ -10,6 +10,7 @@ import DeviceModel from './model';
 import { onlyMessage } from '@/utils/util';
 import { ActionsDeviceProps } from '../../../typings';
 import { service as api } from '@/pages/device/Instance/index';
+import { FormModel } from '../..';
 
 export const service = new Service<any>('');
 
@@ -25,6 +26,7 @@ interface Props {
 
 export default observer((props: Props) => {
   const formRef = useRef<any>();
+  const formProductIdRef = useRef<any>('');
 
   DeviceModel.steps = [
     {
@@ -41,6 +43,7 @@ export default observer((props: Props) => {
           parallel={props.parallel}
           branchGroup={props.branchGroup}
           thenName={props.thenName}
+          formProductId={formProductIdRef.current}
         />
       ),
     },
@@ -138,7 +141,7 @@ export default observer((props: Props) => {
       }));
       // console.log(_options.taglist, 'taglist')
     }
-    console.log(DeviceModel.propertiesValue, _options);
+    // console.log(DeviceModel.propertiesValue, _options);
     props.save(item, _options);
     init();
   };
@@ -161,6 +164,12 @@ export default observer((props: Props) => {
     };
   }, [props.value]);
 
+  useEffect(() => {
+    const item = FormModel.current?.branches?.[0].then?.[0]?.actions?.[0].device?.productId;
+    console.log(item);
+    formProductIdRef.current = item;
+  }, []);
+
   return (
     <Modal
       title={'执行动作'}

+ 2 - 2
src/pages/rule-engine/Scene/Save/action/ListItem/Item.tsx

@@ -152,8 +152,8 @@ export default (props: ItemProps) => {
               <img width={18} src={itemNotifyIconMap.get(data?.notify?.notifyType)} />
               微信
             </span>
-            向<span className={'notify-text-highlight'}>{options?.sendTo || ''}</span>
-            <span className={'notify-text-highlight'}>{options?.orgName || ''}</span>
+            向<span className={'notify-text-highlight'}>{options?.orgName || ''}</span>
+            <span className={'notify-text-highlight'}>{options?.sendTo || ''}</span>
             <span className={'notify-text-highlight'}>{options?.tagName || ''}</span>
             发送
             <span className={'notify-text-highlight'}>

+ 4 - 1
src/pages/rule-engine/Scene/Save/action/notify/components/variableItem/buildIn.tsx

@@ -68,7 +68,10 @@ export default (props: BuiltInProps) => {
             });
           }
         } else {
-          if (item.type === type) {
+          if (
+            item.type === type ||
+            (type === 'double' && ['int', 'float', 'double', 'long'].includes(item.type))
+          ) {
             list.push(item);
           }
         }

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

@@ -1,5 +1,5 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import React, { useRef, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
 import {
@@ -19,6 +19,7 @@ import { onlyMessage } from '@/utils/util';
 import useHistory from '@/hooks/route/useHistory';
 import Save from './Save/save';
 import { getMenuPathByCode } from '@/utils/menu';
+import { Spin } from 'antd';
 
 export const service = new Service('scene');
 
@@ -30,6 +31,8 @@ const Scene = () => {
   const [visible, setVisible] = useState<boolean>(false);
   const [current, setCurrent] = useState<Partial<SceneItem>>({});
   const history = useHistory();
+  const [loading, setLoading] = useState<boolean>(true);
+  const [title, setTitle] = useState<string>('确定删除?');
 
   const deleteById = async (id: string) => {
     // const alarmResp = await service.sceneByAlarm(id);
@@ -43,6 +46,13 @@ const Scene = () => {
     // }
   };
 
+  useEffect(() => {
+    console.log('----------', title, loading);
+    if (title) {
+      setLoading(false);
+    }
+  }, [title]);
+
   const Tools = (record: SceneItem): React.ReactNode[] => {
     return [
       <PermissionButton
@@ -147,8 +157,29 @@ const Scene = () => {
         style={{ padding: 0 }}
         isPermission={permission.delete}
         disabled={record.state.value === 'started'}
+        onClick={async () => {
+          const res = await service.sceneByAlarm(record.id);
+          if (res.status === 200) {
+            // setTimeout(()=>{})
+            setLoading(false);
+            if (res.result !== 0) {
+              setTitle('该场景已绑定告警,确定删除?');
+            } else {
+              setTitle('确定删除?');
+            }
+          } else {
+            setLoading(false);
+          }
+        }}
         popConfirm={{
-          title: '确认删除?',
+          title: <>{loading ? <Spin /> : title}</>,
+          okButtonProps: {
+            loading: loading,
+          },
+          onCancel: () => {
+            setTitle('');
+            setLoading(false);
+          },
           disabled: record.state.value === 'started',
           onConfirm: () => {
             deleteById(record.id);

+ 178 - 0
src/pages/system/Apply/Save/doc.tsx

@@ -0,0 +1,178 @@
+import './index.less';
+import { Image } from 'antd';
+
+interface Props {
+  type:
+    | 'internal-standalone'
+    | 'internal-integrated'
+    | 'dingtalk-ent-app'
+    | 'wechat-webapp'
+    | 'third-party';
+}
+
+const Doc = (props: Props) => {
+  const { type } = props;
+
+  const img1 = require('/public/images/apply/1.png');
+  const img2 = require('/public/images/apply/2.png');
+  const img3 = require('/public/images/apply/3.png');
+  const img4 = require('/public/images/apply/4.png');
+  const img5 = require('/public/images/apply/5.png');
+
+  return (
+    <div className="doc">
+      {type === 'internal-standalone' && (
+        <>
+          <h1>1.概述</h1>
+          <div>
+            内部独立应用适用于将<span>官方开发</span>的其他应用与<span>物联网平台相互集成</span>
+            ,例如将可视化平台集成至物联网平台,或者将物联网平台集成至可视化平台。以实现多处访问、集中管控的业务场景。
+          </div>
+          <div>
+            内部独立应用的<span>后端服务</span>相互<span>独立运行</span>,互不影响。
+          </div>
+          <div className="image">
+            <Image width="100%" src={img1} />
+          </div>
+
+          <h1>2.接入方式说明</h1>
+          <div>1、页面集成</div>
+          <div>
+            集成其他应用的<span>前端页面</span>至物联网平台中。为实现应用与物联网平台数据互联互通,
+            <span>通常还需要配置API服务</span>。
+          </div>
+          <div>2、API客户端</div>
+          <div>
+            <span>物联网平台</span>请求<span>其他应用</span>
+            的接口,以实现将物联网平台集成至其他应用系统。如需实现<span>其他应用</span>
+            登录后可以访问<span>物联网平台</span>页面,<span>还需要配置单点登录</span>。
+          </div>
+          <div>3、API服务</div>
+          <div>
+            <span>外部应用</span>请求<span>物联网平台</span>的接口,实现物联网平台的服务调用能力,
+            <span>通常还需要配置页面集成</span>。
+          </div>
+          <div>
+            配置API服务后,系统将<span>自动创建</span>对应的<span>“第三方应用”用户</span>。用户的
+            <span>账号/密码</span>分别对应appid/secureKey。
+          </div>
+          <div>
+            第三方用户<span>可调用的API服务</span>在其应用管理卡片的<span>其他-{'>'}赋权</span>
+            页面,进行<span>自定义配置</span>。
+          </div>
+          <div>4、单点登录</div>
+          <div>
+            通过<span>第三方平台账号</span>登录到物联网平台。
+          </div>
+        </>
+      )}
+      {type === 'internal-integrated' && (
+        <>
+          <h1>1.概述</h1>
+          <div>
+            内部集成应用适用于将<span>官方开发</span>的其他应用与<span>物联网平台相互集成</span>
+            ,例如将可视化平台集成至物联网平台,或者将物联网平台集成至可视化平台。以实现多处访问、集中管控的业务场景。
+          </div>
+          <div>
+            内部独立应用的<span>后端服务在同一个环境运行</span>。
+          </div>
+          <div className="image">
+            <Image width="100%" src={img2} />
+          </div>
+          <h1>2.接入方式说明</h1>
+          <div>1、页面集成</div>
+          <div>
+            集成其他应用的<span>前端页面</span>
+            至物联网平台中。集成后系统顶部将新增对应的应用管理菜单
+          </div>
+          <div>2、API客户端</div>
+          <div>
+            <span>物联网平台</span>去请求其他应用的接口,以实现将物联网平台集成至其他应用
+          </div>
+        </>
+      )}
+      {type === 'dingtalk-ent-app' && (
+        <>
+          <div className={'url'}>
+            钉钉开放平台:
+            <a href="https://open-dev.dingtalk.com" target="_blank" rel="noopener noreferrer">
+              https://open-dev.dingtalk.com
+            </a>
+          </div>
+          <h1>1.概述</h1>
+          <div>
+            钉钉企业内部应用适用于通过钉钉登录<span>物联网平台</span>
+          </div>
+          <div className="image">
+            <Image width="100%" src={img4} />
+          </div>
+          <h1>2.接入方式说明</h1>
+          <div>1、单点登录</div>
+          <div>通过钉钉账号登录到物联网平台。</div>
+        </>
+      )}
+      {type === 'wechat-webapp' && (
+        <>
+          <div className={'url'}>
+            微信开放平台:
+            <a href="https://open.weixin.qq.com" target="_blank" rel="noopener noreferrer">
+              https://open.weixin.qq.com
+            </a>
+          </div>
+          <h1>1.概述</h1>
+          <div>
+            微信网站应用适用于通过微信授权登录<span>物联网平台</span>
+          </div>
+          <div className="image">
+            <Image width="100%" src={img3} />
+          </div>
+          <h1>2.接入方式说明</h1>
+          <div>1、单点登录</div>
+          <div>通过微信账号登录到物联网平台。</div>
+        </>
+      )}
+      {type === 'third-party' && (
+        <>
+          <h1>1. 概述</h1>
+          <div>
+            第三方应用适用于<span>第三方应用</span>与<span>物联网平台相互集成</span>
+            。例如将公司业务管理系统集成至物联网平台,或者将物联网平台集成至业务管理系统。以实现多处访问、集中管控的业务场景。
+          </div>
+          <div className="image">
+            <Image width="100%" src={img5} />
+          </div>
+          <h1>2.接入方式说明</h1>
+          <div>1、页面集成</div>
+          <div>
+            集成其他应用的<span>前端页面</span>至物联网平台中。为实现应用与物联网平台数据互联互通,
+            <span>还需要配置API服务</span>。
+          </div>
+          <div>2、API客户端</div>
+          <div>
+            <span>物联网平台</span>请求<span>第三方应用</span>
+            的接口,以实现将物联网平台集成至其他应用。如需实现<span>第三方应用</span>登录后可以访问
+            <span>物联网平台</span>页面,<span>还需要配置单点登录</span>。
+          </div>
+          <div>3、API服务</div>
+          <div>
+            <span>第三方应用</span>通过API服务配置,请求物联网平台接口,实现<span>物联网平台</span>
+            的服务调用能力,<span>通常还需要配置页面集成</span>。
+          </div>
+          <div>
+            配置API服务后,系统将<span>自动创建</span>对应的<span>“第三方应用”用户</span>。用户的
+            <span>账号/密码</span>分别对应appid/secureKey。
+          </div>
+          <div>
+            第三方用户<span>可调用的API服务</span>在其应用管理卡片的<span>其他-{'>'}赋权</span>
+            页面,进行<span>自定义配置</span>。
+          </div>
+          <div>4、单点登录</div>
+          <div>
+            通过<span>第三方平台账号</span>登录到物联网平台。
+          </div>
+        </>
+      )}
+    </div>
+  );
+};
+export default Doc;

+ 135 - 107
src/pages/system/Apply/Save/index.tsx

@@ -18,7 +18,7 @@ import {
   ArrayTable,
 } from '@formily/antd';
 import { TreeSelect as ATreeSelect } from 'antd';
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useMemo, useRef, useState } from 'react';
 import { createSchemaField } from '@formily/react';
 import { createForm, Field, onFieldReact, onFieldValueChange, onFormInit } from '@formily/core';
 import { onlyMessage, randomString, testIP, useAsyncDataSource } from '@/utils/util';
@@ -32,6 +32,7 @@ import { getMenuPathByCode } from '@/utils/menu';
 import MenuPage from '../Menu';
 import _ from 'lodash';
 import { UploadImage } from '@/components';
+import Doc from './doc';
 
 const Save = () => {
   const location = useLocation();
@@ -44,6 +45,7 @@ const Save = () => {
   const [visible, setVisiable] = useState<boolean>(false);
   const [detail, setDetail] = useState<any>({});
   const accessRef = useRef<any>([]);
+  const [type, setType] = useState<any>('');
 
   const provider1 = require('/public/images/apply/provider1.png');
   const provider2 = require('/public/images/apply/provider2.png');
@@ -138,16 +140,6 @@ const Save = () => {
     },
   });
 
-  // const getProvidersAll = () => {
-  //   return service.getProvidersAll().then((res) => {
-  //     if (res.status === 200) {
-  //       return res.result.map((item: any) => ({
-  //         label: createImageLabel(providerType.get(item.provider), item.name),
-  //         value: item.provider,
-  //       }));
-  //     }
-  //   });
-  // };
   const getRole = () => service.queryRoleList();
   const getOrg = () => service.queryOrgList();
 
@@ -165,103 +157,112 @@ const Save = () => {
     );
   };
 
-  const form = createForm({
-    validateFirst: true,
-    effects() {
-      onFormInit(async (formInit) => {
-        if (!id) return;
-        const resp = await service.detail(id);
-        const integrationModes = resp.result.integrationModes.map((item: any) => item.value);
-        // setAccess(integrationModes)
-        accessRef.current = integrationModes;
-        formInit.setInitialValues({
-          ...resp.result,
-          integrationModes,
-          'apiServer.appId': id,
-        });
-      });
-      onFieldValueChange('provider', (field, form1) => {
-        const value = field.value;
-        // console.log(value);
-        if (field.modified) {
-          switch (value) {
-            case 'internal-standalone':
-              form1.setFieldState('integrationModes', (f1) => {
-                f1.value = [];
-                f1.dataSource = integrationModesList;
-              });
-              break;
-            case 'internal-integrated':
-              form1.setFieldState('integrationModes', (f2) => {
-                f2.value = [];
-                f2.dataSource = integrationModesList?.filter(
-                  (item) => item.value === 'apiClient' || item.value === 'page',
-                );
-              });
-              break;
-            case 'dingtalk-ent-app':
-              form1.setFieldState('integrationModes', (f3) => {
-                f3.value = ['ssoClient'];
-                f3.dataSource = integrationModesList?.filter((item) => item.value === 'ssoClient');
-              });
-              break;
-            case 'wechat-webapp':
-              form1.setFieldState('integrationModes', (f4) => {
-                f4.value = ['ssoClient'];
-                f4.dataSource = integrationModesList?.filter((item) => item.value === 'ssoClient');
+  const form = useMemo(
+    () =>
+      createForm({
+        validateFirst: true,
+        effects() {
+          onFormInit(async (formInit) => {
+            if (!id) return;
+            const resp = await service.detail(id);
+            const integrationModes = resp.result.integrationModes.map((item: any) => item.value);
+            // setAccess(integrationModes)
+            accessRef.current = integrationModes;
+            formInit.setInitialValues({
+              ...resp.result,
+              integrationModes,
+              'apiServer.appId': id,
+            });
+          });
+          onFieldValueChange('provider', (field, form1) => {
+            const value = field.value;
+            setType(value);
+            // console.log(value);
+            if (field.modified) {
+              switch (value) {
+                case 'internal-standalone':
+                  form1.setFieldState('integrationModes', (f1) => {
+                    f1.value = [];
+                    f1.dataSource = integrationModesList;
+                  });
+                  break;
+                case 'internal-integrated':
+                  form1.setFieldState('integrationModes', (f2) => {
+                    f2.value = [];
+                    f2.dataSource = integrationModesList?.filter(
+                      (item) => item.value === 'apiClient' || item.value === 'page',
+                    );
+                  });
+                  break;
+                case 'dingtalk-ent-app':
+                  form1.setFieldState('integrationModes', (f3) => {
+                    f3.value = ['ssoClient'];
+                    f3.dataSource = integrationModesList?.filter(
+                      (item) => item.value === 'ssoClient',
+                    );
+                  });
+                  break;
+                case 'wechat-webapp':
+                  form1.setFieldState('integrationModes', (f4) => {
+                    f4.value = ['ssoClient'];
+                    f4.dataSource = integrationModesList?.filter(
+                      (item) => item.value === 'ssoClient',
+                    );
+                  });
+                  break;
+                case 'third-party':
+                  form1.setFieldState('integrationModes', (f5) => {
+                    f5.value = [];
+                    f5.dataSource = integrationModesList;
+                  });
+                  break;
+                default:
+                  break;
+              }
+            }
+          });
+          onFieldValueChange('integrationModes', (field, form2) => {
+            const value = field.value;
+            formCollapse.activeKeys = field.value;
+            const modes = ['page', 'apiClient', 'apiServer', 'ssoClient'];
+            const items = modes.concat(field.value).filter((item) => !value?.includes(item)); //未被选中
+            // console.log(value);
+            items.forEach((i) => {
+              form2.setFieldState(`config.${i}`, (state) => {
+                state.visible = false;
               });
-              break;
-            case 'third-party':
-              form1.setFieldState('integrationModes', (f5) => {
-                f5.value = [];
-                f5.dataSource = integrationModesList;
+            });
+            field.value?.forEach((parms: any) => {
+              form2.setFieldState(`config.${parms}`, (state) => {
+                state.visible = true;
               });
-              break;
-            default:
-              break;
-          }
-        }
-      });
-      onFieldValueChange('integrationModes', (field, form2) => {
-        const value = field.value;
-        formCollapse.activeKeys = field.value;
-        const modes = ['page', 'apiClient', 'apiServer', 'ssoClient'];
-        const items = modes.concat(field.value).filter((item) => !value?.includes(item)); //未被选中
-        // console.log(value);
-        items.forEach((i) => {
-          form2.setFieldState(`config.${i}`, (state) => {
-            state.visible = false;
+            });
           });
-        });
-        field.value?.forEach((parms: any) => {
-          form2.setFieldState(`config.${parms}`, (state) => {
-            state.visible = true;
+          onFieldReact('apiClient.authConfig.oauth2.clientId', (field) => {
+            if (id && accessRef.current?.includes('apiClient')) {
+              field.componentProps = {
+                disabled: true,
+              };
+            }
           });
-        });
-      });
-      onFieldReact('apiClient.authConfig.oauth2.clientId', (field) => {
-        if (id && accessRef.current?.includes('apiClient')) {
-          field.componentProps = {
-            disabled: true,
-          };
-        }
-      });
-      onFieldReact('apiServer.ipWhiteList', (field: any) => {
-        const value = (field as Field).value;
-        if (value) {
-          const str = value?.split(/[\n,]/g).filter((i: any) => i && i.trim());
-          const NoIP = str.find((item: any) => !testIP(item.replace(/\s*/g, '')));
-          if (NoIP) {
-            field.selfErrors = `[${NoIP}]不是正确的IP地址`;
-          } else {
-            field.selfErrors = '';
-          }
-        } else {
-          field.selfErrors = '';
-        }
-      });
-    },
-  });
+          onFieldReact('apiServer.ipWhiteList', (field: any) => {
+            const value = (field as Field).value;
+            if (value) {
+              const str = value?.split(/[\n,]/g).filter((i: any) => i && i.trim());
+              const NoIP = str.find((item: any) => !testIP(item.replace(/\s*/g, '')));
+              if (NoIP) {
+                field.selfErrors = `[${NoIP}]不是正确的IP地址`;
+              } else {
+                field.selfErrors = '';
+              }
+            } else {
+              field.selfErrors = '';
+            }
+          });
+        },
+      }),
+    [id],
+  );
 
   const handleSave = async () => {
     const data: any = await form.submit();
@@ -340,6 +341,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: 'oauth2授权地址',
       },
       required: true,
       'x-component': 'Input',
@@ -363,6 +365,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '授权完成后跳转到具体页面的回调地址',
       },
       // required: true,
       'x-component': 'Input',
@@ -386,6 +389,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '应用的唯一标识',
       },
       required: true,
       'x-component': 'Input',
@@ -409,6 +413,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '应用的唯一标识的秘钥',
       },
       required: true,
       'x-component': 'Input',
@@ -434,6 +439,8 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip:
+          '开启后,第三方用户第一次授权登录系统时,无需进入授权绑定页面。系统默认创建一个新用户与之绑定。',
       },
       'x-component': 'Switch',
     },
@@ -448,6 +455,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '应用的唯一标识',
       },
       'x-reactions': {
         dependencies: ['provider'],
@@ -471,6 +479,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '应用的唯一标识',
       },
       required: true,
       'x-component': 'Input',
@@ -504,6 +513,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '应用的唯一标识的秘钥',
       },
       required: true,
       'x-component': 'Input',
@@ -531,6 +541,8 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip:
+          '开启后,第三方用户第一次授权登录系统时,无需进入授权绑定页面。系统默认创建一个新用户与之绑定。',
       },
       'x-component': 'Switch',
     },
@@ -561,6 +573,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '限制应用程序对用户账号的访问',
       },
       'x-component': 'Input',
       'x-component-props': {
@@ -586,6 +599,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '应用唯一标识',
       },
       'x-component': 'Input',
       'x-component-props': {
@@ -611,6 +625,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '应用唯一标识的秘钥',
       },
       'x-component': 'Input',
       'x-component-props': {
@@ -636,6 +651,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: 'oauth2授权地址',
       },
       'x-component': 'Input',
       'x-component-props': {
@@ -651,6 +667,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '设置token令牌的地址',
       },
       'x-component': 'Input',
       'x-component-props': {
@@ -793,6 +810,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '认证授权地址',
       },
       required: true,
       'x-component': 'Input',
@@ -808,6 +826,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '授权完成后跳转到具体页面的回调地址',
       },
       // required: true,
       'x-component': 'Input',
@@ -823,6 +842,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '第三方应用唯一标识',
       },
       required: true,
       'x-component': 'Input',
@@ -848,6 +868,7 @@ const Save = () => {
         gridSpan: 2,
         layout: 'vertical',
         labelAlign: 'left',
+        tooltip: '第三方应用唯一标识的密钥',
       },
       required: true,
       'x-component': 'Input',
@@ -998,6 +1019,7 @@ const Save = () => {
                     gridSpan: 2,
                     layout: 'vertical',
                     labelAlign: 'left',
+                    tooltip: '认证授权地址',
                   },
                   required: true,
                   'x-component': 'Input',
@@ -1044,6 +1066,7 @@ const Save = () => {
                     gridSpan: 2,
                     layout: 'vertical',
                     labelAlign: 'left',
+                    tooltip: '应用唯一标识',
                   },
                   required: true,
                   'x-component': 'Input',
@@ -1056,6 +1079,7 @@ const Save = () => {
                     gridSpan: 2,
                     layout: 'vertical',
                     labelAlign: 'left',
+                    tooltip: '应用唯一标识的秘钥',
                   },
                   required: true,
                   'x-component': 'Input',
@@ -1163,6 +1187,7 @@ const Save = () => {
                   gridSpan: 2,
                   layout: 'vertical',
                   labelAlign: 'left',
+                  tooltip: '第三方应用唯一标识',
                 },
                 required: true,
                 'x-component': 'Input',
@@ -1187,6 +1212,7 @@ const Save = () => {
                   gridSpan: 2,
                   layout: 'vertical',
                   labelAlign: 'left',
+                  tooltip: '第三方应用唯一标识匹配的秘钥',
                 },
                 required: true,
                 'x-component': 'Input',
@@ -1212,6 +1238,7 @@ const Save = () => {
                   gridSpan: 2,
                   layout: 'vertical',
                   labelAlign: 'left',
+                  tooltip: '授权知道后跳转到具体页面的回调地址',
                 },
                 // required: true,
                 'x-component': 'Input',
@@ -1244,7 +1271,7 @@ const Save = () => {
                   gridSpan: 2,
                   layout: 'vertical',
                   labelAlign: 'left',
-                  tooltip: '为API用户分配角色',
+                  tooltip: '为第三方应用用户分配角色,根据绑定的角色,进行系统菜单赋权',
                   addonAfter: (
                     <PermissionButton
                       type="link"
@@ -1292,7 +1319,7 @@ const Save = () => {
                   gridSpan: 2,
                   layout: 'vertical',
                   labelAlign: 'left',
-                  tooltip: '为API用户分组所属组织',
+                  tooltip: '为第三方应用用户分配所属组织,根据绑定的组织,进行数据隔离',
                   addonAfter: (
                     <PermissionButton
                       type="link"
@@ -1446,6 +1473,7 @@ const Save = () => {
                   gridSpan: 2,
                   layout: 'vertical',
                   labelAlign: 'left',
+                  tooltip: '根据不同应用的调用规范,自定义请求头内容',
                 },
                 items: {
                   type: 'object',
@@ -1947,7 +1975,7 @@ const Save = () => {
             </Form>
           </Col>
           <Col span={10} className={styles.apply}>
-            <div className={styles.doc}></div>
+            <Doc type={type} />
           </Col>
         </Row>
       </Card>

+ 6 - 6
src/pages/system/DataSource/Save/index.tsx

@@ -113,8 +113,8 @@ const Save = (props: Props) => {
             },
             'x-validator': [
               {
-                max: 64,
-                message: '最多可输入64个字符',
+                format: 'url',
+                message: '请输入正确的URL',
               },
               {
                 required: true,
@@ -145,8 +145,8 @@ const Save = (props: Props) => {
             },
             'x-validator': [
               {
-                max: 64,
-                message: '最多可输入64个字符',
+                format: 'url',
+                message: '请输入正确的管理地址',
               },
               {
                 required: true,
@@ -177,8 +177,8 @@ const Save = (props: Props) => {
             },
             'x-validator': [
               {
-                max: 64,
-                message: '最多可输入64个字符',
+                format: 'url',
+                message: '请输入正确的链接地址',
               },
               {
                 required: true,