wzyyy 3 лет назад
Родитель
Сommit
f5b2208447
46 измененных файлов с 634 добавлено и 285 удалено
  1. 2 1
      src/app.tsx
  2. 3 3
      src/components/ProTableCard/CardItems/DataCollect/channel.tsx
  3. 4 1
      src/components/ProTableCard/CardItems/cascade.tsx
  4. 8 0
      src/components/ProTableCard/index.less
  5. 18 9
      src/components/ProTableCard/index.tsx
  6. 8 0
      src/pages/DataCollect/Channel/index.tsx
  7. 6 8
      src/pages/DataCollect/Collector/components/Device/Save/index.tsx
  8. 17 11
      src/pages/DataCollect/Collector/components/Point/index.tsx
  9. 53 28
      src/pages/DataCollect/Collector/components/Tree/index.tsx
  10. 15 1
      src/pages/DataCollect/Collector/index.tsx
  11. 126 62
      src/pages/device/Firmware/Task/Detail/index.tsx
  12. 1 1
      src/pages/device/Firmware/Task/Save/index.tsx
  13. 29 26
      src/pages/device/Firmware/Task/index.tsx
  14. 1 1
      src/pages/device/Firmware/index.tsx
  15. 111 29
      src/pages/device/Instance/Detail/ChildDevice/index.tsx
  16. 1 1
      src/pages/device/Instance/Detail/Running/Property/FileComponent/index.tsx
  17. 30 7
      src/pages/device/Instance/Detail/Running/Property/PropertyCard.tsx
  18. 34 10
      src/pages/device/Instance/Detail/Running/Property/index.tsx
  19. 17 7
      src/pages/device/Instance/Detail/Tags/Edit.tsx
  20. 1 0
      src/pages/device/Instance/Detail/Tags/index.tsx
  21. 3 3
      src/pages/device/components/Empty/index.tsx
  22. 1 0
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  23. 2 2
      src/pages/device/components/Metadata/Base/index.tsx
  24. 1 1
      src/pages/device/components/Metadata/Cat/index.tsx
  25. 8 8
      src/pages/edge/Resource/Issue/Result.tsx
  26. 2 0
      src/pages/iot-card/CardManagement/ExportModal.tsx
  27. 3 0
      src/pages/iot-card/CardManagement/index.tsx
  28. 2 0
      src/pages/media/Cascade/index.tsx
  29. 8 0
      src/pages/media/Stream/index.tsx
  30. 13 3
      src/pages/notice/Config/Debug/index.tsx
  31. 4 1
      src/pages/notice/Config/Detail/index.tsx
  32. 7 4
      src/pages/notice/Template/Detail/index.tsx
  33. 1 4
      src/pages/notice/Template/index.tsx
  34. 2 0
      src/pages/rule-engine/Instance/Save/index.tsx
  35. 1 1
      src/pages/rule-engine/Scene/Save/action/DeviceOutput/index.tsx
  36. 6 3
      src/pages/rule-engine/Scene/Save/components/Buttons/ParamsDropdown.tsx
  37. 9 0
      src/pages/rule-engine/Scene/Save/manual/index.tsx
  38. 10 0
      src/pages/rule-engine/Scene/Save/terms/branchItem.tsx
  39. 8 0
      src/pages/rule-engine/Scene/Save/terms/index.tsx
  40. 8 0
      src/pages/rule-engine/Scene/Save/timer/index.tsx
  41. 6 3
      src/pages/system/Department/Tree/tree.tsx
  42. 1 0
      src/pages/system/Department/save.tsx
  43. 1 1
      src/pages/system/Menu/Detail/edit.tsx
  44. 33 40
      src/pages/system/Menu/Setting/baseMenu.ts
  45. 8 4
      src/pages/system/User/Save/index.tsx
  46. 1 1
      src/utils/util.ts

+ 2 - 1
src/app.tsx

@@ -187,7 +187,8 @@ export const request: RequestConfig = {
       response.status === 400 ||
       response.status === 500 ||
       response.status === 404 ||
-      response.status === 403
+      response.status === 403 ||
+      response.status === 504
     ) {
       // 添加clone() 避免后续其它地方用response.text()时报错
       response

+ 3 - 3
src/components/ProTableCard/CardItems/DataCollect/channel.tsx

@@ -30,13 +30,13 @@ export default (props: ChannelCardProps) => {
       showMask={false}
       statusNames={{
         running: StatusColorEnum.success,
-        disabled: StatusColorEnum.processing,
+        disabled: StatusColorEnum.error,
         partialError: StatusColorEnum.warning,
-        failed: StatusColorEnum.error,
+        failed: StatusColorEnum.processing,
         stopped: StatusColorEnum.default,
       }}
     >
-      <div className={'pro-table-card-item'}>
+      <div className={'pro-table-card-item'} onClick={props?.onClick}>
         <div className={'card-item-avatar'}>
           <img
             width={88}

+ 4 - 1
src/components/ProTableCard/CardItems/cascade.tsx

@@ -37,7 +37,10 @@ export default (props: CascadeCardProps) => {
           </div>
           <div>通道数量: {props?.count || 0}</div>
           <div style={{ display: 'flex', width: '100%' }}>
-            <Badge status={props.onlineStatus?.value === 'offline' ? 'error' : 'success'} />
+            <Badge
+              style={{ marginRight: 5 }}
+              status={props.onlineStatus?.value === 'offline' ? 'error' : 'success'}
+            />
             {/*<div*/}
             {/*  style={{*/}
             {/*    width: '90%',*/}

+ 8 - 0
src/components/ProTableCard/index.less

@@ -270,6 +270,14 @@
           background-color: rgba(#e50012, 0.1);
         }
 
+        &.default {
+          background-color: rgba(229, 229, 229);
+        }
+
+        &.processing {
+          background-color: rgba(36, 178, 118, 0.1);
+        }
+
         .card-state-content {
           transform: skewX(-45deg);
         }

+ 18 - 9
src/components/ProTableCard/index.tsx

@@ -237,15 +237,16 @@ const ProTableCard = <
               resp = await request(newParam, sort, filter);
             }
             setLoading(false);
-            setTotal(resp.result ? resp.result.total : 0);
+            const result = {
+              data: resp.result ? resp.result.data : [],
+              pageIndex: resp.result ? resp.result.pageIndex : 0,
+              pageSize: resp.result ? resp.result.pageSize : 0,
+              total: resp.result ? resp.result.total : 0,
+            };
+            setTotal(result.total);
             return {
               code: resp.message,
-              result: {
-                data: resp.result ? resp.result.data : [],
-                pageIndex: resp.result ? resp.result.pageIndex : 0,
-                pageSize: resp.result ? resp.result.pageSize : 0,
-                total: resp.result ? resp.result.total : 0,
-              },
+              result,
               status: resp.status,
             };
           }
@@ -319,9 +320,17 @@ const ProTableCard = <
               pageSizeOptions={pageSizeOptions}
               pageSize={pageSize}
               showTotal={(num) => {
-                const minSize = pageIndex * pageSize + 1;
                 const MaxSize = (pageIndex + 1) * pageSize;
-                return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
+                const max = MaxSize > num ? num : MaxSize;
+
+                const minSize = pageIndex * pageSize + 1;
+                const pageIndexInt =
+                  parseInt(num / pageSize) === num / pageSize
+                    ? num / pageSize - 1
+                    : parseInt(num / pageSize);
+                const min = minSize > num ? pageIndexInt * pageSize + 1 : minSize;
+
+                return `第 ${min} - ${max} 条/总共 ${num} 条`;
               }}
             />
           )}

+ 8 - 0
src/pages/DataCollect/Channel/index.tsx

