first commit

This commit is contained in:
jerryjzhang
2023-06-12 18:44:01 +08:00
commit dc4fc69b57
879 changed files with 573090 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { Form, Input, Spin } from 'antd';
import type { FormInstance } from 'antd/lib/form';
const FormItem = Form.Item;
const { TextArea } = Input;
type Props = {
isEdit?: boolean;
form: FormInstance<any>;
tableLoading?: boolean;
};
const DataSourceBasicForm: React.FC<Props> = ({ isEdit, tableLoading = false }) => {
return (
<Spin spinning={tableLoading}>
<FormItem
name="name"
label="数据源中文名"
rules={[{ required: true, message: '请输入数据源中文名' }]}
>
<Input placeholder="名称不可重复" />
</FormItem>
<FormItem
name="bizName"
label="数据源英文名"
rules={[{ required: true, message: '请输入数据源英文名' }]}
>
<Input placeholder="名称不可重复" disabled={isEdit} />
</FormItem>
<FormItem name="description" label="数据源描述">
<TextArea placeholder="请输入数据源描述" />
</FormItem>
</Spin>
);
};
export default DataSourceBasicForm;

View File

@@ -0,0 +1,291 @@
import React, { useEffect, useRef, useState } from 'react';
import { Form, Button, Modal, Steps, message } from 'antd';
import BasicInfoForm from './DataSourceBasicForm';
import FieldForm from './DataSourceFieldForm';
import { formLayout } from '@/components/FormHelper/utils';
import { EnumDataSourceType } from '../constants';
import type { DataInstanceItem, FieldItem, SaveDataSetForm } from '../data';
import styles from '../style.less';
import { createDatasource, updateDatasource } from '../../service';
export type CreateFormProps = {
createModalVisible: boolean;
sql: string;
domainId: number;
dataSourceItem: DataInstanceItem | any;
onCancel?: () => void;
onSubmit?: (dataSourceInfo: any) => void;
scriptColumns: any[];
};
const { Step } = Steps;
const initFormVal = {
name: '', // 数据源名称
bizName: '', // 数据源英文名
description: '', // 数据源描述
};
const DataSourceCreateForm: React.FC<CreateFormProps> = ({
onCancel,
createModalVisible,
domainId,
scriptColumns,
sql,
onSubmit,
dataSourceItem,
}) => {
const isEdit = !!dataSourceItem?.id;
const [fields, setFields] = useState<FieldItem[]>([]);
const [currentStep, setCurrentStep] = useState(0);
const [saveLoading, setSaveLoading] = useState(false);
const formValRef = useRef(initFormVal as any);
const [form] = Form.useForm();
const updateFormVal = (val: SaveDataSetForm) => {
formValRef.current = val;
};
const forward = () => setCurrentStep(currentStep + 1);
const backward = () => setCurrentStep(currentStep - 1);
const getFieldsClassify = (fieldsList: FieldItem[]) => {
const classify = fieldsList.reduce(
(fieldsClassify, item: FieldItem) => {
const {
type,
bizName,
timeGranularity,
agg,
isCreateDimension,
name,
isCreateMetric,
dateFormat,
} = item;
switch (type) {
case EnumDataSourceType.CATEGORICAL:
fieldsClassify.dimensions.push({
bizName,
type,
isCreateDimension,
name,
});
break;
case EnumDataSourceType.TIME:
fieldsClassify.dimensions.push({
bizName,
type,
isCreateDimension,
name,
dateFormat,
typeParams: {
isPrimary: true,
timeGranularity,
},
});
break;
case EnumDataSourceType.FOREIGN:
case EnumDataSourceType.PRIMARY:
fieldsClassify.identifiers.push({
bizName,
name,
type,
});
break;
case EnumDataSourceType.MEASURES:
fieldsClassify.measures.push({
bizName,
type,
agg,
name,
isCreateMetric,
});
break;
default:
break;
}
return fieldsClassify;
},
{
identifiers: [],
dimensions: [],
measures: [],
} as any,
);
return classify;
};
const handleNext = async () => {
const fieldsValue = await form.validateFields();
const fieldsClassify = getFieldsClassify(fields);
const submitForm = {
...formValRef.current,
...fieldsValue,
...fieldsClassify,
};
updateFormVal(submitForm);
if (currentStep < 1) {
forward();
} else {
setSaveLoading(true);
const queryParams = {
...submitForm,
sqlQuery: sql,
databaseId: dataSourceItem.databaseId,
queryType: 'sql_query',
domainId,
};
const queryDatasource = isEdit ? updateDatasource : createDatasource;
const { code, msg, data } = await queryDatasource(queryParams);
setSaveLoading(false);
if (code === 200) {
message.success('保存数据源成功!');
onSubmit?.({
...queryParams,
...data,
resData: data,
});
return;
}
message.error(msg);
}
};
const initFields = (fieldsClassifyList: any[]) => {
const columnFields: any[] = scriptColumns.map((item: any) => {
const { type, nameEn } = item;
const oldItem = fieldsClassifyList.find((oItem) => oItem.bizName === item.nameEn) || {};
return {
...oldItem,
bizName: nameEn,
// name,
sqlType: type,
};
});
setFields(columnFields || []);
};
const formatterMeasures = (measuresList: any[] = []) => {
return measuresList.map((measures: any) => {
return {
...measures,
type: EnumDataSourceType.MEASURES,
};
});
};
const formatterDimensions = (dimensionsList: any[] = []) => {
return dimensionsList.map((dimension: any) => {
const { typeParams } = dimension;
return {
...dimension,
timeGranularity: typeParams?.timeGranularity || '',
};
});
};
const initData = () => {
const { id, name, bizName, description, datasourceDetail } = dataSourceItem as any;
const initValue = {
id,
name,
bizName,
description,
};
const editInitFormVal = {
...formValRef.current,
...initValue,
};
updateFormVal(editInitFormVal);
form.setFieldsValue(initValue);
const { dimensions, identifiers, measures } = datasourceDetail;
const formatFields = [
...formatterDimensions(dimensions || []),
...(identifiers || []),
...formatterMeasures(measures || []),
];
initFields(formatFields);
};
useEffect(() => {
if (isEdit) {
initData();
} else {
initFields([]);
}
}, [dataSourceItem]);
const handleFieldChange = (fieldName: string, data: any) => {
const result = fields.map((field) => {
if (field.bizName === fieldName) {
return {
...field,
...data,
};
}
return {
...field,
};
});
setFields(result);
};
const renderContent = () => {
if (currentStep === 1) {
return <FieldForm fields={fields} onFieldChange={handleFieldChange} />;
}
return <BasicInfoForm form={form} isEdit={isEdit} />;
};
const renderFooter = () => {
if (currentStep === 1) {
return (
<>
<Button style={{ float: 'left' }} onClick={backward}>
</Button>
<Button onClick={onCancel}></Button>
<Button type="primary" loading={saveLoading} onClick={handleNext}>
</Button>
</>
);
}
return (
<>
<Button onClick={onCancel}></Button>
<Button type="primary" onClick={handleNext}>
</Button>
</>
);
};
return (
<Modal
forceRender
width={1300}
bodyStyle={{ padding: '32px 40px 48px' }}
destroyOnClose
title={`${isEdit ? '编辑' : '新建'}数据源`}
maskClosable={false}
open={createModalVisible}
footer={renderFooter()}
onCancel={onCancel}
>
<Steps style={{ marginBottom: 28 }} size="small" current={currentStep}>
<Step title="基本信息" />
<Step title="字段信息" />
</Steps>
<Form
{...formLayout}
form={form}
initialValues={{
...formValRef.current,
}}
className={styles.form}
>
{renderContent()}
</Form>
</Modal>
);
};
export default DataSourceCreateForm;

