sun-chaochao 3 лет назад
Родитель
Сommit
ff4fbb5d37

+ 360 - 0
src/pages/system/DataSource/Management/DataRow.tsx

@@ -0,0 +1,360 @@
+import { Form, FormGrid, FormItem, Input, Password, Select } from '@formily/antd';
+import { createForm } from '@formily/core';
+import type { ISchema } from '@formily/react';
+import { createSchemaField } from '@formily/react';
+import { message, Modal } from 'antd';
+import { Store } from 'jetlinks-store';
+import { service } from '@/pages/system/DataSource';
+
+interface Props {
+  close: () => void;
+  reload: () => void;
+  data: Partial<DataSourceItem>;
+}
+
+const DataRow = (props: Props) => {
+  const form = createForm({
+    validateFirst: true,
+    initialValues: props.data,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Password,
+      Select,
+      FormGrid,
+    },
+  });
+
+  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: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入名称',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入名称',
+              },
+            ],
+            required: true,
+          },
+          typeId: {
+            title: '类型',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请选择类型',
+            },
+            name: 'typeId',
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择类型',
+              },
+            ],
+            required: true,
+            enum: Store.get('datasource-type'),
+          },
+          'shareConfig.url': {
+            title: 'URL',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入r2bdc或者jdbc连接地址,示例:r2dbc:mysql://127.0.0.1:3306/test',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入URL',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rdb"}}',
+                },
+              },
+            },
+          },
+          'shareConfig.adminUrl': {
+            title: '管理地址',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入管理地址,示例:http://localhost:15672',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入管理地址',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rabbitmq"}}',
+                },
+              },
+            },
+          },
+          'shareConfig.addresses': {
+            title: '链接地址',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入连接地址,示例:localhost:5672',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入链接地址',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rabbitmq"}}',
+                },
+              },
+            },
+          },
+          'shareConfig.username': {
+            title: '用户名',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入用户名',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入用户名',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{["rdb","rabbitmq"].includes($deps[0])}}',
+                },
+              },
+            },
+          },
+          'shareConfig.password': {
+            title: '密码',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Password',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入密码',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入密码',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{["rdb","rabbitmq"].includes($deps[0])}}',
+                },
+              },
+            },
+          },
+          'shareConfig.virtualHost': {
+            title: '虚拟域',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入虚拟域',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入虚拟域',
+              },
+            ],
+            required: true,
+            default: '/',
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rabbitmq"}}',
+                },
+              },
+            },
+          },
+          'shareConfig.schema': {
+            title: 'schema',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入schema',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入schema',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rdb"}}',
+                },
+              },
+            },
+          },
+          description: {
+            title: '说明',
+            'x-component': 'Input.TextArea',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              rows: 3,
+              showCount: true,
+              maxLength: 200,
+              placeholder: '请输入说明',
+            },
+          },
+        },
+      },
+    },
+  };
+
+  const handleSave = async () => {
+    const data: any = await form.submit();
+    const response: any = props.data?.id ? await service.update(data) : await service.save(data);
+    if (response.status === 200) {
+      message.success('保存成功');
+      props.reload();
+    }
+  };
+
+  return (
+    <Modal
+      width={'55vw'}
+      title={`${props.data?.id ? '编辑' : '新增'}数据源`}
+      visible
+      onCancel={() => {
+        props.close();
+      }}
+      onOk={() => {
+        handleSave();
+      }}
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+
+export default DataRow;

+ 87 - 0
src/pages/system/DataSource/Management/DataTable.tsx

@@ -0,0 +1,87 @@
+import { Form, FormGrid, FormItem, Input, Password, Select } from '@formily/antd';
+import { createForm } from '@formily/core';
+import type { ISchema } from '@formily/react';
+import { createSchemaField } from '@formily/react';
+import { message, Modal } from 'antd';
+import { service } from '@/pages/system/DataSource';
+
+interface Props {
+  close: () => void;
+  reload: () => void;
+  data: any;
+}
+
+const DataTable = (props: Props) => {
+  const form = createForm({
+    validateFirst: true,
+    initialValues: props.data,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Password,
+      Select,
+      FormGrid,
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      name: {
+        title: '名称',
+        type: 'string',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-decorator-props': {
+          gridSpan: 1,
+        },
+        'x-component-props': {
+          placeholder: '请输入名称',
+        },
+        name: 'name',
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+          {
+            required: true,
+            message: '请输入名称',
+          },
+        ],
+        required: true,
+      },
+    },
+  };
+
+  const handleSave = async () => {
+    const data: any = await form.submit();
+    const response: any = props.data?.id ? await service.update(data) : await service.save(data);
+    if (response.status === 200) {
+      message.success('保存成功');
+      props.reload();
+    }
+  };
+
+  return (
+    <Modal
+      title={`${props.data?.name ? '编辑' : '新增'}`}
+      visible
+      onCancel={() => {
+        props.close();
+      }}
+      onOk={() => {
+        handleSave();
+      }}
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+
+export default DataTable;

+ 263 - 0
src/pages/system/DataSource/Management/index.tsx

@@ -0,0 +1,263 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { Card, Col, Input, Popconfirm, Row, Tree } from 'antd';
+import { useEffect, useRef, useState } from 'react';
+import { service } from '@/pages/system/DataSource';
+import { useIntl, useLocation } from 'umi';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import PermissionButton from '@/components/PermissionButton';
+import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
+import usePermissions from '@/hooks/permission';
+import SearchComponent from '@/components/SearchComponent';
+import ProTable from '@jetlinks/pro-table';
+import DataTable from './DataTable';
+
+const Management = () => {
+  const location = useLocation<{ id: string }>();
+  const id = (location as any).query?.id;
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const [param, setParam] = useState({});
+  const { permission: userPermission } = usePermissions('system/User');
+
+  const [rdbList, setRdbList] = useState<any[]>([]);
+  const [defaultSelectedKeys, setDefaultSelectedKeys] = useState<any[]>([]);
+  const [visible, setVisible] = useState<boolean>(false);
+  const [current, setCurrent] = useState<any>({});
+
+  useEffect(() => {
+    service.rdbTree(id).then((resp) => {
+      if (resp.status === 200) {
+        setRdbList(resp.result);
+        setDefaultSelectedKeys([resp.result[0]?.name]);
+      }
+    });
+  }, []);
+
+  useEffect(() => {
+    actionRef.current?.reload();
+  }, [defaultSelectedKeys]);
+
+  const columns: ProColumns<DataSourceType>[] = [
+    {
+      title: '列名',
+      dataIndex: 'name',
+      ellipsis: true,
+    },
+    {
+      title: '类型',
+      dataIndex: 'type',
+      ellipsis: true,
+    },
+    {
+      title: '长度',
+      dataIndex: 'length',
+      ellipsis: true,
+    },
+    {
+      title: '精度',
+      dataIndex: 'scale',
+      ellipsis: true,
+    },
+    {
+      title: '不能为空',
+      dataIndex: 'notnull',
+      ellipsis: true,
+      render: (text, record) => {
+        console.log(record.notnull);
+        return <span>{text ? '是' : '否'}</span>;
+      },
+      valueType: 'select',
+      valueEnum: {
+        true: {
+          text: '是',
+          status: true,
+        },
+        false: {
+          text: '否',
+          status: false,
+        },
+      },
+    },
+    {
+      dataIndex: 'description',
+      title: '说明',
+      ellipsis: true,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      render: (_, record) => [
+        <PermissionButton
+          style={{ padding: 0 }}
+          type="link"
+          isPermission={userPermission.update}
+          key="editable"
+          onClick={() => {
+            setCurrent(record);
+            setVisible(true);
+          }}
+          tooltip={{
+            title: intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '编辑',
+            }),
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          type="link"
+          key="delete"
+          style={{ padding: 0 }}
+          isPermission={userPermission.delete}
+          tooltip={{ title: '删除' }}
+        >
+          <Popconfirm
+            onConfirm={async () => {
+              // await service.remove(record.id);
+              // actionRef.current?.reload();
+            }}
+            title="确认删除?"
+          >
+            <DeleteOutlined />
+          </Popconfirm>
+        </PermissionButton>,
+      ],
+    },
+  ];
+
+  return (
+    <PageContainer>
+      <Card>
+        <Row gutter={24}>
+          <Col span={6}>
+            <Input.Search
+              placeholder="请输入"
+              onSearch={() => {}}
+              style={{ width: '100%', marginBottom: 10 }}
+            />
+            <Tree
+              showLine
+              defaultExpandAll
+              height={500}
+              selectedKeys={[...defaultSelectedKeys]}
+              onSelect={(selectedKeys) => {
+                if (!selectedKeys.includes('tables')) {
+                  setDefaultSelectedKeys([...selectedKeys]);
+                }
+              }}
+            >
+              <Tree.TreeNode
+                title={() => {
+                  return (
+                    <div style={{ display: 'flex', justifyContent: 'space-between', width: 230 }}>
+                      <div>数据源名称</div>
+                      <div>
+                        <PlusOutlined
+                          onClick={() => {
+                            setCurrent({});
+                            setVisible(true);
+                          }}
+                        />
+                      </div>
+                    </div>
+                  );
+                }}
+                key={'tables'}
+              >
+                {rdbList.map((item) => (
+                  <Tree.TreeNode
+                    key={item.name}
+                    title={() => {
+                      return (
+                        <div
+                          style={{ display: 'flex', justifyContent: 'space-between', width: 200 }}
+                        >
+                          <div>{item.name}</div>
+                          <div>
+                            <PlusOutlined
+                              onClick={() => {
+                                setCurrent(item);
+                                setVisible(true);
+                              }}
+                            />
+                          </div>
+                        </div>
+                      );
+                    }}
+                  />
+                ))}
+              </Tree.TreeNode>
+            </Tree>
+          </Col>
+          <Col span={18}>
+            <div>
+              <SearchComponent<DataSourceType>
+                field={columns}
+                target="datasource-manage"
+                onSearch={(data) => {
+                  // 重置分页数据
+                  actionRef.current?.reset?.();
+                  setParam(data);
+                }}
+              />
+              <ProTable<DataSourceType>
+                actionRef={actionRef}
+                params={param}
+                columns={columns}
+                search={false}
+                rowKey="name"
+                headerTitle={
+                  <PermissionButton
+                    onClick={() => {}}
+                    isPermission={userPermission.add}
+                    key="add"
+                    icon={<PlusOutlined />}
+                    type="primary"
+                  >
+                    新增列
+                  </PermissionButton>
+                }
+                request={async (params) => {
+                  if (defaultSelectedKeys.length > 0) {
+                    const response = await service.rdbTables(id, defaultSelectedKeys[0], {
+                      ...params,
+                      sorts: [{ name: 'createTime', order: 'desc' }],
+                    });
+                    return {
+                      result: { data: response.result?.columns || [] },
+                      success: true,
+                      status: 200,
+                    } as any;
+                  } else {
+                    return {
+                      result: { data: [] },
+                      success: true,
+                      status: 200,
+                    } as any;
+                  }
+                }}
+              />
+            </div>
+          </Col>
+        </Row>
+      </Card>
+      {visible && (
+        <DataTable
+          data={current}
+          reload={() => {
+            setVisible(false);
+          }}
+          close={() => {
+            setVisible(false);
+          }}
+        />
+      )}
+    </PageContainer>
+  );
+};
+
+export default Management;

+ 360 - 0
src/pages/system/DataSource/Save/index.tsx

@@ -0,0 +1,360 @@
+import { Form, FormGrid, FormItem, Input, Password, Select } from '@formily/antd';
+import { createForm } from '@formily/core';
+import type { ISchema } from '@formily/react';
+import { createSchemaField } from '@formily/react';
+import { message, Modal } from 'antd';
+import { Store } from 'jetlinks-store';
+import { service } from '@/pages/system/DataSource';
+
+interface Props {
+  close: () => void;
+  reload: () => void;
+  data: Partial<DataSourceItem>;
+}
+
+const Save = (props: Props) => {
+  const form = createForm({
+    validateFirst: true,
+    initialValues: props.data,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Password,
+      Select,
+      FormGrid,
+    },
+  });
+
+  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: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入名称',
+            },
+            name: 'name',
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入名称',
+              },
+            ],
+            required: true,
+          },
+          typeId: {
+            title: '类型',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Select',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请选择类型',
+            },
+            name: 'typeId',
+            'x-validator': [
+              {
+                required: true,
+                message: '请选择类型',
+              },
+            ],
+            required: true,
+            enum: Store.get('datasource-type'),
+          },
+          'shareConfig.url': {
+            title: 'URL',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入r2bdc或者jdbc连接地址,示例:r2dbc:mysql://127.0.0.1:3306/test',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入URL',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rdb"}}',
+                },
+              },
+            },
+          },
+          'shareConfig.adminUrl': {
+            title: '管理地址',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入管理地址,示例:http://localhost:15672',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入管理地址',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rabbitmq"}}',
+                },
+              },
+            },
+          },
+          'shareConfig.addresses': {
+            title: '链接地址',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入连接地址,示例:localhost:5672',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入链接地址',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rabbitmq"}}',
+                },
+              },
+            },
+          },
+          'shareConfig.username': {
+            title: '用户名',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入用户名',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入用户名',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{["rdb","rabbitmq"].includes($deps[0])}}',
+                },
+              },
+            },
+          },
+          'shareConfig.password': {
+            title: '密码',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Password',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+            'x-component-props': {
+              placeholder: '请输入密码',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入密码',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{["rdb","rabbitmq"].includes($deps[0])}}',
+                },
+              },
+            },
+          },
+          'shareConfig.virtualHost': {
+            title: '虚拟域',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入虚拟域',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入虚拟域',
+              },
+            ],
+            required: true,
+            default: '/',
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rabbitmq"}}',
+                },
+              },
+            },
+          },
+          'shareConfig.schema': {
+            title: 'schema',
+            type: 'string',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+            'x-visible': false,
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              placeholder: '请输入schema',
+            },
+            'x-validator': [
+              {
+                max: 64,
+                message: '最多可输入64个字符',
+              },
+              {
+                required: true,
+                message: '请输入schema',
+              },
+            ],
+            required: true,
+            'x-reactions': {
+              dependencies: ['typeId'],
+              fulfill: {
+                state: {
+                  visible: '{{$deps[0]==="rdb"}}',
+                },
+              },
+            },
+          },
+          description: {
+            title: '说明',
+            'x-component': 'Input.TextArea',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 2,
+            },
+            'x-component-props': {
+              rows: 3,
+              showCount: true,
+              maxLength: 200,
+              placeholder: '请输入说明',
+            },
+          },
+        },
+      },
+    },
+  };
+
+  const handleSave = async () => {
+    const data: any = await form.submit();
+    const response: any = props.data?.id ? await service.update(data) : await service.save(data);
+    if (response.status === 200) {
+      message.success('保存成功');
+      props.reload();
+    }
+  };
+
+  return (
+    <Modal
+      width={'55vw'}
+      title={`${props.data?.id ? '编辑' : '新增'}数据源`}
+      visible
+      onCancel={() => {
+        props.close();
+      }}
+      onOk={() => {
+        handleSave();
+      }}
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+
+export default Save;

+ 202 - 274
src/pages/system/DataSource/index.tsx

@@ -1,75 +1,75 @@
-import { PageContainer } from '@ant-design/pro-layout';
-import BaseCrud from '@/components/BaseCrud';
-import type { ProColumns } from '@jetlinks/pro-table';
-import { CurdModel } from '@/components/BaseCrud/model';
-import { message, Popconfirm, Tooltip } from 'antd';
-import { CloseCircleOutlined, EditOutlined, PlayCircleOutlined } from '@ant-design/icons';
-import type { ActionType } from '@jetlinks/pro-table';
-import { useEffect, useRef, useState } from 'react';
-import type { ISchema } from '@formily/json-schema';
 import Service from '@/pages/system/DataSource/service';
-import { from, mergeMap, toArray } from 'rxjs';
-import { map } from 'rxjs/operators';
+import { PageContainer } from '@ant-design/pro-layout';
+import SearchComponent from '@/components/SearchComponent';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { Badge, message, Popconfirm } from 'antd';
+import {
+  CloseCircleOutlined,
+  DatabaseOutlined,
+  DeleteOutlined,
+  EditOutlined,
+  PlayCircleOutlined,
+  PlusOutlined,
+} from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import { useEffect, useRef, useState } from 'react';
+import { observer } from '@formily/react';
+import { PermissionButton } from '@/components';
+import usePermissions from '@/hooks/permission';
+import Save from './Save';
+import { Store } from 'jetlinks-store';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import { useHistory } from 'umi';
 
 export const service = new Service('datasource/config');
 
-const stateIconMap = {
-  enabled: <CloseCircleOutlined />,
-  disabled: <PlayCircleOutlined />,
-};
-
-const DataSource = () => {
+const DataSource = observer(() => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
+  const history = useHistory<Record<string, string>>();
+
+  const { permission: userPermission } = usePermissions('system/User');
+  const [visible, setVisible] = useState<boolean>(false);
+  const [current, setCurrent] = useState<Partial<DataSourceItem>>({});
 
-  const [type, setType] = useState<DataSourceType[]>([]);
   useEffect(() => {
-    service
-      .getType()
-      .pipe(
-        mergeMap((data: DataSourceType[]) => from(data)),
-        map((i: DataSourceType) => ({ label: i.name, value: i.id })),
-        toArray(),
-      )
-      .subscribe((data: Partial<DataSourceType>[]) => {
-        setType(data as DataSourceType[]);
-      });
+    service.getType().then((res) => {
+      if (res.status === 200) {
+        const list = res?.result.map((pItem: any) => ({ label: pItem.name, value: pItem.id }));
+        Store.set('datasource-type', list);
+      }
+    });
   }, []);
 
   const columns: ProColumns<DataSourceItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
-      title: 'ID',
-      dataIndex: 'id',
-      width: 240,
-      sorter: true,
-      defaultSortOrder: 'ascend',
-    },
-    {
-      title: intl.formatMessage({
-        id: 'pages.table.name',
-        defaultMessage: '名称',
-      }),
+      title: '名称',
       dataIndex: 'name',
+      ellipsis: true,
     },
     {
-      title: intl.formatMessage({
-        id: 'pages.table.type',
-        defaultMessage: '类型',
-      }),
+      title: '类型',
       dataIndex: 'typeId',
+      ellipsis: true,
+      valueType: 'select',
+      request: async () => {
+        const res = await service.getType();
+        if (res.status === 200) {
+          const list = res.result.map((pItem: any) => ({ label: pItem.name, value: pItem.id }));
+          return list;
+        }
+        return [];
+      },
+      render: (_, row) =>
+        (Store.get('datasource-type') || []).find((item: any) => row.typeId === item.value)
+          ?.label || row.typeId,
+      filterMultiple: true,
     },
     {
-      title: intl.formatMessage({
-        id: 'pages.table.description',
-        defaultMessage: '说明',
-      }),
       dataIndex: 'description',
+      title: '说明',
+      ellipsis: true,
     },
     {
       title: intl.formatMessage({
@@ -77,7 +77,29 @@ const DataSource = () => {
         defaultMessage: '状态',
       }),
       dataIndex: 'state',
-      render: (value: any) => value.text,
+      valueType: 'select',
+      valueEnum: {
+        enabled: {
+          text: intl.formatMessage({
+            id: 'pages.searchTable.titleStatus.normal',
+            defaultMessage: '正常',
+          }),
+          status: 'enabled',
+        },
+        disabled: {
+          text: intl.formatMessage({
+            id: 'pages.searchTable.titleStatus.disable',
+            defaultMessage: '已禁用',
+          }),
+          status: 'disabled',
+        },
+      },
+      render: (_, record) => (
+        <Badge
+          status={record.state?.value === 'enabled' ? 'success' : 'error'}
+          text={record.state?.text}
+        />
+      ),
     },
     {
       title: intl.formatMessage({
@@ -85,247 +107,153 @@ const DataSource = () => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
-      render: (text, record) => [
-        <a key="editable" onClick={() => CurdModel.update(record)}>
-          <Tooltip
-            title={intl.formatMessage({
+      render: (_, record) => [
+        <PermissionButton
+          style={{ padding: 0 }}
+          type="link"
+          isPermission={userPermission.update}
+          key="editable"
+          onClick={() => {
+            setCurrent(record);
+            setVisible(true);
+          }}
+          tooltip={{
+            title: intl.formatMessage({
               id: 'pages.data.option.edit',
               defaultMessage: '编辑',
-            })}
-          >
-            <EditOutlined />
-          </Tooltip>
-        </a>,
-        <a key="status">
-          <Popconfirm
-            title={intl.formatMessage({
+            }),
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          style={{ padding: 0 }}
+          type="link"
+          isPermission={userPermission.update}
+          key="manage"
+          onClick={() => {
+            const url = getMenuPathByCode(MENUS_CODE[`system/DataSource/Management`]);
+            history.push(`${url}?id=${record.id}`);
+          }}
+          tooltip={{
+            title: '管理',
+          }}
+        >
+          <DatabaseOutlined />
+        </PermissionButton>,
+        <PermissionButton
+          style={{ padding: 0 }}
+          isPermission={userPermission.action}
+          type="link"
+          key="changeState"
+          popConfirm={{
+            title: intl.formatMessage({
               id: `pages.data.option.${
-                record.state.value === 'disabled' ? 'enabled' : 'disabled'
+                record.state?.value === 'enabled' ? 'disabled' : 'enabled'
               }.tips`,
-              defaultMessage: `确认${record.state.value === 'disabled' ? '启' : '禁'}用?`,
-            })}
-            onConfirm={async () => {
-              const state = record.state.value === 'disabled' ? 'enable' : 'disable';
-              await service.changeStatus(record.id, state);
-              message.success(
-                intl.formatMessage({
-                  id: 'pages.data.option.success',
-                  defaultMessage: '操作成功!',
-                }),
+              defaultMessage: `确认${record.state?.value === 'enabled' ? '禁用' : '启用'}?`,
+            }),
+            onConfirm: async () => {
+              const resp = await service.changeStatus(
+                record.id,
+                record.state?.value === 'enabled' ? 'disable' : 'enable',
               );
+              if (resp.status === 200) {
+                message.success(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }
+            },
+          }}
+          tooltip={{
+            title: intl.formatMessage({
+              id: `pages.data.option.${record.state?.value === 'enabled' ? 'disabled' : 'enabled'}`,
+              defaultMessage: record.state?.value === 'enabled' ? '禁用' : '启用',
+            }),
+          }}
+        >
+          {record.state?.value === 'enabled' ? <CloseCircleOutlined /> : <PlayCircleOutlined />}
+        </PermissionButton>,
+        <PermissionButton
+          type="link"
+          key="delete"
+          style={{ padding: 0 }}
+          isPermission={userPermission.delete}
+          disabled={record.state?.value === 'enabled'}
+          tooltip={{ title: record.state?.value === 'disabled' ? '删除' : '请先禁用,再删除。' }}
+        >
+          <Popconfirm
+            onConfirm={async () => {
+              await service.remove(record.id);
               actionRef.current?.reload();
             }}
+            title="确认删除?"
           >
-            <Tooltip
-              title={intl.formatMessage({
-                id: `pages.data.option.${
-                  record.state.value === 'enabled' ? 'disabled' : 'enabled'
-                }`,
-                defaultMessage: record.state.text,
-              })}
-            >
-              {stateIconMap[record.state.value]}
-            </Tooltip>
+            <DeleteOutlined />
           </Popconfirm>
-        </a>,
+        </PermissionButton>,
       ],
     },
   ];
 
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      grid: {
-        type: 'void',
-        'x-component': 'FormGrid',
-        'x-component-props': {
-          minColumns: [1],
-          maxColumns: [2],
-        },
-        properties: {
-          name: {
-            title: intl.formatMessage({
-              id: 'pages.table.name',
-              defaultMessage: '名称',
-            }),
-            'x-component': 'Input',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-              labelCol: 6,
-            },
-          },
-          typeId: {
-            title: intl.formatMessage({
-              id: 'pages.table.type',
-              defaultMessage: '类型',
-            }),
-            'x-component': 'Select',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-              labelCol: 6,
-            },
-            enum: type,
-          },
-          'shareConfig.adminUrl': {
-            title: '管理地址',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-              labelCol: 6,
-            },
-            required: true,
-            default: 'http://localhost:15672',
-            'x-visible': false,
-            'x-component': 'Input',
-            'x-display': 'none',
-            'x-reactions': {
-              dependencies: ['typeId'],
-              fulfill: {
-                state: {
-                  display: '{{$deps[0]==="rabbitmq"?"visible":"none"}}',
-                },
-              },
-            },
-          },
-          'shareConfig.addresses': {
-            title: '链接地址',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-              labelCol: 6,
-            },
-            required: true,
-            default: 'localhost:5672',
-            'x-component': 'Input',
-            'x-display': 'none',
-            'x-reactions': {
-              dependencies: ['typeId'],
-              fulfill: {
-                state: {
-                  display: '{{$deps[0]==="rabbitmq"?"visible":"none"}}',
-                },
-              },
-            },
-          },
-          'shareConfig.virtualHost': {
-            title: '虚拟域',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-              labelCol: 6,
-            },
-            required: true,
-            default: '/',
-            'x-component': 'Input',
-            'x-display': 'none',
-            'x-reactions': {
-              dependencies: ['typeId'],
-              fulfill: {
-                state: {
-                  display: '{{$deps[0]==="rabbitmq"?"visible":"none"}}',
-                },
-              },
-            },
-          },
-          'shareConfig.username': {
-            title: '用户名',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-              labelCol: 6,
-            },
-            'x-component': 'Input',
-            'x-display': 'none',
-            'x-reactions': {
-              dependencies: ['typeId'],
-              fulfill: {
-                state: {
-                  display: '{{$deps[0]==="rabbitmq"?"visible":"none"}}',
-                },
-              },
-            },
-          },
-          'shareConfig.password': {
-            title: '密码',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 1,
-              labelCol: 6,
-            },
-            'x-display': 'none',
-            'x-reactions': {
-              dependencies: ['typeId'],
-              fulfill: {
-                state: {
-                  display: '{{$deps[0]==="rabbitmq"?"visible":"none"}}',
-                },
-              },
-            },
-            'x-component': 'Input',
-          },
-          'shareConfig.bootstrapServers': {
-            title: '地址',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 2,
-              labelCol: 3,
-              wrapperCol: 20,
-            },
-            'x-component': 'Select',
-            'x-component-props': {
-              mode: 'tags',
-            },
-            'x-display': 'none',
-            'x-reactions': {
-              dependencies: ['typeId'],
-              fulfill: {
-                state: {
-                  display: '{{$deps[0]==="kafka"?"visible":"none"}}',
-                },
-              },
-            },
-          },
-          description: {
-            title: intl.formatMessage({
-              id: 'pages.table.description',
-              defaultMessage: '说明',
-            }),
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              gridSpan: 2,
-              labelCol: 3,
-              wrapperCol: 20,
-            },
-            'x-component': 'Input.TextArea',
-            'x-component-props': {
-              rows: 4,
-            },
-          },
-        },
-      },
-    },
-  };
+  const [param, setParam] = useState({});
 
   return (
     <PageContainer>
-      <BaseCrud<DataSourceItem>
-        modelConfig={{
-          width: 1000,
+      <SearchComponent<DataSourceItem>
+        field={columns}
+        target="datasource"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
         }}
-        columns={columns}
-        service={service}
-        title={intl.formatMessage({
-          id: 'pages.system.datasource.',
-          defaultMessage: '数据源管理',
-        })}
-        schema={schema}
+      />
+      <ProTable<DataSourceItem>
         actionRef={actionRef}
+        params={param}
+        columns={columns}
+        search={false}
+        rowKey="id"
+        headerTitle={
+          <PermissionButton
+            onClick={() => {
+              setCurrent({});
+              setVisible(true);
+            }}
+            isPermission={userPermission.add}
+            key="add"
+            icon={<PlusOutlined />}
+            type="primary"
+          >
+            {intl.formatMessage({
+              id: 'pages.data.option.add',
+              defaultMessage: '新增',
+            })}
+          </PermissionButton>
+        }
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
       />
+      {visible && (
+        <Save
+          close={() => {
+            setVisible(false);
+          }}
+          data={current}
+          reload={() => {
+            setVisible(false);
+            actionRef.current?.reload();
+          }}
+        />
+      )}
     </PageContainer>
   );
