This is an automated email from the ASF dual-hosted git repository.

healchow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git


The following commit(s) were added to refs/heads/master by this push:
     new be60ede17 [INLONG-5035][Dashboard] Support version control for backend 
APIs (#5048)
be60ede17 is described below

commit be60ede17ecf3e816f077b61f91c9fde0be2d838
Author: Daniel <[email protected]>
AuthorDate: Thu Jul 14 18:56:10 2022 +0800

    [INLONG-5035][Dashboard] Support version control for backend APIs (#5048)
---
 .../AccessHelper/DataSourcesEditor/CreateModal.tsx |  35 ++---
 .../AccessHelper/DataStorageEditor/DetailModal.tsx |   1 +
 inlong-dashboard/src/configs/routes/index.tsx      |   2 +-
 .../AccessDetail/DataStream/StreamItemModal.tsx    |   7 +-
 .../src/pages/AccessDetail/DataStream/helper.ts    |  51 +------
 .../src/pages/AccessDetail/DataStream/index.tsx    |   4 +-
 .../src/pages/AccessDetail/Info/index.tsx          |   1 +
 .../src/pages/ClusterTags/ClusterBindModal.tsx     |   2 +-
 .../src/pages/ClusterTags/TagDetailModal.tsx       |   3 +-
 .../src/pages/Clusters/CreateModal.tsx             |   4 +-
 .../src/pages/Clusters/NodeEditModal.tsx           |   6 +-
 .../src/pages/ConsumeCreate/Info/index.tsx         |   1 -
 .../src/pages/ConsumeDetail/Info/config.tsx        | 113 +++++++++-------
 .../src/pages/ConsumeDetail/Info/index.tsx         |  82 +++++++-----
 .../src/pages/ConsumeDetail/common.d.ts            |   5 +-
 inlong-dashboard/src/pages/ConsumeDetail/index.tsx | 147 +++++++++++++++++----
 .../src/pages/UserManagement/DetailModal.tsx       |   3 +-
 17 files changed, 281 insertions(+), 186 deletions(-)

diff --git 
a/inlong-dashboard/src/components/AccessHelper/DataSourcesEditor/CreateModal.tsx
 
b/inlong-dashboard/src/components/AccessHelper/DataSourcesEditor/CreateModal.tsx
index 9c120a693..519a5a0df 100644
--- 
a/inlong-dashboard/src/components/AccessHelper/DataSourcesEditor/CreateModal.tsx
+++ 
b/inlong-dashboard/src/components/AccessHelper/DataSourcesEditor/CreateModal.tsx
@@ -67,8 +67,26 @@ const Comp: React.FC<Props> = ({ type, id, content = [], 
record, ...modalProps }
     [type],
   );
 
+  const { data, run: getData } = useRequest(
+    id => ({
+      url: `/source/get/${id}`,
+      params: {
+        sourceType: type,
+      },
+    }),
+    {
+      manual: true,
+      formatResult: result => toFormVals(result),
+      onSuccess: result => {
+        form.setFieldsValue(result);
+        setCurrentValues(result);
+      },
+    },
+  );
+
   const onOk = async () => {
     const values = await form.validateFields();
+    if (data) values.version = data.version;
     modalProps?.onOk(toSubmitVals(values));
   };
 
@@ -87,23 +105,6 @@ const Comp: React.FC<Props> = ({ type, id, content = [], 
record, ...modalProps }
     }
   }, [modalProps.visible]);
 
