Procházet zdrojové kódy

fix: (merge解决冲突)

sun-chaochao před 4 roky
rodič
revize
3d542a00cf
31 změnil soubory, kde provedl 655 přidání a 230 odebrání
  1. 4 0
      src/components/Player/ScreenPlayer.tsx
  2. 14 7
      src/components/Player/index.tsx
  3. 14 0
      src/components/ProTableCard/index.less
  4. 39 19
      src/components/ProTableCard/index.tsx
  5. 37 6
      src/components/SearchComponent/index.tsx
  6. 1 1
      src/locales/zh-CN/pages.ts
  7. 1 0
      src/pages/device/Instance/Detail/Diagnose/Message/Dialog/index.less
  8. 1 0
      src/pages/device/Instance/Detail/Diagnose/Message/index.tsx
  9. 111 11
      src/pages/device/Instance/index.tsx
  10. 3 0
      src/pages/device/Instance/service.ts
  11. 35 3
      src/pages/device/Product/Detail/BaseInfo/index.tsx
  12. 10 1
      src/pages/device/Product/Detail/index.tsx
  13. 61 0
      src/pages/device/Product/index.tsx
  14. 3 3
      src/pages/device/components/Metadata/Base/columns.ts
  15. 1 1
      src/pages/link/Protocol/index.tsx
  16. 5 5
      src/pages/link/Type/index.tsx
  17. 1 1
      src/pages/media/Cascade/index.tsx
  18. 44 35
      src/pages/media/Device/Playback/index.tsx
  19. 57 38
      src/pages/media/Device/Playback/timeLine.tsx
  20. 1 1
      src/pages/media/Device/Save/ProviderSelect.tsx
  21. 1 1
      src/pages/media/Device/Save/SaveProduct.tsx
  22. 4 2
      src/pages/notice/Template/Detail/index.tsx
  23. 1 1
      src/pages/rule-engine/Instance/index.tsx
  24. 1 0
      src/pages/rule-engine/Scene/Save/index.tsx
  25. 177 92
      src/pages/rule-engine/Scene/index.tsx
  26. 9 0
      src/pages/rule-engine/Scene/service.ts
  27. 1 0
      src/pages/rule-engine/Scene/typings.d.ts
  28. 4 0
      src/pages/system/Menu/Detail/buttons.tsx
  29. 11 1
      src/pages/system/Menu/Detail/edit.tsx
  30. 2 0
      src/pages/system/Role/index.tsx
  31. 1 1
      src/utils/BaseService.ts

+ 4 - 0
src/components/Player/ScreenPlayer.tsx

