wzyyy 3 år sedan
förälder
incheckning
6f05b21fd9
57 ändrade filer med 985 tillägg och 413 borttagningar
  1. BIN
      public/images/home/comprehensive.png
  2. BIN
      public/images/home/device.png
  3. BIN
      public/images/home/ops.png
  4. 2 0
      src/components/DashBoard/echarts.tsx
  5. 48 24
      src/components/ProTableCard/CardItems/device.tsx
  6. 2 2
      src/components/ProTableCard/CardItems/networkCard.tsx
  7. 47 24
      src/components/ProTableCard/CardItems/product.tsx
  8. 6 3
      src/components/ProTableCard/index.less
  9. 1 1
      src/global.less
  10. 0 1
      src/hooks/document/useDomFullHeight.tsx
  11. 12 1
      src/pages/device/Instance/Detail/Config/Edit.tsx
  12. 2 2
      src/pages/device/Instance/Detail/Functions/index.tsx
  13. 1 1
      src/pages/device/Instance/Detail/Running/Property/EditProperty.tsx
  14. 1 0
      src/pages/device/Instance/Detail/Running/Property/index.tsx
  15. 23 20
      src/pages/device/Instance/Detail/index.tsx
  16. 6 8
      src/pages/device/Instance/Export/index.tsx
  17. 2 0
      src/pages/device/Instance/Import/index.tsx
  18. 1 0
      src/pages/device/Instance/typings.d.ts
  19. 68 45
      src/pages/device/Product/Detail/Access/AccessConfig/index.tsx
  20. 21 1
      src/pages/device/Product/Detail/Access/index.tsx
  21. 1 0
      src/pages/device/Product/typings.d.ts
  22. 12 0
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  23. 16 14
      src/pages/device/components/Metadata/Import/index.tsx
  24. 10 13
      src/pages/home/components/DeviceChoose.tsx
  25. 1 1
      src/pages/home/components/Guide.tsx
  26. 2 7
      src/pages/home/components/ProductChoose.tsx
  27. 11 4
      src/pages/home/components/index.less
  28. 17 7
      src/pages/home/comprehensive/index.tsx
  29. 11 3
      src/pages/home/device/index.tsx
  30. 18 3
      src/pages/home/init/index.less
  31. 20 15
      src/pages/home/init/index.tsx
  32. 26 21
      src/pages/home/ops/index.tsx
  33. 13 8
      src/pages/link/AccessConfig/Detail/Access/index.tsx
  34. 6 2
      src/pages/link/AccessConfig/Detail/Provider/index.tsx
  35. 14 8
      src/pages/link/DashBoard/index.less
  36. 317 103
      src/pages/link/DashBoard/index.tsx
  37. 8 1
      src/pages/link/Protocol/FileUpload/index.tsx
  38. 22 0
      src/pages/link/Protocol/save/index.tsx
  39. 3 0
      src/pages/link/Protocol/service.ts
  40. 2 2
      src/pages/link/Type/index.tsx
  41. 17 7
      src/pages/media/Cascade/Save/index.tsx
  42. 11 0
      src/pages/media/Device/Channel/Live/index.tsx
  43. 3 2
      src/pages/media/Home/index.tsx
  44. 11 0
      src/pages/notice/Config/Log/index.tsx
  45. 11 0
      src/pages/notice/Template/Log/index.tsx
  46. 7 3
      src/pages/rule-engine/Scene/Save/components/TimeSelect/index.tsx
  47. 3 1
      src/pages/system/Department/Assets/deivce/bind.tsx
  48. 7 0
      src/pages/system/Department/Assets/deivce/index.tsx
  49. 31 8
      src/pages/system/Department/Assets/index.tsx
  50. 3 1
      src/pages/system/Department/Assets/product/bind.tsx
  51. 7 0
      src/pages/system/Department/Assets/product/index.tsx
  52. 7 1
      src/pages/system/Department/Assets/productCategory/index.tsx
  53. 28 7
      src/pages/system/Department/Tree/tree.tsx
  54. 24 2
      src/pages/system/Department/index.less
  55. 15 9
      src/pages/system/Menu/Detail/buttons.tsx
  56. 26 23
      src/pages/system/Menu/Detail/edit.tsx
  57. 1 4
      src/pages/system/Permission/Save/index.tsx

BIN
public/images/home/comprehensive.png


BIN
public/images/home/device.png


BIN
public/images/home/ops.png


+ 2 - 0
src/components/DashBoard/echarts.tsx

@@ -53,6 +53,8 @@ const DefaultOptions = {
   ],
 };
 
