sun-chaochao 3 лет назад
Родитель
Сommit
dcba572821

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

@@ -12,42 +12,46 @@
 
   .dialog-card {
     display: flex;
+    flex-direction: column;
     width: 60%;
     padding: 24px;
     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;
-      flex-direction: column;
-      width: 100%;
+      .dialog-icon {
+        margin-right: 10px;
+        color: rgba(0, 0, 0, 0.75);
+        font-weight: 500;
+        font-size: 12px;
+      }
 
-      .dialog-header {
-        .dialog-title {
-          color: rgba(0, 0, 0, 0.75);
-          font-weight: 700;
-          font-size: 14px;
-        }
+      .dialog-box {
+        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-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);
+        .dialog-editor {
+          width: 100%;
+          margin-top: 10px;
+          color: rgba(0, 0, 0, 0.75);
 
-        textarea::-webkit-scrollbar {
-          width: 5px !important;
+          textarea::-webkit-scrollbar {
+            width: 5px !important;
+          }
         }
       }
     }
@@ -57,26 +61,27 @@
 .dialog-active {
   display: flex;
   justify-content: flex-end;
-
   .dialog-card {
     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('success', '#24B276');
 
-  const [visible, setVisible] = useState<boolean>(false);
+  const [visible, setVisible] = useState<string[]>([]);
 
   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-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 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>
   );

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

@@ -21,7 +21,6 @@ import {
 import { randomString } from '@/utils/util';
 import Log from './Log';
 import { Store } from 'jetlinks-store';
-
 interface Props {
   onChange: (type: string) => void;
 }
@@ -60,10 +59,35 @@ const Message = (props: Props) => {
           });
           setLogList([...logList]);
         } 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]);
           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';
 
 const EditableContext: any = React.createContext(null);
