Przeglądaj źródła

feat(merge): merge sc

fix: 修改国标级联错误
Lind 3 lat temu
rodzic
commit
3c1abc8e8c

+ 87 - 0
src/components/SipSelectComponent/index.tsx

@@ -0,0 +1,87 @@
+import { Select } from 'antd';
+import { useEffect, useState } from 'react';
+
+interface SipSelectComponentProps {
+  onChange?: (data: any) => void;
+  value?: {
+    host?: string;
+    port?: number;
+  };
+  transport?: 'UDP' | 'TCP';
+  data: any[];
+}
+
+const SipSelectComponent = (props: SipSelectComponentProps) => {
+  const { value, onChange, transport } = props;
+  const [data, setData] = useState<{ host?: string; port?: number } | undefined>(value);
+  const [list, setList] = useState<any[]>([]);
+
+  useEffect(() => {
+    setData(value);
+  }, [value]);
+
+  useEffect(() => {
+    setData(undefined);
+  }, [transport]);
+
+  return (
+    <div style={{ display: 'flex', alignItems: 'center' }}>
+      <Select
+        showSearch
+        value={data?.host}
+        style={{ marginRight: 10 }}
+        placeholder="请选择IP地址"
+        optionFilterProp="children"
+        onChange={(e) => {
+          if (onChange) {
+            const item = {
+              port: undefined,
+              host: e,
+            };
+            setData(item);
+            onChange(item);
+            const dt: any = props.data.find((i) => i.host === e);
+            setList(dt?.ports[transport || ''] || []);
+          }
+        }}
+        filterOption={(input: string, option: any) =>
+          String(option?.children)?.toLowerCase()?.indexOf(String(input).toLowerCase()) >= 0
+        }
+      >
+        {(props.data || []).map((item: any) => (
+          <Select.Option key={item.host} value={item.host}>
+            {item.host}
+          </Select.Option>
+        ))}
+      </Select>
+      <Select
+        showSearch
+        style={{ maxWidth: 100 }}
+        value={data?.port}
+        placeholder="请选择端口"
+        optionFilterProp="children"
+        onChange={(e: number) => {
+          if (onChange) {
+            const item = {
+              ...data,
+              port: e,
+            };
+            setData(item);
+            onChange(item);
+          }
+        }}
+        filterOption={(input: string, option: any) =>
+          String(option?.children)?.toLowerCase()?.indexOf(String(input).toLowerCase()) >= 0
+        }
+      >
+        {list.map((item) => (
+          <Select.Option key={item} value={item}>
+            {item}
+          </Select.Option>
+        ))}
+      </Select>
+    </div>
+  );
+};
+
+export default SipSelectComponent;

+ 34 - 14
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -1,6 +1,7 @@
 import React, { useContext, useEffect, useState } from 'react';
 import { Form, Input, message, Pagination, Select, Table } from 'antd';
 import { service } from '@/pages/device/Instance';
+import { QuestionCircleOutlined } from '@ant-design/icons';
 
 const EditableContext: any = React.createContext(null);
 
