Kaynağa Gözat

feat: merge

xieyonghong 3 yıl önce
ebeveyn
işleme
96ff3de3d9

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

@@ -18,7 +18,6 @@ interface PermissionButtonProps extends ButtonProps {
  */
 const PermissionButton = (props: PermissionButtonProps) => {
   const { tooltip, popConfirm, isPermission, ...buttonProps } = props;
-
   const _isPermission =
     'isPermission' in props && props.isPermission
       ? 'disabled' in buttonProps

+ 226 - 0
src/pages/device/Instance/Detail/Running/Property/Indicators.tsx

@@ -0,0 +1,226 @@
+import { message, Modal } from 'antd';
+import {
+  FormItem,
+  Input,
+  Select,
+  DatePicker,
+  ArrayItems,
+  Form,
+  Checkbox,
+  NumberPicker,
+  FormGrid,
+} from '@formily/antd';
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+
+import type { PropertyMetadata } from '@/pages/device/Product/typings';
+import { useEffect, useState } from 'react';
+import { InstanceModel, service } from '@/pages/device/Instance';
+
+interface Props {
+  data: Partial<PropertyMetadata>;
+  onCancel: () => void;
+}
+
+const componentMap = {
+  int: 'NumberPicker',
+  long: 'NumberPicker',
+  float: 'NumberPicker',
+  double: 'NumberPicker',
+  number: 'NumberPicker',
+  date: 'DatePicker',
+  boolean: 'Select',
+};
+
+const Indicators = (props: Props) => {
+  const { data } = props;
+
+  const [metrics, setMetrics] = useState<any[]>(data.expands?.metrics || []);
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: {
+      metrics: metrics,
+    },
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Checkbox,
+      Select,
+      DatePicker,
+      ArrayItems,
+      NumberPicker,
+      FormGrid,
+    },
+  });
+
+  const schema = {
+    type: 'object',
+    properties: {
+      metrics: {
+        type: 'array',
+        'x-component': 'ArrayItems',
+        'x-decorator': 'FormItem',
+        items: {
+          type: 'object',
+          properties: {
+            name: {
+              type: 'string',
+              title: '',
+              'x-decorator': 'FormItem',
+              'x-component': 'Input',
+              'x-hidden': true,
+            },
+            space: {
+              type: 'void',
+              title: '指标值',
+              'x-decorator': 'FormItem',
+              'x-component': 'FormGrid',
+              'x-decorator-props': {
+                labelAlign: 'left',
+                layout: 'vertical',
+              },
+              'x-component-props': {
+                maxColumns: 12,
+                minColumns: 12,
+              },
+              'x-reactions': [
+                {
+                  dependencies: ['.name'],
+                  fulfill: {
+                    state: {
+                      title: '{{$deps[0]}}',
+                    },
+                  },
+                },
+              ],
+              properties: {
+                'value[0]': {
+                  'x-decorator': 'FormItem',
+                  'x-component': componentMap[data.valueType?.type || ''] || 'Input',
+                  'x-reactions': [
+                    {
+                      dependencies: ['..range', data.valueType?.type],
+                      fulfill: {
+                        state: {
+                          dataSource:
+                            data.valueType?.type === 'boolean'
+                              ? [
+                                  {
+                                    label: data.valueType?.trueText,
+                                    value: data.valueType?.trueValue,
+                                  },
+                                  {
+                                    label: data.valueType?.falseText,
+                                    value: data.valueType?.falseValue,
+                                  },
+                                ]
+                              : [],
+                          decoratorProps: {
+                            gridSpan: '{{!!$deps[0]?5:$deps[1]==="boolean"?12:10}}',
+                          },
+                        },
+                      },
+                    },
+                  ],
+                  'x-decorator-props': {
+                    gridSpan: 5,
+                  },
+                },
+                'value[1]': {
+                  title: '~',
+                  'x-decorator': 'FormItem',
+                  'x-component': componentMap[data.valueType?.type || ''] || 'Input',
+                  'x-reactions': [
+                    {
+                      dependencies: ['..range'],
+                      fulfill: {
+                        state: {
+                          visible: '{{!!$deps[0]}}',
+                        },
+                      },
+                    },
+                  ],
+                  'x-decorator-props': {
+                    gridSpan: 5,
+                  },
+                },
+                range: {
+                  type: 'boolean',
+                  title: '',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Checkbox',
+                  'x-hidden': true,
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  };
+
+  console.log(data);
+  console.log(InstanceModel.detail);
+
+  useEffect(() => {
+    if (InstanceModel.detail.id && data.id) {
+      service.queryMetric(InstanceModel.detail.id || '', data.id || '').then((resp) => {
+        if (resp.status === 200) {
+          if ((resp?.result || []).length > 0) {
+            setMetrics(resp.result);
+          } else {
+            setMetrics(data.expands?.metrics || []);
+          }
+        }
+      });
+    }
+  }, [data.id]);
+
+  return (
+    <Modal
+      maskClosable={false}
+      title="编辑指标"
+      visible
+      width={600}
+      onOk={async () => {
+        const params = (await form.submit()) as any;
+        const resp = await service.saveMetric(
+          InstanceModel.detail.id || '',
+          data.id || '',
+          params.metrics,
+        );
+        if (resp.status === 200) {
+          message.success('操作成功!');
+          props.onCancel();
+        }
+      }}
+      onCancel={() => {
+        props.onCancel();
+      }}
+    >
+      <div
+        style={{
+          width: '100%',
+          height: 40,
+          lineHeight: '40px',
+          color: 'rgba(0, 0, 0, 0.55)',
+          backgroundColor: '#f6f6f6',
+          paddingLeft: 10,
+        }}
+      >
+        场景联动页面可引用指标配置触发条件
+      </div>
+      <div style={{ marginTop: '30px' }}>
+        <Form form={form} layout="vertical">
+          <SchemaField schema={schema} />
+        </Form>
+      </div>
+    </Modal>
+  );
+};
+
+export default Indicators;

+ 18 - 1
src/pages/device/Instance/Detail/Running/Property/PropertyCard.less

@@ -4,8 +4,25 @@
   }
 }
 
+.card-title-box {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  margin-bottom: 10px;
+  .card-title {
+    width: 60%;
+    margin-right: 10px;
+    overflow: hidden;
+    color: rgba(0, 0, 0, 0.65);
+    font-weight: 400;
+    font-size: 12px;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+}
+
 .value {
-  width: '100%';
+  width: 100%;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;

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

@@ -1,6 +1,10 @@
-import { EditOutlined, SyncOutlined, UnorderedListOutlined } from '@ant-design/icons';
-import { Divider, message, Spin, Tooltip } from 'antd';
-import ProCard from '@ant-design/pro-card';
+import {
+  ClockCircleOutlined,
+  EditOutlined,
+  SyncOutlined,
+  UnorderedListOutlined,
+} from '@ant-design/icons';
+import { message, Space, Spin, Tooltip, Card } from 'antd';
 import type { PropertyMetadata } from '@/pages/device/Product/typings';
 import { useState } from 'react';
 import { service } from '@/pages/device/Instance';
@@ -8,6 +12,7 @@ import { useParams } from 'umi';
 import PropertyLog from '@/pages/device/Instance/Detail/MetadataLog/Property';
 import EditProperty from '@/pages/device/Instance/Detail/Running/Property/EditProperty';
 import moment from 'moment';
+import Indicators from './Indicators';
 import './PropertyCard.less';
 
 interface Props {
@@ -33,36 +38,39 @@ const Property = (props: Props) => {
 
   const [visible, setVisible] = useState<boolean>(false);
   const [editVisible, setEditVisible] = useState<boolean>(false);
+  const [indicatorVisible, setIndicatorVisible] = useState<boolean>(false);
 
   const renderTitle = (title: string) => {
     return (
-      <div className="value" style={{ color: 'rgba(0, 0, 0, .65)', fontSize: 14 }}>
-        {title}
-      </div>
-    );
-  };
-
-  return (
-    <ProCard
-      title={renderTitle(data?.name || '')}
-      extra={
-        <>
+      <div className="card-title-box">
+        <div className="card-title">
+          <Tooltip title={title}>{title}</Tooltip>
+        </div>
+        <Space style={{ fontSize: 12 }}>
           {(data.expands?.readOnly === false || data.expands?.readOnly === 'false') && (
-            <>
-              <Tooltip placement="top" title="设置属性至设备">
-                <EditOutlined
+            <Tooltip placement="top" title="设置属性至设备">
+              <EditOutlined
+                onClick={() => {
+                  setEditVisible(true);
+                }}
+              />
+            </Tooltip>
+          )}
+          {(data.expands?.metrics || []).length > 0 &&
+            ['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
+              data.valueType?.type || '',
+            ) && (
+              <Tooltip placement="top" title="指标">
+                <ClockCircleOutlined
                   onClick={() => {
-                    setEditVisible(true);
+                    setIndicatorVisible(true);
                   }}
                 />
               </Tooltip>
-              <Divider type="vertical" />
-            </>
-          )}
+            )}
           <Tooltip placement="top" title="获取最新属性值">
             <SyncOutlined onClick={refreshProperty} />
           </Tooltip>
-          <Divider type="vertical" />
           <Tooltip placement="top" title="详情">
             <UnorderedListOutlined
               onClick={() => {
@@ -70,15 +78,16 @@ const Property = (props: Props) => {
               }}
             />
           </Tooltip>
-        </>
-      }
-      bordered
-      hoverable
-      colSpan={{ xs: 12, sm: 8, md: 6, lg: 6, xl: 6 }}
-      style={{ backgroundColor: 'rgba(0, 0, 0, .02)' }}
-    >
+        </Space>
+      </div>
+    );
+  };
+
+  return (
+    <Card bordered hoverable style={{ backgroundColor: 'rgba(0, 0, 0, .02)' }}>
       <Spin spinning={loading}>
         <div>
+          <div>{renderTitle(data?.name || '')}</div>
           <div className="value" style={{ fontWeight: 700, fontSize: '24px', color: '#323130' }}>
             {value?.formatValue || '--'}
           </div>
@@ -98,7 +107,15 @@ const Property = (props: Props) => {
         data={data}
       />
       <PropertyLog data={data} visible={visible} close={() => setVisible(false)} />
-    </ProCard>
+      {indicatorVisible && (
+        <Indicators
+          data={data}
+          onCancel={() => {
+            setIndicatorVisible(false);
+          }}
+        />
+      )}
+    </Card>
   );
 };
 export default Property;

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

@@ -1,7 +1,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { InstanceModel, service } from '@/pages/device/Instance';
+import { InstanceModel } from '@/pages/device/Instance';
 import { history, useParams } from 'umi';
-import { Badge, Card, Descriptions, Divider, message, Tooltip } from 'antd';
+import { Badge, Card, Descriptions, Divider, Tooltip } from 'antd';
 import type { ReactNode } from 'react';
 import { useEffect, useState } from 'react';
 import { observer } from '@formily/react';
@@ -23,6 +23,7 @@ import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { PermissionButton } from '@/components';
 import { QuestionCircleOutlined } from '@ant-design/icons';
+import Service from '@/pages/device/Instance/service';
 
 export const deviceStatus = new Map();
 deviceStatus.set('online', <Badge status="success" text={'在线'} />);
@@ -33,18 +34,18 @@ const InstanceDetail = observer(() => {
   const intl = useIntl();
   const [tab, setTab] = useState<string>('detail');
   const params = useParams<{ id: string }>();
-  const { permission } = PermissionButton.usePermission('device/Instance');
+  const service = new Service('device-instance');
 
-  const resetMetadata = async () => {
-    const resp = await service.deleteMetadata(params.id);
-    if (resp.status === 200) {
-      message.success('操作成功');
-      Store.set(SystemConst.REFRESH_DEVICE, true);
-      setTimeout(() => {
-        Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
-      }, 400);
-    }
-  };
+  // const resetMetadata = async () => {
+  //   const resp = await service.deleteMetadata(params.id);
+  //   if (resp.status === 200) {
+  //     message.success('操作成功');
+  //     Store.set(SystemConst.REFRESH_DEVICE, true);
+  //     setTimeout(() => {
+  //       Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
+  //     }, 400);
+  //   }
+  // };
   const baseList = [
     {
       key: 'detail',
@@ -99,21 +100,21 @@ const InstanceDetail = observer(() => {
         <Card>
           <Metadata
             type="device"
-            tabAction={
-              <PermissionButton
-                isPermission={permission.update}
-                popConfirm={{
-                  title: '确认重置?',
-                  onConfirm: resetMetadata,
-                }}
-                tooltip={{
-                  title: '重置后将使用产品的物模型配置',
-                }}
-                key={'reload'}
-              >
-                重置操作
-              </PermissionButton>
-            }
+            // tabAction={
+            //   <PermissionButton
+            //     isPermission={permission.update}
+            //     popConfirm={{
+            //       title: '确认重置?',
+            //       onConfirm: resetMetadata,
+            //     }}
+            //     tooltip={{
+            //       title: '重置后将使用产品的物模型配置',
+            //     }}
+            //     key={'reload'}
+            //   >
+            //     重置操作1
+            //   </PermissionButton>
+            // }
           />
         </Card>
       ),

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

@@ -265,6 +265,17 @@ class Service extends BaseService<DeviceInstance> {
         paging: false,
       },
     });
+  // 保存设备的物模型指标
+  public saveMetric = (deviceId: string, propertyId: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/device-instance/${deviceId}/metric/property/${propertyId}`, {
+      method: 'PATCH',
+      data,
+    });
+  // 查询设备的物模型指标
+  public queryMetric = (deviceId: string, propertyId: string) =>
+    request(`/${SystemConst.API_BASE}/device-instance/${deviceId}/metric/property/${propertyId}`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

+ 36 - 5
src/pages/device/components/Metadata/index.tsx

@@ -1,16 +1,19 @@
 import { observer } from '@formily/react';
-import { Space, Tabs } from 'antd';
+import { message, Space, Tabs } from 'antd';
 import BaseMetadata from './Base';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import Import from './Import';
 import type { ReactNode } from 'react';
 import { useState } from 'react';
 import Cat from './Cat';
-import Service from '@/pages/device/components/Metadata/service';
+// import Service from '@/pages/device/components/Metadata/service';
 import { InfoCircleOutlined } from '@ant-design/icons';
 import styles from './index.less';
-import { InstanceModel } from '@/pages/device/Instance';
+import { InstanceModel, service } from '@/pages/device/Instance';
 import { PermissionButton } from '@/components';
+import { Store } from 'jetlinks-store';
+import SystemConst from '@/utils/const';
+import { useParams } from 'umi';
 
 interface Props {
   tabAction?: ReactNode;
@@ -18,7 +21,7 @@ interface Props {
   independentMetadata?: boolean;
 }
 
-export const service = new Service();
+// export const service = new Service();
 const Metadata = observer((props: Props) => {
   const intl = useIntl();
   const [visible, setVisible] = useState<boolean>(false);
@@ -26,6 +29,20 @@ const Metadata = observer((props: Props) => {
   const { permission } = PermissionButton.usePermission(
     props.type === 'device' ? 'device/Instance' : 'device/Product',
   );
+
+  const params = useParams<{ id: string }>();
+
+  const resetMetadata = async () => {
+    const resp = await service.deleteMetadata(params.id);
+    if (resp.status === 200) {
+      message.success('操作成功');
+      Store.set(SystemConst.REFRESH_DEVICE, true);
+      setTimeout(() => {
+        Store.set(SystemConst.REFRESH_METADATA_TABLE, true);
+      }, 400);
+    }
+  };
+
   return (
     <div style={{ position: 'relative' }}>
       <div className={styles.tips}>
@@ -38,7 +55,21 @@ const Metadata = observer((props: Props) => {
         className={styles.metadataNav}
         tabBarExtraContent={
           <Space>
-            {props?.tabAction}
+            {props.type === 'device' && (
+              <PermissionButton
+                isPermission={permission.update}
+                popConfirm={{
+                  title: '确认重置?',
+                  onConfirm: resetMetadata,
+                }}
+                tooltip={{
+                  title: '重置后将使用产品的物模型配置',
+                }}
+                key={'reload'}
+              >
+                重置操作
+              </PermissionButton>
+            )}
             <PermissionButton isPermission={permission.update} onClick={() => setVisible(true)}>
               {intl.formatMessage({
                 id: 'pages.device.productDetail.metadata.quickImport',