-  const { run: getData } = useRequest(
-    id => ({
-      url: `/source/get/${id}`,
-      params: {
-        sourceType: type,
-      },
-    }),
-    {
-      manual: true,
-      formatResult: result => toFormVals(result),
-      onSuccess: result => {
-        form.setFieldsValue(result);
-        setCurrentValues(result);
-      },
-    },
-  );
-
   const getCreateFormContent = useMemo(
     () => currentValues => {
       const config = {
diff --git 
a/inlong-dashboard/src/components/AccessHelper/DataStorageEditor/DetailModal.tsx
 
b/inlong-dashboard/src/components/AccessHelper/DataStorageEditor/DetailModal.tsx
index c3186e969..9bdff00d5 100644
--- 
a/inlong-dashboard/src/components/AccessHelper/DataStorageEditor/DetailModal.tsx
+++ 
b/inlong-dashboard/src/components/AccessHelper/DataStorageEditor/DetailModal.tsx
@@ -213,6 +213,7 @@ const Comp: React.FC<DetailModalProps> = ({
   const onOk = async () => {
     const values = await form.validateFields();
     delete values._showHigher; // delete front-end key
+    if (data) values.version = data.version;
     modalProps.onOk && modalProps.onOk(toSubmitVals(values));
   };
 
diff --git a/inlong-dashboard/src/configs/routes/index.tsx 
b/inlong-dashboard/src/configs/routes/index.tsx
index 9ecd804af..1bfda4f25 100644
--- a/inlong-dashboard/src/configs/routes/index.tsx
+++ b/inlong-dashboard/src/configs/routes/index.tsx
@@ -60,7 +60,7 @@ const routes: RouteProps[] = [
     childRoutes: [
       {
         path: '/create',
-        component: () => import('@/pages/ConsumeCreate'),
+        component: () => import('@/pages/ConsumeDetail'),
         exact: true,
       },
       {
diff --git 
a/inlong-dashboard/src/pages/AccessDetail/DataStream/StreamItemModal.tsx 
b/inlong-dashboard/src/pages/AccessDetail/DataStream/StreamItemModal.tsx
index 4f5313147..bc8c3b5bf 100644
--- a/inlong-dashboard/src/pages/AccessDetail/DataStream/StreamItemModal.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/DataStream/StreamItemModal.tsx
@@ -94,16 +94,15 @@ const Comp: React.FC<Props> = ({ inlongGroupId, record, 
mqType, ...modalProps })
   const [form] = useForm();
   const onOk = async () => {
     const values = {
-      ...pickObject(['id', 'inlongGroupId', 'inlongStreamId'], record),
+      ...pickObject(['id', 'inlongGroupId', 'inlongStreamId', 'version'], 
record),
       ...(await form.validateFields()),
     };
 
-    const data = valuesToData(values ? [values] : [], inlongGroupId);
-    const submitData = data.map(item => pickObject(['streamInfo'], item));
+    const submitData = valuesToData(values ? [values] : [], inlongGroupId);
     await request({
       url: '/stream/update',
       method: 'POST',
-      data: submitData?.[0]?.streamInfo,
+      data: submitData?.[0],
     });
     await modalProps?.onOk(values);
     message.success(i18n.t('basic.OperatingSuccess'));
diff --git a/inlong-dashboard/src/pages/AccessDetail/DataStream/helper.ts 
b/inlong-dashboard/src/pages/AccessDetail/DataStream/helper.ts
index a71cd5781..6f77c642b 100644
--- a/inlong-dashboard/src/pages/AccessDetail/DataStream/helper.ts
+++ b/inlong-dashboard/src/pages/AccessDetail/DataStream/helper.ts
@@ -20,50 +20,7 @@
 // Convert form data into interface submission data format
 export const valuesToData = (values, inlongGroupId) => {
   const array = values.map(item => {
-    const {
-      inlongStreamId,
-      predefinedFields = [],
-      rowTypeFields = [],
-      dataSourceType,
-      dataSourcesConfig = [],
-      streamSink = [],
-      ...rest
-    } = item;
-    const output = {} as any;
-    if (dataSourceType !== 'AUTO_PUSH') {
-      output.sourceInfo = dataSourcesConfig.map(k => {
-        return {
-          ...k,
-          sourceType: dataSourceType,
-          inlongGroupId,
-          inlongStreamId,
-        };
-      });
-    } else {
-      output.sourceInfo = [
-        {
-          sourceType: dataSourceType,
-          sourceName: inlongStreamId,
-          inlongGroupId,
-          inlongStreamId,
-        },
-      ];
-    }
-
-    output.sinkInfo = streamSink.reduce((acc, type) => {
-      if (!type) return acc;
-
-      const data = rest[`streamSink${type}`] || [];
-      delete rest[`streamSink${type}`];
-      const formatData = data.map(ds => ({
-        ...ds,
-        inlongGroupId,
-        inlongStreamId,
-        sinkType: type,
-      }));
-
-      return acc.concat(formatData);
-    }, []);
+    const { inlongStreamId, predefinedFields = [], rowTypeFields = [], 
version, ...rest } = item;
 
     const fieldList = predefinedFields.concat(rowTypeFields).map((item, idx) 
=> ({
       ...item,
@@ -72,14 +29,14 @@ export const valuesToData = (values, inlongGroupId) => {
       isPredefinedField: idx < predefinedFields.length ? 1 : 0,
     }));
 
-    output.streamInfo = {
+    const output = {
       ...rest,
       inlongGroupId,
       inlongStreamId,
-      dataSourceType,
+      version,
     };
 
-    if (fieldList?.length) output.streamInfo.fieldList = fieldList;
+    if (fieldList?.length) output.fieldList = fieldList;
 
     return output;
   });
diff --git a/inlong-dashboard/src/pages/AccessDetail/DataStream/index.tsx 
b/inlong-dashboard/src/pages/AccessDetail/DataStream/index.tsx
index a8b3962a6..47260411f 100644
--- a/inlong-dashboard/src/pages/AccessDetail/DataStream/index.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/DataStream/index.tsx
@@ -119,7 +119,7 @@ const Comp = ({ inlongGroupId, readonly, mqType }: Props, 
ref) => {
       await request({
         url: '/stream/update',
         method: 'POST',
-        data: data?.[0]?.streamInfo,
+        data: data?.[0],
       });
     } else {
       // create
@@ -129,7 +129,7 @@ const Comp = ({ inlongGroupId, readonly, mqType }: Props, 
ref) => {
       await request({
         url: '/stream/save',
         method: 'POST',
-        data: data?.[0]?.streamInfo,
+        data: data?.[0],
       });
     }
     await getList();
diff --git a/inlong-dashboard/src/pages/AccessDetail/Info/index.tsx 
b/inlong-dashboard/src/pages/AccessDetail/Info/index.tsx
index d2cf67e45..433ce4d0b 100644
--- a/inlong-dashboard/src/pages/AccessDetail/Info/index.tsx
+++ b/inlong-dashboard/src/pages/AccessDetail/Info/index.tsx
@@ -57,6 +57,7 @@ const Comp = ({ inlongGroupId, readonly, isCreate }: Props, 
ref) => {
 
     const submitData = {
       ...values,
+      version: data.version,
       inCharges: values.inCharges?.join(','),
       followers: values.followers?.join(','),
     };
diff --git a/inlong-dashboard/src/pages/ClusterTags/ClusterBindModal.tsx 
b/inlong-dashboard/src/pages/ClusterTags/ClusterBindModal.tsx
index 1705b0f11..9cecf3b1e 100644
--- a/inlong-dashboard/src/pages/ClusterTags/ClusterBindModal.tsx
+++ b/inlong-dashboard/src/pages/ClusterTags/ClusterBindModal.tsx
@@ -79,7 +79,7 @@ const Comp: React.FC<Props> = ({ clusterTag, ...modalProps }) 
=> {
               formatResult: result =>
                 result?.list?.map(item => ({
                   ...item,
-                  label: item.name,
+                  label: `${item.name} (${item.type})`,
                   value: item.id,
                 })),
             },
diff --git a/inlong-dashboard/src/pages/ClusterTags/TagDetailModal.tsx 
b/inlong-dashboard/src/pages/ClusterTags/TagDetailModal.tsx
index 1790faed7..1e12913d3 100644
--- a/inlong-dashboard/src/pages/ClusterTags/TagDetailModal.tsx
+++ b/inlong-dashboard/src/pages/ClusterTags/TagDetailModal.tsx
@@ -33,7 +33,7 @@ export interface TagDetailModalProps extends ModalProps {
 const TagDetailModal: React.FC<TagDetailModalProps> = ({ id, ...modalProps }) 
=> {
   const [form] = useForm();
 
-  const { run: getData } = useRequest(
+  const { data: savedData, run: getData } = useRequest(
     id => ({
       url: `/cluster/tag/get/${id}`,
     }),
@@ -58,6 +58,7 @@ const TagDetailModal: React.FC<TagDetailModalProps> = ({ id, 
...modalProps }) =>
     };
     if (isUpdate) {
       submitData.id = id;
+      submitData.version = savedData?.version;
     }
     await request({
       url: `/cluster/tag/${isUpdate ? 'update' : 'save'}`,
diff --git a/inlong-dashboard/src/pages/Clusters/CreateModal.tsx 
b/inlong-dashboard/src/pages/Clusters/CreateModal.tsx
index a0e3a150f..3b2793509 100644
--- a/inlong-dashboard/src/pages/Clusters/CreateModal.tsx
+++ b/inlong-dashboard/src/pages/Clusters/CreateModal.tsx
@@ -35,7 +35,7 @@ export interface Props extends ModalProps {
 const Comp: React.FC<Props> = ({ type, id, ...modalProps }) => {
   const [form] = useForm();
 
-  const { run: getData } = useRequest(
+  const { data: savedData, run: getData } = useRequest(
     id => ({
       url: `/cluster/get/${id}`,
     }),
@@ -63,7 +63,7 @@ const Comp: React.FC<Props> = ({ type, id, ...modalProps }) 
=> {
     };
     if (isUpdate) {
       submitData.id = id;
-      // submitData.version = data?.version;
+      submitData.version = savedData?.version;
     }
     await request({
       url: `/cluster/${isUpdate ? 'update' : 'save'}`,
diff --git a/inlong-dashboard/src/pages/Clusters/NodeEditModal.tsx 
b/inlong-dashboard/src/pages/Clusters/NodeEditModal.tsx
index 1916d05f9..bd500d084 100644
--- a/inlong-dashboard/src/pages/Clusters/NodeEditModal.tsx
+++ b/inlong-dashboard/src/pages/Clusters/NodeEditModal.tsx
@@ -40,10 +40,6 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id, 
type, clusterId, ...m
     }),
     {
       manual: true,
-      formatResult: result => ({
-        ...result,
-        // inCharges: result.inCharges.split(','),
-      }),
       onSuccess: result => {
         form.setFieldsValue(result);
       },
@@ -57,10 +53,10 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id, 
type, clusterId, ...m
       ...values,
       type,
       parentId: savedData?.parentId || clusterId,
-      // inCharges: values.inCharges?.join(','),
     };
     if (isUpdate) {
       submitData.id = id;
+      submitData.version = savedData?.version;
     }
     await request({
       url: `/cluster/node/${isUpdate ? 'update' : 'save'}`,
diff --git a/inlong-dashboard/src/pages/ConsumeCreate/Info/index.tsx 
b/inlong-dashboard/src/pages/ConsumeCreate/Info/index.tsx
index 4ceab172d..f4f0e37b7 100644
--- a/inlong-dashboard/src/pages/ConsumeCreate/Info/index.tsx
+++ b/inlong-dashboard/src/pages/ConsumeCreate/Info/index.tsx
@@ -81,7 +81,6 @@ const Comp = ({ id }: Props, ref) => {
     await request({
       url: `/consumption/startProcess/${result}`,
       method: 'POST',
-      data,
     });
     return result;
   };
diff --git a/inlong-dashboard/src/pages/ConsumeDetail/Info/config.tsx 
b/inlong-dashboard/src/pages/ConsumeDetail/Info/config.tsx
index 6132b64ea..e4cfd04d9 100644
--- a/inlong-dashboard/src/pages/ConsumeDetail/Info/config.tsx
+++ b/inlong-dashboard/src/pages/ConsumeDetail/Info/config.tsx
@@ -17,59 +17,82 @@
  * under the License.
  */
 
-import React from 'react';
 import { genBasicFields } from '@/components/ConsumeHelper';
 import i18n from '@/i18n';
 
-export const getFormContent = ({ editing, initialValues }) =>
-  genBasicFields(
-    [
-      {
-        type: 'text',
-        label: i18n.t('pages.ConsumeDetail.Info.config.ConsumerGroupID'),
-        name: 'consumerGroupId',
-        rules: [{ required: true }],
-      },
-      'consumerGroupName',
-      'inCharges',
-      'masterUrl',
-      'inlongGroupId',
-      'topic',
-      'filterEnabled',
-      'inlongStreamId',
-      'mqExtInfo.isDlq',
-      'mqExtInfo.deadLetterTopic',
-      'mqExtInfo.isRlq',
-      'mqExtInfo.retryLetterTopic',
-    ],
-    initialValues,
-  ).map(item => {
-    const obj = { ...item };
-    if (typeof obj.suffix !== 'string') {
-      delete obj.suffix;
-    }
-    delete obj.extra;
-    if (!editing) {
-      if (typeof obj.type === 'string') {
-        obj.type = 'text';
-      }
-      if (obj.name === 'inCharges') {
-        obj.type = <span>{initialValues?.inCharges?.join(', ')}</span>;
-      }
-    }
+export const getFormContent = ({ editing, initialValues, isCreate }) => {
+  const keys = [
+    !isCreate && {
+      type: 'text',
+      label: i18n.t('pages.ConsumeDetail.Info.config.ConsumerGroupID'),
+      name: 'consumerGroupId',
+      rules: [{ required: true }],
+    },
+    'consumerGroupName',
+    'inCharges',
+    !isCreate && 'masterUrl',
+    'inlongGroupId',
+    'topic',
+    'filterEnabled',
+    'inlongStreamId',
+    'mqExtInfo.isDlq',
+    'mqExtInfo.deadLetterTopic',
+    'mqExtInfo.isRlq',
+    'mqExtInfo.retryLetterTopic',
+  ].filter(Boolean);
 
-    if (
-      [
+  return isCreate
+    ? genBasicFields(keys, initialValues).map(item => {
+        return item;
+      })
+    : genBasicFields(keys, initialValues).map(item => ({
+        ...item,
+        type: transType(editing, item, initialValues),
+        suffix:
+          typeof item.suffix === 'object' && !editing
+            ? {
+                ...item.suffix,
+                type: 'text',
+              }
+            : item.suffix,
+        extra: null,
+      }));
+};
+
+function transType(editing: boolean, conf, initialValues) {
+  const arr = [
+    {
+      name: [
         'consumerGroupId',
         'consumerGroupName',
         'inlongGroupId',
         'topic',
         'filterEnabled',
         'inlongStreamId',
-      ].includes(obj.name as string)
-    ) {
-      obj.type = 'text';
-    }
+      ],
+      as: 'text',
+      active: true,
+    },
+    {
+      name: [
+        'inCharges',
+        'mqExtInfo.isDlq',
+        'mqExtInfo.deadLetterTopic',
+        'mqExtInfo.isRlq',
+        'mqExtInfo.retryLetterTopic',
+      ],
+      as: 'text',
+      active: !editing,
+    },
+  ].reduce((acc, cur) => {
+    return acc.concat(Array.isArray(cur.name) ? cur.name.map(name => ({ 
...cur, name })) : cur);
+  }, []);
+
+  const map = new Map(arr.map(item => [item.name, item]));
+  if (map.has(conf.name)) {
+    const item = map.get(conf.name);
+    return item.active ? item.as : conf.type;
+  }
 
-    return obj;
-  });
+  return conf.type;
+}
diff --git a/inlong-dashboard/src/pages/ConsumeDetail/Info/index.tsx 
b/inlong-dashboard/src/pages/ConsumeDetail/Info/index.tsx
index 7e4c28c87..b0d05dd04 100644
--- a/inlong-dashboard/src/pages/ConsumeDetail/Info/index.tsx
+++ b/inlong-dashboard/src/pages/ConsumeDetail/Info/index.tsx
@@ -17,8 +17,7 @@
  * under the License.
  */
 
-import React from 'react';
-import ReactDom from 'react-dom';
+import React, { useState, useMemo, useImperativeHandle, forwardRef } from 
'react';
 import { Button, Space, message } from 'antd';
 import FormGenerator, { useForm } from '@/components/FormGenerator';
 import { useRequest, useBoolean } from '@/hooks';
@@ -29,12 +28,18 @@ import { getFormContent } from './config';
 
 type Props = CommonInterface;
 
-const Comp: React.FC<Props> = ({ id, isActive, readonly, extraRef }) => {
+const Comp = ({ id, readonly, isCreate }: Props, ref) => {
   const { t } = useTranslation();
   const [editing, { setTrue, setFalse }] = useBoolean(false);
 
   const [form] = useForm();
 
+  const isUpdate = useMemo(() => {
+    return !!id;
+  }, [id]);
+
+  const [changedValues, setChangedValues] = useState<Record<string, 
unknown>>({});
+
   const { data, run: getDetail } = useRequest(
     {
       url: `/consumption/get/${id}`,
@@ -46,27 +51,41 @@ const Comp: React.FC<Props> = ({ id, isActive, readonly, 
extraRef }) => {
         ...result,
         inCharges: result.inCharges?.split(',') || [],
       }),
-      onSuccess: data => form.setFieldsValue(data),
+      onSuccess: data => {
+        form.setFieldsValue(data);
+        setChangedValues(data);
+      },
     },
   );
 
-  const onSave = async () => {
+  const onOk = async () => {
     const values = await form.validateFields();
     const submitData = {
       ...values,
       inCharges: values.inCharges.join(','),
-      consumerGroupId: values.consumerGroupName,
-      mqType: values?.mqType || data?.mqType,
+      consumerGroupId: values.consumerGroupName || data?.consumerGroupId,
+      topic: Array.isArray(values.topic) ? values.topic.join(',') : 
values.topic,
+      version: data?.version,
       mqExtInfo: {
         ...values.mqExtInfo,
         mqType: values.mqType,
       },
     };
-    await request({
-      url: `/consumption/update/${id}`,
+
+    const result = await request({
+      url: isUpdate ? `/consumption/update/${id}` : '/consumption/save',
       method: 'POST',
       data: submitData,
     });
+    return result;
+  };
+
+  useImperativeHandle(ref, () => ({
+    onOk,
+  }));
+
+  const onSave = async () => {
+    await onOk();
     await getDetail();
     setFalse();
     message.success(t('basic.OperatingSuccess'));
@@ -77,39 +96,38 @@ const Comp: React.FC<Props> = ({ id, isActive, readonly, 
extraRef }) => {
     setFalse();
   };
 
-  const Extra = () => {
-    return editing ? (
-      <Space>
-        <Button type="primary" onClick={onSave}>
-          {t('basic.Save')}
-        </Button>
-        <Button onClick={onCancel}>{t('basic.Cancel')}</Button>
-      </Space>
-    ) : (
-      <Button type="primary" onClick={setTrue}>
-        {t('basic.Edit')}
-      </Button>
-    );
-  };
-
   return (
-    <>
+    <div style={{ position: 'relative' }}>
       <FormGenerator
         form={form}
         content={getFormContent({
           editing,
-          initialValues: data,
+          initialValues: changedValues,
+          isCreate,
         })}
         allValues={data}
         useMaxWidth={800}
+        onValuesChange={(c, v) => setChangedValues(prev => ({ ...prev, ...v 
}))}
       />
 
-      {isActive &&
-        !readonly &&
-        extraRef?.current &&
-        ReactDom.createPortal(<Extra />, extraRef.current)}
-    </>
+      {!isCreate && !readonly && (
+        <div style={{ position: 'absolute', top: 0, right: 0 }}>
+          {editing ? (
+            <Space>
+              <Button type="primary" onClick={onSave}>
+                {t('basic.Save')}
+              </Button>
+              <Button onClick={onCancel}>{t('basic.Cancel')}</Button>
+            </Space>
+          ) : (
+            <Button type="primary" onClick={setTrue}>
+              {t('basic.Edit')}
+            </Button>
+          )}
+        </div>
+      )}
+    </div>
   );
 };
 
-export default Comp;
+export default forwardRef(Comp);
diff --git a/inlong-dashboard/src/pages/ConsumeDetail/common.d.ts 
b/inlong-dashboard/src/pages/ConsumeDetail/common.d.ts
index a1731b668..e92b23429 100644
--- a/inlong-dashboard/src/pages/ConsumeDetail/common.d.ts
+++ b/inlong-dashboard/src/pages/ConsumeDetail/common.d.ts
@@ -22,7 +22,6 @@ import React from 'react';
 export interface CommonInterface {
   id: number;
   readonly?: boolean;
-  isActive?: boolean;
-  // extraRef of Tab
-  extraRef?: React.RefObject<HTMLDivElement>;
+  isCreate?: boolean;
+  ref?: React.RefObject<unknown>;
 }
diff --git a/inlong-dashboard/src/pages/ConsumeDetail/index.tsx 
b/inlong-dashboard/src/pages/ConsumeDetail/index.tsx
index be4311460..0af00ef80 100644
--- a/inlong-dashboard/src/pages/ConsumeDetail/index.tsx
+++ b/inlong-dashboard/src/pages/ConsumeDetail/index.tsx
@@ -18,23 +18,34 @@
  */
 
 import React, { useState, useMemo, useRef } from 'react';
-import { Tabs } from 'antd';
+import { Tabs, Button, Card, message, Steps, Space } from 'antd';
 import { useTranslation } from 'react-i18next';
-import { PageContainer } from '@/components/PageContainer';
-import { useParams, useRequest } from '@/hooks';
+import { parse } from 'qs';
+import { PageContainer, FooterToolbar } from '@/components/PageContainer';
+import { useParams, useRequest, useSet, useHistory, useLocation } from 
'@/hooks';
+import request from '@/utils/request';
 import Info from './Info';
 
 const Comp: React.FC = () => {
   const { t } = useTranslation();
-  const id = +useParams<{ id: string }>().id;
+  const history = useHistory();
+  const location = useLocation();
+  const _id = +useParams<{ id: string }>().id;
+
+  const qs = parse(location.search.slice(1));
+
+  const [current, setCurrent] = useState(+qs.step || 0);
+  const [, { add: addOpened, has: hasOpened }] = useSet([current]);
+  const [confirmLoading, setConfirmLoading] = useState(false);
+  const [id, setId] = useState(_id);
+  const childRef = useRef(null);
+  const [isCreate] = useState(location.pathname.indexOf('/consume/create') === 
0);
 
   const { data } = useRequest(`/consumption/get/${id}`, {
     ready: !!id,
     refreshDeps: [id],
   });
 
-  const extraRef = useRef<HTMLDivElement>();
-
   const isReadonly = useMemo(() => [11, 20, 22].includes(data?.status), 
[data]);
 
   const list = useMemo(
@@ -48,30 +59,118 @@ const Comp: React.FC = () => {
     [t],
   );
 
-  const [actived, setActived] = useState(list[0].value);
+  const onOk = async current => {
+    const onOk = childRef?.current?.onOk;
+    setConfirmLoading(true);
+    try {
+      const result = onOk && (await onOk());
+      if (current === 0) {
+        setId(result);
+        history.push({
+          search: `?id=${result}&step=1`,
+        });
+      }
+    } finally {
+      setConfirmLoading(false);
+    }
+  };
+
+  const onSubmit = async () => {
+    await request({
+      url: `/consumption/startProcess/${id}`,
+      method: 'POST',
+      data,
+    });
+    message.success(t('basic.OperatingSuccess'));
+    history.push('/consume');
+  };
+
+  const Footer = () => (
+    <Space style={{ display: 'flex', justifyContent: 'center' }}>
+      {current > 0 && (
+        <Button onClick={() => setCurrent(current - 
1)}>{t('pages.ConsumeCreate.Prev')}</Button>
+      )}
+      {current !== list.length - 1 && (
+        <Button
+          type="primary"
+          loading={confirmLoading}
+          onClick={async () => {
+            await onOk(current);
+            const newCurrent = current + 1;
+            setCurrent(newCurrent);
+            if (!hasOpened(newCurrent)) addOpened(newCurrent);
+          }}
+        >
+          {t('pages.ConsumeCreate.Next')}
+        </Button>
+      )}
+      {current === list.length - 1 && (
+        <Button
+          type="primary"
+          onClick={async () => {
+            await onOk(current);
+            await onSubmit();
+          }}
+        >
+          {t('pages.ConsumeCreate.Submit')}
+        </Button>
+      )}
+      <Button onClick={() => 
history.push('/consume')}>{t('pages.ConsumeCreate.Back')}</Button>
+    </Space>
+  );
+
+  const Div = isCreate ? Card : Tabs;
 
   return (
     <PageContainer
       breadcrumb={[
-        { name: 
`${t('pages.ConsumeDetail.ConsumptionDetails')}${data?.consumerGroupId}` },
+        {
+          name: isCreate
+            ? t('pages.ConsumeCreate.NewConsume')
+            : `${t('pages.ConsumeDetail.ConsumptionDetails')}${data?.id}`,
+        },
       ]}
+      useDefaultContainer={!isCreate}
     >
-      <Tabs
-        activeKey={actived}
-        onChange={val => setActived(val)}
-        tabBarExtraContent={<div ref={extraRef} />}
-      >
-        {list.map(({ content: Content, ...item }) => (
-          <Tabs.TabPane tab={item.label} key={item.value}>
-            <Content
-              id={id}
-              isActive={actived === item.value}
-              readonly={isReadonly}
-              extraRef={extraRef}
-            />
-          </Tabs.TabPane>
-        ))}
-      </Tabs>
+      {isCreate && (
+        <Steps
+          current={current}
+          size="small"
+          style={{ marginBottom: 20, width: 600 }}
+          onChange={c => setCurrent(c)}
+        >
+          {list.map(item => (
+            <Steps.Step key={item.label} title={item.label} />
+          ))}
+        </Steps>
+      )}
+
+      <Div>
+        {list.map(({ content: Content, ...item }, index) => {
+          // Lazy load the content of the step, and at the same time make the 
loaded useCache content not destroy
+          const child =
+            !isCreate || hasOpened(index) ? (
+              <Content
+                id={id}
+                readonly={isReadonly}
+                isCreate={isCreate}
+                ref={index === current ? childRef : null}
+              />
+            ) : null;
+
+          return isCreate ? (
+            <div key={item.label} style={{ display: `${index === current ? 
'block' : 'none'}` }}>
+              {child}
+            </div>
+          ) : (
+            <Tabs.TabPane tab={item.label} key={item.value}>
+              {child}
+            </Tabs.TabPane>
+          );
+        })}
+      </Div>
+
+      {isCreate && <FooterToolbar extra={<Footer />} />}
     </PageContainer>
   );
 };
diff --git a/inlong-dashboard/src/pages/UserManagement/DetailModal.tsx 
b/inlong-dashboard/src/pages/UserManagement/DetailModal.tsx
index 22bac8699..a7c37ab5a 100644
--- a/inlong-dashboard/src/pages/UserManagement/DetailModal.tsx
+++ b/inlong-dashboard/src/pages/UserManagement/DetailModal.tsx
@@ -75,7 +75,7 @@ const content = [
 const Comp: React.FC<Props> = ({ id, ...modalProps }) => {
   const [form] = useForm();
 
-  const { run: getData } = useRequest(
+  const { data: savedData, run: getData } = useRequest(
     id => ({
       url: `/user/get/${id}`,
     }),
@@ -92,6 +92,7 @@ const Comp: React.FC<Props> = ({ id, ...modalProps }) => {
     const isUpdate = id;
     if (isUpdate) {
       values.id = id;
+      values.version = savedData?.version;
     }
     await request({
       url: isUpdate ? '/user/update' : '/user/register',

Reply via email to