@@ -58,7 +59,14 @@ const EditableCell = ({
   if (editable) {
     childNode = (
       <Form.Item style={{ margin: 0 }} name={dataIndex}>
-        <Select onChange={save}>
+        <Select
+          onChange={save}
+          showSearch
+          optionFilterProp="children"
+          filterOption={(input: string, option: any) =>
+            (option?.children || '').toLowerCase()?.indexOf(input.toLowerCase()) >= 0
+          }
+        >
           {list.map((item: any) => (
             <Select.Option key={item?.id} value={item?.id}>
               {item?.id}
@@ -231,19 +239,31 @@ const EditableTable = (props: Props) => {
 
   return (
     <div>
-      <Input.Search
-        placeholder="请输入物模型属性名"
-        allowClear
-        style={{ width: 300, marginBottom: 20 }}
-        onSearch={(e: string) => {
-          setValue(e);
-          handleSearch({
-            name: e,
-            pageIndex: 0,
-            pageSize: 10,
-          });
-        }}
-      />
+      <div style={{ display: 'flex', alignItems: 'center', marginBottom: 10 }}>
+        <Input.Search
+          placeholder="请输入物模型属性名"
+          allowClear
+          style={{ width: 300, marginRight: 10 }}
+          onSearch={(e: string) => {
+            setValue(e);
+            handleSearch({
+              name: e,
+              pageIndex: 0,
+              pageSize: 10,
+            });
+          }}
+        />
+        <div>
+          <div style={{ color: 'rgba(0, 0, 0, .65)' }}>
+            <QuestionCircleOutlined style={{ margin: 5 }} />
+            该设备已脱离产品物模型映射,修改产品物模型映射对该设备物模型映射无影响
+          </div>
+          <div style={{ color: 'rgba(0, 0, 0, .65)' }}>
+            <QuestionCircleOutlined style={{ margin: 5 }} />
+            设备会默认继承产品的物模型映射,修改设备物模型映射后将脱离产品物模型映射
+          </div>
+        </div>
+      </div>
       <Table
         components={components}
         rowClassName={() => 'editable-row'}

+ 14 - 10
src/pages/link/Protocol/index.tsx

@@ -23,9 +23,9 @@ const Protocol = () => {
   const modifyState = async (id: string, type: 'deploy' | 'un-deploy') => {
     const resp = await service.modifyState(id, type);
     if (resp.status === 200) {
-      message.success('插件发布成功!');
+      message.success('操作成功!');
     } else {
-      message.error('插件发布失败!');
+      message.error('操作失败!');
     }
     actionRef.current?.reload();
   };
@@ -141,14 +141,18 @@ const Protocol = () => {
                 defaultMessage: '确认删除?',
               })}
               onConfirm={async () => {
-                await service.remove(record.id);
-                message.success(
-                  intl.formatMessage({
-                    id: 'pages.data.option.success',
-                    defaultMessage: '操作成功!',
-                  }),
-                );
-                actionRef.current?.reload();
+                const resp: any = await service.remove(record.id);
+                if (resp.status === 200) {
+                  message.success(
+                    intl.formatMessage({
+                      id: 'pages.data.option.success',
+                      defaultMessage: '操作成功!',
+                    }),
+                  );
+                  actionRef.current?.reload();
+                } else {
+                  message.error(resp?.message || '操作失败');
+                }
               }}
             >
               <Tooltip

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

@@ -1,9 +1,9 @@
 import { service } from '@/pages/media/Cascade';
 import SearchComponent from '@/components/SearchComponent';
-import { DisconnectOutlined } from '@ant-design/icons';
+import { DisconnectOutlined, EditOutlined } from '@ant-design/icons';
 import { PageContainer } from '@ant-design/pro-layout';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Button, message, Popconfirm, Space, Tooltip } from 'antd';
+import { Button, Input, message, Popconfirm, Popover, Space, Tooltip } from 'antd';
 import { useRef, useState } from 'react';
 import ProTable from '@jetlinks/pro-table';
 import { useIntl, useLocation } from 'umi';
@@ -18,15 +18,46 @@ const Channel = () => {
   const [visible, setVisible] = useState<boolean>(false);
   const [selectedRowKey, setSelectedRowKey] = useState<string[]>([]);
   const id = location?.query?.id || '';
+  const [data, setData] = useState<string>('');
 
-  const unbind = async (data: string[]) => {
-    const resp = await service.unbindChannel(id, data);
+  const unbind = async (list: string[]) => {
+    const resp = await service.unbindChannel(id, list);
     if (resp.status === 200) {
       actionRef.current?.reload();
       message.success('操作成功!');
     }
   };
 
+  const content = (record: any) => {
+    return (
+      <div>
+        <Input
+          value={data}
+          placeholder="请输入国标ID"
+          onChange={(e) => {
+            setData(e.target.value);
+          }}
+        />
+        <Button
+          type="primary"
+          style={{ marginTop: 10, width: '100%' }}
+          onClick={async () => {
+            if (!!data) {
+              const resp: any = service.editBindInfo(record.id, { gbChannelId: data });
+              if (resp.status === 200) {
+                actionRef.current?.reload();
+              }
+            } else {
+              message.error('请输入国标ID');
+            }
+          }}
+        >
+          保存
+        </Button>
+      </div>
+    );
+  };
+
   const columns: ProColumns<any>[] = [
     {
       dataIndex: 'deviceName',
@@ -39,7 +70,22 @@ const Channel = () => {
     {
       dataIndex: 'channelId',
       title: '国标ID',
-      // render: (text: string) => <span>{text}<EditOutlined /></span>
+      tooltip: '国标级联有18位、20位两种格式。在当前页面修改不会修改视频设备-通道页面中的国标ID',
+      render: (text: any, record: any) => (
+        <span>
+          {text}
+          <Popover trigger="click" content={content(record)} title="编辑通道ID">
+            <a
+              style={{ marginLeft: 10 }}
+              onClick={() => {
+                setData('');
+              }}
+            >
+              <EditOutlined />
+            </a>
+          </Popover>
+        </span>
+      ),
     },
     {
       dataIndex: 'address',
@@ -91,11 +137,11 @@ const Channel = () => {
       <SearchComponent<any>
         field={columns}
         target="unbind-channel"
-        onSearch={(data) => {
+        onSearch={(params) => {
           actionRef.current?.reload();
           setParam({
             ...param,
-            terms: data?.terms ? [...data?.terms] : [],
+            terms: params?.terms ? [...params?.terms] : [],
           });
         }}
       />
@@ -149,7 +195,16 @@ const Channel = () => {
           >
             绑定通道
           </Button>,
-          <Button onClick={() => {}} key="unbind">
+          <Button
+            onClick={() => {
+              if (selectedRowKey.length > 0) {
+                unbind(selectedRowKey);
+              } else {
+                message.error('请先选择需要解绑的通道列表');
+              }
+            }}
+            key="unbind"
+          >
             批量解绑
           </Button>,
         ]}

+ 57 - 14
src/pages/media/Cascade/Save/index.tsx

@@ -15,6 +15,7 @@ import {
   Tooltip,
 } from 'antd';
 import SipComponent from '@/components/SipComponent';
+import SipSelectComponent from '@/components/SipSelectComponent';
 import { testIP } from '@/utils/util';
 import { useEffect, useState } from 'react';
 import { service } from '../index';
@@ -25,10 +26,14 @@ const Save = () => {
   const [form] = Form.useForm();
   const [clusters, setClusters] = useState<any[]>([]);
   const id = location?.query?.id || '';
+  const [list, setList] = useState<any[]>([]);
+  const [transport, setTransport] = useState<'UDP' | 'TCP'>('UDP');
 
   const checkSIP = (_: any, value: { host: string; port: number }) => {
-    if (!value || !value.host) {
-      return Promise.reject(new Error('请输入API HOST'));
+    if (!value) {
+      return Promise.resolve();
+    } else if (!value.host) {
+      return Promise.reject(new Error('请输入IP 地址'));
     } else if (value?.host && !testIP(value.host)) {
       return Promise.reject(new Error('请输入正确的IP地址'));
     } else if (!value?.port) {
@@ -38,13 +43,27 @@ const Save = () => {
     }
     return Promise.resolve();
   };
-
+  const checkLocalSIP = (_: any, value: { host: string; port: number }) => {
+    if (!value) {
+      return Promise.resolve();
+    } else if (!value.host) {
+      return Promise.reject(new Error('请选择IP地址'));
+    } else if (!value?.port) {
+      return Promise.reject(new Error('请选择端口'));
+    }
+    return Promise.resolve();
+  };
   useEffect(() => {
     service.queryClusters().then((resp) => {
       if (resp.status === 200) {
         setClusters(resp.result);
       }
     });
+    service.queryResources().then((resp) => {
+      if (resp.status === 200) {
+        setList(resp.result);
+      }
+    });
     if (!!id) {
       service.detail(id).then((resp) => {
         if (resp.status === 200) {
@@ -69,6 +88,13 @@ const Save = () => {
     }
   }, []);
 
+  const keepValidator = (_: any, value: any) => {
+    if ((!value && value !== 0) || (Number(value) >= 1 && Number(value) <= 10000)) {
+      return Promise.resolve();
+    }
+    return Promise.reject(new Error('请输入1~10000之间的数字'));
+  };
+
   return (
     <PageContainer>
       <Card>
@@ -80,6 +106,8 @@ const Save = () => {
             proxyStream: false,
             sipConfigs: {
               transport: 'UDP',
+              keepaliveInterval: 60,
+              registerInterval: 3000,
             },
           }}
           onFinish={async (values: any) => {
@@ -141,9 +169,9 @@ const Save = () => {
                   </span>
                 }
                 name={['sipConfigs', 'clusterNodeId']}
-                rules={[{ required: true, message: '请选择信令服务配置' }]}
+                rules={[{ required: true, message: '请选择集群节点' }]}
               >
-                <Select placeholder="请选择信令服务配置">
+                <Select placeholder="请选择集群节点">
                   {clusters.map((item) => (
                     <Select.Option key={item.id} value={item.id}>
                       {item.name}
@@ -174,9 +202,9 @@ const Save = () => {
               <Form.Item
                 label="上级SIP域"
                 name={['sipConfigs', 'domain']}
-                rules={[{ required: true, message: '请输入上级SIP域' }]}
+                rules={[{ required: true, message: '请输入上级平台SIP域' }]}
               >
-                <Input placeholder="请输入上级SIP域" />
+                <Input placeholder="请输入上级平台SIP域" />
               </Form.Item>
             </Col>
             <Col span={12}>
@@ -192,9 +220,9 @@ const Save = () => {
               <Form.Item
                 label="本地SIP ID"
                 name={['sipConfigs', 'localSipId']}
-                rules={[{ required: true, message: '请输入本地SIP ID' }]}
+                rules={[{ required: true, message: '请输入网关侧的SIP ID' }]}
               >
-                <Input placeholder="请输入本地SIP ID" />
+                <Input placeholder="网关侧的SIP ID" />
               </Form.Item>
             </Col>
             <Col span={12}>
@@ -203,7 +231,13 @@ const Save = () => {
                 name={['sipConfigs', 'transport']}
                 rules={[{ required: true, message: '请选择传输协议' }]}
               >
-                <Radio.Group optionType="button" buttonStyle="solid">
+                <Radio.Group
+                  optionType="button"
+                  buttonStyle="solid"
+                  onChange={(e) => {
+                    setTransport(e.target.value);
+                  }}
+                >
                   <Radio.Button value="UDP">UDP</Radio.Button>
                   <Radio.Button value="TCP">TCP</Radio.Button>
                 </Radio.Group>
@@ -220,9 +254,12 @@ const Save = () => {
                   </span>
                 }
                 name={['sipConfigs', 'local']}
-                rules={[{ required: true, message: '请输入SIP本地地址' }, { validator: checkSIP }]}
+                rules={[
+                  { required: true, message: '请输入SIP本地地址' },
+                  { validator: checkLocalSIP },
+                ]}
               >
-                <SipComponent />
+                <SipSelectComponent data={list} transport={transport} />
               </Form.Item>
             </Col>
             <Col span={12}>
@@ -274,7 +311,10 @@ const Save = () => {
               <Form.Item
                 label="心跳周期(秒)"
                 name={['sipConfigs', 'keepaliveInterval']}
-                rules={[{ required: true, message: '请输入心跳周期' }]}
+                rules={[
+                  { required: true, message: '请输入心跳周期' },
+                  { validator: keepValidator },
+                ]}
               >
                 <InputNumber placeholder="请输入心跳周期" style={{ width: '100%' }} />
               </Form.Item>
@@ -283,7 +323,10 @@ const Save = () => {
               <Form.Item
                 label="注册间隔(秒)"
                 name={['sipConfigs', 'registerInterval']}
-                rules={[{ required: true, message: '请输入注册间隔' }]}
+                rules={[
+                  { required: true, message: '请输入注册间隔' },
+                  { validator: keepValidator },
+                ]}
               >
                 <InputNumber placeholder="请输入注册间隔" style={{ width: '100%' }} />
               </Form.Item>

+ 2 - 2
src/pages/media/Cascade/index.tsx

@@ -68,7 +68,7 @@ const Cascade = () => {
         选择通道
       </Tooltip>
     </Button>,
-    <Button type={'link'} key={'share'}>
+    <Button type={'link'} key={'share'} disabled={record.status.value === 'disabled'}>
       <Popconfirm
         key={'share'}
         title="确认共享!"
@@ -259,7 +259,7 @@ const Cascade = () => {
           </Button>
         </Tooltip>,
         <Tooltip title={'共享'} key={'share'}>
-          <Button type="link" style={{ padding: 0 }}>
+          <Button type="link" style={{ padding: 0 }} disabled={record.status.value === 'disabled'}>
             <Popconfirm
               onConfirm={() => {
                 setVisible(true);

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

@@ -61,6 +61,17 @@ class Service extends BaseService<CascadeItem> {
       method: 'POST',
       data,
     });
+  // 编辑绑定信息
+  editBindInfo = (id: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/media/gb28181-cascade/binding/${id}`, {
+      method: 'PUT',
+      data,
+    });
+  //
+  queryResources = () =>
+    request(`/${SystemConst.API_BASE}/network/resources/alive/_all`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

+ 6 - 0
src/pages/media/Stream/Detail/index.tsx

@@ -169,6 +169,9 @@ const Detail = () => {
   }, [params.id]);
 
   const checkSIP = (_: any, value: { host: string; port: number }) => {
+    if (!value) {
+      return Promise.resolve();
+    }
     if (!value || !value.host) {
       return Promise.reject(new Error('请输入API HOST'));
     } else if (value?.host && !testIP(value.host)) {
@@ -194,6 +197,9 @@ const Detail = () => {
       dynamicRtpPortRange: number[];
     },
   ) => {
+    if (!value) {
+      return Promise.resolve();
+    }
     if (!value || !value.rtpIp) {
       return Promise.reject(new Error('请输入RTP IP'));
     } else if (value?.rtpIp && !testIP(value.rtpIp)) {