Explorar o código

feat(merge): merge sc

lind %!s(int64=3) %!d(string=hai) anos
pai
achega
2c09cb46a7
Modificáronse 36 ficheiros con 573 adicións e 451 borrados
  1. 9 4
      src/components/Player/index.tsx
  2. 4 2
      src/components/ProTableCard/index.less
  3. 46 40
      src/pages/device/Instance/Detail/Diagnose/Message/Dialog/index.less
  4. 34 22
      src/pages/device/Instance/Detail/Diagnose/Message/Dialog/index.tsx
  5. 29 4
      src/pages/device/Instance/Detail/Diagnose/Message/index.tsx
  6. 4 4
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx
  7. 14 6
      src/pages/device/Instance/index.tsx
  8. 8 3
      src/pages/device/Product/Detail/Access/index.tsx
  9. 1 3
      src/pages/device/Product/Detail/index.tsx
  10. 28 35
      src/pages/device/Product/index.tsx
  11. 49 16
      src/pages/link/AccessConfig/Detail/Media/index.tsx
  12. 8 13
      src/pages/link/AccessConfig/index.tsx
  13. 7 3
      src/pages/link/Protocol/index.tsx
  14. 58 33
      src/pages/media/Cascade/Channel/index.tsx
  15. 16 28
      src/pages/media/Cascade/index.tsx
  16. 7 1
      src/pages/media/Device/Playback/index.tsx
  17. 8 7
      src/pages/media/Stream/Detail/index.tsx
  18. 18 3
      src/pages/media/Stream/index.tsx
  19. 8 14
      src/pages/notice/Config/Detail/doc/AliyunSms.tsx
  20. 8 14
      src/pages/notice/Config/Detail/doc/AliyunVoice.tsx
  21. 9 15
      src/pages/notice/Config/Detail/doc/DingTalk.tsx
  22. 10 9
      src/pages/notice/Config/Detail/doc/DingTalkRebot.tsx
  23. 9 7
      src/pages/notice/Config/Detail/doc/Email.tsx
  24. 9 15
      src/pages/notice/Config/Detail/doc/WeixinApp.tsx
  25. 9 15
      src/pages/notice/Config/Detail/doc/WeixinCorp.tsx
  26. 33 0
      src/pages/notice/Config/Detail/doc/index.less
  27. 11 17
      src/pages/notice/Template/Detail/doc/AliyunSms.tsx
  28. 12 17
      src/pages/notice/Template/Detail/doc/AliyunVoice.tsx
  29. 12 17
      src/pages/notice/Template/Detail/doc/DingTalk.tsx
  30. 9 14
      src/pages/notice/Template/Detail/doc/DingTalkRebot.tsx
  31. 9 8
      src/pages/notice/Template/Detail/doc/Email.tsx
  32. 9 15
      src/pages/notice/Template/Detail/doc/WeixinApp.tsx
  33. 10 16
      src/pages/notice/Template/Detail/doc/WeixinCorp.tsx
  34. 33 0
      src/pages/notice/Template/Detail/doc/index.less
  35. 24 31
      src/pages/rule-engine/Instance/index.tsx
  36. 1 0
      src/pages/system/Permission/index.tsx

+ 9 - 4
src/components/Player/index.tsx

@@ -1,4 +1,4 @@
-import { useEffect, useRef } from 'react';
+import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
 import { isFunction } from 'lodash';
 import { isFunction } from 'lodash';
 
 
 export type PlayerProps = {
 export type PlayerProps = {
@@ -37,13 +37,12 @@ const EventsEnum = {
   snapInside: 'onSnapInside',
   snapInside: 'onSnapInside',
   customButtons: 'onCustomButtons',
   customButtons: 'onCustomButtons',
 };
 };
-export default (props: PlayerProps) => {
+const LivePlayer = forwardRef((props: PlayerProps, ref) => {
   const player = useRef<HTMLVideoElement>(null);
   const player = useRef<HTMLVideoElement>(null);
 
 
   useEffect(() => {
   useEffect(() => {
     return () => {
     return () => {
       // 销毁播放器
       // 销毁播放器
-      console.log('销毁', props);
       if ('onDestroy' in props && isFunction(props.onDestroy)) {
       if ('onDestroy' in props && isFunction(props.onDestroy)) {
         props.onDestroy();
         props.onDestroy();
       }
       }
@@ -63,6 +62,10 @@ export default (props: PlayerProps) => {
     }
     }
   };
   };
 
 
+  useImperativeHandle(ref, () => ({
+    ...player.current,
+  }));
+
   return (
   return (
     // @ts-ignore: Unreachable code error
     // @ts-ignore: Unreachable code error
     <live-player
     <live-player
@@ -84,4 +87,6 @@ export default (props: PlayerProps) => {
       video-url={props.url || ''}
       video-url={props.url || ''}
     />
     />
   );
   );
-};
+});
+
+export default LivePlayer;

+ 4 - 2
src/components/ProTableCard/index.less

@@ -112,10 +112,12 @@
         top: 0;
         top: 0;
         left: @card-content-padding-left + 10px;
         left: @card-content-padding-left + 10px;
         display: block;
         display: block;
-        width: 120px;
-        height: 4px;
+        width: 15%;
+        min-width: 64px;
+        height: 2px;
         background-image: url('/images/rectangle.png');
         background-image: url('/images/rectangle.png');
         background-repeat: no-repeat;
         background-repeat: no-repeat;
+        background-size: 100% 100%;
         content: ' ';
         content: ' ';
       }
       }
 
 

+ 46 - 40
src/pages/device/Instance/Detail/Diagnose/Message/Dialog/index.less

@@ -12,42 +12,47 @@
 
 
   .dialog-card {
   .dialog-card {
     display: flex;
     display: flex;
+    flex-direction: column;
     width: 60%;
     width: 60%;
     padding: 24px;
     padding: 24px;
     background-color: #fff;
     background-color: #fff;
 
 
-    .dialog-icon {
-      margin-right: 10px;
-      color: rgba(0, 0, 0, 0.75);
-      font-weight: 500;
-      font-size: 12px;
-    }
-
-    .dialog-box {
+    .dialog-list {
       display: flex;
       display: flex;
-      flex-direction: column;
-      width: 100%;
-
-      .dialog-header {
-        .dialog-title {
-          color: rgba(0, 0, 0, 0.75);
-          font-weight: 700;
-          font-size: 14px;
-        }
 
 
-        .dialog-time {
-          color: rgba(0, 0, 0, 0.65);
-          font-size: 12px;
-        }
+      .dialog-icon {
+        margin-right: 10px;
+        color: rgba(0, 0, 0, 0.75);
+        font-weight: 500;
+        font-size: 12px;
       }
       }
 
 
-      .dialog-editor {
+      .dialog-box {
+        display: flex;
+        flex-direction: column;
         width: 100%;
         width: 100%;
-        margin-top: 10px;
-        color: rgba(0, 0, 0, 0.75);
 
 
-        textarea::-webkit-scrollbar {
-          width: 5px !important;
+        .dialog-header {
+          .dialog-title {
+            color: rgba(0, 0, 0, 0.75);
+            font-weight: 700;
+            font-size: 14px;
+          }
+
+          .dialog-time {
+            color: rgba(0, 0, 0, 0.65);
+            font-size: 12px;
+          }
+        }
+
+        .dialog-editor {
+          width: 100%;
+          margin-top: 10px;
+          color: rgba(0, 0, 0, 0.75);
+
+          textarea::-webkit-scrollbar {
+            width: 5px !important;
+          }
         }
         }
       }
       }
     }
     }
@@ -57,26 +62,27 @@
 .dialog-active {
 .dialog-active {
   display: flex;
   display: flex;
   justify-content: flex-end;
   justify-content: flex-end;
-
   .dialog-card {
   .dialog-card {
     background-color: @primary-color;
     background-color: @primary-color;
 
 
-    .dialog-icon {
-      color: #fff;
-    }
+    .dialog-list {
+      .dialog-icon {
+        color: #fff;
+      }
 
 
-    .dialog-box {
-      .dialog-header {
-        .dialog-title,
-        .dialog-time {
-          color: #fff;
+      .dialog-box {
+        .dialog-header {
+          .dialog-title,
+          .dialog-time {
+            color: #fff;
+          }
         }
         }
-      }
 
 
-      .dialog-editor {
-        textarea {
-          color: #fff !important;
-          background-color: @primary-color !important;
+        .dialog-editor {
+          textarea {
+            color: #fff !important;
+            background-color: @primary-color !important;
+          }
         }
         }
       }
       }
     }
     }

+ 34 - 22
src/pages/device/Instance/Detail/Diagnose/Message/Dialog/index.tsx

@@ -26,33 +26,45 @@ const Dialog = (props: Props) => {
   statusColor.set('error', '#E50012');
   statusColor.set('error', '#E50012');
   statusColor.set('success', '#24B276');
   statusColor.set('success', '#24B276');
 
 
-  const [visible, setVisible] = useState<boolean>(false);
+  const [visible, setVisible] = useState<string[]>([]);
 
 
   return (
   return (
-    <div className={classNames('dialog-item', { 'dialog-active': !data.upstream })} key={data.key}>
+    <div className={classNames('dialog-item', { 'dialog-active': !data?.upstream })} key={data.key}>
       <div className="dialog-card">
       <div className="dialog-card">
-        <div
-          className="dialog-icon"
-          onClick={() => {
-            setVisible(!visible);
-          }}
-        >
-          {visible ? <DownOutlined /> : <RightOutlined />}
-        </div>
-        <div className="dialog-box">
-          <div className="dialog-header">
-            <div className="dialog-title">
-              <Badge color={statusColor.get(data.error ? 'error' : 'success')} />
-              {operationMap.get(data.operation) || data?.operation}
+        {data.list.map((item: any) => (
+          <div key={item.key} className="dialog-list">
+            <div
+              className="dialog-icon"
+              onClick={() => {
+                const index = visible.indexOf(item.key);
+                if (index === -1) {
+                  visible.push(item.key);
+                } else {
+                  visible.splice(index, 1);
+                }
+                setVisible([...visible]);
+              }}
+            >
+              {visible.includes(item.key) ? <DownOutlined /> : <RightOutlined />}
             </div>
             </div>
-            <div className="dialog-time">{moment(data.endTime).format('YYYY-MM-DD HH:mm:ss')}</div>
-          </div>
-          {visible && (
-            <div className="dialog-editor">
-              <Input.TextArea autoSize bordered={false} value={data?.detail} />
+            <div className="dialog-box">
+              <div className="dialog-header">
+                <div className="dialog-title">
+                  <Badge color={statusColor.get(item.error ? 'error' : 'success')} />
+                  {operationMap.get(item.operation) || item?.operation}
+                </div>
+                <div className="dialog-time">
+                  {moment(item.endTime).format('YYYY-MM-DD HH:mm:ss')}
+                </div>
+              </div>
+              {visible.includes(item.key) && (
+                <div className="dialog-editor">
+                  <Input.TextArea autoSize bordered={false} value={item?.detail} />
+                </div>
+              )}
             </div>
             </div>
-          )}
-        </div>
+          </div>
+        ))}
       </div>
       </div>
     </div>
     </div>
   );
   );

+ 29 - 4
src/pages/device/Instance/Detail/Diagnose/Message/index.tsx

@@ -60,10 +60,35 @@ const Message = (props: Props) => {
           });
           });
           setLogList([...logList]);
           setLogList([...logList]);
         } else {
         } else {
-          dialogList.push({
-            key: randomString(),
-            ...payload,
-          });
+          const t = dialogList.find(
+            (item) =>
+              item.traceId === payload.traceId &&
+              payload.downstream === item.downstream &&
+              payload.upstream === item.upstream,
+          );
+          if (t) {
+            dialogList.map((item) => {
+              if (item.key === payload.traceId) {
+                item.list.push({
+                  key: randomString(),
+                  ...payload,
+                });
+              }
+            });
+          } else {
+            dialogList.push({
+              key: randomString(),
+              traceId: payload.traceId,
+              downstream: payload.downstream,
+              upstream: payload.upstream,
+              list: [
+                {
+                  key: randomString(),
+                  ...payload,
+                },
+              ],
+            });
+          }
           setDialogList([...dialogList]);
           setDialogList([...dialogList]);
           Store.set('diagnose', dialogList);
           Store.set('diagnose', dialogList);
         }
         }

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

@@ -1,6 +1,6 @@
-import React, {useContext, useEffect, useState} from 'react';
-import {Form, Input, message, Pagination, Select, Table} from 'antd';
-import {service} from '@/pages/device/Instance';
+import React, { useContext, useEffect, useState } from 'react';
+import { Form, Input, message, Pagination, Select, Table } from 'antd';
+import { service } from '@/pages/device/Instance';
 import _ from 'lodash';
 import _ from 'lodash';
 
 
 const EditableContext: any = React.createContext(null);
 const EditableContext: any = React.createContext(null);
@@ -236,7 +236,7 @@ const EditableTable = (props: Props) => {
   const handleSearch = (params: any) => {
   const handleSearch = (params: any) => {
     if (params.name) {
     if (params.name) {
       const data = properties.filter((i: any) => {
       const data = properties.filter((i: any) => {
-        return i?.name.indexOf(params?.nmae) !== -1;
+        return i?.name.includes(params?.name);
       });
       });
       setDataSource({
       setDataSource({
         data: data.slice(
         data: data.slice(

+ 14 - 6
src/pages/device/Instance/index.tsx

@@ -12,6 +12,7 @@ import {
   ExportOutlined,
   ExportOutlined,
   EyeOutlined,
   EyeOutlined,
   ImportOutlined,
   ImportOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   PlusOutlined,
   StopOutlined,
   StopOutlined,
   SyncOutlined,
   SyncOutlined,
@@ -69,6 +70,8 @@ const Instance = () => {
   const [bindKeys, setBindKeys] = useState<any[]>([]);
   const [bindKeys, setBindKeys] = useState<any[]>([]);
   const history = useHistory<Record<string, string>>();
   const history = useHistory<Record<string, string>>();
   const { permission } = PermissionButton.usePermission('device/Instance');
   const { permission } = PermissionButton.usePermission('device/Instance');
+  const [jumpParams, setJumpParams] = useState<SearchTermsServer | undefined>(undefined);
+
   const intl = useIntl();
   const intl = useIntl();
   const location = useLocation();
   const location = useLocation();
 
 
@@ -81,9 +84,12 @@ const Instance = () => {
           value: location.state[key],
           value: location.state[key],
         });
         });
       });
       });
-      setSearchParams({
-        terms: _terms,
-      });
+      setJumpParams([
+        {
+          terms: _terms,
+          type: 'or',
+        },
+      ]);
     }
     }
   }, [location]);
   }, [location]);
 
 
@@ -141,7 +147,7 @@ const Instance = () => {
         }),
         }),
       }}
       }}
     >
     >
