Quellcode durchsuchen

feat(rule): rule editor

Lind vor 3 Jahren
Ursprung
Commit
c1fb1051f9

+ 16 - 0
src/components/FRuleEditor/Advance/index.less

@@ -0,0 +1,16 @@
+.box {
+  display: flex;
+  justify-content: flex-start;
+  width: 100%;
+
+  .left {
+    width: 1000px;
+  }
+
+  .right {
+    width: 30%;
+    margin-left: 10px;
+    padding-left: 10px;
+    border-left: 1px solid lightgray;
+  }
+}

+ 33 - 0
src/components/FRuleEditor/Advance/index.tsx

@@ -0,0 +1,33 @@
+import { Modal } from 'antd';
+import Debug from '../Debug';
+import Operator from '../Operator';
+import styles from './index.less';
+import Editor from '@/components/FRuleEditor/Editor';
+
+interface Props {
+  model: 'advance' | 'simple';
+  onChange: (value: 'advance' | 'simple') => void;
+}
+
+const Advance = (props: Props) => {
+  const { model, onChange } = props;
+  return (
+    <Modal
+      visible={model === 'advance'}
+      width="70vw"
+      title="设置属性规则"
+      onCancel={() => onChange('simple')}
+    >
+      <div className={styles.box}>
+        <div className={styles.left}>
+          <Editor mode="advance" />
+          <Debug />
+        </div>
+        <div className={styles.right}>
+          <Operator data={{}} />
+        </div>
+      </div>
+    </Modal>
+  );
+};
+export default Advance;

+ 78 - 0
src/components/FRuleEditor/Debug/index.less

@@ -0,0 +1,78 @@
+.container {
+  display: flex;
+  width: 100%;
+  height: 340px;
+  margin-top: 20px;
+
+  .left {
+    flex: auto;
+    max-width: 550px;
+    overflow-y: auto;
+    border: 1px solid lightgray;
+
+    .header {
+      display: flex;
+      align-items: center;
+      width: 100%;
+      height: 40px;
+      border-bottom: 1px solid lightgray;
+      //justify-content: space-around;
+
+      div {
+        display: flex;
+        //width: 100%;
+        align-items: center;
+        justify-content: flex-start;
+        height: 100%;
+
+        .title {
+          margin: 0 10px;
+          font-weight: 600;
+          font-size: 16px;
+        }
+
+        .description {
+          margin-left: 10px;
+          color: lightgray;
+          font-size: 12px;
+        }
+      }
+
+      .action {
+        width: 150px;
+        font-size: 14px;
+      }
+    }
+  }
+
+  .right {
+    flex: auto;
+    border: 1px solid lightgray;
+    border-left: none;
+
+    .header {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 100%;
+      height: 40px;
+      border-bottom: 1px solid lightgray;
+
+      .title {
+        display: flex;
+
+        div {
+          margin: 0 10px;
+        }
+      }
+
+      .action {
+        display: flex;
+
+        div {
+          margin: 0 10px;
+        }
+      }
+    }
+  }
+}

+ 151 - 0
src/components/FRuleEditor/Debug/index.tsx