@@ -70,6 +70,7 @@ export default forwardRef((props: ScreenProps, ref) => {
   const [playerActive, setPlayerActive] = useState(0);
   const [historyList, setHistoryList] = useState<any>([]);
   const [visible, setVisible] = useState(false);
+  const [loading, setLoading] = useState(false);
 
   const fullscreenRef = useRef(null);
   const [isFullscreen, { setFull }] = useFullscreen(fullscreenRef);
@@ -189,7 +190,9 @@ export default forwardRef((props: ScreenProps, ref) => {
         players: players.map((item) => ({ deviceId: item.id, channelId: item.channelId })),
       }),
     };
+    setLoading(true);
     const resp = await service.history.save(DEFAULT_SAVE_CODE, param);
+    setLoading(false);
     if (resp.status === 200) {
       setVisible(false);
       getHistory();
@@ -347,6 +350,7 @@ export default forwardRef((props: ScreenProps, ref) => {
                       <Button
                         type={'primary'}
                         onClick={saveHistory}
+                        loading={loading}
                         style={{ width: '100%', marginTop: 16 }}
                       >
                         保存

+ 14 - 7
src/components/Player/index.tsx

@@ -1,4 +1,4 @@
-import { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
+import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
 import { isFunction } from 'lodash';
 
 export type PlayerProps = {
@@ -12,17 +12,18 @@ export type PlayerProps = {
   updateTime?: number;
   key?: string | number;
   loading?: boolean;
-  onDestroy?: () => void;
+  onDestroy?: (e?: any) => void;
   onMessage?: (msg: any) => void;
   onError?: (err: any) => void;
   onTimeUpdate?: (time: any) => void;
-  onPause?: () => void;
-  onPlay?: () => void;
+  onPause?: (e?: any) => void;
+  onPlay?: (e?: any) => void;
   protocol?: 'mp4' | 'flv' | 'hls';
   onFullscreen?: () => void;
   onSnapOutside?: (base64: any) => void;
   onSnapInside?: (base64: any) => void;
   onCustomButtons?: (name: any) => void;
+  onEnded?: (e?: any) => void;
   onClick?: () => void;
 };
 
@@ -36,9 +37,11 @@ const EventsEnum = {
   snapOutside: 'onSnapOutside',
   snapInside: 'onSnapInside',
   customButtons: 'onCustomButtons',
+  ended: 'onEnded',
 };
 const LivePlayer = forwardRef((props: PlayerProps, ref) => {
   const player = useRef<HTMLVideoElement>(null);
+  const [url, setUrl] = useState(props.url);
 
   useEffect(() => {
     return () => {
@@ -49,14 +52,18 @@ const LivePlayer = forwardRef((props: PlayerProps, ref) => {
     };
   }, []);
 
+  useEffect(() => {
+    setUrl(props.url);
+  }, [props.url]);
+
   /**
    * 事件初始化
    */
   const EventInit = () => {
     for (const key in EventsEnum) {
-      player.current?.addEventListener(key, () => {
+      player.current?.addEventListener(key, (e) => {
         if (EventsEnum[key] in props) {
-          props[EventsEnum[key]]();
+          props[EventsEnum[key]](e);
         }
       });
     }
@@ -84,7 +91,7 @@ const LivePlayer = forwardRef((props: PlayerProps, ref) => {
       hide-big-play-button={true}
       poster={props.poster || ''}
       timeout={props.timeout || 20}
-      video-url={props.url || ''}
+      video-url={url || ''}
     />
   );
 });

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

@@ -88,6 +88,20 @@
       display: none;
     }
   }
+
+  .mask-loading {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 99;
+    display: none;
+    width: 100%;
+    height: 100%;
+
+    &.show {
+      display: block;
+    }
+  }
 }
 
 .iot-card {

+ 39 - 19
src/components/ProTableCard/index.tsx

@@ -6,6 +6,7 @@ import { isFunction } from 'lodash';
 import { Empty, Pagination, Space } from 'antd';
 import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
 import classNames from 'classnames';
+import LoadingComponent from '@ant-design/pro-layout/es/PageLoading';
 import './index.less';
 
 enum ModelEnum {
@@ -36,12 +37,15 @@ const ProTableCard = <
   const [pageIndex, setPageIndex] = useState(0);
   const [pageSize, setPageSize] = useState(Default_Size * 2); // 每页条数
   const [column, setColumn] = useState(props.gridColumn || 4);
+  const [loading, setLoading] = useState(false);
+  const [dataLength, setDataLength] = useState<number>(0);
 
   /**
    * 处理 Card
    * @param dataSource
    */
   const handleCard = (dataSource: readonly T[] | undefined): JSX.Element => {
+    setDataLength(dataSource ? dataSource.length : 0);
     return (
       <>
         {dataSource && dataSource.length ? (
@@ -82,6 +86,11 @@ const ProTableCard = <
 
   const pageSizeOptions = [Default_Size * 2, Default_Size * 4, Default_Size * 8, Default_Size * 16];
 
+  useEffect(() => {
+    setCurrent(1);
+    setPageIndex(0);
+  }, [props.params]);
+
   return (
     <div className={'pro-table-card'}>
       <ProTable<T, U, ValueType>
@@ -112,6 +121,10 @@ const ProTableCard = <
           }
           return {};
         }}
+        onLoadingChange={(l) => {
+          console.log(l);
+          setLoading(!!l);
+        }}
         pagination={{
           onChange: (page, size) => {
             setCurrent(page);
@@ -163,25 +176,32 @@ const ProTableCard = <
         }
       />
       {model === ModelEnum.CARD && (
-        <Pagination
-          showSizeChanger
-          size="small"
-          className={'pro-table-card-pagination'}
-          total={total}
-          current={current}
-          onChange={(page, size) => {
-            setCurrent(page);
-            setPageIndex(page - 1);
-            setPageSize(size);
-          }}
-          pageSizeOptions={pageSizeOptions}
-          pageSize={pageSize}
-          showTotal={(num) => {
-            const minSize = pageIndex * pageSize + 1;
-            const MaxSize = (pageIndex + 1) * pageSize;
-            return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
-          }}
-        />
+        <>
+          <div className={classNames('mask-loading', { show: loading })}>
+            <LoadingComponent />
+          </div>
+          {!!dataLength && (
+            <Pagination
+              showSizeChanger
+              size="small"
+              className={'pro-table-card-pagination'}
+              total={total}
+              current={current}
+              onChange={(page, size) => {
+                setCurrent(page);
+                setPageIndex(page - 1);
+                setPageSize(size);
+              }}
+              pageSizeOptions={pageSizeOptions}
+              pageSize={pageSize}
+              showTotal={(num) => {
+                const minSize = pageIndex * pageSize + 1;
+                const MaxSize = (pageIndex + 1) * pageSize;
+                return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
+              }}
+            />
+          )}
+        </>
       )}
     </div>
   );

+ 37 - 6
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],
@@ -366,7 +397,7 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
     if (initParam && initParam[0].terms && initParam[0].terms.length > 1) {
       handleExpand();
     }
-  }, []);
+  }, [initParam]);
   const simpleSchema: ISchema = {
     type: 'object',
     properties: {
@@ -460,10 +491,10 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
   };
 
   useEffect(() => {
-    if (defaultParam || initParam) {
+    if (defaultParam) {
       handleSearch();
     }
-  }, [defaultParam, initParam]);
+  }, []);
 
   const handleSaveLog = async () => {
     const value = await form.submit<SearchTermsUI>();

+ 1 - 1
src/locales/zh-CN/pages.ts

@@ -208,7 +208,7 @@ export default {
   'pages.device.productDetail.metadata.eventLevel': '事件级别',
   'pages.device.productDetail.metadata.level': '级别',
   'pages.device.productDetail.metadata.whetherAsync': '是否异步',
-  'pages.device.productDetail.metadata.whetherReadOnly': '是否只读',
+  'pages.device.productDetail.metadata.whetherReadOnly': '读写类型',
   'pages.device.productDetail.metadata.true': '是',
   'pages.device.productDetail.metadata.false': '否',
   'pages.device.productDetail.metadata.dataType': '数据类型',

+ 1 - 0
src/pages/device/Instance/Detail/Diagnose/Message/Dialog/index.less

@@ -19,6 +19,7 @@
 
     .dialog-list {
       display: flex;
+
       .dialog-icon {
         margin-right: 10px;
         color: rgba(0, 0, 0, 0.75);

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

@@ -21,6 +21,7 @@ import {
 import { randomString } from '@/utils/util';
 import Log from './Log';
 import { Store } from 'jetlinks-store';
+
 interface Props {
   onChange: (type: string) => void;
 }

+ 111 - 11
src/pages/device/Instance/index.tsx

@@ -12,10 +12,10 @@ import {
   ExportOutlined,
   EyeOutlined,
   ImportOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   StopOutlined,
   SyncOutlined,
-  PlayCircleOutlined,
 } from '@ant-design/icons';
 import { model } from '@formily/reactive';
 import Service from '@/pages/device/Instance/service';
@@ -31,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');
@@ -152,13 +154,17 @@ const Instance = () => {
       key={'delete'}
       style={{ padding: 0 }}
       isPermission={permission.delete}
+      tooltip={
+        record.state.value !== 'notActive'
+          ? { title: intl.formatMessage({ id: 'pages.device.instance.deleteTip' }) }
+          : undefined
+      }
+      disabled={record.state.value !== 'notActive'}
       popConfirm={{
         title: intl.formatMessage({
-          id:
-            record.state.value === 'notActive'
-              ? 'pages.data.option.remove.tips'
-              : 'pages.device.instance.deleteTip',
+          id: 'pages.data.option.remove.tips',
         }),
+        disabled: record.state.value !== 'notActive',
         onConfirm: async () => {
           if (record.state.value === 'notActive') {
             await service.remove(record.id);
@@ -261,6 +267,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: '说明',
@@ -281,8 +379,6 @@ const Instance = () => {
     },
   ];
 
-  console.log(jumpParams);
-
   const menu = (
     <Menu>
       <Menu.Item key="1">
@@ -529,13 +625,17 @@ const Instance = () => {
                 isPermission={permission.delete}
                 type={'link'}
                 style={{ padding: 0 }}
+                tooltip={
+                  record.state.value !== 'notActive'
+                    ? { title: intl.formatMessage({ id: 'pages.device.instance.deleteTip' }) }
+                    : undefined
+                }
+                disabled={record.state.value !== 'notActive'}
                 popConfirm={{
                   title: intl.formatMessage({
-                    id:
-                      record.state.value === 'notActive'
-                        ? 'pages.data.option.remove.tips'
-                        : 'pages.device.instance.deleteTip',
+                    id: 'pages.data.option.remove.tips',
                   }),
+                  disabled: record.state.value !== 'notActive',
                   onConfirm: async () => {
                     if (record.state.value === 'notActive') {
                       await service.remove(record.id);

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

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

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

@@ -1,5 +1,5 @@
 import { productModel, service } from '@/pages/device/Product';
-import { Descriptions } from 'antd';
+import { Button, Descriptions } from 'antd';
 import { useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { EditOutlined } from '@ant-design/icons';
@@ -12,7 +12,11 @@ import { PermissionButton } from '@/components';
 //   password: 'Password',
 // };
 
-const BaseInfo = () => {
+interface BaseInfoProps {
+  onJump?: (type?: string) => void;
+}
+
+const BaseInfo = (props: BaseInfoProps) => {
   const intl = useIntl();
   // const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
   // const [state, setState] = useState<boolean>(false);
@@ -190,7 +194,35 @@ const BaseInfo = () => {
             defaultMessage: '接入方式',
           })}
         >
-          {productModel.current?.transportProtocol}
+          {permission.update ? (
+            productModel.current?.transportProtocol ? (
+              <Button
+                type={'link'}
+                style={{ padding: 0 }}
+                onClick={() => {
+                  if (props.onJump) {
+                    props.onJump('access');
+                  }
+                }}
+              >
+                {productModel.current?.transportProtocol}
+              </Button>
+            ) : (
+              <Button
+                type={'link'}
+                style={{ padding: 0 }}
+                onClick={() => {
+                  if (props.onJump) {
+                    props.onJump('access');
+                  }
+                }}
+              >
+                配置接入方式
+              </Button>
+            )
+          ) : (
+            productModel.current?.transportProtocol
+          )}
         </Descriptions.Item>
 
         <Descriptions.Item

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

@@ -146,7 +146,15 @@ const ProductDetail = observer(() => {
         id: 'pages.device.productDetail.base',
         defaultMessage: '配置信息',
       }),
-      component: <BaseInfo />,
+      component: (
+        <BaseInfo
+          onJump={(type) => {
+            if (type) {
+              setMode(type);
+            }
+          }}
+        />
+      ),
     },
     {
       key: 'metadata',
@@ -254,6 +262,7 @@ const ProductDetail = observer(() => {
           type={'primary'}
           popConfirm={{
             title: '确定应用配置?',
+            disabled: productModel.current?.state === 0,
             onConfirm: () => {
               changeDeploy('deploy');
             },

+ 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: '操作',

+ 3 - 3
src/pages/device/components/Metadata/Base/columns.ts

@@ -44,7 +44,7 @@ const FunctionColumns: ProColumns<MetadataItem>[] = BaseColumns.concat([
     render: (text) => (text ? '是' : '否'),
   },
   {
-    title: '是否只读',
+    title: '读写类型',
     dataIndex: 'expands.readOnly',
     render: (text) => (text ? '是' : '否'),
   },
@@ -57,7 +57,7 @@ const PropertyColumns: ProColumns<MetadataItem>[] = BaseColumns.concat([
     render: (text: any) => text?.type,
   },
   {
-    title: '是否只读',
+    title: '读写类型',
     dataIndex: 'expands.readOnly',
     render: (text) => (text === 'true' || text === true ? '是' : '否'),
   },
@@ -70,7 +70,7 @@ const TagColumns: ProColumns<MetadataItem>[] = BaseColumns.concat([
     render: (text: any) => text?.type,
   },
   {
-    title: '是否只读',
+    title: '读写类型',
     dataIndex: 'expands.readOnly',
     render: (text) => (text === 'true' || text === true ? '是' : '否'),
   },

+ 1 - 1
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 {
-  PlayCircleOutlined,
   DeleteOutlined,
   EditOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   StopOutlined,
 } from '@ant-design/icons';

+ 5 - 5
src/pages/link/Type/index.tsx

@@ -1,6 +1,6 @@
 import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Badge, Button, message, Popconfirm, Tooltip } from 'antd';
+import { Badge, message, Popconfirm, Tooltip } from 'antd';
 import {
   CloseCircleOutlined,
   DeleteOutlined,
@@ -13,7 +13,7 @@ import { PageContainer } from '@ant-design/pro-layout';
 import type { NetworkItem } from '@/pages/link/Type/typings';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import SearchComponent from '@/components/SearchComponent';
-import { getButtonPermission, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import { history } from 'umi';
 import Service from '@/pages/link/service';
 import { Store } from 'jetlinks-store';
@@ -244,8 +244,8 @@ const Network = () => {
         columns={columns}
         search={false}
         headerTitle={
-          <Button
-            disabled={getButtonPermission('link/Type', ['add'])}
+          <PermissionButton
+            isPermission={networkPermission.add}
             onClick={() => {
               pageJump();
             }}
@@ -257,7 +257,7 @@ const Network = () => {
               id: 'pages.data.option.add',
               defaultMessage: '新增',
             })}
-          </Button>
+          </PermissionButton>
         }
         request={async (params) =>
           service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })

+ 1 - 1
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 {
-  PlayCircleOutlined,
   DeleteOutlined,
   EditOutlined,
   LinkOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   ShareAltOutlined,
   StopOutlined,

+ 44 - 35
src/pages/media/Device/Playback/index.tsx

@@ -26,19 +26,22 @@ export default () => {
   const [type, setType] = useState('local');
   const [historyList, setHistoryList] = useState<recordsItemType[]>([]);
   const [time, setTime] = useState<Moment | undefined>(undefined);
-  const [playTime, setPlayTime] = useState(0);
   // const [loading, setLoading] = useState(false)
   const [cloudTime, setCloudTime] = useState<any>();
-  const [playing, setPlaying] = useState(false);
   const location = useLocation();
   const player = useRef<any>();
+  const [playStatus, setPlayStatus] = useState(0); // 播放状态, 0 停止, 1 播放, 2 暂停, 3 播放完成
+  const [playTime, setPlayTime] = useState(0);
 
+  const playNowTime = useRef<number>(0); // 当前播放视频标识
+  const playTimeNode = useRef<any>(null);
+  const isEnded = useRef(false); // 是否结束播放
   const param = new URLSearchParams(location.search);
   const deviceId = param.get('id');
   const channelId = param.get('channelId');
 
   const queryLocalRecords = async (date: Moment) => {
-    setPlaying(false);
+    setPlayStatus(0);
     setUrl('');
     if (deviceId && channelId && date) {
       const params = {
@@ -73,7 +76,7 @@ export default () => {
   };
 
   const queryServiceRecords = async (date: Moment) => {
-    setPlaying(false);
+    setPlayStatus(0);
     setUrl('');
     if (deviceId && channelId && date) {
       const params = {
@@ -189,25 +192,35 @@ export default () => {
             live={type === 'local'}
             ref={player}
             onPlay={() => {
-              setPlaying(true);
+              isEnded.current = false;
+              setPlayStatus(1);
             }}
             onPause={() => {
-              setPlaying(false);
+              setPlayStatus(2);
             }}
-            onDestroy={() => {
-              setPlaying(false);
+            onEnded={() => {
+              setPlayStatus(0);
+              if (playTimeNode.current && !isEnded.current) {
+                isEnded.current = true;
+                playTimeNode.current.onNextPlay();
+              }
             }}
             onError={() => {
-              setPlaying(false);
+              setPlayStatus(0);
+            }}
+            onTimeUpdate={(e) => {
+              setPlayTime(parseInt(e.detail[0]));
             }}
           />
           <TimeLine
+            ref={playTimeNode}
             type={type}
             data={historyList}
             dateTime={time}
             onChange={(times) => {
               if (times) {
-                setPlayTime(Number(times.endTime.valueOf()));
+                playNowTime.current = Number(times.startTime.valueOf());
+                setPlayTime(0);
                 setUrl(
                   type === 'local'
                     ? service.playbackLocal(
@@ -223,7 +236,8 @@ export default () => {
                 setUrl('');
               }
             }}
-            playing={playing}
+            playStatus={playStatus}
+            playTime={playNowTime.current + playTime * 1000}
             localToServer={cloudTime}
           />
         </div>
@@ -266,48 +280,43 @@ export default () => {
                 itemLayout="horizontal"
                 dataSource={historyList}
                 renderItem={(item) => {
+                  const _startTime = item.startTime || item.mediaStartTime;
                   const startTime = moment(item.startTime || item.mediaStartTime).format(
                     'HH:mm:ss',
                   );
                   const endTime = moment(item.endTime || item.mediaEndTime).format('HH:mm:ss');
                   const downloadObj = DownloadIcon(item);
-                  const timeId = item.endTime || item.mediaEndTime;
-
-                  console.log(timeId, playTime);
                   return (
                     <List.Item
                       actions={[
                         <Tooltip
                           key="play-btn"
-                          title={item.startTime === playTime ? '暂停' : '播放'}
+                          title={
+                            _startTime === playNowTime.current && playStatus === 1 ? '暂停' : '播放'
+                          }
                         >
                           <a
                             onClick={() => {
-                              if (!playTime) {
-                                setPlayTime(item.startTime);
-                                if (item.filePath) {
-                                  service.playbackStart(item.id);
-                                } else if (deviceId && channelId) {
-                                  setUrl(
-                                    service.playbackLocal(
-                                      deviceId,
-                                      channelId,
-                                      'mp4',
-                                      moment(item.startTime).format('YYYY-MM-DD HH:mm:ss'),
-                                      moment(item.endTime).format('YYYY-MM-DD HH:mm:ss'),
-                                    ),
-                                  );
+                              if (playStatus === 0 || _startTime !== playNowTime.current) {
+                                if (playTimeNode.current) {
+                                  playTimeNode.current.playByStartTime(_startTime);
+                                }
+                              } else if (playStatus == 1 && _startTime === playNowTime.current) {
+                                if (player.current.getVueInstance) {
+                                  player.current.getVueInstance().pause();
                                 }
-                              } else {
-                                console.log(player.current);
-                                if (player.current.pause) {
-                                  player.current.pause();
+                              } else if (playStatus == 2 && _startTime === playNowTime.current) {
+                                if (player.current.getVueInstance) {
+                                  player.current.getVueInstance().play();
                                 }
-                                setPlayTime(0);
                               }
                             }}
                           >
-                            {timeId === playTime ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
+                            {_startTime === playNowTime.current && playStatus === 1 ? (
+                              <PauseCircleOutlined />
+                            ) : (
+                              <PlayCircleOutlined />
+                            )}
                           </a>
                         </Tooltip>,
                         <Tooltip key={'download'} title={downloadObj.title}>

+ 57 - 38
src/pages/media/Device/Playback/timeLine.tsx

@@ -1,7 +1,7 @@
 import { message } from 'antd';
-import moment from 'moment';
 import type { Moment } from 'moment';
-import { useEffect, useState, useRef } from 'react';
+import moment from 'moment';
+import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
 import './index.less';
 import { recordsItemType } from '@/pages/media/Device/Playback/typings';
 import { useSize } from 'ahooks';
@@ -19,7 +19,8 @@ interface Props {
   data: recordsItemType[];
   dateTime?: Moment;
   type: string;
-  playing: boolean;
+  playStatus: number;
+  playTime: number;
   server?: any;
   localToServer?: {
     endTime: number;
@@ -28,7 +29,7 @@ interface Props {
   getPlayList?: (data: any) => void;
 }
 
-const Progress = (props: Props) => {
+const Progress = forwardRef((props: Props, ref) => {
   const [startT, setStartT] = useState<number>(
     new Date(moment(props.dateTime).startOf('day').format('YYYY-MM-DD HH:mm:ss')).getTime(),
   ); // 获取选中当天开始时间戳
@@ -37,7 +38,7 @@ const Progress = (props: Props) => {
   ).getTime(); // 获取选中当天结束时间戳
 
   const [list, setList] = useState<any[]>([]);
-  const [time, setTime] = useState<number>(startT);
+  const [playTime, setPlayTime] = useState<number>(0);
 
   const LineContent = useRef<HTMLDivElement>(null);
   const LineContentSize = useSize(LineContent);
@@ -61,6 +62,7 @@ const Progress = (props: Props) => {
   }, [props.dateTime]);
 
   const onChange = (startTime: number, endTime: number, deviceId: string, channelId: string) => {
+    setPlayTime(startTime);
     props.onChange({
       startTime: moment(startTime),
       endTime: moment(endTime),
@@ -69,6 +71,41 @@ const Progress = (props: Props) => {
     });
   };
 
+  const playByStartTime = useCallback(
+    (time) => {
+      const playNow = props.data.find((item) => {
+        const startTime = item.startTime || item.mediaStartTime;
+        return startTime === time;
+      });
+
+      if (playNow) {
+        const startTime = playNow.startTime || playNow.mediaStartTime;
+        const endTime = playNow.endTime || playNow.mediaEndTime;
+        const deviceId = props.type === 'local' ? playNow.deviceId : playNow.id;
+        onChange(startTime, endTime, deviceId, playNow.channelId);
+      }
+    },
+    [props.type, props.data],
+  );
+
+  const onNextPlay = useCallback(() => {
+    if (playTime) {
+      // 查找下一个视频
+      const nowIndex = props.data.findIndex((item) => {
+        const startTime = item.startTime || item.mediaStartTime;
+        return startTime === playTime;
+      });
+      // 是否为最后一个
+      if (nowIndex !== props.data.length - 1) {
+        const nextPlay = props.data[nowIndex + 1];
+        const startTime = nextPlay.startTime || nextPlay.mediaStartTime;
+        const endTime = nextPlay.endTime || nextPlay.mediaEndTime;
+        const deviceId = props.type === 'local' ? nextPlay.deviceId : nextPlay.id;
+        onChange(startTime, endTime, deviceId, nextPlay.channelId);
+      }
+    }
+  }, [props.type, playTime, props.data]);
+
   useEffect(() => {
     const { data, localToServer, type } = props;
     if (data && Array.isArray(data) && data.length > 0) {
@@ -76,7 +113,6 @@ const Progress = (props: Props) => {
       if (type === 'local') {
         // 播放第一个
         onChange(data[0].startTime, data[0].endTime, data[0].deviceId, data[0].channelId);
-        setTime(startT);
       } else if (type === 'cloud') {
         // 是否从本地跳转到云端播放
         if (localToServer && Object.keys(localToServer).length > 0) {
@@ -95,14 +131,11 @@ const Progress = (props: Props) => {
               playItem.id,
               playItem.channelId,
             );
-            setTime(playItem.mediaStartTime);
           } else {
             props.onChange(undefined);
-            setTime(localToServer.startTime);
             message.error('没有可播放的视频资源');
           }
         } else {
-          setTime(data[0].mediaStartTime);
           onChange(data[0].mediaStartTime, data[0].mediaEndTime, data[0].id, data[0].channelId);
         }
       }
@@ -110,28 +143,14 @@ const Progress = (props: Props) => {
       // 本地跳转云端但是无资源
       props.onChange(undefined);
       message.error('没有可播放的视频资源');
-      setTime(startT);
       setList([]);
     } else {
       // 啥都没有
-      setTime(startT);
       setList([]);
       props.onChange(undefined);
     }
   }, [props.data]);
 
-  useEffect(() => {
-    // if(props.server && Object.keys(props.server).length > 0){
-    //   if(props.type === 'local'){
-    //     setTime(props.server.startTime)
-    //     props.play({ start: props.server.startTime, end: props.server.endTime })
-    //   } else {
-    //     setTime(props.server.mediaStartTime)
-    //     props.play(props.server)
-    //   }
-    // }
-  }, [props.server]);
-
   const getLineItemStyle = (
     startTime: number,
     endTime: number,
@@ -146,21 +165,21 @@ const Progress = (props: Props) => {
   };
 
   useEffect(() => {
-    let timerId: any = null;
-    if (props.playing) {
-      timerId = setInterval(() => {
-        // eslint-disable-next-line @typescript-eslint/no-shadow
-        setTime((time) => time + 1000);
-      }, 1000);
+    if (
+      props.playTime &&
+      props.playTime >= startT &&
+      props.playTime <= endT &&
+      props.data &&
+      props.data.length
+    ) {
+      setTimeAndPosition((props.playTime - startT) / 3600000 / 24);
     }
-    return () => timerId && clearInterval(timerId);
-  }, [props.playing]);
+  }, [props.playTime, startT]);
 
-  useEffect(() => {
-    if (time >= startT && time <= endT && props.data && props.data.length) {
-      setTimeAndPosition((time - startT) / 3600000 / 24);
-    }
-  }, [time]);
+  useImperativeHandle(ref, () => ({
+    onNextPlay,
+    playByStartTime,
+  }));
 
   return (
     <div className={'time-line-warp'}>
@@ -196,10 +215,10 @@ const Progress = (props: Props) => {
         </div>
         <div id="btn" className={classNames('time-line-btn')}></div>
         <div id="time" className={classNames('time-line')}>
-          {moment(time).format('HH:mm:ss')}
+          {moment(props.playTime || 0).format('HH:mm:ss')}
         </div>
       </div>
     </div>
   );
-};
+});
 export default Progress;

+ 1 - 1
src/pages/media/Device/Save/ProviderSelect.tsx

@@ -61,7 +61,7 @@ export default (props: ProviderProps) => {
     tab!.onTabSaveSuccess = (value: any) => {
       addItemKey.current = value.id;
       getProviderList({
-        sorts: [{ name: 'createTime', value: 'asc' }],
+        sorts: [{ name: 'createTime', order: 'desc' }],
         terms: [{ column: 'provider', value: props.type }],
         pageSize: 100,
       });

+ 1 - 1
src/pages/media/Device/Save/SaveProduct.tsx

@@ -24,7 +24,7 @@ export default (props: SaveProps) => {
   useEffect(() => {
     if (visible) {
       getProviderList({
-        sorts: [{ name: 'createTime', value: 'desc' }],
+        sorts: [{ name: 'createTime', order: 'desc' }],
         terms: [{ column: 'provider', value: props.type }],
         pageSize: 100,
       });

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

@@ -111,8 +111,10 @@ const Detail = observer(() => {
           onFieldValueChange('provider', (field, form1) => {
             const value = field.value;
             setProvider(value);
-            // form1.setValuesIn('configId', null);
-            // form1.setValuesIn('template', null);
+            if (field.modified) {
+              form1.setValuesIn('configId', null);
+              form1.setValuesIn('template', null);
+            }
             // 设置绑定配置的数据
             form1.setFieldState('configId', async (state1) => {
               state1.dataSource = await getConfig(value);

+ 1 - 1
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 {
-  PlayCircleOutlined,
   DeleteOutlined,
   EditOutlined,
   EyeOutlined,
+  PlayCircleOutlined,
   PlusOutlined,
   StopOutlined,
 } from '@ant-design/icons';

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

@@ -0,0 +1 @@
+export default () => {};

+ 177 - 92
src/pages/rule-engine/Scene/index.tsx

@@ -1,32 +1,125 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import BaseService from '@/utils/BaseService';
-import { useRef } from 'react';
+import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
-import { Tooltip } from 'antd';
-import {
-  CaretRightOutlined,
-  EditOutlined,
-  EyeOutlined,
-  MinusOutlined,
-  ReloadOutlined,
-  StopOutlined,
-} from '@ant-design/icons';
-import BaseCrud from '@/components/BaseCrud';
+import { Badge, message } from 'antd';
+import { EditOutlined, PlayCircleOutlined, PlusOutlined, StopOutlined } from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import { PermissionButton, ProTableCard } from '@/components';
+import { statusMap } from '@/pages/device/Instance';
+import SearchComponent from '@/components/SearchComponent';
+import Service from './service';
+
+export const service = new Service('rule-engine/scene');
 
-export const service = new BaseService<SceneItem>('rule-engine/scene');
 const Scene = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
+  const { permission } = PermissionButton.usePermission('rule-engine/Scene');
+  const [searchParams, setSearchParams] = useState<any>({});
+
+  const Tools = (record: any, type: 'card' | 'table') => {
+    return [
+      <PermissionButton
+        key={'update'}
+        type={'link'}
+        style={{ padding: 0 }}
+        isPermission={permission.update}
+        tooltip={
+          type === 'table'
+            ? {
+                title: intl.formatMessage({
+                  id: 'pages.data.option.edit',
+                  defaultMessage: '编辑',
+                }),
+              }
+            : undefined
+        }
+      >
+        <EditOutlined />
+        {type === 'table' &&
+          intl.formatMessage({
+            id: 'pages.data.option.edit',
+            defaultMessage: '编辑',
+          })}
+      </PermissionButton>,
+      <PermissionButton
+        key={'update'}
+        type={'link'}
+        style={{ padding: 0 }}
+        isPermission={permission.action}
+        popConfirm={{
+          title: intl.formatMessage({
+            id: `pages.data.option.${
+              record.state.value !== 'notActive' ? 'disabled' : 'enabled'
+            }.tips`,
+            defaultMessage: '确认禁用?',
+          }),
+          onConfirm: async () => {
+            message.success(
+              intl.formatMessage({
+                id: 'pages.data.option.success',
+                defaultMessage: '操作成功!',
+              }),
+            );
+            actionRef.current?.reload();
+          },
+        }}
+        tooltip={
+          type === 'table'
+            ? {
+                title: intl.formatMessage({
+                  id: 'pages.data.option.edit',
+                  defaultMessage: '编辑',
+                }),
+              }
+            : undefined
+        }
+      >
+        {record.state.value !== 'notActive' ? <StopOutlined /> : <PlayCircleOutlined />}
+        {type === 'table' &&
+          intl.formatMessage({
+            id: `pages.data.option.${record.state.value !== 'notActive' ? 'disabled' : 'enabled'}`,
+            defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
+          })}
+      </PermissionButton>,
+      <PermissionButton
+        key={'delete'}
+        type={'link'}
+        style={{ padding: 0 }}
+        isPermission={permission.delete}
+        popConfirm={{
+          title: intl.formatMessage({
+            id:
+              record.state.value === 'notActive'
+                ? 'pages.data.option.remove.tips'
+                : 'pages.device.instance.deleteTip',
+          }),
+          onConfirm: async () => {},
+        }}
+        tooltip={
+          type === 'table'
+            ? {
+                title: intl.formatMessage({
+                  id: 'pages.data.option.edit',
+                  defaultMessage: '编辑',
+                }),
+              }
+            : undefined
+        }
+      >
+        <EditOutlined />
+        {type === 'table' &&
+          intl.formatMessage({
+            id: 'pages.data.option.edit',
+            defaultMessage: '编辑',
+          })}
+      </PermissionButton>,
+    ];
+  };
 
   const columns: ProColumns<SceneItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       dataIndex: 'name',
       title: intl.formatMessage({
         id: 'pages.table.name',
@@ -39,7 +132,13 @@ const Scene = () => {
         id: 'pages.ruleEngine.scene.triggers',
         defaultMessage: '触发方式',
       }),
-      render: () => 'todo',
+    },
+    {
+      dataIndex: 'describe',
+      title: intl.formatMessage({
+        id: 'pages.system.description',
+        defaultMessage: '说明',
+      }),
     },
     {
       dataIndex: 'state',
@@ -47,7 +146,26 @@ const Scene = () => {
         id: 'pages.searchTable.titleStatus',
         defaultMessage: '状态',
       }),
-      render: (text, record) => record.state.value,
+      width: '90px',
+      valueType: 'select',
+      renderText: (record) =>
+        record ? <Badge status={statusMap.get(record.value)} text={record.text} /> : '',
+      valueEnum: {
+        offline: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.offLine',
+            defaultMessage: '离线',
+          }),
+          status: 'offline',
+        },
+        online: {
+          text: intl.formatMessage({
+            id: 'pages.device.instance.status.onLine',
+            defaultMessage: '在线',
+          }),
+          status: 'online',
+        },
+      },
     },
     {
       title: intl.formatMessage({
@@ -57,85 +175,52 @@ const Scene = () => {
       valueType: 'option',
       align: 'center',
       width: 200,
-      render: (text, record) => [
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.ruleEngine.option.detail',
-              defaultMessage: '查看',
-            })}
-          >
-            <EyeOutlined />
-          </Tooltip>
-        </a>,
-        <a onClick={() => console.log(record)}>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.edit',
-              defaultMessage: '编辑',
-            })}
-          >
-            <EditOutlined />
-          </Tooltip>
-        </a>,
-        <a onClick={() => console.log(record)}>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.ruleEngine.option.start',
-              defaultMessage: '启动',
-            })}
-          >
-            <CaretRightOutlined />
-          </Tooltip>
-        </a>,
-        <a onClick={() => console.log(record)}>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.ruleEngine.option.stop',
-              defaultMessage: '停止',
-            })}
-          >
-            <StopOutlined />
-          </Tooltip>
-        </a>,
-        <a onClick={() => console.log(record)}>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.ruleEngine.option.restart',
-              defaultMessage: '重启',
-            })}
-          >
-            <ReloadOutlined />
-          </Tooltip>
-        </a>,
-
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.remove',
-              defaultMessage: '删除',
-            })}
-          >
-            <MinusOutlined />
-          </Tooltip>
-        </a>,
-      ],
+      render: (text, record) => Tools(record, 'table'),
     },
   ];
 
-  const schema = {};
-
   return (
     <PageContainer>
-      <BaseCrud
+      <SearchComponent
+        field={columns}
+        target={'rule-engine-scene'}
+        onSearch={(data) => {
+          actionRef.current?.reset?.();
+          setSearchParams(data);
+        }}
+      />
+      <ProTableCard
         columns={columns}
-        service={service}
-        title={intl.formatMessage({
-          id: 'pages.ruleEngine.scene',
-          defaultMessage: '场景联动',
-        })}
-        schema={schema}
         actionRef={actionRef}
+        params={searchParams}
+        options={{ fullScreen: true }}
+        request={(params) =>
+          service.query({
+            ...params,
+            sorts: [
+              {
+                name: 'createTime',
+                order: 'desc',
+              },
+            ],
+          })
+        }
+        rowKey="id"
+        search={false}
+        headerTitle={[
+          <PermissionButton
+            key="button"
+            icon={<PlusOutlined />}
+            type="primary"
+            isPermission={permission.add}
+            onClick={() => {}}
+          >
+            {intl.formatMessage({
+              id: 'pages.data.option.add',
+              defaultMessage: '新增',
+            })}
+          </PermissionButton>,
+        ]}
       />
     </PageContainer>
   );

+ 9 - 0
src/pages/rule-engine/Scene/service.ts

@@ -0,0 +1,9 @@
+import { request } from '@@/plugin-request/request';
+import BaseService from '@/utils/BaseService';
+import type { SceneItem } from '@/pages/rule-engine/Scene/typings';
+
+class Service extends BaseService<SceneItem> {
+  start = (id: string) => request(`${this.uri}/${id}`, { methods: 'GET' });
+}
+
+export default Service;

+ 1 - 0
src/pages/rule-engine/Scene/typings.d.ts

@@ -9,6 +9,7 @@ type Trigger = {
   trigger: string;
   device: Record<string, unknown>;
 };
+
 type SceneItem = {
   parallel: boolean;
   state: State;

+ 4 - 0
src/pages/system/Menu/Detail/buttons.tsx

@@ -23,6 +23,7 @@ export default (props: ButtonsProps) => {
   const [visible, setVisible] = useState(false); // Modal显示影藏
   const [id, setId] = useState(''); // 缓存ID
   const [form] = Form.useForm();
+  const [loading, setLoading] = useState(false);
   const { permission } = PermissionButton.usePermission('system/Menu');
 
   const { data: permissions, run: queryPermissions } = useRequest(service.queryPermission, {
@@ -67,10 +68,12 @@ export default (props: ButtonsProps) => {
   const updateMenuInfo = useCallback(
     async (data: MenuButtonInfo[]) => {
       if (props.data.id) {
+        setLoading(true);
         const response = await service.update({
           ...props.data,
           buttons: data,
         });
+        setLoading(false);
         if (response.status === 200) {
           message.success('操作成功!');
           props.onLoad();
@@ -283,6 +286,7 @@ export default (props: ButtonsProps) => {
           resetForm();
           setVisible(false);
         }}
+        confirmLoading={loading}
       >
         <Form form={form} layout={'vertical'}>
           <Form.Item

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

@@ -34,6 +34,7 @@ export default (props: EditProps) => {
   const intl = useIntl();
   const [disabled, setDisabled] = useState(true);
   const [show] = useState(true);
+  const [loading, setLoading] = useState(false);
   const [accessSupport, setAccessSupport] = useState('unsupported');
   const history = useHistory();
   const { getOtherPermission } = PermissionButton.usePermission('system/Menu');
@@ -71,9 +72,11 @@ export default (props: EditProps) => {
       //   switch: show,
       // };
 
+      setLoading(true);
       const response: any = !formData.id
         ? await service.save(formData)
         : await service.update(formData);
+      setLoading(false);
       if (response.status === 200) {
         message.success('操作成功!');
         setDisabled(true);
@@ -217,7 +220,13 @@ export default (props: EditProps) => {
             </Col>
             <Col span={24}>
               <Form.Item name={'describe'} label={'说明'}>
-                <Input.TextArea rows={4} maxLength={200} showCount placeholder={'请输入说明'} />
+                <Input.TextArea
+                  disabled={disabled}
+                  rows={4}
+                  maxLength={200}
+                  showCount
+                  placeholder={'请输入说明'}
+                />
               </Form.Item>
             </Col>
           </Row>
@@ -328,6 +337,7 @@ export default (props: EditProps) => {
                 saveData();
               }
             }}
+            loading={loading}
             isPermission={getOtherPermission(['add', 'update'])}
           >
             {intl.formatMessage({

+ 2 - 0
src/pages/system/Role/index.tsx

@@ -197,6 +197,8 @@ const Role: React.FC = observer(() => {
       CurdModel.add();
     }
     const subscription = Store.subscribe(SystemConst.BASE_UPDATE_DATA, (data) => {
+      debugger;
+      console.log('订阅数据');
       if ((window as any).onTabSaveSuccess) {
         (window as any).onTabSaveSuccess(data);
         setTimeout(() => window.close(), 300);

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