View File

@@ -0,0 +1,196 @@
import React from 'react';
import { Table, Select, Checkbox, Input } from 'antd';
import type { FieldItem } from '../data';
import { isUndefined } from 'lodash';
import { TYPE_OPTIONS, DATE_FORMATTER, AGG_OPTIONS, EnumDataSourceType } from '../constants';
type Props = {
fields: FieldItem[];
onFieldChange: (fieldName: string, data: Partial<FieldItem>) => void;
};
const { Option } = Select;
const FieldForm: React.FC<Props> = ({ fields, onFieldChange }) => {
const handleFieldChange = (record: FieldItem, fieldName: string, value: any) => {
onFieldChange(record.bizName, {
...record,
[fieldName]: value,
});
};
const columns = [
{
title: '字段名称',
dataIndex: 'bizName',
width: 100,
},
{
title: '数据类型',
dataIndex: 'sqlType',
width: 80,
},
{
title: '字段类型',
dataIndex: 'type',
width: 100,
render: (_: any, record: FieldItem) => {
const type = fields.find((field) => field.bizName === record.bizName)?.type;
return (
<Select
placeholder="字段类型"
value={type}
onChange={(value) => {
let defaultParams = {};
if (value === EnumDataSourceType.MEASURES) {
defaultParams = {
agg: AGG_OPTIONS[0].value,
};
} else if (value === EnumDataSourceType.TIME) {
defaultParams = {
dateFormat: DATE_FORMATTER[0],
timeGranularity: 'day',
};
} else {
defaultParams = {
agg: undefined,
dateFormat: undefined,
timeGranularity: undefined,
};
}
// handleFieldChange(record, 'type', value);
onFieldChange(record.bizName, {
...record,
type: value,
...defaultParams,
});
}}
style={{ width: '100%' }}
>
{TYPE_OPTIONS.map((item) => (
<Option key={item.label} value={item.value}>
{item.label}
</Option>
))}
</Select>
);
},
},
{
title: '扩展配置',
dataIndex: 'extender',
width: 100,
render: (_: any, record: FieldItem) => {
const { type } = record;
if (type === EnumDataSourceType.MEASURES) {
const agg = fields.find((field) => field.bizName === record.bizName)?.agg;
return (
<Select
placeholder="度量算子"
value={agg}
onChange={(value) => {
handleFieldChange(record, 'agg', value);
}}
defaultValue={AGG_OPTIONS[0].value}
style={{ width: '100%' }}
>
{AGG_OPTIONS.map((item) => (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
))}
</Select>
);
}
if (type === EnumDataSourceType.TIME) {
const dateFormat = fields.find((field) => field.bizName === record.bizName)?.dateFormat;
return (
<Select
placeholder="时间格式"
value={dateFormat}
onChange={(value) => {
handleFieldChange(record, 'dateFormat', value);
}}
defaultValue={DATE_FORMATTER[0]}
style={{ width: '100%' }}
>
{DATE_FORMATTER.map((item) => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
);
}
return <></>;
},
},
{
title: '快速创建',
dataIndex: 'fastCreate',
width: 100,
render: (_: any, record: FieldItem) => {
const { type, name } = record;
if (
[
EnumDataSourceType.PRIMARY,
EnumDataSourceType.FOREIGN,
EnumDataSourceType.CATEGORICAL,
EnumDataSourceType.TIME,
EnumDataSourceType.MEASURES,
].includes(type as EnumDataSourceType)
) {
const isCreateName = [EnumDataSourceType.CATEGORICAL, EnumDataSourceType.TIME].includes(
type as EnumDataSourceType,
)
? 'isCreateDimension'
: 'isCreateMetric';
const editState = !isUndefined(record[isCreateName]) ? !!record[isCreateName] : true;
return (
<Checkbox
checked={editState}
onChange={(e) => {
const value = e.target.checked ? 1 : 0;
if (!value) {
onFieldChange(record.bizName, {
...record,
name: '',
[isCreateName]: value,
});
} else {
handleFieldChange(record, isCreateName, value);
}
}}
>
<Input
value={name}
disabled={!editState}
onChange={(e) => {
const value = e.target.value;
handleFieldChange(record, 'name', value);
}}
placeholder="请输入中文名"
/>
</Checkbox>
);
}
return <></>;
},
},
];
return (
<>
<Table<FieldItem>
dataSource={fields}
columns={columns}
className="fields-table"
rowKey="bizName"
pagination={false}
scroll={{ y: 500 }}
/>
</>
);
};
export default FieldForm;

View File

@@ -0,0 +1,530 @@
import React, { useState, useEffect, useRef } from 'react';
import { Button, Table, message, Tooltip, Space, Dropdown } from 'antd';
import SplitPane from 'react-split-pane';
import Pane from 'react-split-pane/lib/Pane';
import sqlFormatter from 'sql-formatter';
import {
FullscreenOutlined,
WarningOutlined,
EditOutlined,
PlayCircleTwoTone,
SwapOutlined,
PlayCircleOutlined,
CloudServerOutlined,
} from '@ant-design/icons';
import { isFunction } from 'lodash';
import FullScreen from '@/components/FullScreen';
import SqlEditor from '@/components/SqlEditor';
import type { TaskResultParams, TaskResultItem, DataInstanceItem, TaskResultColumn } from '../data';
import { excuteSql } from '../service';
import { getDatabaseByDomainId } from '../../service';
import DataSourceCreateForm from './DataSourceCreateForm';
import styles from '../style.less';
import 'ace-builds/src-min-noconflict/ext-searchbox';
import 'ace-builds/src-min-noconflict/theme-sqlserver';
import 'ace-builds/src-min-noconflict/theme-monokai';
import 'ace-builds/src-min-noconflict/mode-sql';
type IProps = {
oprType: 'add' | 'edit';
dataSourceItem: DataInstanceItem;
domainId: number;
onUpdateSql?: (sql: string) => void;
sql?: string;
onSubmitSuccess?: (dataSourceInfo: any) => void;
onJdbcSourceChange?: (jdbcId: number) => void;
};
type ResultTableItem = Record<string, any>;
type ResultColItem = {
key: string;
title: string;
dataIndex: string;
};
type ScreenSize = 'small' | 'middle' | 'large';
type JdbcSourceItems = {
label: string;
key: number;
};
const SqlDetail: React.FC<IProps> = ({
dataSourceItem,
onSubmitSuccess,
domainId,
sql = '',
onUpdateSql,
onJdbcSourceChange,
}) => {
const [resultTable, setResultTable] = useState<ResultTableItem[]>([]);
const [resultTableLoading, setResultTableLoading] = useState(false);
const [resultCols, setResultCols] = useState<ResultColItem[]>([]);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 20,
total: 0,
});
const [jdbcSourceItems, setJdbcSourceItems] = useState<JdbcSourceItems[]>([]);
const [dataSourceModalVisible, setDataSourceModalVisible] = useState(false);
const [tableScroll, setTableScroll] = useState({
scrollToFirstRowOnChange: true,
x: '100%',
y: 200,
});
// const [dataSourceResult, setDataSourceResult] = useState<any>({});
const [runState, setRunState] = useState<boolean | undefined>();
const [taskLog, setTaskLog] = useState('');
const [isSqlExcLocked, setIsSqlExcLocked] = useState(false);
const [screenSize, setScreenSize] = useState<ScreenSize>('middle');
const [isSqlIdeFullScreen, setIsSqlIdeFullScreen] = useState<boolean>(false);
const [isSqlResFullScreen, setIsSqlResFullScreen] = useState<boolean>(false);
// const [sqlParams, setSqlParams] = useState<SqlParamsItem[]>([]);
const resultInnerWrap = useRef<HTMLDivElement>();
const [editorSize, setEditorSize] = useState<number>(0);
const DEFAULT_FULLSCREEN_TOP = 0;
const [partialSql, setPartialSql] = useState('');
const [isPartial, setIsPartial] = useState(false);
const [isRight, setIsRight] = useState(false);
const [scriptColumns, setScriptColumns] = useState<any[]>([]);
// const [jdbcSourceName, setJdbcSourceName] = useState<string>(() => {
// const sourceId = dataSourceItem.databaseId;
// if (sourceId) {
// const target: any = jdbcSourceItems.filter((item: any) => {
// return item.key === Number(sourceId);
// })[0];
// if (target) {
// return target.label;
// }
// }
// return 'ClickHouse';
// });
const queryDatabaseConfig = async () => {
const { code, data } = await getDatabaseByDomainId(domainId);
if (code === 200) {
setJdbcSourceItems([
{
label: data?.name,
key: data?.id,
},
]);
onJdbcSourceChange?.(data?.id && Number(data?.id));
return;
}
message.error('数据库配置获取错误');
};
function creatCalcItem(key: string, data: string) {
const line = document.createElement('div'); // 需要每条数据一行,这样避免数据换行的时候获得的宽度不准确
const child = document.createElement('span');
child.classList.add(`resultCalcItem_${key}`);
child.innerText = data;
line.appendChild(child);
return line;
}
// 计算每列的宽度,通过容器插入文档中动态得到该列数据(包括表头)的最长宽度,设为列宽度,保证每列的数据都能一行展示完
function getKeyWidthMap(list: TaskResultItem[]): TaskResultItem {
const widthMap = {};
const container = document.createElement('div');
container.id = 'resultCalcWrap';
container.style.position = 'fixed';
container.style.left = '-99999px';
container.style.top = '-99999px';
container.style.width = '19999px';
container.style.fontSize = '12px';
list.forEach((item, index) => {
if (index === 0) {
Object.keys(item).forEach((key, keyIndex) => {
// 因为key可能存在一些特殊字符导致querySelectorAll获取的时候报错所以用keyIndex(而不用key)拼接className
container.appendChild(creatCalcItem(`${keyIndex}`, key));
container.appendChild(creatCalcItem(`${keyIndex}`, `${item[key]}`));
});
} else {
Object.keys(item).forEach((key, keyIndex) => {
container.appendChild(creatCalcItem(`${keyIndex}`, `${item[key]}`));
});
}
});
document.body.appendChild(container);
Object.keys(list[0]).forEach((key, keyIndex) => {
// 因为key可能存在一些特殊字符导致querySelectorAll获取的时候报错所以用keyIndex(而不用key)拼接className
const widthArr = Array.from(container.querySelectorAll(`.resultCalcItem_${keyIndex}`)).map(
(node: any) => node.offsetWidth,
);
widthMap[key] = Math.max(...widthArr);
});
document.body.removeChild(container);
return widthMap;
}
const updateResultCols = (list: TaskResultItem[], columns: TaskResultColumn[]) => {
if (list.length) {
const widthMap = getKeyWidthMap(list);
const cols = columns.map(({ nameEn }) => {
return {
key: nameEn,
title: nameEn,
dataIndex: nameEn,
width: `${(widthMap[nameEn] as number) + 22}px`, // 字宽度 + 20px(比左右padding宽几像素作为一个buffer值)
};
});
setResultCols(cols);
}
};
const fetchTaskResult = (params: TaskResultParams) => {
setResultTable(
params.resultList.map((item, index) => {
return {
...item,
index,
};
}),
);
setPagination({
current: 1,
pageSize: 20,
total: params.resultList.length,
});
setScriptColumns(params.columns);
updateResultCols(params.resultList, params.columns);
};
const changePaging = (paging: Pagination) => {
setPagination({
...pagination,
...paging,
});
};
const onSqlChange = (sqlString: string) => {
if (onUpdateSql && isFunction(onUpdateSql)) {
onUpdateSql(sqlString);
}
};
const formatSQL = () => {
const sqlvalue = sqlFormatter.format(sql);
if (onUpdateSql && isFunction(onUpdateSql)) {
onUpdateSql(sqlvalue);
}
// eslint-disable-next-line no-param-reassign
sql = sqlvalue;
};
const separateSql = async (value: string) => {
setResultTableLoading(true);
const { code, data, msg } = await excuteSql({
sql: value,
domainId,
});
setResultTableLoading(false);
if (code === 200) {
// setDataSourceResult(data);
fetchTaskResult(data);
setRunState(true);
} else {
setRunState(false);
setTaskLog(msg);
}
};
const onSelect = (value: string) => {
if (value) {
setIsPartial(true);
setPartialSql(value);
} else {
setIsPartial(false);
}
};
const excuteScript = () => {
if (!sql) {
return message.error('SQL查询语句不可以为空');
}
if (isSqlExcLocked) {
return message.warning('请间隔5s再重新执行');
}
const waitTime = 5000;
setIsSqlExcLocked(true); // 加锁5s后再解锁
setTimeout(() => {
setIsSqlExcLocked(false);
}, waitTime);
return isPartial ? separateSql(partialSql) : separateSql(sql);
};
const showDataSetModal = () => {
setDataSourceModalVisible(true);
};
const startCreatDataSource = async () => {
showDataSetModal();
};
const updateNormalResScroll = () => {
const node = resultInnerWrap?.current;
if (node) {
setTableScroll({
scrollToFirstRowOnChange: true,
x: '100%',
y: node.clientHeight - 120,
});
}
};
const updateFullScreenResScroll = () => {
const windowHeight = window.innerHeight;
const paginationHeight = 96;
setTableScroll({
scrollToFirstRowOnChange: true,
x: '100%',
y: windowHeight - DEFAULT_FULLSCREEN_TOP - paginationHeight - 30, // 30为退出全屏按钮的高度
});
};
const handleFullScreenSqlIde = () => {
setIsSqlIdeFullScreen(true);
};
const handleNormalScreenSqlIde = () => {
setIsSqlIdeFullScreen(false);
};
const handleFullScreenSqlResult = () => {
setIsSqlResFullScreen(true);
};
const handleNormalScreenSqlResult = () => {
setIsSqlResFullScreen(false);
};
const handleThemeChange = () => {
setIsRight(!isRight);
};
const renderResult = () => {
if (runState === false) {
return (
<>
{
<div className={styles.taskFailed}>
<WarningOutlined className={styles.resultFailIcon} />
</div>
}
<div
className={styles.sqlResultLog}
dangerouslySetInnerHTML={{
__html: taskLog.replace(/\r\n/g, '<br/>').replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;'),
}}
/>
</>
);
}
if (runState) {
return (
<>
<div className={styles.detail} />
<Table<TaskResultItem>
loading={resultTableLoading}
dataSource={resultTable}
columns={resultCols}
onChange={changePaging}
pagination={pagination}
scroll={tableScroll}
className={styles.resultTable}
rowClassName="resultTableRow"
rowKey="index"
/>
</>
);
}
return <div className={styles.sqlResultContent}></div>;
};
// 更新任务结果列表的高度,使其撑满容器
useEffect(() => {
if (isSqlResFullScreen) {
updateFullScreenResScroll();
} else {
updateNormalResScroll();
}
}, [resultTable, isSqlResFullScreen]);
useEffect(() => {
queryDatabaseConfig();
const windowHeight = window.innerHeight;
let size: ScreenSize = 'small';
if (windowHeight > 1100) {
size = 'large';
} else if (windowHeight > 850) {
size = 'middle';
}
setScreenSize(size);
}, []);
const exploreEditorSize = localStorage.getItem('exploreEditorSize');
return (
<>
<div className={styles.sqlOprBar}>
<div className={styles.sqlOprBarLeftBox}>
<Tooltip title="数据类型">
<Dropdown
menu={{
items: jdbcSourceItems,
onClick: (e) => {
const value = e.key;
const target: any = jdbcSourceItems.filter((item: any) => {
return item.key === Number(value);
})[0];
if (target) {
// setJdbcSourceName(target.label);
onJdbcSourceChange?.(Number(value));
}
},
}}
placement="bottom"
>
<Button style={{ marginRight: '15px', minWidth: '140px' }}>
<Space>
<CloudServerOutlined className={styles.sqlOprIcon} style={{ marginRight: 0 }} />
<span>{jdbcSourceItems[0]?.label}</span>
</Space>
</Button>
</Dropdown>
</Tooltip>
<Tooltip title="全屏">
<FullscreenOutlined className={styles.sqlOprIcon} onClick={handleFullScreenSqlIde} />
</Tooltip>
<Tooltip title="格式化SQL语句">
<EditOutlined className={styles.sqlOprIcon} onClick={formatSQL} />
</Tooltip>
<Tooltip title="改变主题">
<SwapOutlined className={styles.sqlOprIcon} onClick={handleThemeChange} />
</Tooltip>
<Tooltip title="执行脚本">
<Button
style={{
lineHeight: '24px',
top: '3px',
position: 'relative',
}}
type="primary"
shape="round"
icon={
isPartial ? '' : isSqlExcLocked ? <PlayCircleOutlined /> : <PlayCircleTwoTone />
}
size={'small'}
className={
isSqlExcLocked ? `${styles.disableIcon} ${styles.sqlOprIcon}` : styles.sqlOprBtn
}
onClick={excuteScript}
>
{isPartial ? '部分运行' : '运行'}
</Button>
</Tooltip>
</div>
</div>
<SplitPane
split="horizontal"
onChange={(size) => {
setEditorSize(size);
localStorage.setItem('exploreEditorSize', size[0]);
}}
>
<Pane initialSize={exploreEditorSize || '500px'}>
<div className={styles.sqlMain}>
<div className={styles.sqlEditorWrapper}>
<FullScreen
isFullScreen={isSqlIdeFullScreen}
top={`${DEFAULT_FULLSCREEN_TOP}px`}
triggerBackToNormal={handleNormalScreenSqlIde}
>
<SqlEditor
value={sql}
// height={sqlEditorHeight}
// theme="monokai"
isRightTheme={isRight}
sizeChanged={editorSize}
onSqlChange={onSqlChange}
onSelect={onSelect}
/>
</FullScreen>
</div>
</div>
</Pane>
<div className={`${styles.sqlBottmWrap} ${screenSize}`}>
<div className={styles.sqlResultWrap}>
<div className={styles.sqlToolBar}>
{
<Button
className={styles.sqlToolBtn}
type="primary"
onClick={startCreatDataSource}
disabled={!runState}
>
</Button>
}
<Button
className={styles.sqlToolBtn}
type="primary"
onClick={handleFullScreenSqlResult}
disabled={!runState}
>
</Button>
</div>
<div
className={styles.sqlResultPane}
ref={resultInnerWrap as React.MutableRefObject<HTMLDivElement | null>}
>
<FullScreen
isFullScreen={isSqlResFullScreen}
top={`${DEFAULT_FULLSCREEN_TOP}px`}
triggerBackToNormal={handleNormalScreenSqlResult}
>
{renderResult()}
</FullScreen>
</div>
</div>
</div>
</SplitPane>
{dataSourceModalVisible && (
<DataSourceCreateForm
sql={sql}
domainId={domainId}
dataSourceItem={dataSourceItem}
scriptColumns={scriptColumns}
onCancel={() => {
setDataSourceModalVisible(false);
}}
onSubmit={(dataSourceInfo: any) => {
setDataSourceModalVisible(false);
onSubmitSuccess?.(dataSourceInfo);
}}
createModalVisible={dataSourceModalVisible}
/>
)}
</>
);
};
export default SqlDetail;

