Przeglądaj źródła

fix: 视频中心及应用管理

wzyyy 3 lat temu
rodzic
commit
edd5344faf

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


+ 20 - 8
src/pages/media/Cascade/Channel/index.tsx

@@ -26,6 +26,7 @@ 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 unbind = async (list: string[]) => {
     const resp = await service.unbindChannel(id, list);
@@ -47,23 +48,34 @@ const Channel = () => {
         <Input
           value={data}
           placeholder="请输入国标ID"
-          onChange={(e) => {
+          onChange={async (e) => {
             setData(e.target.value);
+            if (e.target.value) {
+              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>}
         <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个字符');

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

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

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

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

@@ -0,0 +1,139 @@
+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');
+
+  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>。
+          </div>
+          <div>3、API服务</div>
+          <div>
+            <span>外部应用</span>请求<span>物联网平台</span>的接口,以调用物联网平台的能力。
+            <span>通常还需要配置页面集成</span>。也可<span>不配置</span>页面集成,
+            <span>自行开发</span>前端页面来调用API服务。
+          </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>
+
+          <h1>3.配置说明</h1>
+          <div>1、页面集成</div>
+          <div>接入地址:</div>
+          <div>访问其他平台的地址,url地址+端口</div>
+
+          <div>2、API客户端</div>
+          <div>接入地址:</div>
+          <div>访问API服务的地址,url地址+端口</div>
+          <div>授权地址:</div>
+          <div>认证授权地址,url地址+端口</div>
+          <div>回调地址:</div>
+          <div>授权之后跳转到具体页面的回调地址,url地址+端口</div>
+          <div>请求头:</div>
+          <div>根据不同应用的调用规范,自定义请求头内容</div>
+
+          <div>3、API服务</div>
+          <div>appid:</div>
+          <div>调用API服务时所需的用户账号</div>
+          <div>secureKey:</div>
+          <div>调用API服务时所需的用户密码</div>
+          <div>回调地址:</div>
+          <div>授权之后跳转到具体页面的回调地址,url地址+端口</div>
+          <div>角色:</div>
+          <div>为API用户分配角色,根据绑定的角色,进行系统菜单赋权</div>
+          <div>组织:</div>
+          <div>为API用户分配所属组织,根据绑定的组织,进行数据隔离</div>
+
+          <div>4、单点登录</div>
+          <div>授权地址:</div>
+          <div>oauth2授权地址,url地址+端口</div>
+          <div>回调地址:</div>
+          <div>授权之后跳转到具体页面的回调地址,url地址+端口</div>
+          <div>appid:</div>
+          <div>应用唯一标识</div>
+          <div>appkey:</div>
+          <div>与应用匹配的唯一秘钥</div>
+          <div>自动创建用户:</div>
+          <div>
+            开启后,第三方用户第一次授权登录系统时,无需进入授权绑定页面。系统默认创建一个新用户与之绑定。
+          </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' && <>3</>}
+      {type === 'wechat-webapp' && <>4</>}
+      {type === 'third-party' && <>5</>}
+    </div>
+  );
+};
+export default Doc;

+ 111 - 105
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,8 @@ const Save = () => {
   const [visible, setVisiable] = useState<boolean>(false);
   const [detail, setDetail] = useState<any>({});
   const accessRef = useRef<any>([]);
+  const [type, setType] = useState<any>('');
+  const typeRef = useRef<any>('');
 
   const provider1 = require('/public/images/apply/provider1.png');
   const provider2 = require('/public/images/apply/provider2.png');
@@ -138,16 +141,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 +158,116 @@ 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],
+  );
+
+  useEffect(() => {
+    console.log('_________', typeRef.current);
+  }, [typeRef.current]);
 
   const handleSave = async () => {
     const data: any = await form.submit();
@@ -1947,7 +1953,7 @@ const Save = () => {
             </Form>
           </Col>
           <Col span={10} className={styles.apply}>
-            <div className={styles.doc}></div>
+            <Doc type={type} />
           </Col>
         </Row>
       </Card>