Bläddra i källkod

feat: 关系配置

sun-chaochao 3 år sedan
förälder
incheckning
138633ee2f

BIN
public/images/alarm/background.png


BIN
public/images/alarm/level_1.png


BIN
public/images/alarm/level_2.png


BIN
public/images/alarm/level_3.png


BIN
public/images/alarm/level_4.png


BIN
public/images/alarm/level_5.png


BIN
public/images/alarm/log.png


BIN
public/images/metadata-map.png


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

@@ -4,6 +4,7 @@ import moment from 'moment';
 import { observer } from '@formily/react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import Config from '@/pages/device/Instance/Detail/Config';
+import Reation from '@/pages/device/Instance/Detail/Reation';
 import Save from '../../Save';
 import { useState } from 'react';
 import type { DeviceInstance } from '../../typings';
@@ -115,6 +116,9 @@ const Info = observer(() => {
         </Descriptions>
         <Config />
         {InstanceModel.detail?.tags && InstanceModel.detail?.tags.length > 0 && <Tags />}
+        {InstanceModel.detail?.relations && InstanceModel.detail?.relations.length > 0 && (
+          <Reation />
+        )}
       </Card>
       <Save
         model={'edit'}

+ 160 - 0
src/pages/device/Instance/Detail/Reation/Edit.tsx

@@ -0,0 +1,160 @@
+import type { Field } from '@formily/core';
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { InstanceModel, service } from '@/pages/device/Instance';
+import type { ISchema } from '@formily/json-schema';
+import { Form, FormGrid, FormItem, Select, PreviewText } from '@formily/antd';
+import { useParams } from 'umi';
+import { Button, Drawer, message, Space } from 'antd';
+import { action } from '@formily/reactive';
+import type { Response } from '@/utils/typings';
+import { useEffect, useState } from 'react';
+
+interface Props {
+  close: () => void;
+  data: any[];
+}
+
+const Edit = (props: Props) => {
+  const { data } = props;
+  const params = useParams<{ id: string }>();
+  const id = InstanceModel.detail?.id || params?.id;
+  const [initData, setInitData] = useState<any>({});
+
+  const getUsers = () => service.queryUserListNopaging();
+
+  const useAsyncDataSource = (api: any) => (field: Field) => {
+    field.loading = true;
+    api(field).then(
+      action.bound!((resp: Response<any>) => {
+        field.dataSource = resp.result?.map((item: Record<string, unknown>) => ({
+          ...item,
+          label: item.name,
+          value: JSON.stringify({
+            id: item.id,
+            name: item.name,
+          }),
+        }));
+        field.loading = false;
+      }),
+    );
+  };
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: initData,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Select,
+      FormGrid,
+      PreviewText,
+    },
+  });
+
+  const configToSchema = (list: any[]) => {
+    const config = {};
+    list.forEach((item) => {
+      config[item.relation] = {
+        type: 'string',
+        title: item.relationName,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请选择关联方',
+          showSearch: true,
+          showArrow: true,
+          mode: 'multiple',
+          filterOption: (input: string, option: any) =>
+            option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+        },
+        'x-reactions': ['{{useAsyncDataSource(getUsers)}}'],
+      };
+    });
+    return config;
+  };
+
+  const renderConfigCard = () => {
+    const itemSchema: ISchema = {
+      type: 'object',
+      properties: {
+        grid: {
+          type: 'void',
+          'x-component': 'FormGrid',
+          'x-component-props': {
+            minColumns: [1],
+            maxColumns: [1],
+          },
+          properties: configToSchema(data),
+        },
+      },
+    };
+
+    return (
+      <>
+        <PreviewText.Placeholder value="-">
+          <Form form={form} layout="vertical">
+            <SchemaField schema={itemSchema} scope={{ useAsyncDataSource, getUsers }} />
+          </Form>
+        </PreviewText.Placeholder>
+      </>
+    );
+  };
+
+  useEffect(() => {
+    const obj: any = {};
+    (props?.data || []).map((item: any) => {
+      obj[item.relation] = [...(item?.related || []).map((i: any) => JSON.stringify(i))];
+    });
+    setInitData(obj);
+  }, [props.data]);
+
+  return (
+    <Drawer
+      title="编辑"
+      placement="right"
+      onClose={() => {
+        props.close();
+      }}
+      visible
+      extra={
+        <Space>
+          <Button
+            type="primary"
+            onClick={async () => {
+              const values = (await form.submit()) as any;
+              if (Object.keys(values).length > 0) {
+                const param: any[] = [];
+                Object.keys(values).forEach((key) => {
+                  const item = data.find((i) => i.relation === key);
+                  const items = (values[key] || []).map((i: string) => JSON.parse(i));
+                  if (item) {
+                    param.push({
+                      relatedType: 'user',
+                      relation: item.relation,
+                      description: '',
+                      related: [...items],
+                    });
+                  }
+                });
+                const resp = await service.saveRelations(id || '', param);
+                if (resp.status === 200) {
+                  message.success('操作成功!');
+                  props.close();
+                }
+              }
+            }}
+          >
+            保存
+          </Button>
+        </Space>
+      }
+    >
+      {renderConfigCard()}
+    </Drawer>
+  );
+};
+
+export default Edit;

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

