xieyonghong 3 лет назад
Родитель
Сommit
35d175c94d

+ 1 - 0
package.json

@@ -82,6 +82,7 @@
     "braft-editor": "^2.3.9",
     "classnames": "^2.3.1",
     "dexie": "^3.0.3",
+    "driver.js": "^0.9.8",
     "echarts": "^5.3.2",
     "event-source-polyfill": "^1.0.25",
     "isomorphic-form-data": "^2.0.0",

BIN
public/images/home/product.png


+ 19 - 1
src/components/FMonacoEditor/index.tsx

@@ -1,5 +1,23 @@
 import MonacoEditor from 'react-monaco-editor';
 import { connect, mapProps } from '@formily/react';
+import { useState } from 'react';
 
-const FMonacoEditor = connect(MonacoEditor, mapProps());
+const JMonacoEditor = (props: any) => {
+  const [loading, setLoading] = useState(false);
+
+  return (
+    <div
+      ref={() => {
+        setTimeout(() => {
+          setLoading(true);
+        }, 100);
+      }}
+      style={{ height: '100%', width: '100%' }}
+    >
+      {loading && <MonacoEditor {...props} />}
+    </div>
+  );
+};
+
+const FMonacoEditor = connect(JMonacoEditor, mapProps());
 export default FMonacoEditor;

+ 1 - 1
src/pages/device/Instance/Detail/Config/index.tsx

@@ -22,7 +22,7 @@ const Config = () => {
     }
   }, []);
 
-  const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
+  const [metadata, setMetadata] = useState<ConfigMetadata[]>(InstanceModel.config);
   const [visible, setVisible] = useState<boolean>(false);
   const { permission } = PermissionButton.usePermission('device/Instance');
 

+ 9 - 2
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -139,11 +139,18 @@ const EditableTable = (props: Props) => {
       render: (text: any, record: any) => <span>{record.valueType?.type}</span>,
     },
     {
-      title: '映射状态',
+      title: (
+        <span>
+          映射状态
+          <Tooltip title="默认映射:当前属性不做映射,使用物模型属性进行数据处理。">
+            <QuestionCircleOutlined />
+          </Tooltip>
+        </span>
+      ),
       dataIndex: 'customMapping',
       render: (text: any) => (
         <span>
-          <Badge status={text ? 'success' : 'error'} text={text ? '已映射' : '未映射'} />
+          <Badge status={'success'} text={text ? '已映射' : '默认映射'} />
         </span>
       ),
     },

+ 25 - 16
src/pages/device/Instance/Detail/index.tsx

@@ -24,7 +24,7 @@ import SystemConst from '@/utils/const';
 import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { PermissionButton } from '@/components';
