wzyyy il y a 3 ans
Parent
commit
e92a8b9a48
100 fichiers modifiés avec 4607 ajouts et 551 suppressions
  1. 1 0
      config/config.ts
  2. 2 0
      package.json
  3. BIN
      public/images/firmware/button.png
  4. BIN
      public/images/init-home/data-disabled.png
  5. BIN
      public/images/init-home/data-enabled.png
  6. BIN
      public/images/init-home/menu.png
  7. BIN
      public/images/init-home/role1.png
  8. BIN
      public/images/init-home/role2.png
  9. BIN
      public/images/init-home/role3.png
  10. 1 1
      src/app.tsx
  11. 1 0
      src/components/BaseCrud/index.tsx
  12. 1 0
      src/components/BindParentDevice/index.tsx
  13. 1 0
      src/components/BindUser/Bound.tsx
  14. 1 0
      src/components/BindUser/Unbound.tsx
  15. 5 1
      src/components/Ellipsis/index.tsx
  16. 2 1
      src/components/FSelectDevice/index.tsx
  17. 29 15
      src/components/FSelectDevices/index.tsx
  18. 1 1
      src/components/FUpload/index.tsx
  19. 3 3
      src/components/ProTableCard/CardItems/Stream/index.less
  20. 9 3
      src/components/ProTableCard/CardItems/Stream/index.tsx
  21. 5 11
      src/components/ProTableCard/CardItems/device.tsx
  22. 4 4
      src/components/ProTableCard/CardItems/mediaDevice.tsx
  23. 1 1
      src/components/ProTableCard/CardItems/networkCard.tsx
  24. 2 5
      src/components/ProTableCard/CardItems/noticeConfig.tsx
  25. 5 8
      src/components/ProTableCard/CardItems/product.tsx
  26. 16 5
      src/components/ProTableCard/index.tsx
  27. 92 66
      src/components/SearchComponent/index.tsx
  28. 3 1
      src/global.less
  29. 1 0
      src/pages/Log/Access/index.tsx
  30. 1 0
      src/pages/Log/System/index.tsx
  31. 1 0
      src/pages/account/NotificationRecord/index.tsx
  32. 1 0
      src/pages/account/NotificationSubscription/index.tsx
  33. 1 0
      src/pages/device/Alarm/index.tsx
  34. 1 0
      src/pages/device/Category/index.tsx
  35. 1 0
      src/pages/device/Command/index.tsx
  36. 22 0
      src/pages/device/Firmware/Task/Detail/Details/index.tsx
  37. 22 3
      src/pages/device/Firmware/Task/Detail/index.less
  38. 233 30
      src/pages/device/Firmware/Task/Detail/index.tsx
  39. 43 31
      src/pages/device/Firmware/Task/Save/index.tsx
  40. 85 32
      src/pages/device/Firmware/Task/index.tsx
  41. 24 4
      src/pages/device/Firmware/index.tsx
  42. 31 15
      src/pages/device/Firmware/service.ts
  43. 1 0
      src/pages/device/Instance/Detail/ChildDevice/BindChildDevice/index.tsx
  44. 4 1
      src/pages/device/Instance/Detail/ChildDevice/index.tsx
  45. 6 6
      src/pages/device/Instance/Detail/Diagnose/Status/model.ts
  46. 1 0
      src/pages/device/Instance/Detail/Log/index.tsx
  47. 1 0
      src/pages/device/Instance/Detail/MetadataLog/Event/index.tsx
  48. 1 0
      src/pages/device/Instance/Detail/Opcua/index.tsx
  49. 1 0
      src/pages/device/Instance/Detail/Running/Event/index.tsx
  50. 2 0
      src/pages/device/Instance/Process/index.tsx
  51. 4 4
      src/pages/device/Instance/index.tsx
  52. 1 0
      src/pages/device/components/Alarm/Record/index.tsx
  53. 6 1
      src/pages/document.ejs
  54. 1 0
      src/pages/home/components/DeviceChoose.tsx
  55. 128 0
      src/pages/init-home/components/basis.tsx
  56. 2470 0
      src/pages/init-home/components/data/RoleData.ts
  57. 106 0
      src/pages/init-home/components/data/index.tsx
  58. 175 0
      src/pages/init-home/components/data/save/index.tsx
  59. 85 0
      src/pages/init-home/components/menu.tsx
  60. 122 0
      src/pages/init-home/components/role.tsx
  61. 92 7
      src/pages/init-home/index.less
  62. 137 28
      src/pages/init-home/index.tsx
  63. 76 0
      src/pages/init-home/service.ts
  64. 1 0
      src/pages/link/Certificate/index.tsx
  65. 1 0
      src/pages/link/Channel/Modbus/Access/bindDevice/index.tsx
  66. 1 0
      src/pages/link/Channel/Modbus/Access/index.tsx
  67. 1 0
      src/pages/link/Channel/Opcua/Access/bindDevice/index.tsx
  68. 1 0
      src/pages/link/Channel/Opcua/Access/index.tsx
  69. 1 0
      src/pages/link/Protocol/save/index.tsx
  70. 0 1
      src/pages/link/Type/Detail/index.tsx
  71. 1 0
      src/pages/media/Cascade/Channel/BindChannel/index.tsx
  72. 1 0
      src/pages/media/Cascade/Channel/index.tsx
  73. 1 1
      src/pages/media/Cascade/index.tsx
  74. 2 1
      src/pages/media/Device/Channel/index.tsx
  75. 182 161
      src/pages/media/Device/Playback/index.tsx
  76. 1 0
      src/pages/media/Device/Playback/timeLine.tsx
  77. 1 0
      src/pages/media/Home/deviceModal.tsx
  78. 1 1
      src/pages/media/Home/index.tsx
  79. 4 3
      src/pages/media/Home/service.tsx
  80. 64 13
      src/pages/notice/Config/Debug/index.tsx
  81. 1 0
      src/pages/notice/Config/Log/index.tsx
  82. 1 0
      src/pages/notice/Config/SyncUser/index.tsx
  83. 7 1
      src/pages/notice/Template/Debug/index.tsx
  84. 3 0
      src/pages/notice/Template/Detail/doc/DingTalkRebot.tsx
  85. 1 0
      src/pages/notice/Template/Log/index.tsx
  86. 1 0
      src/pages/notice/Template/index.tsx
  87. 34 16
      src/pages/rule-engine/Alarm/Config/Save/input.tsx
  88. 35 20
      src/pages/rule-engine/Alarm/Config/Save/output.tsx
  89. 38 10
      src/pages/rule-engine/Alarm/Config/index.tsx
  90. 37 2
      src/pages/rule-engine/Alarm/Configuration/index.tsx
  91. 1 0
      src/pages/rule-engine/Alarm/Log/Detail/index.tsx
  92. 1 0
      src/pages/rule-engine/Alarm/Log/SolveLog/index.tsx
  93. 6 7
      src/pages/rule-engine/Alarm/Log/TabComponent/index.tsx
  94. 1 1
      src/pages/rule-engine/Instance/index.tsx
  95. 23 2
      src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx
  96. 29 11
      src/pages/rule-engine/Scene/Save/action/action.tsx
  97. 5 1
      src/pages/rule-engine/Scene/Save/action/device/ConditionalFiltering.tsx
  98. 43 9
      src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx
  99. 4 2
      src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx
  100. 0 0
      src/pages/rule-engine/Scene/Save/action/device/index.tsx

+ 1 - 0
config/config.ts

@@ -91,5 +91,6 @@ export default defineConfig({
         languages: ['json', 'javascript', 'typescript', 'sql'],
       }),
     );
+    memo.module.rule('mjs-rule').test(/.m?js/).resolve.set('fullySpecified', false);
   },
 });

+ 2 - 0
package.json

@@ -97,6 +97,8 @@
     "react-beautiful-dnd": "^13.1.0",
     "react-custom-scrollbars": "^4.2.1",
     "react-dev-inspector": "^1.1.1",
+    "react-dnd": "^16.0.1",
+    "react-dnd-html5-backend": "^16.0.1",
     "react-dom": "^17.0.0",
     "react-helmet-async": "^1.0.4",
     "react-json-view": "^1.21.3",

BIN
public/images/firmware/button.png


BIN
public/images/init-home/data-disabled.png


BIN
public/images/init-home/data-enabled.png


BIN
public/images/init-home/menu.png


BIN
public/images/init-home/role1.png


BIN
public/images/init-home/role2.png


BIN
public/images/init-home/role3.png


+ 1 - 1
src/app.tsx