-};
+});
 export default DataSource;

+ 13 - 9
src/pages/system/DataSource/service.ts

@@ -1,19 +1,23 @@
 import BaseService from '@/utils/BaseService';
+import SystemConst from '@/utils/const';
 import { request } from '@@/plugin-request/request';
-import { defer, from } from 'rxjs';
-import { filter, map } from 'rxjs/operators';
 
 class Service extends BaseService<DataSourceItem> {
   changeStatus = (id: string, status: 'disable' | 'enable') =>
     request(`${this.uri}/${id}/_${status}`, { method: 'PUT' });
 
-  getType = () =>
-    defer(() =>
-      from(request(`${this.uri}/types`, { method: 'GET' })).pipe(
-        filter((resp) => resp.status === 200),
-        map((resp) => resp.result),
-      ),
-    );
+  getType = () => request(`${this.uri}/types`, { method: 'GET' });
+
+  rdbTree = (datasourceId: string) =>
+    request(`${SystemConst.API_BASE}/datasource/rdb/${datasourceId}/tables?includeColumns=false`, {
+      method: 'GET',
+    });
+
+  rdbTables = (datasourceId: string, table: string, data: any) =>
+    request(`/jetlinks/datasource/rdb/${datasourceId}/table/${table}`, {
+      method: 'GET',
+      params: data,
+    });
 }
 
 export default Service;

