feat: code migration

This commit is contained in:
Jerry Fan
2023-06-23 01:11:37 +08:00
parent e31271a1c6
commit 5b4431818b
201 changed files with 1530 additions and 1295 deletions

View File

@ -0,0 +1,169 @@
{
"success": true,
"errorCode": null,
"errorMessage": null,
"data": [
{
"sql": "SELECT *\nFROM students\nLIMIT 500",
"description": "执行成功",
"message": null,
"success": true,
"headerList": [
{
"dataType": "NUMERIC",
"name": "id"
},
{
"dataType": "STRING",
"name": "name"
},
{
"dataType": "STRING",
"name": "gender"
},
{
"dataType": "DATETIME",
"name": "birthday"
},
{
"dataType": "STRING",
"name": "address"
},
{
"dataType": "STRING",
"name": "phone"
},
{
"dataType": "STRING",
"name": "email"
},
{
"dataType": "DATETIME",
"name": "create_time"
},
{
"dataType": "DATETIME",
"name": "update_time"
}
],
"dataList": [
[
"1",
"张三",
"男",
null,
"北京市海淀区",
"12345678901",
"zhangsan@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"2",
"李四",
"男",
null,
"上海市浦东新区",
"12345678902",
"lisi@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"3",
"王五",
"女",
null,
"广州市天河区",
"12345678903",
"wangwu@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"4",
"赵六",
"男",
null,
"深圳市南山区",
"12345678904",
"zhaoliu@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"5",
"陈七",
"女",
null,
"武汉市江汉区",
"12345678905",
"chenqi@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"6",
"刘八",
"男",
null,
"成都市高新区",
"12345678906",
"liuba@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"7",
"魏九",
"女",
null,
"重庆市渝北区",
"12345678907",
"weijiu@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"8",
"孙十",
"男",
null,
"南京市鼓楼区",
"12345678908",
"sunshi@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"9",
"郑十一",
"男",
null,
"西安市雁塔区",
"12345678909",
"zhengshiyi@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
],
[
"10",
"许十二",
"女",
null,
"苏州市姑苏区",
"12345678910",
"xushier@example.com",
"2023-05-31 10:41:56.000",
"2023-05-31 10:41:56.000"
]
],
"sqlType": "SELECT",
"hasNextPage": false,
"pageNo": 1,
"pageSize": 500,
"duration": 6
}
],
"traceId": null
}

View File

