lind 3 лет назад
Родитель
Сommit
38407b777b
31 измененных файлов с 561 добавлено и 124 удалено
  1. 22 3
      src/app.tsx
  2. 1 1
      src/components/AMapComponent/APILoader.ts
  3. 4 3
      src/components/AMapComponent/amap.tsx
  4. 39 5
      src/components/DashBoard/header.tsx
  5. 33 20
      src/components/DashBoard/timePicker.tsx
  6. 117 0
      src/components/FormItems/MetadataJsonInput/index.tsx
  7. 97 0
      src/components/GeoPoint/AMap.tsx
  8. 13 0
      src/components/GeoPoint/index.less
  9. 55 0
      src/components/GeoPoint/index.tsx
  10. 1 0
      src/components/GeoPoint/style.js
  11. 2 0
      src/components/index.ts
  12. 2 2
      src/pages/Northbound/AliCloud/index.tsx
  13. 2 2
      src/pages/Northbound/DuerOS/index.tsx
  14. 1 0
      src/pages/device/DashBoard/index.tsx
  15. 2 2
      src/pages/device/Instance/Detail/Diagnose/Status/DiagnosticAdvice.tsx
  16. 2 2
      src/pages/device/Instance/Detail/Diagnose/Status/ManualInspection.tsx
  17. 30 4
      src/pages/device/Instance/Detail/Functions/AdvancedMode.tsx
  18. 14 4
      src/pages/device/Instance/Detail/Functions/form.tsx
  19. 2 2
      src/pages/device/Instance/Detail/Functions/index.less
  20. 82 32
      src/pages/device/Instance/Detail/Functions/index.tsx
  21. 1 0
      src/pages/device/Instance/Detail/index.tsx
  22. 3 3
      src/pages/link/AccessConfig/Detail/Access/index.tsx
  23. 3 3
      src/pages/link/AccessConfig/Detail/Cloud/index.tsx
  24. 2 2
      src/pages/link/AccessConfig/Detail/Media/index.tsx
  25. 1 0
      src/pages/link/DashBoard/index.tsx
  26. 1 0
      src/pages/media/DashBoard/index.tsx
  27. 4 3
      src/pages/media/Device/Channel/index.tsx
  28. 1 0
      src/pages/rule-engine/Alarm/Configuration/index.tsx
  29. 1 0
      src/pages/rule-engine/DashBoard/index.tsx
  30. 21 29
      src/pages/system/Menu/Detail/edit.tsx
  31. 2 2
      src/pages/system/Platforms/save.tsx

+ 22 - 3
src/app.tsx

@@ -13,7 +13,13 @@ import type { RequestOptionsInit } from 'umi-request';
 import ReconnectingWebSocket from 'reconnecting-websocket';
 import SystemConst from '@/utils/const';
 import { service as MenuService } from '@/pages/system/Menu';
-import getRoutes, { extraRouteArr, getMenus, handleRoutes, saveMenusCache } from '@/utils/menu';
+import getRoutes, {
+  extraRouteArr,
+  getMenuPathByCode,
+  getMenus,
+  handleRoutes,
+  saveMenusCache,
+} from '@/utils/menu';
 import { AIcon } from '@/components';
 import React from 'react';
 
