xieyonghong 3 лет назад
Родитель
Сommit
eaba7b6d1e
27 измененных файлов с 607 добавлено и 315 удалено
  1. 34 3
      src/components/SearchComponent/index.tsx
  2. 46 40
      src/pages/device/Instance/Detail/Diagnose/Message/Dialog/index.less
  3. 34 22
      src/pages/device/Instance/Detail/Diagnose/Message/Dialog/index.tsx
  4. 29 4
      src/pages/device/Instance/Detail/Diagnose/Message/index.tsx
  5. 1 1
      src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx
  6. 97 4
      src/pages/device/Instance/index.tsx
  7. 3 0
      src/pages/device/Instance/service.ts
  8. 7 2
      src/pages/device/Product/Detail/Access/index.tsx
  9. 61 0
      src/pages/device/Product/index.tsx
  10. 49 16
      src/pages/link/AccessConfig/Detail/Media/index.tsx
  11. 8 13
      src/pages/link/AccessConfig/index.tsx
  12. 7 3
      src/pages/link/Protocol/index.tsx
  13. 58 33
      src/pages/media/Cascade/Channel/index.tsx
  14. 16 28
      src/pages/media/Cascade/index.tsx
  15. 8 7
      src/pages/media/Stream/Detail/index.tsx
  16. 18 3
      src/pages/media/Stream/index.tsx
  17. 11 17
      src/pages/notice/Template/Detail/doc/AliyunSms.tsx
  18. 12 17
      src/pages/notice/Template/Detail/doc/AliyunVoice.tsx
  19. 12 17
      src/pages/notice/Template/Detail/doc/DingTalk.tsx
  20. 9 14
      src/pages/notice/Template/Detail/doc/DingTalkRebot.tsx
  21. 9 8
      src/pages/notice/Template/Detail/doc/Email.tsx
  22. 9 15
      src/pages/notice/Template/Detail/doc/WeixinApp.tsx
  23. 10 16
      src/pages/notice/Template/Detail/doc/WeixinCorp.tsx
  24. 33 0
      src/pages/notice/Template/Detail/doc/index.less
  25. 24 31
      src/pages/rule-engine/Instance/index.tsx
  26. 1 0
      src/pages/system/Permission/index.tsx
  27. 1 1
      src/utils/BaseService.ts

+ 34 - 3
src/components/SearchComponent/index.tsx

@@ -12,9 +12,10 @@ import {
   PreviewText,
   Select,
   Space,
+  TreeSelect,
 } from '@formily/antd';
 import type { Field, FieldDataSource } from '@formily/core';
-import { createForm, onFieldReact } from '@formily/core';
+import { createForm, onFieldReact, onFieldValueChange } from '@formily/core';
 import GroupNameControl from '@/components/SearchComponent/GroupNameControl';
 import {
   DeleteOutlined,
@@ -95,6 +96,7 @@ const SchemaField = createSchemaField({
     PreviewText,
     GroupNameControl,
     Space,
+    TreeSelect,
   },
 });
 
@@ -181,9 +183,33 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
               });
               f.setFieldState(typeFiled.query('.value'), async (state) => {
                 state.componentType = 'Select';
-                state.loading = true;
+                // state.loading = true;
                 state.dataSource = option;
-                state.loading = false;
+                // state.loading = false;
+              });
+            } else if (_field?.valueType === 'treeSelect') {
+              let option: { label: any; value: any }[] | FieldDataSource | undefined = [];
+              if (_field?.valueEnum) {
+                option = Object.values(_field?.valueEnum || {}).map((item) => ({
+                  label: item.text,
+                  value: item.status,
+                }));
+              } else if (_field?.request) {
+                option = await _field.request();
+              }
+              f.setFieldState(typeFiled.query('.termType'), (_state) => {
+                _state.value = 'eq';
+              });
+              f.setFieldState(typeFiled.query('.value'), (state) => {
+                state.componentType = 'TreeSelect';
+                state.dataSource = option;
+                console.log(option, 'optin');
+                state.componentProps = {
+                  ..._field.fieldProps,
+                  treeNodeFilterProp: 'name',
+                  // filterOption: (input: string, option: any) =>
+                  //   option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                };
               });
             } else if (_field?.valueType === 'digit') {
               f.setFieldState(typeFiled.query('.value'), async (state) => {
@@ -211,6 +237,11 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
               });
             }
           });
+          onFieldValueChange('*.*.column', (field1, form1) => {
+            form1.setFieldState(field1.query('.value'), (state1) => {
+              state1.value = null;
+            });
+          });
         },
       }),
     [target],

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

@@ -12,42 +12,47 @@
 
   .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-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%;
-        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 {
   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 - 4
src/pages/device/Instance/Detail/Diagnose/Message/index.tsx

@@ -60,10 +60,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);
         }

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