+ 8 - 4
src/pages/system/DataSource/typings.d.ts

@@ -3,7 +3,7 @@ type DataSourceItem = {
   name: string;
   shareCluster: true;
   shareConfig: Record<string, any>;
-  state: {
+  state?: {
     text: string;
     value: string;
   };
@@ -15,8 +15,12 @@ type DataSourceItem = {
 };
 
 type DataSourceType = {
-  label: string;
-  value: string;
-  id: string;
   name: string;
+  comment: string;
+  length: number;
+  notnull: boolean;
+  precision: number;
+  primaryKey: boolean;
+  scale: number;
+  type: string;
 };

+ 3 - 0
src/utils/menu/index.ts

@@ -69,6 +69,9 @@ const extraRouteObj = {
       { code: 'View', name: 'Api详情' },
     ],
   },
+  'system/DataSource': {
+    children: [{ code: 'Management', name: '管理' }],
+  },
 };
 //额外路由
 export const extraRouteArr = [

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

@@ -73,6 +73,7 @@ export enum MENUS_CODE {
   'rule-engine/Alarm/Configuration' = 'rule-engine/Alarm/Configuration',
   'simulator/Device' = 'simulator/Device',
   'system/DataSource' = 'system/DataSource',
+  'system/DataSource/Management' = 'system/DataSource/Management',
   'system/Department/Assets' = 'system/Department/Assets',
   'system/Department/Member' = 'system/Department/Member',
   'system/Department' = 'system/Department',