Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
robin
2023-10-26 15:05:30 +08:00
46 changed files with 631 additions and 155 deletions

View File

@ -255,7 +255,9 @@ jobs:
cp chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/chat2db-server-start.jar ./oss_temp_file cp chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/chat2db-server-start.jar ./oss_temp_file
cp -r chat2db-client/release/*.dmg ./oss_temp_file cp -r chat2db-client/release/*.dmg ./oss_temp_file
cp -r chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/dist ./oss_temp_file/dist cp -r chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/dist ./oss_temp_file/dist
cd chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/ && zip -r chat2db-server-start.zip ./ cd chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/ && zip -r ${{ steps.chat2db_version.outputs.substring }}.zip ./
cp -r ${{ steps.chat2db_version.outputs.substring }}.zip ../../../oss_temp_file
cd static/ && zip -r chat2db-server-start.zip ./
cp -r chat2db-server-start.zip ../../../../oss_temp_file cp -r chat2db-server-start.zip ../../../../oss_temp_file
# 准备要需要的数据 MacOS arm64 # 准备要需要的数据 MacOS arm64

View File

@ -1,3 +1,28 @@
# 3.0.5
`2023-10-23`
**Changelog**
- ⭐【New Features】Supports visual database creation
- ⭐【New Features】Support hot update
- ⭐【New Features】Double-click the table to open it directly
-【Optimize】The search table supports size fuzzy matching
-【Optimize】Sort Database and Schema at the top
-【Optimize】The queried data supports editing and modification in the large popup window of the view
-【Optimize】Example Query the page loading effect of data
-【Optimize】Keep the top focused tab always in the viewable area
-【Optimize】Query data cell does not have scroll bar problem
**更新日志**
- ⭐【新功能】支持可视化创建数据库
- ⭐【新功能】支持热更新
- ⭐【新功能】双击表直接打开表
- ⚡️【优化】搜索表支持大小模糊匹配
-【优化】Database 和 Schema 排序
- ⚡️【优化】查询的数据支持在查看的大的弹窗中编辑修改
-【优化】查询数据翻页loading效果
-【优化】保持顶部聚焦的tab永远在可视区域内
- ⚡️【优化】查询数据单元格没有滚动条问题
# 3.0.4 # 3.0.4
`2023-10-20` `2023-10-20`

View File

@ -97,7 +97,7 @@ export default defineConfig({
define: { define: {
__ENV__: process.env.UMI_ENV, __ENV__: process.env.UMI_ENV,
__BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()), __BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()),
__APP_VERSION__: yarn_config.app_version || '9.9.9', __APP_VERSION__: yarn_config.app_version || '0.0.0',
__APP_PORT__: yarn_config.app_port, __APP_PORT__: yarn_config.app_port,
}, },
esbuildMinifyIIFE: true esbuildMinifyIIFE: true

View File

@ -129,7 +129,7 @@
} }
], ],
"publisherName": "Chat2DB", "publisherName": "Chat2DB",
"icon": "src/assets/logo/logo.png" "icon": "src/assets/logo/logo.ico"
}, },
"linux": { "linux": {
"maintainer": "Chat2DB, huanyueyaoqin@qq.com", "maintainer": "Chat2DB, huanyueyaoqin@qq.com",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -4,7 +4,7 @@ import styles from './index.less';
import classnames from 'classnames'; import classnames from 'classnames';
import DraggableContainer from '@/components/DraggableContainer'; import DraggableContainer from '@/components/DraggableContainer';
import Console, { IAppendValue } from '@/components/Console'; import Console, { IAppendValue } from '@/components/Console';
import SearchResult from '@/components/SearchResult'; import SearchResult, { ISearchResultRef } from '@/components/SearchResult';
import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import { DatabaseTypeCode, ConsoleStatus } from '@/constants';
import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace';
import { IAIState } from '@/models/ai'; import { IAIState } from '@/models/ai';
@ -31,7 +31,8 @@ const SQLExecute = memo<IProps>((props) => {
const draggableRef = useRef<any>(); const draggableRef = useRef<any>();
const [appendValue, setAppendValue] = useState<IAppendValue>(); const [appendValue, setAppendValue] = useState<IAppendValue>();
const { curTableList, curWorkspaceParams } = workspaceModel; const { curTableList, curWorkspaceParams } = workspaceModel;
const [sql, setSql] = useState<string>(''); // const [sql, setSql] = useState<string>('');
const searchResultRef = useRef<ISearchResultRef>(null);
// useEffect(() => { // useEffect(() => {
// if (!doubleClickTreeNodeData) { // if (!doubleClickTreeNodeData) {
@ -67,7 +68,9 @@ const SQLExecute = memo<IProps>((props) => {
executeParams={{ ...data }} executeParams={{ ...data }}
hasAiChat={true} hasAiChat={true}
hasAi2Lang={true} hasAi2Lang={true}
onExecuteSQL={setSql} onExecuteSQL={(sql) => {
searchResultRef.current?.handleExecuteSQL(sql);
}}
onConsoleSave={() => { onConsoleSave={() => {
dispatch({ dispatch({
type: 'workspace/fetchGetSavedConsole', type: 'workspace/fetchGetSavedConsole',
@ -89,7 +92,7 @@ const SQLExecute = memo<IProps>((props) => {
/> />
</div> </div>
<div className={styles.boxRightResult}> <div className={styles.boxRightResult}>
<SearchResult executeSqlParams={data} sql={sql} /> <SearchResult ref={searchResultRef} executeSqlParams={data} />
</div> </div>
</DraggableContainer> </DraggableContainer>
</div> </div>

View File

@ -9,7 +9,6 @@ import { DownloadOutlined } from '@ant-design/icons';
import { IUpdateDetectionData } from '../index'; import { IUpdateDetectionData } from '../index';
import { IUpdateDetectionRef, UpdatedStatusEnum } from '../UpdateDetection'; import { IUpdateDetectionRef, UpdatedStatusEnum } from '../UpdateDetection';
import Iconfont from '@/components/Iconfont'; import Iconfont from '@/components/Iconfont';
interface IProps { interface IProps {
updateDetectionData: IUpdateDetectionData | null; updateDetectionData: IUpdateDetectionData | null;
updateDetectionRef: React.MutableRefObject<IUpdateDetectionRef> | null; updateDetectionRef: React.MutableRefObject<IUpdateDetectionRef> | null;
@ -35,7 +34,6 @@ export default function AboutUs(props: IProps) {
}; };
const restartApp = () => { const restartApp = () => {
console.log(window.electronApi)
window.electronApi?.quitApp(); window.electronApi?.quitApp();
} }
@ -80,6 +78,12 @@ export default function AboutUs(props: IProps) {
{i18n('setting.button.restart')} {i18n('setting.button.restart')}
</Button> </Button>
); );
// case UpdatedStatusEnum.UPDATED:
// return (
// <Button icon={<RedoOutlined />} type="primary">
// {i18n('setting.button.restart')}
// </Button>
// );
default: default:
return false; return false;
} }

View File

@ -37,10 +37,9 @@ const MAX_TIMES = 200;
const UpdateDetection = memo( const UpdateDetection = memo(
forwardRef((props: IProps, ref: ForwardedRef<IUpdateDetectionRef>) => { forwardRef((props: IProps, ref: ForwardedRef<IUpdateDetectionRef>) => {
const { openSettingModal, updateDetectionData, setUpdateDetectionData } = props; const { openSettingModal, setUpdateDetectionData } = props;
const [notificationApi, notificationDom] = notification.useNotification(); const [notificationApi, notificationDom] = notification.useNotification();
const timesRef = React.useRef(0); const timesRef = React.useRef(0);
console.log(updateDetectionData);
useEffect(() => { useEffect(() => {
checkUpdate(); checkUpdate();

View File

@ -2,7 +2,8 @@ import React, { ForwardedRef, forwardRef, useEffect, useImperativeHandle, useRef
import cs from 'classnames'; import cs from 'classnames';
import { useTheme } from '@/hooks'; import { useTheme } from '@/hooks';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { DatabaseTypeCode, editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; import { DatabaseTypeCode, EditorThemeType, ThemeType } from '@/constants';
import { editorDefaultOptions } from './monacoEditorConfig';
import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'; import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput';
import styles from './index.less'; import styles from './index.less';
@ -88,6 +89,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef<IExportRefFunction>) {
// 'editorLineNumber.foreground': colorPrimary, // 行号颜色 // 'editorLineNumber.foreground': colorPrimary, // 行号颜色
'editorLineNumber.activeForeground': colorPrimary, // 当前行号颜色 'editorLineNumber.activeForeground': colorPrimary, // 当前行号颜色
// 'editorCursor.foreground': colorPrimary, // 光标颜色 // 'editorCursor.foreground': colorPrimary, // 光标颜色
'editorRuler.foreground': colorPrimary + '15',
}; };
monaco.editor.defineTheme(ThemeType.Light, { monaco.editor.defineTheme(ThemeType.Light, {
base: 'vs', base: 'vs',

View File

@ -30,13 +30,13 @@ enum IPromptType {
ChatRobot = 'ChatRobot', ChatRobot = 'ChatRobot',
} }
enum IPromptTypeText { // enum IPromptTypeText {
NL_2_SQL = '自然语言转换', // NL_2_SQL = '自然语言转换',
SQL_EXPLAIN = '解释SQL', // SQL_EXPLAIN = '解释SQL',
SQL_OPTIMIZER = 'SQL优化', // SQL_OPTIMIZER = 'SQL优化',
SQL_2_SQL = 'SQL转换', // SQL_2_SQL = 'SQL转换',
ChatRobot = 'Chat机器人', // ChatRobot = 'Chat机器人',
} // }
export type IAppendValue = { export type IAppendValue = {
text: any; text: any;
@ -65,11 +65,12 @@ interface IProps {
consoleId?: number; consoleId?: number;
schemaName?: string; schemaName?: string;
consoleName?: string; consoleName?: string;
status?: ConsoleStatus;
}; };
tableList?: ITreeNode[]; tableList?: ITreeNode[];
editorOptions?: IEditorOptions; editorOptions?: IEditorOptions;
aiModel: IAIState; aiModel: IAIState;
dispatch: Function; dispatch: any;
remainingBtnLoading: boolean; remainingBtnLoading: boolean;
// remainingUse: IAIState['remainingUse']; // remainingUse: IAIState['remainingUse'];
// onSQLContentChange: (v: string) => void; // onSQLContentChange: (v: string) => void;
@ -108,8 +109,9 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
const [isStream, setIsStream] = useState(false); const [isStream, setIsStream] = useState(false);
const timerRef = useRef<any>(); const timerRef = useRef<any>();
const aiFetchIntervalRef = useRef<any>(); const aiFetchIntervalRef = useRef<any>();
const initializeSuccessful = useRef(false);
const closeEventSource = useRef<any>(); const closeEventSource = useRef<any>();
// 上一次同步的console数据
const lastSyncConsole = useRef<any>(defaultValue);
/** /**
* 当前选择的AI类型是Chat2DBAI * 当前选择的AI类型是Chat2DBAI
@ -133,23 +135,19 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
editorRef: editorRef?.current, editorRef: editorRef?.current,
})); }));
useEffect(() => {}, []);
useEffect(() => { useEffect(() => {
if (source !== 'workspace') { if (source !== 'workspace') {
return; return;
} }
// 离开时保存 // 离开时保存
if (!isActive) { if (!isActive && timerRef.current) {
// 离开时清除定时器 // 离开时清除定时器
indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { indexedDB.updateData('chat2db', 'workspaceConsoleDDL', {
consoleId: executeParams.consoleId!, consoleId: executeParams.consoleId!,
ddl: editorRef?.current?.getAllContent(), ddl: editorRef?.current?.getAllContent(),
userId: getCookie('CHAT2DB.USER_ID'), userId: getCookie('CHAT2DB.USER_ID'),
}); });
if (timerRef.current) { clearInterval(timerRef.current);
clearInterval(timerRef.current);
}
} else { } else {
// 活跃时自动保存 // 活跃时自动保存
indexedDB indexedDB
@ -158,12 +156,14 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
userId: getCookie('CHAT2DB.USER_ID'), userId: getCookie('CHAT2DB.USER_ID'),
}) })
.then((res: any) => { .then((res: any) => {
const value = res?.[0]?.ddl || ''; const value = defaultValue || res?.[0]?.ddl || '';
if (value) { const oldValue = editorRef?.current?.getAllContent();
if (value !== oldValue) {
editorRef?.current?.setValue(value, 'reset'); editorRef?.current?.setValue(value, 'reset');
initializeSuccessful.current = true;
timingAutoSave();
} }
setTimeout(() => {
timingAutoSave();
}, 0);
}); });
} }
return () => { return () => {
@ -173,14 +173,30 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
}; };
}, [isActive]); }, [isActive]);
function timingAutoSave() { function timingAutoSave(status?: ConsoleStatus) {
if (timerRef.current) {
clearInterval(timerRef.current);
}
timerRef.current = setInterval(() => { timerRef.current = setInterval(() => {
indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { const ddl = editorRef?.current?.getAllContent();
consoleId: executeParams.consoleId!, if (ddl === lastSyncConsole.current) {
ddl: editorRef?.current?.getAllContent(), return;
userId: getCookie('CHAT2DB.USER_ID'), }
}); lastSyncConsole.current = ddl;
}, 3000); if (executeParams.status === ConsoleStatus.RELEASE || status === ConsoleStatus.RELEASE) {
const p: any = {
id: executeParams.consoleId,
ddl,
};
historyServer.updateSavedConsole(p);
} else {
indexedDB.updateData('chat2db', 'workspaceConsoleDDL', {
consoleId: executeParams.consoleId!,
ddl,
userId: getCookie('CHAT2DB.USER_ID'),
});
}
}, 5000);
} }
const tableListName = useMemo(() => { const tableListName = useMemo(() => {
@ -370,7 +386,7 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
const saveConsole = (value?: string) => { const saveConsole = (value?: string) => {
// const a = editorRef.current?.getAllContent(); // const a = editorRef.current?.getAllContent();
let p: any = { const p: any = {
id: executeParams.consoleId, id: executeParams.consoleId,
status: ConsoleStatus.RELEASE, status: ConsoleStatus.RELEASE,
ddl: value, ddl: value,
@ -380,6 +396,7 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', executeParams.consoleId!); indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', executeParams.consoleId!);
message.success(i18n('common.tips.saveSuccessfully')); message.success(i18n('common.tips.saveSuccessfully'));
props.onConsoleSave && props.onConsoleSave(); props.onConsoleSave && props.onConsoleSave();
timingAutoSave(ConsoleStatus.RELEASE);
}); });
}; };
@ -447,10 +464,10 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
}; };
const handleSelectTableSyncModel = () => { const handleSelectTableSyncModel = () => {
const syncModel: SyncModelType | null = Number(localStorage.getItem('syncTableModel')) ?? null; const syncModel = localStorage.getItem('syncTableModel');
const hasAiAccess = aiModel.hasWhite; const hasAiAccess = aiModel.hasWhite;
if (syncModel !== null) { if (syncModel !== null) {
setSyncTableModel(syncModel); setSyncTableModel(Number(syncModel));
return; return;
} }

View File

@ -0,0 +1,39 @@
@import '../../styles/var.less';
.monacoEditorBox {
height: 200px;
border: 1px solid var(--color-border);
border-radius: 4px;
overflow: hidden;
}
.errorBox {
margin-top: 10px;
}
.previewBox {
// 伪元素画一条剧中的线条
position: relative;
display: flex;
margin-bottom: 4px;
.previewText {
background: var(--color-bg-base);
flex-shrink: 0;
margin-right: 10px;
}
.previewLine {
flex: 1;
position: relative;
&::after {
position: absolute;
left: 0;
right: 0px;
top: 50%;
content: '';
width: 100%;
height: 1px;
background: var(--color-border);
transform: translateY(-50%);
}
}
}

View File

@ -0,0 +1,177 @@
import React, { useCallback, forwardRef, ForwardedRef, useImperativeHandle, useMemo, useState, useEffect } from 'react';
import styles from './index.less';
import classnames from 'classnames';
import { Form, Input, Modal } from 'antd';
import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor';
import { v4 as uuid } from 'uuid';
import sqlService from '@/service/sql';
import i18n from '@/i18n';
import { debounce } from 'lodash';
import { DatabaseTypeCode } from '@/constants';
interface IProps {
className?: string;
curWorkspaceParams: any;
executedCallback?: () => void;
}
export type CreateType = 'database' | 'schema';
export interface ICreateDatabaseRef {
setOpen: (open: boolean, type?: CreateType) => void;
}
export interface ICreateDatabase {
databaseName?: string;
schemaName?: string;
comment?: string;
}
// 创建database不支持注释的数据库
const noCommentDatabase = [DatabaseTypeCode.MYSQL];
export default forwardRef((props: IProps, ref: ForwardedRef<ICreateDatabaseRef>) => {
const { className, curWorkspaceParams, executedCallback } = props;
const [form] = Form.useForm<ICreateDatabase>();
const monacoEditorUuid = useMemo(() => uuid(), []);
const monacoEditorRef = React.useRef<IExportRefFunction>(null);
const [open, setOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState<{ success: boolean; message: string; originalSql: string } | null>(
null,
);
const [confirmLoading, setConfirmLoading] = useState(false);
const [createType, setCreateType] = useState<CreateType>('database');
useEffect(() => {
if (!open) {
setErrorMessage(null);
form.resetFields();
monacoEditorRef.current?.setValue('', 'cover');
}
}, [open]);
const config = useMemo(() => {
return createType === 'database'
? {
title: `${i18n('common.title.create')} Database`,
api: sqlService.getCreateDatabaseSql,
formName: 'databaseName',
}
: {
title: `${i18n('common.title.create')} Schema`,
api: sqlService.getCreateSchemaSql,
formName: 'schemaName',
};
}, [createType]);
const exposedSetOpen = (_open: boolean, type?: CreateType) => {
setOpen(_open);
setCreateType(type || 'database');
};
useImperativeHandle(ref, () => ({
setOpen: exposedSetOpen,
}));
const labelCol = { flex: '70px' };
const handleFieldsChange = useCallback(
debounce(() => {
const formData: ICreateDatabase = form.getFieldsValue();
if (!formData.databaseName && createType === 'database') {
return;
}
if (!formData.schemaName && createType === 'schema') {
return;
}
const params = {
databaseType: curWorkspaceParams.databaseType,
dataSourceId: curWorkspaceParams.dataSourceId,
databaseName: curWorkspaceParams.databaseName,
...formData,
};
config.api(params).then((res) => {
const { sql } = res;
monacoEditorRef.current?.setValue(sql, 'cover');
});
}, 500),
[curWorkspaceParams, createType, monacoEditorRef, config],
);
const executeUpdateDataSql = (sql: string) => {
const params: any = {
dataSourceId: curWorkspaceParams.dataSourceId,
databaseType: curWorkspaceParams.databaseType,
databaseName: curWorkspaceParams.databaseName,
sql,
};
setConfirmLoading(true);
setErrorMessage(null);
return sqlService
.executeDDL(params)
.then((res) => {
if (res.success) {
setOpen(false);
executedCallback?.();
} else {
setErrorMessage(res);
}
})
.finally(() => {
setConfirmLoading(false);
});
};
const onOk = () => {
const sql = monacoEditorRef.current?.getAllContent() || '';
executeUpdateDataSql(sql);
};
return (
<Modal
onCancel={() => {
setOpen(false);
}}
title={config.title}
destroyOnClose
confirmLoading={confirmLoading}
open={open}
onOk={onOk}
>
<div className={classnames(styles.box, className)}>
<Form labelAlign="left" form={form} labelCol={labelCol} onFieldsChange={handleFieldsChange} name="create">
<Form.Item label={i18n('common.label.name')} name={config.formName}>
<Input autoComplete="off" />
</Form.Item>
{noCommentDatabase.includes(curWorkspaceParams.databaseType) ? null : (
<Form.Item label={i18n('common.label.comment')} name="comment">
<Input autoComplete="off" />
</Form.Item>
)}
</Form>
<div className={styles.previewBox}>
<div className={styles.previewText}>{i18n('common.title.preview')}</div>
<div className={styles.previewLine} />
</div>
<div className={styles.monacoEditorBox}>
<MonacoEditor
ref={monacoEditorRef}
options={{
lineNumbers: 'off',
}}
id={monacoEditorUuid}
/>
</div>
{errorMessage && (
<>
<div className={classnames(styles.previewBox, styles.errorBox)}>
<div className={styles.previewText}>{i18n('common.title.errorMessage')}</div>
<div className={styles.previewLine} />
</div>
<div>{errorMessage.message}</div>
</>
)}
</div>
</Modal>
);
});

View File

@ -85,6 +85,7 @@
padding: 4px 10px; padding: 4px 10px;
background-color: var(--color-bg-subtle); background-color: var(--color-bg-subtle);
border-radius: 8px; border-radius: 8px;
.f-doc-en-break();
} }
} }
} }

View File

@ -0,0 +1 @@
@import '../../styles/var.less';

View File

@ -0,0 +1,31 @@
import React, { memo, Fragment, ReactElement, cloneElement } from 'react';
import { Modal } from 'antd';
interface ITriggeredModal extends React.ComponentProps<typeof Modal> {
children: ReactElement;
modalContent: React.ReactNode;
onOk?: any;
}
const TriggeredModal = memo<ITriggeredModal>((props) => {
const { children, modalContent, ...orgs } = props;
const [open, setOpen] = React.useState(false);
const onClose = () => {
setOpen(false);
};
const onOk = () => {
orgs?.onOk?.(setOpen);
};
return (
<Fragment>
{cloneElement(children, { onClick: () => setOpen(true) })}
<Modal {...orgs} open={open} onCancel={onClose} onOk={onOk}>
{modalContent}
</Modal>
</Fragment>
);
});
export default TriggeredModal;

View File

@ -234,7 +234,6 @@ export default function TableBox(props: ITableProps) {
} }
}); });
setTableData(newTableData); setTableData(newTableData);
console.log(newTableData);
// 添加更新记录 // 添加更新记录
setUpdateData([ setUpdateData([

View File

@ -1,4 +1,13 @@
import React, { memo, useCallback, useEffect, useMemo, useState, useRef } from 'react'; import React, {
useCallback,
useEffect,
useMemo,
useState,
useRef,
forwardRef,
ForwardedRef,
useImperativeHandle,
} from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import Tabs from '@/components/Tabs'; import Tabs from '@/components/Tabs';
import Iconfont from '@/components/Iconfont'; import Iconfont from '@/components/Iconfont';
@ -27,7 +36,11 @@ const defaultResultConfig: IResultConfig = {
hasNextPage: true, hasNextPage: true,
}; };
export default memo<IProps>((props) => { export interface ISearchResultRef {
handleExecuteSQL: (sql: string) => void;
}
export default forwardRef((props: IProps, ref: ForwardedRef<ISearchResultRef>) => {
const { className, sql, executeSqlParams } = props; const { className, sql, executeSqlParams } = props;
// const [currentTab, setCurrentTab] = useState<string | number | undefined>(); // const [currentTab, setCurrentTab] = useState<string | number | undefined>();
const [resultDataList, setResultDataList] = useState<IManageResultData[]>(); const [resultDataList, setResultDataList] = useState<IManageResultData[]>();
@ -40,6 +53,10 @@ export default memo<IProps>((props) => {
} }
}, [sql]); }, [sql]);
useImperativeHandle(ref, () => ({
handleExecuteSQL,
}));
/** /**
* 执行SQL * 执行SQL
* @param sql * @param sql

View File

@ -6,9 +6,7 @@ interface IProps {
className?: string; className?: string;
} }
export default memo<IProps>(function XXXX_FN(props) { export default memo<IProps>((props) => {
const { className } = props const { className } = props;
return <div className={classnames(styles.box, className)}> return <div className={classnames(styles.box, className)}>demo</div>;
});
</div>
})

View File

@ -2,7 +2,6 @@ export * from './appConfig';
export * from './common'; export * from './common';
export * from './database'; export * from './database';
export * from './environment'; export * from './environment';
export * from './monacoEditor';
export * from './table'; export * from './table';
export * from './theme'; export * from './theme';
export * from './tree'; export * from './tree';

View File

@ -93,5 +93,12 @@ export default {
'common.text.affectedRows': 'Affected rows: {1}', 'common.text.affectedRows': 'Affected rows: {1}',
'common.text.selectFile' : 'Select File', 'common.text.selectFile' : 'Select File',
'common.text.noTableFoundUp' : 'No tables in this database', 'common.text.noTableFoundUp' : 'No tables in this database',
'common.text.noTableFoundDown' : 'Switch databases at the top' 'common.text.noTableFoundDown': 'Switch databases at the top',
'common.title.preview': 'Preview',
'common.title.errorMessage': 'Error message',
'common.Button.addDatabase': 'Add database',
'common.Button.addSchema': 'Add schema',
'common.label.comment': 'Comment',
'common.label.name': 'Name',
'common.title.create': 'Create',
}; };

View File

@ -40,7 +40,7 @@ export default {
'setting.button.restart': 'Restart', 'setting.button.restart': 'Restart',
'setting.text.discoverNewVersion': 'Discover new version {1}', 'setting.text.discoverNewVersion': 'Discover new version {1}',
'setting.text.isLatestVersion': 'This is the latest version', 'setting.text.isLatestVersion': 'This is the latest version',
'setting.button.changeLog': 'ChangeLog', 'setting.button.changeLog': 'Changelog',
'setting.title.updateRule': 'Update rule', 'setting.title.updateRule': 'Update rule',
'setting.text.autoUpdate': 'The new version automatically downloads and installs updates', 'setting.text.autoUpdate': 'The new version automatically downloads and installs updates',
'setting.text.manualUpdate': 'Only alert me when a new version is released', 'setting.text.manualUpdate': 'Only alert me when a new version is released',

View File

@ -93,4 +93,11 @@ export default {
'common.text.noTableFoundUp' : '当前库没有查询到表', 'common.text.noTableFoundUp' : '当前库没有查询到表',
'common.text.noTableFoundDown' : '你可以在顶部切换数据库', 'common.text.noTableFoundDown' : '你可以在顶部切换数据库',
'common.text.updateNow' : '立即更新', 'common.text.updateNow' : '立即更新',
'common.title.preview' : '预览',
'common.title.errorMessage': '错误信息',
'common.Button.addDatabase': '添加数据库',
'common.Button.addSchema': '添加Schema',
'common.label.comment': '备注',
'common.label.name': '名称',
'common.title.create': '创建',
}; };

View File

@ -6,7 +6,6 @@ import { v4 as uuidv4 } from 'uuid';
import { getAntdThemeConfig, injectThemeVar } from '@/theme'; import { getAntdThemeConfig, injectThemeVar } from '@/theme';
import { IVersionResponse } from '@/typings'; import { IVersionResponse } from '@/typings';
import miscService from '@/service/misc'; import miscService from '@/service/misc';
import configService from '@/service/config';
import antdEnUS from 'antd/locale/en_US'; import antdEnUS from 'antd/locale/en_US';
import antdZhCN from 'antd/locale/zh_CN'; import antdZhCN from 'antd/locale/zh_CN';
import { useTheme } from '@/hooks'; import { useTheme } from '@/hooks';
@ -34,7 +33,7 @@ declare global {
electronApi?: { electronApi?: {
startServerForSpawn: () => void; startServerForSpawn: () => void;
quitApp: () => void; quitApp: () => void;
beforeQuitApp: (fn: () => void) => void; setBaseURL: (baseUrl:string) => void;
registerAppMenu: (data:any) => void; registerAppMenu: (data:any) => void;
}; };
} }
@ -111,10 +110,9 @@ function AppContainer() {
window.electronApi?.registerAppMenu({ window.electronApi?.registerAppMenu({
version: __APP_VERSION__, version: __APP_VERSION__,
}) })
// 把关闭java服务的的方法传给electron
window.electronApi?.beforeQuitApp(()=>{ window.electronApi?.setBaseURL?.(window._BaseURL)
configService.stopJavaService() // console.log(window.electronApi)
})
} }
// 初始化indexedDB // 初始化indexedDB

View File

@ -10,6 +10,8 @@ const { loadMainResource } = require('./utils');
let mainWindow = null; let mainWindow = null;
let baseUrl = null;
function createWindow() { function createWindow() {
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
minWidth: 1080, minWidth: 1080,
@ -69,7 +71,18 @@ app.on('window-all-closed', () => {
}); });
app.on('before-quit', () => { app.on('before-quit', () => {
mainWindow.webContents.send('before-quit-app'); if(baseUrl){
try {
const request = net.request({
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
url: `${baseUrl}/api/system/stop`,
});
request.end();
} catch (error) {}
}
}); });
ipcMain.handle('get-product-name', () => { ipcMain.handle('get-product-name', () => {
@ -86,3 +99,9 @@ ipcMain.on('quit-app', () => {
ipcMain.on('register-app-menu', (event, orgs) => { ipcMain.on('register-app-menu', (event, orgs) => {
registerAppMenu(mainWindow, orgs); registerAppMenu(mainWindow, orgs);
}); });
ipcMain.on('set-base-url',(event,_baseUrl)=>{
baseUrl = _baseUrl;
})

View File

@ -43,13 +43,10 @@ contextBridge.exposeInMainWorld('electronApi', {
quitApp: () => { quitApp: () => {
ipcRenderer.send('quit-app'); ipcRenderer.send('quit-app');
}, },
beforeQuitApp: (callback)=>{ setBaseURL: (baseUrl) => {
ipcRenderer.on('before-quit-app', () => { ipcRenderer.send('set-base-url', baseUrl);
callback();
});
}, },
registerAppMenu: (menuProps)=>{ registerAppMenu: (menuProps) => {
ipcRenderer.send('register-app-menu', menuProps); ipcRenderer.send('register-app-menu', menuProps);
} },
}); });

View File

@ -58,7 +58,7 @@ const WorkspaceModel: IWorkspaceModelType = {
state: { state: {
databaseAndSchema: undefined, databaseAndSchema: undefined,
curWorkspaceParams: getCurrentWorkspaceDatabase(), curWorkspaceParams: {} as any,
doubleClickTreeNodeData: undefined, doubleClickTreeNodeData: undefined,
consoleList: [], consoleList: [],
openConsoleList: [], openConsoleList: [],

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import indexedDB from '@/indexedDB';
export enum CustomerTypeEnum { export enum CustomerTypeEnum {
visitor = 'visitor', visitor = 'visitor',
@ -14,31 +13,10 @@ export enum CustomerTypeEnum2 {
export type CustomerType1 = CustomerTypeEnum | CustomerTypeEnum2; export type CustomerType1 = CustomerTypeEnum | CustomerTypeEnum2;
const App: React.FC = () => { const App: React.FC = () => {
const [db, setDb] = useState<any>();
useEffect(() => {
indexedDB.createDB('chat2db', 2).then((db) => {
setDb(db);
});
}, []);
const add = () => {
indexedDB.addData(db, 'workspaceConsoleDDL', {
userId: '1',
consoleId: '1',
ddl: 'select * from user',
});
};
const deleteFn = () => {
indexedDB.deleteData(db, 'workspaceConsoleDDL', '1');
};
return ( return (
<div> <div>demo</div>
<button onClick={add}>add</button>
<button onClick={deleteFn}>deleteFn</button>
</div>
); );
}; };

View File

@ -107,7 +107,7 @@ const SaveList = dvaModel((props: any) => {
id: data.id, id: data.id,
status: ConsoleStatus.DRAFT, status: ConsoleStatus.DRAFT,
}; };
historyServer.updateSavedConsole(params).then((res) => { historyServer.updateSavedConsole(params).then(() => {
dispatch({ dispatch({
type: 'workspace/fetchGetSavedConsole', type: 'workspace/fetchGetSavedConsole',
payload: { payload: {
@ -115,10 +115,10 @@ const SaveList = dvaModel((props: any) => {
status: ConsoleStatus.RELEASE, status: ConsoleStatus.RELEASE,
...curWorkspaceParams, ...curWorkspaceParams,
}, },
callback: (res: any) => { callback: (_res: any) => {
dispatch({ dispatch({
type: 'workspace/setConsoleList', type: 'workspace/setConsoleList',
payload: res.data, payload: _res.data,
}); });
}, },
}); });

View File

@ -119,3 +119,18 @@
} }
} }
} }
.dropdownFooter {
display: flex;
align-items: center;
margin: 4px;
padding: 4px 8px;
line-height: 20px;
i {
margin-right: 4px;
}
&:hover {
background-color: var(--color-hover-bg);
cursor: pointer;
}
}

View File

@ -6,12 +6,14 @@ import Iconfont from '@/components/Iconfont';
import { IConnectionModelType } from '@/models/connection'; import { IConnectionModelType } from '@/models/connection';
import { IWorkspaceModelType } from '@/models/workspace'; import { IWorkspaceModelType } from '@/models/workspace';
import { IMainPageType } from '@/models/mainPage'; import { IMainPageType } from '@/models/mainPage';
import { Cascader, Spin, Modal, Tag } from 'antd'; import { Cascader, Spin, Modal, Tag, Divider } from 'antd';
import { databaseMap, TreeNodeType } from '@/constants'; import { databaseMap, TreeNodeType, DatabaseTypeCode } from '@/constants';
import { treeConfig } from '../Tree/treeConfig'; import { treeConfig } from '../Tree/treeConfig';
import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { useUpdateEffect } from '@/hooks/useUpdateEffect';
import styles from './index.less'; import styles from './index.less';
import i18n from '@/i18n'; import i18n from '@/i18n';
import CreateDatabase, { ICreateDatabaseRef } from '@/components/CreateDatabase';
import { getCurrentWorkspaceDatabase } from '@/utils/localStorage';
interface IProps { interface IProps {
className?: string; className?: string;
@ -26,6 +28,14 @@ interface IOption {
value: number | string; value: number | string;
} }
// 不支持创建数据库的数据库类型
const notSupportCreateDatabaseType = [DatabaseTypeCode.H2];
// 不支持创建schema的数据库类型
const notSupportCreateSchemaType = [DatabaseTypeCode.ORACLE];
const localStorageWorkspaceDatabase = getCurrentWorkspaceDatabase();
const WorkspaceHeader = memo<IProps>((props) => { const WorkspaceHeader = memo<IProps>((props) => {
const { connectionModel, workspaceModel, mainPageModel, dispatch } = props; const { connectionModel, workspaceModel, mainPageModel, dispatch } = props;
const { connectionList, curConnection } = connectionModel; const { connectionList, curConnection } = connectionModel;
@ -37,6 +47,18 @@ const WorkspaceHeader = memo<IProps>((props) => {
const [curDBOptions, setCurDBOptions] = useState<IOption[]>([]); const [curDBOptions, setCurDBOptions] = useState<IOption[]>([]);
const [curSchemaOptions, setCurSchemaOptions] = useState<IOption[]>([]); const [curSchemaOptions, setCurSchemaOptions] = useState<IOption[]>([]);
const [isRefresh, setIsRefresh] = useState(false); const [isRefresh, setIsRefresh] = useState(false);
const [openDBCascaderDropdown, setOpenDBCascaderDropdown] = useState<false | undefined>(undefined);
const [openSchemaCascaderDropdown, setOpenSchemaCascaderDropdown] = useState<false | undefined>(undefined);
const createDatabaseRef = React.useRef<ICreateDatabaseRef>(null);
useEffect(() => {
if (openDBCascaderDropdown === false) {
setOpenDBCascaderDropdown(undefined);
}
if (openSchemaCascaderDropdown === false) {
setOpenSchemaCascaderDropdown(undefined);
}
}, [openDBCascaderDropdown, openSchemaCascaderDropdown]);
useEffect(() => { useEffect(() => {
if (curPage !== 'workspace') { if (curPage !== 'workspace') {
@ -44,7 +66,14 @@ const WorkspaceHeader = memo<IProps>((props) => {
} }
// 如果没有curConnection默认选第一个 // 如果没有curConnection默认选第一个
if (!curConnection?.id && connectionList.length) { if (!curConnection?.id && connectionList.length) {
connectionChange([connectionList[0].id], [connectionList[0]]); if (
localStorageWorkspaceDatabase.dataSourceId &&
connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId))
) {
connectionChange([localStorageWorkspaceDatabase.dataSourceId]);
return;
}
connectionChange([connectionList[0].id]);
return; return;
} }
// 如果都有的话 // 如果都有的话
@ -52,7 +81,12 @@ const WorkspaceHeader = memo<IProps>((props) => {
// 如果curConnection不再connectionList里也是默认选第一个 // 如果curConnection不再connectionList里也是默认选第一个
const flag = connectionList.findIndex((t: any) => t.id === curConnection?.id); const flag = connectionList.findIndex((t: any) => t.id === curConnection?.id);
if (flag === -1) { if (flag === -1) {
connectionChange([connectionList[0].id], [connectionList[0]]); if (localStorageWorkspaceDatabase.dataSourceId &&
connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId))) {
connectionChange([localStorageWorkspaceDatabase.dataSourceId]);
return;
}
connectionChange([connectionList[0].id]);
return; return;
} }
@ -71,10 +105,6 @@ const WorkspaceHeader = memo<IProps>((props) => {
getDatabaseList(isRefresh); getDatabaseList(isRefresh);
setIsRefresh(false); setIsRefresh(false);
} }
// connectionList转换成可用的ConnectionOptions
// if (!connectionList.length) {
// setNoConnectionModal(true);
// }
setConnectionOptions( setConnectionOptions(
connectionList?.map((t) => { connectionList?.map((t) => {
return { return {
@ -118,7 +148,7 @@ const WorkspaceHeader = memo<IProps>((props) => {
dataSourceName: curConnection.name, dataSourceName: curConnection.name,
}, },
}) })
.then((res) => { .then((res: any) => {
const dbList = const dbList =
res?.map((t) => { res?.map((t) => {
return { return {
@ -127,14 +157,20 @@ const WorkspaceHeader = memo<IProps>((props) => {
}; };
}) || []; }) || [];
setCurDBOptions(dbList); setCurDBOptions(dbList);
// 如果是切换那么就默认取列表的第一个database 如果不是切换那么就取缓存的如果缓存没有还是取列表第一个这里是兜底如果原先他并没有database后来他加了database如果还是取缓存的空就不对了 let databaseName = '';
const databaseName = if(dbList.find((t: any) => t.value === localStorageWorkspaceDatabase.databaseName)){
curWorkspaceParams.dataSourceId !== curConnection?.id databaseName = localStorageWorkspaceDatabase.databaseName!;
? dbList[0]?.label }else{
: curWorkspaceParams.databaseName || dbList[0]?.label; // 如果是切换那么就默认取列表的第一个database 如果不是切换那么就取缓存的如果缓存没有还是取列表第一个这里是兜底如果原先他并没有database后来他加了database如果还是取缓存的空就不对了
getSchemaList(databaseName, refresh); databaseName =
curWorkspaceParams.dataSourceId !== curConnection?.id
? dbList[0]?.label
: curWorkspaceParams.databaseName || dbList[0]?.label;
}
databaseChange([databaseName], [{ label: databaseName }],refresh);
// getSchemaList(databaseName, refresh);
}) })
.catch((error) => { .catch(() => {
setCascaderLoading(false); setCascaderLoading(false);
}); });
} }
@ -155,7 +191,7 @@ const WorkspaceHeader = memo<IProps>((props) => {
dataSourceName: curConnection.name, dataSourceName: curConnection.name,
}, },
}) })
.then((res) => { .then((res: any) => {
const schemaList = const schemaList =
res?.map((t) => { res?.map((t) => {
return { return {
@ -164,10 +200,17 @@ const WorkspaceHeader = memo<IProps>((props) => {
}; };
}) || []; }) || [];
setCurSchemaOptions(schemaList); setCurSchemaOptions(schemaList);
const schemaName =
curWorkspaceParams.dataSourceId !== curConnection?.id let schemaName = '';
? schemaList[0]?.label if(schemaList.find((t: any) => t.value === localStorageWorkspaceDatabase.schemaName)){
: curWorkspaceParams.schemaName || schemaList[0]?.label; schemaName = localStorageWorkspaceDatabase.schemaName!;
}else{
schemaName =
curWorkspaceParams.dataSourceId !== curConnection?.id
? schemaList[0]?.label
: curWorkspaceParams.schemaName || schemaList[0]?.label;
}
// schemaChange([schemaName], [{ label: schemaName }]);
const data: any = { const data: any = {
dataSourceId: curConnection.id, dataSourceId: curConnection.id,
dataSourceName: curConnection.alias, dataSourceName: curConnection.alias,
@ -226,17 +269,17 @@ const WorkspaceHeader = memo<IProps>((props) => {
} }
// 数据库切换 // 数据库切换
function databaseChange(valueArr: any, selectedOptions: any) { function databaseChange(valueArr: any, selectedOptions: any,refresh) {
if (selectedOptions[0].label !== curWorkspaceParams.databaseName) { // if (selectedOptions[0].label !== curWorkspaceParams.databaseName) {
getSchemaList(selectedOptions[0].label); getSchemaList(selectedOptions[0].label,refresh);
} // }
} }
// schema切换 // schema切换
function schemaChange(valueArr: any, selectedOptions: any) { function schemaChange(valueArr: any, selectedOptions: any) {
if (selectedOptions[0].label !== curWorkspaceParams.schemaName) { // if (selectedOptions[0].label !== curWorkspaceParams.schemaName) {
setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value }); setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value });
} // }
} }
function handleRefresh() { function handleRefresh() {
@ -265,11 +308,34 @@ const WorkspaceHeader = memo<IProps>((props) => {
</Cascader> </Cascader>
{!!curDBOptions?.length && <Iconfont className={styles.arrow} code="&#xe641;" />} {!!curDBOptions?.length && <Iconfont className={styles.arrow} code="&#xe641;" />}
{!!curDBOptions?.length && ( {!!curDBOptions?.length && (
<Cascader <Cascader
popupClassName={styles.cascaderPopup} popupClassName={styles.cascaderPopup}
options={curDBOptions} options={curDBOptions}
open={openDBCascaderDropdown}
dropdownRender={(menu) => {
return (
<div>
{menu}
<Divider style={{ margin: 0 }} />
{
// 不支持创建数据库的数据库类型
!notSupportCreateDatabaseType.includes(curWorkspaceParams?.databaseType) && (
<div
className={styles.dropdownFooter}
onClick={() => {
setOpenDBCascaderDropdown(false);
createDatabaseRef.current?.setOpen(true, 'database');
}}
>
<Iconfont code="&#xe631;" />
{i18n('common.Button.addDatabase')}
</div>
)
}
</div>
);
}}
onChange={databaseChange} onChange={databaseChange}
bordered={false} bordered={false}
value={[curWorkspaceParams?.databaseName || '']} value={[curWorkspaceParams?.databaseName || '']}
@ -286,7 +352,31 @@ const WorkspaceHeader = memo<IProps>((props) => {
options={curSchemaOptions} options={curSchemaOptions}
onChange={schemaChange} onChange={schemaChange}
bordered={false} bordered={false}
open={openSchemaCascaderDropdown}
value={[curWorkspaceParams?.schemaName || '']} value={[curWorkspaceParams?.schemaName || '']}
dropdownRender={(menu) => {
return (
<div>
{menu}
<Divider style={{ margin: 0 }} />
{
// 不支持创建schema的数据库类型
!notSupportCreateSchemaType.includes(curWorkspaceParams?.databaseType) && (
<div
className={styles.dropdownFooter}
onClick={() => {
setOpenSchemaCascaderDropdown(false);
createDatabaseRef.current?.setOpen(true, 'schema');
}}
>
<Iconfont code="&#xe631;" />
{i18n('common.Button.addSchema')}
</div>
)
}
</div>
);
}}
> >
<div className={styles.crumbsItem}> <div className={styles.crumbsItem}>
<div className={styles.text}>{curWorkspaceParams.schemaName}</div> <div className={styles.text}>{curWorkspaceParams.schemaName}</div>
@ -330,6 +420,11 @@ const WorkspaceHeader = memo<IProps>((props) => {
</div> </div>
</div> </div>
</Modal> </Modal>
<CreateDatabase
executedCallback={handleRefresh}
curWorkspaceParams={curWorkspaceParams}
ref={createDatabaseRef}
/>
</> </>
); );
}); });

View File

@ -64,7 +64,6 @@ const WorkspaceRight = memo<IProps>((props: IProps) => {
uniqueData: t, uniqueData: t,
}; };
}); });
console.log(workspaceTabList, newTabList);
if (workspaceTabList.length) { if (workspaceTabList.length) {
const newWorkspaceTabList = lodash.cloneDeep(workspaceTabList); const newWorkspaceTabList = lodash.cloneDeep(workspaceTabList);
const newAddList: any = []; const newAddList: any = [];
@ -312,11 +311,23 @@ const WorkspaceRight = memo<IProps>((props: IProps) => {
} }
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) { if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) {
const { extraParams } = doubleClickTreeNodeData; const { extraParams } = doubleClickTreeNodeData;
const { tableName } = extraParams || {}; const { tableName } = extraParams || {};
const sql = `SELECT * FROM ${compatibleDataBaseName(tableName!, curWorkspaceParams.databaseType)};\n`; const sql = `SELECT * FROM ${compatibleDataBaseName(tableName!, curWorkspaceParams.databaseType)};\n`;
const title = tableName!; const title = tableName!;
const id = uuidV4(); const id = uuidV4();
let flag = false;
workspaceTabList.forEach((t) => {
if (t.uniqueData?.sql === sql) {
setActiveConsoleId(t.id);
flag = true
return;
}
})
if(flag){
return
}
setWorkspaceTabList([ setWorkspaceTabList([
...(workspaceTabList || []), ...(workspaceTabList || []),
{ {
@ -355,6 +366,9 @@ const WorkspaceRight = memo<IProps>((props: IProps) => {
// 更新表名提示 // 更新表名提示
useUpdateEffect(() => { useUpdateEffect(() => {
const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams;
if (dataSourceId === null || dataSourceId === undefined) {
return;
}
sqlService sqlService
.getAllTableList({ .getAllTableList({
dataSourceId, dataSourceId,
@ -571,6 +585,7 @@ const WorkspaceRight = memo<IProps>((props: IProps) => {
schemaName: curWorkspaceParams?.schemaName, schemaName: curWorkspaceParams?.schemaName,
consoleId: t.id as number, consoleId: t.id as number,
consoleName: uniqueData.name, consoleName: uniqueData.name,
status: uniqueData.status,
}} }}
/> />
)} )}

View File

@ -78,11 +78,6 @@ const setAppUpdateType = createRequest<ILatestVersion['type'], boolean>('/api/sy
method: 'post', method: 'post',
}); });
// 退出electron时关闭后端服务
const stopJavaService = createRequest<void, void>('/api/system/stop', {
method: 'post',
});
export default { export default {
getSystemConfig, getSystemConfig,
setSystemConfig, setSystemConfig,
@ -92,6 +87,5 @@ export default {
getLatestVersion, getLatestVersion,
isUpdateSuccess, isUpdateSuccess,
updateDesktopVersion, updateDesktopVersion,
setAppUpdateType, setAppUpdateType
stopJavaService
}; };

View File

@ -1,10 +1,4 @@
import createRequest from './base'; import createRequest from './base';
import { IVersionResponse } from '@/typings';
const checkVersion = createRequest<void, IVersionResponse>('/api/client/version/check/v2', {
errorLevel: false,
outside: true,
});
const dynamicUrl = createRequest<string, void>('', { const dynamicUrl = createRequest<string, void>('', {
dynamicUrl: true, dynamicUrl: true,
@ -12,5 +6,4 @@ const dynamicUrl = createRequest<string, void>('', {
export default { export default {
dynamicUrl, dynamicUrl,
checkVersion,
}; };

View File

@ -288,7 +288,22 @@ const executeUpdateDataSql = createRequest<IExecuteSqlParams, { success: boolean
/** 获取修改表数据的接口 */ /** 获取修改表数据的接口 */
const getExecuteUpdateSql = createRequest<any, string>('/api/rdb/dml/get_update_sql', { method: 'post' }); const getExecuteUpdateSql = createRequest<any, string>('/api/rdb/dml/get_update_sql', { method: 'post' });
/** 创建数据库 */
const getCreateDatabaseSql = createRequest<{
dataSourceId: number;
databaseName: string;
}, { sql: string }>('/api/rdb/database/create_database_sql', { method: 'post' });
/** 创建schema */
const getCreateSchemaSql = createRequest<{
dataSourceId: number;
databaseName?: string;
schemaName?: string;
}, {sql:string}>('/api/rdb/schema/create_schema_sql', { method: 'post' });
export default { export default {
getCreateSchemaSql,
getCreateDatabaseSql,
executeUpdateDataSql, executeUpdateDataSql,
executeDDL, executeDDL,
getExecuteUpdateSql, getExecuteUpdateSql,

View File

@ -51,6 +51,11 @@
} }
} }
// 文档英文强制换行
.f-doc-en-break {
word-break: break-all;
}
@keyframes loading-animation { @keyframes loading-animation {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);

View File

@ -244,9 +244,9 @@ export function formatSql(sql: string, dbType: DatabaseTypeCode) {
// 桌面端用hash模式web端用history模式路由跳转 // 桌面端用hash模式web端用history模式路由跳转
export function navigate(path: string) { export function navigate(path: string) {
if (__ENV__ === 'desktop') { if (__ENV__ === 'desktop') {
window.location.href = `#${path}`; window.location.replace(`#${path}`)
} else { } else {
window.location.href = path; window.location.replace(path)
} }
} }

View File

@ -1,13 +1,32 @@
package ai.chat2db.plugin.h2; package ai.chat2db.plugin.h2;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException;
import ai.chat2db.spi.DBManage; import ai.chat2db.spi.DBManage;
import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.jdbc.DefaultDBManage;
import ai.chat2db.spi.sql.Chat2DBContext;
import ai.chat2db.spi.sql.ConnectInfo;
import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.sql.SQLExecutor;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
public class H2DBManage extends DefaultDBManage implements DBManage { public class H2DBManage extends DefaultDBManage implements DBManage {
@Override
public void connectDatabase(Connection connection, String database) {
ConnectInfo connectInfo = Chat2DBContext.getConnectInfo();
if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) {
return;
}
String schemaName = connectInfo.getSchemaName();
try {
SQLExecutor.getInstance().execute(connection, "SET SCHEMA \"" + schemaName + "\"");
} catch (SQLException e) {
}
}
@Override @Override
public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) {

View File

@ -129,7 +129,7 @@ public class MysqlSqlBuilder extends DefaultSqlBuilder implements SqlBuilder {
@Override @Override
public String buildCreateDatabaseSql(Database database) { public String buildCreateDatabaseSql(Database database) {
StringBuilder sqlBuilder = new StringBuilder(); StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("CREATE DATABASE "+database.getName()); sqlBuilder.append("CREATE DATABASE `"+database.getName()+"`");
if (StringUtils.isNotBlank(database.getCharset())) { if (StringUtils.isNotBlank(database.getCharset())) {
sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset()); sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset());
} }

View File

@ -4,17 +4,22 @@ import java.sql.Connection;
import ai.chat2db.spi.DBManage; import ai.chat2db.spi.DBManage;
import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.jdbc.DefaultDBManage;
import ai.chat2db.spi.sql.Chat2DBContext;
import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.ConnectInfo;
import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.sql.SQLExecutor;
import org.apache.commons.lang3.StringUtils;
public class PostgreSQLDBManage extends DefaultDBManage implements DBManage { public class PostgreSQLDBManage extends DefaultDBManage implements DBManage {
@Override @Override
public void connectDatabase(Connection connection, String database) { public void connectDatabase(Connection connection, String database) {
//try { try {
// SQLExecutor.getInstance().execute(connection,"SELECT pg_database_size('"+database+"');"); ConnectInfo connectInfo = Chat2DBContext.getConnectInfo();
//} catch (SQLException e) { if (!StringUtils.isEmpty(connectInfo.getSchemaName())) {
// throw new RuntimeException(e); SQLExecutor.getInstance().execute(connection, "SET search_path TO \"" + connectInfo.getSchemaName() + "\"");
//} }
} catch (Exception e) {
}
} }
@Override @Override
@ -26,7 +31,7 @@ public class PostgreSQLDBManage extends DefaultDBManage implements DBManage {
} }
connectInfo.setUrl(url); connectInfo.setUrl(url);
return super.getConnection(connectInfo); return super.getConnection(connectInfo);
} }
@ -53,8 +58,8 @@ public class PostgreSQLDBManage extends DefaultDBManage implements DBManage {
@Override @Override
public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) {
String sql = "DROP TABLE "+ tableName; String sql = "DROP TABLE " + tableName;
SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> null);
} }
} }