@@ -0,0 +1,151 @@
+import styles from './index.less';
+import { createSchemaField } from '@formily/react';
+import { ArrayTable, Form, FormItem, Input, Select } from '@formily/antd';
+import { useMemo } from 'react';
+import { createForm } from '@formily/core';
+import type { ISchema } from '@formily/json-schema';
+import FAutoComplete from '@/components/FAutoComplete';
+
+const Debug = () => {
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      ArrayTable,
+      FAutoComplete,
+    },
+  });
+  const form = useMemo(() => createForm(), []);
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      array: {
+        type: 'array',
+        'x-decorator': 'FormItem',
+        'x-component': 'ArrayTable',
+        'x-component-props': {
+          pagination: {
+            pageSize: 9999,
+          },
+          style: {
+            maxHeight: 260,
+            overflowY: 'auto',
+          },
+          scroll: { y: 240 },
+        },
+        items: {
+          type: 'object',
+          properties: {
+            column1: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: '属性ID',
+              },
+              properties: {
+                id: {
+                  'x-decorator': 'FormItem',
+                  'x-component': 'FAutoComplete',
+                  enum: [1, 2, 4],
+                },
+              },
+            },
+            column2: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: '当前值',
+              },
+              properties: {
+                current: {
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                },
+              },
+            },
+            column3: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: '上一值',
+              },
+              properties: {
+                last: {
+                  'x-decorator': 'FormItem',
+                  'x-component': 'Input',
+                },
+              },
+            },
+            column6: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: '',
+                dataIndex: 'operations',
+                width: 50,
+                fixed: 'right',
+              },
+              properties: {
+                item: {
+                  type: 'void',
+                  'x-component': 'FormItem',
+                  properties: {
+                    remove: {
+                      type: 'void',
+                      'x-component': 'ArrayTable.Remove',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+        properties: {
+          add: {
+            type: 'void',
+            'x-component': 'ArrayTable.Addition',
+            title: '添加条目',
+          },
+        },
+      },
+    },
+  };
+  return (
+    <div className={styles.container}>
+      <div className={styles.left}>
+        <div className={styles.header}>
+          <div>
+            <div className={styles.title}>
+              属性赋值
+              <div className={styles.description}>请对上方规则使用的属性进行赋值</div>
+            </div>
+          </div>
+        </div>
+        <Form form={form}>
+          <SchemaField schema={schema} />
+        </Form>
+      </div>
+      <div className={styles.right}>
+        <div className={styles.header}>
+          <div className={styles.title}>
+            <div>运行结果</div>
+          </div>
+
+          <div className={styles.action}>
+            <div>
+              <a>开始运行</a>
+              {/*<a>停止运行</a>*/}
+            </div>
+            <div>
+              <a>清空</a>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default Debug;

+ 180 - 0
src/components/FRuleEditor/Editor/index.tsx

@@ -0,0 +1,180 @@
+import styles from '@/components/FRuleEditor/index.less';
+import { Dropdown, Menu } from 'antd';
+import { FullscreenOutlined, MoreOutlined } from '@ant-design/icons';
+import MonacoEditor, { monaco } from 'react-monaco-editor';
+import { useEffect, useRef } from 'react';
+import type * as monacoEditor from 'monaco-editor';
+import { Store } from 'jetlinks-store';
+import { State } from '@/components/FRuleEditor';
+
+const symbolList = [
+  {
+    key: 'add',
+    value: '+',
+  },
+  {
+    key: 'subtract',
+    value: '-',
+  },
+  {
+    key: 'multiply',
+    value: '*',
+  },
+  {
+    key: 'divide',
+    value: '/',
+  },
+  {
+    key: 'parentheses',
+    value: '()',
+  },
+  {
+    key: 'cubic',
+    value: '^',
+  },
+  {
+    key: 'dayu',
+    value: '>',
+  },
+  {
+    key: 'dayudengyu',
+    value: '>=',
+  },
+  {
+    key: 'dengyudengyu',
+    value: '==',
+  },
+  {
+    key: 'xiaoyudengyu',
+    value: '<=',
+  },
+  {
+    key: 'xiaoyu',
+    value: '<',
+  },
+  {
+    key: 'jiankuohao',
+    value: '<>',
+  },
+  {
+    key: 'andand',
+    value: '&&',
+  },
+  {
+    key: 'huohuo',
+    value: '||',
+  },
+  {
+    key: 'fei',
+    value: '!',
+  },
+  {
+    key: 'and',
+    value: '&',
+  },
+  {
+    key: 'huo',
+    value: '|',
+  },
+  {
+    key: 'bolang',
+    value: '~',
+  },
+];
+
+interface Props {
+  mode?: 'advance' | 'simple';
+  onChange?: (value: 'advance' | 'simple') => void;
+}
+
+const Editor = (props: Props) => {
+  const editorRef = useRef<monacoEditor.editor.IStandaloneCodeEditor>();
+  const editorDidMountHandle = (editor: monacoEditor.editor.IStandaloneCodeEditor) => {
+    editorRef.current = editor;
+  };
+
+  const handleInsertCode = (value: string) => {
+    const editor = editorRef.current;
+    if (!editor) return;
+    const position = editor.getPosition()!;
+    editor?.executeEdits(State.code, [
+      {
+        range: new monaco.Range(
+          position?.lineNumber,
+          position?.column,
+          position?.lineNumber,
+          position?.column,
+        ),
+        text: value,
+      },
+    ]);
+  };
+
+  useEffect(() => {
+    const subscription = Store.subscribe('add-operator-value', handleInsertCode);
+    return () => subscription.unsubscribe();
+  }, []);
+
+  return (
+    <div className={styles.box}>
+      <div className={styles.top}>
+        <div className={styles.left}>
+          {symbolList
+            .filter((t, i) => i <= 3)
+            .map((item) => (
+              <span
+                key={item.key}
+                onClick={() => {
+                  handleInsertCode(item.value);
+                }}
+              >
+                {item.value}
+              </span>
+            ))}
+          <span>
+            <Dropdown
+              overlay={
+                <Menu>
+                  {symbolList
+                    .filter((t, i) => i > 6)
+                    .map((item) => (
+                      <Menu.Item
+                        key={item.key}
+                        onClick={async () => {
+                          handleInsertCode(item.value);
+                        }}
+                      >
+                        {item.value}
+                      </Menu.Item>
+                    ))}
+                </Menu>
+              }
+            >
+              <a className="ant-dropdown-link" onClick={(e) => e.preventDefault()}>
+                <MoreOutlined />
+              </a>
+            </Dropdown>
+          </span>
+        </div>
+        <div className={styles.right}>
+          {props.mode !== 'advance' && (
+            <span>
+              <FullscreenOutlined onClick={() => props.onChange?.('advance')} />
+            </span>
+          )}
+        </div>
+      </div>
+      <MonacoEditor
+        editorDidMount={(editor) => editorDidMountHandle(editor)}
+        language={'javascript'}
+        height={300}
+        onChange={(c) => {
+          State.code = c;
+          Store.set('rule-editor-value', State.code);
+        }}
+        value={State.code}
+      />
+    </div>
+  );
+};
+export default Editor;

+ 38 - 0
src/components/FRuleEditor/Operator/index.less

@@ -0,0 +1,38 @@
+.border {
+  margin-top: 10px;
+  padding: 10px;
+  border-top: 1px solid lightgray;
+}
+
+.box {
+  width: 100%;
+
+  .explain {
+    .border;
+  }
+
+  .tree {
+    .border;
+
+    height: 350px;
+    overflow-y: auto;
+
+    .node {
+      display: flex;
+      justify-content: space-between;
+      width: 220px;
+
+      .add {
+        display: none;
+      }
+
+      &:hover .add {
+        display: block;
+      }
+
+      .parent {
+        display: none;
+      }
+    }
+  }
+}

+ 83 - 0
src/components/FRuleEditor/Operator/index.tsx

@@ -0,0 +1,83 @@
+import { Input, Tree } from 'antd';
+import Service from './service';
+import { useEffect, useRef, useState } from 'react';
+import styles from './index.less';
+import ReactMarkdown from 'react-markdown';
+import { treeFilter } from '@/utils/tree';
+import type { OperatorItem } from './typings';
+import { Store } from 'jetlinks-store';
+
+const service = new Service();
+
+const Operator = () => {
+  const [data, setData] = useState<OperatorItem[]>([]);
+  const [item, setItem] = useState<Partial<OperatorItem>>({});
+  const dataRef = useRef<OperatorItem[]>([]);
+  const getData = async () => {
+    // TODO 从物模型中获取属性数据
+    const properties = {
+      id: 'property',
+      name: '属性',
+      description: '',
+      code: '',
+      children: [
+        {
+          id: 'test',
+          name: '测试数据',
+        },
+      ],
+    };
+    const response = await service.getOperator();
+    if (response.status === 200) {
+      setData([properties, ...response.result]);
+      dataRef.current = [properties, ...response.result];
+    }
+  };
+  useEffect(() => {
+    getData();
+  }, []);
+
+  const search = (value: string) => {
+    if (value) {
+      const nodes = treeFilter(dataRef.current, value, 'name') as OperatorItem[];
+      setData(nodes);
+    } else {
+      setData(dataRef.current);
+    }
+  };
+  return (
+    <div className={styles.box}>
+      <Input.Search onSearch={search} allowClear placeholder="搜索关键字" />
+      <Tree
+        className={styles.tree}
+        onSelect={(k, info) => {
+          setItem(info.node as unknown as OperatorItem);
+        }}
+        fieldNames={{
+          title: 'name',
+          key: 'id',
+        }}
+        titleRender={(node) => (
+          <div className={styles.node}>
+            <div>{node.name}</div>
+            <div className={node.children?.length > 0 ? styles.parent : styles.add}>
+              <a
+                onClick={() => {
+                  Store.set('add-operator-value', node.code);
+                }}
+              >
+                添加
+              </a>
+            </div>
+          </div>
+        )}
+        autoExpandParent={true}
+        treeData={data}
+      />
+      <div className={styles.explain}>
+        <ReactMarkdown>{item.description || ''}</ReactMarkdown>
+      </div>
+    </div>
+  );
+};
+export default Operator;

+ 11 - 0
src/components/FRuleEditor/Operator/service.ts

@@ -0,0 +1,11 @@
+import BaseService from '@/utils/BaseService';
+import type { ProductItem } from '@/pages/device/Product/typings';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<ProductItem> {
+  public getOperator = () =>
+    request(`/${SystemConst.API_BASE}/property-calculate-rule/description`, { method: 'GET' });
+}
+
+export default Service;

+ 10 - 0
src/components/FRuleEditor/Operator/typings.d.ts

@@ -0,0 +1,10 @@
+import type { TreeNode } from '@/utils/tree';
+
+interface OperatorItem extends TreeNode {
+  id: string;
+  name: string;
+  key: string;
+  description: string;
+  code: string;
+  children: OperatorItem[];
+}

+ 2 - 2
src/components/FRuleEditor/index.less

@@ -11,7 +11,7 @@
     .left {
       display: flex;
       align-items: center;
-      width: 200px;
+      width: 60%;
       margin: 0 5px;
 
       span {
@@ -26,7 +26,7 @@
     .right {
       display: flex;
       align-items: center;
-      width: 100px;
+      width: 10%;
       margin: 0 5px;
 
       span {

+ 41 - 133
src/components/FRuleEditor/index.tsx

@@ -1,137 +1,45 @@
-import MonacoEditor from 'react-monaco-editor';
-import styles from './index.less';
-import { Dropdown, Menu, message, Tooltip } from 'antd';
-import { BugOutlined, FullscreenOutlined, MoreOutlined, PlusOutlined } from '@ant-design/icons';
+import Advance from '@/components/FRuleEditor/Advance';
+import Editor from '@/components/FRuleEditor/Editor';
+import { model } from '@formily/reactive';
+import { observer } from '@formily/react';
+import { useEffect } from 'react';
+import { Store } from 'jetlinks-store';
 
-const symbolList = [
-  {
-    key: 'add',
-    value: '+',
-  },
-  {
-    key: 'subtract',
-    value: '-',
-  },
-  {
-    key: 'multiply',
-    value: '*',
-  },
-  {
-    key: 'divide',
-    value: '/',
-  },
-  {
-    key: 'parentheses',
-    value: '()',
-  },
-  {
-    key: 'cubic',
-    value: '^',
-  },
-  {
-    key: 'dayu',
-    value: '>',
-  },
-  {
-    key: 'dayudengyu',
-    value: '>=',
-  },
-  {
-    key: 'dengyudengyu',
-    value: '==',
-  },
-  {
-    key: 'xiaoyudengyu',
-    value: '<=',
-  },
-  {
-    key: 'xiaoyu',
-    value: '<',
-  },
-  {
-    key: 'jiankuohao',
-    value: '<>',
-  },
-  {
-    key: 'andand',
-    value: '&&',
-  },
-  {
-    key: 'huohuo',
-    value: '||',
-  },
-  {
-    key: 'fei',
-    value: '!',
-  },
-  {
-    key: 'and',
-    value: '&',
-  },
-  {
-    key: 'huo',
-    value: '|',
-  },
-  {
-    key: 'bolang',
-    value: '~',
-  },
-];
-const FRuleEditor = () => {
+export const State = model<{
+  model: 'simple' | 'advance';
+  code: string;
+}>({
+  model: 'simple',
+  code: '',
+});
+
+interface Props {
+  value: string;
+  onChange: (value: string) => void;
+}
+
+const FRuleEditor = observer((props: Props) => {
+  const { value, onChange } = props;
+
+  useEffect(() => {
+    const subscription = Store.subscribe('rule-editor-value', onChange);
+    State.code = value;
+    return () => subscription.unsubscribe();
+  });
   return (
-    <div className={styles.box}>
-      <div className={styles.top}>
-        <div className={styles.left}>
-          {symbolList
-            .filter((t, i) => i <= 3)
-            .map((item) => (
-              <span key={item.key} onClick={() => message.success(`插入数据${item.value}`)}>
-                {item.value}
-              </span>
-            ))}
-          <span>
-            <Dropdown
-              overlay={
-                <Menu>
-                  {symbolList
-                    .filter((t, i) => i > 6)
-                    .map((item) => (
-                      <Menu.Item
-                        key={item.key}
-                        onClick={async () => {
-                          message.success(`选中了这个${item.value}`);
-                        }}
-                      >
-                        {item.value}
-                      </Menu.Item>
-                    ))}
-                </Menu>
-              }
-            >
-              <a className="ant-dropdown-link" onClick={(e) => e.preventDefault()}>
-                <MoreOutlined />
-              </a>
-            </Dropdown>
-          </span>
-        </div>
-        <div className={styles.right}>
-          <span>
-            <Tooltip title="进行调试">
-              <BugOutlined />
-            </Tooltip>
-          </span>
-          <span>
-            <Tooltip title="快速添加">
-              <PlusOutlined />
-            </Tooltip>
-          </span>
-          <span>
-            <FullscreenOutlined />
-          </span>
-        </div>
-      </div>
-      <MonacoEditor language={'javascript'} height={300} />
-    </div>
+    <>
+      <Editor
+        onChange={(v) => {
+          State.model = v;
+        }}
+      />
+      <Advance
+        model={State.model}
+        onChange={(v) => {
+          State.model = v;
+        }}
+      />
+    </>
   );
-};
+});
 export default FRuleEditor;

+ 44 - 8
src/pages/device/Product/Detail/index.tsx

@@ -1,6 +1,6 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { history, useParams } from 'umi';
-import { Button, Card, Descriptions, Space, Tabs, Badge, message, Spin } from 'antd';
+import { Button, Card, Descriptions, Space, Tabs, Badge, message, Spin, Tooltip } from 'antd';
 import BaseInfo from '@/pages/device/Product/Detail/BaseInfo';
 import { observer } from '@formily/react';
 import { productModel, service } from '@/pages/device/Product';
@@ -12,6 +12,7 @@ import type { DeviceMetadata } from '@/pages/device/Product/typings';
 import { Link } from 'umi';
 import { Store } from 'jetlinks-store';
 import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
+import { QuestionCircleOutlined } from '@ant-design/icons';
 
 const ProductDetail = observer(() => {
   const intl = useIntl();
@@ -52,9 +53,11 @@ const ProductDetail = observer(() => {
     if (!productModel.current) {
       history.goBack();
     } else {
-      service.getProductDetail(param.id).subscribe((data) => {
-        const metadata: DeviceMetadata = JSON.parse(data.metadata);
-        MetadataAction.insert(metadata);
+      service.getProductDetail(param?.id).subscribe((data) => {
+        if (data.metadata) {
+          const metadata: DeviceMetadata = JSON.parse(data.metadata);
+          MetadataAction.insert(metadata);
+        }
       });
     }
 
@@ -193,10 +196,43 @@ const ProductDetail = observer(() => {
             <BaseInfo />
           </Tabs.TabPane>
           <Tabs.TabPane
-            tab={intl.formatMessage({
-              id: 'pages.device.productDetail.metadata',
-              defaultMessage: '物模型',
-            })}
+            tab={
+              <Tooltip
+                placement="right"
+                title={
+                  <div>
+                    <p>
+                      属性:
+                      <br />
+                      用于描述设备运行时具体信息和状态。
+                      例如,环境监测设备所读取的当前环境温度、智能灯开关状态、电风扇风力等级等。
+                    </p>
+                    功能:
+                    <br />
+                    <p>
+                      指设备可供外部调用的指令或方法。功能调用中可设置输入和输出参数。输入参数是服务执行时的参数,输出参数是服务执行后的结果。
+                      相比于属性,功能可通过一条指令实现更复杂的业务逻辑,例如执行某项特定的任务。
+                      功能分为异步和同步两种调用方式。
+                    </p>
+                    <p> 事件:</p>
+                    <p>
+                      设备运行时,主动上报给云端的信息,一般包含需要被外部感知和处理的信息、告警和故障。事件中可包含多个输出参数。
+                      例如,某项任务完成后的通知信息;设备发生故障时的温度、时间信息;设备告警时的运行状态等。
+                    </p>
+                    <p> 标签:</p>
+                    <p>
+                      统一为设备添加拓展字段,添加后将在设备信息页显示。可用于对设备基本信息描述的补充。
+                    </p>
+                  </div>
+                }
+              >
+                {intl.formatMessage({
+                  id: 'pages.device.productDetail.metadata',
+                  defaultMessage: '物模型',
+                })}
+                <QuestionCircleOutlined />
+              </Tooltip>
+            }
             key="metadata"
           >
             <Metadata type="product" />

+ 0 - 6
src/pages/device/Product/index.tsx

@@ -21,9 +21,6 @@ import ProTable from '@jetlinks/pro-table';
 import { lastValueFrom } from 'rxjs';
 import encodeQuery from '@/utils/encodeQuery';
 import Save from '@/pages/device/Product/Save';
-import Edit from '@/pages/device/components/Metadata/Base/Edit';
-// import Operator from '@/pages/device/components/Operator';
-// import Debug from '@/pages/device/components/Debug';
 
 export const service = new Service('device-product');
 export const statusMap = {
@@ -209,9 +206,6 @@ const Product = observer(() => {
         }}
         visible={visible}
       />
-      <Edit type={'product'} />
-      {/*<Operator />*/}
-      {/*<Debug />*/}
     </PageContainer>
   );
 });

+ 64 - 52
src/pages/device/components/Metadata/Base/Edit/index.tsx

@@ -42,7 +42,7 @@ import DB from '@/db';
 import _ from 'lodash';
 import { useParams } from 'umi';
 import { InstanceModel } from '@/pages/device/Instance';
-// import FRuleEditor from '@/components/FRuleEditor';
+import FRuleEditor from '@/components/FRuleEditor';
 
 interface Props {
   type: 'product' | 'device';
@@ -97,7 +97,7 @@ const Edit = (props: Props) => {
       EnumParam,
       BooleanEnum,
       ConfigParam,
-      // FRuleEditor,
+      FRuleEditor,
     },
     scope: {
       async asyncOtherConfig(field: Field) {
@@ -361,29 +361,36 @@ const Edit = (props: Props) => {
           rule: {
             type: 'string',
             'x-component': 'FRuleEditor',
+            'x-visible': false,
+            'x-reactions': {
+              dependencies: ['.source'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rule"}}',
+                },
+              },
+            },
           },
-          readOnly: {
-            title: intl.formatMessage({
-              id: 'pages.device.productDetail.metadata.whetherReadOnly',
-              defaultMessage: '是否只读',
-            }),
+          type: {
+            title: '属性类型',
             required: true,
             'x-decorator': 'FormItem',
-            'x-component': 'Radio.Group',
+            'x-component': 'Select',
+            'x-component-props': {
+              mode: 'tags',
+            },
             enum: [
               {
-                label: intl.formatMessage({
-                  id: 'pages.device.productDetail.metadata.true',
-                  defaultMessage: '是',
-                }),
-                value: true,
+                label: '读',
+                value: 'read',
               },
               {
-                label: intl.formatMessage({
-                  id: 'pages.device.productDetail.metadata.false',
-                  defaultMessage: '否',
-                }),
-                value: false,
+                label: '写',
+                value: 'write',
+              },
+              {
+                label: '上报',
+                value: 'report',
               },
             ],
           },
@@ -529,7 +536,10 @@ const Edit = (props: Props) => {
     const params = (await form.submit()) as MetadataItem;
 
     if (!typeMap.get(props.type)) return;
+
+    console.log(props.type, typeMap.get(props.type).metadata, 'JSON-PARSE');
     const metadata = JSON.parse(typeMap.get(props.type).metadata) as DeviceMetadata;
+    console.log(metadata, 'metadata');
     const config = metadata[type] as MetadataItem[];
     const index = config.findIndex((item) => item.id === params.id);
     if (index > -1) {
@@ -570,40 +580,42 @@ const Edit = (props: Props) => {
     </Menu>
   );
   return (
-    <Drawer
-      width="25vw"
-      visible
-      title={`${intl.formatMessage({
-        id: `pages.data.option.${MetadataModel.action}`,
-        defaultMessage: '新增',
-      })}-${intl.formatMessage({
-        id: `pages.device.metadata.${MetadataModel.type}`,
-        defaultMessage: metadataTypeMapping[MetadataModel.type].name,
-      })}`}
-      onClose={() => {
-        MetadataModel.edit = false;
-        MetadataModel.item = {};
-      }}
-      destroyOnClose
-      zIndex={1000}
-      placement={'right'}
-      extra={
-        props.type === 'product' ? (
-          <Dropdown.Button type="primary" onClick={() => saveMetadata()} overlay={menu}>
-            保存数据
-          </Dropdown.Button>
-        ) : (
-          <Button type="primary" onClick={() => saveMetadata()}>
-            保存数据
-          </Button>
-        )
-      }
-    >
-      <Form form={form} layout="vertical" size="small">
-        <SchemaField schema={metadataTypeMapping.properties.schema} />
-        {/*<SchemaField schema={metadataTypeMapping[MetadataModel.type].schema} />*/}
-      </Form>
-    </Drawer>
+    <>
+      <Drawer
+        width="25vw"
+        visible
+        title={`${intl.formatMessage({
+          id: `pages.data.option.${MetadataModel.action}`,
+          defaultMessage: '新增',
+        })}-${intl.formatMessage({
+          id: `pages.device.metadata.${MetadataModel.type}`,
+          defaultMessage: metadataTypeMapping[MetadataModel.type].name,
+        })}`}
+        onClose={() => {
+          MetadataModel.edit = false;
+          MetadataModel.item = {};
+        }}
+        destroyOnClose
+        zIndex={1000}
+        placement={'right'}
+        extra={
+          props.type === 'product' ? (
+            <Dropdown.Button type="primary" onClick={() => saveMetadata()} overlay={menu}>
+              保存数据
+            </Dropdown.Button>
+          ) : (
+            <Button type="primary" onClick={() => saveMetadata()}>
+              保存数据
+            </Button>
+          )
+        }
+      >
+        <Form form={form} layout="vertical" size="small">
+          <SchemaField schema={metadataTypeMapping.properties.schema} />
+          {/*<SchemaField schema={metadataTypeMapping[MetadataModel.type].schema} />*/}
+        </Form>
+      </Drawer>
+    </>
   );
 };
 export default Edit;

+ 1 - 1
src/pages/device/components/Metadata/Base/index.tsx

@@ -20,7 +20,7 @@ interface Props {
 }
 
 const BaseMetadata = observer((props: Props) => {
-  const { type, target } = props;
+  const { type, target = 'product' } = props;
   const intl = useIntl();
   const param = useParams<{ id: string }>();