@@ -0,0 +1,88 @@
+import { Descriptions, Tooltip } from 'antd';
+import { InstanceModel, service } from '@/pages/device/Instance';
+import { useEffect, useState } from 'react';
+import { history, useParams } from 'umi';
+import { EditOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import Edit from './Edit';
+import { PermissionButton } from '@/components';
+import _ from 'lodash';
+
+const Reation = () => {
+  const params = useParams<{ id: string }>();
+  useEffect(() => {
+    const id = InstanceModel.current?.id || params.id;
+    if (id) {
+      service.getConfigMetadata(id).then((response) => {
+        InstanceModel.config = response?.result;
+      });
+    } else {
+      history.goBack();
+    }
+  }, []);
+
+  const [data, setData] = useState<any[]>([]);
+  const [visible, setVisible] = useState<boolean>(false);
+  const { permission } = PermissionButton.usePermission('device/Instance');
+
+  const id = InstanceModel.detail?.id || params?.id;
+
+  const getDetail = () => {
+    service.detail(id || '').then((resp) => {
+      if (resp.status === 200) {
+        InstanceModel.detail = { id, ...resp.result };
+      }
+    });
+  };
+
+  useEffect(() => {
+    if (id) {
+      setData(InstanceModel.detail?.relations || []);
+    }
+  }, [id]);
+
+  return (
+    <div style={{ width: '100%', marginTop: '20px' }}>
+      <Descriptions
+        style={{ marginBottom: 20 }}
+        bordered
+        column={3}
+        size="small"
+        title={
+          <span>
+            关系信息
+            <PermissionButton
+              isPermission={permission.update}
+              type="link"
+              onClick={async () => {
+                setVisible(true);
+              }}
+            >
+              <EditOutlined />
+              编辑
+              <Tooltip title={`管理设备与其他业务的关联关系,关系来源于关系配置`}>
+                <QuestionCircleOutlined />
+              </Tooltip>
+            </PermissionButton>
+          </span>
+        }
+      >
+        {(data || [])?.map((item: any) => (
+          <Descriptions.Item span={1} label={item.relationName} key={item.objectId}>
+            {_.map(item?.related || [], 'name').join(',')}
+          </Descriptions.Item>
+        ))}
+      </Descriptions>
+      {visible && (
+        <Edit
+          data={data || []}
+          close={() => {
+            setVisible(false);
+            getDetail();
+          }}
+        />
+      )}
+    </div>
+  );
+};
+
+export default Reation;

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

@@ -251,6 +251,20 @@ class Service extends BaseService<DeviceInstance> {
 
   //接入方式
   public queryGatewayList = () => request(`/${SystemConst.API_BASE}/gateway/device/providers`);
+  // 保存设备关系
+  public saveRelations = (id: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/device/instance/${id}/relations`, {
+      method: 'PATCH',
+      data,
+    });
+  // 查询用户
+  public queryUserListNopaging = () =>
+    request(`/${SystemConst.API_BASE}/user/_query/no-paging`, {
+      method: 'POST',
+      data: {
+        paging: false,
+      },
+    });
 }
 
 export default Service;

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

@@ -29,6 +29,7 @@ export type DeviceInstance = {
   orgId: string;
   orgName: string;
   configuration: Record<string, any>;
+  relations?: any[];
   cachedConfiguration: any;
   transport: string;
   protocol: string;

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

@@ -42,6 +42,7 @@ const Access = (props: Props) => {
   const [config, setConfig] = useState<any>();
   const networkPermission = PermissionButton.usePermission('link/Type').permission;
   const protocolPermission = PermissionButton.usePermission('link/Protocol').permission;
+  const [steps, setSteps] = useState<string[]>(['网络组件', '消息协议', '完成']);
 
   const MetworkTypeMapping = new Map();
   MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
@@ -69,7 +70,7 @@ const Access = (props: Props) => {
     });
   };
 
-  const queryProcotolList = (id: string, params?: any) => {
+  const queryProcotolList = (id?: string, params?: any) => {
     service.getProtocolList(ProcotoleMapping.get(id), params).then((resp) => {
       if (resp.status === 200) {
         setProcotolList(resp.result);
@@ -79,25 +80,39 @@ const Access = (props: Props) => {
 
   useEffect(() => {
     if (props.provider?.id && !props.data?.id) {
-      queryNetworkList(props.provider?.id, {
-        include: networkCurrent || '',
-      });
-      setCurrent(0);
+      if (props.provider?.id !== 'child-device') {
+        setSteps(['网络组件', '消息协议', '完成']);
+        queryNetworkList(props.provider?.id, {
+          include: networkCurrent || '',
+        });
+        setCurrent(0);
+      } else {
+        setSteps(['消息协议', '完成']);
+        setCurrent(1);
+        queryProcotolList(props.provider?.id);
+      }
     }
   }, [props.provider]);
 
   useEffect(() => {
     if (props.data?.id) {
       setProcotolCurrent(props.data?.protocol);
-      setNetworkCurrent(props.data?.channelId);
       form.setFieldsValue({
         name: props.data?.name,
         description: props.data?.description,
       });
-      setCurrent(0);
-      queryNetworkList(props.data?.provider, {
-        include: props.data?.channelId,
-      });
+      if (props.data?.provider !== 'child-device') {
+        setCurrent(0);
+        setSteps(['网络组件', '消息协议', '完成']);
+        setNetworkCurrent(props.data?.channelId);
+        queryNetworkList(props.data?.provider, {
+          include: props.data?.channelId,
+        });
+      } else {
+        setSteps(['消息协议', '完成']);
+        setCurrent(1);
+        queryProcotolList(props.data?.provider);
+      }
     }
   }, [props.data]);
 
@@ -130,18 +145,6 @@ const Access = (props: Props) => {
     setCurrent(current - 1);
   };
 
-  const steps = [
-    {
-      title: '网络组件',
-    },
-    {
-      title: '消息协议',
-    },
-    {
-      title: '完成',
-    },
-  ];
-
   const columnsMQTT: any[] = [
     {
       title: '分组',
@@ -525,7 +528,10 @@ const Access = (props: Props) => {
                               description: values.description,
                               provider: props.provider.id,
                               protocol: procotolCurrent,
-                              transport: ProcotoleMapping.get(props.provider.id),
+                              transport:
+                                props.provider?.id === 'child-device'
+                                  ? 'Gateway'
+                                  : ProcotoleMapping.get(props.provider.id),
                               channel: 'network', // 网络组件
                               channelId: networkCurrent,
                             })
@@ -648,13 +654,13 @@ const Access = (props: Props) => {
         <div className={styles.steps}>
           <Steps size="small" current={current}>
             {steps.map((item) => (
-              <Steps.Step key={item.title} title={item.title} />
+              <Steps.Step key={item} title={item} />
             ))}
           </Steps>
         </div>
         <div className={styles.content}>{renderSteps(current)}</div>
         <div className={styles.action}>
-          {current === 1 && (
+          {current === 1 && props.provider.id !== 'child-device' && (
             <Button style={{ margin: '0 8px' }} onClick={() => prev()}>
               上一步
             </Button>

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

@@ -50,7 +50,7 @@ const Provider = (props: Props) => {
                     <div className={styles.images}>{item.name}</div>
                     <div style={{ margin: '10px', width: 'calc(100% - 84px)' }}>
                       <div style={{ fontWeight: 600 }}>{item.name}</div>
-                      <div className={styles.desc}>{item.description}</div>
+                      <div className={styles.desc}>{item.description || '--'}</div>
                     </div>
                   </div>
                   <div style={{ width: '70px' }}>

+ 3 - 2
src/pages/link/AccessConfig/service.ts

@@ -31,11 +31,12 @@ class Service extends BaseService<AccessItem> {
       method: 'GET',
       params,
     });
-  public getProtocolList = (transport: string, params?: any) =>
-    request(`/${SystemConst.API_BASE}/protocol/supports/${transport}`, {
+  public getProtocolList = (transport?: string, params?: any) => {
+    return request(`/${SystemConst.API_BASE}/protocol/supports/${transport ? transport : ''}`, {
       method: 'GET',
       params,
     });
+  };
   public getConfigView = (id: string, transport: string) =>
     request(`/${SystemConst.API_BASE}/protocol/${id}/transport/${transport}`, {
       method: 'GET',

+ 251 - 0
src/pages/system/Relationship/Save/index.tsx

@@ -0,0 +1,251 @@
+import { useIntl } from 'umi';
+import type { Field } from '@formily/core';
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import React from 'react';
+import * as ICONS from '@ant-design/icons';
+import { Form, FormGrid, FormItem, Input, Select } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+import { action } from '@formily/reactive';
+import type { Response } from '@/utils/typings';
+import { service } from '@/pages/system/Relationship';
+import { Modal } from '@/components';
+import { message } from 'antd';
+
+interface Props {
+  data: Partial<ReationItem>;
+  close: () => void;
+}
+
+const Save = (props: Props) => {
+  const intl = useIntl();
+
+  const getTypes = () => service.getTypes();
+
+  const useAsyncDataSource = (api: any) => (field: Field) => {
+    field.loading = true;
+    api(field).then(
+      action.bound!((resp: Response<any>) => {
+        field.dataSource = resp.result?.map((item: Record<string, unknown>) => ({
+          ...item,
+          label: item.name,
+          value: JSON.stringify({
+            objectType: item.id,
+            objectTypeName: item.name,
+          }),
+        }));
+        field.loading = false;
+      }),
+    );
+  };
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: props.data,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      FormGrid,
+    },
+    scope: {
+      icon(name: any) {
+        return React.createElement(ICONS[name]);
+      },
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-decorator': 'FormGrid',
+        'x-decorator-props': {
+          maxColumns: 2,
+          minColumns: 2,
+          columnGap: 24,
+        },
+        properties: {
+          name: {
+            title: '名称',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入名称',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入名称',
+              },
+            ],
+          },
+          relation: {
+            title: '标识',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            type: 'string',
+            'x-disabled': !!props.data?.id,
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-component-props': {
+              placeholder: '请输入标识',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入标识',
+              },
+              // {
+              //   triggerType: 'onBlur',
+              //   // validator: (value: string) => {
+              //   //   return new Promise((resolve) => {
+              //   //     service
+              //   //       .validateField('username', value)
+              //   //       .then((resp) => {
+              //   //         if (resp.status === 200) {
+              //   //           if (resp.result.passed) {
+              //   //             resolve('');
+              //   //           } else {
+              //   //             resolve(model === 'edit' ? '' : resp.result.reason);
+              //   //           }
+              //   //         }
+              //   //         resolve('');
+              //   //       })
+              //   //       .catch(() => {
+              //   //         return '验证失败!';
+              //   //       });
+              //   //   });
+              //   // },
+              // },
+            ],
+            name: 'relation',
+            required: true,
+          },
+          object: {
+            title: '关联方',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-disabled': !!props.data?.id,
+            'x-component': 'Select',
+            'x-component-props': {
+              placeholder: '请选择关联方',
+              showArrow: true,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+            },
+            required: true,
+            'x-reactions': ['{{useAsyncDataSource(getTypes)}}'],
+          },
+          target: {
+            title: '被关联方',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-disabled': !!props.data?.id,
+            'x-component': 'Select',
+            'x-component-props': {
+              placeholder: '请选择被关联方',
+            },
+            'x-reactions': {
+              dependencies: ['..object'],
+              fulfill: {
+                state: {
+                  dataSource:
+                    '{{JSON.parse($deps[0] || "{}").objectType==="device"?[{label: "用户", value: JSON.stringify({"targetType":"user", "targetTypeName": "用户"})}] : []}}',
+                },
+              },
+            },
+            required: true,
+          },
+          description: {
+            title: '说明',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input.TextArea',
+            'x-component-props': {
+              rows: 5,
+              placeholder: '请输入说明',
+            },
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                max: 200,
+                message: '最多可输入200个字符',
+              },
+            ],
+          },
+        },
+      },
+    },
+  };
+
+  const save = async () => {
+    const value = await form.submit<any>();
+    const temp: any = {
+      ...props.data,
+      ...value,
+      ...JSON.parse(value?.object || '{}'),
+      ...JSON.parse(value?.target || '{}'),
+    };
+    delete temp.object;
+    delete temp.target;
+    const response: any = await service[!props.data?.id ? 'save' : 'update']({ ...temp });
+    if (response.status === 200) {
+      message.success(
+        intl.formatMessage({
+          id: 'pages.data.option.success',
+          defaultMessage: '操作成功',
+        }),
+      );
+      props.close();
+    } else {
+      message.error('操作失败!');
+    }
+  };
+
+  return (
+    <Modal
+      title={intl.formatMessage({
+        id: `pages.data.option.${props.data.id ? 'edit' : 'add'}`,
+        defaultMessage: '编辑',
+      })}
+      maskClosable={false}
+      visible
+      onCancel={props.close}
+      onOk={save}
+      width="35vw"
+      permissionCode={'system/Relationship'}
+      permission={['add', 'edit']}
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} scope={{ useAsyncDataSource, getTypes }} />
+      </Form>
+    </Modal>
+  );
+};
+export default Save;

+ 146 - 0
src/pages/system/Relationship/index.tsx

@@ -0,0 +1,146 @@
+import SearchComponent from '@/components/SearchComponent';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { useRef, useState } from 'react';
+import Service from '@/pages/system/Relationship/service';
+import { PageContainer } from '@ant-design/pro-layout';
+import { PermissionButton } from '@/components';
+import { useIntl } from 'umi';
+import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
+import { message } from 'antd';
+import Save from './Save';
+
+export const service = new Service('relation');
+
+const Relationship = () => {
+  const intl = useIntl();
+  const [param, setParam] = useState<any>({});
+  const [current, setCurrent] = useState<Partial<ReationItem>>({});
+  const [visible, setVisible] = useState<boolean>(false);
+  const actionRef = useRef<ActionType>();
+  const { permission } = PermissionButton.usePermission('system/Relationship');
+
+  const columns: ProColumns<ReationItem>[] = [
+    {
+      dataIndex: 'name',
+      title: '名称',
+      ellipsis: true,
+    },
+    {
+      dataIndex: 'objectTypeName',
+      title: '关联方',
+      ellipsis: true,
+    },
+    {
+      dataIndex: 'targetTypeName',
+      title: '被关联方',
+      ellipsis: true,
+    },
+    {
+      dataIndex: 'description',
+      title: '说明',
+      ellipsis: true,
+    },
+    {
+      title: '操作',
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: (text, record) => [
+        <PermissionButton
+          isPermission={permission.update}
+          key="warning"
+          onClick={() => {
+            setVisible(true);
+            setCurrent(record);
+          }}
+          type={'link'}
+          style={{ padding: 0 }}
+          tooltip={{
+            title: intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '编辑',
+            }),
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          isPermission={permission.delete}
+          style={{ padding: 0 }}
+          popConfirm={{
+            title: '确认删除',
+            onConfirm: async () => {
+              const resp: any = await service.remove(record.id);
+              if (resp.status === 200) {
+                message.success(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }
+            },
+          }}
+          key="button"
+          type="link"
+        >
+          <DeleteOutlined />
+        </PermissionButton>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <SearchComponent<ReationItem>
+        field={columns}
+        target="relationship"
+        onSearch={(data) => {
+          actionRef.current?.reload();
+          setParam(data);
+        }}
+      />
+      <ProTable<ReationItem>
+        actionRef={actionRef}
+        params={param}
+        columns={columns}
+        search={false}
+        rowKey="id"
+        request={async (params) => {
+          return service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] });
+        }}
+        headerTitle={[
+          <PermissionButton
+            isPermission={permission.add}
+            key="add"
+            onClick={() => {
+              setVisible(true);
+              setCurrent({});
+            }}
+            type="primary"
+            tooltip={{
+              title: intl.formatMessage({
+                id: 'pages.data.option.add',
+                defaultMessage: '新增',
+              }),
+            }}
+          >
+            新增
+          </PermissionButton>,
+        ]}
+      />
+      {visible && (
+        <Save
+          data={current}
+          close={() => {
+            setVisible(false);
+            actionRef.current?.reload();
+          }}
+        />
+      )}
+    </PageContainer>
+  );
+};
+export default Relationship;

+ 12 - 0
src/pages/system/Relationship/service.ts

@@ -0,0 +1,12 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<ReationItem> {
+  getTypes = () =>
+    request(`/${SystemConst.API_BASE}/relation/types`, {
+      method: 'GET',
+    });
+}
+
+export default Service;

+ 12 - 0
src/pages/system/Relationship/typings.d.ts

@@ -0,0 +1,12 @@
+type ReationItem = {
+  id: string;
+  name: string;
+  objectType: string;
+  objectTypeName: string;
+  relation: string;
+  targetType: string;
+  targetTypeName: string;
+  createTime: number;
+  description?: string;
+  expands?: Record<string, any>;
+};

+ 1 - 0
src/utils/menu/router.ts

@@ -77,6 +77,7 @@ export enum MENUS_CODE {
   'system/Tenant/Detail' = 'system/Tenant/Detail',
   'system/Tenant' = 'system/Tenant',
   'system/User' = 'system/User',
+  'system/Relationship' = 'system/Relationship',
   'user/Login' = 'user/Login',
   'visualization/Category' = 'visualization/Category',
   'visualization/Configuration' = 'visualization/Configuration',