@@ -13,6 +13,8 @@ import ChannelCard from '@/components/ProTableCard/CardItems/DataCollect/channel
 import { DeleteOutlined, EditOutlined, PlayCircleOutlined, StopOutlined } from '@ant-design/icons';
 import { onlyMessage } from '@/utils/util';
 import Save from '@/pages/DataCollect/Channel/Save';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import useHistory from '@/hooks/route/useHistory';
 
 const ChannelModel = model<{
   visible: boolean;
@@ -36,6 +38,8 @@ export default observer(() => {
     pageIndex: 0,
     total: 0,
   });
+  const history = useHistory();
+
   const columns: ProColumns<ChannelItem>[] = [
     {
       title: '通道名称',
@@ -280,6 +284,10 @@ export default observer(() => {
                         <DeleteOutlined />
                       </PermissionButton>,
                     ]}
+                    onClick={() => {
+                      const url = getMenuPathByCode(MENUS_CODE['DataCollect/Collector']);
+                      history.push(url, { channelId: record.id });
+                    }}
                   />
                 </Col>
               ))}

+ 6 - 8
src/pages/DataCollect/Collector/components/Device/Save/index.tsx

@@ -55,14 +55,12 @@ export default (props: Props) => {
             if (value) {
               const dt = channelListRef.current.find((item) => item.id === value);
               channelRef.current = dt;
-              if (dt?.provider && dt?.provider === 'MODBUS_TCP') {
-                f.setFieldState('configuration.unitId', (state) => {
-                  state.visible = true;
-                });
-                f.setFieldState('configuration.endian', (state) => {
-                  state.visible = true;
-                });
-              }
+              f.setFieldState('configuration.unitId', (state) => {
+                state.visible = dt?.provider && dt?.provider === 'MODBUS_TCP';
+              });
+              f.setFieldState('configuration.endian', (state) => {
+                state.visible = dt?.provider && dt?.provider === 'MODBUS_TCP';
+              });
             }
           });
           onFieldReact('circuitBreaker.type', async (field, f) => {

+ 17 - 11
src/pages/DataCollect/Collector/components/Point/index.tsx

@@ -102,7 +102,7 @@ const PointCard = observer((props: PointCardProps) => {
         setPropertyValue({ ...propertyValue });
       });
   };
-  const handleSearch = (params: any) => {
+  const handleSearch = async (params: any) => {
     if (subRef.current) {
       subRef.current?.unsubscribe();
     }
@@ -111,8 +111,8 @@ const PointCard = observer((props: PointCardProps) => {
     PointModel.list = [];
     setLoading(true);
     setParam(params);
-    service
-      .queryPoint({
+    if (props.data?.id) {
+      const resp = await service.queryPoint({
         ...params,
         terms: [
           ...params?.terms,
@@ -123,14 +123,20 @@ const PointCard = observer((props: PointCardProps) => {
           },
         ],
         sorts: [{ name: 'id', order: 'desc' }],
-      })
-      .then((resp) => {
-        if (resp.status === 200) {
-          setDataSource(resp.result);
-          subscribeProperty((resp.result?.data || []).map((item: any) => item.id));
-        }
-        setLoading(false);
       });
+      if (resp.status === 200) {
+        setDataSource(resp.result);
+        subscribeProperty((resp.result?.data || []).map((item: any) => item.id));
+      }
+    } else {
+      setDataSource({
+        data: [],
+        pageSize: 12,
+        pageIndex: 0,
+        total: 0,
+      });
+    }
+    setLoading(false);
   };
 
   useEffect(() => {
@@ -235,7 +241,7 @@ const PointCard = observer((props: PointCardProps) => {
       >
         <div>
           <div style={{ height: '100%', paddingBottom: 48 }}>
-            {props.data?.id !== '*' && (
+            {props.data?.id !== '*' && props.data?.id && (
               <div style={{ width: '100%', display: 'flex', justifyContent: 'flex-start' }}>
                 <PermissionButton
                   isPermission={permission.add}

+ 53 - 28
src/pages/DataCollect/Collector/components/Tree/index.tsx

@@ -24,15 +24,20 @@ const TreeModel = model<{
   param: any;
   visible: boolean;
   current: any;
+  search: string;
 }>({
   selectedKeys: ['*'],
   dataSource: [],
   loading: true,
-  param: {},
+  param: {
+    terms: [],
+  },
   visible: false,
   current: {},
+  search: '',
 });
 interface Props {
+  channelId?: string;
   change: (data?: any) => void;
 }
 
@@ -41,33 +46,50 @@ export default observer((props: Props) => {
   const { permission } = PermissionButton.usePermission('DataCollect/Collector');
   const intl = useIntl();
 
-  const handleSearch = (params: any) => {
+  const handleSearch = async (params: any) => {
     TreeModel.loading = true;
     TreeModel.param = params;
-    service
-      .queryCollector({ ...params, paging: false, sorts: [{ name: 'createTime', order: 'desc' }] })
-      .then((resp) => {
-        if (resp.status === 200) {
-          if (params.terms) {
-            TreeModel.dataSource = resp.result;
-          } else {
-            TreeModel.dataSource = [
-              {
-                id: '*',
-                name: '全部',
-                children: resp.result,
-              },
-            ];
-          }
-          props.change(TreeModel.dataSource[0]);
-        }
-        TreeModel.loading = false;
-      });
+    const resp = await service.queryCollector({
+      ...params,
+      paging: false,
+      sorts: [{ name: 'createTime', order: 'desc' }],
+    });
+    if (resp.status === 200) {
+      if (TreeModel?.search || props?.channelId) {
+        TreeModel.dataSource = resp.result;
+      } else {
+        TreeModel.dataSource = [
+          {
+            id: '*',
+            name: '全部',
+            children: resp.result,
+          },
+        ];
+      }
+      TreeModel.selectedKeys = [TreeModel.dataSource?.[0]?.id] || [];
+      props.change(TreeModel.dataSource?.[0]);
+    }
+    TreeModel.loading = false;
   };
 
   useEffect(() => {
-    handleSearch(TreeModel.param);
-  }, [TreeModel.param]);
+    if (props.channelId) {
+      TreeModel.param = {
+        terms: [
+          {
+            column: 'channelId',
+            value: props?.channelId,
+          },
+        ],
+      };
+      handleSearch(TreeModel.param);
+    } else {
+      TreeModel.param = {
+        terms: [],
+      };
+      handleSearch(TreeModel.param);
+    }
+  }, [props.channelId]);
 
   const getState = (record: any) => {
     if (record) {
@@ -99,13 +121,15 @@ export default observer((props: Props) => {
           placeholder="请输入名称"
           allowClear
           onSearch={(val) => {
+            TreeModel.search = val;
             if (val) {
               TreeModel.param = {
                 terms: [{ column: 'name', value: `%${val}%`, termType: 'like' }],
               };
             } else {
-              TreeModel.param = {};
+              TreeModel.param = { terms: [] };
             }
+            handleSearch(TreeModel.param);
           }}
           style={{ width: '100%' }}
         />
@@ -133,6 +157,7 @@ export default observer((props: Props) => {
             height={500}
             selectedKeys={TreeModel.selectedKeys}
             defaultExpandAll
+            autoExpandParent
             switcherIcon={<DownOutlined />}
             fieldNames={{
               title: 'name',
@@ -196,7 +221,7 @@ export default observer((props: Props) => {
                                   runningState: 'running',
                                 });
                           if (resp.status === 200) {
-                            TreeModel.param = {};
+                            TreeModel.param = { terms: [] };
                             handleSearch(TreeModel.param);
                             onlyMessage('操作成功');
                           } else {
@@ -218,7 +243,7 @@ export default observer((props: Props) => {
                         onConfirm={async () => {
                           const resp = await service.removeCollector(i.id);
                           if (resp.status === 200) {
-                            TreeModel.param = {};
+                            TreeModel.param = { terms: [] };
                             handleSearch(TreeModel.param);
                             onlyMessage('操作成功');
                           }
@@ -242,7 +267,7 @@ export default observer((props: Props) => {
               </div>
             )}
             treeData={TreeModel.dataSource}
-          ></Tree>
+          />
         ) : (
           <Empty />
         )}
@@ -255,7 +280,7 @@ export default observer((props: Props) => {
           }}
           reload={() => {
             TreeModel.visible = false;
-            TreeModel.param = {};
+            TreeModel.param = { terms: [] };
             handleSearch(TreeModel.param);
           }}
         />

+ 15 - 1
src/pages/DataCollect/Collector/index.tsx

@@ -5,28 +5,42 @@ import CollectorTree from './components/Tree';
 import { observer } from '@formily/reactive-react';
 import { model } from '@formily/reactive';
 import Point from './components/Point';
+import { useEffect } from 'react';
+import useLocations from '@/hooks/route/useLocation';
 
 export const DataCollectModel = model<{
   provider: 'OPC_UA' | 'MODBUS_TCP';
   data: any;
   reload: boolean;
   refresh: boolean;
+  channelId: string;
 }>({
   provider: 'MODBUS_TCP',
   data: {},
   reload: false,
   refresh: false,
+  channelId: '',
 });
 
 export default observer(() => {
+  const location = useLocations();
+
+  useEffect(() => {
+    if (location.state?.channelId) {
+      DataCollectModel.channelId = location.state.channelId;
+    } else {
+      DataCollectModel.channelId = '';
+    }
+  }, [location.state]);
   return (
     <PageContainer>
       <Card bordered={false} bodyStyle={{ paddingTop: 0 }}>
         <div className={styles.container}>
           <div className={styles.left}>
             <CollectorTree
+              channelId={DataCollectModel.channelId}
               change={(data) => {
-                DataCollectModel.provider = data.provider;
+                DataCollectModel.provider = data?.provider;
                 DataCollectModel.data = data || {};
               }}
             />

+ 126 - 62
src/pages/device/Firmware/Task/Detail/index.tsx

@@ -1,9 +1,8 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { observer } from '@formily/react';
-import { Badge, Card, message, Popconfirm } from 'antd';
+import { Badge, Card, message } from 'antd';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { Tooltip } from 'antd';
 import { useEffect, useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { RedoOutlined, SearchOutlined } from '@ant-design/icons';
@@ -15,6 +14,8 @@ import styles from './index.less';
 import { model } from '@formily/reactive';
 import { useParams } from 'umi';
 import Details from './Details/index';
+import { PermissionButton } from '@/components';
+import usePermissions from '@/hooks/permission';
 
 const colorMap = new Map();
 colorMap.set('waiting', '#FF9000');
@@ -47,6 +48,7 @@ const Detail = observer(() => {
   const params = useParams<any>();
   const [visible, setVisible] = useState<boolean>(false);
   const [reason, setReason] = useState<string>('');
+  const { permission } = usePermissions('device/Firmware');
 
   const buttonImg = require('/public/images/firmware/button.png');
 
@@ -273,44 +275,81 @@ const Detail = observer(() => {
       render: (text: any, record: any) =>
         record?.state?.value === 'failed'
           ? [
-              <a
+              // <a
+              //   onClick={() => {
+              //     setVisible(true);
+              //     setReason(record?.errorReason || '');
+              //   }}
+              //   key="link"
+              // >
+              //   <Tooltip
+              //     title={intl.formatMessage({
+              //       id: 'pages.data.option.detail',
+              //       defaultMessage: '查看',
+              //     })}
+              //     key={'detail'}
+              //   >
+              //     <SearchOutlined />
+              //   </Tooltip>
+              // </a>,
+              <PermissionButton
+                key="link"
+                type={'link'}
+                style={{ padding: 0 }}
+                isPermission={permission.update}
+                tooltip={{
+                  title: '重试',
+                }}
                 onClick={() => {
                   setVisible(true);
                   setReason(record?.errorReason || '');
                 }}
-                key="link"
               >
-                <Tooltip
-                  title={intl.formatMessage({
-                    id: 'pages.data.option.detail',
-                    defaultMessage: '查看',
-                  })}
-                  key={'detail'}
-                >
-                  <SearchOutlined />
-                </Tooltip>
-              </a>,
+                <SearchOutlined />
+              </PermissionButton>,
               <>
                 {state.info?.mode?.value === 'push' ? (
-                  <Popconfirm
-                    key="refresh"
-                    onConfirm={async () => {
-                      const resp = await service.startOneTask([record.id]);
-                      if (resp.status === 200) {
-                        message.success('操作成功!');
-                        handleSearch();
-                        actionRef.current?.reload?.();
-                      }
+                  <PermissionButton
+                    key="patch"
+                    type={'link'}
+                    style={{ padding: 0 }}
+                    isPermission={permission.update}
+                    tooltip={{
+                      title: '重试',
+                    }}
+                    popConfirm={{
+                      title: '确认重试',
+                      onConfirm: async () => {
+                        const resp = await service.startOneTask([record.id]);
+                        if (resp.status === 200) {
+                          message.success('操作成功!');
+                          handleSearch();
+                          actionRef.current?.reload?.();
+                        }
+                      },
                     }}
-                    title={'确认重试'}
                   >
-                    <a>
-                      <Tooltip title={'重试'} key={'refresh'}>
-                        <RedoOutlined />
-                      </Tooltip>
-                    </a>
-                  </Popconfirm>
-                ) : null}
+                    <RedoOutlined />
+                  </PermissionButton>
+                ) : // <Popconfirm
+                //   key="refresh"
+                //   onConfirm={async () => {
+                //     const resp = await service.startOneTask([record.id]);
+                //     if (resp.status === 200) {
+                //       message.success('操作成功!');
+                //       handleSearch();
+                //       actionRef.current?.reload?.();
+                //     }
+                //   }}
+                //   title={'确认重试'}
+                // >
+                //   <a>
+                //     <Tooltip title={'重试'} key={'refresh'}>
+                //       <RedoOutlined />
+                //     </Tooltip>
+                //   </a>
+                // </Popconfirm>
+                null}
               </>,
             ]
           : [],
@@ -326,44 +365,69 @@ const Detail = observer(() => {
               <div className={styles.firmwareDetailCard}>
                 <div className={styles.firmwareDetailCardHeader}>
                   <div className={styles.firmwareDetailCardTitle}>
-                    <Badge color={colorMap.get(item.key)} />
+                    <Badge color={colorMap.get(item.key)} style={{ marginRight: 5 }} />
                     {item.name}
                   </div>
                   <div className={styles.firmwareDetailCardRight}>
                     {item.key === 'error' && state.info?.mode?.value === 'push' && (
-                      <Popconfirm
-                        title="确认批量重试"
-                        onConfirm={async () => {
-                          const resp = await service.startTask(params.id, ['failed']);
-                          if (resp.status === 200) {
-                            message.success('操作成功!');
-                            queryFailed();
-                            actionRef.current?.reload?.();
-                          }
+                      // <Popconfirm
+                      //   title="确认批量重试"
+                      //   onConfirm={async () => {
+                      //     const resp = await service.startTask(params.id, ['failed']);
+                      //     if (resp.status === 200) {
+                      //       message.success('操作成功!');
+                      //       queryFailed();
+                      //       actionRef.current?.reload?.();
+                      //     }
+                      //   }}
+                      // >
+                      //   <a>批量重试</a>
+                      // </Popconfirm>
+                      <PermissionButton
+                        key="patch"
+                        type={'link'}
+                        style={{ padding: 0 }}
+                        isPermission={permission.update}
+                        tooltip={{
+                          title: '批量重试',
+                        }}
+                        popConfirm={{
+                          title: '确认批量重试',
+                          onConfirm: async () => {
+                            const resp = await service.startTask(params.id, ['failed']);
+                            if (resp.status === 200) {
+                              message.success('操作成功!');
+                              queryFailed();
+                              actionRef.current?.reload?.();
+                            }
+                          },
                         }}
                       >
-                        <a>批量重试</a>
-                      </Popconfirm>
+                        批量重试
+                      </PermissionButton>
                     )}
-                    <div className={styles.firmwareDetailCardRefresh}>
-                      <img
-                        style={{ width: '100%' }}
-                        src={buttonImg}
-                        onClick={() => {
-                          if (item.key === 'waiting') {
-                            queryWaiting();
-                          } else if (item.key === 'finish') {
-                            querySuccess();
-                          } else if (item.key === 'loading') {
-                            queryProcessing();
-                          } else if (item.key === 'canceled') {
-                            queryCancel();
-                          } else {
-                            queryFailed();
-                          }
-                        }}
-                      />
-                    </div>
+                    <PermissionButton
+                      key="patch1"
+                      style={{ padding: 0, backgroundColor: 'inherit', border: 0 }}
+                      isPermission={permission.update}
+                      onClick={() => {
+                        if (item.key === 'waiting') {
+                          queryWaiting();
+                        } else if (item.key === 'finish') {
+                          querySuccess();
+                        } else if (item.key === 'loading') {
+                          queryProcessing();
+                        } else if (item.key === 'canceled') {
+                          queryCancel();
+                        } else {
+                          queryFailed();
+                        }
+                      }}
+                    >
+                      <div className={styles.firmwareDetailCardRefresh}>
+                        <img style={{ width: '100%' }} src={buttonImg} />
+                      </div>
+                    </PermissionButton>
                   </div>
                 </div>
                 <div

+ 1 - 1
src/pages/device/Firmware/Task/Save/index.tsx

@@ -23,7 +23,7 @@ const Save = (props: Props) => {
   const devices = useRef<DeviceInstance[]>([]);
 
   useEffect(() => {
-    console.log(data);
+    // console.log(data);
     if (data && data.id) {
       setMode(data.mode.value);
     }

+ 29 - 26
src/pages/device/Firmware/Task/index.tsx

@@ -1,7 +1,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { message, Popconfirm, Tooltip } from 'antd';
+import { message, Tooltip } from 'antd';
 import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import {
@@ -131,7 +131,7 @@ const Task = observer(() => {
           key={'api'}
           type={'link'}
           style={{ padding: 0 }}
-          isPermission={true}
+          isPermission={permission.view}
           tooltip={{
             title: '详情',
           }}
@@ -142,29 +142,39 @@ const Task = observer(() => {
         >
           <AIcon type={'icon-details'} />
         </PermissionButton>,
-        <a
+        <PermissionButton
+          key="link"
+          type={'link'}
+          style={{ padding: 0 }}
+          isPermission={permission.view}
+          tooltip={{
+            title: '查看',
+          }}
           onClick={() => {
             state.visible = true;
             state.current = record;
           }}
-          key="link"
         >
-          <Tooltip title="查看" key={'detail'}>
-            <EyeOutlined />
-          </Tooltip>
-        </a>,
+          <EyeOutlined />
+        </PermissionButton>,
         <UpgradeBtn data={record} actions={actionRef.current} key="btn" />,
-        <a key="delete">
-          <Popconfirm
-            title={
+        <PermissionButton
+          key="delete"
+          type={'link'}
+          style={{ padding: 0 }}
+          isPermission={permission.delete}
+          tooltip={{
+            title: '删除',
+          }}
+          popConfirm={{
+            title:
               record.waiting > 0 || record.processing > 0
                 ? '删除将导致正在进行的任务终止,确定要删除吗?'
                 : intl.formatMessage({
                     id: 'pages.data.option.remove.tips',
                     defaultMessage: '确认删除?',
-                  })
-            }
-            onConfirm={async () => {
+                  }),
+            onConfirm: async () => {
               const resp = await service.deleteTask(record.id);
               if (resp.status === 200) {
                 onlyMessage(
@@ -177,18 +187,11 @@ const Task = observer(() => {
               } else {
                 message.error(resp?.message || '删除失败!');
               }
-            }}
-          >
-            <Tooltip
-              title={intl.formatMessage({
-                id: 'pages.data.option.remove',
-                defaultMessage: '删除',
-              })}
-            >
-              <DeleteOutlined />
-            </Tooltip>
-          </Popconfirm>
-        </a>,
+            },
+          }}
+        >
+          <DeleteOutlined />
+        </PermissionButton>,
       ],
     },
   ];

+ 1 - 1
src/pages/device/Firmware/index.tsx

@@ -126,7 +126,7 @@ const Firmware = observer(() => {
         <PermissionButton
           style={{ padding: 0 }}
           type="link"
-          isPermission={permission.action}
+          isPermission={permission.view}
           key="upgrade"
           onClick={() => {
             //缓存路由参数

+ 111 - 29
src/pages/device/Instance/Detail/ChildDevice/index.tsx

@@ -1,7 +1,7 @@
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import type { LogItem } from '@/pages/device/Instance/Detail/Log/typings';
-import { Badge, Button, Card, Popconfirm, Tooltip } from 'antd';
+import { Badge, Card, Tooltip } from 'antd';
 import { DisconnectOutlined, EditOutlined, SearchOutlined } from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { InstanceModel, service } from '@/pages/device/Instance';
@@ -14,6 +14,7 @@ import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import { useDomFullHeight } from '@/hooks';
 import { onlyMessage } from '@/utils/util';
 import SaveChild from './SaveChild';
+import PermissionButton from '../../../../../components/PermissionButton';
 
 const statusMap = new Map();
 statusMap.set('online', 'success');
@@ -32,6 +33,7 @@ const ChildDevice = (props: Props) => {
   const [bindKeys, setBindKeys] = useState<any[]>([]);
   const [childVisible, setChildVisible] = useState<boolean>(false);
   const [current, setCurrent] = useState<any>({});
+  const devicePermission = PermissionButton.usePermission('device/Instance').permission;
 
   const { minHeight } = useDomFullHeight(`.device-detail-childDevice`);
 
@@ -149,30 +151,63 @@ const ChildDevice = (props: Props) => {
             <SearchOutlined />
           </Tooltip>
         </Link>,
-        <a key="unbind">
-          <Popconfirm
-            onConfirm={() => {
+        // <a key="unbind">
+        //   <Popconfirm
+        //     onConfirm={() => {
+        //       unBindSingleDevice(record.id);
+        //     }}
+        //     title={'确认解绑吗?'}
+        //   >
+        //     <Tooltip title={'解绑'}>
+        //       <DisconnectOutlined />
+        //     </Tooltip>
+        //   </Popconfirm>
+        // </a>,
+        <PermissionButton
+          key="unbind"
+          type={'link'}
+          popConfirm={{
+            title: '确认解绑吗?',
+            onConfirm: async () => {
               unBindSingleDevice(record.id);
-            }}
-            title={'确认解绑吗?'}
-          >
-            <Tooltip title={'解绑'}>
-              <DisconnectOutlined />
-            </Tooltip>
-          </Popconfirm>
-        </a>,
+            },
+          }}
+          tooltip={{
+            title: devicePermission.update ? '解绑' : '暂无权限,请联系管理员',
+          }}
+          style={{ padding: 0 }}
+          isPermission={devicePermission.update}
+        >
+          <DisconnectOutlined />
+        </PermissionButton>,
         <>
           {props.data.accessProvider === 'official-edge-gateway' && (
-            <a
+            // <a
+            //   onClick={() => {
+            //     setCurrent(record);
+            //     setChildVisible(true);
+            //   }}
+            // >
+            //   <Tooltip title={'编辑'} key={'edit'}>
+            //     <EditOutlined />
+            //   </Tooltip>
+            // </a>
+            <PermissionButton
+              key={'edit'}
+              type={'link'}
               onClick={() => {
                 setCurrent(record);
                 setChildVisible(true);
               }}
+              tooltip={{
+                placement: 'top',
+                title: devicePermission.update ? '编辑' : '暂无权限,请联系管理员',
+              }}
+              style={{ padding: 0 }}
+              isPermission={devicePermission.update}
             >
-              <Tooltip title={'编辑'} key={'edit'}>
-                <EditOutlined />
-              </Tooltip>
-            </a>
+              <EditOutlined />
+            </PermissionButton>
           )}
         </>,
       ],
@@ -227,32 +262,79 @@ const ChildDevice = (props: Props) => {
             toolBarRender={() => [
               <>
                 {props.data.accessProvider === 'official-edge-gateway' && (
-                  <Button
+                  // <Button
+                  //   onClick={() => {
+                  //     // actionRef.current?.reset?.();
+                  //     setCurrent({});
+                  //     setChildVisible(true);
+                  //   }}
+                  //   key="save"
+                  //   type="primary"
+                  // >
+                  //   新增并绑定
+                  // </Button>
+                  <PermissionButton
+                    key={'edit'}
+                    type={'primary'}
                     onClick={() => {
-                      // actionRef.current?.reset?.();
                       setCurrent({});
                       setChildVisible(true);
                     }}
-                    key="save"
-                    type="primary"
+                    tooltip={{
+                      placement: 'top',
+                      title: devicePermission.update ? '' : '暂无权限,请联系管理员',
+                    }}
+                    isPermission={devicePermission.update}
                   >
                     新增并绑定
-                  </Button>
+                  </PermissionButton>
                 )}
               </>,
-              <Button
+              // <Button
+              //   onClick={() => {
+              //     setVisible(true);
+              //     actionRef.current?.reset?.();
+              //   }}
+              //   key="bind"
+              //   type="primary"
+              // >
+              //   绑定
+              // </Button>,
+              <PermissionButton
+                key={'bind'}
+                type={'primary'}
                 onClick={() => {
                   setVisible(true);
                   actionRef.current?.reset?.();
                 }}
-                key="bind"
-                type="primary"
+                tooltip={{
+                  placement: 'top',
+                  title: devicePermission.update ? '' : '暂无权限,请联系管理员',
+                }}
+                isPermission={devicePermission.update}
               >
                 绑定
-              </Button>,
-              <Popconfirm key="unbind" onConfirm={handleUnBind} title={'确认解绑吗?'}>
-                <Button>批量解绑</Button>
-              </Popconfirm>,
+              </PermissionButton>,
+              // <Popconfirm key="unbind" onConfirm={handleUnBind} title={'确认解绑吗?'}>
+              //   <Button>批量解绑</Button>
+              // </Popconfirm>,
+              <PermissionButton
+                key={'unbind'}
+                type={'primary'}
+                popConfirm={{
+                  title: '确认解绑吗?',
+                  onConfirm: async () => {
+                    handleUnBind();
+                  },
+                }}
+                tooltip={{
+                  placement: 'top',
+                  title: devicePermission.update ? '' : '暂无权限,请联系管理员',
+                }}
+                isPermission={devicePermission.update}
+              >
+                批量解绑
+              </PermissionButton>,
             ]}
             pagination={{
               pageSize: 10,

+ 1 - 1
src/pages/device/Instance/Detail/Running/Property/FileComponent/index.tsx

@@ -33,7 +33,7 @@ export const getType = (url: string) => {
   let t: string = '';
   [...imgList, ...videoList, ...fileList].map((item) => {
     const str = item.slice(1, item.length);
-    if (url.indexOf(str) !== -1) {
+    if (url && String(url).indexOf(str) !== -1) {
       if (imgList.includes(item)) {
         t = 'img';
       } else if (videoList.includes(item)) {

+ 30 - 7
src/pages/device/Instance/Detail/Running/Property/PropertyCard.tsx

@@ -16,6 +16,7 @@ import Indicators from './Indicators';
 import './PropertyCard.less';
 import FileComponent from './FileComponent';
 import { onlyMessage } from '@/utils/util';
+import PermissionButton from '../../../../../../components/PermissionButton';
 
 interface Props {
   data: Partial<PropertyMetadata>;
@@ -26,6 +27,7 @@ const Property = (props: Props) => {
   const { data, value } = props;
 
   const params = useParams<{ id: string }>();
+  const devicePermission = PermissionButton.usePermission('device/Instance').permission;
 
   const [loading, setLoading] = useState<boolean>(false);
   const refreshProperty = async () => {
@@ -53,13 +55,20 @@ const Property = (props: Props) => {
         </div>
         <Space style={{ fontSize: 12 }}>
           {data.expands?.type?.includes('write') && (
-            <Tooltip placement="top" title="设置属性至设备">
-              <EditOutlined
-                onClick={() => {
-                  setEditVisible(true);
-                }}
-              />
-            </Tooltip>
+            <PermissionButton
+              key={'edit'}
+              onClick={() => {
+                setEditVisible(true);
+              }}
+              tooltip={{
+                placement: 'top',
+                title: devicePermission.update ? '设置属性至设备' : '暂无权限,请联系管理员',
+              }}
+              style={{ padding: 0, border: 'none', backgroundColor: 'inherit' }}
+              isPermission={devicePermission.update}
+            >
+              <EditOutlined />
+            </PermissionButton>
           )}
           {(data.expands?.metrics || []).length > 0 &&
             ['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
@@ -72,6 +81,20 @@ const Property = (props: Props) => {
                   }}
                 />
               </Tooltip>
+              // <PermissionButton
+              //   key={'metrics'}
+              //   onClick={() => {
+              //     setIndicatorVisible(true);
+              //   }}
+              //   tooltip={{
+              //     placement: "top",
+              //     title: devicePermission.update ? "指标" : '暂无权限,请联系管理员'
+              //   }}
+              //   style={{ padding: 0, border: "none", backgroundColor: 'inherit' }}
+              //   isPermission={devicePermission.update}
+              // >
+              //   <ClockCircleOutlined  />
+              // </PermissionButton>
             )}
           {data.expands?.type?.includes('read') && (
             <Tooltip placement="top" title="获取最新属性值">

+ 34 - 10
src/pages/device/Instance/Detail/Running/Property/index.tsx

@@ -21,6 +21,7 @@ import PropertyTable from './PropertyTable';
 import { onlyMessage } from '@/utils/util';
 import Indicators from './Indicators';
 import { Empty } from '@/components';
+import PermissionButton from '../../../../../../components/PermissionButton';
 
 interface Props {
   data: Partial<PropertyMetadata>[];
@@ -51,6 +52,7 @@ const Property = (props: Props) => {
     pageSize: 8,
     currentPage: 0,
   });
+  const devicePermission = PermissionButton.usePermission('device/Instance').permission;
   const [indicatorVisible, setIndicatorVisible] = useState<boolean>(false);
   const [loading, setLoading] = useState<boolean>(true);
 
@@ -94,16 +96,22 @@ const Property = (props: Props) => {
       render: (text: any, record: any) => (
         <Space size="middle">
           {record.expands?.type?.includes('write') && (
-            <Tooltip placement="top" title="设置属性至设备">
-              <a
-                onClick={() => {
-                  setVisible(true);
-                  setCurrentInfo(record);
-                }}
-              >
-                <EditOutlined />
-              </a>
-            </Tooltip>
+            <PermissionButton
+              type={'link'}
+              onClick={() => {
+                setVisible(true);
+                setCurrentInfo(record);
+              }}
+              tooltip={{
+                placement: 'top',
+                title: devicePermission.update ? '设置属性至设备' : '暂无权限,请联系管理员',
+              }}
+              style={{ padding: 0 }}
+              key={'edit'}
+              isPermission={devicePermission.update}
+            >
+              <EditOutlined />
+            </PermissionButton>
           )}
           {(record.expands?.metrics || []).length > 0 &&
             ['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
@@ -119,6 +127,22 @@ const Property = (props: Props) => {
                   <ClockCircleOutlined />
                 </a>
               </Tooltip>
+              // <PermissionButton
+              //   type={'link'}
+              //   onClick={() => {
+              //     setVisible(true);
+              //     setCurrentInfo(record);
+              //   }}
+              //   tooltip={{
+              //     placement: "top",
+              //     title: devicePermission.update ? "指标" : '暂无权限,请联系管理员'
+              //   }}
+              //   style={{ padding: 0 }}
+              //   key={'edit'}
+              //   isPermission={devicePermission.update}
+              // >
+              //   <ClockCircleOutlined />
+              // </PermissionButton>
             )}
           {record.expands?.type?.includes('read') && (
             <Tooltip placement="top" title="获取最新属性值">

+ 17 - 7
src/pages/device/Instance/Detail/Tags/Edit.tsx

@@ -258,18 +258,28 @@ const Edit = (props: Props) => {
           props.close();
         } else {
           const list = (values?.tags || [])
-            .filter((item: any) => item?.key)
+            .filter((item: any) => item?.key && item?.value)
             .map((i: any) => {
               const { dataType, ...extra } = i;
               return { ...extra };
             });
-          const resp = await service.saveTags(InstanceModel.detail?.id || '', list);
-          if (resp.status === 200) {
-            props.refresh();
-            // InstanceModel.detail = { ...InstanceModel.detail, tags: values.tags };
-            onlyMessage('操作成功!');
-            props.close();
+          if (list.length) {
+            // 填值
+            const resp = await service.saveTags(InstanceModel.detail?.id || '', list);
+            if (resp.status === 200) {
+              onlyMessage('操作成功!');
+            }
           }
+          const _list = (values?.tags || []).filter((item: any) => item?.key && !item?.value);
+          if (_list.length) {
+            // 删除值
+            _list.map(async (item: any) => {
+              if (item.id) {
+                await service.delTags(InstanceModel.detail?.id || '', item.id);
+              }
+            });
+          }
+          props.refresh();
         }
       }}
     >

+ 1 - 0
src/pages/device/Instance/Detail/Tags/index.tsx

@@ -68,6 +68,7 @@ const Tags = () => {
       {visible && (
         <Edit
           refresh={() => {
+            setVisible(false);
             getDetail();
           }}
           close={() => {

+ 3 - 3
src/pages/device/components/Empty/index.tsx

@@ -14,11 +14,11 @@ export default () => {
 
   if (!isIndependent) {
     if (!permission.update) {
-      description = <span>请联系管理员配置物模型属性</span>;
+      description = <span>请联系管理员配置对应产品的物模型功能</span>;
     } else {
       description = (
         <span>
-          暂无数据, 请前往产品配置
+          请配置对应产品的
           <Button
             style={{ margin: 0, padding: '0 4px' }}
             type={'link'}
@@ -26,7 +26,7 @@ export default () => {
               history.push(`${path}?key=metadata`);
             }}
           >
-            物模型
+            物模型属性功能
           </Button>
         </span>
       );

+ 1 - 0
src/pages/device/components/Metadata/Base/Edit/index.tsx

@@ -562,6 +562,7 @@ const Edit = observer((props: Props) => {
             'x-component': 'Select',
             enum: PropertySource,
             'x-visible': props.type === 'product',
+            default: 'device',
           },
           'virtualRule.type': {
             type: 'string',

+ 2 - 2
src/pages/device/components/Metadata/Base/index.tsx

@@ -93,7 +93,7 @@ const BaseMetadata = observer((props: Props) => {
             }
           }}
           tooltip={{
-            title: operateLimits('add', type) ? '暂不支持' : '编辑',
+            title: operateLimits('updata', type) ? '当前的存储方式不支持编辑' : '编辑',
           }}
         >
           <EditOutlined />
@@ -211,7 +211,7 @@ const BaseMetadata = observer((props: Props) => {
             icon={<PlusOutlined />}
             type="primary"
             tooltip={{
-              title: operateLimits('add', type) ? '暂不支持' : '新增',
+              title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增',
             }}
           >
             {intl.formatMessage({

+ 1 - 1
src/pages/device/components/Metadata/Cat/index.tsx

@@ -40,7 +40,7 @@ const Cat = observer((props: Props) => {
       } else {
         service.detail(id).then((resp) => {
           setLoading(false);
-          productModel.current = resp.result;
+          // productModel.current = resp.result;
           setValue(resp.result.metadata);
         });
       }

+ 8 - 8
src/pages/edge/Resource/Issue/Result.tsx

@@ -24,13 +24,13 @@ const Publish = (props: Props) => {
     const errMessages: any[] = [];
     const _terms = {
       deviceId: (props.list || []).map((item) => item.id),
-      // params: {
-      name: props.data.name,
-      targetId: props.data.targetId,
-      targetType: props.data.targetType,
-      category: props.data.category,
-      metadata: encodeURIComponent(props.data?.metadata || ''),
-      // }
+      params: JSON.stringify({
+        name: props.data.name,
+        targetId: props.data.targetId,
+        targetType: props.data.targetType,
+        category: props.data.category,
+        metadata: encodeURIComponent(props.data?.metadata || ''),
+      }),
     };
     const url = new URLSearchParams();
     Object.keys(_terms).forEach((key) => {
@@ -45,7 +45,7 @@ const Publish = (props: Props) => {
     const source = new EventSourcePolyfill(
       `/${
         SystemConst.API_BASE
-      }/edge/operations/entity-template-save/invoke/_batch?:X_Access_Token=${Token.get()}&${url.toString()}`,
+      }/edge/operations/entity-template-save/invoke/_batch?:X_Access_Token=${Token.get()}&${url}`,
     );
     source.onmessage = (e: any) => {
       const res = JSON.parse(e.data);

+ 2 - 0
src/pages/iot-card/CardManagement/ExportModal.tsx

@@ -6,6 +6,7 @@ import moment from 'moment';
 
 type ExportModalType = {
   onCancel: () => void;
+  onOk: () => void;
   keys: string[];
 };
 
@@ -26,6 +27,7 @@ const ExportModal = (props: ExportModalType) => {
           `物联卡管理-${moment(new Date()).format('YYYY/MM/DD HH:mm:ss')}`,
           type.current,
         );
+        props.onOk?.();
       }
     });
   };

+ 3 - 0
src/pages/iot-card/CardManagement/index.tsx

@@ -507,6 +507,9 @@ const CardManagementNode = () => {
           onCancel={() => {
             setExportVisible(false);
           }}
+          onOk={() => {
+            setExportVisible(false);
+          }}
         />
       )}
       {importVisible && (

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

@@ -274,6 +274,8 @@ const Cascade = () => {
               if (resp?.status === 200) {
                 onlyMessage('操作成功!');
                 actionRef.current?.reset?.();
+              } else {
+                onlyMessage('操作失败!', 'error');
               }
             },
           }}

+ 8 - 0
src/pages/media/Stream/index.tsx

@@ -194,6 +194,14 @@ const Stream = () => {
                           </PermissionButton>,
                           <PermissionButton
                             isPermission={permission.delete}
+                            tooltip={
+                              item?.state?.value === 'enabled'
+                                ? {
+                                    title: '正常的流媒体服务不能删除',
+                                  }
+                                : undefined
+                            }
+                            disabled={item?.state?.value === 'enabled'}
                             popConfirm={{
                               title: '确认删除',
                               onConfirm: () => {

+ 13 - 3
src/pages/notice/Config/Debug/index.tsx

@@ -1,5 +1,5 @@
 import { Modal } from 'antd';
-import { useMemo, useRef } from 'react';
+import { useMemo, useRef, useState } from 'react';
 import { createForm, Field, onFieldReact, onFieldValueChange } from '@formily/core';
 import { createSchemaField, observer } from '@formily/react';
 import {
@@ -25,6 +25,7 @@ const Debug = observer(() => {
   // const location = useLocation<{ id: string }>();
   const id = state.current?.type; // (location as any).query?.id;
   const variableRef = useRef<any>([]);
+  const [loading, setLoading] = useState(false);
 
   const form = useMemo(
     () =>
@@ -44,6 +45,12 @@ const Debug = observer(() => {
                   state1.visible = true;
                   state1.value = _template?.variableDefinitions;
                 });
+              } else {
+                variableRef.current = [];
+                form1.setFieldState('variableDefinitions', (state1) => {
+                  state1.visible = true;
+                  state1.value = undefined;
+                });
               }
             });
 
@@ -162,6 +169,7 @@ const Debug = observer(() => {
       variableDefinitions: {
         title: '变量',
         type: 'string',
+        required: true,
         'x-decorator': 'FormItem',
         'x-component': 'ArrayTable',
         'x-component-props': {
@@ -230,7 +238,7 @@ const Debug = observer(() => {
     const templateId = data.templateId;
     // const list = Store.get('notice-template-list');
     // const _template = list.find((item: any) => item.id === templateId);
-
+    setLoading(true);
     const resp = await service.debug(
       state?.current.id,
       templateId,
@@ -248,8 +256,9 @@ const Debug = observer(() => {
     );
     if (resp.status === 200) {
       onlyMessage('操作成功!');
-      state.debug = false;
     }
+    state.debug = false;
+    setLoading(false);
   };
   return (
     <Modal
@@ -258,6 +267,7 @@ const Debug = observer(() => {
       visible={state.debug}
       onCancel={() => (state.debug = false)}
       onOk={start}
+      confirmLoading={loading}
     >
       <Form form={form} layout={'vertical'}>
         <SchemaField schema={schema} scope={{ getTemplate, useAsyncDataSource }} />

+ 4 - 1
src/pages/notice/Config/Detail/index.tsx

@@ -695,7 +695,10 @@ const Detail = observer(() => {
         'x-decorator': 'FormItem',
         'x-component': 'Input.TextArea',
         'x-component-props': {
-          rows: 4,
+          rows: 5,
+          placeholder: '请输入说明',
+          showCount: true,
+          maxLength: 200,
         },
         'x-validator': [
           {

+ 7 - 4
src/pages/notice/Template/Detail/index.tsx

@@ -174,7 +174,7 @@ const Detail = observer(() => {
               state1.dataSource = typeList[value];
             });
           });
-          onFieldValueChange('provider', (field, form1) => {
+          onFieldValueChange('provider', async (field, form1) => {
             const value = field.value;
             if (field.modified) {
               form1.setValuesIn('configId', null);
@@ -182,10 +182,10 @@ const Detail = observer(() => {
             }
             const _type = field.query('type').value();
             // 设置绑定配置的数据
+            const _list = await getConfig(_type, value);
             form1.setFieldState('configId', async (state1) => {
-              state1.dataSource = await getConfig(_type, value);
+              state1.dataSource = _list;
             });
-            console.log(_type, value);
             if (_type === 'email') {
               setProviderItem('embedded');
             } else {
@@ -1637,7 +1637,10 @@ const Detail = observer(() => {
         'x-decorator': 'FormItem',
         'x-component': 'Input.TextArea',
         'x-component-props': {
-          rows: 4,
+          rows: 5,
+          placeholder: '请输入说明',
+          showCount: true,
+          maxLength: 200,
         },
         'x-validator': [
           {

+ 1 - 4
src/pages/notice/Template/index.tsx

@@ -113,10 +113,7 @@ const Template = observer(() => {
           tooltip={{ title: '导出' }}
           isPermission={templatePermission.export}
           onClick={() => {
-            downloadObject(
-              record,
-              `${record.name}-${moment(new Date()).format('YYYY/MM/DD HH:mm:ss')}`,
-            );
+            downloadObject(record, `${record.name}-${moment(new Date()).format('YYYY_MM_DD')}`);
           }}
         >
           <ArrowDownOutlined />

+ 2 - 0
src/pages/rule-engine/Instance/Save/index.tsx

@@ -74,6 +74,8 @@ const Save = (props: Props) => {
         'x-component-props': {
           rows: 5,
           placeholder: '请输入说明',
+          showCount: true,
+          maxLength: 200,
         },
         'x-validator': [
           {

+ 1 - 1
src/pages/rule-engine/Scene/Save/action/DeviceOutput/index.tsx

@@ -187,7 +187,7 @@ export default observer((props: Props) => {
   }, [props.value]);
 
   useEffect(() => {
-    const item = FormModel.current?.branches?.[0].then?.[0]?.actions?.[0].device?.productId;
+    const item = FormModel.current?.branches?.[0].then?.[0]?.actions?.[0]?.device?.productId;
     formProductIdRef.current = item;
     // console.log('---------', FormModel.current.options?.trigger?.name)
   }, []);

+ 6 - 3
src/pages/rule-engine/Scene/Save/components/Buttons/ParamsDropdown.tsx

@@ -111,7 +111,6 @@ export default (props: ParamsDropdownProps) => {
               value={_value}
               options={props.metricsOptions}
               onChange={(v, l) => {
-                console.log(l);
                 onValueChange(v, l);
                 setOpen(false);
               }}
@@ -218,7 +217,11 @@ export default (props: ParamsDropdownProps) => {
           setLabel(v ? '是' : '否');
           break;
         case 'metric':
-          findLabel(v, props.metricsOptions || []);
+          props.metricsOptions?.forEach((item) => {
+            if (item.value === v) {
+              setLabel(item.label);
+            }
+          });
           break;
         case 'enum':
         case 'object':
@@ -246,7 +249,7 @@ export default (props: ParamsDropdownProps) => {
       if (props.BuiltInOptions && props.value.source === 'upper') {
         valueLabel(_value, 'built');
       } else {
-        valueLabel(_value, props.valueType);
+        valueLabel(_value, props.isMetric ? 'metric' : props.valueType);
       }
     }
   }, [props.value, props.options, props.valueType, props.BuiltInOptions]);

+ 9 - 0
src/pages/rule-engine/Scene/Save/manual/index.tsx

@@ -13,8 +13,17 @@ export default () => {
             rules={[
               {
                 validator(_, v) {
+                  console.log(v);
                   if (!v || (v && !v.length)) {
                     return Promise.reject('至少配置一个执行动作');
+                  } else {
+                    let isActions = false;
+                    v.forEach((item: any) => {
+                      if (item.actions && item.actions.length) {
+                        isActions = true;
+                      }
+                    });
+                    return isActions ? Promise.resolve() : Promise.reject('至少配置一个执行动作');
                   }
                   return Promise.resolve();
                 },

+ 10 - 0
src/pages/rule-engine/Scene/Save/terms/branchItem.tsx

@@ -150,6 +150,16 @@ export default observer((props: BranchesItemProps) => {
                       validator(_, v) {
                         if (!v || (v && !v.length)) {
                           return Promise.reject('至少配置一个执行动作');
+                        } else {
+                          let isActions = false;
+                          v.forEach((item: any) => {
+                            if (item.actions && item.actions.length) {
+                              isActions = true;
+                            }
+                          });
+                          return isActions
+                            ? Promise.resolve()
+                            : Promise.reject('至少配置一个执行动作');
                         }
                         return Promise.resolve();
                       },

+ 8 - 0
src/pages/rule-engine/Scene/Save/terms/index.tsx

@@ -190,6 +190,14 @@ export default observer((props: Props) => {
               validator(_, v) {
                 if (!v || (v && !v.length)) {
                   return Promise.reject('至少配置一个执行动作');
+                } else {
+                  let isActions = false;
+                  v.forEach((item: any) => {
+                    if (item.actions && item.actions.length) {
+                      isActions = true;
+                    }
+                  });
+                  return isActions ? Promise.resolve() : Promise.reject('至少配置一个执行动作');
                 }
                 return Promise.resolve();
               },

+ 8 - 0
src/pages/rule-engine/Scene/Save/timer/index.tsx

@@ -82,6 +82,14 @@ export default observer((props: Props) => {
                   validator(_, v) {
                     if (!v || (v && !v.length)) {
                       return Promise.reject('至少配置一个执行动作');
+                    } else {
+                      let isActions = false;
+                      v.forEach((item: any) => {
+                        if (item.actions && item.actions.length) {
+                          isActions = true;
+                        }
+                      });
+                      return isActions ? Promise.resolve() : Promise.reject('至少配置一个执行动作');
                     }
                     return Promise.resolve();
                   },

+ 6 - 3
src/pages/system/Department/Tree/tree.tsx

@@ -154,10 +154,10 @@ export default (props: TreeProps) => {
 
       dig(searchTree);
 
-      const arr = ArrayToTree([...treeArray.values()]);
+      const arr = ArrayToTree(cloneDeep([...treeArray.values()]));
       setTreeData(arr);
     } else {
-      setTreeData([...TreeMap.values()]);
+      setTreeData(ArrayToTree(cloneDeep([...TreeMap.values()])));
     }
   };
 
@@ -271,7 +271,9 @@ export default (props: TreeProps) => {
         type="primary"
         isPermission={permission.add}
         onClick={() => {
-          setData({ sortIndex: treeData && treeData.length + 1 });
+          const sortIndex =
+            treeData && treeData.length ? treeData[treeData.length - 1].sortIndex + 1 : 1;
+          setData({ sortIndex });
           setVisible(true);
           setTreeDataList(treeData);
         }}
@@ -366,6 +368,7 @@ export default (props: TreeProps) => {
                           sortIndex: nodeData.children ? nodeData.children.length + 1 : 1,
                         });
                         setVisible(true);
+                        setTreeDataList(treeData);
                       }}
                     >
                       <PlusCircleOutlined />

+ 1 - 0
src/pages/system/Department/save.tsx

@@ -98,6 +98,7 @@ const Save = <T extends object>(props: SaveModalProps<T>) => {
           if (item) {
             if (item.children && item.children.length) {
               f.setFieldState(typeFiled.query('.sortIndex'), async (state) => {
+                console.log(item.children);
                 state.value = item.children[item.children.length - 1].sortIndex + 1;
               });
             } else {

+ 1 - 1
src/pages/system/Menu/Detail/edit.tsx

@@ -337,7 +337,7 @@ export default (props: EditProps) => {
                 saveData();
               }}
               loading={loading}
-              isPermission={getOtherPermission(['add', 'update'])}
+              isPermission={getOtherPermission(['update'])}
             >
               {intl.formatMessage({
                 // id: `pages.data.option.${disabled ? 'edit' : 'save'}`,

+ 33 - 40
src/pages/system/Menu/Setting/baseMenu.ts

@@ -56,7 +56,7 @@ export default [
         permissions: [],
         children: [
           {
-            code: 'notice',
+            code: 'notice/Config',
             name: '通知配置',
             owner: 'iot',
             //parentId: '1',
@@ -208,7 +208,7 @@ export default [
             ],
           },
           {
-            code: 'notice',
+            code: 'notice/Template',
             name: '通知模板',
             owner: 'iot',
             //parentId: '1',
@@ -1024,16 +1024,16 @@ export default [
                   },
                 ],
               },
-              {
-                id: 'action',
-                name: '启/禁用',
-                permissions: [
-                  {
-                    permission: 'protocol-supports',
-                    actions: ['enable', 'disable', 'query', 'save'],
-                  },
-                ],
-              },
+              // {
+              //   id: 'action',
+              //   name: '启/禁用',
+              //   permissions: [
+              //     {
+              //       permission: 'protocol-supports',
+              //       actions: ['enable', 'disable', 'query', 'save'],
+              //     },
+              //   ],
+              // },
               {
                 id: 'delete',
                 name: '删除',
@@ -1469,24 +1469,6 @@ export default [
                 ],
               },
               {
-                id: 'action',
-                name: '启/禁用',
-                permissions: [
-                  {
-                    permission: 'firmware-manager',
-                    actions: ['query'],
-                  },
-                  {
-                    permission: 'device-instance',
-                    actions: ['query'],
-                  },
-                  {
-                    permission: 'firmware-upgrade-task-manager',
-                    actions: ['query', 'deploy'],
-                  },
-                ],
-              },
-              {
                 id: 'delete',
                 name: '删除',
                 permissions: [
@@ -2872,6 +2854,16 @@ export default [
         permissions: [],
         buttons: [
           {
+            id: 'view',
+            name: '查看',
+            permissions: [
+              {
+                permission: 'system_config',
+                actions: ['query'],
+              },
+            ],
+          },
+          {
             id: 'update',
             name: '保存',
             permissions: [
@@ -3289,16 +3281,17 @@ export default [
               },
             ],
           },
-          {
-            id: 'setting',
-            name: '配置',
-            permissions: [
-              {
-                permission: 'menu',
-                actions: ['query', 'save', 'grant'],
-              },
-            ],
-          },
+          // 超管才具备该权限
+          // {
+          //   id: 'setting',
+          //   name: '配置',
+          //   permissions: [
+          //     {
+          //       permission: 'menu',
+          //       actions: ['query', 'save', 'grant'],
+          //     },
+          //   ],
+          // },
           {
             id: 'update',
             name: '编辑',

+ 8 - 4
src/pages/system/User/Save/index.tsx

@@ -23,6 +23,7 @@ import { service } from '@/pages/system/User';
 import { Modal, PermissionButton } from '@/components';
 import usePermissions from '@/hooks/permission';
 import { onlyMessage } from '@/utils/util';
+import { getMenuPathByCode } from '@/utils/menu';
 
 interface Props {
   model: 'add' | 'edit' | 'query';
@@ -43,6 +44,9 @@ const Save = (props: Props) => {
 
   const getOrg = () => service.queryOrgList({ paging: false });
 
+  const departmentPath = getMenuPathByCode('system/Department');
+  const rolePath = getMenuPathByCode('system/Role');
+
   const useAsyncDataSource = (api: any) => (field: Field) => {
     field.loading = true;
     api(field).then(
@@ -342,9 +346,9 @@ const Save = (props: Props) => {
                   type="primary"
                   ghost
                   style={{ padding: '0 8px' }}
-                  isPermission={rolePermission.add}
+                  isPermission={rolePermission.add && !!rolePath}
                   onClick={() => {
-                    const tab: any = window.open(`${origin}/#/system/role?save=true`);
+                    const tab: any = window.open(`${origin}/#${rolePath}?save=true`);
                     tab!.onTabSaveSuccess = (value: any) => {
                       form.setFieldState('roleIdList', async (state) => {
                         state.dataSource = await getRole().then((resp) =>
@@ -390,9 +394,9 @@ const Save = (props: Props) => {
                   type="primary"
                   ghost
                   style={{ padding: '0 8px' }}
-                  isPermission={deptPermission.add}
+                  isPermission={deptPermission.add && !!departmentPath}
                   onClick={() => {
-                    const tab: any = window.open(`${origin}/#/system/department?save=true`);
+                    const tab: any = window.open(`${origin}/#${departmentPath}?save=true`);
                     tab!.onTabSaveSuccess = (value: any) => {
                       form.setFieldState('orgIdList', async (state) => {
                         state.dataSource = await getOrg().then((resp) =>

+ 1 - 1
src/utils/util.ts

@@ -54,7 +54,7 @@ export const downloadObject = (record: Record<string, any>, fileName: string, fo
   // 创建隐藏的可下载链接
   const ghostLink = document.createElement('a');
   ghostLink.download = `${record?.name || ''}${fileName}_${moment(new Date()).format(
-    format || 'YYYY/MM/DD HH:mm:ss',
+    format || 'YYYY_MM_DD',
   )}.json`;
   ghostLink.style.display = 'none';
   //字符串内容转成Blob地址