@@ -236,7 +236,7 @@ const EditableTable = (props: Props) => {
   const handleSearch = (params: any) => {
     if (params.name) {
       const data = properties.filter((i: any) => {
-        return i?.name.indexOf(params?.nmae) !== -1;
+        return i?.name.includes(params?.name);
       });
       setDataSource({
         data: data.slice(

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

@@ -1,10 +1,10 @@
-import {PageContainer} from '@ant-design/pro-layout';
-import type {ActionType, ProColumns} from '@jetlinks/pro-table';
-import type {DeviceInstance} from '@/pages/device/Instance/typings';
+import { PageContainer } from '@ant-design/pro-layout';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import type { DeviceInstance } from '@/pages/device/Instance/typings';
 import moment from 'moment';
-import {Badge, Button, Dropdown, Menu, message, Tooltip} from 'antd';
-import {useEffect, useRef, useState} from 'react';
-import {useHistory, useIntl} from 'umi';
+import { Badge, Button, Dropdown, Menu, message, Tooltip } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import { useHistory, useIntl } from 'umi';
 import {
   CheckCircleOutlined,
   DeleteOutlined,
@@ -15,21 +15,22 @@ import {
   PlusOutlined,
   StopOutlined,
   SyncOutlined,
+  PlayCircleOutlined,
 } from '@ant-design/icons';
-import {model} from '@formily/reactive';
+import { model } from '@formily/reactive';
 import Service from '@/pages/device/Instance/service';
-import type {MetadataItem} from '@/pages/device/Product/typings';
+import type { MetadataItem } from '@/pages/device/Product/typings';
 import Save from './Save';
 import Export from './Export';
 import Import from './Import';
 import Process from './Process';
 import SearchComponent from '@/components/SearchComponent';
-import {PermissionButton, ProTableCard} from '@/components';
+import { PermissionButton, ProTableCard } from '@/components';
 import SystemConst from '@/utils/const';
 import Token from '@/utils/token';
 import DeviceCard from '@/components/ProTableCard/CardItems/device';
-import {getMenuPathByParams, MENUS_CODE} from '@/utils/menu';
-import {useLocation} from '@/hooks';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { useLocation } from '@/hooks';
 
 export const statusMap = new Map();
 statusMap.set('在线', 'success');
@@ -139,7 +140,7 @@ const Instance = () => {
         }),
       }}
     >
-      {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
+      {record.state.value !== 'notActive' ? <StopOutlined /> : <PlayCircleOutlined />}
     </PermissionButton>,
     <PermissionButton
       type={'link'}
@@ -506,7 +507,7 @@ const Instance = () => {
                   },
                 }}
               >
-                {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
+                {record.state.value !== 'notActive' ? <StopOutlined /> : <PlayCircleOutlined />}
                 {intl.formatMessage({
                   id: `pages.data.option.${
                     record.state.value !== 'notActive' ? 'disabled' : 'enabled'

+ 18 - 13
src/pages/device/Product/Detail/Access/index.tsx

@@ -1,17 +1,17 @@
-import {Badge, Button, Col, Empty, message, Row, Table, Tooltip} from 'antd';
-import {service} from '@/pages/link/AccessConfig';
-import {productModel, service as productService} from '@/pages/device/Product';
+import { Badge, Button, Col, Empty, message, Row, Table, Tooltip } from 'antd';
+import { service } from '@/pages/link/AccessConfig';
+import { productModel, service as productService } from '@/pages/device/Product';
 import styles from './index.less';
-import type {SetStateAction} from 'react';
-import {useEffect, useState} from 'react';
+import type { SetStateAction } from 'react';
+import { useEffect, useState } from 'react';
 import AccessConfig from './AccessConfig';
 import ReactMarkdown from 'react-markdown';
-import {Form, FormGrid, FormItem, FormLayout, Input, Password, PreviewText} from '@formily/antd';
-import type {ISchema} from '@formily/json-schema';
-import type {ConfigProperty} from '@/pages/device/Product/typings';
-import {createSchemaField} from '@formily/react';
-import {createForm} from '@formily/core';
-import {QuestionCircleOutlined} from '@ant-design/icons';
+import { Form, FormGrid, FormItem, FormLayout, Input, Password, PreviewText } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+import type { ConfigProperty } from '@/pages/device/Product/typings';
+import { createSchemaField } from '@formily/react';
+import { createForm } from '@formily/core';
+import { QuestionCircleOutlined } from '@ant-design/icons';
 import TitleComponent from '@/components/TitleComponent';
 import usePermissions from '@/hooks/permission';
 
@@ -350,13 +350,15 @@ const Access = () => {
               permission.add ? (
                 <span>
                   请先
-                  <a
+                  <Button
+                    type="link"
+                    disabled={!!(productModel.current?.count && productModel.current?.count > 0)}
                     onClick={() => {
                       setConfigVisible(true);
                     }}
                   >
                     选择
-                  </a>
+                  </Button>
                   设备接入网关,用以提供设备接入能力
                 </span>
               ) : (
@@ -379,6 +381,9 @@ const Access = () => {
                         type="primary"
                         ghost
                         style={{ marginLeft: 20 }}
+                        disabled={
+                          !!(productModel.current?.count && productModel.current?.count > 0)
+                        }
                         onClick={() => {
                           setConfigVisible(true);
                         }}

+ 59 - 26
src/pages/link/AccessConfig/Detail/Media/index.tsx

@@ -1,5 +1,5 @@
-import {Button, Card, Col, Form, Input, message, Row, Steps} from 'antd';
-import {useEffect, useState} from 'react';
+import { Button, Card, Col, Form, Input, message, Row, Steps } from 'antd';
+import { useEffect, useState } from 'react';
 import styles from './index.less';
 import {
   ArrayCollapse,
@@ -12,16 +12,16 @@ import {
   Radio,
   Select,
 } from '@formily/antd';
-import {createSchemaField} from '@formily/react';
-import type {ISchema} from '@formily/json-schema';
-import {createForm, registerValidateRules} from '@formily/core';
-import {service} from '@/pages/link/AccessConfig';
-import {useLocation} from 'umi';
+import { createSchemaField } from '@formily/react';
+import type { ISchema } from '@formily/json-schema';
+import { createForm, registerValidateRules } from '@formily/core';
+import { service } from '@/pages/link/AccessConfig';
+import { useLocation } from 'umi';
 import SipComponent from '@/components/SipComponent';
 import TitleComponent from '@/components/TitleComponent';
-import {ExclamationCircleFilled} from '@ant-design/icons';
-import {testIP} from '@/utils/util';
-import {getButtonPermission} from '@/utils/menu';
+import { ExclamationCircleFilled } from '@ant-design/icons';
+import { testIP } from '@/utils/util';
+import { getButtonPermission } from '@/utils/menu';
 
 type LocationType = {
   id?: string;
@@ -111,16 +111,26 @@ const Media = (props: Props) => {
 
     registerValidateRules({
       checkSIP(value: { host: string; port: number }) {
-        if (Number(value.port) < 1 || Number(value.port) > 65535) {
+        if (!value.host) {
           return {
             type: 'error',
-            message: '端口请输入1~65535之间的正整数',
+            message: '请输入IP地址',
           };
         } else if (!testIP(value.host)) {
           return {
             type: 'error',
             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;
       },
@@ -156,10 +166,15 @@ const Media = (props: Props) => {
             layout: 'vertical',
             tooltip: '绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0',
           },
-          required: true,
-          'x-validator': {
-            checkSIP: true,
-          },
+          'x-validator': [
+            {
+              required: true,
+              message: '请输入SIP 地址',
+            },
+            {
+              checkSIP: true,
+            },
+          ],
         },
         public: {
           title: '公网 Host',
@@ -173,9 +188,15 @@ const Media = (props: Props) => {
           type: 'number',
           'x-decorator': 'FormItem',
           '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': [
                 {
                   required: true,
-                  message: 'SIP ID',
+                  message: '请输入SIP ID',
                 },
               ],
             },
@@ -284,9 +305,15 @@ const Media = (props: Props) => {
                         labelAlign: 'left',
                         layout: 'vertical',
                       },
-                      'x-validator': {
-                        checkSIP: true,
-                      },
+                      'x-validator': [
+                        {
+                          required: true,
+                          message: '请输入SIP 地址',
+                        },
+                        {
+                          checkSIP: true,
+                        },
+                      ],
                     },
                     public: {
                       title: '公网 Host',
@@ -298,9 +325,15 @@ const Media = (props: Props) => {
                         labelAlign: 'left',
                         layout: 'vertical',
                       },
-                      'x-validator': {
-                        checkSIP: true,
-                      },
+                      'x-validator': [
+                        {
+                          required: true,
+                          message: '请输入公网 Host',
+                        },
+                        {
+                          checkSIP: true,
+                        },
+                      ],
                     },
                   },
                 },

+ 7 - 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 { useHistory } from 'umi';
 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 { PermissionButton } from '@/components';
 
@@ -143,20 +143,15 @@ const AccessConfig = () => {
                         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
                       isPermission={permission.delete}
+                      disabled={item.state.value !== 'disabled'}
+                      tooltip={{
+                        title: item.state.value !== 'disabled' ? '请先禁用,再删除' : '',
+                      }}
                       popConfirm={{
                         title: '确认删除',
                         onConfirm: () => {
@@ -174,7 +169,6 @@ const AccessConfig = () => {
                       type="link"
                     >
                       <DeleteOutlined />
-                      删除
                     </PermissionButton>,
                   ]}
                 />

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

@@ -1,13 +1,19 @@
-import {PageContainer} from '@ant-design/pro-layout';
-import type {ActionType, ProColumns} from '@jetlinks/pro-table';
-import type {ProtocolItem} from '@/pages/link/Protocol/typings';
-import {Badge, message} from 'antd';
-import {useRef, useState} from 'react';
-import {CheckCircleOutlined, DeleteOutlined, EditOutlined, PlusOutlined, StopOutlined,} from '@ant-design/icons';
+import { PageContainer } from '@ant-design/pro-layout';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import type { ProtocolItem } from '@/pages/link/Protocol/typings';
+import { Badge, message } from 'antd';
+import { useRef, useState } from 'react';
+import {
+  PlayCircleOutlined,
+  DeleteOutlined,
+  EditOutlined,
+  PlusOutlined,
+  StopOutlined,
+} from '@ant-design/icons';
 import Service from '@/pages/link/Protocol/service';
-import {useIntl} from 'umi';
+import { useIntl } from 'umi';
 import SearchComponent from '@/components/SearchComponent';
-import {PermissionButton, ProTableCard} from '@/components';
+import { PermissionButton, ProTableCard } from '@/components';
 import ProcotolCard from '@/components/ProTableCard/CardItems/protocol';
 import Save from './save';
 
@@ -108,7 +114,7 @@ const Protocol = () => {
             },
           }}
         >
-          {record.state === 1 ? <StopOutlined /> : <CheckCircleOutlined />}
+          {record.state === 1 ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         <PermissionButton
           isPermission={permission.delete}
@@ -211,6 +217,7 @@ const Protocol = () => {
                 }}
               >
                 <EditOutlined />
+                编辑
               </PermissionButton>,
               <PermissionButton
                 isPermission={permission.action}
@@ -231,7 +238,8 @@ const Protocol = () => {
                   },
                 }}
               >
-                {record.state === 1 ? <StopOutlined /> : <CheckCircleOutlined />}
+                {record.state === 1 ? <StopOutlined /> : <PlayCircleOutlined />}
+                {record.state === 1 ? '撤销' : '发布'}
               </PermissionButton>,
               <PermissionButton
                 isPermission={permission.delete}

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

@@ -1,14 +1,15 @@
 import { service } from '@/pages/media/Cascade';
 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 type { ActionType, ProColumns } 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 { useIntl, useLocation } from 'umi';
 import BindChannel from './BindChannel';
 import BadgeStatus, { StatusColorEnum } from '@/components/BadgeStatus';
+import { PermissionButton } from '@/components';
 
 const Channel = () => {
   const location: any = useLocation();
@@ -20,6 +21,7 @@ const Channel = () => {
   const id = location?.query?.id || '';
   const [data, setData] = useState<string>('');
   const [popVisible, setPopvisible] = useState<string>('');
+  const { permission } = PermissionButton.usePermission('media/Cascade');
 
   const unbind = async (list: string[]) => {
     const resp = await service.unbindChannel(id, list);
@@ -44,7 +46,9 @@ const Channel = () => {
           style={{ marginTop: 10, width: '100%' }}
           onClick={async () => {
             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) {
                 message.success('操作成功');
                 actionRef.current?.reload();
@@ -78,16 +82,33 @@ const Channel = () => {
         <span>
           {text}
           <Popover
-            visible={popVisible === record.id}
+            visible={popVisible === record.gbChannelId}
             trigger="click"
             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
               style={{ marginLeft: 10 }}
               onClick={() => {
                 setData('');
-                setPopvisible(record.id);
+                setPopvisible(record.gbChannelId);
               }}
             >
               <EditOutlined />
@@ -124,19 +145,19 @@ const Channel = () => {
       align: 'center',
       width: 200,
       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>,
       ],
     },
   ];
@@ -195,29 +216,33 @@ const Channel = () => {
           </Space>
         )}
         toolBarRender={() => [
-          <Button
+          <PermissionButton
+            isPermission={permission.channel}
+            key={'bind'}
             onClick={() => {
               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 && (

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

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

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

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

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

@@ -115,7 +115,12 @@ const Stream = () => {
                               key="button"
                               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>
                             </PermissionButton>
                             <PermissionButton
@@ -134,8 +139,18 @@ const Stream = () => {
                               key="delete"
                               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>
                             </PermissionButton>

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

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

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

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

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

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

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

@@ -1,28 +1,23 @@
+import './index.less';
+
 const DingTalkRebot = () => {
   const b = '{name}';
   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>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
       </div>
-      <b>2.模板配置说明</b>
+      <h1>2.模板配置说明</h1>
       <div>
-        <div> 1、绑定配置</div>
+        <h2> 1、绑定配置</h2>
         <div> 绑定通知配置</div>
-        <div> 2、消息类型</div>
+        <h2> 2、消息类型</h2>
         <div> 目前支持text、markdown、link3种。</div>
-        <div> 3. 模板内容</div>
+        <h2> 3. 模板内容</h2>
         <div>
           支持填写带变量的动态模板。变量填写规范示例:${b}
           。填写动态参数后,可对变量的名称、类型、格式进行配置,以便告警通知时填写。

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

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

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

@@ -1,39 +1,33 @@
 import { Image } from 'antd';
+import './index.less';
 
 const WeixinApp = () => {
   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');
 
   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>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
         通知配置可以结合通知配置为告警消息通知提供支撑。也可以用于系统中其他自定义模块的调用。
       </div>
-      <b>2.通知配置说明</b>
+      <h1>2.通知配置说明</h1>
       <div>
-        <div>1. AppID</div>
+        <h2>1. AppID</h2>
         <div>微信服务号的唯一专属编号。</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
-        <div>
+        <div className="image">
           <Image width="100%" src={agentId} />
         </div>
       </div>
-      <b>2. AppSecret</b>
+      <h2>2. AppSecret</h2>
       <div>
         <div>公众号开发者身份的密码</div>
         <div>获取路径:“微信公众平台”管理后台--“设置与开发”--“基本配置”</div>
-        <div>
+        <div className="image">
           <Image width="100%" src={appId} />
         </div>
       </div>

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

@@ -1,4 +1,5 @@
 import { Image } from 'antd';
+import './index.less';
 
 const WeixinCorp = () => {
   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');
 
   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>
       </div>
-      <b>1. 概述</b>
+      <h1>1. 概述</h1>
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
       </div>
-      <b>2.模版配置说明</b>
+      <h1>2.模版配置说明</h1>
       <div>
-        <div> 1、绑定配置</div>
+        <h2> 1、绑定配置</h2>
         <div> 绑定通知配置</div>
-        <div> 2. Agentid</div>
+        <h2> 2. Agentid</h2>
         <div> 应用唯一标识</div>
         <div> 获取路径:“企业微信”管理后台--“应用管理”--“应用”--“查看应用”</div>
-        <div>
+        <div className="image">
           <Image width="100%" src={agentId} />
         </div>
-        <div> 3. 收信人ID、收信部门ID、标签推送</div>
+        <h2> 3. 收信人ID、收信部门ID、标签推送</h2>
         <div>
           接收通知的3种方式,3个字段若在此页面都没有填写,则在模板调试和配置告警通知时需要手动填写
         </div>
         <div> 收信人ID获取路径:【通讯录】-{'>'}【成员信息】查看成员账号</div>
         <div> 收信部门ID获取路径:【通讯录】-{'>'}【部门信息】查看部门ID</div>
-        <div>
+        <div className="image">
           <Image width="100%" src={userId} />
           <Image width="100%" src={toDept} />
           <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;
+  }
+}

+ 12 - 23
src/pages/rule-engine/Instance/index.tsx

@@ -4,7 +4,7 @@ import type { InstanceItem } from '@/pages/rule-engine/Instance/typings';
 import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import {
-  CheckCircleOutlined,
+  PlayCircleOutlined,
   DeleteOutlined,
   EditOutlined,
   EyeOutlined,
@@ -40,12 +40,6 @@ const Instance = () => {
       }}
       type={'link'}
       style={{ padding: 0 }}
-      tooltip={{
-        title: intl.formatMessage({
-          id: 'pages.data.option.edit',
-          defaultMessage: '编辑',
-        }),
-      }}
     >
       <EditOutlined />
       编辑
@@ -72,24 +66,16 @@ const Instance = () => {
         },
       }}
       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
       isPermission={permission.delete}
+      disabled={record.state.value !== 'disable'}
+      tooltip={{
+        title: record.state.value !== 'disable' ? '请先禁用,再删除' : '',
+      }}
       popConfirm={{
         title: '确认删除',
         onConfirm: async () => {
@@ -111,7 +97,6 @@ const Instance = () => {
       type="link"
     >
       <DeleteOutlined />
-      删除
     </PermissionButton>,
   ];
 
@@ -224,11 +209,15 @@ const Instance = () => {
             title: record.state.value !== 'disable' ? '禁用' : '启用',
           }}
         >
-          {record.state.value !== 'disable' ? <StopOutlined /> : <CheckCircleOutlined />}
+          {record.state.value !== 'disable' ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         <PermissionButton
           isPermission={permission.delete}
           style={{ padding: 0 }}
+          disabled={record.state.value !== 'disable'}
+          tooltip={{
+            title: record.state.value !== 'disable' ? '请先禁用,再删除' : '',
+          }}
           popConfirm={{
             title: '确认删除',
             onConfirm: async () => {