View File

@ -11,7 +11,7 @@ public class SqlServerDBManage extends DefaultDBManage implements DBManage {
@Override @Override
public void connectDatabase(Connection connection, String database) { public void connectDatabase(Connection connection, String database) {
try { try {
SQLExecutor.getInstance().execute(connection,"use [" + database + "];"); SQLExecutor.getInstance().execute(connection, "use [" + database + "];");
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -153,7 +153,7 @@ public class SqlServerSqlBuilder extends DefaultSqlBuilder implements SqlBuilder
sqlBuilder.append("\ngo\n"); sqlBuilder.append("\ngo\n");
if (StringUtils.isNotBlank(database.getComment())) { if (StringUtils.isNotBlank(database.getComment())) {
sqlBuilder.append("exec [" + database.getName() + "].sys. sp_addextendedproperty 'MS_Description','") sqlBuilder.append("exec [" + database.getName() + "].sys. sp_addextendedproperty 'MS_Description','")
.append(database.getComment()).append("'").append("'\ngo\n"); .append(database.getComment()).append("'").append("\ngo\n");
} }
return sqlBuilder.toString(); return sqlBuilder.toString();
} }
@ -166,7 +166,7 @@ public class SqlServerSqlBuilder extends DefaultSqlBuilder implements SqlBuilder
if (StringUtils.isNotBlank(schema.getComment())) { if (StringUtils.isNotBlank(schema.getComment())) {
sqlBuilder.append("exec sp_addextendedproperty 'MS_Description','") sqlBuilder.append("exec sp_addextendedproperty 'MS_Description','")
.append(schema.getComment()).append("'").append(",'SCHEMA'") .append(schema.getComment()).append("'").append(",'SCHEMA'")
.append(",'").append(schema.getName()).append("'").append("'\ngo\n"); .append(",'").append(schema.getName()).append("'").append("\ngo\n");
} }
return sqlBuilder.toString(); return sqlBuilder.toString();
} }

View File

@ -56,7 +56,7 @@ public class Chat2dbWebMvcConfigurer implements WebMvcConfigurer {
* 全局放行的url * 全局放行的url
*/ */
private static final String[] FRONT_PERMIT_ALL = new String[] {"/favicon.ico", "/error", "/static/**", private static final String[] FRONT_PERMIT_ALL = new String[] {"/favicon.ico", "/error", "/static/**",
"/api/system", "/login"}; "/api/system", "/login", "/api/system/get_latest_version"};
@Resource @Resource
private UserService userService; private UserService userService;

View File

@ -93,7 +93,7 @@ public class RdbDmlController {
try { try {
boolean flag = true; boolean flag = true;
ExecuteResultVO executeResult = null; ExecuteResultVO executeResult = null;
connection.setAutoCommit(false); //connection.setAutoCommit(false);
ListResult<ExecuteResult> resultDTOListResult = dlTemplateService.execute(param); ListResult<ExecuteResult> resultDTOListResult = dlTemplateService.execute(param);
List<ExecuteResultVO> resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); List<ExecuteResultVO> resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData());
if (!CollectionUtils.isEmpty(resultVOS)) { if (!CollectionUtils.isEmpty(resultVOS)) {
@ -107,7 +107,7 @@ public class RdbDmlController {
} }
} }
if (flag) { if (flag) {
connection.commit(); //connection.commit();
return DataResult.of(resultVOS.get(0)); return DataResult.of(resultVOS.get(0));
}else { }else {
connection.rollback(); connection.rollback();

View File

@ -108,7 +108,7 @@ public class DatabaseExportService {
try { try {
export(outputStream, exportOptions); export(outputStream, exportOptions);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("导出失败!请联系开发者邮箱963565242@qq.com" + e); throw new RuntimeException("导出失败!请联系开发者" + e);
} }
init(); init();
} }