+export { echarts };
+
 export default (props: EchartsProps) => {
   const chartsRef = useRef<any>(null);
 

+ 48 - 24
src/components/ProTableCard/CardItems/device.tsx

@@ -5,7 +5,7 @@ import { TableCard } from '@/components';
 import '@/style/common.less';
 import '../index.less';
 import { DisconnectOutlined } from '@ant-design/icons';
-import { Popconfirm } from 'antd';
+import { Popconfirm, Tooltip } from 'antd';
 import { useIntl } from '@@/plugin-locale/localeExports';
 
 export interface DeviceCardProps extends Partial<DeviceInstance> {
@@ -17,6 +17,8 @@ export interface DeviceCardProps extends Partial<DeviceInstance> {
   onClick?: () => void;
   grantedPermissions?: string[];
   onUnBind?: (e: any) => void;
+  showBindBtn?: boolean;
+  cardType?: 'bind' | 'unbind';
 }
 
 const defaultImage = require('/public/images/device-type-3-big.png');
@@ -30,7 +32,7 @@ export const PermissionsMap = {
 export const handlePermissionsMap = (permissions?: string[]) => {
   return permissions && permissions.length
     ? permissions.map((item) => PermissionsMap[item]).toString()
-    : '--';
+    : '';
 };
 
 export const ExtraDeviceCard = (props: DeviceCardProps) => {
@@ -65,34 +67,56 @@ export const ExtraDeviceCard = (props: DeviceCardProps) => {
         </div>
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
-            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+            <span className={'card-item-header-name'}>
+              <Tooltip title={props.name}>
+                <span className={'ellipsis'}>{props.name}</span>
+              </Tooltip>
+            </span>
           </div>
           <div className={'card-item-content-flex'}>
             <div className={'flex-auto'}>
               <label>ID</label>
-              <div className={'ellipsis'}>{props.id || '--'}</div>
-            </div>
-            <div className={'flex-auto'}>
-              <label>资产权限</label>
-              <div className={'ellipsis'}>{handlePermissionsMap(props.grantedPermissions)}</div>
+              <div className={'ellipsis'}>
+                <Tooltip title={props.id}>{props.id || ''}</Tooltip>
+              </div>
             </div>
-            <Popconfirm
-              title={intl.formatMessage({
-                id: 'pages.system.role.option.unBindUser',
-                defaultMessage: '是否解除绑定',
-              })}
-              key="unBind"
-              onConfirm={(e) => {
-                e?.stopPropagation();
-                if (props.onUnBind) {
-                  props.onUnBind(e);
-                }
-              }}
-            >
-              <div className={'flex-button'}>
-                <DisconnectOutlined />
+            {props.cardType === 'bind' ? (
+              <div className={'flex-auto'}>
+                <label>说明</label>
+                <Tooltip title={props.describe}>
+                  <div className={'ellipsis'}>{props.describe}</div>
+                </Tooltip>
+              </div>
+            ) : (
+              <div className={'flex-auto'}>
+                <label>资产权限</label>
+                <div className={'ellipsis'}>
+                  <Tooltip title={handlePermissionsMap(props.grantedPermissions)}>
+                    {handlePermissionsMap(props.grantedPermissions)}
+                  </Tooltip>
+                </div>
               </div>
-            </Popconfirm>
+            )}
+
+            {props.showBindBtn !== false && (
+              <Popconfirm
+                title={intl.formatMessage({
+                  id: 'pages.system.role.option.unBindUser',
+                  defaultMessage: '是否解除绑定',
+                })}
+                key="unBind"
+                onConfirm={(e) => {
+                  e?.stopPropagation();
+                  if (props.onUnBind) {
+                    props.onUnBind(e);
+                  }
+                }}
+              >
+                <div className={'flex-button'}>
+                  <DisconnectOutlined />
+                </div>
+              </Popconfirm>
+            )}
           </div>
         </div>
       </div>

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

@@ -30,8 +30,8 @@ export default (props: NoticeCardProps) => {
     } else {
       const log = record.cluster?.map(
         (item) =>
-          `${item.configuration?.publicHost || record.configuration?.remoteHost}:${
-            item.configuration?.publicPort || record.configuration?.remotePort
+          `${item.configuration?.publicHost || item.configuration?.remoteHost}:${
+            item.configuration?.publicPort || item.configuration?.remotePort
           }`,
       );
       return (

+ 47 - 24
src/components/ProTableCard/CardItems/product.tsx

@@ -5,7 +5,7 @@ import { useIntl } from 'umi';
 import { TableCard } from '@/components';
 import '@/style/common.less';
 import '../index.less';
-import { Popconfirm } from 'antd';
+import { Popconfirm, Tooltip } from 'antd';
 import { DisconnectOutlined } from '@ant-design/icons';
 
 export interface ProductCardProps extends Partial<ProductItem> {
@@ -17,6 +17,8 @@ export interface ProductCardProps extends Partial<ProductItem> {
   onClick?: () => void;
   grantedPermissions?: string[];
   onUnBind?: (e: any) => void;
+  showBindBtn?: boolean;
+  cardType?: 'bind' | 'unbind';
 }
 
 const defaultImage = require('/public/images/device-product.png');
@@ -30,7 +32,7 @@ export const PermissionsMap = {
 export const handlePermissionsMap = (permissions?: string[]) => {
   return permissions && permissions.length
     ? permissions.map((item) => PermissionsMap[item]).toString()
-    : '--';
+    : '';
 };
 
 export const ExtraProductCard = (props: ProductCardProps) => {
@@ -67,7 +69,11 @@ export const ExtraProductCard = (props: ProductCardProps) => {
         </div>
         <div className={'card-item-body'}>
           <div className={'card-item-header'}>
-            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+            <span className={'card-item-header-name'}>
+              <Tooltip title={props.name}>
+                <span className={'ellipsis'}>{props.name}</span>
+              </Tooltip>
+            </span>
           </div>
           <div className={'card-item-content-items'} style={{ display: 'flex', gap: 12 }}>
             {props.content}
@@ -75,29 +81,46 @@ export const ExtraProductCard = (props: ProductCardProps) => {
           <div className={'card-item-content-flex'}>
             <div className={'flex-auto'}>
               <label>ID</label>
-              <div className={'ellipsis'}>{props.id || '--'}</div>
-            </div>
-            <div className={'flex-auto'}>
-              <label>资产权限</label>
-              <div className={'ellipsis'}>{handlePermissionsMap(props.grantedPermissions)}</div>
+              <div className={'ellipsis'}>
+                <Tooltip title={props.id}>{props.id || ''}</Tooltip>
+              </div>
             </div>
-            <Popconfirm
-              title={intl.formatMessage({
-                id: 'pages.system.role.option.unBindUser',
-                defaultMessage: '是否解除绑定',
-              })}
-              key="unBind"
-              onConfirm={(e) => {
-                e?.stopPropagation();
-                if (props.onUnBind) {
-                  props.onUnBind(e);
-                }
-              }}
-            >
-              <div className={'flex-button'}>
-                <DisconnectOutlined />
+            {props.cardType === 'bind' ? (
+              <div className={'flex-auto'}>
+                <label>说明</label>
+                <Tooltip title={props.describe}>
+                  <div className={'ellipsis'}>{props.describe}</div>
+                </Tooltip>
+              </div>
+            ) : (
+              <div className={'flex-auto'}>
+                <label>资产权限</label>
+                <div className={'ellipsis'}>
+                  <Tooltip title={handlePermissionsMap(props.grantedPermissions)}>
+                    {handlePermissionsMap(props.grantedPermissions)}
+                  </Tooltip>
+                </div>
               </div>
-            </Popconfirm>
+            )}
+            {props.showBindBtn !== false && (
+              <Popconfirm
+                title={intl.formatMessage({
+                  id: 'pages.system.role.option.unBindUser',
+                  defaultMessage: '是否解除绑定',
+                })}
+                key="unBind"
+                onConfirm={(e) => {
+                  e?.stopPropagation();
+                  if (props.onUnBind) {
+                    props.onUnBind(e);
+                  }
+                }}
+              >
+                <div className={'flex-button'}>
+                  <DisconnectOutlined />
+                </div>
+              </Popconfirm>
+            )}
           </div>
         </div>
       </div>

+ 6 - 3
src/components/ProTableCard/index.less

@@ -58,7 +58,10 @@
           gap: 12px;
 
           .flex-auto {
-            flex: 1 1 auto;
+            display: flex;
+            flex-direction: column;
+            flex-grow: 1;
+            width: 0;
           }
 
           .flex-button {
@@ -78,7 +81,7 @@
           }
 
           .ellipsis {
-            width: 70%;
+            max-width: 70%;
             font-weight: bold;
             font-size: 14px;
           }
@@ -103,7 +106,7 @@
           }
 
           .ellipsis {
-            width: 70%;
+            max-width: 70%;
             font-weight: bold;
             font-size: 14px;
           }

+ 1 - 1
src/global.less

@@ -117,7 +117,7 @@ ol {
 }
 
 .ellipsis {
-  width: 100%;
+  max-width: 100%;
   overflow: hidden;
   white-space: nowrap;
   text-align: left;

+ 0 - 1
src/hooks/document/useDomFullHeight.tsx

@@ -38,7 +38,6 @@ const useDomFullHeight = (target: BasicTarget | string, extraHeight: number = 0)
         entries.forEach((entry) => {
           const bodyClient = document.body.getBoundingClientRect();
           const domClient = entry.target.getBoundingClientRect();
-          console.log(domClient);
           if (domClient.y < 50) {
             setState(100);
           } else {

+ 12 - 1
src/pages/device/Instance/Detail/Config/Edit.tsx

@@ -2,7 +2,7 @@ import { createForm } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import { InstanceModel, service } from '@/pages/device/Instance';
 import type { ISchema } from '@formily/json-schema';
-import { Form, FormGrid, FormItem, Input, Password, PreviewText } from '@formily/antd';
+import { Form, FormGrid, FormItem, Input, Password, PreviewText, Select } from '@formily/antd';
 import { Button, Drawer, Space } from 'antd';
 import { useParams } from 'umi';
 import { onlyMessage } from '@/utils/util';
@@ -10,6 +10,7 @@ import { onlyMessage } from '@/utils/util';
 const componentMap = {
   string: 'Input',
   password: 'Password',
+  enum: 'Select',
 };
 
 interface Props {
@@ -33,6 +34,7 @@ const Edit = (props: Props) => {
       FormItem,
       Input,
       Password,
+      Select,
       FormGrid,
       PreviewText,
     },
@@ -49,6 +51,15 @@ const Edit = (props: Props) => {
         'x-decorator-props': {
           tooltip: item.description,
         },
+        enum:
+          item?.type?.type === 'enum' && item?.type?.elements
+            ? (item?.type?.elements || []).map((t: { value: string; text: string }) => {
+                return {
+                  label: t.text,
+                  value: t.value,
+                };
+              })
+            : [],
       };
     });
     return config;

+ 2 - 2
src/pages/device/Instance/Detail/Functions/index.tsx

@@ -15,13 +15,13 @@ const Functions = () => {
 
   return (
     <Card className={'device-detail-function'} style={{ minHeight: minHeight }}>
-      {functionList ? (
+      {functionList && functionList.length ? (
         <Tabs>
           <Tabs.TabPane tab={'精简模式'} key={1}>
             <>
               <div style={{ paddingBottom: 12 }}>
                 <ExclamationCircleOutlined style={{ marginRight: 12 }} />
-                精简模式下参数只支持输入框的方式录入
+                精简模式下参数只支持输入框的方式录入
               </div>
               <Tabs tabPosition="left">
                 {functionList &&

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

@@ -29,7 +29,7 @@ const EditProperty = (props: Props) => {
     properties: {
       propertyValue: {
         type: 'string',
-        title: '自定义属性',
+        title: data?.name || '自定义属性',
         required: true,
         'x-decorator': 'FormItem',
         'x-component': 'Input',

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

@@ -95,6 +95,7 @@ const Property = (props: Props) => {
               <a
                 onClick={() => {
                   setVisible(true);
+                  setCurrentInfo(record);
                 }}
               >
                 <EditOutlined />

+ 23 - 20
src/pages/device/Instance/Detail/index.tsx

@@ -147,32 +147,35 @@ const InstanceDetail = observer(() => {
       tab: '设备诊断',
       component: <Diagnose />,
     },
-    {
-      key: 'metadata-map',
-      tab: '物模型映射',
-      component: <MetadataMap type="device" />,
-    },
   ];
 
-  // const pList = [
-  //   'websocket-server',
-  //   'http-server-gateway',
-  //   'udp-device-gateway',
-  //   'coap-server-gateway',
-  //   'mqtt-client-gateway',
-  //   'mqtt-server-gateway',
-  //   'tcp-server-gateway',
-  // ];
+  const pList = [
+    'websocket-server',
+    'http-server-gateway',
+    'udp-device-gateway',
+    'coap-server-gateway',
+    'mqtt-client-gateway',
+    'mqtt-server-gateway',
+    'tcp-server-gateway',
+  ];
   const [list, setList] =
     useState<{ key: string; tab: string | ReactNode; component: ReactNode }[]>(baseList);
 
-  const getDetail = (id: string) => {
-    service.detail(id).then((response) => {
+  const getDetail = async (id: string) => {
+    const response = await service.detail(id);
+    if (response.status === 200) {
       InstanceModel.detail = response?.result;
       const datalist = [...baseList];
-      // if(InstanceModel.detail){
-
-      // }
+      if (
+        InstanceModel.detail?.accessProvider &&
+        pList.includes(InstanceModel.detail?.accessProvider)
+      ) {
+        datalist.push({
+          key: 'metadata-map',
+          tab: '物模型映射',
+          component: <MetadataMap type="device" />,
+        });
+      }
       if (response.result.protocol === 'modbus-tcp') {
         datalist.push({
           key: 'modbus',
@@ -199,7 +202,7 @@ const InstanceDetail = observer(() => {
       // 写入物模型数据
       const metadata: DeviceMetadata = JSON.parse(response.result?.metadata || '{}');
       MetadataAction.insert(metadata);
-    });
+    }
   };
 
   const [subscribeTopic] = useSendWebsocketMessage();

+ 6 - 8
src/pages/device/Instance/Export/index.tsx

@@ -1,7 +1,7 @@
 import { FormItem, FormLayout, Radio, Select } from '@formily/antd';
 import { createForm } from '@formily/core';
 import { createSchemaField, FormProvider } from '@formily/react';
-import { Alert, Modal } from 'antd';
+import { Modal } from 'antd';
 import 'antd/lib/tree-select/style/index.less';
 import { useEffect, useState } from 'react';
 import { service } from '@/pages/device/Instance';
@@ -9,7 +9,6 @@ import type { DeviceInstance } from '../typings';
 import SystemConst from '@/utils/const';
 import encodeQuery from '@/utils/encodeQuery';
 import { downloadFile } from '@/utils/util';
-
 interface Props {
   visible: boolean;
   close: () => void;
@@ -115,12 +114,11 @@ const Export = (props: Props) => {
       title="导出"
       onOk={downloadTemplate}
     >
-      <Alert
-        message="选择单个产品时可导出其下属设备的详细数据,不选择产品时导出所有设备的基础数据"
-        type="warning"
-        showIcon
-        closable
-      />
+      <div style={{ background: 'rgb(236, 237, 238)' }}>
+        <p style={{ padding: 10 }}>
+          选择单个产品时可导出其下属设备的详细数据,不选择产品时导出所有设备的基础数
+        </p>
+      </div>
       <div style={{ marginTop: '20px' }}>
         <FormProvider form={form}>
           <SchemaField schema={schema} />

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

@@ -200,9 +200,11 @@ const Import = (props: Props) => {
         });
       });
       onFieldValueChange('fileType', (field) => {
+        const product = form.getValuesIn('product') || '';
         form.setFieldState('*(upload)', (state) => {
           state.componentProps = {
             fileType: field.value,
+            product,
           };
         });
       });

+ 1 - 0
src/pages/device/Instance/typings.d.ts

@@ -40,6 +40,7 @@ export type DeviceInstance = {
   tags: any;
   photoUrl: string;
   independentMetadata?: boolean;
+  accessProvider?: string;
 };
 
 type Unit = {

+ 68 - 45
src/pages/device/Product/Detail/Access/AccessConfig/index.tsx

@@ -11,6 +11,7 @@ import AccessConfigCard from '@/components/ProTableCard/CardItems/AccessConfig';
 import { getMenuPathByCode } from '@/utils/menu';
 import PermissionButton from '@/components/PermissionButton';
 import { onlyMessage } from '@/utils/util';
+import Empty from '@/components/Empty';
 
 interface Props {
   close: () => void;
@@ -28,7 +29,7 @@ const AccessConfig = (props: Props) => {
     pageIndex: 0,
     total: 0,
   });
-  const [param, setParam] = useState<any>({ pageSize: 4 });
+  const [param, setParam] = useState<any>({ pageSize: 4, terms: [] });
 
   const [currrent, setCurrrent] = useState<any>({
     id: productModel.current?.accessId,
@@ -42,11 +43,27 @@ const AccessConfig = (props: Props) => {
 
   const handleSearch = (params: any) => {
     setParam(params);
-    service
-      .queryList({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
-      .then((resp) => {
-        setDataSource(resp?.result);
-      });
+    const temp = {
+      ...params,
+      terms:
+        productModel.current?.deviceType?.value === 'childrenDevice'
+          ? [
+              ...params.terms,
+              {
+                terms: [
+                  {
+                    column: 'provider',
+                    termType: 'eq',
+                    value: 'child-device',
+                  },
+                ],
+              },
+            ]
+          : [...params?.terms],
+    };
+    service.queryList({ ...temp, sorts: [{ name: 'createTime', order: 'desc' }] }).then((resp) => {
+      setDataSource(resp?.result);
+    });
   };
 
   const columns: ProColumns<any>[] = [
@@ -157,46 +174,52 @@ const AccessConfig = (props: Props) => {
           </PermissionButton>
         </div>
       </div>
-      <Row gutter={[16, 16]}>
-        {(dataSource?.data || []).map((item: any) => (
-          <Col
-            key={item.name}
-            span={12}
-            onClick={() => {
-              setCurrrent(item);
+      {dataSource?.data?.length > 0 ? (
+        <Row gutter={[16, 16]}>
+          {(dataSource?.data || []).map((item: any) => (
+            <Col
+              key={item.name}
+              span={12}
+              onClick={() => {
+                setCurrrent(item);
+              }}
+            >
+              <AccessConfigCard
+                {...item}
+                showTool={false}
+                activeStyle={currrent?.id === item.id ? 'active' : ''}
+              />
+            </Col>
+          ))}
+        </Row>
+      ) : (
+        <Empty />
+      )}
+      {dataSource?.data?.length > 0 && (
+        <div style={{ display: 'flex', marginTop: 20, justifyContent: 'flex-end' }}>
+          <Pagination
+            showSizeChanger
+            size="small"
+            className={'pro-table-card-pagination'}
+            total={dataSource?.total || 0}
+            current={dataSource?.pageIndex + 1}
+            onChange={(page, size) => {
+              handleSearch({
+                ...param,
+                pageIndex: page - 1,
+                pageSize: size,
+              });
             }}
-          >
-            <AccessConfigCard
-              {...item}
-              showTool={false}
-              activeStyle={currrent?.id === item.id ? 'active' : ''}
-            />
-          </Col>
-        ))}
-      </Row>
-      <div style={{ display: 'flex', marginTop: 20, justifyContent: 'flex-end' }}>
-        <Pagination
-          showSizeChanger
-          size="small"
-          className={'pro-table-card-pagination'}
-          total={dataSource?.total || 0}
-          current={dataSource?.pageIndex + 1}
-          onChange={(page, size) => {
-            handleSearch({
-              ...param,
-              pageIndex: page - 1,
-              pageSize: size,
-            });
-          }}
-          pageSizeOptions={[4, 8, 16, 32]}
-          pageSize={dataSource?.pageSize}
-          showTotal={(num) => {
-            const minSize = dataSource?.pageIndex * dataSource?.pageSize + 1;
-            const MaxSize = (dataSource?.pageIndex + 1) * dataSource?.pageSize;
-            return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
-          }}
-        />
-      </div>
+            pageSizeOptions={[4, 8, 16, 32]}
+            pageSize={dataSource?.pageSize}
+            showTotal={(num) => {
+              const minSize = dataSource?.pageIndex * dataSource?.pageSize + 1;
+              const MaxSize = (dataSource?.pageIndex + 1) * dataSource?.pageSize;
+              return `第 ${minSize} - ${MaxSize > num ? num : MaxSize} 条/总共 ${num} 条`;
+            }}
+          />
+        </div>
+      )}
     </Modal>
   );
 };

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

@@ -6,7 +6,16 @@ import type { SetStateAction } from 'react';
 import { useEffect, useState } from 'react';
 import AccessConfig from './AccessConfig';
 import ReactMarkdown from 'react-markdown';
-import { Form, FormGrid, FormItem, FormLayout, Input, Password, PreviewText } from '@formily/antd';
+import {
+  Form,
+  FormGrid,
+  FormItem,
+  FormLayout,
+  Input,
+  Password,
+  PreviewText,
+  Select,
+} from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
 import type { ConfigProperty } from '@/pages/device/Product/typings';
 import { createSchemaField } from '@formily/react';
@@ -19,6 +28,7 @@ import { onlyMessage } from '@/utils/util';
 const componentMap = {
   string: 'Input',
   password: 'Password',
+  enum: 'Select',
 };
 
 const Access = () => {
@@ -226,6 +236,7 @@ const Access = () => {
       PreviewText,
       FormItem,
       Input,
+      Select,
     },
   });
 
@@ -243,6 +254,15 @@ const Access = () => {
           labelAlign: 'left',
           layout: 'vertical',
         },
+        enum:
+          item?.type?.type === 'enum' && item?.type?.elements
+            ? (item?.type?.elements || []).map((t: { value: string; text: string }) => {
+                return {
+                  label: t.text,
+                  value: t.value,
+                };
+              })
+            : [],
       };
     });
     return obj;

+ 1 - 0
src/pages/device/Product/typings.d.ts

@@ -38,6 +38,7 @@ export type ConfigProperty = {
     name: string;
     id: string;
     type: string;
+    elements?: any[];
   };
   scopes: any[];
 };

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

@@ -662,6 +662,18 @@ const Edit = observer((props: Props) => {
                 value: 'report',
               },
             ],
+            'x-reactions': [
+              {
+                dependencies: ['.source'],
+                fulfill: {
+                  state: {
+                    value:
+                      '{{$deps[0]==="manual" ? ["write"] : $deps[0]==="rule" ? ["report"] : []}}',
+                    disabled: '{{["manual", "rule"].includes($deps[0])}}',
+                  },
+                },
+              },
+            ],
           },
           // 存储配置
           configConfig: {

+ 16 - 14
src/pages/device/components/Metadata/Import/index.tsx

@@ -40,11 +40,13 @@ const Import = (props: Props) => {
       paging: false,
       terms: [{ column: 'id$not', value: param.id }],
     })) as any;
-    field.dataSource = product.result.map((item: any) => ({
-      label: item.name,
-      value: item.metadata,
-      key: item.id,
-    }));
+    field.dataSource = product.result
+      .filter((i: any) => i?.metadata)
+      .map((item: any) => ({
+        label: item.name,
+        value: item.metadata,
+        key: item.id,
+      }));
     field.loading = false;
   };
 
@@ -213,15 +215,15 @@ const Import = (props: Props) => {
         <p style={{ padding: 10 }}>
           <span style={{ color: '#f5222d' }}>注</span>
           :导入的物模型会覆盖原来的属性、功能、事件、标签,请谨慎操作。
-          <br />
-          物模型格式请参考文档:
-          <a
-            rel="noopener noreferrer"
-            target="_blank"
-            href="http://doc.jetlinks.cn/basics-guide/device-manager.html#%E8%AE%BE%E5%A4%87%E5%9E%8B%E5%8F%B7"
-          >
-            设备型号
-          </a>
+          {/* <br /> */}
+          {/*物模型格式请参考文档:*/}
+          {/*<a*/}
+          {/*  rel="noopener noreferrer"*/}
+          {/*  target="_blank"*/}
+          {/*  href="http://doc.jetlinks.cn/basics-guide/device-manager.html#%E8%AE%BE%E5%A4%87%E5%9E%8B%E5%8F%B7"*/}
+          {/*>*/}
+          {/*  设备型号*/}
+          {/*</a>*/}
         </p>
       </div>
       <Form form={form} layout="vertical">

+ 10 - 13
src/pages/home/components/DeviceChoose.tsx

@@ -6,7 +6,7 @@ import SearchComponent from '@/components/SearchComponent';
 import type { DeviceItem } from '@/pages/media/Home/typings';
 import { useEffect, useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { BadgeStatus, PermissionButton } from '@/components';
+import { BadgeStatus } from '@/components';
 import { StatusColorEnum } from '@/components/BadgeStatus';
 import useHistory from '@/hooks/route/useHistory';
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
@@ -20,7 +20,6 @@ interface DeviceModalProps {
 export default (props: DeviceModalProps) => {
   const intl = useIntl();
   const history = useHistory();
-  const permission = PermissionButton.usePermission('device/Instance').permission;
 
   const actionRef = useRef<ActionType>();
   const [searchParam, setSearchParam] = useState({});
@@ -44,14 +43,17 @@ export default (props: DeviceModalProps) => {
       dataIndex: 'id',
       title: '设备ID',
       width: 220,
+      ellipsis: true,
     },
     {
       dataIndex: 'name',
       title: '设备名称',
+      ellipsis: true,
     },
     {
       dataIndex: 'productName',
       title: '产品名称',
+      ellipsis: true,
     },
     {
       dataIndex: 'modifyTime',
@@ -103,17 +105,12 @@ export default (props: DeviceModalProps) => {
       onCancel={cancel}
       onOk={() => {
         if (deviceItem?.id) {
-          if (!!permission.update) {
-            history.push(
-              `${getMenuPathByParams(MENUS_CODE['device/Instance/Detail'], deviceItem.id)}`,
-              {
-                tab: 'diagnose',
-              },
-            );
-          } else {
-            message.warning('暂无权限,请联系管理员');
-            cancel();
-          }
+          history.push(
+            `${getMenuPathByParams(MENUS_CODE['device/Instance/Detail'], deviceItem.id)}`,
+            {
+              tab: 'diagnose',
+            },
+          );
         } else {
           message.warning('请选择设备');
         }

+ 1 - 1
src/pages/home/components/Guide.tsx

@@ -36,7 +36,7 @@ const GuideItem = (props: GuideItemProps) => {
   };
 
   return (
-    <div className={'home-guide-item step-bar arrow-2'} onClick={jumpPage}>
+    <div className={'home-guide-item step-bar arrow-2 pointer'} onClick={jumpPage}>
       <div className={'item-english'}>{props.english}</div>
       <div className={'item-title'}>{props.name}</div>
       <div className={`item-index`}>

+ 2 - 7
src/pages/home/components/ProductChoose.tsx

@@ -1,11 +1,10 @@
 import { FormItem, FormLayout, Select } from '@formily/antd';
 import { createForm } from '@formily/core';
 import { createSchemaField, FormProvider } from '@formily/react';
-import { Button, message, Modal } from 'antd';
+import { Button, Modal } from 'antd';
 import 'antd/lib/tree-select/style/index.less';
 import { useEffect, useState } from 'react';
 import { service } from '@/pages/device/Instance';
-import { PermissionButton } from '@/components';
 import useHistory from '@/hooks/route/useHistory';
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 
@@ -15,7 +14,6 @@ interface Props {
 }
 
 const ProductChoose = (props: Props) => {
-  const productPermission = PermissionButton.usePermission('device/Product').permission;
   const { visible, close } = props;
   const [productList, setProductList] = useState<any[]>([]);
 
@@ -94,16 +92,13 @@ const ProductChoose = (props: Props) => {
           onClick={async () => {
             const data: any = await form.submit();
             const path = getMenuPathByParams(`device/Product/Detail`);
-            if (path && !!productPermission.update) {
+            if (path) {
               history.push(
                 `${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], data.product)}`,
                 {
                   tab: 'access',
                 },
               );
-            } else {
-              message.warning('暂无权限,请联系管理员');
-              close();
             }
           }}
         >

+ 11 - 4
src/pages/home/components/index.less

@@ -26,7 +26,10 @@
   background: linear-gradient(135.62deg, #f6f7fd 22.27%, rgba(255, 255, 255, 0.86) 91.82%);
   border-radius: 2px;
   box-shadow: 0 4px 18px #efefef;
-  cursor: pointer;
+
+  &.pointer {
+    cursor: pointer;
+  }
 
   .item-english {
     color: #4f4f4f;
@@ -85,9 +88,8 @@
 }
 
 .home-body {
-  .home-base;
+  // .home-base;
   min-height: 440px;
-
   margin-bottom: @margin;
   padding-bottom: 26.5%;
   overflow: hidden;
@@ -143,7 +145,7 @@
         padding: 16px 24px;
         color: #333;
         font-weight: bold;
-        font-size: 20px;
+        font-size: 14px;
         background-color: #f8f9fd;
         cursor: pointer;
 
@@ -152,6 +154,11 @@
           top: 0;
           right: 0;
           z-index: 1;
+          height: 100%;
+
+          img {
+            height: 100%;
+          }
         }
 
         > span {

+ 17 - 7
src/pages/home/comprehensive/index.tsx

@@ -131,7 +131,7 @@ const Comprehensive = () => {
       img: require('/public/images/home/guide-home4.png'),
       english: 'DEVICE ACCESS CONFIGURATION',
       auth: !!accessPermission,
-      url: 'link/AccessConfig',
+      url: accessPermission,
     },
     {
       key: 'logger',
@@ -139,7 +139,7 @@ const Comprehensive = () => {
       english: 'LOG SCREEN',
       img: require('/public/images/home/guide-home5.png'),
       auth: !!logPermission,
-      url: 'Log',
+      url: logPermission,
       param: {
         key: 'system',
       },
@@ -150,7 +150,7 @@ const Comprehensive = () => {
       img: require('/public/images/home/guide-home6.png'),
       english: 'REAL-TIME MONITORING',
       auth: !!linkPermission,
-      url: 'link/DashBoard',
+      url: linkPermission,
       param: {
         save: true,
       },
@@ -267,9 +267,13 @@ const Comprehensive = () => {
             {
               title: '配置产品接入方式',
               content:
-                '通过产品对同一类型的所有设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
+                '通过产品对同一类型的设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
               onClick: () => {
-                setProductVisible(true);
+                if (!!productPermission.update) {
+                  setProductVisible(true);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
               },
             },
             {
@@ -290,7 +294,11 @@ const Comprehensive = () => {
               title: '功能调试',
               content: '对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。',
               onClick: () => {
-                setDeviceVisible(true);
+                if (!!devicePermission.update) {
+                  setDeviceVisible(true);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
               },
             },
             {
@@ -380,7 +388,9 @@ const Comprehensive = () => {
               onClick: () => {
                 const url = getMenuPathByCode(MENUS_CODE['Log']);
                 if (!!url) {
-                  history.push(url);
+                  history.push(url, {
+                    key: 'system',
+                  });
                 } else {
                   message.warning('暂无权限,请联系管理员');
                 }

+ 11 - 3
src/pages/home/device/index.tsx

@@ -153,9 +153,13 @@ const Device = () => {
             {
               title: '配置产品接入方式',
               content:
-                '通过产品对同一类型的所有设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
+                '通过产品对同一类型的设备进行统一的接入方式配置。请参照设备铭牌说明选择匹配的接入方式。',
               onClick: () => {
-                setProductVisible(true);
+                if (!!productPermission.update) {
+                  setProductVisible(true);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
               },
             },
             {
@@ -176,7 +180,11 @@ const Device = () => {
               title: '功能调试',
               content: '对添加的测试设备进行功能调试,验证能否连接到平台,设备功能是否配置正确。',
               onClick: () => {
-                setDeviceVisible(true);
+                if (!!devicePermission.update) {
+                  setDeviceVisible(true);
+                } else {
+                  message.warning('暂无权限,请联系管理员');
+                }
               },
             },
             {

+ 18 - 3
src/pages/home/init/index.less

@@ -1,5 +1,17 @@
+@import '~antd/es/style/themes/default.less';
+
+.homeBox {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 100%;
+  height: calc(100vh - 150px);
+  padding: 20px;
+  background-color: white;
+}
 .title {
-  margin-bottom: 10px;
+  margin-top: 28px;
+  margin-bottom: 48px;
   font-weight: 400;
   font-size: 26px;
   text-align: center;
@@ -10,8 +22,11 @@
   flex-direction: column;
   align-items: center;
 
+  &.active {
+    border: 2px solid @primary-color-active;
+  }
+
   img {
-    width: 80%;
-    margin-bottom: 5px;
+    width: 100%;
   }
 }

+ 20 - 15
src/pages/home/init/index.tsx

@@ -1,4 +1,5 @@
-import { Button, Col, Radio, Row } from 'antd';
+import { Button, Col, Row } from 'antd';
+import classNames from 'classnames';
 import { useState } from 'react';
 import { service } from '..';
 import styles from './index.less';
@@ -28,22 +29,26 @@ const Init = (props: Props) => {
     },
   ];
   return (
-    <div>
+    <div className={styles.homeBox}>
       <div className={styles.title}>请选择首页视图</div>
-      <Radio.Group value={value} onChange={(e) => setValue(e.target.value)}>
-        <Row gutter={24}>
-          {viewMap.map((item) => (
-            <Col span={8} key={item.value}>
-              <div className={styles.item}>
-                <img src={item.img} className={styles.item} />
-                <Radio value={item.value}>{item.title}</Radio>
-              </div>
-            </Col>
-          ))}
-        </Row>
-      </Radio.Group>
 
-      <div style={{ textAlign: 'center', marginTop: 30 }}>
+      <Row gutter={24}>
+        {viewMap.map((item) => (
+          <Col
+            span={8}
+            key={item.value}
+            onClick={() => {
+              setValue(item.value);
+            }}
+          >
+            <div className={classNames(styles.item, value === item.value ? styles.active : {})}>
+              <img src={item.img} className={styles.item} />
+            </div>
+          </Col>
+        ))}
+      </Row>
+
+      <div style={{ textAlign: 'center', marginTop: 48 }}>
         <Button
           type="primary"
           onClick={() => {

+ 26 - 21
src/pages/home/ops/index.tsx

@@ -1,6 +1,5 @@
 import { Col, message, Row, Tooltip } from 'antd';
 import Guide from '../components/Guide';
-import { PermissionButton } from '@/components';
 import Statistics from '../components/Statistics';
 import Pie from '@/pages/home/components/Pie';
 import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
@@ -16,9 +15,9 @@ const Ops = () => {
   const [subscribeTopic] = useSendWebsocketMessage();
   const history = useHistory();
 
-  const productPermission = PermissionButton.usePermission('device/Product').permission;
-  const devicePermission = PermissionButton.usePermission('device/Instance').permission;
-  const rulePermission = PermissionButton.usePermission('rule-engine/Instance').permission;
+  const accessPermission = getMenuPathByCode(MENUS_CODE['link/AccessConfig']);
+  const logPermission = getMenuPathByCode(MENUS_CODE['Log']);
+  const linkPermission = getMenuPathByCode(MENUS_CODE['link/DashBoard']);
 
   const [cpuValue, setCpuValue] = useState<number>(0);
   const [jvmValue, setJvmValue] = useState<number>(0);
@@ -57,30 +56,34 @@ const Ops = () => {
       jvmRealTime?.unsubscribe();
     };
   }, []);
-  const guideOpsList: any[] = [
+
+  const guideOpsList = [
     {
-      key: 'product',
+      key: 'access',
       name: '设备接入配置',
-      english: 'CREATE PRODUCT',
-      auth: !!productPermission.add,
-      url: 'device/Product',
-      param: '?save=true',
+      english: 'DEVICE ACCESS CONFIGURATION',
+      auth: !!accessPermission,
+      url: accessPermission,
     },
     {
-      key: 'device',
+      key: 'logger',
       name: '日志排查',
-      english: 'CREATE DEVICE',
-      auth: !!devicePermission.add,
-      url: 'device/Instance',
-      param: '?save=true',
+      english: 'LOG SCREEN',
+      auth: !!logPermission,
+      url: logPermission,
+      param: {
+        key: 'system',
+      },
     },
     {
-      key: 'rule-engine',
+      key: 'realtime',
       name: '实时监控',
-      english: 'RULE ENGINE',
-      auth: !!rulePermission.add,
-      url: 'rule-engine/Instance',
-      param: '?save=true',
+      english: 'REAL-TIME MONITORING',
+      auth: !!linkPermission,
+      url: linkPermission,
+      param: {
+        save: true,
+      },
     },
   ];
   return (
@@ -194,7 +197,9 @@ const Ops = () => {
               onClick: () => {
                 const url = getMenuPathByCode(MENUS_CODE['Log']);
                 if (!!url) {
-                  history.push(url);
+                  history.push(url, {
+                    key: 'system',
+                  });
                 } else {
                   message.warning('暂无权限,请联系管理员');
                 }

+ 13 - 8
src/pages/link/AccessConfig/Detail/Access/index.tsx

@@ -311,14 +311,18 @@ const Access = (props: Props) => {
                         <Tooltip
                           placement="topLeft"
                           title={
-                            <div>
-                              {[...item.addresses].map((i: any) => (
-                                <div key={i.address}>
-                                  <Badge color={i.health === -1 ? 'red' : 'green'} />
-                                  {i.address}
-                                </div>
-                              ))}
-                            </div>
+                            item.addresses?.length > 1 ? (
+                              <div>
+                                {[...item.addresses].map((i: any) => (
+                                  <div key={i.address}>
+                                    <Badge color={i.health === -1 ? 'red' : 'green'} />
+                                    {i.address}
+                                  </div>
+                                ))}
+                              </div>
+                            ) : (
+                              ''
+                            )
                           }
                         >
                           <div
@@ -334,6 +338,7 @@ const Access = (props: Props) => {
                             {item.addresses.slice(0, 1).map((i: any) => (
                               <div className={styles.item} key={i.address}>
                                 <Badge color={i.health === -1 ? 'red' : 'green'} text={i.address} />
+                                {item.addresses?.length > 1 && '...'}
                               </div>
                             ))}
                           </div>

+ 6 - 2
src/pages/link/AccessConfig/Detail/Provider/index.tsx

@@ -1,6 +1,6 @@
 import { useEffect, useState } from 'react';
 import { TitleComponent } from '@/components';
-import { Button, Card, Col, Row } from 'antd';
+import { Button, Card, Col, Row, Tooltip } from 'antd';
 import styles from './index.less';
 
 interface Props {
@@ -84,7 +84,11 @@ const Provider = (props: Props) => {
                       </div>
                       <div className={styles.context}>
                         <div style={{ fontWeight: 600 }}>{item.name}</div>
-                        <div className={styles.desc}>{item?.description || ''}</div>
+                        <div className={styles.desc}>
+                          <Tooltip title={item?.description || ''}>
+                            {item?.description || ''}
+                          </Tooltip>
+                        </div>
                       </div>
                     </div>
                     <div style={{ width: '70px' }}>

+ 14 - 8
src/pages/link/DashBoard/index.less

@@ -1,19 +1,25 @@
 .link-dash-board {
-  background-color: #fff;
-
   .echarts-items {
     position: relative;
     display: flex;
-    gap: 12px;
-    margin: 12px 0;
+    gap: 24px;
+    margin-bottom: 24px;
 
     .echarts-item {
       display: flex;
       flex-grow: 1;
-      align-items: center;
-      justify-content: center;
       width: 0;
-      height: 160px;
+      height: 180px;
+      padding: 16px;
+      background-color: #fff;
+
+      .echarts-item-left {
+        width: 45%;
+      }
+
+      .echarts-item-right {
+        width: 55%;
+      }
 
       .echarts-item-title {
         margin-bottom: 8px;
@@ -23,7 +29,7 @@
 
       .echarts-item-value {
         font-weight: bold;
-        font-size: 18px;
+        font-size: 36px;
       }
     }
   }

+ 317 - 103
src/pages/link/DashBoard/index.tsx

@@ -1,6 +1,6 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import DashBoard from '@/components/DashBoard';
-import { Progress, Radio, Select } from 'antd';
+import { Radio, Select } from 'antd';
 import { useEffect, useRef, useState } from 'react';
 import type { EChartsOption } from 'echarts';
 import { useRequest } from 'umi';
@@ -9,13 +9,143 @@ import moment from 'moment';
 import './index.less';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { map } from 'rxjs/operators';
+import Echarts, { echarts } from '@/components/DashBoard/echarts';
 
 type RefType = {
   getValues: Function;
 };
+type TopEchartsItemNodeType = {
+  value: any;
+  title: string;
+};
 
 const service = new Service('dashboard');
 
+const TopEchartsItemNode = (props: TopEchartsItemNodeType) => {
+  const options = {
+    series: [
+      {
+        type: 'gauge',
+        min: 0,
+        max: 100,
+        startAngle: 200,
+        endAngle: -20,
+        center: ['50%', '65%'],
+        title: {
+          show: false,
+        },
+        axisTick: {
+          distance: -20,
+          lineStyle: {
+            width: 1,
+            color: 'rgba(0,0,0,0.15)',
+          },
+        },
+        splitLine: {
+          distance: -22,
+          length: 9,
+          lineStyle: {
+            width: 1,
+            color: '#000',
+          },
+        },
+        axisLabel: {
+          distance: -18,
+          color: 'auto',
+          fontSize: 12,
+        },
+        pointer: {
+          length: '80%',
+          width: 4,
+          itemStyle: {
+            color: 'auto',
+          },
+        },
+        anchor: {
+          show: true,
+          showAbove: true,
+          size: 20,
+          itemStyle: {
+            borderWidth: 3,
+            borderColor: '#fff',
+            shadowBlur: 20,
+            shadowColor: 'rgba(0, 0, 0, .25)',
+            color: 'auto',
+          },
+        },
+        axisLine: {
+          lineStyle: {
+            width: 10,
+            color: [
+              [0.25, 'rgba(36, 178, 118, 1)'],
+              [
+                0.4,
+                new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  {
+                    offset: 0,
+                    color: 'rgba(66, 147, 255, 1)',
+                  },
+                  {
+                    offset: 1,
+                    color: 'rgba(36, 178, 118, 1)',
+                  },
+                ]),
+              ],
+              [
+                0.5,
+                new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  {
+                    offset: 0,
+                    color: 'rgba(250, 178, 71, 1)',
+                  },
+                  {
+                    offset: 1,
+                    color: 'rgba(66, 147, 255, 1)',
+                  },
+                ]),
+              ],
+              [
+                1,
+                new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  {
+                    offset: 0,
+                    color: 'rgba(250, 178, 71, 1)',
+                  },
+                  {
+                    offset: 1,
+                    color: 'rgba(247, 111, 93, 1)',
+                  },
+                ]),
+              ],
+            ],
+          },
+        },
+        detail: {
+          show: false,
+        },
+        data: [{ value: props.value || 0 }],
+      },
+    ],
+  };
+
+  return (
+    <div className={'echarts-item'}>
+      <div className={'echarts-item-left'}>
+        <div className={'echarts-item-title'}>{props.title}</div>
+        <div className={'echarts-item-value'}>{props.value}%</div>
+      </div>
+      <div className={'echarts-item-right'}>
+        <>
+          {
+            // @ts-ignore
+            <Echarts options={options} />
+          }
+        </>
+      </div>
+    </div>
+  );
+};
+
 export default () => {
   const [networkOptions, setNetworkOptions] = useState<EChartsOption | undefined>(undefined);
   const [cpuOptions, setCpuOptions] = useState<EChartsOption | undefined>(undefined);
@@ -39,11 +169,11 @@ export default () => {
     formatResult: (res) => res.result.map((item: any) => ({ label: item.name, value: item.id })),
   });
 
-  const handleNetworkOptions = (data: Record<string, any>) => {
+  const handleNetworkOptions = (data: Record<string, any>, xAxis: string[]) => {
     setNetworkOptions({
       xAxis: {
         type: 'category',
-        data: Object.keys(data),
+        data: xAxis,
       },
       tooltip: {
         trigger: 'axis',
@@ -52,25 +182,36 @@ export default () => {
         type: 'value',
       },
       grid: {
-        left: '3%',
+        left: '80px',
         right: '2%',
       },
-      series: [
-        {
-          data: Object.values(data),
-          type: 'line',
+      color: ['#979AFF'],
+      series: Object.keys(data).map((key) => ({
+        data: data[key]._data,
+        name: key,
+        type: 'line',
+        smooth: true,
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: 'rgba(151, 154, 255, 0)',
+            },
+            {
+              offset: 1,
+              color: 'rgba(151, 154, 255, .24)',
+            },
+          ]),
         },
-      ],
+      })),
     });
   };
 
-  const handleJVMOptions = (data: Record<string, any>) => {
+  const handleJVMOptions = (data: Record<string, any>, xAxis: string[]) => {
     setJvmOptions({
       xAxis: {
         type: 'category',
-        data: Object.keys(data).map((item) => {
-          return moment(Number(item)).format('YYYY-MM-DD HH:mm:ss');
-        }),
+        data: xAxis,
       },
       tooltip: {
         trigger: 'axis',
@@ -79,7 +220,7 @@ export default () => {
         type: 'value',
       },
       grid: {
-        left: '3%',
+        left: '50px',
         right: '2%',
       },
       dataZoom: [
@@ -93,22 +234,33 @@ export default () => {
           end: 10,
         },
       ],
-      series: [
-        {
-          data: Object.values(data),
-          type: 'line',
+      color: ['#60DFC7'],
+      series: Object.keys(data).map((key) => ({
+        data: data[key],
+        name: key,
+        type: 'line',
+        smooth: true,
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 1,
+              color: 'rgba(96, 223, 199, 0)',
+            },
+            {
+              offset: 0,
+              color: 'rgba(96, 223, 199, .24)',
+            },
+          ]),
         },
-      ],
+      })),
     });
   };
 
-  const handleCpuOptions = (data: Record<string, any>) => {
+  const handleCpuOptions = (data: Record<string, any>, xAxis: string[]) => {
     setCpuOptions({
       xAxis: {
         type: 'category',
-        data: Object.keys(data).map((item) => {
-          return moment(Number(item)).format('YYYY-MM-DD HH:mm:ss');
-        }),
+        data: xAxis,
       },
       tooltip: {
         trigger: 'axis',
@@ -117,7 +269,7 @@ export default () => {
         type: 'value',
       },
       grid: {
-        left: '3%',
+        left: '50px',
         right: '2%',
       },
       dataZoom: [
@@ -131,12 +283,25 @@ export default () => {
           end: 10,
         },
       ],
-      series: [
-        {
-          data: Object.values(data),
-          type: 'line',
+      color: ['#2CB6E0'],
+      series: Object.keys(data).map((key) => ({
+        data: data[key],
+        name: key,
+        type: 'line',
+        smooth: true,
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 1,
+              color: 'rgba(44, 182, 224, 0)',
+            },
+            {
+              offset: 0,
+              color: 'rgba(44, 182, 224, .24)',
+            },
+          ]),
         },
-      ],
+      })),
     });
   };
 
@@ -186,29 +351,49 @@ export default () => {
       .then((res) => {
         if (res.status === 200) {
           const _networkOptions = {};
+          const _networkXAxis = new Set<string>();
           const _jvmOptions = {};
+          const _jvmXAxis = new Set<string>();
           const _cpuOptions = {};
+          const _cpuXAxis = new Set<string>();
 
           res.result.forEach((item: any) => {
             const value = item.data.value;
+            const nodeID = item.data.clusterNodeId;
             if (item.group === 'network') {
+              const _data: any[] = [];
               value.forEach((networkItem: any) => {
-                _networkOptions[networkItem.timeString] = networkItem.value;
+                _data.push(Number(networkItem.value).toFixed(2));
+                _networkXAxis.add(networkItem.timeString);
               });
+              _networkOptions[nodeID] = {
+                _data: _networkOptions[nodeID]
+                  ? _networkOptions[nodeID]._data.concat(_data)
+                  : _data,
+              };
             } else if (item.group === 'cpu') {
               const memoryJvmHeapFree = value.memoryJvmHeapFree;
               const memoryJvmHeapTotal = value.memoryJvmHeapTotal;
-              _jvmOptions[value.timestamp] = (
+              const _value = (
                 ((memoryJvmHeapTotal - memoryJvmHeapFree) / memoryJvmHeapTotal) *
                 100
               ).toFixed(2);
+              if (!_jvmOptions[nodeID]) {
+                _jvmOptions[nodeID] = [];
+              }
+              _jvmXAxis.add(moment(value.timestamp).format('YYYY-MM-DD HH:mm:ss'));
+              _jvmOptions[nodeID].push(_value);
             } else {
-              _cpuOptions[value.timestamp] = value.cpuSystemUsage;
+              if (!_cpuOptions[nodeID]) {
+                _cpuOptions[nodeID] = [];
+              }
+              _cpuXAxis.add(moment(value.timestamp).format('YYYY-MM-DD HH:mm:ss'));
+              _cpuOptions[nodeID].push(Number(value.cpuSystemUsage).toFixed(2));
             }
           });
-          handleNetworkOptions(_networkOptions);
-          handleJVMOptions(_jvmOptions);
-          handleCpuOptions(_cpuOptions);
+          handleNetworkOptions(_networkOptions, [..._networkXAxis.keys()]);
+          handleJVMOptions(_jvmOptions, [..._jvmXAxis.keys()]);
+          handleCpuOptions(_cpuOptions, [..._cpuXAxis.keys()]);
         }
       });
   };
@@ -234,14 +419,24 @@ export default () => {
         ])
         .then((res) => {
           if (res.status === 200) {
-            const _options = {};
+            const _networkOptions = {};
+            const _networkXAxis = new Set<string>();
             res.result.forEach((item: any) => {
               const value = item.data.value;
+              const _data: any[] = [];
+              const nodeID = item.data.clusterNodeId;
               value.forEach((networkItem: any) => {
-                _options[networkItem.timeString] = networkItem.value;
+                _data.push(Number(networkItem.value).toFixed(2));
+                _networkXAxis.add(networkItem.timeString);
               });
+
+              _networkOptions[nodeID] = {
+                _data: _networkOptions[nodeID]
+                  ? _networkOptions[nodeID]._data.concat(_data)
+                  : _data,
+              };
             });
-            handleNetworkOptions(_options);
+            handleNetworkOptions(_networkOptions, [..._networkXAxis.keys()]);
           }
         });
     }
@@ -266,12 +461,18 @@ export default () => {
         ])
         .then((res) => {
           if (res.status === 200) {
-            const _options = {};
+            const _cpuOptions = {};
+            const _cpuXAxis = new Set<string>();
             res.result.forEach((item: any) => {
               const value = item.data.value;
-              _options[value.timestamp] = value.cpuSystemUsage;
+              const nodeID = item.data.clusterNodeId;
+              _cpuXAxis.add(moment(value.timestamp).format('YYYY-MM-DD HH:mm:ss'));
+              if (!_cpuOptions[nodeID]) {
+                _cpuOptions[nodeID] = [];
+              }
+              _cpuOptions[nodeID].push(Number(value.cpuSystemUsage).toFixed(2));
             });
-            handleCpuOptions(_options);
+            handleCpuOptions(_cpuOptions, [..._cpuXAxis.keys()]);
           }
         });
     }
@@ -296,17 +497,25 @@ export default () => {
         ])
         .then((res) => {
           if (res.status === 200) {
-            const _options = {};
+            const _jvmOptions = {};
+            const _jvmXAxis = new Set<string>();
             res.result.forEach((item: any) => {
               const value = item.data.value;
               const memoryJvmHeapFree = value.memoryJvmHeapFree;
               const memoryJvmHeapTotal = value.memoryJvmHeapTotal;
-              _options[value.timestamp] = (
+              const nodeID = item.data.clusterNodeId;
+
+              const _value = (
                 ((memoryJvmHeapTotal - memoryJvmHeapFree) / memoryJvmHeapTotal) *
                 100
               ).toFixed(2);
+              if (!_jvmOptions[nodeID]) {
+                _jvmOptions[nodeID] = [];
+              }
+              _jvmXAxis.add(moment(value.timestamp).format('YYYY-MM-DD HH:mm:ss'));
+              _jvmOptions[nodeID].push(_value);
             });
-            handleJVMOptions(_options);
+            handleJVMOptions(_jvmOptions, [..._jvmXAxis.keys()]);
           }
         });
     }
@@ -361,68 +570,73 @@ export default () => {
             onChange={(value) => {
               setServerId(value);
             }}
-            style={{ width: 300 }}
+            style={{ width: 300, marginBottom: 24 }}
           />
         ) : null}
         <div className={'echarts-items'}>
-          <div className={'echarts-item'}>
-            <Progress
-              type="circle"
-              strokeWidth={8}
-              width={160}
-              percent={topValues.cpu}
-              format={(percent) => (
-                <div>
-                  <div className={'echarts-item-title'}>CPU使用率</div>
-                  <div className={'echarts-item-value'}>{percent}%</div>
-                </div>
-              )}
-            />
-          </div>
-          <div className={'echarts-item'}>
-            <Progress
-              type="circle"
-              strokeWidth={8}
-              width={160}
-              percent={topValues.jvm}
-              format={(percent) => (
-                <div>
-                  <div className={'echarts-item-title'}>JVM内存</div>
-                  <div className={'echarts-item-value'}>{percent}%</div>
-                </div>
-              )}
-            />
-          </div>
-          <div className={'echarts-item'}>
-            <Progress
-              type="circle"
-              strokeWidth={8}
-              width={160}
-              percent={topValues.usage}
-              format={(percent) => (
-                <div>
-                  <div className={'echarts-item-title'}>磁盘占用率</div>
-                  <div className={'echarts-item-value'}>{percent}%</div>
-                </div>
-              )}
-            />
-          </div>
-          <div className={'echarts-item'}>
-            <Progress
-              type="circle"
-              strokeWidth={8}
-              width={160}
-              percent={topValues.systemUsage}
-              format={(percent) => (
-                <div>
-                  <div className={'echarts-item-title'}>系统内存</div>
-                  <div className={'echarts-item-value'}>{percent}%</div>
-                </div>
-              )}
-            />
-          </div>
+          <TopEchartsItemNode title={'CPU使用率'} value={topValues.cpu} />
+          <TopEchartsItemNode title={'JVM内存'} value={topValues.jvm} />
+          <TopEchartsItemNode title={'磁盘占用率'} value={topValues.usage} />
+          <TopEchartsItemNode title={'磁盘占用率'} value={topValues.systemUsage} />
+          {/*<div className={'echarts-item'}>*/}
+          {/*  */}
+          {/*  <Progress*/}
+          {/*    type="circle"*/}
+          {/*    strokeWidth={8}*/}
+          {/*    width={160}*/}
+          {/*    percent={topValues.cpu}*/}
+          {/*    format={(percent) => (*/}
+          {/*      <div>*/}
+          {/*        <div className={'echarts-item-title'}>CPU使用率</div>*/}
+          {/*        <div className={'echarts-item-value'}>{percent}%</div>*/}
+          {/*      </div>*/}
+          {/*    )}*/}
+          {/*  />*/}
+          {/*</div>*/}
+          {/*<div className={'echarts-item'}>*/}
+          {/*  <Progress*/}
+          {/*    type="circle"*/}
+          {/*    strokeWidth={8}*/}
+          {/*    width={160}*/}
+          {/*    percent={topValues.jvm}*/}
+          {/*    format={(percent) => (*/}
+          {/*      <div>*/}
+          {/*        <div className={'echarts-item-title'}>JVM内存</div>*/}
+          {/*        <div className={'echarts-item-value'}>{percent}%</div>*/}
+          {/*      </div>*/}
+          {/*    )}*/}
+          {/*  />*/}
+          {/*</div>*/}
+          {/*<div className={'echarts-item'}>*/}
+          {/*  <Progress*/}
+          {/*    type="circle"*/}
+          {/*    strokeWidth={8}*/}
+          {/*    width={160}*/}
+          {/*    percent={topValues.usage}*/}
+          {/*    format={(percent) => (*/}
+          {/*      <div>*/}
+          {/*        <div className={'echarts-item-title'}>磁盘占用率</div>*/}
+          {/*        <div className={'echarts-item-value'}>{percent}%</div>*/}
+          {/*      </div>*/}
+          {/*    )}*/}
+          {/*  />*/}
+          {/*</div>*/}
+          {/*<div className={'echarts-item'}>*/}
+          {/*  <Progress*/}
+          {/*    type="circle"*/}
+          {/*    strokeWidth={8}*/}
+          {/*    width={160}*/}
+          {/*    percent={topValues.systemUsage}*/}
+          {/*    format={(percent) => (*/}
+          {/*      <div>*/}
+          {/*        <div className={'echarts-item-title'}>系统内存</div>*/}
+          {/*        <div className={'echarts-item-value'}>{percent}%</div>*/}
+          {/*      </div>*/}
+          {/*    )}*/}
+          {/*  />*/}
+          {/*</div>*/}
         </div>
-        <div>
+        <div style={{ marginBottom: 24 }}>
           <DashBoard
             title={'网络流量'}
             ref={NETWORKRef}
@@ -444,7 +658,7 @@ export default () => {
             onParamsChange={getNetworkEcharts}
           />
         </div>
-        <div style={{ display: 'flex' }}>
+        <div style={{ display: 'flex', gap: 24 }}>
           <DashBoard
             title={'CPU使用率趋势'}
             closeInitialParams={true}

+ 8 - 1
src/pages/link/Protocol/FileUpload/index.tsx

@@ -10,6 +10,7 @@ interface Props {
   value: string;
   onChange: (value: string) => void;
   accept?: string;
+  disabled?: boolean;
 }
 
 const FileUpload = connect((props: Props) => {
@@ -43,13 +44,19 @@ const FileUpload = connect((props: Props) => {
           <Input
             style={{ width: 'calc(100% - 100px)' }}
             value={url}
+            disabled={props?.disabled}
             onClick={(e) => {
               e.preventDefault();
               e.stopPropagation();
             }}
             placeholder="请上传文件"
           />
-          <Button shape="round" style={{ width: '100px', textAlign: 'center' }} type="primary">
+          <Button
+            disabled={props?.disabled}
+            shape="round"
+            style={{ width: '100px', textAlign: 'center' }}
+            type="primary"
+          >
             上传jar包
           </Button>
         </Input.Group>

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

@@ -20,9 +20,30 @@ interface Props {
 const Save = (props: Props) => {
   const [data, setData] = useState<ProtocolItem | undefined>(props.data);
   const { permission } = PermissionButton.usePermission('link/Protocol');
+  const [count, setCount] = useState<number>(0);
 
   useEffect(() => {
     setData(props.data);
+    if (props.data?.id) {
+      service
+        .productCount({
+          terms: [
+            {
+              terms: [
+                {
+                  column: 'message_protocol',
+                  value: props.data.id,
+                },
+              ],
+            },
+          ],
+        })
+        .then((resp) => {
+          if (resp.status === 200) {
+            setCount(resp.result);
+          }
+        });
+    }
   }, [props.data]);
 
   const form = createForm({
@@ -179,6 +200,7 @@ const Save = (props: Props) => {
                 'x-decorator-props': {
                   gridSpan: 2,
                 },
+                'x-disabled': !!count,
                 'x-validator': [
                   {
                     required: true,

+ 3 - 0
src/pages/link/Protocol/service.ts

@@ -16,6 +16,9 @@ class Service extends BaseService<ProtocolItem> {
     request(`${this.uri}/${type}`, { method: 'POST', data });
 
   public validator = (id: string) => request(`${SystemConst.API_BASE}/protocol/${id}/exists`);
+
+  public productCount = (data: Record<string, unknown>) =>
+    request(`${SystemConst.API_BASE}/device-product/_count`, { method: 'POST', data });
 }
 
 export default Service;

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

@@ -106,8 +106,8 @@ const Network = () => {
         } else {
           const log = record.cluster?.map(
             (item) =>
-              `${item.configuration?.publicHost || record.configuration?.remoteHost}:${
-                item.configuration?.publicPort || record.configuration?.remotePort
+              `${item.configuration?.publicHost || item.configuration?.remoteHost}:${
+                item.configuration?.publicPort || item.configuration?.remotePort
               }`,
           );
           return (

+ 17 - 7
src/pages/media/Cascade/Save/index.tsx

@@ -385,30 +385,40 @@ const Save = () => {
               <h1>2.配置说明</h1>
               <div>以下配置说明以将本平台数据级联到LiveGBS平台为例。</div>
               <h2>1、上级SIP ID</h2>
-              <div>请填写第三方平台中配置的SIP ID。</div>
+              <div>
+                请填写第三方平台中配置的<b>SIP ID</b>。
+              </div>
               <div className={styles.image}>
                 <Image width="100%" src={img2} />
               </div>
               <h2>2、上级SIP 域</h2>
-              <div>请填写第三方平台中配置的SIP ID域。</div>
+              <div>
+                请填写第三方平台中配置的<b>SIP ID域</b>。
+              </div>
               <div className={styles.image}>
                 <Image width="100%" src={img1} />
               </div>
               <h2>3、上级SIP 地址</h2>
-              <div>请填写第三方平台中配置的SIP ID地址。</div>
+              <div>
+                请填写第三方平台中配置的<b>SIP ID地址</b>。
+              </div>
               <div className={styles.image}>
                 <Image width="100%" src={img3} />
               </div>
               <h2>4、本地SIP ID</h2>
               <div>
-                请填写本地的SIP ID地址。
-                地址由中心编码(8位)、行业编码(2位)、类型编码(3位)和序号(7位)四个码段共20位十
+                请填写本地的<b>SIP ID地址</b>
+                地址由中心编码(8位)、行业编码(2位)、类型编码(3位)和序号(7位)四个码段共20位十
                 进制数字字符构成。详细规则请参见《GB/T28181-2016》中附录D部分。
               </div>
               <h2>5、SIP本地地址</h2>
-              <div>请选择指定的网卡和端口,如有疑问请联系系统运维人员。</div>
+              <div>
+                请选择<b>指定的网卡和端口</b>,如有疑问请联系系统运维人员。
+              </div>
               <h2>6、用户</h2>
-              <div>部分平台有基于用于和接入密码的特殊认证。通常情况下,请填写本地SIP ID值。</div>
+              <div>
+                部分平台有基于用户和接入密码的特殊认证。通常情况下,请填写<b>本地SIP ID</b>值。
+              </div>
               <h2>7、接入密码</h2>
               <div>需与上级平台设置的接入密码一致,用于身份认证。</div>
               <h2>8、厂商/型号/版本号</h2>

+ 11 - 0
src/pages/media/Device/Channel/Live/index.tsx

@@ -27,6 +27,17 @@ const LiveFC = (props: LiveProps) => {
   );
 
   useEffect(() => {
+    if (props.channelId && props.deviceId) {
+      //   查询当前视频是否在录像
+      service.ptzIsRecord(props.deviceId, props.channelId).then((res) => {
+        if (res.code === 200) {
+          setIsRecord(res.result ? 2 : 0);
+        }
+      });
+    }
+  }, [props.channelId, props.deviceId]);
+
+  useEffect(() => {
     if (props.visible) {
       mediaStart('mp4');
     }

+ 3 - 2
src/pages/media/Home/index.tsx

@@ -20,6 +20,7 @@ export default () => {
   const dashBoardUrl = getMenuPathByCode('media/DashBoard');
   const deviceUrl = getMenuPathByCode('media/Device');
   const channelUrl = getMenuPathByCode('media/Device/Channel');
+  const cascadeUrl = getMenuPathByCode('media/Cascade');
   const splitScreenUrl = getMenuPathByCode('media/SplitScreen');
 
   const [visible, setVisible] = useState(false);
@@ -83,8 +84,8 @@ export default () => {
       key: 'CASCADE',
       name: '国标级联',
       english: 'GB CASCADE',
-      auth: !!channelUrl,
-      url: channelUrl,
+      auth: !!cascadeUrl,
+      url: cascadeUrl,
     },
   ];
 

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

@@ -25,6 +25,17 @@ const Log = observer(() => {
     {
       dataIndex: 'state',
       title: '状态',
+      valueType: 'select',
+      valueEnum: {
+        success: {
+          text: '成功',
+          status: 'success',
+        },
+        error: {
+          text: '失败',
+          status: 'error',
+        },
+      },
       renderText: (text: { value: string; text: string }, record) => {
         return (
           <>

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

@@ -25,6 +25,17 @@ const Log = observer(() => {
     {
       dataIndex: 'state',
       title: '状态',
+      valueType: 'select',
+      valueEnum: {
+        success: {
+          text: '成功',
+          status: 'success',
+        },
+        error: {
+          text: '失败',
+          status: 'error',
+        },
+      },
       renderText: (text: { value: string; text: string }, record) => {
         return (
           <>

+ 7 - 3
src/pages/rule-engine/Scene/Save/components/TimeSelect/index.tsx

@@ -49,7 +49,7 @@ export default (props: TimeSelect) => {
     if (props.value && props.options) {
       if (props.value.length >= props.options.length) {
         setCheckedKeys([...props.value, 'null'].map(String));
-        setCheckedNames([...props.options.map((item) => item.label), '全部']);
+        setCheckedNames([...props.options.map((item) => item.label), '每天']);
       } else {
         setCheckedKeys(props.value.map(String));
         const selectedItems = props.options.filter((item) => {
@@ -65,7 +65,7 @@ export default (props: TimeSelect) => {
 
   const menu = (
     <Menu multiple={true} selectedKeys={checkedKeys} onClick={onclick}>
-      <Menu.Item key={'null'}>全部</Menu.Item>
+      <Menu.Item key={'null'}>每天</Menu.Item>
       {props.options &&
         props.options.map((item) => <Menu.Item key={item.value}>{item.label}</Menu.Item>)}
     </Menu>
@@ -106,7 +106,11 @@ export default (props: TimeSelect) => {
       >
         <div className={'time-select-content ellipsis'}>
           {checkedNames.length ? (
-            checkedNames.toString()
+            checkedKeys.includes('null') ? (
+              '每天'
+            ) : (
+              checkedNames.toString()
+            )
           ) : (
             <span style={{ color: 'rgba(0,0,0,.3)' }}>请选择时间</span>
           )}

+ 3 - 1
src/pages/system/Department/Assets/deivce/bind.tsx

@@ -169,7 +169,9 @@ const Bind = observer((props: Props) => {
         rowKey="id"
         search={false}
         gridColumn={2}
-        cardRender={(record) => <ExtraDeviceCard {...record} />}
+        cardRender={(record) => (
+          <ExtraDeviceCard showBindBtn={false} {...record} cardType={'bind'} />
+        )}
         rowSelection={{
           selectedRowKeys: Models.bindKeys,
           onChange: (selectedRowKeys, selectedRows) => {

+ 7 - 0
src/pages/system/Department/Assets/deivce/index.tsx

@@ -13,6 +13,7 @@ import SearchComponent from '@/components/SearchComponent';
 import { ExtraDeviceCard, handlePermissionsMap } from '@/components/ProTableCard/CardItems/device';
 import { ProTableCard } from '@/components';
 import { onlyMessage } from '@/utils/util';
+import { ASSETS_TABS_ENUM, AssetsModel } from '@/pages/system/Department/Assets';
 
 export const service = new Service<DeviceItem>('assets');
 
@@ -34,6 +35,12 @@ export default observer((props: { parentId: string }) => {
   const actionRef = useRef<ActionType>();
 
   const [searchParam, setSearchParam] = useState({});
+
+  useEffect(() => {
+    if (AssetsModel.tabsIndex === ASSETS_TABS_ENUM.Device && actionRef.current) {
+      actionRef.current.reload();
+    }
+  }, [AssetsModel.tabsIndex]);
   /**
    * 解除资产绑定
    */

+ 31 - 8
src/pages/system/Department/Assets/index.tsx

@@ -5,12 +5,26 @@ import ProductCategory from './productCategory';
 import Product from './product';
 import Device from '@/pages/system/Department/Assets/deivce';
 import Member from '@/pages/system/Department/Member';
+import { model } from '@formily/reactive';
+import { observer } from '@formily/react';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
 
 interface AssetsProps {
   parentId: string;
 }
 
-const Assets = (props: AssetsProps) => {
+export enum ASSETS_TABS_ENUM {
+  'ProductCategory' = 'ProductCategory',
+  'Product' = 'Product',
+  'Device' = 'Device',
+  'User' = 'User',
+}
+
+export const AssetsModel = model<{ tabsIndex: string }>({
+  tabsIndex: ASSETS_TABS_ENUM.ProductCategory,
+});
+
+const Assets = observer((props: AssetsProps) => {
   const intl = useIntl();
 
   // 资产类型
@@ -18,32 +32,41 @@ const Assets = (props: AssetsProps) => {
     {
       intlTitle: '1',
       defaultMessage: '产品分类',
-      key: 'ProductCategory',
+      key: ASSETS_TABS_ENUM.ProductCategory,
       components: ProductCategory,
     },
     {
       intlTitle: '2',
       defaultMessage: '产品',
-      key: 'Product',
+      key: ASSETS_TABS_ENUM.Product,
       components: Product,
     },
     {
       intlTitle: '3',
       defaultMessage: '设备',
-      key: 'Device',
+      key: ASSETS_TABS_ENUM.Device,
       components: Device,
     },
     {
       intlTitle: '4',
       defaultMessage: '用户',
-      key: 'User',
+      key: ASSETS_TABS_ENUM.User,
       components: Member,
     },
   ];
 
   return (
-    <div>
-      <Tabs defaultActiveKey="ProductCategory">
+    <div style={{ position: 'relative' }}>
+      <div style={{ position: 'absolute', top: 12, left: 270 }}>
+        <ExclamationCircleOutlined style={{ marginRight: 12 }} />
+        部门拥有的资产为所有类型资产的并集
+      </div>
+      <Tabs
+        accessKey={AssetsModel.tabsIndex}
+        onChange={(key) => {
+          AssetsModel.tabsIndex = key;
+        }}
+      >
         {TabsArray.map((item) => (
           <Tabs.TabPane
             tab={intl.formatMessage({
@@ -58,6 +81,6 @@ const Assets = (props: AssetsProps) => {
       </Tabs>
     </div>
   );
-};
+});
 
 export default Assets;

+ 3 - 1
src/pages/system/Department/Assets/product/bind.tsx

@@ -130,7 +130,9 @@ const Bind = observer((props: Props) => {
         }}
         request={(params) => service.queryProductList(params)}
         params={searchParam}
-        cardRender={(record) => <ExtraProductCard {...record} />}
+        cardRender={(record) => (
+          <ExtraProductCard showBindBtn={false} {...record} cardType={'bind'} />
+        )}
       />
     </Modal>
   );

+ 7 - 0
src/pages/system/Department/Assets/product/index.tsx

@@ -16,6 +16,7 @@ import {
 } from '@/components/ProTableCard/CardItems/product';
 import { ProTableCard } from '@/components';
 import { onlyMessage } from '@/utils/util';
+import { ASSETS_TABS_ENUM, AssetsModel } from '@/pages/system/Department/Assets';
 
 export const service = new Service<ProductItem>('assets');
 
@@ -25,6 +26,12 @@ export default observer((props: { parentId: string }) => {
 
   const [searchParam, setSearchParam] = useState({});
 
+  useEffect(() => {
+    if (AssetsModel.tabsIndex === ASSETS_TABS_ENUM.Product && actionRef.current) {
+      actionRef.current.reload();
+    }
+  }, [AssetsModel.tabsIndex]);
+
   /**
    * 解除资产绑定
    */

+ 7 - 1
src/pages/system/Department/Assets/productCategory/index.tsx

@@ -13,6 +13,7 @@ import Bind from './bind';
 import SearchComponent from '@/components/SearchComponent';
 import { difference } from 'lodash';
 import { onlyMessage } from '@/utils/util';
+import { AssetsModel, ASSETS_TABS_ENUM } from '@/pages/system/Department/Assets';
 
 export const service = new Service<ProductCategoryItem>('assets');
 
@@ -34,6 +35,12 @@ export default observer((props: { parentId: string }) => {
   const actionRef = useRef<ActionType>();
   const [searchParam, setSearchParam] = useState({});
 
+  useEffect(() => {
+    if (AssetsModel.tabsIndex === ASSETS_TABS_ENUM.ProductCategory && actionRef.current) {
+      actionRef.current.reload();
+    }
+  }, [AssetsModel.tabsIndex]);
+
   /**
    * 解除资产绑定
    */
@@ -238,7 +245,6 @@ export default observer((props: { parentId: string }) => {
           selectedRowKeys: Models.unBindKeys,
           onSelect: (record, selected, selectedRows) => {
             const keys = getTableKeys(selected ? selectedRows : [record]);
-            console.log(record, selected, selectedRows);
             if (selected) {
               const _map = new Map();
               keys.forEach((k) => {

+ 28 - 7
src/pages/system/Department/Tree/tree.tsx

@@ -1,4 +1,4 @@
-import { Button, Input, Tree } from 'antd';
+import { Button, Input, Tooltip, Tree } from 'antd';
 import {
   DeleteOutlined,
   EditOutlined,
@@ -16,6 +16,7 @@ import { ISchema } from '@formily/json-schema';
 import { useLocation } from 'umi';
 import { DepartmentItem } from '@/pages/system/Department/typings';
 import { onlyMessage } from '@/utils/util';
+import classnames from 'classnames';
 
 interface TreeProps {
   onSelect: (id: string) => void;
@@ -50,6 +51,7 @@ export default (props: TreeProps) => {
   const [visible, setVisible] = useState(false);
   const [data, setData] = useState<any>();
   const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
+  const [showToolIndex, setShowToolIndex] = useState('');
   const searchKey = useRef('');
 
   const location = useLocation();
@@ -72,6 +74,9 @@ export default (props: TreeProps) => {
 
     if (resp.status === 200) {
       setTreeData(resp.result);
+      if (resp.result && resp.result.length) {
+        setKeys([resp.result[0].id]);
+      }
     }
   };
 
@@ -220,9 +225,25 @@ export default (props: TreeProps) => {
             }}
             titleRender={(nodeData: any) => {
               return (
-                <div>
-                  <span>{nodeData.name}</span>
-                  <span>
+                <div
+                  className={classnames('tree-node-name')}
+                  onMouseEnter={() => {
+                    setShowToolIndex(nodeData.id);
+                  }}
+                  onMouseLeave={() => {
+                    setShowToolIndex('');
+                  }}
+                >
+                  <span className={'tree-node-name--title'}>
+                    <Tooltip title={nodeData.name}>
+                      <span className={'ellipsis'}>{nodeData.name}</span>
+                    </Tooltip>
+                  </span>
+                  <span
+                    className={classnames('tree-node-name--btn', {
+                      'show-btn': nodeData.id === showToolIndex,
+                    })}
+                  >
                     <PermissionButton
                       key="editable"
                       tooltip={{
@@ -232,7 +253,7 @@ export default (props: TreeProps) => {
                         }),
                       }}
                       isPermission={permission.update}
-                      style={{ padding: '0 0 0 6px' }}
+                      style={{ padding: '0 0 0 10px', height: 24 }}
                       type="link"
                       onClick={(e) => {
                         e.stopPropagation();
@@ -246,7 +267,7 @@ export default (props: TreeProps) => {
                     </PermissionButton>
                     <PermissionButton
                       key={'addChildren'}
-                      style={{ padding: '0 0 0 6px' }}
+                      style={{ padding: '0 0 0 10px', height: 24 }}
                       tooltip={{
                         title: intl.formatMessage({
                           id: 'pages.system.department.option.add',
@@ -269,7 +290,7 @@ export default (props: TreeProps) => {
                     <PermissionButton
                       type="link"
                       key="delete"
-                      style={{ padding: '0 0 0 6px' }}
+                      style={{ padding: '0 0 0 10px', height: 24 }}
                       popConfirm={{
                         title: intl.formatMessage({
                           id: 'pages.system.role.option.delete',

+ 24 - 2
src/pages/system/Department/index.less

@@ -9,7 +9,7 @@
 
     .department-left {
       display: flex;
-      flex-basis: 300px;
+      width: 300px;
       height: 100%;
 
       .border-left {
@@ -41,11 +41,33 @@
       .left-tree-body {
         flex: 1 1 auto;
         overflow-y: auto;
+
+        .tree-node-name {
+          display: flex;
+
+          .tree-node-name--title {
+            display: flex;
+            flex-grow: 1;
+            width: 0;
+          }
+
+          .tree-node-name--btn {
+            width: 80px;
+            opacity: 0;
+            transition: opacity 0.3s;
+
+            &.show-btn {
+              opacity: 1;
+            }
+          }
+        }
       }
     }
 
     .department-right {
-      flex: 1 1 auto;
+      display: flex;
+      flex-grow: 1;
+      width: 0;
       padding-left: 24px;
     }
   }

+ 15 - 9
src/pages/system/Menu/Detail/buttons.tsx

@@ -10,6 +10,7 @@ import Permission from '@/pages/system/Menu/components/permission';
 import { useRequest } from '@@/plugin-request/request';
 import { PermissionButton } from '@/components';
 import { onlyMessage } from '@/utils/util';
+import { debounce } from 'lodash';
 
 type ButtonsProps = {
   data: MenuItem;
@@ -52,15 +53,15 @@ export default (props: ButtonsProps) => {
     setDisabled(false);
   };
 
-  // const filterThree = (e: any) => {
-  //   const _data: any = {
-  //     paging: false,
-  //   };
-  //   if (e.target.value) {
-  //     _data.terms = [{ column: 'name', value: e.target.value }];
-  //   }
-  //   queryPermissions(_data);
-  // };
+  const filterThree = (e: any) => {
+    const _data: any = {
+      paging: false,
+    };
+    if (e.target.value) {
+      _data.terms = [{ column: 'name$like', value: `%${e.target.value}%` }];
+    }
+    queryPermissions(_data);
+  };
 
   /**
    * 更新菜单信息
@@ -338,6 +339,11 @@ export default (props: ButtonsProps) => {
             })}
             required={true}
           >
+            <Input
+              onChange={debounce(filterThree, 500)}
+              style={{ width: 300, marginBottom: 12 }}
+              placeholder={'请输入权限名称'}
+            />
             <Form.Item name="permissions" rules={[{ required: true, message: '请选择权限' }]}>
               <Permission
                 title={intl.formatMessage({

+ 26 - 23
src/pages/system/Menu/Detail/edit.tsx

@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
 import { service } from '@/pages/system/Menu';
 import { useHistory, useRequest } from 'umi';
 import type { MenuItem } from '@/pages/system/Menu/typing';
-// import { debounce } from 'lodash';
+import { debounce } from 'lodash';
 import Title from '../components/Title';
 import Icons from '../components/Icons';
 import { QuestionCircleFilled } from '@ant-design/icons';
@@ -91,15 +91,15 @@ export default (props: EditProps) => {
     /* eslint-disable */
   }, []);
 
-  // const filterThree = (e: any) => {
-  //   const _data: any = {
-  //     paging: false,
-  //   };
-  //   if (e.target.value) {
-  //     _data.terms = [{ column: 'name', value: e.target.value }];
-  //   }
-  //   queryPermissions(_data);
-  // };
+  const filterThree = (e: any) => {
+    const _data: any = {
+      paging: false,
+    };
+    if (e.target.value) {
+      _data.terms = [{ column: 'name$like', value: `%${e.target.value}%` }];
+    }
+    queryPermissions(_data);
+  };
 
   useEffect(() => {
     if (form) {
@@ -123,7 +123,7 @@ export default (props: EditProps) => {
         <Card>
           <Title title={'基本信息'} />
           <Row>
-            <Col span={3}>
+            <Col flex={'186px'}>
               <Form.Item
                 name={'icon'}
                 label={'菜单图标'}
@@ -138,7 +138,7 @@ export default (props: EditProps) => {
                 <Icons />
               </Form.Item>
             </Col>
-            <Col span={21}>
+            <Col flex="auto">
               <Row gutter={[24, 0]}>
                 <Col span={12}>
                   <Form.Item
@@ -293,19 +293,22 @@ export default (props: EditProps) => {
                     id: 'page.system.menu.permissions',
                     defaultMessage: '权限',
                   })}
-                  name="permissions"
                 >
-                  {/*<Input disabled={disabled} onChange={debounce(filterThree, 300)} style={{ width: 300 }}/>*/}
-                  {/*<Form.Item name='permissions'>*/}
-                  <Permission
-                    title={intl.formatMessage({
-                      id: 'page.system.menu.permissions.operate',
-                      defaultMessage: '操作权限',
-                    })}
-                    // disabled={disabled}
-                    data={permissions}
+                  <Input
+                    onChange={debounce(filterThree, 500)}
+                    style={{ width: 300, marginBottom: 12 }}
+                    placeholder={'请输入权限名称'}
                   />
-                  {/*</Form.Item>*/}
+                  <Form.Item name="permissions" noStyle>
+                    <Permission
+                      title={intl.formatMessage({
+                        id: 'page.system.menu.permissions.operate',
+                        defaultMessage: '操作权限',
+                      })}
+                      // disabled={disabled}
+                      data={permissions}
+                    />
+                  </Form.Item>
                 </Form.Item>
               </Col>
             </Row>

+ 1 - 4
src/pages/system/Permission/Save/index.tsx

@@ -219,10 +219,7 @@ const Save = (props: Props) => {
           add: {
             type: 'void',
             'x-component': 'ArrayTable.Addition',
-            title: intl.formatMessage({
-              id: 'pages.system.permission.add',
-              defaultMessage: '添加条目',
-            }),
+            title: '添加',
           },
         },
       },