-import { ExclamationCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import { ExclamationCircleOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
 import Service from '@/pages/device/Instance/service';
 import useLocation from '@/hooks/route/useLocation';
 import { onlyMessage } from '@/utils/util';
@@ -404,21 +404,30 @@ const InstanceDetail = observer(() => {
           </Space>
         </div>
       }
-      // extra={[
-      //   statusMap[0],
-      //   <Button key="2">
-      //     {intl.formatMessage({
-      //       id: 'pages.device.productDetail.disable',
-      //       defaultMessage: '停用',
-      //     })}
-      //   </Button>,
-      //   <Button key="1" type="primary">
-      //     {intl.formatMessage({
-      //       id: 'pages.device.productDetail.setting',
-      //       defaultMessage: '应用配置',
-      //     })}
-      //   </Button>,
-      // ]}
+      extra={[
+        // <Button key="2">
+        //   {intl.formatMessage({
+        //     id: 'pages.device.productDetail.disable',
+        //     defaultMessage: '停用',
+        //   })}
+        // </Button>,
+        // <Button key="1" type="primary">
+        //   {intl.formatMessage({
+        //     id: 'pages.device.productDetail.setting',
+        //     defaultMessage: '应用配置',
+        //   })}
+        // </Button>,
+        <SyncOutlined
+          onClick={() => {
+            getDetail(params.id);
+            service.getConfigMetadata(params.id).then((config) => {
+              InstanceModel.config = config?.result || [];
+            });
+          }}
+          style={{ fontSize: 20, marginRight: 20 }}
+          key="1"
+        />,
+      ]}
     >
       {list.find((k) => k.key === InstanceModel.active)?.component}
     </PageContainer>

+ 41 - 0
src/pages/device/Product/Detail/Access/index.less

@@ -30,3 +30,44 @@
   padding: 20px;
   background-color: #e6e6e6;
 }
+
+.driver {
+  .driver-next-btn {
+    color: #fff !important;
+    font-size: 14px !important;
+    line-height: 22px !important;
+    text-shadow: 0 0 black !important;
+    background-color: #2f54eb !important;
+  }
+
+  .driver-prev-btn {
+    font-size: 14px !important;
+    line-height: 22px !important;
+    background-color: #fff !important;
+  }
+  .driver-prev-btn.driver-disabled {
+    display: none !important;
+  }
+
+  .driver-close-btn {
+    padding: 5px 0 0 0 !important;
+    color: #828282 !important;
+    font-size: 14px !important;
+    background-color: #fff !important;
+    border: none !important;
+  }
+
+  .driver-popover-description {
+    margin-bottom: 10px !important;
+  }
+
+  .driver-popover-title {
+    display: flex !important;
+    justify-content: space-between !important;
+
+    #guide {
+      margin-top: 3px;
+      font-size: 14px;
+    }
+  }
+}

+ 142 - 10
src/pages/device/Product/Detail/Access/index.tsx

@@ -24,6 +24,9 @@ import { QuestionCircleOutlined } from '@ant-design/icons';
 import TitleComponent from '@/components/TitleComponent';
 import usePermissions from '@/hooks/permission';
 import { onlyMessage } from '@/utils/util';
+import Driver from 'driver.js';
+import 'driver.js/dist/driver.min.css';
+import './index.less';
 
 const componentMap = {
   string: 'Input',
@@ -53,6 +56,74 @@ const Access = () => {
 
   const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
 
+  const steps = [
+    {
+      element: '#metadata-driver',
+      popover: {
+        className: 'driver',
+        title: `<div id='title'>配置物模型</div><div id='guide'>1/3</div>`,
+        description: `配置产品物模型,实现设备在云端的功能描述。`,
+        position: 'bottom',
+      },
+    },
+    {
+      element: '.ant-switch',
+      popover: {
+        className: 'driver',
+        title: `<div id='title'>启用产品</div><div id='guide'>2/3</div>`,
+        description: '启用产品后,可在产品下新增设备。',
+        position: 'bottom',
+      },
+    },
+    {
+      element: '.ant-descriptions-item-label',
+      popover: {
+        className: 'driver',
+        title: `<div id='title'>添加设备</div><div id='guide'>3/3</div>`,
+        description: '添加设备,并连接到平台。',
+        position: 'bottom',
+      },
+    },
+  ];
+  const steps1 = [
+    {
+      element: '#driver-config',
+      popover: {
+        className: 'driver',
+        title: `<div id='title'>填写配置</div><div id='guide'>1/4</div>`,
+        description: `填写设备接入所需的配置参数。`,
+        position: 'right',
+      },
+    },
+    {
+      element: '#metadata-driver',
+      popover: {
+        className: 'driver',
+        title: `<div id='title'>配置物模型</div><div id='guide'>2/4</div>`,
+        description: `配置产品物模型,实现设备在云端的功能描述。`,
+        position: 'bottom',
+      },
+    },
+    {
+      element: '.ant-switch',
+      popover: {
+        className: 'driver',
+        title: `<div id='title'>启用产品</div><div id='guide'>3/4</div>`,
+        description: '启用产品后,可在产品下新增设备。',
+        position: 'bottom',
+      },
+    },
+    {
+      element: '.ant-descriptions-item-label',
+      popover: {
+        className: 'driver',
+        title: `<div id='title'>添加设备</div><div id='guide'>4/4</div>`,
+        description: '添加设备,并连接到平台。',
+        position: 'bottom',
+      },
+    },
+  ];
+
   const queryAccessDetail = (id: string) => {
     service
       .queryGatewayDetail({
@@ -202,15 +273,44 @@ const Access = () => {
   const id = productModel.current?.id;
 
   useEffect(() => {
-    if (id) {
-      productService
-        .getConfigMetadata(id)
-        .then((resp: { result: SetStateAction<ConfigMetadata[]> }) => {
-          setMetadata(resp.result);
-        });
-    }
+    const driver = new Driver({
+      allowClose: false,
+      doneBtnText: '我知道了',
+      closeBtnText: '不在提示',
+      nextBtnText: '下一步',
+      prevBtnText: '上一步',
+      // onDeselected:(e)=>{
+      //   console.log(e)
+      // },
+      onNext: () => {
+        console.log('下一步');
+      },
+      onPrevious: () => {
+        console.log('上一步');
+      },
+      onReset: () => {
+        console.log('关闭');
+      },
+      // onDeselected:()=>{
+      //   console.log('oncolse')
+      // }
+    });
     setVisible(!!productModel.current?.accessId);
     if (productModel.current?.accessId) {
+      if (id) {
+        productService
+          .getConfigMetadata(id)
+          .then((resp: { result: SetStateAction<ConfigMetadata[]> }) => {
+            setMetadata(resp.result);
+            if (resp.result && resp.result.length > 0) {
+              driver.defineSteps(steps1);
+              driver.start();
+            } else {
+              driver.defineSteps(steps);
+              driver.start();
+            }
+          });
+      }
       queryAccessDetail(productModel.current?.accessId);
       getConfigDetail(
         productModel.current?.messageProtocol || '',
@@ -221,9 +321,35 @@ const Access = () => {
           setDataSource(resp.result);
         }
       });
+    } else {
+      if (id) {
+        productService
+          .getConfigMetadata(id)
+          .then((resp: { result: SetStateAction<ConfigMetadata[]> }) => {
+            setMetadata(resp.result);
+          });
+      }
     }
   }, [productModel.current]);
 
+  // useEffect(() => {
+  //   console.log(productModel.current)
+  //   console.log(productModel.current?.accessId)
+  //   const driver = new Driver({
+  //     allowClose: false,
+  //     doneBtnText: '我知道了',
+  //     closeBtnText: '不在提示',
+  //     nextBtnText: '下一步',
+  //     prevBtnText: '上一步'
+  //   });
+  //   if (productModel.current?.accessId) {
+  //     setTimeout(() => {
+  //       driver.defineSteps(steps)
+  //       driver.start();
+  //     }, 1000)
+  //   }
+  // }, [])
+
   const form = createForm({
     validateFirst: true,
     initialValues: productModel.current?.configuration,
@@ -298,12 +424,12 @@ const Access = () => {
               title: (
                 <TitleComponent
                   data={
-                    <span>
+                    <div className="config">
                       {item.name}
                       <Tooltip title="此配置来自于该产品接入方式所选择的协议">
                         <QuestionCircleOutlined />
                       </Tooltip>
-                    </span>
+                    </div>
                   }
                 />
               ),
@@ -354,6 +480,10 @@ const Access = () => {
     );
   };
 
+  // useEffect(() => {
+
+  // }, [])
+
   return (
     <div>
       {!visible ? (
@@ -454,7 +584,9 @@ const Access = () => {
                   : '暂无连接信息'}
               </div>
 
-              <div className={styles.item}>{renderConfigCard()}</div>
+              <div className={styles.item} id="driver-config">
+                {renderConfigCard()}
+              </div>
             </div>
           </Col>
           {config?.routes && config?.routes?.length > 0 && (

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

@@ -54,7 +54,7 @@ const ProductDetail = observer(() => {
     {
       key: 'metadata',
       tab: (
-        <>
+        <div id="metadata-driver">
           {intl.formatMessage({
             id: 'pages.device.instanceDetail.metadata',
             defaultMessage: '物模型',
@@ -82,7 +82,7 @@ const ProductDetail = observer(() => {
           >
             <QuestionCircleOutlined style={{ marginLeft: 5 }} />
           </Tooltip>
-        </>
+        </div>
       ),
       component: <Metadata type="product" />,
     },

+ 10 - 3
src/pages/home/components/Steps.tsx

@@ -7,17 +7,19 @@ interface StepItemProps {
   content: string | React.ReactNode;
   onClick: () => void;
   url?: string;
+  after?: any;
 }
 
 interface StepsProps {
   title: string | React.ReactNode;
   data: StepItemProps[];
+  style?: any;
 }
 
 const ItemDefaultImg = require('/public/images/home/bottom-1.png');
 const StepsItem = (props: StepItemProps) => {
   return (
-    <div className={'step-item step-bar arrow-1'}>
+    <div className={props.after ? 'step-item step-bar ' : 'step-item step-bar arrow-1'}>
       <div className={'step-item-title'} onClick={props.onClick}>
         <div className={'step-item-img'}>
           <img src={props.url || ItemDefaultImg} />
@@ -35,9 +37,14 @@ const Steps = (props: StepsProps) => {
       <Title title={props.title} />
       <div
         className={'home-step-items'}
-        style={{ gridTemplateColumns: `repeat(${props.data ? props.data.length : 1}, 1fr)` }}
+        style={{
+          gridTemplateColumns: `repeat(${props.data ? props.data.length : 1}, 1fr)`,
+          minHeight: props.style?.height,
+          gridColumnGap: props?.style?.gridColumnGap,
+        }}
       >
-        {props.data && props.data.map((item) => <StepsItem {...item} />)}
+        {props.data &&
+          props.data.map((item) => <StepsItem {...item} after={props.style ? true : false} />)}
       </div>
     </div>
   );

+ 1 - 1
src/pages/home/comprehensive/index.tsx

@@ -171,7 +171,7 @@ const Comprehensive = () => {
                 {
                   name: '产品数量',
                   value: productCount,
-                  children: '',
+                  children: require('/public/images/home/top-2.png'),
                 },
                 {
                   name: '设备数量',

+ 9 - 19
src/pages/home/device/index.tsx

@@ -79,8 +79,12 @@ const Device = () => {
 
   return (
     <Row gutter={24}>
-      <Col span={24}>
+      <Col span={18}>
         <Steps
+          style={{
+            height: 275,
+            gridColumnGap: 20,
+          }}
           title={
             <span>
               设备接入推荐步骤
@@ -161,24 +165,10 @@ const Device = () => {
           ]}
         />
       </Col>
-      <Col span={18} style={{ marginTop: 24 }}>
-        <Body
-          title={'平台架构图'}
-          english={'PLATFORM ARCHITECTURE DIAGRAM'}
-          url={require('/public/images/home/content1.png')}
-        />
-        {/* <Guide
-          title="物联网引导"
-          data={guideList}
-        // jump={(auth: boolean, url: string, param: string) => {
-        //   pageJump(auth, url, param);
-        // }}
-        /> */}
-      </Col>
-      <Col span={6} style={{ marginTop: 24 }}>
+      <Col span={6}>
         <Statistics
           style={{ gridTemplateColumns: 'repeat(1, 1fr)' }}
-          height={448}
+          // height={448}
           data={[
             {
               name: '产品数量',
@@ -210,9 +200,9 @@ const Device = () => {
           }
         />
       </Col>
-      {/* <Col span={24}>
+      <Col span={24} style={{ marginTop: 24 }}>
         <Body title={'平台架构图'} english={'PLATFORM ARCHITECTURE DIAGRAM'} />
-      </Col> */}
+      </Col>
 
       <ProductChoose
         visible={productVisible}

+ 6 - 0
src/pages/link/Type/Detail/index.less

@@ -7,3 +7,9 @@
     border-top: none;
   }
 }
+
+.parser {
+  width: 100%;
+  padding: 16px;
+  background-color: #fafafa;
+}

+ 237 - 66
src/pages/link/Type/Detail/index.tsx

@@ -15,9 +15,9 @@ import {
 } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
 import { useEffect, useMemo, useRef } from 'react';
-import { Field, FieldDataSource } from '@formily/core';
+import type { Field, FieldDataSource } from '@formily/core';
 import { createForm, onFieldReact, onFieldValueChange } from '@formily/core';
-import { Card } from 'antd';
+import { Card, Col, Row } from 'antd';
 import styles from './index.less';
 import { onlyMessage, useAsyncDataSource } from '@/utils/util';
 import { service } from '../index';
@@ -27,6 +27,7 @@ import { Store } from 'jetlinks-store';
 import { PermissionButton } from '@/components';
 import usePermissions from '@/hooks/permission';
 import { action } from '@formily/reactive';
+import FMonacoEditor from '@/components/FMonacoEditor';
 
 /**
  *  根据类型过滤配置信息
@@ -187,6 +188,16 @@ const Save = observer(() => {
               state.dataSource = _ports?.ports?.map((i: any) => ({ label: i, value: i }));
             });
           });
+          // onFieldValueChange('grid.cluster.cluster.*.layout2.host', async (field, f4) => {
+          //   const host = (field as Field).value;
+          //   const value = (field.query('.serverId').take() as Field).value;
+          //   const type = (field.query('type').take() as Field).value;
+          //   const response = await getResourceById(value, type);
+          //   const _ports = response.find((item) => item.host === host);
+          //   f4.setFieldState(field.query('.port').take(), async (state) => {
+          //     state.dataSource = _ports?.ports?.map((i: any) => ({ label: i, value: i }));
+          //   });
+          // });
         },
       }),
     [],
@@ -221,6 +232,7 @@ const Save = observer(() => {
       FormCollapse,
       ArrayCollapse,
       FAutoComplete,
+      FMonacoEditor,
     },
   });
 
@@ -233,12 +245,11 @@ const Save = observer(() => {
         value: item.id,
       })),
     );
-
   const clusterConfig: ISchema = {
     type: 'void',
     'x-component': 'FormGrid',
     'x-component-props': {
-      maxColumns: 3,
+      maxColumns: 2,
       minColumns: 1,
       columnGap: 48,
     },
@@ -528,44 +539,12 @@ const Save = observer(() => {
           },
         },
       },
-      parserType: {
-        // TCP
-        required: true,
-        title: '粘拆包规则',
-        'x-decorator-props': {
-          gridSpan: 1,
-          tooltip: '处理TCP粘拆包的方式',
-          layout: 'vertical',
-          labelAlign: 'left',
-        },
-        'x-visible': false,
-        'x-decorator': 'FormItem',
-        'x-component': 'Select',
-        'x-component-props': {
-          placeholder: '请选择粘拆包规则',
-        },
-        enum: [
-          { value: 'DIRECT', label: '不处理' },
-          { value: 'delimited', label: '分隔符' },
-          { value: 'script', label: '自定义脚本' },
-          { value: 'fixed_length', label: '固定长度' },
-        ],
-        'x-reactions': {
-          dependencies: ['type'],
-          fulfill: {
-            state: {
-              // visible: '{{$deps[0]==="UDP"}}',
-              visible: '{{["TCP_SERVER"].includes($deps[0])}}',
-            },
-          },
-        },
-      },
       secure: {
         title: '开启DTLS',
         'x-decorator': 'FormItem',
         'x-component': 'Radio.Group',
         'x-decorator-props': {
-          gridSpan: 1,
+          gridSpan: 2,
           labelAlign: 'left',
           layout: 'vertical',
         },
@@ -633,6 +612,190 @@ const Save = observer(() => {
           },
         },
       },
+      parserType: {
+        // TCP
+        required: true,
+        title: '粘拆包规则',
+        'x-decorator-props': {
+          gridSpan: 2,
+          tooltip: '处理TCP粘拆包的方式',
+          layout: 'vertical',
+          labelAlign: 'left',
+        },
+        'x-visible': false,
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        'x-component-props': {
+          placeholder: '请选择粘拆包规则',
+          style: { width: 'calc(50% - 24px)' },
+        },
+        enum: [
+          { value: 'DIRECT', label: '不处理' },
+          { value: 'delimited', label: '分隔符' },
+          { value: 'script', label: '自定义脚本' },
+          { value: 'fixed_length', label: '固定长度' },
+        ],
+        'x-reactions': {
+          dependencies: ['type'],
+          fulfill: {
+            state: {
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible: '{{["TCP_SERVER"].includes($deps[0])}}',
+            },
+          },
+        },
+      },
+      parserConfiguration: {
+        type: 'object',
+        'x-component': 'FormGrid',
+        'x-decorator': 'FormItem',
+        'x-component-props': {
+          maxColumns: 2,
+          minColumns: 1,
+          className: styles.parser,
+        },
+        'x-decorator-props': {
+          gridSpan: 2,
+        },
+        'x-reactions': [
+          {
+            dependencies: ['.parserType'],
+            fulfill: {
+              state: {
+                visible: '{{["delimited", "script", "fixed_length"].includes($deps[0])}}',
+              },
+            },
+          },
+        ],
+        properties: {
+          delimited: {
+            title: '分隔符',
+            'x-component': 'Input',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              labelAlign: 'left',
+              layout: 'vertical',
+              gridSpan: 1,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入分隔符',
+              },
+            ],
+            'x-component-props': {
+              style: { width: '50%' },
+              placeholder: '请输入分隔符',
+            },
+            'x-reactions': [
+              {
+                dependencies: ['..parserType'],
+                fulfill: {
+                  state: {
+                    visible: '{{$deps[0] === "delimited"}}',
+                  },
+                },
+              },
+            ],
+          },
+          lang: {
+            title: '脚本语言',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            'x-disabled': true,
+            'x-hidden': true,
+            'x-value': 'javascript',
+            'x-decorator-props': {
+              labelAlign: 'left',
+              layout: 'vertical',
+              gridSpan: 2,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择',
+              },
+            ],
+            'x-reactions': [
+              {
+                dependencies: ['..parserType'],
+                fulfill: {
+                  state: {
+                    // visible: '{{$deps[0] === "script"}}',
+                    value: '{{$deps[0] === "script" ? "javascript" : ""}}',
+                  },
+                },
+              },
+            ],
+            enum: [{ label: 'JavaScript', value: 'javascript' }],
+          },
+          script: {
+            title: '脚本解析',
+            'x-component': 'FMonacoEditor',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              labelAlign: 'left',
+              layout: 'vertical',
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              language: 'javascript',
+              height: 200,
+              editorDidMount: (editor1: any) => {
+                editor1.onDidScrollChange?.(() => {
+                  editor1.getAction('editor.action.formatDocument').run();
+                });
+              },
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入脚本',
+              },
+            ],
+            'x-reactions': [
+              {
+                dependencies: ['..parserType'],
+                fulfill: {
+                  state: {
+                    visible: '{{$deps[0] === "script"}}',
+                  },
+                },
+              },
+            ],
+          },
+          size: {
+            title: '长度值',
+            'x-component': 'NumberPicker',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              labelAlign: 'left',
+              layout: 'vertical',
+              gridSpan: 1,
+            },
+            'x-validator': [
+              {
+                required: true,
+                message: '请输入长度值',
+              },
+            ],
+            'x-component-props': {
+              style: { width: '50%' },
+              placeholder: '请输入长度值',
+            },
+            'x-reactions': [
+              {
+                dependencies: ['..parserType'],
+                fulfill: {
+                  state: {
+                    visible: '{{$deps[0] === "fixed_length"}}',
+                  },
+                },
+              },
+            ],
+          },
+        },
+      },
     },
   };
   const schema: ISchema = {
@@ -642,7 +805,7 @@ const Save = observer(() => {
         type: 'void',
         'x-component': 'FormGrid',
         'x-component-props': {
-          maxColumns: 3,
+          maxColumns: 2,
           minColumns: 1,
           columnGap: 48,
         },
@@ -698,8 +861,12 @@ const Save = observer(() => {
               { label: '共享配置', value: true },
               { label: '独立配置', value: false },
             ],
+            'x-component-props': {
+              buttonStyle: 'solid',
+              optionType: 'button',
+            },
             'x-decorator-props': {
-              gridSpan: 3,
+              gridSpan: 2,
               tooltip:
                 '共享配置:集群下所有节点共用同一配置\r\n' + '独立配置:集群下不同节点使用不同配置',
             },
@@ -724,7 +891,7 @@ const Save = observer(() => {
               },
             ],
             'x-decorator-props': {
-              gridSpan: 3,
+              gridSpan: 2,
             },
             properties: {
               panel1: {
@@ -740,7 +907,7 @@ const Save = observer(() => {
             type: 'void',
             'x-decorator': 'FormItem',
             'x-decorator-props': {
-              gridSpan: 3,
+              gridSpan: 2,
             },
             'x-reactions': {
               dependencies: ['.shareCluster', 'type'],
@@ -789,7 +956,7 @@ const Save = observer(() => {
             'x-component': 'Input.TextArea',
             'x-decorator': 'FormItem',
             'x-decorator-props': {
-              gridSpan: 3,
+              gridSpan: 2,
             },
             'x-component-props': {
               placeholder: '请输入说明',
@@ -830,30 +997,34 @@ const Save = observer(() => {
   return (
     <PageContainer>
       <Card>
-        <Form form={form} layout="vertical" style={{ padding: 30 }}>
-          <SchemaField
-            schema={schema}
-            scope={{
-              formCollapse,
-              useAsyncDataSource,
-              useAsyncData,
-              getSupports,
-              getResourcesClusters,
-              getCertificates,
-            }}
-          />
-          <FormButtonGroup.Sticky>
-            <FormButtonGroup.FormItem>
-              <PermissionButton
-                type="primary"
-                isPermission={getOtherPermission(['add', 'update'])}
-                onClick={() => handleSave()}
-              >
-                保存
-              </PermissionButton>
-            </FormButtonGroup.FormItem>
-          </FormButtonGroup.Sticky>
-        </Form>
+        <Row gutter={24}>
+          <Col span={16}>
+            <Form form={form} layout="vertical" style={{ padding: 30 }}>
+              <SchemaField
+                schema={schema}
+                scope={{
+                  formCollapse,
+                  useAsyncDataSource,
+                  useAsyncData,
+                  getSupports,
+                  getResourcesClusters,
+                  getCertificates,
+                }}
+              />
+              <FormButtonGroup.Sticky>
+                <FormButtonGroup.FormItem>
+                  <PermissionButton
+                    type="primary"
+                    isPermission={getOtherPermission(['add', 'update'])}
+                    onClick={() => handleSave()}
+                  >
+                    保存
+                  </PermissionButton>
+                </FormButtonGroup.FormItem>
+              </FormButtonGroup.Sticky>
+            </Form>
+          </Col>
+        </Row>
       </Card>
     </PageContainer>
   );