@@ -343,7 +343,7 @@ export function patchRoutes(routes: any) {
 }
 
 export function render(oldRender: any) {
-  if (history.location.pathname !== loginPath && history.location.pathname !== bindPath) {
+  if (![loginPath, bindPath].includes(history.location.pathname)) {
     // SystemConfigService.getAMapKey().then((res) => {
     //   if (res && res.status === 200 && res.result) {
     //     localStorage.setItem(SystemConst.AMAP_KEY, res.result.apiKey);

+ 1 - 0
src/components/BaseCrud/index.tsx

@@ -96,6 +96,7 @@ const BaseCrud = <T extends Record<string, any>>(props: Props<T>) => {
           columns={columns}
           actionRef={actionRef}
           scroll={scroll}
+          columnEmptyText={''}
           options={{ fullScreen: true }}
           request={
             request ||

+ 1 - 0
src/components/BindParentDevice/index.tsx

@@ -178,6 +178,7 @@ const BindParentDevice = (props: Props) => {
         actionRef={actionRef}
         params={searchParams}
         rowKey="id"
+        columnEmptyText={''}
         toolBarRender={false}
         pagination={{
           pageSize: 10,

+ 1 - 0
src/components/BindUser/Bound.tsx

@@ -128,6 +128,7 @@ const Bound = observer(() => {
         pagination={{
           pageSize: 10,
         }}
+        columnEmptyText={''}
         request={async (params) => service.query(params)}
         defaultParams={{
           [`id$in-dimension$${BindModel.dimension.type}`]: BindModel.dimension.id,

+ 1 - 0
src/components/BindUser/Unbound.tsx

@@ -112,6 +112,7 @@ const Unbound = observer(() => {
             }));
           },
         }}
+        columnEmptyText={''}
         tableAlertRender={({ selectedRowKeys, onCleanSelected }) => (
           <Space size={24}>
             <span>

+ 5 - 1
src/components/Ellipsis/index.tsx

@@ -93,7 +93,11 @@ export default (props: EllipsisProps) => {
       )}
       <div
         className={classnames(props.titleClassName?.replace('ellipsis', ''), Style['ellipsis-max'])}
-        style={{ ...props.titleStyle, width: 'max-content !important' }}
+        style={
+          props.titleStyle
+            ? { ...props.titleStyle, width: 'max-content !important' }
+            : { width: 'max-content !important' }
+        }
         ref={extraNode}
       >
         {props.title}

+ 2 - 1
src/components/FSelectDevice/index.tsx

@@ -53,7 +53,7 @@ const FSelectDevice = connect((props: Props) => {
       }),
       dataIndex: 'registryTime',
       width: '200px',
-      render: (text: any) => (text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : '/'),
+      render: (text: any) => (text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : ''),
       sorter: true,
     },
     {
@@ -105,6 +105,7 @@ const FSelectDevice = connect((props: Props) => {
             pagination={{
               pageSize: 10,
             }}
+            columnEmptyText={''}
             columns={columns}
             actionRef={actionRef}
             request={(params) => service.query(params)}

+ 29 - 15
src/components/FSelectDevices/index.tsx

@@ -9,6 +9,7 @@ import type { DeviceInstance } from '@/pages/device/Instance/typings';
 import moment from 'moment';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import Service from '@/pages/device/Instance/service';
+import SearchComponent from '../SearchComponent';
 
 interface Props {
   value: Partial<DeviceInstance>[];
@@ -21,12 +22,13 @@ const FSelectDevices = connect((props: Props) => {
   const [visible, setVisible] = useState<boolean>(false);
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
+  const [searchParam, setSearchParam] = useState({});
   const columns: ProColumns<DeviceInstance>[] = [
-    {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
+    // {
+    //   dataIndex: 'index',
+    //   valueType: 'indexBorder',
+    //   width: 48,
+    // },
     {
       title: 'ID',
       dataIndex: 'id',
@@ -54,18 +56,18 @@ const FSelectDevices = connect((props: Props) => {
       }),
       dataIndex: 'registryTime',
       width: '200px',
-      render: (text: any) => (text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : '/'),
+      render: (text: any) => (text ? moment(text).format('YYYY-MM-DD HH:mm:ss') : ''),
       sorter: true,
     },
-    {
-      title: intl.formatMessage({
-        id: 'pages.table.description',
-        defaultMessage: '说明',
-      }),
-      dataIndex: 'description',
-      width: '15%',
-      ellipsis: true,
-    },
+    // {
+    //   title: intl.formatMessage({
+    //     id: 'pages.table.description',
+    //     defaultMessage: '说明',
+    //   }),
+    //   dataIndex: 'description',
+    //   width: '15%',
+    //   ellipsis: true,
+    // },
   ];
 
   const [data, setData] = useState<Partial<DeviceInstance>[]>(props.value);
@@ -95,17 +97,29 @@ const FSelectDevices = connect((props: Props) => {
             props.onChange(data);
           }}
         >
+          <SearchComponent<DeviceInstance>
+            field={columns}
+            enableSave={false}
+            model="simple"
+            onSearch={async (data1) => {
+              setSearchParam(data1);
+            }}
+            target="choose-device"
+          />
           <ProTable<DeviceInstance>
             tableAlertRender={false}
             rowSelection={{
               type: 'checkbox',
               ...rowSelection,
             }}
+            search={false}
+            columnEmptyText={''}
             toolBarRender={false}
             rowKey="id"
             pagination={{
               pageSize: 10,
             }}
+            params={searchParam}
             columns={columns}
             actionRef={actionRef}
             request={(params) => service.query(params)}

+ 1 - 1
src/components/FUpload/index.tsx

@@ -22,7 +22,7 @@ const FUpload = connect((props: Props) => {
       const result = info.file.response?.result;
       const f = {
         ...result,
-        url: `${location.protocol}://${SystemConst.API_BASE}/file/${result?.id}?accessKey=${result?.others?.accessKey}`,
+        url: `${location.protocol}//${SystemConst.API_BASE}/file/${result?.id}?accessKey=${result?.others?.accessKey}`,
       };
       setUrl(f.url);
       props.onChange(f);

+ 3 - 3
src/components/ProTableCard/CardItems/Stream/index.less

@@ -18,14 +18,14 @@
 
       .header {
         .stream-title {
-          width: 100%;
+          //width: 100%;
           font-weight: 700;
           font-size: 18px;
         }
       }
       .container {
-        display: flex;
-        justify-content: space-between;
+        display: grid;
+        grid-template-columns: 1fr 1fr 1fr;
         width: 100%;
         margin-bottom: 20px;
         padding-right: 20px;

+ 9 - 3
src/components/ProTableCard/CardItems/Stream/index.tsx

@@ -37,18 +37,24 @@ export default (props: StreamCardProps) => {
               {/*<div className={'ellipsis'}>*/}
               {/*  <Tooltip title={props?.provider}>{props?.provider}</Tooltip>*/}
               {/*</div>*/}
-              <Ellipsis title={props?.provider} titleClassName={'stream-title'} />
+              <div style={{ width: '100%' }}>
+                <Ellipsis title={props?.provider} titleClassName={'stream-title'} />
+              </div>
             </div>
             <div>
               <label>RTP IP</label>
-              <Ellipsis title={props?.configuration?.rtpIp} />
+              <div style={{ width: '100%' }}>
+                <Ellipsis title={props?.configuration?.rtpIp} />
+              </div>
               {/*<div className={'ellipsis'}>*/}
               {/*  <Tooltip title={props?.configuration?.rtpIp}>{props?.configuration?.rtpIp}</Tooltip>*/}
               {/*</div>*/}
             </div>
             <div>
               <label>API HOST</label>
-              <Ellipsis title={props?.configuration?.apiHost} />
+              <div style={{ width: '100%' }}>
+                <Ellipsis title={props?.configuration?.apiHost} />
+              </div>
               {/*<div className={'ellipsis'}>*/}
               {/*  <Tooltip title={props?.configuration?.apiHost}>*/}
               {/*    {props?.configuration?.apiHost}*/}

+ 5 - 11
src/components/ProTableCard/CardItems/device.tsx

@@ -71,7 +71,7 @@ export const ExtraDeviceCard = (props: DeviceCardProps) => {
           <div className={'card-item-content-flex'}>
             <div className={'flex-auto'}>
               <label>ID</label>
-              <Ellipsis title={props.id || ''} titleClassName={'ellipsis'} />
+              <Ellipsis title={props.id || ''} />
               {/*<div className={'ellipsis'}>*/}
               {/*  <Tooltip title={props.id}>{props.id || ''}</Tooltip>*/}
               {/*</div>*/}
@@ -79,7 +79,7 @@ export const ExtraDeviceCard = (props: DeviceCardProps) => {
             {props.cardType === 'bind' ? (
               <div className={'flex-auto'}>
                 <label>说明</label>
-                <Ellipsis title={props.describe || ''} titleClassName={'ellipsis'} />
+                <Ellipsis title={props.describe || ''} />
                 {/*<Tooltip title={props.describe}>*/}
                 {/*  <div className={'ellipsis'}>{props.describe}</div>*/}
                 {/*</Tooltip>*/}
@@ -87,10 +87,7 @@ export const ExtraDeviceCard = (props: DeviceCardProps) => {
             ) : (
               <div className={'flex-auto'}>
                 <label>资产权限</label>
-                <Ellipsis
-                  title={handlePermissionsMap(props.grantedPermissions)}
-                  titleClassName={'ellipsis'}
-                />
+                <Ellipsis title={handlePermissionsMap(props.grantedPermissions)} />
                 {/*<div className={'ellipsis'}>*/}
                 {/*  <Tooltip title={handlePermissionsMap(props.grantedPermissions)}>*/}
                 {/*    {handlePermissionsMap(props.grantedPermissions)}*/}
@@ -134,15 +131,12 @@ export default (props: DeviceCardProps) => {
           <div className={'card-item-content'}>
             <div>
               <label>设备类型</label>
-              <Ellipsis
-                title={props.deviceType ? props.deviceType.text : ''}
-                titleClassName={'ellipsis'}
-              />
+              <Ellipsis title={props.deviceType ? props.deviceType.text : ''} />
               {/*<div className={'ellipsis'}>{props.deviceType ? props.deviceType.text : ''}</div>*/}
             </div>
             <div>
               <label>产品名称</label>
-              <Ellipsis title={props.productName} titleClassName={'ellipsis'} />
+              <Ellipsis title={props.productName} />
               {/*<div className={'ellipsis'}>{props.productName || ''}</div>*/}
             </div>
           </div>

+ 4 - 4
src/components/ProTableCard/CardItems/mediaDevice.tsx

@@ -39,22 +39,22 @@ export default (props: ProductCardProps) => {
           <div className={'card-item-content'}>
             <div>
               <label>厂商</label>
-              <Ellipsis title={props?.manufacturer} titleClassName={'ellipsis'} />
+              <Ellipsis title={props?.manufacturer} />
               {/*<div className={'ellipsis'}>{props.manufacturer || ''}</div>*/}
             </div>
             <div>
               <label>通道数量</label>
-              <Ellipsis title={props?.channelNumber} titleClassName={'ellipsis'} />
+              <Ellipsis title={props?.channelNumber} />
               {/*<div className={'ellipsis'}>{props.channelNumber || 0}</div>*/}
             </div>
             <div>
               <label>型号</label>
-              <Ellipsis title={props?.model} titleClassName={'ellipsis'} />
+              <Ellipsis title={props?.model} />
               {/*<div className={'ellipsis'}>{props.model || ''}</div>*/}
             </div>
             <div>
               <label>接入方式</label>
-              <Ellipsis title={props?.provider} titleClassName={'ellipsis'} />
+              <Ellipsis title={props?.provider} />
               {/*<div className={'ellipsis'}>{props.provider || ''}</div>*/}
             </div>
           </div>

+ 1 - 1
src/components/ProTableCard/CardItems/networkCard.tsx

@@ -72,7 +72,7 @@ export default (props: NoticeCardProps) => {
           <div className={'card-item-content'}>
             <div>
               <label>类型</label>
-              <Ellipsis title={props?.type} titleClassName={'ellipsis'} />
+              <Ellipsis title={props?.type} />
               {/*<div className={'ellipsis'}>*/}
               {/*  <Tooltip title={props?.type}>{props.type}</Tooltip>*/}
               {/*</div>*/}

+ 2 - 5
src/components/ProTableCard/CardItems/noticeConfig.tsx

@@ -29,15 +29,12 @@ export default (props: NoticeCardProps) => {
           <div className={'card-item-content'}>
             <div>
               <label>通知方式</label>
-              <Ellipsis
-                title={typeList[props.type][props.provider] || '暂无'}
-                titleClassName={'ellipsis'}
-              />
+              <Ellipsis title={typeList[props.type][props.provider] || '暂无'} />
               {/*<div className={'ellipsis'}>{typeList[props.type][props.provider] || '暂无'}</div>*/}
             </div>
             <div>
               <label>说明</label>
-              <Ellipsis title={props.description} titleClassName={'ellipsis'} />
+              <Ellipsis title={props.description} />
               {/*<div className={'ellipsis'}>*/}
               {/*  <Tooltip placement="topLeft" title={props.description}>*/}
               {/*    {props.description}*/}

+ 5 - 8
src/components/ProTableCard/CardItems/product.tsx

@@ -81,20 +81,17 @@ export const ExtraProductCard = (props: ProductCardProps) => {
           <div className={'card-item-content-flex'}>
             <div className={'flex-auto'}>
               <label>ID</label>
-              <Ellipsis title={props.id || ''} titleClassName={'ellipsis'} />
+              <Ellipsis title={props.id || ''} />
             </div>
             {props.cardType === 'bind' ? (
               <div className={'flex-auto'}>
                 <label>说明</label>
-                <Ellipsis title={props.describe} titleClassName={'ellipsis'} />
+                <Ellipsis title={props.describe} />
               </div>
             ) : (
               <div className={'flex-auto'}>
                 <label>资产权限</label>
-                <Ellipsis
-                  title={handlePermissionsMap(props.grantedPermissions)}
-                  titleClassName={'ellipsis'}
-                />
+                <Ellipsis title={handlePermissionsMap(props.grantedPermissions)} />
               </div>
             )}
           </div>
@@ -136,11 +133,11 @@ export default (props: ProductCardProps) => {
           <div className={'card-item-content'}>
             <div>
               <label>设备类型</label>
-              <Ellipsis title={props?.deviceType?.text} titleClassName={'ellipsis'} />
+              <Ellipsis title={props?.deviceType?.text} />
             </div>
             <div>
               <label>接入方式</label>
-              <Ellipsis title={props.protocolName || '未接入'} titleClassName={'ellipsis'} />
+              <Ellipsis title={props.protocolName || '未接入'} />
             </div>
           </div>
         </div>

+ 16 - 5
src/components/ProTableCard/index.tsx

@@ -22,6 +22,13 @@ type ModelType = keyof typeof ModelEnum;
 interface ProTableCardProps<T> {
   cardRender?: (data: T) => JSX.Element | React.ReactNode;
   gridColumn?: number;
+  /**
+   * 用于不同分辨率
+   * gridColumns[0] 1366 ~ 1440 分辨率;
+   * gridColumns[1] 1440 ~  1600 分辨率;
+   * gridColumns[2] > 1600 分辨率;
+   */
+  gridColumns?: [number, number, number];
   height?: 'none';
 }
 
@@ -69,7 +76,7 @@ const ProTableCard = <
             'item-active': selectedRowKeys && selectedRowKeys.includes(id),
           }),
           key: id,
-          onClick: (e) => {
+          onClick: (e: any) => {
             e.stopPropagation();
             if (onChange) {
               const isSelect = selectedRowKeys.includes(id);
@@ -123,11 +130,14 @@ const ProTableCard = <
 
   const windowChange = () => {
     if (window.innerWidth <= 1440) {
-      setColumn(props.gridColumn && props.gridColumn < 2 ? props.gridColumn : 2);
+      const _column = props.gridColumn && props.gridColumn < 2 ? props.gridColumn : 2;
+      setColumn(props.gridColumns ? props.gridColumns[0] : _column);
     } else if (window.innerWidth > 1440 && window.innerWidth <= 1600) {
-      setColumn(props.gridColumn && props.gridColumn < 3 ? props.gridColumn : 3);
+      const _column = props.gridColumn && props.gridColumn < 3 ? props.gridColumn : 3;
+      setColumn(props.gridColumns ? props.gridColumns[1] : _column);
     } else if (window.innerWidth > 1600) {
-      setColumn(props.gridColumn && props.gridColumn < 4 ? props.gridColumn : 4);
+      const _column = props.gridColumn && props.gridColumn < 4 ? props.gridColumn : 4;
+      setColumn(props.gridColumns ? props.gridColumns[2] : _column);
     }
   };
 
@@ -137,7 +147,7 @@ const ProTableCard = <
     return () => {
       window.removeEventListener('resize', windowChange);
     };
-  }, []);
+  }, [props.gridColumns]);
 
   const pageSizeOptions = [Default_Size * 2, Default_Size * 4, Default_Size * 8, Default_Size * 16];
 
@@ -162,6 +172,7 @@ const ProTableCard = <
             pageSize,
           } as any
         }
+        columnEmptyText={''}
         className={'pro-table-card-body'}
         options={model === ModelEnum.CARD ? false : props.options}
         request={async (param, sort, filter) => {

+ 92 - 66
src/components/SearchComponent/index.tsx

@@ -200,92 +200,118 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
         initialValues: initParams,
         effects() {
           onFieldReact('*.*.column', async (typeFiled, f) => {
-            if ((typeFiled as Field).modified) {
-              const _column = (typeFiled as Field).value;
-              const _field = field.find((item) => item.dataIndex === _column);
-              if (_column === 'id') {
+            // if ((typeFiled as Field).modified) {
+            const isModified = (typeFiled as Field).modified;
+            const _column = (typeFiled as Field).value;
+            const _field = field.find((item) => item.dataIndex === _column);
+            if (_column === 'id') {
+              if (isModified) {
                 f.setFieldState(typeFiled.query('.termType'), async (state) => {
                   state.value = 'eq';
                 });
-                f.setFieldState(typeFiled.query('.value'), async (state) => {
-                  state.componentType = 'Input';
-                  state.componentProps = { allowClear: true };
-                });
-              } else {
-                switch (_field?.valueType) {
-                  case 'select':
-                    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('.value'), async (state) => {
+                state.componentType = 'Input';
+                state.componentProps = { allowClear: true };
+              });
+            } else {
+              switch (_field?.valueType) {
+                case 'select':
+                  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();
+                  }
+                  if (isModified) {
                     f.setFieldState(typeFiled.query('.termType'), async (state) => {
                       state.value = 'eq';
                     });
-                    f.setFieldState(typeFiled.query('.value'), async (state) => {
-                      console.log(state.value);
-                      state.componentType = 'Select';
-                      state.dataSource = __option;
-                      state.componentProps = { allowClear: true };
-                    });
-                    break;
-                  case '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('.value'), async (state) => {
+                    console.log(state.value);
+                    state.componentType = 'Select';
+                    state.dataSource = __option;
+                    state.componentProps = {
+                      allowClear: true,
+                      showSearch: true,
+                      filterOption: (input: string, option: any) =>
+                        option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                    };
+                  });
+                  break;
+                case '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();
+                  }
+                  if (isModified) {
                     f.setFieldState(typeFiled.query('.termType'), (_state) => {
                       _state.value = 'eq';
                     });
-                    f.setFieldState(typeFiled.query('.value'), (state) => {
-                      state.componentType = 'TreeSelect';
-                      state.dataSource = _option;
-                      state.componentProps = {
-                        ..._field.fieldProps,
-                        allowClear: true,
-                        treeNodeFilterProp: 'name',
-                      };
-                    });
-                    break;
-                  case 'digit':
-                    f.setFieldState(typeFiled.query('.value'), async (state) => {
-                      state.componentType = 'NumberPicker';
-                      state.componentProps = { allowClear: true };
-                    });
+                  }
+
+                  f.setFieldState(typeFiled.query('.value'), (state) => {
+                    state.componentType = 'TreeSelect';
+                    state.dataSource = _option;
+                    state.componentProps = {
+                      ..._field.fieldProps,
+                      allowClear: true,
+                      showSearch: true,
+                      treeNodeFilterProp: 'name',
+                      filterOption: (input: string, option: any) =>
+                        option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                    };
+                  });
+                  break;
+                case 'digit':
+                  f.setFieldState(typeFiled.query('.value'), async (state) => {
+                    state.componentType = 'NumberPicker';
+                    state.componentProps = { allowClear: true };
+                  });
+                  if (isModified) {
                     f.setFieldState(typeFiled.query('.termType'), async (state) => {
                       state.value = 'eq';
                     });
-                    break;
-                  case 'dateTime':
-                    f.setFieldState(typeFiled.query('.value'), async (state) => {
-                      state.componentType = 'DatePicker';
-                      state.componentProps = { showTime: true, allowClear: true };
-                    });
+                  }
+
+                  break;
+                case 'dateTime':
+                  f.setFieldState(typeFiled.query('.value'), async (state) => {
+                    state.componentType = 'DatePicker';
+                    state.componentProps = { showTime: true, allowClear: true };
+                  });
+                  if (isModified) {
                     f.setFieldState(typeFiled.query('.termType'), async (state) => {
                       state.value = 'gte';
                     });
-                    break;
-                  default:
+                  }
+
+                  break;
+                default:
+                  if (isModified) {
                     f.setFieldState(typeFiled.query('.termType'), async (state) => {
                       state.value = 'like';
                     });
-                    f.setFieldState(typeFiled.query('.value'), async (state) => {
-                      state.componentType = 'Input';
-                      state.componentProps = { allowClear: true };
-                    });
-                    break;
-                }
+                  }
+
+                  f.setFieldState(typeFiled.query('.value'), async (state) => {
+                    state.componentType = 'Input';
+                    state.componentProps = { allowClear: true };
+                  });
+                  break;
               }
             }
+            // }
           });
           onFieldValueChange('*.*.column', (field1, form1) => {
             form1.setFieldState(field1.query('.value'), (state1) => {

+ 3 - 1
src/global.less

@@ -137,7 +137,9 @@ input[type='text'],
 input[type='password'],
 input[type='number'],
 input[type='tel'] {
-  box-shadow: inset 0 0 0 100vw white !important;
+  &:not([disabled]) {
+    box-shadow: inset 0 0 0 100vw white !important;
+  }
 }
 
 //引导遮罩

+ 1 - 0
src/pages/Log/Access/index.tsx

@@ -132,6 +132,7 @@ const Access = () => {
       <ProTable<AccessLogItem>
         columns={columns}
         params={param}
+        columnEmptyText={''}
         tableClassName={'accessLog'}
         tableStyle={{ minHeight }}
         scroll={{ x: 1366 }}

+ 1 - 0
src/pages/Log/System/index.tsx

@@ -123,6 +123,7 @@ const System = () => {
       <ProTable<SystemLogItem>
         columns={columns}
         params={param}
+        columnEmptyText={''}
         scroll={{ x: 1366 }}
         tableClassName={'systemLog'}
         tableStyle={{ minHeight }}

+ 1 - 0
src/pages/account/NotificationRecord/index.tsx

@@ -186,6 +186,7 @@ const NotificationRecord = observer(() => {
         search={false}
         tableClassName={'record'}
         tableStyle={{ minHeight }}
+        columnEmptyText={''}
         request={async (params) =>
           service.queryList(encodeQuery({ ...params, sorts: { notifyTime: 'desc' } }))
         }

+ 1 - 0
src/pages/account/NotificationSubscription/index.tsx

@@ -185,6 +185,7 @@ const NotificationSubscription = observer(() => {
         columns={columns}
         scroll={{ x: 1366 }}
         search={false}
+        columnEmptyText={''}
         tableClassName={'subscription'}
         tableStyle={{ minHeight }}
         rowKey="id"

+ 1 - 0
src/pages/device/Alarm/index.tsx

@@ -175,6 +175,7 @@ const Alarm = () => {
         pagination={{
           pageSize: 10,
         }}
+        columnEmptyText={''}
         actionRef={actionRef}
       />
     </PageContainer>

+ 1 - 0
src/pages/device/Category/index.tsx

@@ -179,6 +179,7 @@ const Category = observer(() => {
       <ProTable
         params={param}
         search={false}
+        columnEmptyText={''}
         request={async (params) => {
           const response = await service.queryTree({
             paging: false,

+ 1 - 0
src/pages/device/Command/index.tsx

@@ -198,6 +198,7 @@ const Command = observer(() => {
         columns={columns}
         actionRef={actionRef}
         rowKey="id"
+        columnEmptyText={''}
       />
       <Create
         visible={state.visible}

+ 22 - 0
src/pages/device/Firmware/Task/Detail/Details/index.tsx

@@ -0,0 +1,22 @@
+import { Modal } from 'antd';
+import { useEffect, useState } from 'react';
+
+interface Props {
+  data: string;
+  close: () => void;
+}
+
+const Details = (props: Props) => {
+  const [data, setDada] = useState<string>(props.data || '');
+
+  useEffect(() => {
+    setDada(props.data);
+  }, [props.data]);
+
+  return (
+    <Modal title={'详情'} visible onCancel={props.close} onOk={props.close} width={500}>
+      <p>失败原因: {String(data)}</p>
+    </Modal>
+  );
+};
+export default Details;

+ 22 - 3
src/pages/device/Firmware/Task/Detail/index.less

@@ -7,9 +7,28 @@
   border-radius: 2px;
   box-shadow: 0 4px 18px #efefef;
 
-  .firmwareDetailCardTitle {
-    color: rgba(0, 0, 0, 0.64);
-    font-size: 14px;
+  .firmwareDetailCardHeader {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+
+    .firmwareDetailCardTitle {
+      color: rgba(0, 0, 0, 0.64);
+      font-size: 14px;
+    }
+
+    .firmwareDetailCardRight {
+      z-index: 2;
+      display: flex;
+      align-items: center;
+      margin-right: 10px;
+      .firmwareDetailCardRefresh {
+        width: 25px;
+        margin-left: 5px;
+        cursor: pointer;
+      }
+    }
   }
 
   .firmwareDetailCardNum {

+ 233 - 30
src/pages/device/Firmware/Task/Detail/index.tsx

@@ -1,20 +1,20 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { observer } from '@formily/react';
-import { Badge, Card, Col, Row } from 'antd';
+import { Badge, Card, Col, message, Popconfirm, Row } from 'antd';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import { Tooltip } from 'antd';
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { EyeOutlined } from '@ant-design/icons';
-// import { useHistory } from 'umi';
+import { RedoOutlined, SearchOutlined } from '@ant-design/icons';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import useDomFullHeight from '@/hooks/document/useDomFullHeight';
-// import usePermissions from '@/hooks/permission';
 import SearchComponent from '@/components/SearchComponent';
 import { service } from '@/pages/device/Firmware';
 import styles from './index.less';
 import { model } from '@formily/reactive';
+import { useParams } from 'umi';
+import Details from './Details/index';
 
 const colorMap = new Map();
 colorMap.set('waiting', '#FF9000');
@@ -38,8 +38,12 @@ const Detail = observer(() => {
   const actionRef = useRef<ActionType>();
   const intl = useIntl();
   const { minHeight } = useDomFullHeight(`.firmware-task-detail`, 24);
-  // const { permission } = usePermissions('device/Firmware');
   const [param, setParam] = useState({});
+  const params = useParams<any>();
+  const [visible, setVisible] = useState<boolean>(false);
+  const [reason, setReason] = useState<string>('');
+
+  const buttonImg = require('/public/images/firmware/button.png');
 
   const arr = [
     {
@@ -64,36 +68,160 @@ const Detail = observer(() => {
     },
   ];
 
+  const statusMap = new Map();
+  statusMap.set('waiting', 'warning');
+  statusMap.set('processing', 'processing');
+  statusMap.set('failed', 'error');
+  statusMap.set('success', 'success');
+  statusMap.set('canceled', 'default');
+
+  // 等待升级
+  const queryWaiting = async () => {
+    const resp = await service.historyCount({
+      terms: [
+        {
+          terms: [
+            { column: 'taskId', value: params.id },
+            { column: 'state', value: 'waiting' },
+          ],
+        },
+      ],
+    });
+    if (resp.status === 200) {
+      state.waiting = resp?.result || 0;
+    }
+  };
+  // 升级中
+  const queryProcessing = async () => {
+    const resp = await service.historyCount({
+      terms: [
+        {
+          terms: [
+            { column: 'taskId', value: params.id },
+            { column: 'state', value: 'processing' },
+          ],
+        },
+      ],
+    });
+    if (resp.status === 200) {
+      state.loading = resp?.result || 0;
+    }
+  };
+  // 升级失败
+  const queryFailed = async () => {
+    const resp = await service.historyCount({
+      terms: [
+        {
+          terms: [
+            { column: 'taskId', value: params.id },
+            { column: 'state', value: 'failed' },
+          ],
+        },
+      ],
+    });
+    if (resp.status === 200) {
+      state.error = resp?.result || 0;
+    }
+  };
+  // 升级完成
+  const querySuccess = async () => {
+    const resp = await service.historyCount({
+      terms: [
+        {
+          terms: [
+            { column: 'taskId', value: params.id },
+            { column: 'state', value: 'success' },
+          ],
+        },
+      ],
+    });
+    if (resp.status === 200) {
+      state.finish = resp?.result || 0;
+    }
+  };
+
+  const handleSearch = () => {
+    queryWaiting();
+    queryProcessing();
+    querySuccess();
+    queryFailed();
+  };
+
+  useEffect(() => {
+    handleSearch();
+  }, [params.id]);
+
   const columns: ProColumns<FirmwareItem>[] = [
     {
       title: '设备名称',
       ellipsis: true,
-      dataIndex: 'name',
+      dataIndex: 'deviceName',
     },
     {
       title: '所属产品',
       ellipsis: true,
-      dataIndex: 'version',
+      dataIndex: 'productName',
+      valueType: 'select',
+      request: async () => {
+        const res: any = await service.queryProduct();
+        if (res.status === 200) {
+          return res.result.map((pItem: any) => ({ label: pItem.name, value: pItem.name }));
+        }
+        return [];
+      },
     },
     {
       title: '创建时间',
       ellipsis: true,
-      dataIndex: 'signMethod',
+      dataIndex: 'createTime',
+      valueType: 'dateTime',
+      // render: (text: any) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
     },
     {
       title: '完成时间',
       ellipsis: true,
-      dataIndex: 'signMethod',
+      dataIndex: 'completeTime',
+      valueType: 'dateTime',
+      // render: (text: any) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
     },
     {
       title: '进度',
       ellipsis: true,
-      dataIndex: 'signMethod',
+      dataIndex: 'progress',
     },
     {
       title: '状态',
       ellipsis: true,
-      dataIndex: 'signMethod',
+      dataIndex: 'state',
+      render: (text: any, record: any) =>
+        record?.state ? (
+          <Badge status={statusMap.get(record?.state?.value)} text={record?.state?.text} />
+        ) : (
+          ''
+        ),
+      valueType: 'select',
+      valueEnum: {
+        waiting: {
+          text: '等待升级',
+          status: 'waiting',
+        },
+        processing: {
+          text: '升级中',
+          status: 'processing',
+        },
+        failed: {
+          text: '升级失败',
+          status: 'failed',
+        },
+        success: {
+          text: '升级成功',
+          status: 'success',
+        },
+        canceled: {
+          text: '已取消',
+          status: 'canceled',
+        },
+      },
     },
     {
       title: intl.formatMessage({
@@ -102,22 +230,51 @@ const Detail = observer(() => {
       }),
       valueType: 'option',
       align: 'center',
+      fixed: 'right',
       width: 200,
-      render: () => [
-        <a onClick={() => {}} key="link">
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.detail',
-              defaultMessage: '查看',
-            })}
-            key={'detail'}
-          >
-            <EyeOutlined />
-          </Tooltip>
-        </a>,
-      ],
+      render: (text: any, record: any) =>
+        record?.state?.value === 'failed'
+          ? [
+              <a
+                onClick={() => {
+                  setVisible(true);
+                  setReason(record?.errorReason || '');
+                }}
+                key="link"
+              >
+                <Tooltip
+                  title={intl.formatMessage({
+                    id: 'pages.data.option.detail',
+                    defaultMessage: '查看',
+                  })}
+                  key={'detail'}
+                >
+                  <SearchOutlined />
+                </Tooltip>
+              </a>,
+              <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>,
+            ]
+          : [],
     },
   ];
+
   return (
     <PageContainer>
       <Card style={{ marginBottom: 20 }}>
@@ -125,9 +282,45 @@ const Detail = observer(() => {
           {arr.map((item) => (
             <Col span={6} key={item.key}>
               <div className={styles.firmwareDetailCard}>
-                <div className={styles.firmwareDetailCardTitle}>
-                  <Badge color={colorMap.get(item.key)} />
-                  {item.name}
+                <div className={styles.firmwareDetailCardHeader}>
+                  <div className={styles.firmwareDetailCardTitle}>
+                    <Badge color={colorMap.get(item.key)} />
+                    {item.name}
+                  </div>
+                  <div className={styles.firmwareDetailCardRight}>
+                    {item.key === 'error' && (
+                      <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>
+                    )}
+                    <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 {
+                            queryFailed();
+                          }
+                        }}
+                      />
+                    </div>
+                  </div>
                 </div>
                 <div
                   className={styles.firmwareDetailCardNum}
@@ -146,6 +339,7 @@ const Detail = observer(() => {
       <SearchComponent<FirmwareItem>
         field={columns}
         target="firmware-task-detail"
+        defaultParam={[{ column: 'taskId', value: params?.id }]}
         onSearch={(data) => {
           // 重置分页数据
           actionRef.current?.reset?.();
@@ -157,13 +351,22 @@ const Detail = observer(() => {
         tableClassName={'firmware-task-detail'}
         tableStyle={{ minHeight }}
         search={false}
+        columnEmptyText={''}
         params={param}
-        request={async (params) =>
-          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        request={async (params1) =>
+          service.history({ ...params1, sorts: [{ name: 'createTime', order: 'desc' }] })
         }
         columns={columns}
         actionRef={actionRef}
       />
+      {visible && (
+        <Details
+          data={reason}
+          close={() => {
+            setVisible(false);
+          }}
+        />
+      )}
     </PageContainer>
   );
 });

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

@@ -1,48 +1,43 @@
 import { Modal } from 'antd';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import { createSchemaField } from '@formily/react';
-import {
-  Form,
-  FormGrid,
-  FormItem,
-  Input,
-  Select,
-  ArrayTable,
-  NumberPicker,
-  Radio,
-} from '@formily/antd';
-import { createForm, onFieldValueChange } from '@formily/core';
+import { Form, FormGrid, FormItem, Input, Select, NumberPicker, Radio } from '@formily/antd';
+import { createForm, onFieldValueChange, onFormInit } from '@formily/core';
 import type { ISchema } from '@formily/json-schema';
-import FUpload from '@/components/Upload';
 import { service } from '@/pages/device/Firmware';
-import type { Response } from '@/utils/typings';
-import { useRef } from 'react';
-import type { ProductItem } from '@/pages/device/Product/typings';
+import { useEffect, useRef } from 'react';
 import { onlyMessage } from '@/utils/util';
 import FSelectDevices from '@/components/FSelectDevices';
+import type { DeviceInstance } from '@/pages/device/Instance/typings';
 
 interface Props {
+  ids: { id: string; productId: string };
   data?: FirmwareItem;
   close: () => void;
+  save: () => void;
   visible: boolean;
 }
 
 const Save = (props: Props) => {
-  const { data, close, visible } = props;
+  const { data, close, visible, ids } = props;
 
   const form = createForm({
     validateFirst: true,
     initialValues: data,
     effects() {
+      onFormInit(async (form1) => {
+        if (!data?.id) return;
+        form1.setInitialValues({ ...data, upload: { url: data?.url } });
+      });
       onFieldValueChange('mode', async (field) => {
         field
-          .query('timeoutSeconds1')
+          .query('timeoutSeconds')
           .take()
           .setDecoratorProps({
             gridSpan: field.value === 'push' ? 1 : 2,
           });
         field
-          .query('timeoutSeconds')
+          .query('responseTimeoutSeconds')
           .take()
           .setDecoratorProps({
             gridSpan: field.value === 'push' ? 1 : 2,
@@ -56,33 +51,50 @@ const Save = (props: Props) => {
     },
   });
 
-  const products = useRef<ProductItem[]>([]);
+  const devices = useRef<DeviceInstance[]>([]);
 
   const SchemaField = createSchemaField({
     components: {
       FormItem,
       FormGrid,
       Input,
-      FUpload,
       Select,
-      ArrayTable,
       NumberPicker,
       Radio,
       FSelectDevices,
     },
   });
 
+  useEffect(() => {
+    if (visible) {
+      service.queryDevice().then((resp) => {
+        if (resp.status === 200) {
+          devices.current = resp.result;
+        }
+      });
+    }
+  }, [visible]);
+
   const save = async () => {
-    const values: FirmwareItem = await form.submit();
-    const product = products.current?.find((item) => item.id === values.productId);
-    values.productName = product?.name || '';
-    const resp = (await service.save(values)) as Response<FirmwareItem>;
+    const values: any = await form.submit();
+    if (values?.releaseType !== 'all') {
+      values.deviceId = devices.current.map((item) => item.id);
+    } else {
+      values.deviceId = undefined;
+    }
+    const resp = await service.saveTask({
+      ...values,
+      firmwareId: ids?.id,
+      productId: ids?.productId,
+    });
     if (resp.status === 200) {
       onlyMessage('保存成功!');
+      props.save();
     } else {
       onlyMessage('保存失败!', 'error');
     }
   };
+
   const schema: ISchema = {
     type: 'object',
     properties: {
@@ -138,10 +150,10 @@ const Save = (props: Props) => {
               },
             ],
           },
-          timeoutSeconds: {
+          responseTimeoutSeconds: {
             title: '响应超时时间',
             'x-decorator': 'FormItem',
-            'x-component': 'Input',
+            'x-component': 'NumberPicker',
             'x-component-props': {
               placeholder: '请输入响应超时时间(秒)',
             },
@@ -166,10 +178,10 @@ const Save = (props: Props) => {
               },
             },
           },
-          timeoutSeconds1: {
+          timeoutSeconds: {
             title: '升级超时时间',
             'x-decorator': 'FormItem',
-            'x-component': 'Input',
+            'x-component': 'NumberPicker',
             'x-component-props': {
               placeholder: '请输入升级超时时间(秒)',
             },
@@ -221,7 +233,7 @@ const Save = (props: Props) => {
               },
             },
           },
-          part: {
+          deviceId: {
             title: '选择设备',
             'x-decorator': 'FormItem',
             'x-component': 'FSelectDevices',
@@ -265,7 +277,7 @@ const Save = (props: Props) => {
     <Modal
       maskClosable={false}
       width="50vw"
-      title="新增任务"
+      title={data?.id ? '编辑任务' : '新增任务'}
       onCancel={() => close()}
       onOk={() => save()}
       visible={visible}

+ 85 - 32
src/pages/device/Firmware/Task/index.tsx

@@ -1,11 +1,17 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { Popconfirm, Tooltip } from 'antd';
+import { message, Popconfirm, Tooltip } from 'antd';
 import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { EditOutlined, EyeOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons';
-import { Link, useHistory } from 'umi';
+import {
+  ControlOutlined,
+  DeleteOutlined,
+  EyeOutlined,
+  PlusOutlined,
+  StopOutlined,
+} from '@ant-design/icons';
+import { useHistory, useLocation } from 'umi';
 import { model } from '@formily/reactive';
 import { observer } from '@formily/react';
 import type { FirmwareItem } from '@/pages/device/Firmware/typings';
@@ -18,6 +24,37 @@ import SearchComponent from '@/components/SearchComponent';
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import { service } from '@/pages/device/Firmware';
 
+const UpgradeBtn = (props: { data: any; actions: any }) => {
+  const { data, actions } = props;
+  return (
+    <a>
+      <Tooltip title={data.waiting ? '停止' : '继续升级'}>
+        {data.waiting ? (
+          <StopOutlined
+            onClick={async () => {
+              const resp = await service.stopTask(data.id);
+              if (resp.status === 200) {
+                message.success('操作成功!');
+                actions?.reload();
+              }
+            }}
+          />
+        ) : (
+          <ControlOutlined
+            onClick={async () => {
+              const resp = await service.startTask(data.id, ['canceled']);
+              if (resp.status === 200) {
+                message.success('操作成功!');
+                actions?.reload();
+              }
+            }}
+          />
+        )}
+      </Tooltip>
+    </a>
+  );
+};
+
 export const state = model<{
   current?: FirmwareItem;
   visible: boolean;
@@ -31,8 +68,11 @@ const Task = observer(() => {
   const { permission } = usePermissions('device/Firmware');
   const [param, setParam] = useState({});
   const history = useHistory<Record<string, string>>();
+  const location = useLocation<{ id: string }>();
+  const id = (location as any).query?.id || '';
+  const productId = (location as any).query?.productId || '';
 
-  const columns: ProColumns<FirmwareItem>[] = [
+  const columns: ProColumns<any>[] = [
     {
       title: '任务名称',
       ellipsis: true,
@@ -41,7 +81,19 @@ const Task = observer(() => {
     {
       title: '推送方式',
       ellipsis: true,
-      dataIndex: 'version',
+      dataIndex: 'mode',
+      render: (text: any, record: any) => record?.mode?.text || '',
+      valueType: 'select',
+      valueEnum: {
+        pull: {
+          text: '设备拉取',
+          status: 'pull',
+        },
+        push: {
+          text: '平台推送',
+          status: 'push',
+        },
+      },
     },
     {
       title: intl.formatMessage({
@@ -55,7 +107,8 @@ const Task = observer(() => {
     {
       title: '完成比例',
       ellipsis: true,
-      dataIndex: 'signMethod',
+      // hideInSearch: true,
+      dataIndex: 'progress',
     },
     {
       title: intl.formatMessage({
@@ -65,14 +118,13 @@ const Task = observer(() => {
       valueType: 'option',
       align: 'center',
       width: 200,
-
+      fixed: 'right',
       render: (text, record) => [
-        <Link
+        <a
           onClick={() => {
-            const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task/Detail'], '123');
+            const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task/Detail'], record?.id);
             history.push(url);
           }}
-          to={`/device/firmware/detail/${record.id}`}
           key="link"
         >
           <Tooltip
@@ -84,30 +136,20 @@ const Task = observer(() => {
           >
             <EyeOutlined />
           </Tooltip>
-        </Link>,
-        <a
-          key="editable"
-          onClick={() => {
-            state.visible = true;
-          }}
-        >
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.edit',
-              defaultMessage: '编辑',
-            })}
-          >
-            <EditOutlined />
-          </Tooltip>
         </a>,
+        <UpgradeBtn data={record} actions={actionRef.current} key="btn" />,
         <a key="delete">
           <Popconfirm
-            title={intl.formatMessage({
-              id: 'pages.data.option.remove.tips',
-              defaultMessage: '确认删除?',
-            })}
+            title={
+              record.waiting
+                ? '删除将导致正在进行的任务终止,确定要删除吗?'
+                : intl.formatMessage({
+                    id: 'pages.data.option.remove.tips',
+                    defaultMessage: '确认删除?',
+                  })
+            }
             onConfirm={async () => {
-              await service.remove(record.id);
+              await service.deleteTask(record.id);
               onlyMessage(
                 intl.formatMessage({
                   id: 'pages.data.option.success',
@@ -123,7 +165,7 @@ const Task = observer(() => {
                 defaultMessage: '删除',
               })}
             >
-              <MinusOutlined />
+              <DeleteOutlined />
             </Tooltip>
           </Popconfirm>
         </a>,
@@ -141,6 +183,7 @@ const Task = observer(() => {
           actionRef.current?.reset?.();
           setParam(data);
         }}
+        defaultParam={[{ column: 'firmwareId', value: id }]}
       />
       <ProTable<FirmwareItem>
         scroll={{ x: 1366 }}
@@ -148,6 +191,8 @@ const Task = observer(() => {
         tableStyle={{ minHeight }}
         search={false}
         params={param}
+        rowKey="id"
+        columnEmptyText={''}
         headerTitle={
           <div>
             <PermissionButton
@@ -167,14 +212,22 @@ const Task = observer(() => {
           </div>
         }
         request={async (params) =>
-          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+          service.task({
+            ...params,
+            sorts: [{ name: 'createTime', order: 'desc' }],
+          })
         }
         columns={columns}
         actionRef={actionRef}
       />
       <Save
         data={state.current}
+        ids={{ id: id, productId: productId }}
         visible={state.visible}
+        save={() => {
+          state.visible = false;
+          actionRef.current?.reload?.();
+        }}
         close={() => {
           state.visible = false;
         }}

+ 24 - 4
src/pages/device/Firmware/index.tsx

@@ -2,7 +2,6 @@ import { PageContainer } from '@ant-design/pro-layout';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import { Popconfirm } from 'antd';
-import moment from 'moment';
 import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { DeleteOutlined, EditOutlined, NodeExpandOutlined, PlusOutlined } from '@ant-design/icons';
@@ -58,6 +57,14 @@ const Firmware = observer(() => {
       }),
       ellipsis: true,
       dataIndex: 'productName',
+      valueType: 'select',
+      request: () =>
+        service.queryProduct().then((resp: any) =>
+          (resp?.result || []).map((item: any) => ({
+            label: item.name,
+            value: item.name,
+          })),
+        ),
     },
     {
       title: intl.formatMessage({
@@ -65,7 +72,18 @@ const Firmware = observer(() => {
         defaultMessage: '签名方式',
       }),
       ellipsis: true,
+      valueType: 'select',
       dataIndex: 'signMethod',
+      valueEnum: {
+        md5: {
+          text: 'MD5',
+          status: 'md5',
+        },
+        sha256: {
+          text: 'SHA256',
+          status: 'sha256',
+        },
+      },
     },
     {
       title: intl.formatMessage({
@@ -77,7 +95,7 @@ const Firmware = observer(() => {
       align: 'center',
       ellipsis: true,
       valueType: 'dateTime',
-      render: (text: any) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+      // render: (text: any) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
       // sorter: true,
       // defaultSortOrder: 'descend',
     },
@@ -98,6 +116,7 @@ const Firmware = observer(() => {
       valueType: 'option',
       align: 'center',
       width: 200,
+      fixed: 'right',
       render: (text, record) => [
         <PermissionButton
           style={{ padding: 0 }}
@@ -105,8 +124,8 @@ const Firmware = observer(() => {
           isPermission={permission.action}
           key="upgrade"
           onClick={() => {
-            const url = getMenuPathByParams(MENUS_CODE['device/Firmware/Task'], record?.id);
-            history.push(url);
+            const url = `${getMenuPathByParams(MENUS_CODE['device/Firmware/Task'])}`;
+            history.push(`${url}?id=${record?.id}&productId=${record?.productId}`);
           }}
           tooltip={{
             title: '升级任务',
@@ -169,6 +188,7 @@ const Firmware = observer(() => {
         tableStyle={{ minHeight }}
         search={false}
         params={param}
+        rowKey="id"
         columnEmptyText={''}
         headerTitle={
           <div>

+ 31 - 15
src/pages/device/Firmware/service.ts

@@ -5,9 +5,9 @@ import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 
 class Service extends BaseService<FirmwareItem> {
   task = (params: Record<string, unknown>) =>
-    request(`/${SystemConst.API_BASE}/firmware/upgrade/task/_query`, {
-      method: 'GET',
-      params,
+    request(`/${SystemConst.API_BASE}/firmware/upgrade/task/detail/_query`, {
+      method: 'POST',
+      data: params,
     });
 
   saveTask = (data: Record<string, unknown>) =>
@@ -16,29 +16,45 @@ class Service extends BaseService<FirmwareItem> {
       data,
     });
 
-  deploy = (id: string, type?: 'all' | 'part', deviceId?: string[]) =>
-    request(
-      `/${SystemConst.API_BASE}/firmware/upgrade/task/${id}${type === 'all' ? '/all' : ''}/_deploy`,
-      {
-        method: 'POST',
-        data: deviceId,
-      },
-    );
+  deleteTask = (id: string) =>
+    request(`/${SystemConst.API_BASE}/firmware/upgrade/task/${id}`, {
+      method: 'DELETE',
+    });
 
   history = (params: Record<string, unknown>) =>
     request(`/${SystemConst.API_BASE}/firmware/upgrade/history/_query`, {
-      method: 'GET',
-      params,
+      method: 'POST',
+      data: params,
     });
 
   historyCount = (params: Record<string, unknown>) =>
     request(`/${SystemConst.API_BASE}/firmware/upgrade/history/_count`, {
-      method: 'GET',
-      params,
+      method: 'POST',
+      data: params,
+    });
+
+  startTask = (id: string, params: string[]) =>
+    request(`/${SystemConst.API_BASE}/firmware/upgrade/task/${id}/_start`, {
+      method: 'POST',
+      data: params,
+    });
+
+  stopTask = (id: string) =>
+    request(`/${SystemConst.API_BASE}/firmware/upgrade/task/${id}/_stop`, {
+      method: 'POST',
+    });
+
+  startOneTask = (params: string[]) =>
+    request(`/${SystemConst.API_BASE}/firmware/upgrade/task/_start`, {
+      method: 'POST',
+      data: params,
     });
 
   queryProduct = () =>
     request(`/${SystemConst.API_BASE}/device/product/_query/no-paging?paging=false`);
+
+  queryDevice = () =>
+    request(`/${SystemConst.API_BASE}/device/instance/_query/no-paging?paging=false`);
 }
 
 export default Service;

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

@@ -164,6 +164,7 @@ const BindChildDevice = (props: Props) => {
         search={false}
         columns={columns}
         size="small"
+        columnEmptyText={''}
         rowSelection={{
           selectedRowKeys: bindKeys,
           onChange: (selectedRowKeys, selectedRows) => {

+ 4 - 1
src/pages/device/Instance/Detail/ChildDevice/index.tsx

@@ -127,7 +127,9 @@ const ChildDevice = () => {
             }}
             title={'确认解绑吗?'}
           >
-            <DisconnectOutlined />
+            <Tooltip title={'解绑'}>
+              <DisconnectOutlined />
+            </Tooltip>
           </Popconfirm>
         </a>,
       ],
@@ -162,6 +164,7 @@ const ChildDevice = () => {
         actionRef={actionRef}
         params={searchParams}
         rowKey="id"
+        columnEmptyText={''}
         rowSelection={{
           selectedRowKeys: bindKeys,
           onChange: (selectedRowKeys, selectedRows) => {

+ 6 - 6
src/pages/device/Instance/Detail/Diagnose/Status/model.ts

@@ -9,6 +9,12 @@ StatusMap.set('success', require('/public/images/diagnose/status/success.png'));
 StatusMap.set('warning', require('/public/images/diagnose/status/warning.png'));
 StatusMap.set('loading', require('/public/images/diagnose/status/loading.png'));
 
+export const textColorMap = new Map();
+textColorMap.set('loading', 'black');
+textColorMap.set('error', 'red');
+textColorMap.set('success', 'green');
+textColorMap.set('warning', 'red');
+
 export type ListProps = {
   key: string;
   name: string;
@@ -23,12 +29,6 @@ export const list = [
   { key: 'message', text: '消息通信' },
 ];
 
-export const textColorMap = new Map();
-textColorMap.set('loading', 'black');
-textColorMap.set('error', 'red');
-textColorMap.set('success', 'green');
-textColorMap.set('warning', 'red');
-
 export const networkInitList: ListProps[] = [
   // {
   //   key: 'access',

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

@@ -99,6 +99,7 @@ const Log = () => {
         search={false}
         columns={columns}
         size="small"
+        columnEmptyText={''}
         actionRef={actionRef}
         params={searchParams}
         toolBarRender={false}

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

@@ -47,6 +47,7 @@ const EventLog = (props: Props) => {
       <ProTable
         size="small"
         rowKey="id"
+        columnEmptyText={''}
         toolBarRender={false}
         request={async (param) =>
           service.getEventCount(params.id, data.id!, {

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

@@ -362,6 +362,7 @@ const Opcua = () => {
             params={param}
             columns={columns}
             rowKey="id"
+            columnEmptyText={''}
             search={false}
             headerTitle={
               <>

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

@@ -132,6 +132,7 @@ const EventLog = (props: Props) => {
         rowKey="id"
         actionRef={actionRef}
         search={false}
+        columnEmptyText={''}
         params={searchParams}
         request={async (param) => {
           param.pageIndex = param.current - 1;

+ 2 - 0
src/pages/device/Instance/Process/index.tsx

@@ -34,6 +34,8 @@ const Process = (props: Props) => {
           if (res.success) {
             dt += res.total;
             setCount(dt);
+          } else {
+            setErrMessage(res.message);
           }
           break;
         case 'sync':

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

@@ -36,12 +36,12 @@ import { service as categoryService } from '@/pages/device/Category';
 import { onlyMessage } from '@/utils/util';
 
 export const statusMap = new Map();
-statusMap.set('在线', 'success');
+statusMap.set('在线', 'processing');
 statusMap.set('离线', 'error');
-statusMap.set('未激活', 'processing');
-statusMap.set('online', 'success');
+statusMap.set('未激活', 'warning');
+statusMap.set('online', 'processing');
 statusMap.set('offline', 'error');
-statusMap.set('notActive', 'processing');
+statusMap.set('notActive', 'warning');
 
 export const InstanceModel = model<{
   current: Partial<DeviceInstance>;

+ 1 - 0
src/pages/device/components/Alarm/Record/index.tsx

@@ -111,6 +111,7 @@ const Record = (props: Props) => {
         actionRef={actionRef}
         columns={columns}
         rowKey="id"
+        columnEmptyText={''}
         request={(param) => service.record(param)}
         pagination={{
           pageSize: 10,

+ 6 - 1
src/pages/document.ejs

@@ -13,7 +13,12 @@
       content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
     />
     <title>Jetlinks</title>
-    <link rel="icon" id="ico" href="<%= context.config.publicPath + 'favicon.ico' %>" type="image/x-icon" />
+    <link
+      rel="icon"
+      id="ico"
+      href="<%= context.config.publicPath + 'favicon.ico' %>"
+      type="image/x-icon"
+    />
   </head>
   <body>
     <noscript>

+ 1 - 0
src/pages/home/components/DeviceChoose.tsx

@@ -134,6 +134,7 @@ export default (props: DeviceModalProps) => {
         columns={columns}
         rowKey={'id'}
         search={false}
+        columnEmptyText={''}
         request={(params) =>
           service.query({
             ...params,

+ 128 - 0
src/pages/init-home/components/basis.tsx

@@ -0,0 +1,128 @@
+import { UploadImage } from '@/components';
+import { Col, Form, Input, Row, Select } from 'antd';
+import { useEffect, forwardRef, useImperativeHandle } from 'react';
+import { service } from '../index';
+
+interface Props {
+  getData?: Function;
+}
+
+const Basis = forwardRef((props: Props, ref) => {
+  const [form] = Form.useForm();
+
+  const saveData = () => {
+    return new Promise(async (resolve) => {
+      const formData = await form.validateFields().catch(() => {
+        resolve(false);
+      });
+      if (formData) {
+        const item = [
+          {
+            scope: 'basis',
+            properties: {
+              ...formData,
+              apikey: '',
+            },
+          },
+          {
+            scope: 'api',
+            properties: {
+              api: formData.apikey,
+            },
+          },
+        ];
+        const res = await service.save(item);
+        if (res.status === 200) {
+          resolve(true);
+        } else {
+          resolve(false);
+        }
+      } else {
+        resolve(false);
+      }
+    });
+  };
+
+  useImperativeHandle(ref, () => ({
+    save: saveData,
+  }));
+
+  useEffect(() => {
+    if (props.getData) {
+      props.getData(form);
+    }
+  }, []);
+
+  return (
+    <Form layout="vertical" form={form}>
+      <Row gutter={[24, 24]}>
+        <Col span={10}>
+          <Form.Item label="系统名称" name="title">
+            <Input />
+          </Form.Item>
+          <Form.Item
+            label="主题色"
+            name="headerTheme"
+            initialValue="light"
+            rules={[{ required: true, message: '请选择主题色' }]}
+          >
+            <Select>
+              <Select.Option value="light">白色</Select.Option>
+              <Select.Option value="dark">黑色</Select.Option>
+            </Select>
+          </Form.Item>
+          <Form.Item label="高德API Key" name="apikey" tooltip="配置后平台可调用高德地图GIS服务">
+            <Input />
+          </Form.Item>
+          <Row gutter={[24, 24]}>
+            <Col>
+              <Form.Item
+                name={'logo'}
+                label="系统logo"
+                extra={
+                  <>
+                    <div>推荐尺寸200*200</div>
+                    <div>支持jpg,png</div>
+                  </>
+                }
+              >
+                <UploadImage />
+              </Form.Item>
+            </Col>
+            <Col>
+              <Form.Item
+                name={'ico'}
+                label="浏览器页签"
+                tooltip="浏览器tab页中显示的图片元素"
+                extra={
+                  <>
+                    <div>推荐尺寸64*64</div>
+                    <div>支持ico格式</div>
+                  </>
+                }
+              >
+                <UploadImage size={1} types={['image/x-icon']} backgroundSize={'inherit'} />
+              </Form.Item>
+            </Col>
+          </Row>
+        </Col>
+        <Col span={14}>
+          <Form.Item
+            name={'backgroud'}
+            label="登录背景图"
+            extra={
+              <>
+                <div>支持4M以内的图片:支持jpg、png</div>
+                <div>建议尺寸1400x1080</div>
+              </>
+            }
+            rules={[{ required: true, message: '请上传背景图' }]}
+          >
+            <UploadImage size={4} style={{ width: 570, height: 415 }} />
+          </Form.Item>
+        </Col>
+      </Row>
+    </Form>
+  );
+});
+export default Basis;

Fichier diff supprimé car celui-ci est trop grand
+ 2470 - 0
src/pages/init-home/components/data/RoleData.ts


+ 106 - 0
src/pages/init-home/components/data/index.tsx

@@ -0,0 +1,106 @@
+import { forwardRef, useImperativeHandle, useState } from 'react';
+import { service } from '../../index';
+import Save from './save';
+
+const Data = forwardRef((_, ref) => {
+  const [flag, setFlag] = useState<boolean>(false);
+  const [visible, setVisible] = useState<boolean>(false);
+  const [values, setValues] = useState<any>({});
+
+  const handleChange = () => {
+    return new Promise(async (resolve) => {
+      if (!Object.keys(values).length) {
+        return resolve(true);
+      }
+      try {
+        // 新增网络组件
+        const network = await service.saveNetwork({
+          type: 'MQTT_SERVER',
+          shareCluster: true,
+          name: 'MQTT网络组件',
+          configuration: {
+            host: '0.0.0.0',
+            secure: false,
+            port: values.port,
+            publicHost: values.publicHost,
+            publicPort: values.publicPort,
+          },
+        });
+        // 保存协议
+        const protocol = await service.saveProtocol();
+        let protocolItem: any = undefined;
+        if (protocol.status === 200) {
+          const proid = await service.getProtocol();
+          if (proid.status === 200) {
+            protocolItem = (proid?.result || []).find((it: any) => it.name === 'JetLinks官方协议');
+          }
+        }
+        // 新增设备接入网关
+        const accessConfig = await service.saveAccessConfig({
+          name: 'MQTT类型设备接入网关',
+          provider: 'mqtt-server-gateway',
+          protocol: protocolItem?.id,
+          transport: 'MQTT',
+          channel: 'network',
+          channelId: network?.result?.id,
+        });
+        // 新增产品
+        const product = await service.saveProduct({
+          name: 'MQTT产品',
+          messageProtocol: protocolItem?.id,
+          protocolName: protocolItem?.name,
+          transportProtocol: 'MQTT',
+          deviceType: 'device',
+          accessId: accessConfig.result?.id,
+          accessName: accessConfig.result?.name,
+          accessProvider: 'mqtt-server-gateway',
+        });
+        // 新增设备
+        const device = await service.saveDevice({
+          name: 'MQTT设备',
+          productId: product?.result?.id,
+          productName: product?.result?.name,
+        });
+        resolve(device.status === 200);
+      } catch (e) {
+        console.log(e);
+        resolve(false);
+      }
+    });
+  };
+
+  useImperativeHandle(ref, () => ({
+    save: handleChange,
+  }));
+
+  return (
+    <div>
+      <img
+        style={{ width: 300 }}
+        onClick={() => {
+          setVisible(true);
+        }}
+        src={
+          flag
+            ? require('/public/images/init-home/data-enabled.png')
+            : require('/public/images/init-home/data-disabled.png')
+        }
+      />
+      {visible && (
+        <Save
+          close={() => {
+            setVisible(false);
+          }}
+          data={values}
+          save={(data: any) => {
+            setVisible(false);
+            setFlag(true);
+            setValues(data);
+          }}
+        />
+      )}
+    </div>
+  );
+});
+
+export default Data;

+ 175 - 0
src/pages/init-home/components/data/save/index.tsx

@@ -0,0 +1,175 @@
+import { Modal } from 'antd';
+import { createSchemaField } from '@formily/react';
+import { Form, FormGrid, FormItem, Input, Select, NumberPicker } from '@formily/antd';
+import { onFormInit } from '@formily/core';
+import { createForm } from '@formily/core';
+import type { ISchema } from '@formily/json-schema';
+import { service } from '../../../index';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { onlyMessage } from '@/utils/util';
+
+interface Props {
+  data?: any;
+  close: () => void;
+  save: (values: any) => void;
+}
+
+const Save = (props: Props) => {
+  const { close, data } = props;
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+    effects: () => {
+      onFormInit(async (form1) => {
+        const resp = await service.getResourcesCurrent();
+        const current = resp?.result;
+        const _host = current.find((item: any) => item.host === '0.0.0.0')?.ports['TCP'] || [];
+        form1.setFieldState('port', (state) => {
+          state.dataSource = _host?.map((p: any) => ({ label: p, value: p }));
+        });
+      });
+    },
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormGrid,
+      Input,
+      NumberPicker,
+      Select,
+    },
+  });
+
+  const save = async () => {
+    const values: any = await form.submit();
+    onlyMessage('保存成功!');
+    props.save(values);
+  };
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      grid: {
+        type: 'void',
+        'x-component': 'FormGrid',
+        'x-component-props': {
+          minColumns: 2,
+          maxColumns: 2,
+        },
+        properties: {
+          host: {
+            title: '本地地址',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-component-props': {
+              placeholder: '请选择本地地址',
+            },
+            'x-disabled': true,
+            default: '0.0.0.0',
+            'x-decorator-props': {
+              gridSpan: 1,
+              labelAlign: 'left',
+              layout: 'vertical',
+              tooltip: '绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0',
+            },
+            required: true,
+            'x-validator': ['ipv4'],
+          },
+          port: {
+            title: '本地端口',
+            'x-decorator-props': {
+              gridSpan: 1,
+              labelAlign: 'left',
+              tooltip: '监听指定端口的请求',
+              layout: 'vertical',
+            },
+            required: true,
+            type: 'number',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-component-props': {
+              placeholder: '请选择本地端口',
+            },
+            'x-validator': [
+              {
+                max: 65535,
+                message: '请输入1-65535之间的整整数',
+              },
+              {
+                min: 1,
+                message: '请输入1-65535之间的整整数',
+              },
+            ],
+          },
+          publicHost: {
+            title: '公网地址',
+            'x-decorator-props': {
+              gridSpan: 1,
+              labelAlign: 'left',
+              tooltip: '对外提供访问的地址,内网环境是填写服务器的内网IP地址',
+              layout: 'vertical',
+            },
+            required: true,
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-validator': ['ipv4'],
+            'x-component-props': {
+              placeholder: '请输入公网地址',
+            },
+          },
+          publicPort: {
+            title: '公网端口',
+            'x-decorator-props': {
+              gridSpan: 1,
+              tooltip: '对外提供访问的端口',
+              layout: 'vertical',
+              labelAlign: 'left',
+            },
+            'x-component-props': {
+              placeholder: '请输入公网端口',
+            },
+            required: true,
+            'x-decorator': 'FormItem',
+            'x-component': 'NumberPicker',
+            'x-validator': [
+              {
+                max: 65535,
+                message: '请输入1-65535之间的整整数',
+              },
+              {
+                min: 1,
+                message: '请输入1-65535之间的整整数',
+              },
+            ],
+          },
+        },
+      },
+    },
+  };
+
+  return (
+    <Modal
+      maskClosable={false}
+      width="52vw"
+      title={'初始数据'}
+      onCancel={() => close()}
+      onOk={() => save()}
+      visible
+    >
+      <div style={{ background: 'rgb(236, 237, 238)' }}>
+        <p style={{ padding: 10 }}>
+          <ExclamationCircleOutlined style={{ marginRight: 5 }} />
+          初始化数据包括MQTT产品、MQTT设备、MQTT类型设备接入网关、MQTT网络组件、Jetlinks 官方协议
+        </p>
+      </div>
+      <div style={{ marginTop: '20px' }}>
+        <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
+          <SchemaField schema={schema} />
+        </Form>
+      </div>
+    </Modal>
+  );
+};
+
+export default Save;

+ 85 - 0
src/pages/init-home/components/menu.tsx

@@ -0,0 +1,85 @@
+import { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
+import BaseMenu from '@/pages/system/Menu/Setting/baseMenu';
+import { service } from '../index';
+
+const Menu = forwardRef((props: { onChange?: (menu: any) => void }, ref) => {
+  const [count, setCount] = useState(0);
+  const menuRef = useRef<any[]>();
+
+  const menuCount = (menus: any[]) => {
+    return menus.reduce((pre, next) => {
+      let _count = 1;
+      if (next.children) {
+        _count = menuCount(next.children);
+      }
+      return pre + _count;
+    }, 0);
+  };
+
+  /**
+   * 根据权限过滤菜单
+   */
+  const filterMenu = (permissions: string[], menus: any[]) => {
+    return menus.filter((item) => {
+      let isPermissions = true;
+      if (item.permissions && item.permissions.length) {
+        isPermissions = item.permissions.some((pItem: any) =>
+          permissions.includes(pItem.permission),
+        );
+      }
+
+      if (item.children) {
+        item.children = filterMenu(permissions, item.children);
+      }
+
+      return isPermissions || !!item.children?.length;
+    });
+  };
+
+  /**
+   * 获取当前系统所有权限
+   */
+  const getSystemPermissions = async () => {
+    const resp = await service.getPermissionAll();
+    if (resp.status === 200) {
+      const newTree = filterMenu(
+        resp.result.map((item: any) => item.id),
+        BaseMenu,
+      );
+      const _count = menuCount(newTree);
+      menuRef.current = newTree;
+      setCount(_count);
+    }
+  };
+
+  useImperativeHandle(ref, () => ({
+    save: () => {
+      return new Promise((resolve) => {
+        if (props.onChange) {
+          props.onChange(menuRef.current);
+        }
+        service.updateMenus(menuRef.current).then((res) => {
+          resolve(res.status === 200);
+        });
+      });
+    },
+  }));
+
+  useEffect(() => {
+    getSystemPermissions();
+  }, []);
+
+  return (
+    <div style={{ display: 'flex', alignItems: 'center' }}>
+      <div style={{ marginRight: 16 }}>
+        <img src={require('/public/images/init-home/menu.png')} />
+      </div>
+      <div>
+        <b>系统将初始化{count}个菜单</b>
+        <div>初始化后的菜单可在“菜单管理”页面进行维护管理</div>
+      </div>
+    </div>
+  );
+});
+
+export default Menu;

+ 122 - 0
src/pages/init-home/components/role.tsx

@@ -0,0 +1,122 @@
+import { Checkbox } from 'antd';
+import { useState, forwardRef, useImperativeHandle } from 'react';
+import classNames from 'classnames';
+import RoleMenuData, { roleKeysType, ROLEKEYS, RoleData } from './data/RoleData';
+import { service } from '@/pages/init-home';
+import '../index.less';
+
+const Role = forwardRef((_, ref) => {
+  const [keys, setKeys] = useState<roleKeysType[]>([]);
+
+  const findMenuByRole = (menu: any[], code: string): any => {
+    let _item = null;
+    menu.some((item) => {
+      if (item.code === code) {
+        _item = item;
+        return true;
+      }
+
+      if (item.children) {
+        const childrenItem = findMenuByRole(item.children, code);
+        if (childrenItem) {
+          _item = childrenItem;
+          return true;
+        }
+        return false;
+      }
+
+      return null;
+    });
+    return _item;
+  };
+
+  useImperativeHandle(
+    ref,
+    () => ({
+      save: async () => {
+        return new Promise((resolve) => {
+          if (!keys.length) {
+            return resolve(true);
+          }
+          let Count = 0;
+          keys.forEach(async (item, index) => {
+            const _itemData = RoleData[item];
+            // 添加该角色
+            const res = await service.addRole(_itemData);
+            if (res.status === 200) {
+              const menuTree = await service.getRoleMenu(res.result.id);
+              if (menuTree.status === 200) {
+                const _roleData = (RoleMenuData[item] as []).filter((roleItem: any) => {
+                  const _menu = findMenuByRole(menuTree.result, roleItem.code);
+                  if (_menu) {
+                    roleItem.id = _menu.id;
+                    roleItem.parentId = _menu.parentId;
+                    roleItem.createTime = _menu.createTime;
+                    return true;
+                  }
+                  return false;
+                });
+                //更新权限
+                const roleRes = await service.updateRoleMenu(res.result.id, { menus: _roleData });
+                if (roleRes.status === 200) {
+                  Count += 1;
+                }
+                if (index === keys.length - 1) {
+                  resolve(Count === keys.length);
+                }
+              } else if (index === keys.length - 1) {
+                resolve(Count === keys.length);
+              }
+            } else if (index === keys.length - 1) {
+              resolve(Count === keys.length);
+            }
+          });
+        });
+      },
+    }),
+    [keys],
+  );
+
+  return (
+    <div className={'init-home-role'}>
+      <Checkbox.Group
+        onChange={(e) => {
+          setKeys(e as roleKeysType[]);
+        }}
+      >
+        <div className={'init-home-role-content'}>
+          <div
+            className={classNames('role-item role-image-1', { active: keys.includes('device') })}
+          >
+            <div className={'role-item-title'}>
+              <Checkbox value={ROLEKEYS.device}></Checkbox>
+              <div>设备接入岗</div>
+            </div>
+            <div className={'role-item-content'}></div>
+            <div className={'role-item-footer'}>该角色负责设备接入模块的维护管理</div>
+          </div>
+          <div className={classNames('role-item role-image-2', { active: keys.includes('link') })}>
+            <div className={'role-item-title'}>
+              <Checkbox value={ROLEKEYS.link}></Checkbox>
+              <div>运维管理岗</div>
+            </div>
+            <div className={'role-item-content'}></div>
+            <div className={'role-item-footer'}>该角色负责系统运维模块的维护管理</div>
+          </div>
+          <div
+            className={classNames('role-item role-image-3', { active: keys.includes('complex') })}
+          >
+            <div className={'role-item-title'}>
+              <Checkbox value={ROLEKEYS.complex}></Checkbox>
+              <div>综合管理岗</div>
+            </div>
+            <div className={'role-item-content'}></div>
+            <div className={'role-item-footer'}>该角色负责系统运维和设备接入模块的维护管理</div>
+          </div>
+        </div>
+      </Checkbox.Group>
+    </div>
+  );
+});
+
+export default Role;

+ 92 - 7
src/pages/init-home/index.less

@@ -22,20 +22,105 @@
 
       .left {
         width: 30px;
-        height: 100%;
+        height: 1000px;
       }
 
       .right {
-        width: calc(100% - 50px);
-        height: 1200px;
+        width: calc(100% - 70px);
+        // height: 1200px;
 
-        // .collapseTitle {
+        .collapseTitle {
+          display: flex;
+          font-size: 14px;
+          opacity: 0.85;
 
-        // }
+          .collapseDesc {
+            margin-top: 2px;
+            margin-left: 8px;
+            color: #666;
+            font-size: 12px;
+            opacity: 0.85;
+          }
+        }
+      }
+    }
+
+    ::-webkit-scrollbar {
+      width: 12px;
+    }
+
+    /* 滚动槽 */
+    ::-webkit-scrollbar-track {
+      background: #f2f2f2;
+      border-radius: 8px;
+    }
+
+    /* 滚动条滑块 */
+    ::-webkit-scrollbar-thumb {
+      background: #cecece;
+      border-radius: 8px;
+    }
+  }
+}
+
+.init-home-role {
+  .init-home-role-content {
+    display: flex;
+    gap: 24px;
+
+    .role-item {
+      position: relative;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      margin-bottom: 30px;
+      padding: 24px;
+      background-repeat: no-repeat;
+      background-position: center;
+      background-size: 370px;
+      border: 1px solid rgba(245, 245, 245, 1);
+
+      .role-item-title {
+        display: flex;
+
+        > div {
+          flex: 1 1 auto;
+          padding-left: 90px;
+          font-weight: bold;
+          font-size: 16px;
+        }
+      }
 
-        // .collapseDesc {
+      .role-item-content {
+        width: 320px;
+        height: 260px;
+        margin-top: 24px;
+      }
+
+      &.role-image-1 {
+        background-image: url('/images/init-home/role1.png');
+      }
+
+      &.role-image-2 {
+        background-image: url('/images/init-home/role2.png');
+      }
+
+      &.role-image-3 {
+        background-image: url('/images/init-home/role3.png');
+      }
+
+      .role-item-footer {
+        position: absolute;
+        bottom: -30px;
+        left: 0;
+        width: 100%;
+        color: #999;
+        font-size: 12px;
+        text-align: center;
+      }
 
-        // }
+      &.active {
+        background-color: #f5f5f5;
       }
     }
   }

+ 137 - 28
src/pages/init-home/index.tsx

@@ -1,41 +1,150 @@
 import { TitleComponent } from '@/components';
-import { Collapse, Steps } from 'antd';
+import { Button, Collapse, Spin } from 'antd';
 import styles from './index.less';
+import Basis from './components/basis';
+import Menu from './components/menu';
+import Role from './components/role';
+import Data from './components/data';
+import Service from './service';
+import { useHistory } from 'umi';
+import { useState, useRef, useEffect } from 'react';
+import BaseMenu from '@/pages/system/Menu/Setting/baseMenu';
+
+export const service = new Service();
 
 const InitHome = () => {
-  const text = `
-        A dog is a type of domesticated animal.
-        Known for its loyalty and faithfulness,
-        it can be found as a welcome guest in many households across the world.
-    `;
+  const [loadings, setLoadings] = useState<boolean>(false);
+  const [, setCurrent] = useState<number>(0);
+  const history = useHistory();
+
+  const cacheRef = useRef<Set<string>>();
+
+  const baseRef = useRef<{ save: any }>();
+  const menuRef = useRef<{ save: any }>();
+  const roleRef = useRef<{ save: any }>();
+  const dataRef = useRef<{ save: any }>();
+
+  const jump = () => {
+    history.push(BaseMenu[0].url);
+  };
+
+  useEffect(() => {
+    service.getInit().then((res) => {
+      if (res.status === 200 && res.result.length) {
+        // jump()
+      }
+    });
+  }, []);
+
   return (
     <div className={styles.init}>
       <TitleComponent data={'系统初始化'} />
       <div className={styles.box}>
         <div className={styles.container}>
-          <div className={styles.left}>
-            <Steps direction="vertical" current={1} percent={60} style={{ height: '100%' }}>
-              <Steps.Step />
-              <Steps.Step />
-              <Steps.Step />
-              <Steps.Step />
-            </Steps>
-          </div>
+          {/*<div className={styles.left}>*/}
+          {/*  <Steps direction="vertical" current={current} percent={60} style={{ height: '100%' }}>*/}
+          {/*    <Steps.Step />*/}
+          {/*    <Steps.Step />*/}
+          {/*    <Steps.Step />*/}
+          {/*    <Steps.Step />*/}
+          {/*  </Steps>*/}
+          {/*</div>*/}
           <div className={styles.right}>
-            <Collapse defaultActiveKey={['1', '2', '3', '4']}>
-              <Collapse.Panel header={<div>基本信息</div>} key="1">
-                <p>{text}</p>
-              </Collapse.Panel>
-              <Collapse.Panel header="This is panel header 2" key="2">
-                <p>{text}</p>
-              </Collapse.Panel>
-              <Collapse.Panel header="This is panel header 3" key="3">
-                <p>{text}</p>
-              </Collapse.Panel>
-              <Collapse.Panel header="This is panel header 3" key="4">
-                <p>{text}</p>
-              </Collapse.Panel>
-            </Collapse>
+            <Spin spinning={loadings}>
+              <Collapse defaultActiveKey={['1', '2', '3', '4']}>
+                <Collapse.Panel
+                  header={
+                    <div className={styles.collapseTitle}>
+                      基本信息
+                      <div className={styles.collapseDesc}>
+                        配置平台名称、登录背景图、主题色等基本信息
+                      </div>
+                    </div>
+                  }
+                  key="1"
+                >
+                  <Basis ref={baseRef} />
+                </Collapse.Panel>
+                <Collapse.Panel
+                  header={
+                    <div className={styles.collapseTitle}>
+                      菜单初始化<div className={styles.collapseDesc}>初始化菜单数据</div>
+                    </div>
+                  }
+                  key="2"
+                >
+                  <Menu ref={menuRef} />
+                </Collapse.Panel>
+                <Collapse.Panel
+                  header={
+                    <div className={styles.collapseTitle}>
+                      角色初始化<div className={styles.collapseDesc}>初始化内置角色与权限数据</div>
+                    </div>
+                  }
+                  key="3"
+                >
+                  <Role ref={roleRef} />
+                </Collapse.Panel>
+                <Collapse.Panel
+                  header={
+                    <div className={styles.collapseTitle}>
+                      初始数据<div className={styles.collapseDesc}>初始化设备接入示例数据</div>
+                    </div>
+                  }
+                  key="4"
+                >
+                  <Data ref={dataRef} />
+                </Collapse.Panel>
+              </Collapse>
+            </Spin>
+            <Button
+              type="primary"
+              style={{ marginTop: 20 }}
+              loading={loadings}
+              onClick={async () => {
+                setLoadings(true);
+                setCurrent(0);
+                if (!cacheRef.current?.has('base')) {
+                  const baseRes = await baseRef.current?.save();
+                  if (!baseRes) {
+                    return setLoadings(false);
+                  }
+                  cacheRef.current?.add('base');
+                }
+
+                if (!cacheRef.current?.has('menu')) {
+                  const menuRes = await menuRef.current?.save();
+                  if (!menuRes) {
+                    return setLoadings(false);
+                  }
+                  cacheRef.current?.add('menu');
+                }
+
+                if (!cacheRef.current?.has('role')) {
+                  const roleRes = await roleRef.current?.save();
+                  if (!roleRes) {
+                    return setLoadings(false);
+                  }
+                  cacheRef.current?.add('role');
+                }
+
+                if (!cacheRef.current?.has('data')) {
+                  const dataRes = await dataRef.current?.save();
+                  if (!dataRes) {
+                    return setLoadings(false);
+                  }
+                  cacheRef.current?.add('data');
+                }
+                //  记录当前
+                service.saveInit().then((res) => {
+                  if (res.status === 200) {
+                    jump();
+                  }
+                });
+              }}
+            >
+              确认
+            </Button>
           </div>
         </div>
       </div>

+ 76 - 0
src/pages/init-home/service.ts

@@ -0,0 +1,76 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<any> {
+  save = (data?: any) =>
+    request(`/${SystemConst.API_BASE}/system/config/scope/_save`, {
+      method: 'POST',
+      data,
+    });
+  detail = (data?: any) =>
+    request(`/${SystemConst.API_BASE}/system/config/scopes`, {
+      method: 'POST',
+      data,
+    });
+  getResourcesCurrent = () =>
+    request(`${SystemConst.API_BASE}/network/resources/alive/_current`, {
+      method: 'GET',
+    });
+  saveNetwork = (data: any) =>
+    request(`${SystemConst.API_BASE}/network/config`, {
+      method: 'POST',
+      data,
+    });
+  saveProtocol = () =>
+    request(`${SystemConst.API_BASE}/protocol/default-protocol/_save`, {
+      method: 'POST',
+    });
+  getProtocol = () =>
+    request(`${SystemConst.API_BASE}/protocol/_query/no-paging?paging=false`, {
+      method: 'GET',
+    });
+  saveAccessConfig = (data: any) =>
+    request(`${SystemConst.API_BASE}/gateway/device`, {
+      method: 'POST',
+      data,
+    });
+  saveProduct = (data: any) =>
+    request(`${SystemConst.API_BASE}/device/product`, {
+      method: 'POST',
+      data,
+    });
+  saveDevice = (data: any) =>
+    request(`${SystemConst.API_BASE}/device/instance`, {
+      method: 'POST',
+      data,
+    });
+  getPermissionAll = () =>
+    request(`${SystemConst.API_BASE}/permission/_query/no-paging?paging=false`);
+
+  // 更新全部菜单
+  updateMenus = (data: any) =>
+    request(`${SystemConst.API_BASE}/menu/_all`, { method: 'PATCH', data });
+
+  // 添加角色
+  addRole = (data: any) => request(`/${SystemConst.API_BASE}/role`, { method: 'POST', data });
+
+  // 更新权限菜单
+  getRoleMenu = (id: string) =>
+    request(`/${SystemConst.API_BASE}/menu/role/${id}/_grant/tree`, { method: 'GET' });
+
+  // 更新权限菜单
+  updateRoleMenu = (id: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/menu/role/${id}/_grant`, { method: 'PUT', data });
+
+  //  记录初始化
+  saveInit = () =>
+    request(`/${SystemConst.API_BASE}/user/settings/init`, {
+      method: 'POST',
+      data: { init: true },
+    });
+  //  获取初始化
+  getInit = () => request(`/${SystemConst.API_BASE}/user/settings/init`, { method: 'GET' });
+}
+
+export default Service;

+ 1 - 0
src/pages/link/Certificate/index.tsx

@@ -123,6 +123,7 @@ const Certificate = () => {
         search={false}
         rowKey="id"
         tableClassName={'link-certificate'}
+        columnEmptyText={''}
         tableStyle={{ minHeight }}
         headerTitle={
           <PermissionButton

+ 1 - 0
src/pages/link/Channel/Modbus/Access/bindDevice/index.tsx

@@ -118,6 +118,7 @@ const BindDevice = (props: Props) => {
         params={param}
         columns={columns}
         rowKey="id"
+        columnEmptyText={''}
         search={false}
         request={async (params) =>
           service.getDevice({

+ 1 - 0
src/pages/link/Channel/Modbus/Access/index.tsx

@@ -333,6 +333,7 @@ const Access = () => {
               params={param}
               columns={columns}
               rowKey="id"
+              columnEmptyText={''}
               search={false}
               headerTitle={
                 <>

+ 1 - 0
src/pages/link/Channel/Opcua/Access/bindDevice/index.tsx

@@ -129,6 +129,7 @@ const BindDevice = (props: Props) => {
         columns={columns}
         rowKey="id"
         search={false}
+        columnEmptyText={''}
         request={async (params) =>
           service.getDevice({
             ...params,

+ 1 - 0
src/pages/link/Channel/Opcua/Access/index.tsx

@@ -340,6 +340,7 @@ const Access = () => {
               params={param}
               columns={columns}
               rowKey="id"
+              columnEmptyText={''}
               search={false}
               headerTitle={
                 <>

+ 1 - 0
src/pages/link/Protocol/save/index.tsx

@@ -160,6 +160,7 @@ const Save = (props: Props) => {
             'x-decorator-props': {
               gridSpan: 2,
             },
+            default: 'jar',
             'x-component-props': {
               model: 'singular',
               itemStyle: {

+ 0 - 1
src/pages/link/Type/Detail/index.tsx

@@ -122,7 +122,6 @@ const Save = observer(() => {
         effects() {
           onFieldValueChange('type', (field, f) => {
             const value = (field as Field).value;
-            // console.log(field)
             if (f.modified) {
               f.deleteValuesIn('configuration');
               f.deleteValuesIn('cluster');

+ 1 - 0
src/pages/media/Cascade/Channel/BindChannel/index.tsx

@@ -126,6 +126,7 @@ const BindChannel = (props: Props) => {
         params={param}
         columns={columns}
         search={false}
+        columnEmptyText={''}
         headerTitle={'通道列表'}
         request={async (params) => {
           return service.queryChannel({ ...params, sorts: [{ name: 'name', order: 'desc' }] });

+ 1 - 0
src/pages/media/Cascade/Channel/index.tsx

@@ -208,6 +208,7 @@ const Channel = () => {
         columns={columns}
         scroll={{ x: 1366 }}
         search={false}
+        columnEmptyText={''}
         tableClassName={'cascadeDevice'}
         tableStyle={{ minHeight }}
         headerTitle={'通道列表'}

+ 1 - 1
src/pages/media/Cascade/index.tsx

@@ -288,7 +288,7 @@ const Cascade = () => {
           isPermission={permission.delete}
           disabled={record.status.value !== 'disabled'}
           tooltip={{
-            title: record.status.value !== 'disabled' ? '请先禁用,再删除' : '',
+            title: record.status.value !== 'disabled' ? '请先禁用,再删除' : '删除',
           }}
           popConfirm={{
             title: '确认删除',

+ 2 - 1
src/pages/media/Device/Channel/index.tsx

@@ -139,7 +139,7 @@ export default () => {
       }),
       valueType: 'option',
       align: 'center',
-      width: 120,
+      width: 200,
       render: (_, record) => [
         <Tooltip
           key="edit"
@@ -251,6 +251,7 @@ export default () => {
             columns={columns}
             actionRef={actionRef}
             // scroll={{x:1366}}
+            columnEmptyText={''}
             tableClassName={'channelDevice'}
             tableStyle={{ minHeight }}
             options={{ fullScreen: true }}

+ 182 - 161
src/pages/media/Device/Playback/index.tsx

@@ -2,7 +2,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import LivePlayer from '@/components/Player';
 import { useCallback, useEffect, useRef, useState } from 'react';
-import { Calendar, Empty, List, Tooltip } from 'antd';
+import { Calendar, Empty, List, Spin, Tooltip } from 'antd';
 import { useLocation } from 'umi';
 import Service from './service';
 import './index.less';
@@ -62,6 +62,7 @@ const IconNode = (props: IconNodeType) => {
   return (
     <a
       onClick={() => {
+        console.log(props, status);
         if (props.type === 'local') {
           if (status === 2) {
             // 已下载,进行跳转
@@ -85,7 +86,7 @@ export default () => {
   const [type, setType] = useState('local');
   const [historyList, setHistoryList] = useState<recordsItemType[]>([]);
   const [time, setTime] = useState<Moment | undefined>(undefined);
-  // const [loading, setLoading] = useState(false)
+  const [loading, setLoading] = useState(false);
   const [cloudTime, setCloudTime] = useState<any>();
   const location = useLocation();
   const player = useRef<any>();
@@ -103,6 +104,7 @@ export default () => {
     setPlayStatus(0);
     setUrl('');
     if (deviceId && channelId && date) {
+      setLoading(true);
       const params = {
         startTime: date.format('YYYY-MM-DD 00:00:00'),
         endTime: date.format('YYYY-MM-DD 23:59:59'),
@@ -113,6 +115,7 @@ export default () => {
           ...params,
           includeFiles: false,
         });
+        setLoading(false);
         let newList: recordsItemType[] = serviceResp.result;
 
         if (serviceResp.status === 200 && serviceResp.result) {
@@ -120,11 +123,13 @@ export default () => {
           newList = localResp.result.map((item: recordsItemType) => {
             return {
               ...item,
-              isServer: serviceResp.result.some(
-                (serverFile: any) =>
-                  item.startTime >= serverFile.streamStartTime &&
-                  serverFile.streamEndTime <= item.endTime,
-              ),
+              isServer: serviceResp.result.length
+                ? serviceResp.result.some(
+                    (serverFile: any) =>
+                      item.startTime >= serverFile.streamStartTime &&
+                      serverFile.streamEndTime <= item.endTime,
+                  )
+                : false,
             };
           });
           setHistoryList(newList);
@@ -132,37 +137,49 @@ export default () => {
           setHistoryList(newList);
         }
       } else {
+        setLoading(false);
         setHistoryList([]);
       }
     }
   };
 
-  const queryServiceRecords = async (date: Moment) => {
-    setPlayStatus(0);
-    setUrl('');
-    if (deviceId && channelId && date) {
-      const params = {
-        startTime: date.format('YYYY-MM-DD 00:00:00'),
-        endTime: date.format('YYYY-MM-DD 23:59:59'),
-        includeFiles: true,
-      };
-
-      const resp = await service.recordsInServerFiles(deviceId, channelId, params);
+  /**
+   * 查询云端视频
+   * @param date
+   */
+  const queryServiceRecords = useCallback(
+    async (date: Moment) => {
+      setPlayStatus(0);
+      setUrl('');
+      if (deviceId && channelId && date) {
+        setLoading(true);
+        const params = {
+          startTime: date.format('YYYY-MM-DD 00:00:00'),
+          endTime: date.format('YYYY-MM-DD 23:59:59'),
+          includeFiles: true,
+        };
 
-      if (resp.status === 200) {
-        setHistoryList(resp.result);
+        const resp = await service.recordsInServerFiles(deviceId, channelId, params);
+        setLoading(false);
+        if (resp.status === 200) {
+          setHistoryList(resp.result);
+        }
       }
-    }
-  };
+    },
+    [deviceId, channelId],
+  );
 
-  const cloudView = useCallback((startTime: number, endTime: number) => {
-    setType('cloud');
-    setCloudTime({
-      startTime,
-      endTime,
-    });
-    queryServiceRecords(time!);
-  }, []);
+  const cloudView = useCallback(
+    (startTime: number, endTime: number) => {
+      setType('cloud');
+      setCloudTime({
+        startTime,
+        endTime,
+      });
+      queryServiceRecords(time!);
+    },
+    [time],
+  );
 
   const downloadClick = async (item: recordsItemType) => {
     const downloadUrl = service.downLoadFile(item.id);
@@ -240,47 +257,48 @@ export default () => {
           />
         </div>
         <div className={'playback-right'}>
-          <Tooltip
-            // title={<>云端:存储在服务器中 本地:存储在设备本地</>}
-            title={
-              <>
-                <div>云端:存储在服务器中</div>
-                <div>本地:存储在设备本地</div>
-              </>
-            }
-            placement="topLeft"
-          >
-            <div>
-              类型: <QuestionCircleOutlined />
-            </div>
-          </Tooltip>
-          <RadioCard
-            model={'singular'}
-            value={type}
-            itemStyle={{ minWidth: 0, width: '100%' }}
-            onSelect={(key: string) => {
-              setType(key);
-              console.log(key);
-              if (key === 'cloud') {
-                queryServiceRecords(time!);
-              } else {
-                queryLocalRecords(time!);
+          <Spin spinning={loading}>
+            <Tooltip
+              // title={<>云端:存储在服务器中 本地:存储在设备本地</>}
+              title={
+                <>
+                  <div>云端:存储在服务器中</div>
+                  <div>本地:存储在设备本地</div>
+                </>
               }
-            }}
-            options={[
-              {
-                label: '云端',
-                value: 'cloud',
-                imgUrl: require('/public/images/media/cloud.png'),
-              },
-              {
-                label: '本地',
-                value: 'local',
-                imgUrl: require('/public/images/local.png'),
-              },
-            ]}
-          />
-          {/* <Select
+              placement="topLeft"
+            >
+              <div>
+                类型: <QuestionCircleOutlined />
+              </div>
+            </Tooltip>
+            <RadioCard
+              model={'singular'}
+              value={type}
+              itemStyle={{ minWidth: 0, width: '100%' }}
+              onSelect={(key: string) => {
+                setType(key);
+                console.log(key);
+                if (key === 'cloud') {
+                  queryServiceRecords(time!);
+                } else {
+                  queryLocalRecords(time!);
+                }
+              }}
+              options={[
+                {
+                  label: '云端',
+                  value: 'cloud',
+                  imgUrl: require('/public/images/media/cloud.png'),
+                },
+                {
+                  label: '本地',
+                  value: 'local',
+                  imgUrl: require('/public/images/local.png'),
+                },
+              ]}
+            />
+            {/* <Select
             value={type}
             options={[
               { label: '云端', value: 'cloud' },
@@ -296,101 +314,104 @@ export default () => {
               }
             }}
           /> */}
-          <div className={'playback-calendar'}>
-            <Calendar
-              value={time}
-              onChange={(date) => {
-                setTime(date);
-                if (type === 'cloud') {
-                  queryServiceRecords(date);
-                } else {
-                  queryLocalRecords(date);
-                }
-              }}
-              disabledDate={(currentDate) => currentDate > moment(new Date())}
-              fullscreen={false}
-            />
-          </div>
-          <div className={classNames('playback-list', { 'no-list': !historyList.length })}>
-            {historyList && historyList.length ? (
-              <List
-                className={'playback-list-items'}
-                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');
-                  let title = '下载到云端';
-
-                  if (type === 'local') {
-                    if (item.isServer) {
-                      title = '查看';
-                    }
+            <div className={'playback-calendar'}>
+              <Calendar
+                value={time}
+                onChange={(date) => {
+                  setTime(date);
+                  if (type === 'cloud') {
+                    queryServiceRecords(date);
                   } else {
-                    title = '下载录像文件';
+                    queryLocalRecords(date);
                   }
-                  return (
-                    <List.Item
-                      actions={[
-                        <Tooltip
-                          key="play-btn"
-                          title={
-                            _startTime === playNowTime.current && playStatus === 1 ? '暂停' : '播放'
-                          }
-                        >
-                          <a
-                            onClick={() => {
-                              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 if (playStatus == 2 && _startTime === playNowTime.current) {
-                                if (player.current.getVueInstance) {
-                                  player.current.getVueInstance().play();
-                                }
-                              }
-                            }}
+                }}
+                disabledDate={(currentDate) => currentDate > moment(new Date())}
+                fullscreen={false}
+              />
+            </div>
+            <div className={classNames('playback-list', { 'no-list': !historyList.length })}>
+              {historyList && historyList.length ? (
+                <List
+                  className={'playback-list-items'}
+                  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');
+                    let title = '下载到云端';
+
+                    if (type === 'local') {
+                      if (item.isServer) {
+                        title = '查看';
+                      }
+                    } else {
+                      title = '下载录像文件';
+                    }
+                    return (
+                      <List.Item
+                        actions={[
+                          <Tooltip
+                            key="play-btn"
+                            title={
+                              _startTime === playNowTime.current && playStatus === 1
+                                ? '暂停'
+                                : '播放'
+                            }
                           >
-                            {_startTime === playNowTime.current && playStatus === 1 ? (
-                              <PauseCircleOutlined />
-                            ) : (
-                              <PlayCircleOutlined />
-                            )}
-                          </a>
-                        </Tooltip>,
+                            <a
+                              onClick={() => {
+                                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 if (playStatus == 2 && _startTime === playNowTime.current) {
+                                  if (player.current.getVueInstance) {
+                                    player.current.getVueInstance().play();
+                                  }
+                                }
+                              }}
+                            >
+                              {_startTime === playNowTime.current && playStatus === 1 ? (
+                                <PauseCircleOutlined />
+                              ) : (
+                                <PlayCircleOutlined />
+                              )}
+                            </a>
+                          </Tooltip>,
 
-                        <Tooltip key={'download'} title={title}>
-                          <IconNode
-                            type={type}
-                            item={item}
-                            onCloudView={cloudView}
-                            onDownLoad={() => {
-                              downloadClick(item);
-                            }}
-                          />
-                        </Tooltip>,
-                      ]}
-                    >
-                      <div style={{ textAlign: 'center', paddingLeft: 10 }}>
-                        {`${startTime}`} ~ {`${endTime}`}
-                      </div>
-                    </List.Item>
-                  );
-                }}
-              >
-                <div></div>
-              </List>
-            ) : (
-              <Empty />
-            )}
-          </div>
+                          <Tooltip key={'download'} title={title}>
+                            <IconNode
+                              type={type}
+                              item={item}
+                              onCloudView={cloudView}
+                              onDownLoad={() => {
+                                downloadClick(item);
+                              }}
+                            />
+                          </Tooltip>,
+                        ]}
+                      >
+                        <div style={{ textAlign: 'center', paddingLeft: 10 }}>
+                          {`${startTime}`} ~ {`${endTime}`}
+                        </div>
+                      </List.Item>
+                    );
+                  }}
+                >
+                  <div></div>
+                </List>
+              ) : (
+                <Empty />
+              )}
+            </div>
+          </Spin>
         </div>
       </div>
     </PageContainer>

+ 1 - 0
src/pages/media/Device/Playback/timeLine.tsx

@@ -115,6 +115,7 @@ const Progress = forwardRef((props: Props, ref) => {
         onChange(data[0].startTime, data[0].endTime, data[0].deviceId, data[0].channelId);
       } else if (type === 'cloud') {
         // 是否从本地跳转到云端播放
+
         if (localToServer && Object.keys(localToServer).length > 0) {
           // 获取跳转播放段
           const playItem = data.find((item) => {

+ 1 - 0
src/pages/media/Home/deviceModal.tsx

@@ -127,6 +127,7 @@ export default (props: DeviceModalProps) => {
         columns={columns}
         rowKey={'id'}
         search={false}
+        columnEmptyText={''}
         request={(params) =>
           service.query({
             ...params,

+ 1 - 1
src/pages/media/Home/index.tsx

@@ -14,7 +14,7 @@ import Steps from '@/pages/home/components/Steps';
 
 const permissionTip = '暂无权限,请联系管理员';
 
-export const service = new Service('media/device');
+export const service = new Service('media');
 
 export default () => {
   const dashBoardUrl = getMenuPathByCode('media/DashBoard');

+ 4 - 3
src/pages/media/Home/service.tsx

@@ -3,10 +3,11 @@ import { request } from 'umi';
 import type { DeviceItem } from '@/pages/media/Device/typings';
 
 class Service extends BaseService<DeviceItem> {
-  deviceCount = (data?: any) => request(`${this.uri}/_count`, { methods: 'GET', params: data });
+  deviceCount = (data?: any) =>
+    request(`${this.uri}/device/_count`, { methods: 'GET', params: data });
 
-  channelCount = (data?: any) =>
-    request(`${this.uri}/channel/_count`, { method: 'GET', params: data });
+  channelCount = (data: any = {}) =>
+    request(`${this.uri}/channel/_count`, { method: 'POST', data: data });
 }
 
 export default Service;

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

@@ -1,5 +1,5 @@
 import { Modal } from 'antd';
-import { useMemo } from 'react';
+import { useMemo, useRef } from 'react';
 import { createForm, Field, onFieldReact, onFieldValueChange } from '@formily/core';
 import { createSchemaField, observer } from '@formily/react';
 import {
@@ -14,6 +14,7 @@ import {
 } from '@formily/antd';
 import { ISchema } from '@formily/json-schema';
 import { service, state } from '@/pages/notice/Config';
+import { service as TemplateService } from '@/pages/notice/Template';
 import { useLocation } from 'umi';
 import { onlyMessage, useAsyncDataSource } from '@/utils/util';
 import { Store } from 'jetlinks-store';
@@ -22,6 +23,7 @@ import FUpload from '@/components/Upload';
 const Debug = observer(() => {
   const location = useLocation<{ id: string }>();
   const id = (location as any).query?.id;
+  const variableRef = useRef<any>([]);
 
   const form = useMemo(
     () =>
@@ -31,17 +33,30 @@ const Debug = observer(() => {
           onFieldValueChange('templateId', (field, form1) => {
             const value = field.value;
             // 找到模版详情;
-            const list = Store.get('notice-template-list');
+            // const list = Store.get('notice-template-list');
 
-            const _template = list.find((item: any) => item.id === value);
-            form1.setFieldState('variableDefinitions', (_state) => {
-              _state.visible = _template?.variableDefinitions?.length > 0;
-              _state.value = _template.variableDefinitions;
+            TemplateService.getVariableDefinitions(value).then((resp) => {
+              const _template = resp.result;
+              if (_template?.variableDefinitions?.length > 0) {
+                variableRef.current = _template?.variableDefinitions;
+                form1.setFieldState('variableDefinitions', (state1) => {
+                  state1.visible = true;
+                  state1.value = _template?.variableDefinitions;
+                });
+              }
             });
+
+            // const _template = list.find((item: any) => item.id === value);
+            // console.log(_template)
+            // form1.setFieldState('variableDefinitions', (_state) => {
+            //   _state.visible = _template?.variableDefinitions?.length > 0;
+            //   _state.value = _template.variableDefinitions;
+            // });
           });
-          onFieldReact('variableDefinitions.*.type', (field) => {
+          onFieldReact('variableDefinitions.*.type', async (field) => {
             const value = (field as Field).value;
             const format = field.query('.value').take() as any;
+            const _id = field.query('.id').take() as Field;
             switch (value) {
               case 'date':
                 format.setComponent(DatePicker);
@@ -61,6 +76,33 @@ const Debug = observer(() => {
                 format.setComponent(Input);
                 break;
             }
+            console.log(variableRef.current);
+            if (variableRef.current) {
+              const a = variableRef.current?.find((i: any) => i.id === _id.value);
+              const businessType = a?.expands?.businessType;
+              if (id === 'dingTalk' || id === 'weixin') {
+                switch (businessType) {
+                  case 'org':
+                    // 获取org
+                    const orgList = await TemplateService[id].getDepartments(state.current?.id);
+                    format.setComponent(Select);
+                    format.setDataSource(orgList);
+                    break;
+                  case 'user':
+                    // 获取user
+                    const userList = await TemplateService[id].getUser(state.current?.id);
+                    format.setComponent(Select);
+                    format.setDataSource(userList);
+                    break;
+                  case 'tag':
+                    // 获取user
+                    const tagList = await TemplateService[id].getTags(state.current?.id);
+                    format.setComponent(Select);
+                    format.setDataSource(tagList);
+                    break;
+                }
+              }
+            }
           });
         },
       }),
@@ -148,6 +190,13 @@ const Debug = observer(() => {
               'x-component-props': { title: '值', width: '120px' },
               properties: {
                 value: {
+                  required: true,
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                },
+                type: {
+                  'x-hidden': true,
                   type: 'string',
                   'x-decorator': 'FormItem',
                   'x-component': 'Input',
@@ -165,12 +214,13 @@ const Debug = observer(() => {
     // 应该取选择的配置信息
     if (!state.current) return;
     const templateId = data.templateId;
-    const list = Store.get('notice-template-list');
-    const _template = list.find((item: any) => item.id === templateId);
+    // const list = Store.get('notice-template-list');
+    // const _template = list.find((item: any) => item.id === templateId);
 
-    const resp = await service.debug(state?.current.id, templateId, {
-      template: _template,
-      context: data.variableDefinitions?.reduce(
+    const resp = await service.debug(
+      state?.current.id,
+      templateId,
+      data.variableDefinitions?.reduce(
         (previousValue: any, currentValue: { id: any; value: any }) => {
           return {
             ...previousValue,
@@ -179,9 +229,10 @@ const Debug = observer(() => {
         },
         {},
       ),
-    });
+    );
     if (resp.status === 200) {
       onlyMessage('操作成功!');
+      state.debug = false;
     }
   };
   return (

+ 1 - 0
src/pages/notice/Config/Log/index.tsx

@@ -113,6 +113,7 @@ const Log = observer(() => {
         pagination={{
           pageSize: 5,
         }}
+        columnEmptyText={''}
         columns={columns}
         request={async (params) =>
           service.getHistoryLog(state.current?.id || '', {

+ 1 - 0
src/pages/notice/Config/SyncUser/index.tsx

@@ -196,6 +196,7 @@ const SyncUser = observer(() => {
                 actionRef={actionRef}
                 search={false}
                 columns={columns}
+                columnEmptyText={''}
                 params={{ dept: dept }}
                 request={(params) =>
                   service

+ 7 - 1
src/pages/notice/Template/Debug/index.tsx

@@ -89,8 +89,14 @@ const Debug = observer(() => {
                       format.setComponent(Select);
                       format.setDataSource(userList);
                       break;
+                    case 'tag':
+                      // 获取user
+                      const tagList = await service[id].getTags(_configId);
+                      format.setComponent(Select);
+                      format.setDataSource(tagList);
+                      break;
                   }
-                } else if (businessType === 'org' || businessType === 'user') {
+                } else if (['tag', 'org', 'user'].includes(businessType)) {
                   format.setComponent(Select);
                   format.setDataSource([]);
                 }

+ 3 - 0
src/pages/notice/Template/Detail/doc/DingTalkRebot.tsx

@@ -14,6 +14,9 @@ const DingTalkRebot = () => {
       <div>
         通知模板结合通知配置为告警消息通知提供支撑。通知模板只能调用同一类型的通知配置服务。
       </div>
+      <div>
+        使用钉钉群机器人消息通知时需在钉钉开发平台中创建好对应的机器人,再到钉钉客户端在对应的群众绑定智能机器人。
+      </div>
       <h1>2.模板配置说明</h1>
       <div>
         <h2> 1、绑定配置</h2>

+ 1 - 0
src/pages/notice/Template/Log/index.tsx

@@ -116,6 +116,7 @@ const Log = observer(() => {
             // pageSize: 5,
           }
         }
+        columnEmptyText={''}
         params={param}
         columns={columns}
         request={async (params) =>

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

@@ -230,6 +230,7 @@ const Template = observer(() => {
         params={param}
         columns={columns}
         columnEmptyText={''}
+        gridColumns={[2, 2, 3]}
         headerTitle={
           <Space>
             <PermissionButton

+ 34 - 16
src/pages/rule-engine/Alarm/Config/Save/input.tsx

@@ -1,16 +1,15 @@
 import { Modal } from 'antd';
-import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import { createSchemaField } from '@formily/react';
 import { Form, FormGrid, FormItem, Input, Switch } from '@formily/antd';
 import { createForm, onFormInit } from '@formily/core';
 import type { ISchema } from '@formily/json-schema';
 import { service } from '@/pages/rule-engine/Alarm/Config';
 import { onlyMessage } from '@/utils/util';
-import type { IOConfigItem } from '../typing';
 
 interface Props {
-  data?: FirmwareItem;
+  data?: any;
   close: () => void;
+  save: () => void;
 }
 
 const InputSave = (props: Props) => {
@@ -18,14 +17,16 @@ const InputSave = (props: Props) => {
 
   const form = createForm({
     validateFirst: true,
-    initialValues: data,
+    // initialValues: data,
     effects() {
-      onFormInit(async (f) => {
-        const resp = await service.getDataExchange('consume');
-        if (resp.status === 200) {
-          f.setInitialValues(resp.result?.config.config);
-          f.setValuesIn('id', resp.result?.id);
-          f.setValuesIn('state', resp.result?.state?.value === 'enabled' ? true : false);
+      onFormInit((f) => {
+        if (data) {
+          f.setInitialValues({
+            ...data?.data?.config?.config,
+            address: data?.data?.config?.config?.address,
+            topic: data?.data?.config?.config?.topic,
+            state: data?.data?.state?.value === 'enabled' ? true : false,
+          });
         }
       });
     },
@@ -42,19 +43,24 @@ const InputSave = (props: Props) => {
 
   const save = async () => {
     form.validate();
-    const inputConfig: IOConfigItem = await form.submit();
+    const inputConfig: any = await form.submit();
     const res = await service.saveOutputData({
       config: {
         sourceType: 'kafka',
-        config: inputConfig,
+        config: {
+          ...inputConfig,
+          state: inputConfig?.state ? 'enabled' : 'disable',
+        },
       },
-      id: inputConfig.id,
+      state: inputConfig?.state ? 'enabled' : 'disable',
+      id: data?.data?.id,
       sourceType: 'kafka',
       exchangeType: 'consume',
     });
 
     if (res.status === 200) {
       onlyMessage('操作成功');
+      props.save();
     }
   };
 
@@ -68,27 +74,39 @@ const InputSave = (props: Props) => {
       address: {
         title: 'kafka地址',
         type: 'string',
-        required: true,
+        // required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Input',
         'x-component-props': {
           placeholder: '请输入kafka地址',
         },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+        ],
       },
       topic: {
         title: 'topic',
         type: 'string',
-        required: true,
+        // required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Input',
         'x-component-props': {
           placeholder: '请输入topic',
         },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+        ],
       },
       state: {
         title: '状态',
         type: 'string',
-        required: true,
+        // required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Switch',
         default: false,

+ 35 - 20
src/pages/rule-engine/Alarm/Config/Save/output.tsx

@@ -1,16 +1,15 @@
 import { Modal } from 'antd';
-import type { FirmwareItem } from '@/pages/device/Firmware/typings';
 import { createSchemaField } from '@formily/react';
 import { Form, FormGrid, FormItem, Input, Switch } from '@formily/antd';
 import { createForm, onFormInit } from '@formily/core';
 import type { ISchema } from '@formily/json-schema';
 import { service } from '@/pages/rule-engine/Alarm/Config';
 import { onlyMessage } from '@/utils/util';
-import type { IOConfigItem } from '../typing';
 
 interface Props {
-  data?: FirmwareItem;
+  data?: any;
   close: () => void;
+  save: () => void;
 }
 
 const OutputSave = (props: Props) => {
@@ -18,14 +17,16 @@ const OutputSave = (props: Props) => {
 
   const form = createForm({
     validateFirst: true,
-    initialValues: data,
+    // initialValues: data,
     effects() {
       onFormInit(async (f) => {
-        const resp = await service.getDataExchange('producer');
-        if (resp.status === 200) {
-          f.setInitialValues(resp.result?.config.config);
-          f.setValuesIn('id', resp.result?.id);
-          f.setValuesIn('state', resp.result?.state?.value === 'enabled' ? true : false);
+        if (data) {
+          f.setInitialValues({
+            // ...data?.data?.config?.config,
+            address: data?.data?.config?.config?.address,
+            topic: data?.data?.config?.config?.topic,
+            state: data?.data?.state?.value === 'enabled' ? true : false,
+          });
         }
       });
     },
@@ -42,53 +43,67 @@ const OutputSave = (props: Props) => {
 
   const save = async () => {
     form.validate();
-    const inputConfig: IOConfigItem = await form.submit();
+    const inputConfig: any = await form.submit();
+    console.log(inputConfig);
     const res = await service.saveOutputData({
       config: {
         sourceType: 'kafka',
-        config: inputConfig,
+        config: {
+          ...inputConfig,
+          state: inputConfig?.state ? 'enabled' : 'disable',
+        },
       },
-      id: inputConfig.id,
+      state: inputConfig?.state ? 'enabled' : 'disable',
+      id: data?.data?.id,
       sourceType: 'kafka',
-      exchangeType: 'consume',
+      exchangeType: 'producer',
     });
 
     if (res.status === 200) {
       onlyMessage('操作成功');
+      props.save();
     }
   };
 
   const outputSchema: ISchema = {
     type: 'object',
     properties: {
-      id: {
-        'x-component': 'Input',
-        'x-hidden': true,
-      },
       address: {
         title: 'kafka地址',
         type: 'string',
-        required: true,
+        // required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Input',
         'x-component-props': {
           placeholder: '请输入kafka地址',
         },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+        ],
       },
       topic: {
         title: 'topic',
         type: 'string',
-        required: true,
+        // required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Input',
         'x-component-props': {
           placeholder: '请输入topic',
         },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+        ],
       },
       state: {
         title: '状态',
         type: 'string',
-        required: true,
+        // required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Switch',
         default: false,

+ 38 - 10
src/pages/rule-engine/Alarm/Config/index.tsx

@@ -248,17 +248,25 @@ const Config = () => {
     }
   };
 
-  useEffect(() => {
+  const handleInputSearch = () => {
     service.getDataExchange('consume').then((resp) => {
       if (resp.status === 200) {
         setInput(resp.result);
       }
     });
+  };
+
+  const handleOutputSearch = () => {
     service.getDataExchange('producer').then((resp) => {
       if (resp.status === 200) {
         setOutput(resp.result);
       }
     });
+  };
+
+  useEffect(() => {
+    handleOutputSearch();
+    handleInputSearch();
   }, []);
 
   const outputText = `
@@ -343,14 +351,19 @@ const Config = () => {
             />
             <Descriptions bordered column={2}>
               <Descriptions.Item label="kafka地址">
-                {output?.config?.config?.kafka || ''}
+                {output?.data?.config?.config?.address && (
+                  <Badge status={output?.running ? 'success' : 'error'} />
+                )}
+                {output?.data?.config?.config?.address || ''}
               </Descriptions.Item>
               <Descriptions.Item label="topic">
-                {output?.config?.config?.topic || ''}
+                {output?.data?.config?.config?.topic || ''}
               </Descriptions.Item>
               <Descriptions.Item label="状态" span={2}>
-                <Badge status={output?.state?.value === 'enabled' ? 'success' : 'error'} />
-                {output?.state?.text || ''}
+                {output?.data?.state && (
+                  <Badge status={output?.data?.state?.value === 'enabled' ? 'success' : 'error'} />
+                )}
+                {output?.data?.state?.text || ''}
               </Descriptions.Item>
             </Descriptions>
             <Divider />
@@ -374,21 +387,26 @@ const Config = () => {
             />
             <Descriptions bordered column={2}>
               <Descriptions.Item label="kafka地址">
-                {input?.config?.config?.kafka || ''}
+                {input?.data?.config?.config?.address && (
+                  <Badge status={input?.running ? 'success' : 'error'} />
+                )}
+                {input?.data?.config?.config?.address || ''}
               </Descriptions.Item>
               <Descriptions.Item label="topic">
-                {input?.config?.config?.topic || ''}
+                {input?.data?.config?.config?.topic || ''}
               </Descriptions.Item>
               <Descriptions.Item label="状态" span={2}>
-                <Badge status={input?.state?.value === 'enabled' ? 'success' : 'error'} />
-                {input?.state?.text || ''}
+                {input?.data?.state && (
+                  <Badge status={input?.data?.state?.value === 'enabled' ? 'success' : 'error'} />
+                )}
+                {input?.data?.state?.text || ''}
               </Descriptions.Item>
             </Descriptions>
           </Card>
         </div>
       </Col>
       <Col span={10}>
-        <div style={{ height: 560, marginLeft: 20, paddingBottom: 24 }}>
+        <div style={{ height: 650, marginLeft: 20, paddingBottom: 24 }}>
           <div className={styles.doc}>
             <h1>功能图示</h1>
             <div className={styles.image}>
@@ -431,16 +449,26 @@ const Config = () => {
       {list.find((k) => k.key === tab)?.component}
       {inputVisible && (
         <InputSave
+          data={input}
           close={() => {
             setInputVisible(false);
           }}
+          save={() => {
+            setInputVisible(false);
+            handleInputSearch();
+          }}
         />
       )}
       {outputVisible && (
         <OutputSave
+          data={output}
           close={() => {
             setOutputVisible(false);
           }}
+          save={() => {
+            setOutputVisible(false);
+            handleOutputSearch();
+          }}
         />
       )}
     </PageContainer>

+ 37 - 2
src/pages/rule-engine/Alarm/Configuration/index.tsx

@@ -21,6 +21,7 @@ import { Store } from 'jetlinks-store';
 import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
 import { useHistory } from 'umi';
 import { onlyMessage } from '@/utils/util';
+import encodeQuery from '@/utils/encodeQuery';
 
 const service = new Service('alarm/config');
 
@@ -51,6 +52,25 @@ const Configuration = () => {
         };
         return map[text];
       },
+      valueType: 'select',
+      valueEnum: {
+        product: {
+          text: '产品',
+          status: 'product',
+        },
+        device: {
+          text: '设备',
+          status: 'device',
+        },
+        org: {
+          text: '部门',
+          status: 'org',
+        },
+        other: {
+          text: '其他',
+          status: 'other',
+        },
+      },
     },
     {
       title: '告警级别',
@@ -69,6 +89,17 @@ const Configuration = () => {
           </div>
         </Tooltip>
       ),
+      valueType: 'select',
+      request: async () => {
+        const res = await service.queryDefaultLevel();
+        if (res.status === 200) {
+          return (res?.result?.levels || []).map((item: any) => ({
+            label: item.title,
+            value: item.level,
+          }));
+        }
+        return [];
+      },
     },
     {
       title: '关联场景联动',
@@ -103,7 +134,11 @@ const Configuration = () => {
       ),
       valueType: 'select',
       request: async () => {
-        const res = await service.getScene({});
+        const res: any = await service.getScene(
+          encodeQuery({
+            sorts: { createTime: 'desc' },
+          }),
+        );
         if (res.status === 200) {
           return res.result.map((item: any) => ({ label: item.name, value: item.name }));
         }
@@ -153,7 +188,7 @@ const Configuration = () => {
             style={{ padding: 0 }}
             isPermission={permission.tigger}
             tooltip={{
-              title: record.state?.value === 'disabled' ? '未启用,不能手动触发' : '',
+              title: record.state?.value === 'disabled' ? '未启用,不能手动触发' : '手动触发',
             }}
             disabled={record.state?.value === 'disabled'}
             popConfirm={{

+ 1 - 0
src/pages/rule-engine/Alarm/Log/Detail/index.tsx

@@ -130,6 +130,7 @@ const Detail = observer(() => {
         params={param}
         columns={AlarmLogModel.columns}
         search={false}
+        columnEmptyText={''}
         tableClassName={'alarm-log'}
         tableStyle={{ minHeight }}
         scroll={{ x: 1366 }}

+ 1 - 0
src/pages/rule-engine/Alarm/Log/SolveLog/index.tsx

@@ -107,6 +107,7 @@ const SolveLog = (props: Props) => {
         params={param}
         columns={columns}
         search={false}
+        columnEmptyText={''}
         headerTitle={'记录列表'}
         request={async (params) => {
           return service.queryHandleHistory({

+ 6 - 7
src/pages/rule-engine/Alarm/Log/TabComponent/index.tsx

@@ -15,6 +15,7 @@ import { useHistory } from 'umi';
 import classNames from 'classnames';
 import { useDomFullHeight } from '@/hooks';
 import PermissionButton from '@/components/PermissionButton';
+import { Ellipsis } from '@/components';
 
 interface Props {
   type: string;
@@ -202,13 +203,11 @@ const TabComponent = observer((props: Props) => {
                     <div className={classNames('iot-card')}>
                       <div className={'card-warp'}>
                         <div className={classNames('card-content')}>
-                          <div
-                            style={{ fontSize: 20, fontWeight: 700, marginBottom: 20 }}
-                            className="ellipsis"
-                          >
-                            <Tooltip title={item.alarmName}>
-                              <a style={{ cursor: 'default' }}>{item.alarmName}</a>
-                            </Tooltip>
+                          <div style={{ width: 'calc(100% - 90px)' }}>
+                            <Ellipsis title={item.alarmName} titleStyle={{ color: '#2F54EB' }} />
+                            {/*<Tooltip title={item.alarmName}>*/}
+                            {/*  <a style={{ cursor: 'default' }}>{item.alarmName}</a>*/}
+                            {/*</Tooltip>*/}
                           </div>
                           <div className="alarm-log-context">
                             <div className="context-left">

+ 1 - 1
src/pages/rule-engine/Instance/index.tsx

@@ -86,7 +86,7 @@ const Instance = () => {
       isPermission={permission.delete}
       disabled={record.state.value !== 'disable'}
       tooltip={{
-        title: record.state.value !== 'disable' ? '请先禁用,再删除' : '',
+        title: record.state.value !== 'disable' ? '请先禁用,再删除' : '删除',
       }}
       popConfirm={{
         title: '确认删除',

+ 23 - 2
src/pages/rule-engine/Scene/Save/action/VariableItems/builtIn.tsx

@@ -23,6 +23,7 @@ interface BuiltInProps {
   parallel?: boolean;
   form: FormInstance;
   name: number;
+  isEdit?: boolean;
 }
 
 export default (props: BuiltInProps) => {
@@ -32,6 +33,8 @@ export default (props: BuiltInProps) => {
 
   const [builtInList, setBuiltInList] = useState<any[]>([]);
 
+  const [isEdit, setIsEdit] = useState(false);
+
   const onChange = (_source: string = 'fixed', _value?: any, _upperKey?: string) => {
     const obj: ChangeType = {
       source: _source,
@@ -61,13 +64,31 @@ export default (props: BuiltInProps) => {
   };
 
   useEffect(() => {
-    if (source === 'upper') {
+    if (props.isEdit) {
+      setIsEdit(false);
+      const data = props.form.getFieldsValue();
+      const params = props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
+      queryBuiltInParams(data, params).then((res: any) => {
+        if (res.status === 200) {
+          const actionParams = res.result.filter((item: any) => item.id === `action_${props.name}`);
+          const _data = BuiltInParamsHandleTreeData(props.name === 0 ? res.result : actionParams);
+          setBuiltInList(_data);
+        }
+      });
+    }
+    setTimeout(() => {
+      setIsEdit(true);
+    }, 300);
+  }, [props.isEdit]);
+
+  useEffect(() => {
+    if (source === 'upper' && isEdit) {
       sourceChangeEvent();
     }
   }, [source, props.trigger, props.parallel, props.type]);
 
   useEffect(() => {
-    if (props.trigger?.trigger?.device?.productId && source === 'upper') {
+    if (props.trigger?.trigger?.device?.productId && source === 'upper' && isEdit) {
       sourceChangeEvent();
     }
   }, [props.trigger?.trigger?.device?.productId, source]);

+ 29 - 11
src/pages/rule-engine/Scene/Save/action/action.tsx

@@ -30,6 +30,7 @@ interface ActionProps {
   actionItemData?: any;
   trigger?: any;
   parallel?: boolean;
+  isEdit?: boolean;
 }
 
 export default observer((props: ActionProps) => {
@@ -127,7 +128,7 @@ export default observer((props: ActionProps) => {
         setType1(data.executor);
       }
 
-      if (data.terms) {
+      if (data.terms && data.terms.length) {
         // 显示过滤条件
         setIsFiltering(true);
       }
@@ -155,15 +156,15 @@ export default observer((props: ActionProps) => {
     }
   }, [props.parallel]);
 
-  useEffect(() => {
-    if (productId) {
-      const actions = props.form.getFieldValue('actions');
-      if (actions[name].device?.message?.properties) {
-        actions[name].device.message.properties = undefined;
-        props.form.setFieldsValue({ actions });
-      }
-    }
-  }, [productId]);
+  // useEffect(() => {
+  //   if (productId) {
+  //     const actions = props.form.getFieldValue('actions');
+  //     if (actions[name].device?.message?.properties) {
+  //       actions[name].device.message.properties = undefined;
+  //       props.form.setFieldsValue({ actions });
+  //     }
+  //   }
+  // }, [productId]);
 
   const MessageNodes = (
     <>
@@ -237,6 +238,13 @@ export default observer((props: ActionProps) => {
           checked={isFiltering}
           onChange={(e) => {
             setIsFiltering(e.target.checked);
+            if (!e.target.checked) {
+              const actions = props.form?.getFieldValue('actions');
+              delete actions[name].terms;
+              props.form?.setFieldsValue({
+                actions,
+              });
+            }
           }}
         >
           条件过滤
@@ -292,7 +300,15 @@ export default observer((props: ActionProps) => {
             onFunctionChange={setFunctionList}
             restField={props.restField}
             parallel={props.parallel}
-            onProductIdChange={setProductId}
+            onProductIdChange={(_id) => {
+              setProductId(_id);
+              const actions = props.form.getFieldValue('actions');
+              if (actions[name].device?.message?.properties) {
+                actions[name].device.message.properties = undefined;
+                props.form.setFieldsValue({ actions });
+              }
+            }}
+            isEdit={props.isEdit}
           />
         )}
         {type1 === 'delay' && (
@@ -314,6 +330,7 @@ export default observer((props: ActionProps) => {
           triggerType={props.triggerType}
           configId={configId}
           parallel={props.parallel}
+          isEdit={props.isEdit}
         />
       ) : null}
       {type1 === 'device' &&
@@ -347,6 +364,7 @@ export default observer((props: ActionProps) => {
                   form={props.form}
                   parallel={props.parallel}
                   productId={productId}
+                  isEdit={props.isEdit}
                 />
               </Form.Item>
             </Col>

+ 5 - 1
src/pages/rule-engine/Scene/Save/action/device/ConditionalFiltering.tsx

@@ -260,7 +260,11 @@ export default (props: ConditionalFilteringProps) => {
           </ItemGroup>
         </Form.Item>
       </Col>
-      <Col>执行后续动作</Col>
+      <Col>
+        <div style={{ height: '100%', display: 'flex', alignItems: 'center', paddingBottom: 24 }}>
+          执行后续动作
+        </div>
+      </Col>
     </>
   );
 };

+ 43 - 9
src/pages/rule-engine/Scene/Save/action/device/WriteProperty/index.tsx

@@ -17,6 +17,7 @@ interface WritePropertyProps {
   name: number;
   trigger?: any;
   productId: string;
+  isEdit?: boolean;
 }
 
 export default (props: WritePropertyProps) => {
@@ -25,6 +26,7 @@ export default (props: WritePropertyProps) => {
   const [propertiesKey, setPropertiesKey] = useState<string | undefined>(undefined);
   const [propertiesValue, setPropertiesValue] = useState(undefined);
   const [propertiesType, setPropertiesType] = useState('');
+  const [isEdit, setIsEdit] = useState(false);
   const paramsListRef = useRef<any[]>();
 
   const handleName = (data: any) => {
@@ -105,25 +107,57 @@ export default (props: WritePropertyProps) => {
   }, [props.properties, propertiesKey, source]);
 
   useEffect(() => {
-    if (source === 'upper') {
+    if (source === 'upper' && isEdit) {
       sourceChangeEvent();
     }
   }, [source, props.type, props.parallel]);
 
   useEffect(() => {
-    if (props.trigger?.trigger?.device?.productId && source === 'upper') {
-      sourceChangeEvent();
+    if (props.isEdit) {
+      setIsEdit(false);
+      const params = props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
+      const data = props.form.getFieldsValue();
+      queryBuiltInParams(data, params).then((res: any) => {
+        if (res.status === 200) {
+          const actionParams = res.result.filter((item: any) => item.id === `action_${props.name}`);
+          // 获取当前属性类型,过滤不同类型的数据
+          const propertiesItem = props.properties
+            .filter((item) => {
+              if (item.expands && item.expands.type) {
+                return item.expands.type.includes('write');
+              }
+              return false;
+            })
+            .find((item) => item.id === propertiesKey);
+          const type = propertiesItem?.valueType?.type;
+          const _params = props.name === 0 ? res.result : actionParams;
+          paramsListRef.current = cloneDeep(_params);
+          const _filterData = filterParamsData(type, _params);
+          const _data = handleTreeData(_filterData);
+          setBuiltInList(_data);
+        }
+      });
     }
-  }, [props.trigger?.trigger?.device?.productId, source]);
+    setTimeout(() => {
+      setIsEdit(true);
+    }, 300);
+  }, [props.isEdit]);
 
   useEffect(() => {
-    if (props.productId) {
-      setPropertiesKey(undefined);
-      onChange('undefined', undefined, source);
+    if (props.trigger?.trigger?.device?.productId && source === 'upper' && isEdit) {
+      sourceChangeEvent();
     }
-  }, [props.productId]);
+  }, [props.trigger?.trigger?.device?.productId, source]);
+
+  // useEffect(() => {
+  //   if (props.productId) {
+  //     setPropertiesKey(undefined);
+  //     onChange('undefined', undefined, source);
+  //   }
+  // }, [props.productId]);
 
   useEffect(() => {
+    console.log(props.value);
     if (props.value) {
       if (props.properties && props.properties.length) {
         if (0 in props.value) {
@@ -246,7 +280,7 @@ export default (props: WritePropertyProps) => {
             style={{ width: 120 }}
             onChange={(key) => {
               setSource(key);
-              onChange(propertiesKey, propertiesValue, key);
+              onChange(propertiesKey, undefined, key);
             }}
           />
           {source === 'upper' ? (

+ 4 - 2
src/pages/rule-engine/Scene/Save/action/device/deviceModal.tsx

@@ -25,9 +25,9 @@ type ChangeValueType = {
 
 const DeviceBadge = (props: DeviceBadgeProps) => {
   const STATUS = {
-    notActive: 'processing',
+    notActive: 'warning',
     offline: 'error',
-    online: 'success',
+    online: 'processing',
   };
   return <Badge status={STATUS[props.type]} text={props.text} />;
 };
@@ -43,6 +43,7 @@ export default (props: DeviceModelProps) => {
   useEffect(() => {
     setValue(props.value || []);
     setSelectKeys(props.value || []);
+    console.log(props.value);
   }, [props.value]);
 
   const columns: ProColumns<DeviceItem>[] = [
@@ -157,6 +158,7 @@ export default (props: DeviceModelProps) => {
             columns={columns}
             rowKey="id"
             search={false}
+            columnEmptyText={''}
             rowSelection={{
               selectedRowKeys: selectKeys.map((item) => item.value),
               onSelect: (selectedRow: any, selected: any) => {

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


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff