Просмотр исходного кода

feat(metadata): edit metadata Drawer

Lind 4 лет назад
Родитель
Сommit
2b085a247d

+ 5 - 0
src/locales/zh-CN/pages.ts

@@ -132,6 +132,11 @@ export default {
   // 系统设置-数据源管理
   'pages.system.datasource.': '数据源管理',
 
+  // 设备管理-物模型
+  'pages.device.metadata.function': '功能',
+  'pages.device.metadata.property': '属性',
+  'pages.device.metadata.events': '事件',
+  'pages.device.metadata.tag': '标签',
   // 设备管理-产品
   'pages.device.product': '产品',
   'pages.device.product.status.published': '已发布',

+ 265 - 13
src/pages/device/Product/Detail/Metadata/Base/Edit/index.tsx

@@ -1,30 +1,282 @@
 import { Drawer } from 'antd';
-import { observer } from '@formily/react';
+import { createSchemaField } from '@formily/react';
 import MetadataModel from '@/pages/device/Product/Detail/Metadata/Base/model';
+import { createForm } from '@formily/core';
+import { Radio, Select, Form, Input, FormItem, NumberPicker } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+import { DataTypeList, EventLevel, PropertySource } from '@/pages/device/data';
+import { useCallback, useEffect, useState } from 'react';
+import { service } from '@/pages/device/Product';
+import { Store } from 'jetlinks-store';
+import type { UnitType } from '@/pages/device/Product/typings';
+import { useIntl } from 'umi';
 
-const Edit = observer(() => {
-  const metadataTypeMapping = {
-    property: '属性',
-    events: '事件',
-    function: '功能',
-    tag: '标签',
+const Edit = () => {
+  const form = createForm({
+    initialValues: MetadataModel.item as Record<string, unknown>,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      NumberPicker,
+      Radio,
+    },
+  });
+
+  const [units, setUnits] = useState<{ label: string; value: string }[]>();
+
+  const propertySchema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        title: '标识',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      name: {
+        title: '名称',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      'valueType.type': {
+        title: '数据类型',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        enum: DataTypeList,
+      },
+      'valueType.scale': {
+        title: '精度',
+        'x-decorator': 'FormItem',
+        'x-component': 'NumberPicker',
+      },
+      'valueType.unit': {
+        title: '单位',
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        enum: units,
+      },
+      'expands.readOnly': {
+        title: '是否只读',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Radio.Group',
+        enum: [
+          {
+            label: '是',
+            value: true,
+          },
+          {
+            label: '否',
+            value: false,
+          },
+        ],
+      },
+      'expands.source': {
+        title: '来源',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        enum: PropertySource,
+      },
+      description: {
+        title: '描述',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input.TextArea',
+      },
+    },
+  };
+
+  const functionSchema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        title: '标识',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      name: {
+        title: '名称',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      async: {
+        title: '是否异步',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Radio.Group',
+        enum: [
+          {
+            label: '是',
+            value: true,
+          },
+          {
+            label: '否',
+            value: false,
+          },
+        ],
+      },
+      inputParams: {
+        title: '输入参数',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      outputParams: {
+        title: '输出参数',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      description: {
+        title: '描述',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input.TextArea',
+      },
+    },
+  };
+
+  const eventSchema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        title: '标识',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      name: {
+        title: '名称',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      'expands.level': {
+        title: '级别',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        enum: EventLevel,
+      },
+    },
   };
 
+  const tagSchema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        title: '标识',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      name: {
+        title: '名称',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      'valueType.type': {
+        title: '数据类型',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        enum: DataTypeList,
+      },
+      'expands.readOnly': {
+        title: '是否只读',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Radio.Group',
+        enum: [
+          {
+            label: '是',
+            value: true,
+          },
+          {
+            label: '否',
+            value: false,
+          },
+        ],
+      },
+      description: {
+        title: '描述',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input.TextArea',
+      },
+    },
+  };
+
+  const metadataTypeMapping: Record<string, { name: string; schema: ISchema }> = {
+    property: {
+      name: '属性',
+      schema: propertySchema,
+    },
+    events: {
+      name: '事件',
+      schema: eventSchema,
+    },
+    function: {
+      name: '功能',
+      schema: functionSchema,
+    },
+    tag: {
+      name: '标签',
+      schema: tagSchema,
+    },
+  };
+
+  const getUnits = useCallback(async () => {
+    const cache = Store.get('units');
+    if (cache) {
+      return cache;
+    }
+    const response = await service.getUnit();
+    const temp = response.result.map((item: UnitType) => ({
+      label: item.description,
+      value: item.id,
+    }));
+    Store.set('units', temp);
+    return temp;
+  }, []);
+
+  useEffect(() => {
+    getUnits().then((data) => {
+      setUnits(data);
+    });
+  }, [getUnits]);
+
+  const intl = useIntl();
+
   return (
     <Drawer
       width="20vw"
-      visible={true}
-      title={`编辑${metadataTypeMapping[MetadataModel.type]}`}
+      visible
+      title={`${intl.formatMessage({
+        id: `pages.data.option.${MetadataModel.action}`,
+        defaultMessage: '新增',
+      })}-${intl.formatMessage({
+        id: `pages.device.metadata.${MetadataModel.type}`,
+        defaultMessage: '',
+      })}`}
       onClose={() => {
         MetadataModel.edit = false;
         MetadataModel.item = {};
       }}
-      destroyOnClose={true}
+      destroyOnClose
       zIndex={1000}
     >
-      {MetadataModel.type}
-      {JSON.stringify(MetadataModel.item)}
+      <Form form={form} layout="vertical" size="small">
+        <SchemaField schema={metadataTypeMapping[MetadataModel.type].schema} />
+      </Form>
     </Drawer>
   );
-});
+};
 export default Edit;

+ 18 - 2
src/pages/device/Product/Detail/Metadata/Base/index.tsx

@@ -5,8 +5,8 @@ import { useParams } from 'umi';
 import DB from '@/db';
 import type { MetadataItem } from '@/pages/device/Product/typings';
 import MetadataMapping from '@/pages/device/Product/Detail/Metadata/Base/columns';
-import { Popconfirm, Tooltip } from 'antd';
-import { EditOutlined, MinusOutlined } from '@ant-design/icons';
+import { Button, Popconfirm, Tooltip } from 'antd';
+import { EditOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons';
 import Edit from '@/pages/device/Product/Detail/Metadata/Base/Edit';
 import { observer } from '@formily/react';
 import MetadataModel from '@/pages/device/Product/Detail/Metadata/Base/model';
@@ -35,6 +35,7 @@ const BaseMetadata = observer((props: Props) => {
             MetadataModel.edit = true;
             MetadataModel.item = record;
             MetadataModel.type = type;
+            MetadataModel.action = 'edit';
           }}
         >
           <Tooltip title="编辑">
@@ -70,6 +71,21 @@ const BaseMetadata = observer((props: Props) => {
         columns={MetadataMapping.get(type)!.concat(actions)}
         rowKey="id"
         search={false}
+        toolBarRender={() => [
+          <Button
+            onClick={() => {
+              MetadataModel.edit = true;
+              MetadataModel.item = undefined;
+              MetadataModel.type = type;
+              MetadataModel.action = 'add';
+            }}
+            key="button"
+            icon={<PlusOutlined />}
+            type="primary"
+          >
+            新建
+          </Button>,
+        ]}
       />
       {MetadataModel.edit && <Edit />}
     </>

+ 4 - 2
src/pages/device/Product/Detail/Metadata/Base/model.ts

@@ -2,13 +2,15 @@ import { model } from '@formily/reactive';
 import type { MetadataItem } from '@/pages/device/Product/typings';
 
 type MetadataModelType = {
-  item: MetadataItem;
+  item: MetadataItem | unknown;
   edit: boolean;
   type: 'events' | 'function' | 'property' | 'tag';
+  action: 'edit' | 'add';
 };
 const MetadataModel = model<MetadataModelType>({
-  item: {},
+  item: undefined,
   edit: false,
   type: 'events',
+  action: 'add',
 });
 export default MetadataModel;

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

@@ -1,9 +1,5 @@
 import { observer } from '@formily/react';
 import { Button, Space, Tabs } from 'antd';
-// import Property from '@/pages/device/Product/Detail/Metadata/Property';
-// import Function from '@/pages/device/Product/Detail/Metadata/Function';
-// import Event from '@/pages/device/Product/Detail/Metadata/Event';
-// import Tag from '@/pages/device/Product/Detail/Metadata/Tag';
 import BaseMetadata from '@/pages/device/Product/Detail/Metadata/Base';
 
 const Metadata = observer(() => {
@@ -15,6 +11,7 @@ const Metadata = observer(() => {
           <Button>物模型TSL</Button>
         </Space>
       }
+      destroyInactiveTabPane
     >
       <Tabs.TabPane tab="属性定义" key="property">
         <BaseMetadata type={'property'} />

+ 24 - 0
src/pages/device/Product/service.ts

@@ -35,6 +35,30 @@ class Service extends BaseService<ProductItem> {
       filter((resp) => resp.status === 200),
       map((resp) => resp.result),
     );
+
+  public getMetadataConfig = (params: {
+    deviceId: string;
+    metadata: {
+      type: 'function' | 'property' | 'events' | 'tag';
+      id: string;
+      dataType: string;
+    };
+  }) =>
+    defer(() =>
+      from(
+        request(
+          `/${SystemConst.API_BASE}/device/product/${params.deviceId}/config-metadata/${params.metadata.type}/${params.metadata.id}/${params.metadata.dataType}`,
+        ),
+      ),
+    ).pipe(
+      filter((resp) => resp.status === 200),
+      map((resp) => resp.result),
+    );
+
+  public getUnit = () =>
+    request(`/${SystemConst.API_BASE}/protocol/units`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

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

@@ -144,3 +144,13 @@ type AlarmRecord = {
     timestamp: number;
   } & Record<string, unknown>;
 };
+
+type UnitType = {
+  id: string;
+  name: string;
+  description: string;
+  symbol: string;
+  text: string;
+  type: string;
+  value: string;
+};

+ 99 - 0
src/pages/device/data.ts

@@ -0,0 +1,99 @@
+export const DataTypeList: { label: string; value: string }[] = [
+  {
+    value: 'int',
+    label: 'int(整数型)',
+  },
+  {
+    value: 'long',
+    label: 'long(长整数型)',
+  },
+  {
+    value: 'float',
+    label: 'float(单精度浮点型)',
+  },
+  {
+    value: 'double',
+    label: 'double(双精度浮点数)',
+  },
+  {
+    value: 'string',
+    label: 'text(字符串)',
+  },
+  {
+    value: 'boolean',
+    label: 'boolean(布尔型)',
+  },
+  {
+    value: 'date',
+    label: 'date(时间型)',
+  },
+  {
+    value: 'enum',
+    label: 'enum(枚举)',
+  },
+  {
+    value: 'array',
+    label: 'array(数组)',
+  },
+  {
+    value: 'object',
+    label: 'object(结构体)',
+  },
+  {
+    value: 'file',
+    label: 'file(文件)',
+  },
+  {
+    value: 'password',
+    label: 'password(密码)',
+  },
+  {
+    value: 'geoPoint',
+    label: 'geoPoint(地理位置)',
+  },
+];
+
+export const PropertySource: { label: string; value: string }[] = [
+  {
+    value: 'device',
+    label: '设备',
+  },
+  {
+    value: 'manual',
+    label: '手动',
+  },
+  {
+    value: 'rule',
+    label: '规则',
+  },
+];
+
+export const FileTypeList: { label: string; value: string }[] = [
+  {
+    label: '文件类型',
+    value: 'url',
+  },
+  {
+    label: 'Base64(Base64编码)',
+    value: 'base64',
+  },
+  {
+    label: 'binary',
+    value: 'Binary(二进制)',
+  },
+];
+
+export const EventLevel: { label: string; value: string }[] = [
+  {
+    label: '普通',
+    value: 'ordinary',
+  },
+  {
+    label: '警告',
+    value: 'warn',
+  },
+  {
+    label: 'urgent',
+    value: '紧急',
+  },
+];