@ -8,7 +8,7 @@ import { format } from 'sql-formatter';
import sqlServer from '@/service/sql';
import historyServer from '@/service/history';
import MonacoEditor from 'react-monaco-editor';
import { useReducerContext } from '@/pages/main/workspace/index'
import { useReducerContext } from '@/pages/main/workspace/index';
import styles from './index.less';
import Loading from '../Loading/Loading';
@ -43,7 +43,8 @@ interface IProps {
consoleId: number;
schemaName?: string;
consoleName: string;
}
};
onExecuteSQL: (value: any) => void;
}
function Console(props: IProps) {
@ -58,7 +59,7 @@ function Console(props: IProps) {
useEffect(() => {
setContext(value);
}, [value])
}, [value]);
const onPressChatInput = (value: string) => {
const params = formatParams({
@ -108,17 +109,21 @@ function Console(props: IProps) {
sql: sqlContent,
...executeParams,
};
sqlServer.executeSql(p).then((res) => {
console.log(res)
let p = {
...executeParams,
ddl: sqlContent
};
historyServer.createHistory(p);
// setManageResultDataList(res);
}).catch((error) => {
// setManageResultDataList([]);
});
sqlServer
.executeSql(p)
.then((res) => {
props.onExecuteSQL && props.onExecuteSQL(res);
// console.log(res)
let p = {
...executeParams,
ddl: sqlContent,
};
historyServer.createHistory(p);
// setManageResultDataList(res);
})
.catch((error) => {
// setManageResultDataList([]);
});
};
const saveWindowTab = () => {

View File

@ -0,0 +1,577 @@
import React, { memo, useEffect, useMemo, useState, Fragment, useContext, useCallback, useLayoutEffect } from 'react';
import { i18n, isEn } from '@/i18n';
import styles from './index.less';
import classnames from 'classnames';
import connectionService from '@/service/connection';
import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants/database';
import { dataSourceFormConfigs } from './config/dataSource';
import { IConnectionConfig, IFormItem, ISelect } from './config/types';
import { IConnectionDetails } from '@/typings/connection';
import { InputType } from './config/enum';
import { deepClone } from '@/utils';
import { Select, Form, Input, message, Table, Button, Collapse } from 'antd';
import Iconfont from '@/components/Iconfont';
import LoadingContent from '@/components/Loading/LoadingContent';
import { useTheme } from '@/hooks/useTheme';
const { Option } = Select;
type ITabsType = 'ssh' | 'baseInfo';
export enum submitType {
UPDATE = 'update',
SAVE = 'save',
TEST = 'test',
}
interface IProps {
className?: string;
closeCreateConnection: () => void;
connectionData: IConnectionDetails;
submitCallback?: Function;
}
export default function CreateConnection(props: IProps) {
const { className, closeCreateConnection, submitCallback } = props;
const [baseInfoForm] = Form.useForm();
const [sshForm] = Form.useForm();
const [backfillData, setBackfillData] = useState<IConnectionDetails>(props.connectionData);
const [loadings, setLoading] = useState({
confirmButton: false,
testButton: false,
});
// const [connectionData, setConnectionData] = useState<IConnectionDetails>(props.connectionData);
// const [currentType, setCurrentType] = useState<DatabaseTypeCode>(createType || DatabaseTypeCode.MYSQL);
useEffect(() => {
setBackfillData(props.connectionData);
}, [props.connectionData]);
useEffect(() => {
if (backfillData.id) {
getConnectionDetails(backfillData.id);
}
}, [backfillData.id]);
function getConnectionDetails(id: number) {
connectionService.getDetails({ id }).then((res) => {
if (!res) {
return;
}
if (res.user) {
res.authentication = 1;
} else {
res.authentication = 2;
}
setTimeout(() => {
setBackfillData(res);
}, 300);
});
}
const getItems = () => [
{
key: 'ssh',
label: 'SSH Configuration',
children: (
<div className={styles.sshBox}>
<RenderForm backfillData={backfillData!} form={sshForm} tab="ssh" />
<div className={styles.testSSHConnect}>
<div onClick={testSSH} className={styles.testSSHConnectText}>
{i18n('connection.message.testSshConnection')}
</div>
</div>
</div>
),
},
{
key: 'extendInfo',
label: 'Advanced Configuration',
children: (
<div className={styles.extendInfoBox}>
<RenderExtendTable backfillData={backfillData!}></RenderExtendTable>
</div>
),
},
];
// 测试、保存、修改连接
function saveConnection(type: submitType) {
const ssh = sshForm.getFieldsValue();
const baseInfo = baseInfoForm.getFieldsValue();
const extendInfo: any = [];
const loadingsButton = type === submitType.TEST ? 'testButton' : 'confirmButton';
extendTableData.map((t: any) => {
if (t.label || t.value) {
extendInfo.push({
key: t.label,
value: t.value,
});
}
});
let p: any = {
ssh,
...baseInfo,
extendInfo,
// ...values,
ConnectionEnvType: ConnectionEnvType.DAILY,
type: backfillData.type,
};
if (type !== submitType.SAVE) {
p.id = backfillData.id;
}
const api: any = connectionService[type](p);
setLoading({
...loadings,
[loadingsButton]: true,
});
api
.then((res: any) => {
if (type === submitType.TEST) {
message.success(
res === false
? i18n('connection.message.testConnectResult', i18n('common.text.failure'))
: i18n('connection.message.testConnectResult', i18n('common.text.successful')),
);
} else {
submitCallback?.();
message.success(
type === submitType.UPDATE
? i18n('common.message.modifySuccessfully')
: i18n('common.message.addedSuccessfully'),
);
}
})
.finally(() => {
setLoading({
...loadings,
[loadingsButton]: false,
});
});
}
function onCancel() {
closeCreateConnection();
// setEditDataSourceData(false)
}
function testSSH() {
let p = sshForm.getFieldsValue();
connectionService.testSSH(p).then((res) => {
message.success(i18n('connection.message.testConnectResult', i18n('common.text.successful')));
});
}
return (
<div className={classnames(styles.box, className)}>
<LoadingContent className={styles.loadingContent} data={backfillData}>
<div className={styles.connectionBox}>
<div className={styles.title}>
<Iconfont code={databaseMap[backfillData.type]?.icon}></Iconfont>
<div>{databaseMap[backfillData.type]?.name}</div>
</div>
<div className={styles.baseInfoBox}>
<RenderForm backfillData={backfillData!} form={baseInfoForm} tab="baseInfo" />
</div>
<Collapse items={getItems()} />
<div className={styles.formFooter}>
<div className={styles.test}>
{
<Button
loading={loadings.testButton}
onClick={saveConnection.bind(null, submitType.TEST)}
className={styles.test}
>
{i18n('connection.button.testConnection')}
</Button>
}
</div>
<div className={styles.rightButton}>
<Button onClick={onCancel} className={styles.cancel}>
{i18n('common.button.cancel')}
</Button>
<Button
className={styles.save}
type="primary"
loading={loadings.confirmButton}
onClick={saveConnection.bind(null, backfillData.id ? submitType.UPDATE : submitType.SAVE)}
>
{i18n('common.button.save')}
</Button>
</div>
</div>
</div>
</LoadingContent>
</div>
);
}
interface IRenderFormProps {
tab: ITabsType;
form: any;
backfillData: IConnectionDetails;
}
function RenderForm(props: IRenderFormProps) {
const { tab, form, backfillData } = props;
const editId = backfillData.id;
const databaseType = backfillData.type;
let aliasChanged = false;
const dataSourceFormConfigMemo = useMemo<IConnectionConfig>(() => {
return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => {
return t.type === databaseType;
});
}, [databaseType]);
const [dataSourceFormConfig, setDataSourceFormConfig] = useState<IConnectionConfig>(dataSourceFormConfigMemo);
useEffect(() => {
setDataSourceFormConfig(dataSourceFormConfigMemo);
}, [databaseType]);
const initialValuesMemo = useMemo(() => {
return initialFormData(dataSourceFormConfigMemo[tab].items);
}, []);
const [initialValues] = useState(initialValuesMemo);
useEffect(() => {
if (!backfillData) {
return;
}
if (tab === 'baseInfo') {
// TODO:
// selectChange({ name: 'authentication', value: backfillData.user ? 1 : 2 });
regEXFormatting({ url: backfillData.url }, backfillData);
}
if (tab === 'ssh') {
regEXFormatting({}, backfillData.ssh || {});
}
}, [backfillData]);
function initialFormData(dataSourceFormConfig: IFormItem[] | undefined) {
let initValue: any = {};
dataSourceFormConfig?.map((t) => {
initValue[t.name] = t.defaultValue;
if (t.selects?.length) {
t.selects?.map((item) => {
if (item.value === t.defaultValue) {
initValue = {
...initValue,
...initialFormData(item.items),
};
}
});
}
});
return initValue;
}
function selectChange(t: { name: string; value: any }) {
dataSourceFormConfig[tab].items.map((j, i) => {
if (j.name === t.name) {
j.defaultValue = t.value;
}
});
setDataSourceFormConfig({ ...dataSourceFormConfig });
}
function onFieldsChange(data: any, datas: any) {
// 将antd的格式转换为正常的对象格式
if (!data.length) {
return;
}
const keyName = data[0].name[0];
const keyValue = data[0].value;
const variableData = {
[keyName]: keyValue,
};
const dataObj: any = {};
datas.map((t: any) => {
dataObj[t.name[0]] = t.value;
});
// 正则拆分url/组建url
if (tab === 'baseInfo') {
regEXFormatting(variableData, dataObj);
}
}
function extractObj(url: any) {
const { template, pattern } = dataSourceFormConfig.baseInfo;
// 提取关键词对应的内容 value
const matches = url.match(pattern)!;
// 提取花括号内的关键词 key
const reg = /{(.*?)}/g;
let match;
const arr = [];
while ((match = reg.exec(template)) !== null) {
arr.push(match[1]);
}
// key与value一一对应
const newExtract: any = {};
arr.map((t, i) => {
newExtract[t] = t === 'database' ? matches[i + 2] || '' : matches[i + 1];
});
return newExtract;
}
function regEXFormatting(variableData: { [key: string]: any }, dataObj: { [key: string]: any }) {
const { template, pattern } = dataSourceFormConfig.baseInfo;
const keyName = Object.keys(variableData)[0];
const keyValue = variableData[Object.keys(variableData)[0]];
let newData: any = {};
if (keyName === 'url') {
//先判断url是否符合规定的正则
if (pattern.test(keyValue)) {
newData = extractObj(keyValue);
}
} else if (keyName === 'alias') {
aliasChanged = true;
} else {
// 改变上边url动
let url = template;
Object.keys(dataObj).map((t) => {
url = url.replace(`{${t}}`, dataObj[t]);
});
newData = {
url,
};
}
if (keyName === 'host' && !aliasChanged) {
newData.alias = '@' + keyValue;
}
console.log({
...dataObj,
...newData,
});
form.setFieldsValue({
...dataObj,
...newData,
});
}
function renderFormItem(t: IFormItem): React.ReactNode {
const label = isEn ? t.labelNameEN : t.labelNameCN;
const name = t.name;
const width = t?.styles?.width || '100%';
const labelWidth = isEn ? t?.styles?.labelWidthEN || '100px' : t?.styles?.labelWidthCN || '70px';
const labelAlign = t?.styles?.labelAlign || 'left';
const FormItemTypes: { [key in InputType]: () => React.ReactNode } = {
[InputType.INPUT]: () => (
<Form.Item
label={label}
name={name}
style={{ '--form-label-width': labelWidth } as any}
labelAlign={labelAlign}
>
<Input />
</Form.Item>
),
[InputType.SELECT]: () => (
<Form.Item
label={label}
name={name}
style={{ '--form-label-width': labelWidth } as any}
labelAlign={labelAlign}
>
<Select
value={t.defaultValue}
onChange={(e) => {
selectChange({ name: name, value: e });
}}
>
{t.selects?.map((t: ISelect) => (
<Option key={t.value} value={t.value}>
{t.label}
</Option>
))}
</Select>
</Form.Item>
),
[InputType.PASSWORD]: () => (
<Form.Item
label={label}
name={name}
style={{ '--form-label-width': labelWidth } as any}
labelAlign={labelAlign}
>
<Input.Password />
</Form.Item>
),
};
return (
<Fragment key={t.name}>
<div
key={t.name}
className={classnames({ [styles.labelTextAlign]: t.labelTextAlign })}
style={{ width: width }}
>
{FormItemTypes[t.inputType]()}
</div>
{t.selects?.map((item) => {
if (t.defaultValue === item.value) {
return item.items?.map((t) => renderFormItem(t));
}
})}
</Fragment>
);
}
return (
<Form
colon={false}
name={tab}
form={form}
initialValues={initialValues}
className={styles.form}
autoComplete="off"
labelAlign="left"
onFieldsChange={onFieldsChange}
>
{dataSourceFormConfig[tab]!.items.map((t) => renderFormItem(t))}
</Form>
);
}
interface IRenderExtendTableProps {
backfillData: IConnectionDetails;
}
let extendTableData: any = [];
function RenderExtendTable(props: IRenderExtendTableProps) {
const { backfillData } = props;
const databaseType = backfillData.type;
const dataSourceFormConfigMemo = useMemo<IConnectionConfig>(() => {
return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => {
return t.type === databaseType;
});
}, [backfillData.type]);
const extendInfo =
dataSourceFormConfigMemo.extendInfo?.map((t, i) => {
return {
key: i,
label: t.key,
value: t.value,
};
}) || [];
const [data, setData] = useState([...extendInfo, { key: extendInfo.length, label: '', value: '' }]);
useEffect(() => {
const backfillDataExtendInfo =
backfillData?.extendInfo.map((t, i) => {
return {
key: i,
label: t.key,
value: t.value,
};
}) || [];
setData([...backfillDataExtendInfo, { key: extendInfo.length, label: '', value: '' }]);
}, [backfillData]);
useEffect(() => {
extendTableData = data;
}, [data]);
const columns: any = [
{
title: i18n('connection.tableHeader.name'),
dataIndex: 'label',
width: '60%',
render: (value: any, row: any, index: number) => {
let isCustomLabel = true;
dataSourceFormConfigMemo.extendInfo?.map((item) => {
if (item.key === row.label) {
isCustomLabel = false;
}
});
function change(e: any) {
const newData = [...data];
newData[index] = {
key: index,
label: e.target.value,
value: '',
};
setData(newData);
}
function blur() {
const newData = [];
data.map((t) => {
if (t.label) {
newData.push(t);
}
});
if (index === data.length - 1 && row.label) {
newData[index] = {
key: index,
label: row.label,
value: '',
};
}
setData([...newData, { key: newData.length, label: '', value: '' }]);
}
if (index === data.length - 1 || isCustomLabel) {
return (
<Input
onBlur={blur}
placeholder={index === data.length - 1 ? i18n('common.text.custom') : ''}
onChange={change}
value={value}
></Input>
);
} else {
return <span>{value}</span>;
}
},
},
{
title: i18n('connection.tableHeader.value'),
dataIndex: 'value',
width: '40%',
render: (value: any, row: any, index: number) => {
function change(e: any) {
const newData = [...data];
newData[index] = {
key: index,
label: row.label,
value: e.target.value,
};
setData(newData);
}
function blur() {}
if (index === data.length - 1) {
return <Input onBlur={blur} disabled placeholder="<value>" onChange={change} value={value}></Input>;
} else {
return <Input onChange={change} value={value}></Input>;
}
},
},
];
return (
<div className={styles.extendTable}>
<Table bordered size="small" pagination={false} columns={columns} dataSource={data} />
</div>
);
}

View File

@ -0,0 +1,120 @@
@import '../../styles/var.less';
.box {
height: 100%;
display: flex;
flex-direction: column;
}
.recordIcon {
font-size: 16px;
margin-right: 4px;
}
.resultHeader {
flex-shrink: 0;
overflow-x: scroll;
padding: 0px 10px;
background-color: var(--color-bg-300);
border-bottom: 1px solid var(--color-border);
&::-webkit-scrollbar {
display: none;
}
& ::after {
height: 0px !important;
}
.statusIcon {
margin-right: 4px;
font-size: 12px;
}
.successIcon {
color: rgb(71, 157, 255);
}
.failIcon {
color: red;
}
}
.resultContent {
flex: 1;
width: 100%;
position: relative;
overflow: auto;
}
.tableStatus {
display: flex;
align-items: center;
.dot {
display: inline-block;
margin-right: 5px;
width: 10px;
height: 10px;
background-color: #ff4d4f;
border-radius: 50%;
}
.successDot {
background-color: #52c41a;
}
}
.tableBox {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
opacity: 0;
overflow: auto;
background-color: var(--color-bg-100);
}
.cursorTableBox {
z-index: 1;
opacity: 1;
}
.tableIndex {
width: 50px;
}
.tableHoverBox {
display: none;
align-items: center;
position: absolute;
background-color: var(--color-bg-200);
top: 0;
right: 0;
bottom: 0;
i {
font-size: 15px;
margin: 0px 2px;
}
i:hover {
color: var(--custom-primary-color);
}
}
.monacoEditor {
height: 300px;
}
.tableItem {
width: 100%;
.f-lines(1);
cursor: pointer;
&:hover .tableHoverBox {
display: flex;
}
}

View File

@ -0,0 +1,199 @@
import React, { memo, useEffect, useState, useRef } from 'react';
import classnames from 'classnames';
import Tabs from '@/components/Tabs';
import Iconfont from '@/components/Iconfont';
import StateIndicator from '@/components/StateIndicator';
import LoadingContent from '@/components/Loading/LoadingContent';
import MonacoEditor from '@/components/Console/MonacoEditor';
import { Button, DatePicker, Input, Table, Modal, message } from 'antd';
import { StatusType, TableDataType } from '@/constants/table';
import { formatDate } from '@/utils/date';
import { IManageResultData, ITableHeaderItem } from '@/typings/database';
import styles from './index.less';
interface IProps {
className?: string;
manageResultDataList: IManageResultData[];
}
interface DataType {
[key: string]: any;
}
export default memo<IProps>(function SearchResult({ className, manageResultDataList = [] }) {
const [isUnfold, setIsUnfold] = useState(true);
const [currentTab, setCurrentTab] = useState('0');
useEffect(() => {
setCurrentTab('0');
}, [manageResultDataList]);
const renderStatus = (text: string) => {
return (
<div className={styles.tableStatus}>
<i className={classnames(styles.dot, { [styles.successDot]: text == StatusType.SUCCESS })}></i>
{text == StatusType.SUCCESS ? '成功' : '失败'}
</div>
);
};
function onChange(index: string) {
setCurrentTab(index);
}
const makerResultHeaderList = () => {
const list: any = [];
manageResultDataList?.map((item, index) => {
list.push({
label: (
<div key={index}>
<Iconfont
className={classnames(styles[item.success ? 'successIcon' : 'failIcon'], styles.statusIcon)}
code={item.success ? '\ue605' : '\ue87c'}
/>
{index + 1}
</div>
),
key: index,
});
});
return list;
};
return (
<div className={classnames(className, styles.box)}>
<div className={styles.resultHeader}>
<Tabs onChange={onChange} tabs={makerResultHeaderList()} />
</div>
<div className={styles.resultContent}>
<LoadingContent data={manageResultDataList} handleEmpty>
{manageResultDataList.map((item, index) => {
if (item.success) {
return (
<TableBox
key={index}
className={classnames({ [styles.cursorTableBox]: index + '' == currentTab })}
data={item}
headerList={item.headerList}
dataList={item.dataList}
></TableBox>
);
} else {
return <StateIndicator key={index} state="error" text={item.message}></StateIndicator>;
}
})}
</LoadingContent>
</div>
</div>
);
});
interface ITableProps {
headerList: ITableHeaderItem[];
dataList: string[][];
className?: string;
data: IManageResultData;
}
interface IViewTableCellData {
name: string;
value: any;
}
export function TableBox(props: ITableProps) {
const { headerList, dataList, className, data, ...rest } = props;
const [columns, setColumns] = useState<any>();
const [tableData, setTableData] = useState<any>();
const [viewTableCellData, setViewTableCellData] = useState<IViewTableCellData | null>(null);
function viewTableCell(data: IViewTableCellData) {
setViewTableCellData(data);
}
function copyTableCell(data: IViewTableCellData) {
navigator.clipboard.writeText(data?.value || viewTableCellData?.value);
message.success('复制成功');
}
function handleCancel() {
setViewTableCellData(null);
}
useEffect(() => {
if (!headerList?.length) {
return;
}
const columns: any = headerList?.map((item: any, index) => {
const data = {
title: item.name,
dataIndex: item.name,
key: item.name,
type: item.dataType,
sorter: (a: any, b: any) => a[item.name] - b[item.name],
render: (value: any) => (
<div className={styles.tableItem}>
<div className={styles.tableHoverBox}>
<Iconfont code="&#xe606;" onClick={viewTableCell.bind(null, { name: item.name, value })} />
<Iconfont code="&#xeb4e;" onClick={copyTableCell.bind(null, { name: item.name, value })} />
</div>
{value}
</div>
),
};
return data;
});
setColumns(columns);
}, [headerList]);
useEffect(() => {
if (!columns?.length) return;
const tableData = dataList?.map((item, rowIndex) => {
const rowData: any = {};
item.map((i: string | null, index: number) => {
const { dataType: type } = headerList[index] || {};
// console.log('headerList[rowIndex]', headerList[rowIndex]);
if (type === TableDataType.DATETIME && i) {
rowData[columns[index].title] = formatDate(i, 'yyyy-MM-dd hh:mm:ss');
} else if (i === null) {
rowData[columns[index].title] = '[null]';
} else {
rowData[columns[index].title] = i;
}
});
rowData.key = rowIndex;
return rowData;
});
setTableData(tableData);
}, [columns]);
return (
<div {...rest} className={classnames(className, styles.tableBox)}>
{dataList !== null ? (
<Table bordered pagination={false} columns={columns} dataSource={tableData} size="small" />
) : (
<StateIndicator state="success" text="执行成功"/>
)}
<Modal
title={viewTableCellData?.name}
open={!!viewTableCellData?.name}
onCancel={handleCancel}
width="60vw"
maskClosable={false}
footer={
<>
{
<Button onClick={copyTableCell.bind(null, viewTableCellData!)} className={styles.cancel}>
</Button>
}
</>
}
>
<div className={styles.monacoEditor}>
<MonacoEditor value={viewTableCellData?.value} readOnly={true} id="view_table-Cell_data"></MonacoEditor>
</div>
</Modal>
</div>
);
}

View File

@ -0,0 +1,50 @@
// @import '../../var.less';
.box{
display: flex;
position: relative;
.extra{
flex: 1;
}
&::after{
position: absolute;
content: '';
bottom: 1px;
left: 0;
width: 100%;
height: 1px;
background-color: var(--color-border);
}
:global {
.custom-tabs{
width: 100%;
flex-shrink: 0;
}
.custom-tabs-tab {
margin: 0px 10px;
font-size: 12px;
}
.custom-tabs-nav{
margin: 0 !important;
}
.custom-tabs-nav::before{
border: 0;
border-bottom: 0 !important;
}
.custom-tabs-tab{
user-select: none;
padding: 5px 10px;
margin: 0px 0px 5px 0px;
border-radius: 5px;
&:hover{
color: var(--color-text-85);
background-color: var(--color-bg-hover);
}
}
.custom-tabs-tab-active{
&:hover{
background-color: transparent;
}
}
}
}

View File

@ -0,0 +1,37 @@
import React, { memo, ReactNode, useState } from 'react';
import styles from './index.less';
import classnames from 'classnames';
import { Tabs as AntdTabs } from 'antd';
export interface ITab {
label: ReactNode;
key: string;
}
interface IProps {
className?: string;
tabs: ITab[];
currentTab?: string;
onChange: (key: string, index: number) => void;
extra?: React.ReactNode
}
export default memo(function Tabs({ className, tabs, currentTab, onChange, extra }: IProps) {
function myChange(key: string) {
const index = tabs.findIndex(t => {
return t.key === key
})
onChange(key, index)
}
return <div className={classnames(className, styles.box)}>
<AntdTabs
defaultActiveKey={currentTab}
onChange={myChange}
items={tabs}
/>
<div className={styles.extra}>
{extra}
</div>
</div>
})

View File

@ -0,0 +1,22 @@
export enum TableDataType {
BOOLEAN = 'BOOLEAN',
NUMERIC = 'NUMERIC',
STRING = 'STRING',
DATETIME = 'DATETIME',
// 暂时不适配
BINARY = 'BINARY',
CONTENT = 'CONTENT',
STRUCT = 'STRUCT',
DOCUMENT = 'DOCUMENT',
ARRAY = 'ARRAY',
OBJECT = 'OBJECT',
REFERENCE = 'REFERENCE',
ROWID = 'ROWID',
ANY = 'ANY',
UNKNOWN = 'UNKNOWN',
}
export enum StatusType {
SUCCESS = 'success',
FAIL = 'fail',
}

View File

@ -21,9 +21,10 @@ export default {
'common.button.save': 'Save',
'common.button.execute': 'Run',
'common.message.successfulConfig': 'Successful configuration',
'common.text.successful':'successful',
'common.text.successful': 'successful',
'common.text.failure': 'failure',
'common.message.modifySuccessfully':'modify successfully',
'common.message.addedSuccessfully':'successfully added',
'common.text.custom':'custom',
}
'common.message.modifySuccessfully': 'modify successfully',
'common.message.addedSuccessfully': 'successfully added',
'common.text.custom': 'custom',
'common.button.delete': 'Delete',
};

View File

@ -11,7 +11,7 @@ export default {
'connection.button.testConnection': 'Test',
'connection.label.advancedConfiguration': 'Advanced Configuration',
'connection.label.sshConfiguration': 'SSH Configuration',
'connection.button.addConnection': 'ADD Connection',
'connection.button.addConnection': 'Add Connection',
'connection.button.connect': 'Connect',
'connection.message.testConnectResult': 'Test connection is {1}',
'connection.message.testSshConnection': 'Test the ssh connection',

View File

@ -26,4 +26,5 @@ export default {
'common.message.modifySuccessfully':'修改成功',
'common.message.addedSuccessfully': '添加成功',
'common.text.custom':'自定义',
'common.button.delete':'删除'
}

Some files were not shown because too many files have changed in this diff Show More