View File

@@ -0,0 +1,126 @@
import React, { useState, useRef, useEffect } from 'react';
import { Tabs } from 'antd';
import SqlDetail from './SqlDetail';
import type { SqlItem } from '../data';
import styles from '../style.less';
type Panes = {
title: string;
key: string;
type: 'add' | 'edit';
scriptId?: number;
sql?: string;
sqlInfo?: SqlItem;
isSave?: boolean; // 暂存提示保存
};
type TableRef = {
current?: {
fetchSqlList: () => void;
upDateActiveItem: (key: any) => void;
};
};
type Props = {
initialValues: any;
domainId: number;
onSubmitSuccess?: (dataSourceInfo: any) => void;
};
const { TabPane } = Tabs;
const LIST_KEY = 'list';
const SqlSide: React.FC<Props> = ({ initialValues, domainId, onSubmitSuccess }) => {
const defaultPanes: Panes[] = [
{
key: '数据源查询',
title: initialValues?.name || '数据源查询',
type: 'add',
isSave: true,
},
];
const [activeKey, setActiveKey] = useState('数据源查询');
const [panes, setPanes] = useState<Panes[]>(defaultPanes);
const tableRef: TableRef = useRef();
const panesRef = useRef<Panes[]>(defaultPanes);
const [dataSourceItem, setDataSourceItem] = useState<any>(initialValues || {});
const updatePane = (list: Panes[]) => {
setPanes(list);
panesRef.current = list;
};
// 更新脚本内容
const updateTabSql = (sql: string, targetKey: string) => {
const newPanes = panesRef.current.slice();
const index = newPanes.findIndex((item) => item.key === targetKey);
const targetItem = newPanes[index];
newPanes.splice(index, 1, {
...targetItem,
sql,
isSave: false,
});
updatePane(newPanes);
};
useEffect(() => {
if (initialValues) {
updateTabSql(initialValues?.datasourceDetail?.sqlQuery || '', '数据源查询');
}
}, [initialValues]);
const onChange = (key: string) => {
setActiveKey(key);
tableRef?.current?.upDateActiveItem(key);
if (key === LIST_KEY) {
tableRef?.current?.fetchSqlList();
}
};
return (
<>
<div className={styles.outside}>
<Tabs
type="editable-card"
hideAdd={true}
activeKey={activeKey}
onChange={onChange}
className={styles.middleArea}
>
{panes.map((pane) => {
return (
<TabPane
tab={<div className={styles.paneName}>{pane.title}</div>}
closable={false}
key={pane.key}
>
<SqlDetail
onSubmitSuccess={onSubmitSuccess}
dataSourceItem={dataSourceItem}
oprType={pane.type}
domainId={domainId}
onUpdateSql={(sql: string) => {
updateTabSql(sql, pane.key);
}}
onJdbcSourceChange={(databaseId) => {
setDataSourceItem({
...dataSourceItem,
databaseId,
});
}}
sql={pane.sql}
/>
</TabPane>
);
})}
</Tabs>
</div>
{/* </SplitPane> */}
</>
);
};
export default SqlSide;