@@ -236,8 +242,21 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
       // content: initialState?.currentUser?.name,
     },
     itemRender: (route, _, routes) => {
-      const chilck = routes.indexOf(route) > 1;
-      return chilck && route.path !== '/iot/rule-engine/Alarm' ? (
+      console.log(route);
+      const isToParentUrl = getMenuPathByCode('notice');
+      const chilck = routes.indexOf(route) !== 0;
+      const goto = routes.some((item) => {
+        if (!route.path.includes('iot')) {
+          return routes.indexOf(route) <= 1;
+        } else {
+          if (route.path.includes('notice')) {
+            return item.path.indexOf(isToParentUrl) > -1;
+          } else {
+            return routes.indexOf(route) > 1;
+          }
+        }
+      });
+      return chilck && goto && route.path !== '/iot/rule-engine/Alarm' ? (
         <Link to={route.path}>{route.breadcrumbName}</Link>
       ) : (
         <span>{route.breadcrumbName}</span>

+ 1 - 1
src/components/AMapComponent/APILoader.ts

@@ -19,7 +19,7 @@ export const getAMapPlugins = (type: string, map: any, callback: Function) => {
   }
 };
 
-export const getAMapUiPromise = (version: string = '1.1'): Promise<any> => {
+export const getAMapUiPromise = (version: string = '1.0'): Promise<any> => {
   if ((window as any).AMapUI) {
     return Promise.resolve();
   }

+ 4 - 3
src/components/AMapComponent/amap.tsx

@@ -12,7 +12,7 @@ interface AMapProps extends Omit<MapProps, 'amapkey' | 'useAMapUI'> {
 }
 
 export default (props: AMapProps) => {
-  const { style, className, events, onInstanceCreated, ...extraProps } = props;
+  const { style, className, events, onInstanceCreated, children, ...extraProps } = props;
 
   const [uiLoading, setUiLoading] = useState(false);
 
@@ -36,12 +36,13 @@ export default (props: AMapProps) => {
     }
   };
 
+  console.log(isOpenUi, uiLoading);
+
   return (
     <div style={style || { width: '100%', height: '100%' }} className={className}>
       {amapKey ? (
         // @ts-ignore
         <Map
-          version={'2.0'}
           amapkey={amapKey}
           zooms={[3, 20]}
           events={
@@ -56,7 +57,7 @@ export default (props: AMapProps) => {
           }
           {...extraProps}
         >
-          {isOpenUi ? (uiLoading ? props.children : null) : props.children}
+          {isOpenUi ? (uiLoading ? children : null) : children}
         </Map>
       ) : (
         <Empty description={'请配置高德地图key'} />

+ 39 - 5
src/components/DashBoard/header.tsx

@@ -1,8 +1,8 @@
-import React, { forwardRef, useImperativeHandle, useRef } from 'react';
+import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
 import Style from './index.less';
-import { Col, Form, Row } from 'antd';
+import { Col, Form, Radio, Row } from 'antd';
 import type { TimeType } from './timePicker';
-import RangePicker from './timePicker';
+import RangePicker, { TimeKey } from './timePicker';
 
 export interface HeaderProps {
   title: string;
@@ -22,11 +22,14 @@ export interface HeaderProps {
   closeInitialParams?: boolean;
   defaultTime?: TimeType;
   showTime?: boolean;
+  showTimeTool?: boolean;
 }
 
 export default forwardRef((props: HeaderProps, ref) => {
   const [form] = Form.useForm();
+  const [radioValue, setRadioValue] = useState<TimeType | undefined>(undefined);
   const isCloseInitial = useRef<boolean>(false);
+  const pickerRef = useRef<any>(null);
 
   const change = async (data: any) => {
     if (props.onParamsChange) {
@@ -38,6 +41,10 @@ export default forwardRef((props: HeaderProps, ref) => {
     getValues: form.getFieldsValue,
   }));
 
+  useEffect(() => {
+    setRadioValue(props.defaultTime || TimeKey.today);
+  }, []);
+
   return (
     <div className={Style.header}>
       <div className={Style.title}>{props.title}</div>
@@ -62,10 +69,37 @@ export default forwardRef((props: HeaderProps, ref) => {
                 <Form.Item name={props.extraParams.key}>{props.extraParams.Children}</Form.Item>
               </Col>
             )}
+            {}
             <Col span={props.extraParams ? 18 : 24}>
-              <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
+              <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
+                {props.showTimeTool ? (
+                  <Radio.Group
+                    defaultValue="day"
+                    buttonStyle="solid"
+                    value={radioValue}
+                    onChange={(e) => {
+                      setRadioValue(e.target.value);
+                      if (pickerRef.current) {
+                        pickerRef.current.timeChange(e.target.value);
+                      }
+                    }}
+                  >
+                    <Radio.Button value={TimeKey.today}>当天</Radio.Button>
+                    <Radio.Button value={TimeKey.week}>近一周</Radio.Button>
+                    <Radio.Button value={TimeKey.month}>近一月</Radio.Button>
+                    <Radio.Button value={TimeKey.year}>近一年</Radio.Button>
+                  </Radio.Group>
+                ) : null}
                 <Form.Item noStyle name={'time'}>
-                  <RangePicker defaultTime={props.defaultTime} showTime={props.showTime} />
+                  <RangePicker
+                    ref={pickerRef}
+                    defaultTime={props.defaultTime}
+                    showTime={props.showTime}
+                    showTimeTool={props.showTimeTool}
+                    pickerTimeChange={() => {
+                      setRadioValue(undefined);
+                    }}
+                  />
                 </Form.Item>
               </div>
             </Col>

+ 33 - 20
src/components/DashBoard/timePicker.tsx

@@ -1,7 +1,7 @@
 import type { DatePickerProps } from 'antd';
 import { DatePicker, Radio } from 'antd';
 import moment from 'moment';
-import { useEffect, useState } from 'react';
+import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
 
 export enum TimeKey {
   'today' = 'today',
@@ -18,7 +18,9 @@ interface ExtraTimePickerProps extends Omit<DatePickerProps, 'onChange' | 'value
   onChange?: (data: ValueType) => void;
   value?: ValueType;
   defaultTime?: TimeType;
+  pickerTimeChange?: () => void;
   showTime?: boolean;
+  showTimeTool?: boolean;
 }
 
 export const getTimeByType = (type: TimeType) => {
@@ -34,7 +36,7 @@ export const getTimeByType = (type: TimeType) => {
   }
 };
 
-export default (props: ExtraTimePickerProps) => {
+export default forwardRef((props: ExtraTimePickerProps, ref) => {
   const [radioValue, setRadioValue] = useState<TimeType | undefined>(undefined);
 
   const { value, onChange, ...extraProps } = props;
@@ -56,6 +58,10 @@ export default (props: ExtraTimePickerProps) => {
     change(startTime, endTime, type);
   };
 
+  useImperativeHandle(ref, () => ({
+    timeChange,
+  }));
+
   useEffect(() => {
     timeChange(props.defaultTime || TimeKey.today);
   }, []);
@@ -78,26 +84,33 @@ export default (props: ExtraTimePickerProps) => {
             if (rangeValue && rangeValue.length === 2) {
               change(rangeValue[0]!.valueOf(), rangeValue[1]!.valueOf(), radioValue!);
             }
+            if (props.pickerTimeChange) {
+              props.pickerTimeChange();
+            }
           }}
-          renderExtraFooter={() => (
-            <div style={{ padding: '12px 0' }}>
-              <Radio.Group
-                defaultValue="day"
-                buttonStyle="solid"
-                value={radioValue}
-                onChange={(e) => {
-                  timeChange(e.target.value);
-                }}
-              >
-                <Radio.Button value={TimeKey.today}>当天</Radio.Button>
-                <Radio.Button value={TimeKey.week}>近一周</Radio.Button>
-                <Radio.Button value={TimeKey.month}>近一月</Radio.Button>
-                <Radio.Button value={TimeKey.year}>近一年</Radio.Button>
-              </Radio.Group>
-            </div>
-          )}
+          renderExtraFooter={
+            props.showTimeTool !== true
+              ? () => (
+                  <div style={{ padding: '12px 0' }}>
+                    <Radio.Group
+                      defaultValue="day"
+                      buttonStyle="solid"
+                      value={radioValue}
+                      onChange={(e) => {
+                        timeChange(e.target.value);
+                      }}
+                    >
+                      <Radio.Button value={TimeKey.today}>当天</Radio.Button>
+                      <Radio.Button value={TimeKey.week}>近一周</Radio.Button>
+                      <Radio.Button value={TimeKey.month}>近一月</Radio.Button>
+                      <Radio.Button value={TimeKey.year}>近一年</Radio.Button>
+                    </Radio.Group>
+                  </div>
+                )
+              : undefined
+          }
         />
       }
     </>
   );
-};
+});

+ 117 - 0
src/components/FormItems/MetadataJsonInput/index.tsx

@@ -0,0 +1,117 @@
+import { Input, Modal } from 'antd';
+import { EnvironmentOutlined } from '@ant-design/icons';
+import { useEffect, useState } from 'react';
+import MonacoEditor from 'react-monaco-editor';
+import { isObject } from 'lodash';
+
+type MetaDataJsonInputProps = {
+  json: Record<string, any>;
+  value?: string;
+  onChange?: (value: string) => void;
+};
+
+export const MetaDataJsonHandle = (data: any): Record<string, any> => {
+  const _JSON = {};
+
+  if (isObject(data)) {
+    const type = (data as any).valueType.type;
+    const id = (data as any).id;
+
+    switch (type) {
+      case 'object':
+        _JSON[id] = MetaDataJsonHandle((data as any)['json']['properties'][0]);
+        break;
+      case 'array':
+        _JSON[id] = [];
+        break;
+      case 'int':
+      case 'long':
+      case 'float':
+      case 'double':
+        _JSON[id] = 0;
+        break;
+      case 'boolean':
+        _JSON[id] = false;
+        break;
+      default:
+        _JSON[id] = '';
+        break;
+    }
+  }
+
+  return _JSON;
+};
+
+export default (props: MetaDataJsonInputProps) => {
+  const [value, setValue] = useState(props.value || '');
+  const [visible, setVisible] = useState(false);
+  const [monacoValue, setMonacoValue] = useState<string>('');
+
+  const onChange = (data: string) => {
+    if (props.onChange) {
+      const newData = data.replace(/[ ]/g, '');
+      props.onChange(newData);
+    }
+  };
+
+  const editorDidMountHandle = (editor: any) => {
+    editor.onDidContentSizeChange?.(() => {
+      editor.getAction('editor.action.formatDocument').run();
+    });
+  };
+
+  useEffect(() => {
+    setValue(props.value || '');
+  }, [props.value]);
+
+  useEffect(() => {
+    if (props.json) {
+      const _json = MetaDataJsonHandle(props.json);
+      onChange(JSON.stringify(_json));
+    }
+  }, [props.json]);
+
+  return (
+    <>
+      <Input
+        addonAfter={
+          <EnvironmentOutlined
+            onClick={() => {
+              setMonacoValue(value);
+              setVisible(true);
+            }}
+          />
+        }
+        value={value}
+        onChange={(e) => {
+          setValue(e.target.value);
+          onChange(e.target.value);
+        }}
+      />
+      <Modal
+        visible={visible}
+        title={'编辑'}
+        onOk={() => {
+          onChange(monacoValue);
+          setVisible(false);
+        }}
+        onCancel={() => {
+          setVisible(false);
+        }}
+        width={700}
+      >
+        <MonacoEditor
+          width={'100%'}
+          height={400}
+          theme="vs-dark"
+          language={'json'}
+          value={monacoValue}
+          onChange={(newValue) => {
+            setMonacoValue(newValue);
+          }}
+          editorDidMount={editorDidMountHandle}
+        />
+      </Modal>
+    </>
+  );
+};

+ 97 - 0
src/components/GeoPoint/AMap.tsx

@@ -0,0 +1,97 @@
+import { AMap } from '@/components';
+import usePlaceSearch from '@/components/AMapComponent/hooks/PlaceSearch';
+import { Input, Modal, Select } from 'antd';
+import { debounce } from 'lodash';
+import { Marker } from 'react-amap';
+import { useEffect, useState } from 'react';
+import './style';
+
+interface Props {
+  value: any;
+  close: () => void;
+  ok: (data: any) => void;
+}
+
+type MarkerPointType = {
+  longitude: number;
+  latitude: number;
+};
+
+export default (props: Props) => {
+  const [markerCenter, setMarkerCenter] = useState<MarkerPointType>({ longitude: 0, latitude: 0 });
+  const [map, setMap] = useState<any>(null);
+
+  const { data, search } = usePlaceSearch(map);
+
+  const [value, setValue] = useState<any>(props.value);
+
+  const onSearch = (value1: string) => {
+    search(value1);
+  };
+
+  useEffect(() => {
+    setValue(props?.value || '');
+    const list = (props?.value || '').split(',') || [];
+    if (!!props.value && list.length === 2) {
+      setMarkerCenter({
+        longitude: list[0],
+        latitude: list[1],
+      });
+    }
+  }, [props.value]);
+  console.log(markerCenter);
+
+  return (
+    <Modal
+      visible
+      title="地理位置"
+      width={'55vw'}
+      onCancel={() => props.close()}
+      onOk={() => {
+        props.ok(value);
+      }}
+    >
+      <div className={'map-search-warp'}>
+        <div className={'map-search-select'}>
+          <Select
+            showSearch
+            options={data}
+            filterOption={false}
+            onSearch={debounce(onSearch, 300)}
+            style={{ width: '100%', marginBottom: 10 }}
+            onSelect={(key: string, node: any) => {
+              setValue(key);
+              setMarkerCenter({
+                longitude: node.lnglat.lng,
+                latitude: node.lnglat.lat,
+              });
+            }}
+          />
+          <Input value={value} readOnly />
+        </div>
+        <AMap
+          style={{
+            height: 500,
+            width: '100%',
+          }}
+          center={markerCenter.longitude ? markerCenter : undefined}
+          onInstanceCreated={setMap}
+          events={{
+            click: (e: any) => {
+              setValue(`${e.lnglat.lng},${e.lnglat.lat}`);
+              setMarkerCenter({
+                longitude: e.lnglat.lng,
+                latitude: e.lnglat.lat,
+              });
+            },
+          }}
+        >
+          {markerCenter.longitude ? (
+            // @ts-ignore
+            <Marker kye={'marker'} position={markerCenter} />
+          ) : null}
+        </AMap>
+      </div>
+    </Modal>
+  );
+};

+ 13 - 0
src/components/GeoPoint/index.less

@@ -0,0 +1,13 @@
+.map-search-warp {
+  position: relative;
+
+  .map-search-select {
+    position: absolute;
+    top: 5px;
+    right: 5px;
+    z-index: 9;
+    width: 300px;
+    padding: 10px;
+    background-color: #fff;
+  }
+}

+ 55 - 0
src/components/GeoPoint/index.tsx

@@ -0,0 +1,55 @@
+import { EnvironmentOutlined } from '@ant-design/icons';
+import { Input } from 'antd';
+import { useEffect, useState } from 'react';
+import AMap from './AMap';
+
+interface Props {
+  value?: string;
+  onChange?: (value: string) => void;
+}
+
+const GeoComponent = (props: Props) => {
+  const [visible, setVisible] = useState<boolean>(false);
+  const [value, setValue] = useState<any>(props?.value);
+
+  useEffect(() => {
+    setValue(props?.value);
+  }, [props.value]);
+
+  return (
+    <div>
+      <Input
+        addonAfter={
+          <EnvironmentOutlined
+            onClick={() => {
+              setVisible(true);
+            }}
+          />
+        }
+        value={value}
+        onChange={(e) => {
+          // setValue(e.target.value);
+          if (props.onChange) {
+            props.onChange(e.target.value);
+          }
+        }}
+      />
+      {visible && (
+        <AMap
+          value={value}
+          close={() => {
+            setVisible(false);
+          }}
+          ok={(param) => {
+            if (props.onChange) {
+              props.onChange(param);
+            }
+            // setValue(param);
+            setVisible(false);
+          }}
+        />
+      )}
+    </div>
+  );
+};
+export default GeoComponent;

+ 1 - 0
src/components/GeoPoint/style.js

@@ -0,0 +1 @@
+import './index.less';

+ 2 - 0
src/components/index.ts

@@ -12,3 +12,5 @@ export { default as TitleComponent } from './TitleComponent';
 export { default as AMap } from './AMapComponent/amap';
 export { default as PathSimplifier } from './AMapComponent/PathSimplifier';
 export { default as Empty } from './Empty';
+export { default as GeoPoint } from './GeoPoint';
+export { default as MetadataJsonInput } from './FormItems/MetadataJsonInput';

+ 2 - 2
src/pages/Northbound/AliCloud/index.tsx

@@ -7,7 +7,7 @@ import { PermissionButton, ProTableCard } from '@/components';
 import {
   DeleteOutlined,
   EditOutlined,
-  ExclamationCircleFilled,
+  InfoCircleOutlined,
   PlayCircleOutlined,
   PlusOutlined,
   StopOutlined,
@@ -196,7 +196,7 @@ const AliCloud = () => {
             backgroundColor: '#f6f6f6',
           }}
         >
-          <ExclamationCircleFilled style={{ marginRight: 10 }} />
+          <InfoCircleOutlined style={{ marginRight: 10 }} />
           将平台产品与设备数据通过API的方式同步到阿里云物联网平台
         </div>
       </div>

+ 2 - 2
src/pages/Northbound/DuerOS/index.tsx

@@ -7,7 +7,7 @@ import {
   CloseCircleOutlined,
   DeleteOutlined,
   EditOutlined,
-  ExclamationCircleFilled,
+  InfoCircleOutlined,
   PlayCircleOutlined,
   PlusOutlined,
 } from '@ant-design/icons';
@@ -230,7 +230,7 @@ export default () => {
             backgroundColor: '#f6f6f6',
           }}
         >
-          <ExclamationCircleFilled style={{ marginRight: 10 }} />
+          <InfoCircleOutlined style={{ marginRight: 10 }} />
           将平台产品通过API的方式同步DuerOS平台
         </div>
       </div>

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

@@ -416,6 +416,7 @@ const DeviceBoard = () => {
           height={500}
           defaultTime={'week'}
           showTime={true}
+          showTimeTool={true}
           onParamsChange={getEcharts}
         />
         {amapKey && (

+ 2 - 2
src/pages/device/Instance/Detail/Diagnose/Status/DiagnosticAdvice.tsx

@@ -1,5 +1,5 @@
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { ExclamationCircleFilled } from '@ant-design/icons';
+import { InfoCircleOutlined } from '@ant-design/icons';
 import { Badge, Modal } from 'antd';
 import styles from './index.less';
 
@@ -42,7 +42,7 @@ const DiagnosticAdvice = (props: Props) => {
     >
       <div className={styles.advice}>
         <div className={styles.alert}>
-          <ExclamationCircleFilled style={{ marginRight: 10 }} />
+          <InfoCircleOutlined style={{ marginRight: 10 }} />
           所有诊断均无异常但设备任未上线,请检查以下内容
         </div>
         {(data?.product || []).map((item: any) => (

+ 2 - 2
src/pages/device/Instance/Detail/Diagnose/Status/ManualInspection.tsx

@@ -4,7 +4,7 @@ import type { ISchema } from '@formily/json-schema';
 import { Form, FormGrid, FormItem, Input, Password, PreviewText } from '@formily/antd';
 import { Modal } from 'antd';
 import styles from './index.less';
-import { ExclamationCircleFilled } from '@ant-design/icons';
+import { InfoCircleOutlined } from '@ant-design/icons';
 
 const componentMap = {
   string: 'Input',
@@ -132,7 +132,7 @@ const ManualInspection = (props: Props) => {
       visible
     >
       <div className={styles.alert}>
-        <ExclamationCircleFilled style={{ marginRight: 10 }} />
+        <InfoCircleOutlined style={{ marginRight: 10 }} />
         {data.type === 'product'
           ? `当前填写的数据将与产品-设备接入配置中的${data.data.name}数据进行比对`
           : `当前填写的数据将与设备-实例信息配置中的${data.data.name}数据进行比对`}

+ 30 - 4
src/pages/device/Instance/Detail/Functions/AdvancedMode.tsx

@@ -1,12 +1,13 @@
 import type { FunctionMetadata } from '@/pages/device/Product/typings';
-import { useState, useEffect, useRef, useCallback } from 'react';
-import { Input, Button } from 'antd';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { Button, Input } from 'antd';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { InstanceModel, service } from '@/pages/device/Instance';
 import MonacoEditor from 'react-monaco-editor';
 import { isObject } from 'lodash';
 
 import './index.less';
+import { MetaDataJsonHandle } from '@/components/FormItems/MetadataJsonInput';
 
 type FunctionProps = {
   data: FunctionMetadata;
@@ -44,9 +45,31 @@ export default (props: FunctionProps) => {
 
   const handleData = (data: any) => {
     const obj = {};
-    const properties = data.valueType ? data.valueType.properties : [];
+
+    const properties = data.valueType ? data.valueType.properties : data.inputs;
+
     for (const datum of properties) {
-      obj[datum.id] = '';
+      switch (datum.valueType.type) {
+        case 'object':
+          obj[datum.id] = MetaDataJsonHandle(datum['json']['properties'][0]);
+          break;
+        case 'array':
+          obj[datum.id] = [];
+          break;
+        case 'int':
+        case 'long':
+        case 'float':
+        case 'double':
+          obj[datum.id] = 0;
+          break;
+        case 'boolean':
+          obj[datum.id] = false;
+          break;
+        default:
+          obj[datum.id] = '';
+          break;
+      }
+      // obj[datum.id] = '';
     }
     setValue(JSON.stringify(obj));
 
@@ -57,6 +80,9 @@ export default (props: FunctionProps) => {
 
   const editorDidMountHandle = (editor: any) => {
     monacoRef.current = editor;
+    editor.onDidContentSizeChange?.(() => {
+      editor.getAction('editor.action.formatDocument').run();
+    });
   };
 
   useEffect(() => {

+ 14 - 4
src/pages/device/Instance/Detail/Functions/form.tsx

@@ -9,6 +9,7 @@ import ProForm from '@ant-design/pro-form';
 import { InstanceModel, service } from '@/pages/device/Instance';
 import moment from 'moment';
 import './index.less';
+import { GeoPoint, MetadataJsonInput } from '@/components';
 
 type FunctionProps = {
   data: FunctionMetadata;
@@ -57,6 +58,10 @@ export default (props: FunctionProps) => {
       case 'float':
       case 'double':
         return <InputNumber style={{ width: '100%' }} placeholder={'请输入' + name} />;
+      case 'geoPoint':
+        return <GeoPoint />;
+      case 'object':
+        return <MetadataJsonInput json={record.json} />;
       case 'date':
         return (
           <>
@@ -78,14 +83,14 @@ export default (props: FunctionProps) => {
     {
       dataIndex: 'name',
       title: '名称',
-      width: 200,
+      width: 120,
       editable: false,
       ellipsis: true,
     },
     {
       dataIndex: 'type',
       title: '类型',
-      width: 200,
+      width: 120,
       editable: false,
     },
     {
@@ -95,7 +100,6 @@ export default (props: FunctionProps) => {
       }),
       dataIndex: 'value',
       align: 'center',
-      width: 260,
       renderFormItem: (_, row: any) => {
         return getItemNode(row.record);
       },
@@ -107,12 +111,16 @@ export default (props: FunctionProps) => {
     const properties = data.valueType ? data.valueType.properties : data.inputs;
 
     for (const datum of properties) {
+      console.log(datum);
+      const type = datum.valueType ? datum.valueType.type : '-';
+
       array.push({
         id: datum.id,
         name: datum.name,
-        type: datum.valueType ? datum.valueType.type : '-',
+        type: type,
         format: datum.valueType ? datum.valueType.format : undefined,
         options: datum.valueType ? datum.valueType.elements : undefined,
+        json: type === 'object' ? datum['json']['properties'][0] : undefined,
         value: undefined,
       });
     }
@@ -135,6 +143,8 @@ export default (props: FunctionProps) => {
         if (d.value) {
           if (d.type === 'date') {
             data[d.id] = moment(d.value).format('YYYY-MM-DD HH:mm:ss');
+          } else if (d.type === 'object') {
+            data[d.id] = JSON.parse(d.value);
           } else {
             data[d.id] = d.value;
           }

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

@@ -2,7 +2,7 @@
   display: flex;
 
   .left {
-    width: 50%;
+    width: 60%;
 
     .button-tool {
       display: flex;
@@ -16,7 +16,7 @@
   }
 
   .right {
-    width: 50%;
+    width: 40%;
     padding-left: 24px;
   }
 }

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

@@ -1,49 +1,99 @@
-import { Card, Tabs } from 'antd';
+import { Button, Card, Tabs } from 'antd';
 import { InstanceModel } from '@/pages/device/Instance';
 import type { FunctionMetadata } from '@/pages/device/Product/typings';
 import FnForm from './form';
 import AModel from './AdvancedMode';
-import { Empty } from '@/components';
+import { Empty, PermissionButton } from '@/components';
 import { useDomFullHeight } from '@/hooks';
+import { getMenuPathByParams } from '@/utils/menu';
+import useHistory from '@/hooks/route/useHistory';
 
 const Functions = () => {
   const functionList = JSON.parse(InstanceModel.detail.metadata || '{}')
     .functions as FunctionMetadata[];
+  const history = useHistory();
 
   const { minHeight } = useDomFullHeight(`.device-detail-function`);
+  const { permission } = PermissionButton.usePermission('device/Product');
+
+  const empty = () => {
+    const isIndependent = InstanceModel.detail?.independentMetadata;
+    const path = isIndependent
+      ? getMenuPathByParams('device/Product/Detail', InstanceModel.detail?.productId)
+      : getMenuPathByParams('device/Instance/Detail', InstanceModel.detail?.id);
+
+    let description = <></>;
+    if (isIndependent) {
+      // 物模型解绑
+      if (!permission.update) {
+        description = <span>请联系管理员配置物模型属性</span>;
+      } else {
+        description = (
+          <span>
+            暂无数据, 请前往产品配置
+            <Button
+              style={{ margin: '0 6px' }}
+              type={'link'}
+              onClick={() => {
+                history.push(`${path}?key=metadata`);
+              }}
+            >
+              物模型-功能定义
+            </Button>
+          </span>
+        );
+      }
+    } else {
+      description = (
+        <span>
+          暂无数据,请配置
+          <Button
+            style={{ margin: '0 6px' }}
+            type={'link'}
+            onClick={() => {
+              history.push(`${path}?key=metadata`);
+            }}
+          >
+            物模型-功能定义
+          </Button>
+        </span>
+      );
+    }
+
+    return <Empty description={description} />;
+  };
 
   return (
     <Card className={'device-detail-function'} style={{ minHeight: minHeight }}>
-      <Tabs>
-        <Tabs.TabPane tab={'精简模式'} key={1}>
-          <Tabs tabPosition="left">
-            {functionList &&
-              functionList.map((fn) => {
-                return (
-                  <Tabs.TabPane tab={fn.name} key={fn.id}>
-                    <FnForm data={fn} />
-                  </Tabs.TabPane>
-                );
-              })}
-          </Tabs>
-        </Tabs.TabPane>
-        <Tabs.TabPane tab={'高级模式'} key={2}>
-          <Tabs tabPosition="left">
-            {functionList &&
-              functionList.map((fn) => {
-                return (
-                  <Tabs.TabPane tab={fn.name} key={fn.id}>
-                    <AModel data={fn} />
-                  </Tabs.TabPane>
-                );
-              })}
-          </Tabs>
-        </Tabs.TabPane>
-      </Tabs>
-      {!functionList && (
-        <div style={{ height: minHeight - 150 }}>
-          <Empty />
-        </div>
+      {functionList ? (
+        <Tabs>
+          <Tabs.TabPane tab={'精简模式'} key={1}>
+            <Tabs tabPosition="left">
+              {functionList &&
+                functionList.map((fn) => {
+                  return (
+                    <Tabs.TabPane tab={fn.name} key={fn.id}>
+                      <FnForm data={fn} />
+                    </Tabs.TabPane>
+                  );
+                })}
+            </Tabs>
+          </Tabs.TabPane>
+          <Tabs.TabPane tab={'高级模式'} key={2}>
+            <Tabs tabPosition="left">
+              {functionList &&
+                functionList.map((fn) => {
+                  return (
+                    <Tabs.TabPane tab={fn.name} key={fn.id}>
+                      <AModel data={fn} />
+                    </Tabs.TabPane>
+                  );
+                })}
+            </Tabs>
+          </Tabs.TabPane>
+        </Tabs>
+      ) : (
+        <div style={{ height: minHeight - 150 }}>{empty()}</div>
       )}
     </Card>
   );

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

@@ -233,6 +233,7 @@ const InstanceDetail = observer(() => {
   }, [params.id]);
 
   useEffect(() => {
+    console.log(location.query);
     if ((location as any).query?.key) {
       setTab((location as any).query?.key || 'detail');
     }

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

@@ -19,7 +19,7 @@ import encodeQuery from '@/utils/encodeQuery';
 import { useHistory } from 'umi';
 import ReactMarkdown from 'react-markdown';
 import { getButtonPermission, getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
-import { ExclamationCircleFilled } from '@ant-design/icons';
+import { InfoCircleOutlined } from '@ant-design/icons';
 import TitleComponent from '@/components/TitleComponent';
 import { PermissionButton } from '@/components';
 import { useDomFullHeight } from '@/hooks';
@@ -276,7 +276,7 @@ const Access = (props: Props) => {
         return (
           <div>
             <div className={styles.alert}>
-              <ExclamationCircleFilled style={{ marginRight: 10 }} />
+              <InfoCircleOutlined style={{ marginRight: 10 }} />
               选择与设备通信的网络组件
             </div>
             <div className={styles.search}>
@@ -391,7 +391,7 @@ const Access = (props: Props) => {
         return (
           <div>
             <div className={styles.alert}>
-              <ExclamationCircleFilled style={{ marginRight: 10 }} />
+              <InfoCircleOutlined style={{ marginRight: 10 }} />
               使用选择的消息协议,对网络组件通信数据进行编解码、认证等操作
             </div>
             <div className={styles.search}>

+ 3 - 3
src/pages/link/AccessConfig/Detail/Cloud/index.tsx

@@ -1,7 +1,7 @@
 import { Button, Card, Steps } from 'antd';
 import { useEffect, useState } from 'react';
 import styles from './index.less';
-import { ExclamationCircleFilled } from '@ant-design/icons';
+import { InfoCircleOutlined } from '@ant-design/icons';
 import OneNet from './OneNet';
 import CTWing from './CTWing';
 import Protocol from './Protocol';
@@ -40,7 +40,7 @@ const Cloud = (props: Props) => {
         return (
           <div>
             <div className={styles.alert}>
-              <ExclamationCircleFilled style={{ marginRight: 10 }} />
+              <InfoCircleOutlined style={{ marginRight: 10 }} />
               通过{props?.provider?.id === 'OneNet' ? 'OneNet' : 'CTWing'}
               平台的HTTP推送服务进行数据接入
             </div>
@@ -57,7 +57,7 @@ const Cloud = (props: Props) => {
         return (
           <div>
             <div className={styles.alert}>
-              <ExclamationCircleFilled style={{ marginRight: 10 }} />
+              <InfoCircleOutlined style={{ marginRight: 10 }} />
               只能选择HTTP通信方式的协议
             </div>
             <div style={{ marginTop: 10 }}>

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

@@ -19,7 +19,7 @@ import { service } from '@/pages/link/AccessConfig';
 import { useLocation } from 'umi';
 import SipComponent from '@/components/SipComponent';
 import TitleComponent from '@/components/TitleComponent';
-import { ExclamationCircleFilled } from '@ant-design/icons';
+import { InfoCircleOutlined } from '@ant-design/icons';
 import { testIP } from '@/utils/util';
 import { getButtonPermission } from '@/utils/menu';
 
@@ -395,7 +395,7 @@ const Media = (props: Props) => {
     return (
       <div>
         <div className={styles.alert}>
-          <ExclamationCircleFilled style={{ marginRight: 10 }} />
+          <InfoCircleOutlined style={{ marginRight: 10 }} />
           配置设备信令参数
         </div>
         <AForm form={aform} layout="vertical" style={{ padding: 30 }}>

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

@@ -429,6 +429,7 @@ export default () => {
             initialValues={{ type: 'bytesSent' }}
             height={400}
             closeInitialParams={true}
+            showTimeTool={true}
             extraParams={{
               key: 'type',
               Children: (

+ 1 - 0
src/pages/media/DashBoard/index.tsx

@@ -223,6 +223,7 @@ export default () => {
           </DashBoardTopCard.Item>
         </DashBoardTopCard>
         <DashBoard
+          showTimeTool={true}
           className={'media-dash-board-body'}
           title={'播放数量(人次)'}
           options={options}

+ 4 - 3
src/pages/media/Device/Channel/index.tsx

@@ -74,7 +74,7 @@ export default () => {
     {
       dataIndex: 'channelId',
       title: '通道ID',
-      width: 220,
+      width: 200,
       ellipsis: true,
     },
     {
@@ -83,7 +83,7 @@ export default () => {
         id: 'pages.table.name',
         defaultMessage: '名称',
       }),
-      width: 220,
+      width: 200,
       ellipsis: true,
     },
     {
@@ -101,6 +101,7 @@ export default () => {
     },
     {
       dataIndex: 'state',
+      width: 90,
       title: intl.formatMessage({
         id: 'pages.searchTable.titleStatus',
         defaultMessage: '状态',
@@ -135,7 +136,7 @@ export default () => {
       }),
       valueType: 'option',
       align: 'center',
-      width: 200,
+      width: 120,
       render: (_, record) => [
         <Tooltip
           key="edit"

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

@@ -54,6 +54,7 @@ const Configuration = () => {
     {
       title: '告警级别',
       dataIndex: 'level',
+      ellipsis: true,
       render: (text: any) => (
         <span>
           {(Store.get('default-level') || []).find((item: any) => item?.level === text)?.title ||

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

@@ -336,6 +336,7 @@ const Dashboard = observer(() => {
         <DashBoard
           title="告警统计"
           height={600}
+          showTimeTool={true}
           options={options}
           onParamsChange={getEcharts}
           ref={alarmCountRef}

+ 21 - 29
src/pages/system/Menu/Detail/edit.tsx

@@ -32,7 +32,7 @@ type EditProps = {
 
 export default (props: EditProps) => {
   const intl = useIntl();
-  const [disabled, setDisabled] = useState(true);
+  // const [disabled, setDisabled] = useState(true);
   const [show] = useState(true);
   const [loading, setLoading] = useState(false);
   const [accessSupport, setAccessSupport] = useState('unsupported');
@@ -79,7 +79,7 @@ export default (props: EditProps) => {
       setLoading(false);
       if (response.status === 200) {
         message.success('操作成功!');
-        setDisabled(true);
+        // setDisabled(true);
         // 新增后刷新页面,编辑则不需要
         if (!props.data.id) {
           pageJump(response.result.id);
@@ -120,7 +120,7 @@ export default (props: EditProps) => {
       });
       setAccessSupport(props.data.accessSupport ? props.data.accessSupport.value : 'unsupported');
     }
-    setDisabled(!!props.data.id);
+    // setDisabled(!!props.data.id);
 
     // if (props.data.options) {
     //   setShow(props.data.options.switch);
@@ -146,7 +146,7 @@ export default (props: EditProps) => {
                   disabled={disabled}
                   style={{ width: 140, height: 130 }}
                 /> */}
-                <Icons disabled={disabled} />
+                <Icons />
               </Form.Item>
             </Col>
             <Col span={21}>
@@ -164,7 +164,7 @@ export default (props: EditProps) => {
                       { max: 64, message: '最多可输入64个字符' },
                     ]}
                   >
-                    <Input disabled={disabled} placeholder={'请输入名称'} />
+                    <Input placeholder={'请输入名称'} />
                   </Form.Item>
                 </Col>
                 <Col span={12}>
@@ -177,7 +177,7 @@ export default (props: EditProps) => {
                     required={true}
                     rules={[{ required: true, message: '请输入编码' }]}
                   >
-                    <Input disabled={disabled} placeholder={'请输入编码'} />
+                    <Input placeholder={'请输入编码'} />
                   </Form.Item>
                 </Col>
               </Row>
@@ -195,7 +195,7 @@ export default (props: EditProps) => {
                       { max: 120, message: '最多可输入120字符' },
                     ]}
                   >
-                    <Input disabled={disabled} placeholder={'请输入页面地址'} />
+                    <Input placeholder={'请输入页面地址'} />
                   </Form.Item>
                 </Col>
                 <Col span={12}>
@@ -212,24 +212,14 @@ export default (props: EditProps) => {
                       },
                     ]}
                   >
-                    <InputNumber
-                      style={{ width: '100%' }}
-                      disabled={disabled}
-                      placeholder={'请输入排序'}
-                    />
+                    <InputNumber style={{ width: '100%' }} placeholder={'请输入排序'} />
                   </Form.Item>
                 </Col>
               </Row>
             </Col>
             <Col span={24}>
               <Form.Item name={'describe'} label={'说明'}>
-                <Input.TextArea
-                  disabled={disabled}
-                  rows={4}
-                  maxLength={200}
-                  showCount
-                  placeholder={'请输入说明'}
-                />
+                <Input.TextArea rows={4} maxLength={200} showCount placeholder={'请输入说明'} />
               </Form.Item>
             </Col>
           </Row>
@@ -262,7 +252,7 @@ export default (props: EditProps) => {
                     onChange={(e) => {
                       setAccessSupport(e.target.value);
                     }}
-                    disabled={disabled}
+                    // disabled={disabled}
                   >
                     <Radio value={'unsupported'}>不支持</Radio>
                     <Radio value={'support'}>支持</Radio>
@@ -284,7 +274,7 @@ export default (props: EditProps) => {
                   >
                     <Select
                       style={{ width: 500 }}
-                      disabled={disabled}
+                      // disabled={disabled}
                       placeholder={'请选择资产类型'}
                       options={
                         assetsType
@@ -301,7 +291,7 @@ export default (props: EditProps) => {
                   >
                     <TreeSelect
                       style={{ width: 400 }}
-                      disabled={disabled}
+                      // disabled={disabled}
                       multiple
                       placeholder={'请选择关联菜单'}
                       fieldNames={{ label: 'name', value: 'id' }}
@@ -323,7 +313,7 @@ export default (props: EditProps) => {
                       id: 'page.system.menu.permissions.operate',
                       defaultMessage: '操作权限',
                     })}
-                    disabled={disabled}
+                    // disabled={disabled}
                     data={permissions}
                   />
                   {/*</Form.Item>*/}
@@ -334,17 +324,19 @@ export default (props: EditProps) => {
           <PermissionButton
             type="primary"
             onClick={() => {
-              if (disabled) {
-                setDisabled(false);
-              } else {
-                saveData();
-              }
+              // if (disabled) {
+              //   setDisabled(false);
+              // } else {
+              //   saveData();
+              // }
+              saveData();
             }}
             loading={loading}
             isPermission={getOtherPermission(['add', 'update'])}
           >
             {intl.formatMessage({
-              id: `pages.data.option.${disabled ? 'edit' : 'save'}`,
+              // id: `pages.data.option.${disabled ? 'edit' : 'save'}`,
+              id: `pages.data.option.save`,
               defaultMessage: '编辑',
             })}
           </PermissionButton>

+ 2 - 2
src/pages/system/Platforms/save.tsx

@@ -385,8 +385,8 @@ export default (props: SaveProps) => {
             },
             'x-validator': [
               {
-                max: 64,
-                message: '最多可输入64个字符',
+                max: 128,
+                message: '最多可输入128个字符',
               },
               {
                 required: true,