@@ -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(

+ 97 - 4
src/pages/device/Instance/index.tsx

@@ -12,6 +12,7 @@ import {
   ExportOutlined,
   EyeOutlined,
   ImportOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   StopOutlined,
   SyncOutlined,
@@ -30,6 +31,8 @@ import Token from '@/utils/token';
 import DeviceCard from '@/components/ProTableCard/CardItems/device';
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import { useLocation } from '@/hooks';
+import { service as deptService } from '@/pages/system/Department';
+import { service as categoryService } from '@/pages/device/Category';
 
 export const statusMap = new Map();
 statusMap.set('在线', 'success');
@@ -144,7 +147,7 @@ const Instance = () => {
         }),
       }}
     >
-      {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
+      {record.state.value !== 'notActive' ? <StopOutlined /> : <PlayCircleOutlined />}
     </PermissionButton>,
     <PermissionButton
       type={'link'}
@@ -260,6 +263,98 @@ const Instance = () => {
       filterMultiple: false,
     },
     {
+      dataIndex: 'categoryId',
+      title: '产品分类',
+      valueType: 'treeSelect',
+      hideInTable: true,
+      fieldProps: {
+        fieldNames: {
+          label: 'name',
+          value: 'id',
+        },
+      },
+      request: () =>
+        categoryService
+          .queryTree({
+            paging: false,
+          })
+          .then((resp: any) => resp.result),
+    },
+    {
+      dataIndex: 'productId$product-info',
+      title: '接入方式',
+      valueType: 'select',
+      hideInTable: true,
+      request: () =>
+        service.queryGatewayList().then((resp) =>
+          resp.result.map((item: any) => ({
+            label: item.name,
+            value: `accessId is ${item.id}`,
+          })),
+        ),
+    },
+    {
+      dataIndex: 'deviceType',
+      title: '设备类型',
+      valueType: 'select',
+      hideInTable: true,
+      valueEnum: {
+        device: {
+          text: '直连设备',
+          status: 'device',
+        },
+        childrenDevice: {
+          text: '网关子设备',
+          status: 'childrenDevice',
+        },
+        gateway: {
+          text: '网关设备',
+          status: 'gateway',
+        },
+      },
+    },
+    {
+      dataIndex: 'id$dim-assets',
+      title: '所属部门',
+      valueType: 'treeSelect',
+      hideInTable: true,
+      fieldProps: {
+        fieldNames: {
+          label: 'name',
+          value: 'value',
+        },
+      },
+      request: () =>
+        deptService
+          .queryOrgThree({
+            paging: false,
+          })
+          .then((resp) => {
+            const formatValue = (list: any[]) => {
+              const _list: any[] = [];
+              list.forEach((item) => {
+                if (item.children) {
+                  item.children = formatValue(item.children);
+                }
+                _list.push({
+                  ...item,
+                  value: JSON.stringify({
+                    assetType: 'device',
+                    targets: [
+                      {
+                        type: 'org',
+                        id: item.id,
+                      },
+                    ],
+                  }),
+                });
+              });
+              return _list;
+            };
+            return formatValue(resp.result);
+          }),
+    },
+    {
       title: intl.formatMessage({
         id: 'pages.table.description',
         defaultMessage: '说明',
@@ -280,8 +375,6 @@ const Instance = () => {
     },
   ];
 
-  console.log(jumpParams);
-
   const menu = (
     <Menu>
       <Menu.Item key="1">
@@ -515,7 +608,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'

+ 3 - 0
src/pages/device/Instance/service.ts

@@ -238,6 +238,9 @@ class Service extends BaseService<DeviceInstance> {
     request(`/${SystemConst.API_BASE}/device/metadata/mapping/product/${productId}`, {
       method: 'GET',
     });
+
+  //接入方式
+  public queryGatewayList = () => request(`/${SystemConst.API_BASE}/gateway/device/providers`);
 }
 
 export default Service;

+ 7 - 2
src/pages/device/Product/Detail/Access/index.tsx

@@ -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);
                         }}

+ 61 - 0
src/pages/device/Product/index.tsx

@@ -22,6 +22,8 @@ import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import { AIcon, PermissionButton, ProTableCard } from '@/components';
 import ProductCard from '@/components/ProTableCard/CardItems/product';
 import { downloadObject } from '@/utils/util';
+import { service as categoryService } from '@/pages/device/Category';
+import { service as deptService } from '@/pages/system/Department';
 
 export const service = new Service('device-product');
 export const statusMap = {
@@ -294,6 +296,65 @@ const Product = observer(() => {
       // hideInSearch: true,
     },
     {
+      dataIndex: 'categoryId',
+      title: '分类',
+      valueType: 'treeSelect',
+      hideInTable: true,
+      fieldProps: {
+        fieldNames: {
+          label: 'name',
+          value: 'id',
+        },
+      },
+      request: () =>
+        categoryService
+          .queryTree({
+            paging: false,
+          })
+          .then((resp: any) => resp.result),
+    },
+    {
+      dataIndex: 'id$dim-assets',
+      title: '所属部门',
+      valueType: 'treeSelect',
+      hideInTable: true,
+      fieldProps: {
+        fieldNames: {
+          label: 'name',
+          value: 'value',
+        },
+      },
+      request: () =>
+        deptService
+          .queryOrgThree({
+            paging: false,
+          })
+          .then((resp) => {
+            const formatValue = (list: any[]) => {
+              const _list: any[] = [];
+              list.forEach((item) => {
+                if (item.children) {
+                  item.children = formatValue(item.children);
+                }
+                _list.push({
+                  ...item,
+                  value: JSON.stringify({
+                    assetType: 'product',
+                    targets: [
+                      {
+                        type: 'org',
+                        id: item.id,
+                      },
+                    ],
+                  }),
+                });
+              });
+              return _list;
+            };
+            return formatValue(resp.result);
+          }),
+    },
+    {
       title: intl.formatMessage({
         id: 'pages.data.option',
         defaultMessage: '操作',

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

@@ -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,
+                        },
+                      ],
                     },
                   },
                 },

+ 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 { 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,22 +143,18 @@ 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: '确认删除',
+                        disabled: item.state.value !== 'disabled',
                         onConfirm: () => {
                           service.remove(item.id).then((resp: any) => {
                             if (resp.status === 200) {
@@ -174,7 +170,6 @@ const AccessConfig = () => {
                       type="link"
                     >
                       <DeleteOutlined />
-                      删除
                     </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 { useRef, useState } from 'react';
 import {
-  CheckCircleOutlined,
   DeleteOutlined,
   EditOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   StopOutlined,
 } from '@ant-design/icons';
@@ -125,7 +125,7 @@ const Protocol = () => {
             },
           }}
         >
-          {record.state === 1 ? <StopOutlined /> : <CheckCircleOutlined />}
+          {record.state === 1 ? <StopOutlined /> : <PlayCircleOutlined />}
         </PermissionButton>,
         <PermissionButton
           isPermission={permission.delete}
@@ -136,6 +136,7 @@ const Protocol = () => {
           disabled={record.state === 1}
           popConfirm={{
             title: '确认删除',
+            disabled: record.state === 1,
             onConfirm: async () => {
               const resp: any = await service.remove(record.id);
               if (resp.status === 200) {
@@ -228,6 +229,7 @@ const Protocol = () => {
                 }}
               >
                 <EditOutlined />
+                编辑
               </PermissionButton>,
               <PermissionButton
                 isPermission={permission.action}
@@ -248,7 +250,8 @@ const Protocol = () => {
                   },
                 }}
               >
-                {record.state === 1 ? <StopOutlined /> : <CheckCircleOutlined />}
+                {record.state === 1 ? <StopOutlined /> : <PlayCircleOutlined />}
+                {record.state === 1 ? '撤销' : '发布'}
               </PermissionButton>,
               <PermissionButton
                 isPermission={permission.delete}
@@ -258,6 +261,7 @@ const Protocol = () => {
                 disabled={record.state === 1}
                 popConfirm={{
                   title: '确认删除',
+                  disabled: record.state === 1,
                   onConfirm: async () => {
                     const resp: any = await service.remove(record.id);
                     if (resp.status === 200) {

+ 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 />
@@ -135,19 +156,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>,
       ],
     },
   ];
@@ -206,29 +227,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 && (

+ 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 { Badge, message } from 'antd';
 import {
-  CheckCircleOutlined,
   DeleteOutlined,
   EditOutlined,
   LinkOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   ShareAltOutlined,
   StopOutlined,
@@ -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,26 +97,19 @@ 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: '确认删除',
+        disabled: record.status.value !== 'disabled',
         onConfirm: async () => {
           const resp: any = await service.remove(record.id);
           if (resp.status === 200) {
@@ -138,7 +122,6 @@ const Cascade = () => {
       type="link"
     >
       <DeleteOutlined />
-      删除
     </PermissionButton>,
   ];
 
@@ -293,12 +276,17 @@ 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: '确认删除',
+            disabled: record.status.value !== 'disabled',
             onConfirm: async () => {
               const resp: any = await service.remove(record.id);
               if (resp.status === 200) {

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

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

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

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

+ 1 - 1
src/utils/BaseService.ts

@@ -27,7 +27,7 @@ class BaseService<T> implements IBaseService<T> {
     return request(`${this.uri}/_query/no-paging?paging=false`, { data, method: 'POST' });
   }
 
-  queryNoPaging(params: any): Promise<unknown> {
+  queryNoPaging(params?: any): Promise<unknown> {
     return request(`${this.uri}/_query/no-paging?paging=false`, { params, method: 'GET' });
   }