-      {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
+      {record.state.value !== 'notActive' ? <StopOutlined /> : <PlayCircleOutlined />}
     </PermissionButton>,
     </PermissionButton>,
     <PermissionButton
     <PermissionButton
       type={'link'}
       type={'link'}
@@ -196,7 +202,7 @@ const Instance = () => {
         id: 'pages.table.productName',
         id: 'pages.table.productName',
         defaultMessage: '产品名称',
         defaultMessage: '产品名称',
       }),
       }),
-      dataIndex: 'productName',
+      dataIndex: 'productId',
       width: 200,
       width: 200,
       ellipsis: true,
       ellipsis: true,
       valueType: 'select',
       valueType: 'select',
@@ -207,6 +213,7 @@ const Instance = () => {
         }
         }
         return [];
         return [];
       },
       },
+      render: (_, row) => row.productName,
       filterMultiple: true,
       filterMultiple: true,
     },
     },
     {
     {
@@ -485,6 +492,7 @@ const Instance = () => {
       <SearchComponent<DeviceInstance>
       <SearchComponent<DeviceInstance>
         field={columns}
         field={columns}
         target="device-instance"
         target="device-instance"
+        initParam={jumpParams}
         onSearch={(data) => {
         onSearch={(data) => {
           actionRef.current?.reset?.();
           actionRef.current?.reset?.();
           setSearchParams(data);
           setSearchParams(data);
@@ -600,7 +608,7 @@ const Instance = () => {
                   },
                   },
                 }}
                 }}
               >
               >
-                {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
+                {record.state.value !== 'notActive' ? <StopOutlined /> : <PlayCircleOutlined />}
                 {intl.formatMessage({
                 {intl.formatMessage({
                   id: `pages.data.option.${
                   id: `pages.data.option.${
                     record.state.value !== 'notActive' ? 'disabled' : 'enabled'
                     record.state.value !== 'notActive' ? 'disabled' : 'enabled'

+ 8 - 3
src/pages/device/Product/Detail/Access/index.tsx

@@ -26,7 +26,7 @@ const Access = () => {
   const [access, setAccess] = useState<any>();
   const [access, setAccess] = useState<any>();
   const [providers, setProviders] = useState<any[]>([]);
   const [providers, setProviders] = useState<any[]>([]);
   const [networkList, setNetworkList] = useState<any[]>([]);
   const [networkList, setNetworkList] = useState<any[]>([]);
-  const { permission } = usePermissions('device/Product');
+  const { permission } = usePermissions('link/AccessConfig');
 
 
   const MetworkTypeMapping = new Map();
   const MetworkTypeMapping = new Map();
   MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
   MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
@@ -350,13 +350,15 @@ const Access = () => {
               permission.add ? (
               permission.add ? (
                 <span>
                 <span>
                   请先
                   请先
-                  <a
+                  <Button
+                    type="link"
+                    disabled={!!(productModel.current?.count && productModel.current?.count > 0)}
                     onClick={() => {
                     onClick={() => {
                       setConfigVisible(true);
                       setConfigVisible(true);
                     }}
                     }}
                   >
                   >
                     选择
                     选择
-                  </a>
+                  </Button>
                   设备接入网关,用以提供设备接入能力
                   设备接入网关,用以提供设备接入能力
                 </span>
                 </span>
               ) : (
               ) : (
@@ -379,6 +381,9 @@ const Access = () => {
                         type="primary"
                         type="primary"
                         ghost
                         ghost
                         style={{ marginLeft: 20 }}
                         style={{ marginLeft: 20 }}
+                        disabled={
+                          !!(productModel.current?.count && productModel.current?.count > 0)
+                        }
                         onClick={() => {
                         onClick={() => {
                           setConfigVisible(true);
                           setConfigVisible(true);
                         }}
                         }}

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

@@ -187,9 +187,7 @@ const ProductDetail = observer(() => {
       onBack={() => history.goBack()}
       onBack={() => history.goBack()}
       extraContent={<Space size={24} />}
       extraContent={<Space size={24} />}
       onTabChange={(key) => {
       onTabChange={(key) => {
-        if (permission.update) {
-          setMode(key);
-        }
+        setMode(key);
       }}
       }}
       tabList={list}
       tabList={list}
       tabActiveKey={mode}
       tabActiveKey={mode}

+ 28 - 35
src/pages/device/Product/index.tsx

@@ -153,19 +153,18 @@ const Product = observer(() => {
           defaultMessage: '下载',
           defaultMessage: '下载',
         }),
         }),
       }}
       }}
+      onClick={async () => {
+        downloadObject(
+          record,
+          intl.formatMessage({
+            id: 'pages.device.product',
+            defaultMessage: '产品',
+          }),
+        );
+        message.success('操作成功');
+      }}
     >
     >
-      <DownloadOutlined
-        onClick={async () => {
-          downloadObject(
-            record,
-            intl.formatMessage({
-              id: 'pages.device.product',
-              defaultMessage: '产品',
-            }),
-          );
-          message.success('操作成功');
-        }}
-      />
+      <DownloadOutlined />
     </PermissionButton>,
     </PermissionButton>,
     <PermissionButton
     <PermissionButton
       popConfirm={{
       popConfirm={{
@@ -201,12 +200,9 @@ const Product = observer(() => {
           defaultMessage: '是否删除?',
           defaultMessage: '是否删除?',
         }),
         }),
         onConfirm: async () => {
         onConfirm: async () => {
-          if (record.state === 0) {
-            await deleteItem(record.id);
-          } else {
-            message.error('已发布的产品不能进行删除操作');
-          }
+          await deleteItem(record.id);
         },
         },
+        disabled: record.state === 1,
       }}
       }}
       tooltip={{
       tooltip={{
         title: intl.formatMessage({
         title: intl.formatMessage({
@@ -441,13 +437,14 @@ const Product = observer(() => {
                   data.state = 0;
                   data.state = 0;
                   if (Array.isArray(data)) {
                   if (Array.isArray(data)) {
                     message.error('请上传json格式文件');
                     message.error('请上传json格式文件');
-                    return;
+                    return false;
                   }
                   }
                   const res = await service.update(data);
                   const res = await service.update(data);
                   if (res.status === 200) {
                   if (res.status === 200) {
                     message.success('操作成功');
                     message.success('操作成功');
                     actionRef.current?.reload();
                     actionRef.current?.reload();
                   }
                   }
+                  return true;
                 } catch {
                 } catch {
                   message.error('请上传json格式文件');
                   message.error('请上传json格式文件');
                 }
                 }
@@ -499,19 +496,18 @@ const Product = observer(() => {
                 type={'link'}
                 type={'link'}
                 key={'download'}
                 key={'download'}
                 style={{ padding: 0 }}
                 style={{ padding: 0 }}
+                onClick={async () => {
+                  downloadObject(
+                    record,
+                    intl.formatMessage({
+                      id: 'pages.device.product',
+                      defaultMessage: '产品',
+                    }),
+                  );
+                  message.success('操作成功');
+                }}
               >
               >
-                <DownloadOutlined
-                  onClick={async () => {
-                    downloadObject(
-                      record,
-                      intl.formatMessage({
-                        id: 'pages.device.product',
-                        defaultMessage: '产品',
-                      }),
-                    );
-                    message.success('操作成功');
-                  }}
-                />
+                <DownloadOutlined />
                 {intl.formatMessage({
                 {intl.formatMessage({
                   id: 'pages.data.option.download',
                   id: 'pages.data.option.download',
                   defaultMessage: '下载',
                   defaultMessage: '下载',
@@ -560,12 +556,9 @@ const Product = observer(() => {
                     defaultMessage: '是否删除?',
                     defaultMessage: '是否删除?',
                   }),
                   }),
                   onConfirm: async () => {
                   onConfirm: async () => {
-                    if (record.state === 0) {
-                      await deleteItem(record.id);
-                    } else {
-                      message.error('已发布的产品不能进行删除操作');
-                    }
+                    await deleteItem(record.id);
                   },
                   },
+                  disabled: record.state === 1,
                 }}
                 }}
               >
               >
                 <DeleteOutlined />
                 <DeleteOutlined />

+ 49 - 16
src/pages/link/AccessConfig/Detail/Media/index.tsx

@@ -111,16 +111,26 @@ const Media = (props: Props) => {
 
 
     registerValidateRules({
     registerValidateRules({
       checkSIP(value: { host: string; port: number }) {
       checkSIP(value: { host: string; port: number }) {
-        if (Number(value.port) < 1 || Number(value.port) > 65535) {
+        if (!value.host) {
           return {
           return {
             type: 'error',
             type: 'error',
-            message: '端口请输入1~65535之间的正整数',
+            message: '请输入IP地址',
           };
           };
         } else if (!testIP(value.host)) {
         } else if (!testIP(value.host)) {
           return {
           return {
             type: 'error',
             type: 'error',
             message: '请输入正确的IP地址',
             message: '请输入正确的IP地址',
           };
           };
+        } else if (!value.port) {
+          return {
+            type: 'error',
+            message: '请输入端口',
+          };
+        } else if (Number(value.port) < 1 || Number(value.port) > 65535) {
+          return {
+            type: 'error',
+            message: '端口请输入1~65535之间的正整数',
+          };
         }
         }
         return true;
         return true;
       },
       },
@@ -156,10 +166,15 @@ const Media = (props: Props) => {
             layout: 'vertical',
             layout: 'vertical',
             tooltip: '绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0',
             tooltip: '绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0',
           },
           },
-          required: true,
-          'x-validator': {
-            checkSIP: true,
-          },
+          'x-validator': [
+            {
+              required: true,
+              message: '请输入SIP 地址',
+            },
+            {
+              checkSIP: true,
+            },
+          ],
         },
         },
         public: {
         public: {
           title: '公网 Host',
           title: '公网 Host',
@@ -173,9 +188,15 @@ const Media = (props: Props) => {
           type: 'number',
           type: 'number',
           'x-decorator': 'FormItem',
           'x-decorator': 'FormItem',
           'x-component': 'SipComponent',
           'x-component': 'SipComponent',
-          'x-validator': {
-            checkSIP: true,
-          },
+          'x-validator': [
+            {
+              required: true,
+              message: '请输入公网 Host',
+            },
+            {
+              checkSIP: true,
+            },
+          ],
         },
         },
       },
       },
     };
     };
@@ -224,7 +245,7 @@ const Media = (props: Props) => {
               'x-validator': [
               'x-validator': [
                 {
                 {
                   required: true,
                   required: true,
-                  message: 'SIP ID',
+                  message: '请输入SIP ID',
                 },
                 },
               ],
               ],
             },
             },
@@ -284,9 +305,15 @@ const Media = (props: Props) => {
                         labelAlign: 'left',
                         labelAlign: 'left',
                         layout: 'vertical',
                         layout: 'vertical',
                       },
                       },
-                      'x-validator': {
-                        checkSIP: true,
-                      },
+                      'x-validator': [
+                        {
+                          required: true,
+                          message: '请输入SIP 地址',
+                        },
+                        {
+                          checkSIP: true,
+                        },
+                      ],
                     },
                     },
                     public: {
                     public: {
                       title: '公网 Host',
                       title: '公网 Host',
@@ -298,9 +325,15 @@ const Media = (props: Props) => {
                         labelAlign: 'left',
                         labelAlign: 'left',
                         layout: 'vertical',
                         layout: 'vertical',
                       },
                       },
-                      'x-validator': {
-                        checkSIP: true,
-                      },
+                      'x-validator': [
+                        {
+                          required: true,
+                          message: '请输入公网 Host',
+                        },
+                        {
+                          checkSIP: true,
+                        },
+                      ],
                     },
                     },
                   },
                   },
                 },
                 },

+ 8 - 13
src/pages/link/AccessConfig/index.tsx

@@ -6,7 +6,7 @@ import { Card, Col, Empty, message, Pagination, Row } from 'antd';
 import { useEffect, useState } from 'react';
 import { useEffect, useState } from 'react';
 import { useHistory } from 'umi';
 import { useHistory } from 'umi';
 import Service from './service';
 import Service from './service';
-import { CheckCircleOutlined, DeleteOutlined, EditOutlined, StopOutlined } from '@ant-design/icons';
+import { DeleteOutlined, EditOutlined, PlayCircleOutlined, StopOutlined } from '@ant-design/icons';
 import AccessConfigCard from '@/components/ProTableCard/CardItems/AccessConfig';
 import AccessConfigCard from '@/components/ProTableCard/CardItems/AccessConfig';
 import { PermissionButton } from '@/components';
 import { PermissionButton } from '@/components';
 
 
@@ -143,22 +143,18 @@ const AccessConfig = () => {
                         title: item.state.value !== 'disabled' ? '禁用' : '启用',
                         title: item.state.value !== 'disabled' ? '禁用' : '启用',
                       }}
                       }}
                     >
                     >
-                      {item.state.value !== 'disabled' ? (
-                        <span>
-                          <StopOutlined />
-                          禁用
-                        </span>
-                      ) : (
-                        <span>
-                          <CheckCircleOutlined />
-                          启用
-                        </span>
-                      )}
+                      {item.state.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
+                      {item.state.value !== 'disabled' ? '禁用' : '启用'}
                     </PermissionButton>,
                     </PermissionButton>,
                     <PermissionButton
                     <PermissionButton
                       isPermission={permission.delete}
                       isPermission={permission.delete}
+                      disabled={item.state.value !== 'disabled'}
+                      tooltip={{
+                        title: item.state.value !== 'disabled' ? '请先禁用,再删除' : '',
+                      }}
                       popConfirm={{
                       popConfirm={{
                         title: '确认删除',
                         title: '确认删除',
+                        disabled: item.state.value !== 'disabled',
                         onConfirm: () => {
                         onConfirm: () => {
                           service.remove(item.id).then((resp: any) => {
                           service.remove(item.id).then((resp: any) => {
                             if (resp.status === 200) {
                             if (resp.status === 200) {
@@ -174,7 +170,6 @@ const AccessConfig = () => {
                       type="link"
                       type="link"
                     >
                     >
                       <DeleteOutlined />
                       <DeleteOutlined />
-                      删除
                     </PermissionButton>,
                     </PermissionButton>,
                   ]}
                   ]}
                 />
                 />

+ 7 - 3
src/pages/link/Protocol/index.tsx

@@ -4,9 +4,9 @@ import type { ProtocolItem } from '@/pages/link/Protocol/typings';
 import { Badge, message } from 'antd';
 import { Badge, message } from 'antd';
 import { useRef, useState } from 'react';
 import { useRef, useState } from 'react';
 import {
 import {
-  CheckCircleOutlined,
   DeleteOutlined,
   DeleteOutlined,
   EditOutlined,
   EditOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   PlusOutlined,
   StopOutlined,
   StopOutlined,
 } from '@ant-design/icons';
 } from '@ant-design/icons';
@@ -125,7 +125,7 @@ const Protocol = () => {
             },
             },
           }}
           }}
         >
         >
-          {record.state === 1 ? <StopOutlined /> : <CheckCircleOutlined />}
+          {record.state === 1 ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         </PermissionButton>,
         <PermissionButton
         <PermissionButton
           isPermission={permission.delete}
           isPermission={permission.delete}
@@ -136,6 +136,7 @@ const Protocol = () => {
           disabled={record.state === 1}
           disabled={record.state === 1}
           popConfirm={{
           popConfirm={{
             title: '确认删除',
             title: '确认删除',
+            disabled: record.state === 1,
             onConfirm: async () => {
             onConfirm: async () => {
               const resp: any = await service.remove(record.id);
               const resp: any = await service.remove(record.id);
               if (resp.status === 200) {
               if (resp.status === 200) {
@@ -228,6 +229,7 @@ const Protocol = () => {
                 }}
                 }}
               >
               >
                 <EditOutlined />
                 <EditOutlined />
+                编辑
               </PermissionButton>,
               </PermissionButton>,
               <PermissionButton
               <PermissionButton
                 isPermission={permission.action}
                 isPermission={permission.action}
@@ -248,7 +250,8 @@ const Protocol = () => {
                   },
                   },
                 }}
                 }}
               >
               >
-                {record.state === 1 ? <StopOutlined /> : <CheckCircleOutlined />}
+                {record.state === 1 ? <StopOutlined /> : <PlayCircleOutlined />}
+                {record.state === 1 ? '撤销' : '发布'}
               </PermissionButton>,
               </PermissionButton>,
               <PermissionButton
               <PermissionButton
                 isPermission={permission.delete}
                 isPermission={permission.delete}
@@ -258,6 +261,7 @@ const Protocol = () => {
                 disabled={record.state === 1}
                 disabled={record.state === 1}
                 popConfirm={{
                 popConfirm={{
                   title: '确认删除',
                   title: '确认删除',
+                  disabled: record.state === 1,
                   onConfirm: async () => {
                   onConfirm: async () => {
                     const resp: any = await service.remove(record.id);
                     const resp: any = await service.remove(record.id);
                     if (resp.status === 200) {
                     if (resp.status === 200) {

+ 58 - 33
src/pages/media/Cascade/Channel/index.tsx

@@ -1,14 +1,15 @@
 import { service } from '@/pages/media/Cascade';
 import { service } from '@/pages/media/Cascade';
 import SearchComponent from '@/components/SearchComponent';
 import SearchComponent from '@/components/SearchComponent';
-import { DisconnectOutlined, EditOutlined } from '@ant-design/icons';
+import { CloseOutlined, DisconnectOutlined, EditOutlined } from '@ant-design/icons';
 import { PageContainer } from '@ant-design/pro-layout';
 import { PageContainer } from '@ant-design/pro-layout';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { Button, Input, message, Popconfirm, Popover, Space, Tooltip } from 'antd';
+import { Button, Input, message, Popover, Space } from 'antd';
 import { useRef, useState } from 'react';
 import { useRef, useState } from 'react';
 import { useIntl, useLocation } from 'umi';
 import { useIntl, useLocation } from 'umi';
 import BindChannel from './BindChannel';
 import BindChannel from './BindChannel';
 import BadgeStatus, { StatusColorEnum } from '@/components/BadgeStatus';
 import BadgeStatus, { StatusColorEnum } from '@/components/BadgeStatus';
+import { PermissionButton } from '@/components';
 
 
 const Channel = () => {
 const Channel = () => {
   const location: any = useLocation();
   const location: any = useLocation();
@@ -20,6 +21,7 @@ const Channel = () => {
   const id = location?.query?.id || '';
   const id = location?.query?.id || '';
   const [data, setData] = useState<string>('');
   const [data, setData] = useState<string>('');
   const [popVisible, setPopvisible] = useState<string>('');
   const [popVisible, setPopvisible] = useState<string>('');
+  const { permission } = PermissionButton.usePermission('media/Cascade');
 
 
   const unbind = async (list: string[]) => {
   const unbind = async (list: string[]) => {
     const resp = await service.unbindChannel(id, list);
     const resp = await service.unbindChannel(id, list);
@@ -44,7 +46,9 @@ const Channel = () => {
           style={{ marginTop: 10, width: '100%' }}
           style={{ marginTop: 10, width: '100%' }}
           onClick={async () => {
           onClick={async () => {
             if (!!data) {
             if (!!data) {
-              const resp: any = await service.editBindInfo(record.id, { gbChannelId: data });
+              const resp: any = await service.editBindInfo(record.gbChannelId, {
+                gbChannelId: data,
+              });
               if (resp.status === 200) {
               if (resp.status === 200) {
                 message.success('操作成功');
                 message.success('操作成功');
                 actionRef.current?.reload();
                 actionRef.current?.reload();
@@ -78,16 +82,33 @@ const Channel = () => {
         <span>
         <span>
           {text}
           {text}
           <Popover
           <Popover
-            visible={popVisible === record.id}
+            visible={popVisible === record.gbChannelId}
             trigger="click"
             trigger="click"
             content={content(record)}
             content={content(record)}
-            title="编辑国标ID"
+            title={
+              <div
+                style={{
+                  width: '100%',
+                  display: 'flex',
+                  justifyContent: 'space-between',
+                  alignItems: 'center',
+                }}
+              >
+                <div>编辑国标ID</div>
+                <CloseOutlined
+                  onClick={() => {
+                    setPopvisible('');
+                    setData('');
+                  }}
+                />
+              </div>
+            }
           >
           >
             <a
             <a
               style={{ marginLeft: 10 }}
               style={{ marginLeft: 10 }}
               onClick={() => {
               onClick={() => {
                 setData('');
                 setData('');
-                setPopvisible(record.id);
+                setPopvisible(record.gbChannelId);
               }}
               }}
             >
             >
               <EditOutlined />
               <EditOutlined />
@@ -135,19 +156,19 @@ const Channel = () => {
       align: 'center',
       align: 'center',
       width: 200,
       width: 200,
       render: (text: any, record: any) => [
       render: (text: any, record: any) => [
-        <Popconfirm
-          key={'unbinds'}
-          title="确认解绑"
-          onConfirm={() => {
-            unbind([record.channelId]);
+        <PermissionButton
+          isPermission={permission.channel}
+          key={'unbind'}
+          type={'link'}
+          popConfirm={{
+            title: `确认解绑`,
+            onConfirm: () => {
+              unbind([record.channelId]);
+            },
           }}
           }}
         >
         >
-          <a>
-            <Tooltip title={'解绑'}>
-              <DisconnectOutlined />
-            </Tooltip>
-          </a>
-        </Popconfirm>,
+          <DisconnectOutlined />
+        </PermissionButton>,
       ],
       ],
     },
     },
   ];
   ];
@@ -206,29 +227,33 @@ const Channel = () => {
           </Space>
           </Space>
         )}
         )}
         toolBarRender={() => [
         toolBarRender={() => [
-          <Button
+          <PermissionButton
+            isPermission={permission.channel}
+            key={'bind'}
             onClick={() => {
             onClick={() => {
               setVisible(true);
               setVisible(true);
             }}
             }}
-            key="bind"
-            type="primary"
+            type={'primary'}
           >
           >
             绑定通道
             绑定通道
-          </Button>,
-          <Popconfirm
-            title={'确认解绑'}
-            onConfirm={() => {
-              if (selectedRowKey.length > 0) {
-                unbind(selectedRowKey);
-                setSelectedRowKey([]);
-              } else {
-                message.error('请先选择需要解绑的通道列表');
-              }
+          </PermissionButton>,
+          <PermissionButton
+            isPermission={permission.channel}
+            key={'unbind'}
+            popConfirm={{
+              title: `确认解绑`,
+              onConfirm: () => {
+                if (selectedRowKey.length > 0) {
+                  unbind(selectedRowKey);
+                  setSelectedRowKey([]);
+                } else {
+                  message.error('请先选择需要解绑的通道列表');
+                }
+              },
             }}
             }}
-            key="unbind"
           >
           >
-            <Button>批量解绑</Button>
-          </Popconfirm>,
+            批量解绑
+          </PermissionButton>,
         ]}
         ]}
       />
       />
       {visible && (
       {visible && (

+ 16 - 28
src/pages/media/Cascade/index.tsx

@@ -3,10 +3,10 @@ import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { Badge, message } from 'antd';
 import { Badge, message } from 'antd';
 import {
 import {
-  CheckCircleOutlined,
   DeleteOutlined,
   DeleteOutlined,
   EditOutlined,
   EditOutlined,
   LinkOutlined,
   LinkOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   PlusOutlined,
   ShareAltOutlined,
   ShareAltOutlined,
   StopOutlined,
   StopOutlined,
@@ -43,12 +43,6 @@ const Cascade = () => {
       }}
       }}
       type={'link'}
       type={'link'}
       style={{ padding: 0 }}
       style={{ padding: 0 }}
-      tooltip={{
-        title: intl.formatMessage({
-          id: 'pages.data.option.edit',
-          defaultMessage: '编辑',
-        }),
-      }}
     >
     >
       <EditOutlined />
       <EditOutlined />
       编辑
       编辑
@@ -62,9 +56,6 @@ const Cascade = () => {
       }}
       }}
       type={'link'}
       type={'link'}
       style={{ padding: 0 }}
       style={{ padding: 0 }}
-      tooltip={{
-        title: '选择通道',
-      }}
     >
     >
       <LinkOutlined />
       <LinkOutlined />
       选择通道
       选择通道
@@ -80,7 +71,7 @@ const Cascade = () => {
       style={{ padding: 0 }}
       style={{ padding: 0 }}
       disabled={record.status.value === 'disabled'}
       disabled={record.status.value === 'disabled'}
       tooltip={{
       tooltip={{
-        title: record.status.value === 'disabled' ? '禁用状态下不可推送' : '推送',
+        title: record.status.value === 'disabled' ? '禁用状态下不可推送' : '',
       }}
       }}
     >
     >
       <ShareAltOutlined />
       <ShareAltOutlined />
@@ -91,7 +82,7 @@ const Cascade = () => {
       key={'action'}
       key={'action'}
       style={{ padding: 0 }}
       style={{ padding: 0 }}
       popConfirm={{
       popConfirm={{
-        title: `确认${record.status.value === 'disabled' ? '禁用' : '启用'}`,
+        title: `确认${record.status.value !== 'disabled' ? '禁用' : '启用'}`,
         onConfirm: async () => {
         onConfirm: async () => {
           let resp: any = undefined;
           let resp: any = undefined;
           if (record.status.value === 'disabled') {
           if (record.status.value === 'disabled') {
@@ -106,26 +97,19 @@ const Cascade = () => {
         },
         },
       }}
       }}
       isPermission={permission.action}
       isPermission={permission.action}
-      tooltip={{
-        title: record.status.value === 'disabled' ? '禁用' : '启用',
-      }}
     >
     >
-      {record.status.value === 'disabled' ? (
-        <span>
-          <StopOutlined />
-          禁用
-        </span>
-      ) : (
-        <span>
-          <CheckCircleOutlined />
-          启用
-        </span>
-      )}
+      {record.status.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
+      {record.status.value !== 'disabled' ? '禁用' : '启用'}
     </PermissionButton>,
     </PermissionButton>,
     <PermissionButton
     <PermissionButton
       isPermission={permission.delete}
       isPermission={permission.delete}
+      disabled={record.status.value !== 'disabled'}
+      tooltip={{
+        title: record.status.value !== 'disabled' ? '请先禁用,再删除' : '',
+      }}
       popConfirm={{
       popConfirm={{
         title: '确认删除',
         title: '确认删除',
+        disabled: record.status.value !== 'disabled',
         onConfirm: async () => {
         onConfirm: async () => {
           const resp: any = await service.remove(record.id);
           const resp: any = await service.remove(record.id);
           if (resp.status === 200) {
           if (resp.status === 200) {
@@ -138,7 +122,6 @@ const Cascade = () => {
       type="link"
       type="link"
     >
     >
       <DeleteOutlined />
       <DeleteOutlined />
-      删除
     </PermissionButton>,
     </PermissionButton>,
   ];
   ];
 
 
@@ -293,12 +276,17 @@ const Cascade = () => {
             title: record.status.value !== 'disabled' ? '禁用' : '启用',
             title: record.status.value !== 'disabled' ? '禁用' : '启用',
           }}
           }}
         >
         >
-          {record.status.value !== 'disabled' ? <StopOutlined /> : <CheckCircleOutlined />}
+          {record.status.value !== 'disabled' ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         </PermissionButton>,
         <PermissionButton
         <PermissionButton
           isPermission={permission.delete}
           isPermission={permission.delete}
+          disabled={record.status.value !== 'disabled'}
+          tooltip={{
+            title: record.status.value !== 'disabled' ? '请先禁用,再删除' : '',
+          }}
           popConfirm={{
           popConfirm={{
             title: '确认删除',
             title: '确认删除',
+            disabled: record.status.value !== 'disabled',
             onConfirm: async () => {
             onConfirm: async () => {
               const resp: any = await service.remove(record.id);
               const resp: any = await service.remove(record.id);
               if (resp.status === 200) {
               if (resp.status === 200) {

+ 7 - 1
src/pages/media/Device/Playback/index.tsx

@@ -1,7 +1,7 @@
 // 回放
 // 回放
 import { PageContainer } from '@ant-design/pro-layout';
 import { PageContainer } from '@ant-design/pro-layout';
 import LivePlayer from '@/components/Player';
 import LivePlayer from '@/components/Player';
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
 import { Calendar, Empty, List, Select, Tooltip } from 'antd';
 import { Calendar, Empty, List, Select, Tooltip } from 'antd';
 import { useLocation } from 'umi';
 import { useLocation } from 'umi';
 import Service from './service';
 import Service from './service';
@@ -31,6 +31,7 @@ export default () => {
   const [cloudTime, setCloudTime] = useState<any>();
   const [cloudTime, setCloudTime] = useState<any>();
   const [playing, setPlaying] = useState(false);
   const [playing, setPlaying] = useState(false);
   const location = useLocation();
   const location = useLocation();
+  const player = useRef<any>();
 
 
   const param = new URLSearchParams(location.search);
   const param = new URLSearchParams(location.search);
   const deviceId = param.get('id');
   const deviceId = param.get('id');
@@ -186,6 +187,7 @@ export default () => {
             url={url}
             url={url}
             className={'playback-media'}
             className={'playback-media'}
             live={type === 'local'}
             live={type === 'local'}
+            ref={player}
             onPlay={() => {
             onPlay={() => {
               setPlaying(true);
               setPlaying(true);
             }}
             }}
@@ -297,6 +299,10 @@ export default () => {
                                   );
                                   );
                                 }
                                 }
                               } else {
                               } else {
+                                console.log(player.current);
+                                if (player.current.pause) {
+                                  player.current.pause();
+                                }
                                 setPlayTime(0);
                                 setPlayTime(0);
                               }
                               }
                             }}
                             }}

+ 8 - 7
src/pages/media/Stream/Detail/index.tsx

@@ -145,17 +145,17 @@ const Detail = () => {
         setProviders(resp.result);
         setProviders(resp.result);
       }
       }
     });
     });
-    if (params.id && params.id !== ':id') {
+    if (params.id) {
       service.detail(params.id).then((resp) => {
       service.detail(params.id).then((resp) => {
         if (resp.status === 200) {
         if (resp.status === 200) {
           StreamModel.current = resp.result;
           StreamModel.current = resp.result;
-          form.setFieldsValue({
+          const data = {
             name: StreamModel.current?.name,
             name: StreamModel.current?.name,
             provider: StreamModel.current?.provider,
             provider: StreamModel.current?.provider,
             secret: StreamModel.current?.configuration?.secret,
             secret: StreamModel.current?.configuration?.secret,
             api: {
             api: {
-              apiHost: StreamModel.current.configuration?.apiHost,
-              apiPort: StreamModel.current.configuration?.apiPort,
+              host: StreamModel.current.configuration?.apiHost,
+              port: StreamModel.current.configuration?.apiPort,
             },
             },
             rtp: {
             rtp: {
               rtpIp: StreamModel.current.configuration?.rtpIp,
               rtpIp: StreamModel.current.configuration?.rtpIp,
@@ -163,7 +163,8 @@ const Detail = () => {
               dynamicRtpPort: StreamModel.current.configuration?.dynamicRtpPort || false,
               dynamicRtpPort: StreamModel.current.configuration?.dynamicRtpPort || false,
               dynamicRtpPortRange: StreamModel.current.configuration?.dynamicRtpPortRange || [],
               dynamicRtpPortRange: StreamModel.current.configuration?.dynamicRtpPortRange || [],
             },
             },
-          });
+          };
+          form.setFieldsValue(data);
         }
         }
       });
       });
     }
     }
@@ -253,8 +254,8 @@ const Detail = () => {
               provider: values.provider,
               provider: values.provider,
               configuration: {
               configuration: {
                 secret: values?.secret,
                 secret: values?.secret,
-                apiHost: values.api?.apiHost,
-                apiPort: values.api?.apiPort,
+                apiHost: values.api?.host,
+                apiPort: values.api?.port,
                 rtpIp: values.rtp?.rtpIp,
                 rtpIp: values.rtp?.rtpIp,
                 rtpPort: values.rtp?.rtpPort,
                 rtpPort: values.rtp?.rtpPort,
                 dynamicRtpPort: values.rtp?.dynamicRtpPort,
                 dynamicRtpPort: values.rtp?.dynamicRtpPort,

+ 18 - 3
src/pages/media/Stream/index.tsx

@@ -115,7 +115,12 @@ const Stream = () => {
                               key="button"
                               key="button"
                               type="link"
                               type="link"
                             >
                             >
-                              <EditOutlined style={{ color: '#000000' }} />
+                              <EditOutlined
+                                style={{
+                                  color: permission.update ? '#000000' : 'rgba(0, 0, 0, .65)',
+                                  cursor: permission.update ? 'pointer' : 'not-allowed',
+                                }}
+                              />
                               <span>编辑</span>
                               <span>编辑</span>
                             </PermissionButton>
                             </PermissionButton>
                             <PermissionButton
                             <PermissionButton
@@ -134,8 +139,18 @@ const Stream = () => {
                               key="delete"
                               key="delete"
                               type="link"
                               type="link"
                             >
                             >
-                              <span className={styles.action}>
-                                <DeleteOutlined style={{ color: '#E50012' }} />
+                              <span
+                                className={styles.action}
+                                style={{
+                                  color: permission.update ? '#000000' : 'rgba(0, 0, 0, .65)',
+                                  cursor: permission.update ? 'pointer' : 'not-allowed',
+                                }}
+                              >
+                                <DeleteOutlined
+                                  style={{
+                                    color: permission.update ? '#E50012' : 'rgba(0, 0, 0, .65)',
+                                  }}
+                                />
                                 <span>删除</span>
                                 <span>删除</span>
                               </span>
                               </span>
                             </PermissionButton>
                             </PermissionButton>

+ 8 - 14
src/pages/notice/Config/Detail/doc/AliyunSms.tsx

@@ -1,37 +1,31 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const AliyunSms = () => {
 const AliyunSms = () => {
   const accessKey = require('/public/images/notice/doc/config/aliyun-sms-voice/AccesskeyIDSecret.jpg');
   const accessKey = require('/public/images/notice/doc/config/aliyun-sms-voice/AccesskeyIDSecret.jpg');
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className={'doc'}>
+      <div className={'url'}>
         阿里云管理控制台:
         阿里云管理控制台:
         <a href="https://home.console.aliyun.com">https://home.console.aliyun.com</a>
         <a href="https://home.console.aliyun.com">https://home.console.aliyun.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
       </div>
-      <b>2.通知配置说明</b>
+      <h1>2.通知配置说明</h1>
       <div>
       <div>
-        <div>1. RegionID</div>
+        <h2>1. RegionID</h2>
         <div>阿里云内部给每台机器设置的唯一编号。请根据购买的阿里云服务器地址进行填写。</div>
         <div>阿里云内部给每台机器设置的唯一编号。请根据购买的阿里云服务器地址进行填写。</div>
         <div>
         <div>
           阿里云地域和可用区对照表地址:https://help.aliyun.com/document_detail/40654.html?spm=a2c6h.13066369.0.0.54a174710O7rWH
           阿里云地域和可用区对照表地址:https://help.aliyun.com/document_detail/40654.html?spm=a2c6h.13066369.0.0.54a174710O7rWH
         </div>
         </div>
       </div>
       </div>
-      <b>2、AccesskeyID/Secret</b>
+      <h2>2. AccesskeyID/Secret</h2>
       <div>
       <div>
         <div>用于程序通知方式调用云服务费API的用户标识和秘钥</div>
         <div>用于程序通知方式调用云服务费API的用户标识和秘钥</div>
         <div>获取路径:“阿里云管理控制台”--“用户头像”--“”--“AccessKey管理”--“查看”</div>
         <div>获取路径:“阿里云管理控制台”--“用户头像”--“”--“AccessKey管理”--“查看”</div>
-        <div>
+        <div className={'image'}>
           <Image width="100%" src={accessKey} />
           <Image width="100%" src={accessKey} />
         </div>
         </div>
       </div>
       </div>

+ 8 - 14
src/pages/notice/Config/Detail/doc/AliyunVoice.tsx

@@ -1,38 +1,32 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const AliyunVoice = () => {
 const AliyunVoice = () => {
   const accessKey = require('/public/images/notice/doc/config/aliyun-sms-voice/AccesskeyIDSecret.jpg');
   const accessKey = require('/public/images/notice/doc/config/aliyun-sms-voice/AccesskeyIDSecret.jpg');
 
 
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className={'doc'}>
+      <div className={'url'}>
         阿里云管理控制台:
         阿里云管理控制台:
         <a href="https://home.console.aliyun.com">https://home.console.aliyun.com</a>
         <a href="https://home.console.aliyun.com">https://home.console.aliyun.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
       </div>
-      <b>2.通知配置说明</b>
+      <h1>2.通知配置说明</h1>
       <div>
       <div>
-        <div>1. RegionID</div>
+        <h2>1. RegionID</h2>
         <div>
         <div>
           阿里云内部给每台机器设置的唯一编号。请根据购买的阿里云服务器阿里云地域和可用区对照表地址:https://help.aliyun.com/document_detail/40654.html?spm=a2c6h.13066369.0.0.54a174710O7rWH获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”
           阿里云内部给每台机器设置的唯一编号。请根据购买的阿里云服务器阿里云地域和可用区对照表地址:https://help.aliyun.com/document_detail/40654.html?spm=a2c6h.13066369.0.0.54a174710O7rWH获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”
         </div>
         </div>
       </div>
       </div>
-      <b>2、AccesskeyID/Secret</b>
+      <h2>2、AccesskeyID/Secret</h2>
       <div>
       <div>
         <div>用于程序通知方式调用云服务费API的用户标识和秘钥公众号开发者身份的密码</div>
         <div>用于程序通知方式调用云服务费API的用户标识和秘钥公众号开发者身份的密码</div>
         <div>获取路径:“阿里云管理控制台”--“用户头像”--“”--“AccessKey管理”--“查看”</div>
         <div>获取路径:“阿里云管理控制台”--“用户头像”--“”--“AccessKey管理”--“查看”</div>
       </div>
       </div>
-      <div>
+      <div className={'image'}>
         <Image width="100%" src={accessKey} />
         <Image width="100%" src={accessKey} />
       </div>
       </div>
     </div>
     </div>

+ 9 - 15
src/pages/notice/Config/Detail/doc/DingTalk.tsx

@@ -1,40 +1,34 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const DingTalk = () => {
 const DingTalk = () => {
   const appKey = require('/public/images/notice/doc/config/dingTalk-message/01-AppKey.jpg');
   const appKey = require('/public/images/notice/doc/config/dingTalk-message/01-AppKey.jpg');
   const appSecret = require('/public/images/notice/doc/config/dingTalk-message/02-AppSecret.jpg');
   const appSecret = require('/public/images/notice/doc/config/dingTalk-message/02-AppSecret.jpg');
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className={'doc'}>
+      <div className={'url'}>
         钉钉管理后台:<a href="https://open-dev.dingtalk.com">https://open-dev.dingtalk.com</a>
         钉钉管理后台:<a href="https://open-dev.dingtalk.com">https://open-dev.dingtalk.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
       </div>
-      <b>2.通知配置说明</b>
+      <h1>2.通知配置说明</h1>
       <div>
       <div>
-        <div>1. AppKey</div>
+        <h2>1. AppKey</h2>
         <div>
         <div>
           企业内部应用的唯一身份标识。在钉钉开发者后台创建企业内部应用后,系统会自动生成一对AppKey和AppSecret。
           企业内部应用的唯一身份标识。在钉钉开发者后台创建企业内部应用后,系统会自动生成一对AppKey和AppSecret。
         </div>
         </div>
         <div>获取路径:“钉钉开放平台”--“应用开发”--“应用信息”</div>
         <div>获取路径:“钉钉开放平台”--“应用开发”--“应用信息”</div>
-        <div>
+        <div className={'image'}>
           <Image width="100%" src={appKey} />
           <Image width="100%" src={appKey} />
         </div>
         </div>
       </div>
       </div>
-      <b>2. AppSecret</b>
+      <h2>2. AppSecret</h2>
       <div>
       <div>
         <div>钉钉应用对应的调用密钥</div>
         <div>钉钉应用对应的调用密钥</div>
         <div>获取路径:“钉钉开放平台”--“应用开发”--“应用信息”</div>
         <div>获取路径:“钉钉开放平台”--“应用开发”--“应用信息”</div>
-        <div>
+        <div className={'image'}>
           <Image width="100%" src={appSecret} />
           <Image width="100%" src={appSecret} />
         </div>
         </div>
       </div>
       </div>

+ 10 - 9
src/pages/notice/Config/Detail/doc/DingTalkRebot.tsx

@@ -1,4 +1,5 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const DingTalkRebot = () => {
 const DingTalkRebot = () => {
   const groupSetting = require('/public/images/notice/doc/config/dingTalk-rebot/01-group-setting.jpg');
   const groupSetting = require('/public/images/notice/doc/config/dingTalk-rebot/01-group-setting.jpg');
@@ -7,25 +8,25 @@ const DingTalkRebot = () => {
 
 
   const webhook = require('/public/images/notice/doc/config/dingTalk-rebot/03-Webhook.jpg');
   const webhook = require('/public/images/notice/doc/config/dingTalk-rebot/03-Webhook.jpg');
   return (
   return (
-    <div>
-      <b>1. 概述</b>
+    <div className={'doc'}>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
       </div>
-      <b>2.通知配置说明</b>
+      <h1>2.通知配置说明</h1>
       <div>
       <div>
-        <div> 1. WebHook</div>
+        <h2> 1. WebHook</h2>
         <div>在钉钉群内每创建一个钉钉群自定义机器人都会产生唯一的WebHook地址。</div>
         <div>在钉钉群内每创建一个钉钉群自定义机器人都会产生唯一的WebHook地址。</div>
         <div>获取路径:“钉钉桌面客户端”--“群设置”--“智能群助手”--“机器人信息”</div>
         <div>获取路径:“钉钉桌面客户端”--“群设置”--“智能群助手”--“机器人信息”</div>
-        <div>
+        <div className={'image'}>
           <Image width="100%" src={rebot} />
           <Image width="100%" src={rebot} />
         </div>
         </div>
-        <div>1、登录钉钉桌面客户端,进入群设置</div>
-        <div>
+        <h2>1、登录钉钉桌面客户端,进入群设置</h2>
+        <div className={'image'}>
           <Image width="100%" src={groupSetting} />
           <Image width="100%" src={groupSetting} />
         </div>
         </div>
-        <div>2、点击智能群助手,查看机器人信息</div>
-        <div>
+        <h2>2、点击智能群助手,查看机器人信息</h2>
+        <div className={'image'}>
           <Image width="100%" src={webhook} />
           <Image width="100%" src={webhook} />
         </div>
         </div>
       </div>
       </div>

+ 9 - 7
src/pages/notice/Config/Detail/doc/Email.tsx

@@ -1,21 +1,23 @@
+import './index.less';
+
 const Email = () => {
 const Email = () => {
   return (
   return (
-    <div>
-      <b>1. 概述</b>
+    <div className={'doc'}>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
       </div>
-      <b>2.通知配置说明</b>
-      <div>1. 服务器地址</div>
+      <h1>2.通知配置说明</h1>
+      <h2>1. 服务器地址</h2>
       <div>下拉可选择国内常用的邮箱服务配置,也支持手动输入其他地址。</div>
       <div>下拉可选择国内常用的邮箱服务配置,也支持手动输入其他地址。</div>
       <div>
       <div>
         系统POP协议。POP允许电子邮件客户端下载服务器上的邮件,但是您在电子邮件客户端的操作(如:移动邮件、标记已读等),这是不会反馈到服务器上。
         系统POP协议。POP允许电子邮件客户端下载服务器上的邮件,但是您在电子邮件客户端的操作(如:移动邮件、标记已读等),这是不会反馈到服务器上。
       </div>
       </div>
-      <div>2、发件人</div>
+      <h2>2. 发件人</h2>
       <div>用于发送邮件时“发件人“信息的显示</div>
       <div>用于发送邮件时“发件人“信息的显示</div>
-      <div>3、用户名</div>
+      <h2>3. 用户名</h2>
       <div>用该账号进行发送邮件。</div>
       <div>用该账号进行发送邮件。</div>
-      <div>4、密码</div>
+      <h2>4. 密码</h2>
       <div>用与账号身份认证,认证通过后可通过该账号进行发送邮件。</div>
       <div>用与账号身份认证,认证通过后可通过该账号进行发送邮件。</div>
     </div>
     </div>
   );
   );

+ 9 - 15
src/pages/notice/Config/Detail/doc/WeixinApp.tsx

@@ -1,39 +1,33 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const WeixinApp = () => {
 const WeixinApp = () => {
   const appId = require('/public/images/notice/doc/config/weixin-official/01-AppID.jpg');
   const appId = require('/public/images/notice/doc/config/weixin-official/01-AppID.jpg');
   const appSecret = require('/public/images/notice/doc/config/weixin-official/02-AppSecret.jpg');
   const appSecret = require('/public/images/notice/doc/config/weixin-official/02-AppSecret.jpg');
 
 
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className={'doc'}>
+      <div className={'url'}>
         微信公众平台:<a href="https://mp.weixin.qq.com/">https://mp.weixin.qq.com/</a>
         微信公众平台:<a href="https://mp.weixin.qq.com/">https://mp.weixin.qq.com/</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
       </div>
-      <b>2.通知配置说明</b>
+      <h1>2.通知配置说明</h1>
       <div>
       <div>
-        <div>1. AppID</div>
+        <h2>1. AppID</h2>
         <div>微信服务号的唯一专属编号。</div>
         <div>微信服务号的唯一专属编号。</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
-        <div>
+        <div className={'image'}>
           <Image width="100%" src={appId} />
           <Image width="100%" src={appId} />
         </div>
         </div>
       </div>
       </div>
-      <b>2. AppSecret</b>
+      <h2>2. AppSecret</h2>
       <div>
       <div>
         <div>公众号开发者身份的密码</div>
         <div>公众号开发者身份的密码</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
-        <div>
+        <div className={'image'}>
           <Image width="100%" src={appSecret} />
           <Image width="100%" src={appSecret} />
         </div>
         </div>
       </div>
       </div>

+ 9 - 15
src/pages/notice/Config/Detail/doc/WeixinCorp.tsx

@@ -1,39 +1,33 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const WeixinCorp = () => {
 const WeixinCorp = () => {
   const corpId = require('/public/images/notice/doc/config/weixin-corp/01-corpId.jpg');
   const corpId = require('/public/images/notice/doc/config/weixin-corp/01-corpId.jpg');
   const corpSecret = require('/public/images/notice/doc/config/weixin-corp/02-corpSecret.jpg');
   const corpSecret = require('/public/images/notice/doc/config/weixin-corp/02-corpSecret.jpg');
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className={'doc'}>
+      <div className={'url'}>
         企业微信管理后台:<a href="https://work.weixin.qq.com">https://work.weixin.qq.com</a>
         企业微信管理后台:<a href="https://work.weixin.qq.com">https://work.weixin.qq.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
       </div>
-      <b>2.通知配置说明</b>
+      <h1>2.通知配置说明</h1>
       <div>
       <div>
-        <div>1. corpId</div>
+        <h2>1. corpId</h2>
         <div>企业号的唯一专属编号。</div>
         <div>企业号的唯一专属编号。</div>
         <div>获取路径:“企业微信”管理后台--“我的企业”--“企业ID”</div>
         <div>获取路径:“企业微信”管理后台--“我的企业”--“企业ID”</div>
-        <div>
+        <div className={'image'}>
           <Image width="100%" src={corpId} />
           <Image width="100%" src={corpId} />
         </div>
         </div>
       </div>
       </div>
 
 
-      <b>2. corpSecret</b>
+      <h2>2. corpSecret</h2>
       <div>
       <div>
         <div>应用的唯一secret,一个企业微信中可以有多个corpSecret</div>
         <div>应用的唯一secret,一个企业微信中可以有多个corpSecret</div>
         <div>获取路径:“企业微信”--“应用与小程序”--“自建应用”中获取</div>
         <div>获取路径:“企业微信”--“应用与小程序”--“自建应用”中获取</div>
-        <div>
+        <div className={'image'}>
           <Image width="100%" src={corpSecret} />
           <Image width="100%" src={corpSecret} />
         </div>
         </div>
       </div>
       </div>

+ 33 - 0
src/pages/notice/Config/Detail/doc/index.less

@@ -0,0 +1,33 @@
+.doc {
+  padding: 24px;
+  color: rgba(#000, 0.8);
+  font-size: 14px;
+  background-color: #fafafa;
+
+  .url {
+    padding: 8px 16px;
+    color: #2f54eb;
+    background-color: rgba(#a7bdf7, 0.2);
+  }
+
+  h1 {
+    margin: 16px 0;
+    color: rgba(#000, 0.85);
+    font-weight: bold;
+    font-size: 14px;
+
+    &:first-child {
+      margin-top: 0;
+    }
+  }
+
+  h2 {
+    margin: 6px 0;
+    color: rgba(0, 0, 0, 0.8);
+    font-size: 14px;
+  }
+
+  .image {
+    margin: 16px 0;
+  }
+}

+ 11 - 17
src/pages/notice/Template/Detail/doc/AliyunSms.tsx

@@ -1,36 +1,30 @@
+import './index.less';
+
 const AliyunSms = () => {
 const AliyunSms = () => {
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className="doc">
+      <div className="url">
         阿里云短信服务平台:
         阿里云短信服务平台:
         <a href="https://dysms.console.aliyun.com">https://dysms.console.aliyun.com</a>
         <a href="https://dysms.console.aliyun.com">https://dysms.console.aliyun.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         使用阿里云短信时需先在阿里云短信服务平台创建短信模板。
         使用阿里云短信时需先在阿里云短信服务平台创建短信模板。
       </div>
       </div>
-      <b>2.模板配置说明</b>
+      <h1>2.模板配置说明</h1>
 
 
       <div>
       <div>
-        <div> 1、绑定配置</div>
+        <h2> 1、绑定配置</h2>
         <div> 绑定通知配置</div>
         <div> 绑定通知配置</div>
-        <div> 2、模板</div>
+        <h2> 2、模板</h2>
         <div> 阿里云短信平台自定义的模板名称</div>
         <div> 阿里云短信平台自定义的模板名称</div>
-        <div> 3、收信人</div>
+        <h2> 3、收信人</h2>
         <div> 当前仅支持国内手机号,此处若不填,则在模板调试和配置告警通知时手动填写</div>
         <div> 当前仅支持国内手机号,此处若不填,则在模板调试和配置告警通知时手动填写</div>
-        <div> 4、签名</div>
+        <h2> 4、签名</h2>
         <div> 用于短信内容签名信息显示,需在阿里云短信进行配置。</div>
         <div> 用于短信内容签名信息显示,需在阿里云短信进行配置。</div>
-        <div> 5、变量属性</div>
+        <h2> 5、变量属性</h2>
         <div>
         <div>
-          {' '}
           阿里云短信模板可支持变量,当前阿里云的接口可获取模板内容,但不能自动提取其中的变量,所以需要在当前页面手动设置与阿里云短信模板中一样的变量,否则会导致发送异常。
           阿里云短信模板可支持变量,当前阿里云的接口可获取模板内容,但不能自动提取其中的变量,所以需要在当前页面手动设置与阿里云短信模板中一样的变量,否则会导致发送异常。
         </div>
         </div>
       </div>
       </div>

+ 12 - 17
src/pages/notice/Template/Detail/doc/AliyunVoice.tsx

@@ -1,35 +1,30 @@
+import './index.less';
+
 const AliyunVoice = () => {
 const AliyunVoice = () => {
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className="doc">
+      <div className="url">
         阿里云语音服务平台:
         阿里云语音服务平台:
         <a href="https://account.console.aliyun.com">https://account.console.aliyun.com</a>
         <a href="https://account.console.aliyun.com">https://account.console.aliyun.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         使用阿里云语音时需先在阿里云语音服务平台创建语音模板。
         使用阿里云语音时需先在阿里云语音服务平台创建语音模板。
       </div>
       </div>
-      <b>2.模板配置说明</b>
+      <h1>2.模板配置说明</h1>
       <div>
       <div>
-        <div>1、绑定配置</div>
+        <h2>1、绑定配置</h2>
         <div> 绑定通知配置</div>
         <div> 绑定通知配置</div>
-        <div> 2、模板ID</div>
+        <h2> 2、模板ID</h2>
         <div> 阿里云语音对每一条语音通知分配的唯一ID标识</div>
         <div> 阿里云语音对每一条语音通知分配的唯一ID标识</div>
-        <div> 3、被叫号码</div>
+        <h2> 3、被叫号码</h2>
         <div> 当前仅支持国内手机号,此处若不填,则在模板调试和配置告警通知时手动填写</div>
         <div> 当前仅支持国内手机号,此处若不填,则在模板调试和配置告警通知时手动填写</div>
-        <div> 4、被叫显号</div>
+        <h2> 4、被叫显号</h2>
         <div> 用户呼叫号码显示,必须是在阿里云购买的号码。</div>
         <div> 用户呼叫号码显示,必须是在阿里云购买的号码。</div>
-        <div> 5、播放次数</div>
+        <h2> 5、播放次数</h2>
         <div> 最多可播放3次</div>
         <div> 最多可播放3次</div>
-        <div> 6、变量属性</div>
+        <h2> 6、变量属性</h2>
         <div>
         <div>
           阿里云语音模板可支持变量,但当前阿里云未提供相关语音模板内容接口,所以需要在当前页面手动设置与阿里云模板中一样的变量,否则会导致发送异常。
           阿里云语音模板可支持变量,但当前阿里云未提供相关语音模板内容接口,所以需要在当前页面手动设置与阿里云模板中一样的变量,否则会导致发送异常。
         </div>
         </div>

+ 12 - 17
src/pages/notice/Template/Detail/doc/DingTalk.tsx

@@ -1,4 +1,5 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const DingTalk = () => {
 const DingTalk = () => {
   const agentId = require('/public/images/notice/doc/template/dingTalk-message/01-Agentid.jpg');
   const agentId = require('/public/images/notice/doc/template/dingTalk-message/01-Agentid.jpg');
@@ -6,42 +7,36 @@ const DingTalk = () => {
   const dept = require('/public/images/notice/doc/template/dingTalk-message/03-dept.jpg');
   const dept = require('/public/images/notice/doc/template/dingTalk-message/03-dept.jpg');
   const a = '{name}';
   const a = '{name}';
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className="doc">
+      <div className="url">
         钉钉开放平台:<a href="https://open-dev.dingtalk.com">https://open-dev.dingtalk.com</a>
         钉钉开放平台:<a href="https://open-dev.dingtalk.com">https://open-dev.dingtalk.com</a>
+        <br />
         钉钉管理后台:<a href="https://www.dingtalk.com">https://www.dingtalk.com</a>
         钉钉管理后台:<a href="https://www.dingtalk.com">https://www.dingtalk.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
       </div>
       </div>
-      <b> 2.模板配置说明</b>
-      <div> 1、绑定配置</div>
+      <h1> 2.模板配置说明</h1>
+      <h2> 1、绑定配置</h2>
       <div> 绑定通知配置</div>
       <div> 绑定通知配置</div>
-      <div> 2. Agentid</div>
+      <h2> 2. Agentid</h2>
       <div> 应用唯一标识</div>
       <div> 应用唯一标识</div>
-      <div>
+      <div className="image">
         <Image width="100%" src={agentId} />
         <Image width="100%" src={agentId} />
       </div>
       </div>
       <div> 获取路径:“钉钉开发平台”--“应用开发”--“查看应用”</div>
       <div> 获取路径:“钉钉开发平台”--“应用开发”--“查看应用”</div>
-      <div> 3. 收信人ID、收信部门ID</div>
+      <h2> 3. 收信人ID、收信部门ID</h2>
       <div>
       <div>
         接收通知的2种方式,2个字段若在此页面都没有填写,则在模板调试和配置告警通知时需要手动填写
         接收通知的2种方式,2个字段若在此页面都没有填写,则在模板调试和配置告警通知时需要手动填写
       </div>
       </div>
       <div> 收信人ID获取路径:“钉钉管理后台”--“通讯录”--“查看用户”</div>
       <div> 收信人ID获取路径:“钉钉管理后台”--“通讯录”--“查看用户”</div>
       <div> 收信部门ID获取路径:“钉钉管理后台”--“通讯录”--“编辑部门”</div>
       <div> 收信部门ID获取路径:“钉钉管理后台”--“通讯录”--“编辑部门”</div>
-      <div>
+      <div className="image">
         <Image width="100%" src={userId} />
         <Image width="100%" src={userId} />
         <Image width="100%" src={dept} />
         <Image width="100%" src={dept} />
       </div>
       </div>
-      <div> 4. 模板内容</div>
+      <h2> 4. 模板内容</h2>
       <div>
       <div>
         支持填写带变量的动态模板。变量填写规范示例:${a}
         支持填写带变量的动态模板。变量填写规范示例:${a}
         。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。
         。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。

+ 9 - 14
src/pages/notice/Template/Detail/doc/DingTalkRebot.tsx

@@ -1,28 +1,23 @@
+import './index.less';
+
 const DingTalkRebot = () => {
 const DingTalkRebot = () => {
   const b = '{name}';
   const b = '{name}';
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className="doc">
+      <div className="url">
         钉钉管理后台:<a href="https://www.dingtalk.com">https://www.dingtalk.com</a>
         钉钉管理后台:<a href="https://www.dingtalk.com">https://www.dingtalk.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
       </div>
       </div>
-      <b>2.模板配置说明</b>
+      <h1>2.模板配置说明</h1>
       <div>
       <div>
-        <div> 1、绑定配置</div>
+        <h2> 1、绑定配置</h2>
         <div> 绑定通知配置</div>
         <div> 绑定通知配置</div>
-        <div> 2、消息类型</div>
+        <h2> 2、消息类型</h2>
         <div> 目前支持text、markdown、link3种。</div>
         <div> 目前支持text、markdown、link3种。</div>
-        <div> 3. 模板内容</div>
+        <h2> 3. 模板内容</h2>
         <div>
         <div>
           支持填写带变量的动态模板。变量填写规范示例:${b}
           支持填写带变量的动态模板。变量填写规范示例:${b}
           。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。
           。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。

+ 9 - 8
src/pages/notice/Template/Detail/doc/Email.tsx

@@ -1,23 +1,24 @@
+import './index.less';
+
 const Email = () => {
 const Email = () => {
   const a = '{标题}';
   const a = '{标题}';
   const b = '{name}';
   const b = '{name}';
   return (
   return (
-    <div>
-      <b>1. 概述</b>
+    <div className="doc">
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         服务器地址支持自定义输入。
         服务器地址支持自定义输入。
       </div>
       </div>
-      <b>2.模板配置说明</b>
+      <h1>2.模板配置说明</h1>
       <div>
       <div>
-        <div> 1. 服务器地址</div>
+        <h2> 1. 服务器地址</h2>
         <div>服务器地址支持自定义输入</div>
         <div>服务器地址支持自定义输入</div>
-        <div> 2. 标题</div>
+        <h2> 2. 标题</h2>
         <div>支持输入变量,变量格式${a}</div>
         <div>支持输入变量,变量格式${a}</div>
-
-        <div> 2. 收件人</div>
+        <h2> 3. 收件人</h2>
         <div> 支持录入多个邮箱地址,可填写变量参数。</div>
         <div> 支持录入多个邮箱地址,可填写变量参数。</div>
-        <div> 3. 模板内容</div>
+        <h2> 4. 模板内容</h2>
         <div>
         <div>
           支持填写带变量的动态模板。变量填写规范示例:${b}
           支持填写带变量的动态模板。变量填写规范示例:${b}
           。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。
           。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。

+ 9 - 15
src/pages/notice/Template/Detail/doc/WeixinApp.tsx

@@ -1,39 +1,33 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const WeixinApp = () => {
 const WeixinApp = () => {
   const agentId = require('/public/images/notice/doc/template/weixin-official/01-Agentid.jpg');
   const agentId = require('/public/images/notice/doc/template/weixin-official/01-Agentid.jpg');
   const appId = require('/public/images/notice/doc/template/weixin-official/02-mini-Program-Appid.jpg');
   const appId = require('/public/images/notice/doc/template/weixin-official/02-mini-Program-Appid.jpg');
 
 
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className="doc">
+      <div className="url">
         微信公众平台:<a href="https://mp.weixin.qq.com/">https://mp.weixin.qq.com/</a>
         微信公众平台:<a href="https://mp.weixin.qq.com/">https://mp.weixin.qq.com/</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
       </div>
-      <b>2.通知配置说明</b>
+      <h1>2.通知配置说明</h1>
       <div>
       <div>
-        <div>1. AppID</div>
+        <h2>1. AppID</h2>
         <div>微信服务号的唯一专属编号。</div>
         <div>微信服务号的唯一专属编号。</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
-        <div>
+        <div className="image">
           <Image width="100%" src={agentId} />
           <Image width="100%" src={agentId} />
         </div>
         </div>
       </div>
       </div>
-      <b>2. AppSecret</b>
+      <h2>2. AppSecret</h2>
       <div>
       <div>
         <div>公众号开发者身份的密码</div>
         <div>公众号开发者身份的密码</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
-        <div>
+        <div className="image">
           <Image width="100%" src={appId} />
           <Image width="100%" src={appId} />
         </div>
         </div>
       </div>
       </div>

+ 10 - 16
src/pages/notice/Template/Detail/doc/WeixinCorp.tsx

@@ -1,4 +1,5 @@
 import { Image } from 'antd';
 import { Image } from 'antd';
+import './index.less';
 
 
 const WeixinCorp = () => {
 const WeixinCorp = () => {
   const agentId = require('/public/images/notice/doc/template/weixin-corp/01-Agentid.jpg');
   const agentId = require('/public/images/notice/doc/template/weixin-corp/01-Agentid.jpg');
@@ -7,38 +8,31 @@ const WeixinCorp = () => {
   const toTags = require('/public/images/notice/doc/template/weixin-corp/04-toTags.jpg');
   const toTags = require('/public/images/notice/doc/template/weixin-corp/04-toTags.jpg');
 
 
   return (
   return (
-    <div>
-      <div
-        style={{
-          backgroundColor: '#e9eaeb',
-          height: '30px',
-          display: 'flex',
-          alignItems: 'center',
-        }}
-      >
+    <div className="doc">
+      <div className="url">
         企业微信管理后台:<a href="https://work.weixin.qq.com">https://work.weixin.qq.com</a>
         企业微信管理后台:<a href="https://work.weixin.qq.com">https://work.weixin.qq.com</a>
       </div>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
       </div>
       </div>
-      <b>2.模版配置说明</b>
+      <h1>2.模版配置说明</h1>
       <div>
       <div>
-        <div> 1、绑定配置</div>
+        <h2> 1、绑定配置</h2>
         <div> 绑定通知配置</div>
         <div> 绑定通知配置</div>
-        <div> 2. Agentid</div>
+        <h2> 2. Agentid</h2>
         <div> 应用唯一标识</div>
         <div> 应用唯一标识</div>
         <div> 获取路径:“企业微信”管理后台--“应用管理”--“应用”--“查看应用”</div>
         <div> 获取路径:“企业微信”管理后台--“应用管理”--“应用”--“查看应用”</div>
-        <div>
+        <div className="image">
           <Image width="100%" src={agentId} />
           <Image width="100%" src={agentId} />
         </div>
         </div>
-        <div> 3. 收信人ID、收信部门ID、标签推送</div>
+        <h2> 3. 收信人ID、收信部门ID、标签推送</h2>
         <div>
         <div>
           接收通知的3种方式,3个字段若在此页面都没有填写,则在模板调试和配置告警通知时需要手动填写
           接收通知的3种方式,3个字段若在此页面都没有填写,则在模板调试和配置告警通知时需要手动填写
         </div>
         </div>
         <div> 收信人ID获取路径:【通讯录】-{'>'}【成员信息】查看成员账号</div>
         <div> 收信人ID获取路径:【通讯录】-{'>'}【成员信息】查看成员账号</div>
         <div> 收信部门ID获取路径:【通讯录】-{'>'}【部门信息】查看部门ID</div>
         <div> 收信部门ID获取路径:【通讯录】-{'>'}【部门信息】查看部门ID</div>
-        <div>
+        <div className="image">
           <Image width="100%" src={userId} />
           <Image width="100%" src={userId} />
           <Image width="100%" src={toDept} />
           <Image width="100%" src={toDept} />
           <Image width="100%" src={toTags} />
           <Image width="100%" src={toTags} />

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

@@ -0,0 +1,33 @@
+.doc {
+  padding: 24px;
+  color: rgba(#000, 0.8);
+  font-size: 14px;
+  background-color: #fafafa;
+
+  .url {
+    padding: 8px 16px;
+    color: #2f54eb;
+    background-color: rgba(#a7bdf7, 0.2);
+  }
+
+  h1 {
+    margin: 16px 0;
+    color: rgba(#000, 0.85);
+    font-weight: bold;
+    font-size: 14px;
+
+    &:first-child {
+      margin-top: 0;
+    }
+  }
+
+  h2 {
+    margin: 6px 0;
+    color: rgba(0, 0, 0, 0.8);
+    font-size: 14px;
+  }
+
+  .image {
+    margin: 16px 0;
+  }
+}

+ 24 - 31
src/pages/rule-engine/Instance/index.tsx

@@ -4,10 +4,10 @@ import type { InstanceItem } from '@/pages/rule-engine/Instance/typings';
 import { useRef, useState } from 'react';
 import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import {
 import {
-  CheckCircleOutlined,
   DeleteOutlined,
   DeleteOutlined,
   EditOutlined,
   EditOutlined,
   EyeOutlined,
   EyeOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   PlusOutlined,
   StopOutlined,
   StopOutlined,
 } from '@ant-design/icons';
 } from '@ant-design/icons';
@@ -40,12 +40,6 @@ const Instance = () => {
       }}
       }}
       type={'link'}
       type={'link'}
       style={{ padding: 0 }}
       style={{ padding: 0 }}
-      tooltip={{
-        title: intl.formatMessage({
-          id: 'pages.data.option.edit',
-          defaultMessage: '编辑',
-        }),
-      }}
     >
     >
       <EditOutlined />
       <EditOutlined />
       编辑
       编辑
@@ -72,26 +66,19 @@ const Instance = () => {
         },
         },
       }}
       }}
       isPermission={permission.action}
       isPermission={permission.action}
-      tooltip={{
-        title: record.state.value !== 'disable' ? '禁用' : '启用',
-      }}
     >
     >
-      {record.state.value !== 'disable' ? (
-        <span>
-          <StopOutlined />
-          禁用
-        </span>
-      ) : (
-        <span>
-          <CheckCircleOutlined />
-          启用
-        </span>
-      )}
+      {record.state.value !== 'disable' ? <StopOutlined /> : <PlayCircleOutlined />}
+      {record.state.value !== 'disable' ? '禁用' : '启用'}
     </PermissionButton>,
     </PermissionButton>,
     <PermissionButton
     <PermissionButton
       isPermission={permission.delete}
       isPermission={permission.delete}
+      disabled={record.state.value !== 'disable'}
+      tooltip={{
+        title: record.state.value !== 'disable' ? '请先禁用,再删除' : '',
+      }}
       popConfirm={{
       popConfirm={{
         title: '确认删除',
         title: '确认删除',
+        disabled: record.state.value !== 'disable',
         onConfirm: async () => {
         onConfirm: async () => {
           if (record.state.value === 'disable') {
           if (record.state.value === 'disable') {
             await service.remove(record.id);
             await service.remove(record.id);
@@ -111,7 +98,6 @@ const Instance = () => {
       type="link"
       type="link"
     >
     >
       <DeleteOutlined />
       <DeleteOutlined />
-      删除
     </PermissionButton>,
     </PermissionButton>,
   ];
   ];
 
 
@@ -224,23 +210,30 @@ const Instance = () => {
             title: record.state.value !== 'disable' ? '禁用' : '启用',
             title: record.state.value !== 'disable' ? '禁用' : '启用',
           }}
           }}
         >
         >
-          {record.state.value !== 'disable' ? <StopOutlined /> : <CheckCircleOutlined />}
+          {record.state.value !== 'disable' ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         </PermissionButton>,
         <PermissionButton
         <PermissionButton
           isPermission={permission.delete}
           isPermission={permission.delete}
           style={{ padding: 0 }}
           style={{ padding: 0 }}
+          disabled={record.state.value !== 'disable'}
+          tooltip={{
+            title: record.state.value !== 'disable' ? '请先禁用,再删除' : '',
+          }}
           popConfirm={{
           popConfirm={{
             title: '确认删除',
             title: '确认删除',
+            disabled: record.state.value !== 'disable',
             onConfirm: async () => {
             onConfirm: async () => {
               if (record.state.value === 'disable') {
               if (record.state.value === 'disable') {
-                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 {
               } else {
                 message.error('未停止不能删除');
                 message.error('未停止不能删除');
               }
               }

+ 1 - 0
src/pages/system/Permission/index.tsx

@@ -225,6 +225,7 @@ const Permission: React.FC = observer(() => {
           }}
           }}
           popConfirm={{
           popConfirm={{
             title: '确认删除',
             title: '确认删除',
+            disabled: !!record.status,
             onConfirm: async () => {
             onConfirm: async () => {
               if (record.status) {
               if (record.status) {
                 await service.remove(record.id);
                 await service.remove(record.id);