feat: edit table

This commit is contained in:
shanhexi
2023-09-17 23:04:21 +08:00
71 changed files with 3089 additions and 1310 deletions

View File

@ -23,6 +23,7 @@
"echart",
"echarts",
"favicons",
"fulltext",
"ghostoy",
"iconfont",
"jdbc",

View File

@ -7,6 +7,8 @@
function using 'docker'
- Added support for environment selection, better distinguishing between online and daily
# 2.0.14
## 🐞 Bug Fixes
- Fix the issue of 'Oracle' query 'Blob' reporting errors
@ -24,15 +26,22 @@
# 2.0.13
- 修改分页逻辑,修复部分 SQL 无法查询
# 2.0.13
## ⭐ New Features
## 🐞 Bug Fixes
- Fixed a bug where sql formatting was not selected
- Fixed open view lag issue
- Solve the white screen problem of connected non-relational databases (non-relational databases are not supported)
## ⭐ 新特性
## 🐞 问题修复
- 修复不选中sql格式化的bug
- 修复不选中 sql 格式化的 bug
- 修复打开视图卡顿问题
- 解决已连接的非关系型数据库打开白屏问题(暂不支持非关系性数据库)
@ -50,7 +59,7 @@
- Fixed an issue where locally stored theme colors and background colors are incompatible with the new version, causing
page crashes
- Logs desensitize sensitive data
- Fix the issue of 'CLOB' not displaying specific content [Issue #440](https://github.com/chat2db/Chat2DB/issues/440)
- Fix the issue of 'CLOB' not displaying specific content [Issue #440](https://github.com/chat2db/Chat2DB/issues/440)
- Fix the problem that non-Select does not display query results
- Fix the problem that Oracle cannot query without schema
- Fix the problem of special type of SQL execution error reporting
@ -58,19 +67,19 @@
## ⭐ 新特性
- 🔥支持查看视图、函数、触发器、存储过程
- 支持选中sql格式化
- 🔥 支持查看视图、函数、触发器、存储过程
- 支持选中 sql 格式化
- 增加新的暗色主题
## 🐞 问题修复
- 修复sql格式化会失败问题
- 修复 sql 格式化会失败问题
- 修复本地存储的主题色、背景色与新版本不兼容时会导致页面崩溃问题
- 日志对敏感数据进行脱敏
- 修复 `CLOB` 不展示具体内容的问题 [Issue #440](https://github.com/chat2db/Chat2DB/issues/440)
- 修复非Select不展示查询结果的问题
- 修复Oracle不带schema无法查询的问题
- 修复特殊类型的SQL执行报错的问题
- 修复 `CLOB` 不展示具体内容的问题 [Issue #440](https://github.com/chat2db/Chat2DB/issues/440)
- 修复非 Select 不展示查询结果的问题
- 修复 Oracle 不带 schema 无法查询的问题
- 修复特殊类型的 SQL 执行报错的问题
- 修复测试链接成功,但保存链接报错的问题
# 2.0.11
@ -98,11 +107,11 @@
## 🐞 问题修复
- 新建、开打console时激活最新操作的console、记录最后一次使用的console
- edge等浏览器复制功能无法正常使用
- table搜索后导出ddl报错
- 新建、开打 console 时激活最新操作的 console、记录最后一次使用的 console
- edge 等浏览器复制功能无法正常使用
- table 搜索后导出 ddl 报错
- 增加表注释以及列字段类型和注释
- 当数据源添加了database默认选择第一个database
- 当数据源添加了 database 默认选择第一个 database
# 2.0.9
@ -112,18 +121,18 @@
## 🐞 问题修复
- 修复windows闪退的问题
- 修复 windows 闪退的问题
# 2.0.8
## 🐞 Bug Fixes
- Repair the Scientific notation in some databases [Issue #378](https://github.com/chat2db/Chat2DB/issues/378)
- Repair the Scientific notation in some databases [Issue #378](https://github.com/chat2db/Chat2DB/issues/378)
- Fix some cases where data is not displayed
## 🐞 问题修复
- 修复部分数据库出现科学计数法的情况 [Issue #378](https://github.com/chat2db/Chat2DB/issues/378)
- 修复部分数据库出现科学计数法的情况 [Issue #378](https://github.com/chat2db/Chat2DB/issues/378)
- 修复部分情况数据不展示
# 2.0.7
@ -142,18 +151,18 @@
## 🐞 问题修复
- 修复ai配置 [Issue #346](https://github.com/chat2db/Chat2DB/issues/346)
- 修复 ai 配置 [Issue #346](https://github.com/chat2db/Chat2DB/issues/346)
# 2.0.6
## 🐞 Bug Fixes
- Fixed: When there are too many tables under the selected library, the "New Console" button at the bottom
disappears [Issue #314](https://github.com/chat2db/Chat2DB/issues/314)
disappears [Issue #314](https://github.com/chat2db/Chat2DB/issues/314)
## 🐞 问题修复
- Fixed: 当选择的库下面表过多时最下面的“新建控制台”按钮消失 [Issue #314](https://github.com/chat2db/Chat2DB/issues/314)
- Fixed: 当选择的库下面表过多时最下面的“新建控制台”按钮消失 [Issue #314](https://github.com/chat2db/Chat2DB/issues/314)
# 2.0.5

View File

@ -37,7 +37,9 @@
"echarts",
"favicons",
"findstr",
"fulltext",
"gtag",
"hexi",
"Iconfont",
"indexs",
"JDBC",

View File

@ -25,6 +25,7 @@
"start:web": "cross-env UMI_ENV=local cross-env APP_VERSION=${npm_config_app_version} umi dev"
},
"dependencies": {
"@dnd-kit/modifiers": "^6.0.1",
"ahooks": "^3.7.7",
"ali-react-table": "^2.6.1",
"antd": "^5.6.0",
@ -55,7 +56,6 @@
"@umijs/plugins": "^4.0.55",
"concurrently": "^8.1.0",
"cross-env": "^7.0.3",
"electron": "^22.3.0",
"electron-builder": "^23.6.0",
"electron-debug": "^3.2.0",
"electron-reload": "^2.0.0-alpha.1",

View File

@ -0,0 +1,9 @@
@import '../../../styles/var.less';
.box {
padding: 10px;
}
.formBox {
width: 50%;
}

View File

@ -0,0 +1,54 @@
import React, { memo, useState, useContext, useEffect, useImperativeHandle, ForwardedRef, forwardRef } from 'react';
import styles from './index.less';
import classnames from 'classnames';
import { Form, Input } from 'antd';
import { Context } from '../index';
import { IBaseInfo } from '@/typings';
import i18n from '@/i18n';
export interface IBaseInfoRef {
getBaseInfo: () => IBaseInfo;
}
interface IProps {
className?: string;
}
const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef<IBaseInfoRef>) => {
const { className } = props;
const { tableDetails } = useContext(Context);
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue({
name: tableDetails.name,
comment: tableDetails.comment,
});
}, [tableDetails]);
function getBaseInfo(): IBaseInfo {
return form.getFieldsValue();
}
useImperativeHandle(ref, () => ({
getBaseInfo,
}));
return (
<div className={classnames(className, styles.box)}>
<div className={styles.formBox}>
<Form form={form} initialValues={{ remember: true }} autoComplete="off" className={styles.form}>
<Form.Item label={i18n('editTable.label.tableName')} name="name">
<Input />
</Form.Item>
<Form.Item label={i18n('editTable.label.comment')} name="comment">
<Input />
</Form.Item>
</Form>
</div>
</div>
);
});
export default BaseInfo

View File

@ -0,0 +1,36 @@
@import '../../../styles/var.less';
.box {
height: 100%;
padding: 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.columnListHeader {
margin: 0px -5px 10px;
button {
margin: 0px 5px;
}
}
.tableBox {
flex: 1;
overflow: auto;
}
.editableCell {
height: 28px;
line-height: 26px;
padding: 0px 7px;
box-sizing: border-box;
border: 1px solid transparent;
border-radius: 4px;
.f-single-line();
width: 100%;
cursor: pointer;
&:hover {
border: 1px solid var(--color-border);
}
}

View File

@ -0,0 +1,361 @@
import React, { memo, useContext, useEffect, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react';
import styles from './index.less';
import classnames from 'classnames';
import { MenuOutlined } from '@ant-design/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext } from '@dnd-kit/core';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { Table, InputNumber, Input, Form, Select, Checkbox, Button } from 'antd';
import { v4 as uuidv4 } from 'uuid';
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import sqlService from '@/service/sql';
import { Context } from '../index';
import { IColumnItem } from '@/typings'
import i18n from '@/i18n';
interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
'data-row-key': string;
}
interface IProps {
}
export interface IColumnListRef {
getColumnListInfo: () => IColumnItem[];
}
const Row = ({ children, ...props }: RowProps) => {
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id: props['data-row-key'],
});
const style: React.CSSProperties = {
...props.style,
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
return (
<tr {...props} ref={setNodeRef} style={style} {...attributes}>
{React.Children.map(children, (child) => {
if ((child as React.ReactElement).key === 'sort') {
return React.cloneElement(child as React.ReactElement, {
children: (
<MenuOutlined
ref={setActivatorNodeRef}
style={{ touchAction: 'none', cursor: 'move' }}
{...listeners}
/>
),
});
}
return child;
})}
</tr>
);
};
const createInitialData = () => {
return {
key: uuidv4(),
name: null,
columnSize: null,
columnType: null,
nullable: null,
comment: null,
primaryKey: null,
defaultValue: null,
dataType: null,
autoIncrement: null,
numericPrecision: null,
numericScale: null,
characterMaximumLength: null,
};
}
const ColumnList = forwardRef((props: IProps, ref: ForwardedRef<IColumnListRef>) => {
const { dataSourceId, databaseName, tableDetails } = useContext(Context);
const [dataSource, setDataSource] = useState<IColumnItem[]>([createInitialData()]);
const [form] = Form.useForm();
const [editingKey, setEditingKey] = useState('');
const [databaseFieldTypeList, setDatabaseFieldTypeList] = useState<string[]>([])
const isEditing = (record: IColumnItem) => record.key === editingKey;
const edit = (record: Partial<IColumnItem> & { key: React.Key }) => {
form.setFieldsValue({ ...record });
setEditingKey(record.key);
};
useEffect(() => {
if (tableDetails) {
const list = tableDetails?.columnList?.map(t => {
return {
...t,
key: uuidv4(),
}
}) || []
setDataSource(list)
}
}, [tableDetails])
useEffect(() => {
// 获取数据库字段类型列表
sqlService.getDatabaseFieldTypeList({
dataSourceId,
databaseName,
}).then(res => {
setDatabaseFieldTypeList(res.map(i => i.typeName))
})
}, [])
const columns = [
{
key: 'sort',
width: '40px',
align: 'center'
},
{
title: i18n('editTable.label.columnName'),
dataIndex: 'name',
width: '160px',
render: (text: string, record: IColumnItem) => {
const editable = isEditing(record);
return editable ? (
<Form.Item
name="name"
style={{ margin: 0 }}
>
<Input />
</Form.Item>
) : (
<div
className={styles.editableCell}
onClick={() => edit(record)}
>
{text}
</div>
);
}
},
{
title: i18n('editTable.label.columnSize'),
dataIndex: 'columnSize',
width: '120px',
render: (text: string, record: IColumnItem) => {
const editable = isEditing(record);
return editable ? (
<Form.Item
name="columnSize"
style={{ margin: 0 }}
>
<InputNumber />
</Form.Item>
) : (
<div
className={styles.editableCell}
onClick={() => edit(record)}
>
{text}
</div>
);
}
},
{
title: i18n('editTable.label.columnType'),
dataIndex: 'columnType',
width: '200px',
render: (text: string, record: IColumnItem) => {
const editable = isEditing(record);
return <div>
{
editable ? (
<Form.Item
name="columnType"
style={{ margin: 0, maxWidth: '184px' }}
>
<Select
options={databaseFieldTypeList.map((i) => ({ label: i, value: i }))}
/>
</Form.Item>
) : (
<div
style={{ maxWidth: '184px' }}
className={styles.editableCell}
onClick={() => edit(record)}
>
{text}
</div>
)
}
</div>
}
},
{
title: i18n('editTable.label.nullable'),
dataIndex: 'nullable',
width: '100px',
render: (nullable: number, record: IColumnItem) => {
const editable = isEditing(record);
return editable ? (
<Form.Item
name='nullable'
style={{ margin: 0 }}
valuePropName='checked'
>
<Checkbox checked={nullable === 1} />
</Form.Item>
) : (
<div
onClick={() => edit(record)}
>
<Checkbox checked={nullable === 1} />
</div>
);
}
},
{
title: i18n('editTable.label.comment'),
dataIndex: 'comment',
render: (text: string, record: IColumnItem) => {
const editable = isEditing(record);
return editable ? (
<Form.Item
name="comment"
style={{ margin: 0 }}
>
<Input />
</Form.Item>
) : (
<div
className={styles.editableCell}
onClick={() => edit(record)}
>
{text}
</div>
);
}
},
];
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) {
setDataSource((previous) => {
const activeIndex = previous.findIndex((i) => i.key === active.id);
const overIndex = previous.findIndex((i) => i.key === over?.id);
return arrayMove(previous, activeIndex, overIndex);
});
}
};
const handelFieldsChange = (field: any) => {
let { name: nameList, value } = field[0];
const name = nameList[0];
if (name === 'nullable') {
value = value ? 1 : 0
}
const newData = dataSource.map((item) => {
if (item.key === editingKey) {
return {
...item,
[name]: value,
};
}
return item;
});
setDataSource(newData);
}
const addData = () => {
const newData = createInitialData()
setDataSource([...dataSource, newData])
edit(newData)
}
const deleteData = () => {
setDataSource(dataSource.filter(i => i.key !== editingKey))
}
const moveData = (action: 'up' | 'down') => {
const index = dataSource.findIndex(i => i.key === editingKey)
if (index === -1) {
return
}
if (action === 'up') {
if (index === 0) {
return
}
const newData = [...dataSource]
newData[index] = dataSource[index - 1]
newData[index - 1] = dataSource[index]
setDataSource(newData)
} else {
if (index === dataSource.length - 1) {
return
}
const newData = [...dataSource]
newData[index] = dataSource[index + 1]
newData[index + 1] = dataSource[index]
setDataSource(newData)
}
}
function getColumnListInfo(): IColumnItem[] {
return dataSource
}
useImperativeHandle(ref, () => ({
getColumnListInfo,
}));
return (
<div className={styles.box}>
<div className={styles.columnListHeader}>
<Button onClick={addData}>{i18n('editTable.button.add')}</Button>
<Button onClick={deleteData}>{i18n('editTable.button.delete')}</Button>
<Button onClick={moveData.bind(null, 'up')}>{i18n('editTable.button.up')}</Button>
<Button onClick={moveData.bind(null, 'down')}>{i18n('editTable.button.down')}</Button>
</div>
<div className={styles.tableBox}>
<Form form={form} onFieldsChange={handelFieldsChange}>
<DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
<SortableContext
items={dataSource.map((i) => i.key)}
strategy={verticalListSortingStrategy}
>
<Table
components={{
body: {
row: Row,
},
}}
pagination={false}
rowKey="key"
columns={columns as any}
dataSource={dataSource}
/>
</SortableContext>
</DndContext>
</Form>
</div>
</div>
);
})
export default ColumnList;

View File

@ -0,0 +1,33 @@
@import '../../../styles/var.less';
.box {
max-height: 60vh;
overflow-y: auto;
overflow-x: hidden;
}
.ant-input-number {
width: 100%;
}
.indexListHeader {
margin: 0px -10px 10px;
button {
margin: 0px 10px;
}
}
.editableCell {
height: 28px;
line-height: 26px;
padding: 0px 7px;
box-sizing: border-box;
border: 1px solid transparent;
border-radius: 4px;
.f-single-line();
width: 100%;
cursor: pointer;
&:hover {
border: 1px solid var(--color-border);
}
}

View File

@ -0,0 +1,179 @@
import React, { memo, useMemo, useState, useContext, useEffect, forwardRef, ForwardedRef, useImperativeHandle } from 'react';
import styles from './index.less';
import classnames from 'classnames';
import { Table, InputNumber, Input, Form, Select, Checkbox, Button, Modal, message } from 'antd';
import { v4 as uuidv4 } from 'uuid';
import { Context } from '../index';
import { IColumnItem, IIndexIncludeColumnItem } from '@/typings';
import i18n from '@/i18n';
interface IProps {
includedColumnList: IIndexIncludeColumnItem[];
}
const createInitialData = () => {
return {
key: uuidv4(),
indexName: null,
tableName: null,
type: null,
columnName: null,
comment: null,
ordinalPosition: null,
collation: null,
schemaName: null,
databaseName: '',
nonUnique: true,
indexQualifier: null,
ascOrDesc: null,
cardinality: null,
pages: null,
filterCondition: null,
prefixLength: null
};
};
export interface IIncludeColRef {
getIncludeColInfo: () => IIndexIncludeColumnItem[];
}
const InitialDataSource = [createInitialData()];
const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef<IIncludeColRef>) => {
const { includedColumnList } = props;
const { columnListRef } = useContext(Context);
const [dataSource, setDataSource] = useState<IIndexIncludeColumnItem[]>(InitialDataSource);
const [form] = Form.useForm();
const [editingKey, setEditingKey] = useState(dataSource[0]?.key);
const isEditing = (record: IIndexIncludeColumnItem) => record.key === editingKey;
useEffect(() => {
if (includedColumnList.length) {
setDataSource(
includedColumnList.map(t => {
return {
...t,
key: uuidv4(),
};
}),
);
}
}, [includedColumnList]);
const columnList: IColumnItem[] = useMemo(() => {
const columnListInfo = columnListRef.current?.getColumnListInfo()?.filter(i => i.name);
return columnListInfo || [];
}, []);
const edit = (record: Partial<IIndexIncludeColumnItem> & { key: React.Key }) => {
form.setFieldsValue({ ...record });
setEditingKey(record.key);
};
const addData = () => {
const newData = createInitialData();
setDataSource([...dataSource, newData]);
edit(newData);
};
const deleteData = () => {
// if (dataSource.length === 1) {
// message.warning('至少保留一条数据')
// return
// }
setDataSource(dataSource.filter((i) => i.key !== editingKey));
};
const columns = [
{
title: i18n('editTable.label.index'),
dataIndex: 'index',
width: '10%',
render: (text: string, record: IIndexIncludeColumnItem) => {
return dataSource.findIndex((i) => i.key === record.key) + 1;
},
},
{
title: i18n('editTable.label.columnName'),
dataIndex: 'columnName',
width: '45%',
render: (text: string, record: IIndexIncludeColumnItem) => {
const editable = isEditing(record);
return editable ? (
<Form.Item name="columnName" style={{ margin: 0 }}>
<Select options={columnList.map((i) => ({ label: i.name, value: i.name }))} />
</Form.Item>
) : (
<div className={styles.editableCell} onClick={() => edit(record)}>
{text}
</div>
);
},
},
{
title: i18n('editTable.label.prefixLength'),
dataIndex: 'prefixLength',
width: '45%',
render: (text: string, record: IIndexIncludeColumnItem) => {
const editable = isEditing(record);
return editable ? (
<Form.Item name="prefixLength" style={{ margin: 0 }}>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
) : (
<div className={styles.editableCell} onClick={() => edit(record)}>
{text}
</div>
);
},
},
];
const onValuesChange = (changedValues: any, allValues: any) => {
const newDataSource = dataSource?.map((i) => {
if (i.key === editingKey) {
return {
...i,
...allValues,
};
}
return i;
});
setDataSource(newDataSource);
};
const getIncludeColInfo = () => {
const includeColInfo: IIndexIncludeColumnItem[] = [];
dataSource.forEach(t => {
columnList.forEach(columnItem => {
if (t.columnName === columnItem.name) {
includeColInfo.push({
...createInitialData(),
...columnItem,
columnName: t.columnName,
});
}
});
});
return includeColInfo;
};
useImperativeHandle(ref, () => ({
getIncludeColInfo,
}));
return (
<div className={classnames(styles.box)}>
<div className={styles.indexListHeader}>
<Button onClick={addData}>{i18n('editTable.button.add')}</Button>
<Button onClick={deleteData}>{i18n('editTable.button.delete')}</Button>
</div>
<Form form={form} onValuesChange={onValuesChange}>
<Table pagination={false} rowKey="key" columns={columns} dataSource={dataSource} />
</Form>
</div>
);
});
export default IncludeCol;

View File

@ -0,0 +1,37 @@
@import '../../../styles/var.less';
.box {
padding: 10px;
}
.indexListHeader {
margin: 0px -5px 10px;
button {
margin: 0px 5px;
}
}
.editableCell {
height: 28px;
line-height: 26px;
padding: 0px 7px;
box-sizing: border-box;
border: 1px solid transparent;
border-radius: 4px;
.f-single-line();
width: 100%;
cursor: pointer;
&:hover {
border: 1px solid var(--color-border);
}
}
.columnListCell {
span {
margin-right: 8px;
color: var(--color-primary);
cursor: pointer;
&:hover {
color: var(--color-primary-hover);
}
}
}

View File

@ -0,0 +1,337 @@
import React, { memo, useState, forwardRef, ForwardedRef, useImperativeHandle, useContext, useRef, useMemo, useEffect } from 'react';
import styles from './index.less';
import classnames from 'classnames';
import { MenuOutlined } from '@ant-design/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext } from '@dnd-kit/core';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Table, InputNumber, Input, Form, Select, Checkbox, Button, Modal } from 'antd';
import { v4 as uuidv4 } from 'uuid';
import IncludeCol, { IIncludeColRef } from '../IncludeCol';
import { IColumnItem, IIndexItem, IIndexIncludeColumnItem } from '@/typings';
import { IndexesType } from '@/constants';
import { Context } from '../index';
import i18n from '@/i18n';
const indexesTypeList = [IndexesType['Normal'], IndexesType['Unique'], IndexesType['Fulltext'], IndexesType['Spatial']]
interface IProps {
}
export type IIndexListInfo = IIndexItem[];
export interface IIndexListRef {
getIndexListInfo: () => IIndexListInfo;
}
const createInitialData = (): IIndexItem => {
return {
key: uuidv4(),
columnList: [],
name: '',
type: null,
columns: null,
comment: null,
}
}
interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
'data-row-key': string;
}
const IndexList = forwardRef((props: IProps, ref: ForwardedRef<IIndexListRef>) => {
const { tableDetails, columnListRef } = useContext(Context);
const [dataSource, setDataSource] = useState<IIndexItem[]>([createInitialData()]);
const [form] = Form.useForm();
const [editingKey, setEditingKey] = useState(dataSource[0]?.key);
const [includeColModalOpen, setIncludeColModalOpen] = useState(false);
const includeColRef = useRef<IIncludeColRef>(null);
const isEditing = (record: IIndexItem) => record.key === editingKey;
const edit = (record: Partial<IIndexItem> & { key?: React.Key }) => {
form.setFieldsValue({ ...record });
setEditingKey(record.key);
};
useEffect(() => {
const data = tableDetails.indexList?.map(i => {
return {
...i,
key: uuidv4(),
}
})
setDataSource(data || [])
}, [tableDetails])
const addData = () => {
const newData = {
key: uuidv4(),
columnList: [],
name: '',
type: null,
columns: null,
}
setDataSource([...dataSource, newData])
edit(newData)
}
const deleteData = () => {
setDataSource(dataSource.filter(i => i.key !== editingKey))
}
const moveData = (action: 'up' | 'down') => {
const index = dataSource.findIndex(i => i.key === editingKey)
if (index === -1) {
return
}
if (action === 'up') {
if (index === 0) {
return
}
const newData = [...dataSource]
newData[index] = dataSource[index - 1]
newData[index - 1] = dataSource[index]
setDataSource(newData)
} else {
if (index === dataSource.length - 1) {
return
}
const newData = [...dataSource]
newData[index] = dataSource[index + 1]
newData[index + 1] = dataSource[index]
setDataSource(newData)
}
}
const handelFieldsChange = (field: any) => {
let { name: nameList, value } = field[0];
const name = nameList[0];
if (name === 'nullable') {
value = value ? 1 : 0
}
const newData = dataSource.map((item) => {
if (item.key === editingKey) {
return {
...item,
[name]: value,
};
}
return item;
});
setDataSource(newData);
}
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) {
setDataSource((previous) => {
const activeIndex = previous.findIndex((i) => i.key === active.id);
const overIndex = previous.findIndex((i) => i.key === over?.id);
return arrayMove(previous, activeIndex, overIndex);
});
}
};
const Row = ({ children, ...props }: RowProps) => {
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id: props['data-row-key'],
});
const style: React.CSSProperties = {
...props.style,
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
return (
<tr {...props} ref={setNodeRef} style={style} {...attributes}>
{React.Children.map(children, (child) => {
if ((child as React.ReactElement).key === 'sort') {
return React.cloneElement(child as React.ReactElement, {
children: (
<MenuOutlined
ref={setActivatorNodeRef}
style={{ touchAction: 'none', cursor: 'move' }}
{...listeners}
/>
),
});
}
return child;
})}
</tr>
);
};
function getIndexListInfo(): IIndexListInfo {
return dataSource
}
useImperativeHandle(ref, () => ({
getIndexListInfo,
}));
const columns = [
{
key: 'sort',
width: '60px',
},
{
title: i18n('editTable.label.index'),
width: '70px',
render: (text: string, record: IIndexItem) => {
return dataSource.findIndex(i => i.key === record.key) + 1
}
},
{
title: i18n('editTable.label.indexName'),
dataIndex: 'name',
width: '180px',
render: (text: string, record: IIndexItem) => {
const editable = isEditing(record);
return editable ? (
<Form.Item
name="name"
style={{ margin: 0 }}
>
<Input />
</Form.Item>
) : <div
className={styles.editableCell}
onClick={() => edit(record)}
>
{text}
</div>
}
},
{
title: i18n('editTable.label.indexType'),
dataIndex: 'type',
width: '180px',
render: (text: string, record: IIndexItem) => {
const editable = isEditing(record);
return editable ? (
<Form.Item
name="type"
style={{ margin: 0 }}
>
<Select style={{ width: '100%' }}>
{indexesTypeList.map(i => <Select.Option key={i} value={i}>{i}</Select.Option>)}
</Select>
</Form.Item>
) : <div
className={styles.editableCell}
onClick={() => edit(record)}
>
{text}
</div>
}
},
{
title: i18n('editTable.label.includeColumn'),
dataIndex: 'columnList',
render: (columnList: IIndexIncludeColumnItem[], record: IIndexItem) => {
const editable = isEditing(record);
const text = columnList?.map(t => {
return `${t.columnName}`
}).join(',')
return editable ? (
<div className={styles.columnListCell}>
<span onClick={() => { setIncludeColModalOpen(true) }}>{i18n('common.button.edit')}</span>
{text}
</div >
) : (
<div
className={styles.editableCell}
onClick={() => edit(record)}
>
{text}
</div>
);
}
},
];
const getIncludeColInfo = () => {
setDataSource(
dataSource.map(i => {
if (i.key === editingKey) {
i.columnList = includeColRef.current?.getIncludeColInfo()!
}
return i
})
)
setIncludeColModalOpen(false)
}
const indexIncludedColumnList: IIndexIncludeColumnItem[] = useMemo(() => {
let data: IIndexIncludeColumnItem[] | null = [];
dataSource.forEach(i => {
if (i.key === editingKey) {
data = i.columnList
}
})
return data
}, [editingKey])
return <div className={classnames(styles.box)}>
<div className={styles.indexListHeader}>
<Button onClick={addData}>{i18n('editTable.button.add')}</Button>
<Button onClick={deleteData}>{i18n('editTable.button.delete')}</Button>
{/* <Button onClick={moveData.bind(null, 'up')}>上移</Button>
<Button onClick={moveData.bind(null, 'down')}>下移</Button> */}
</div>
<Form form={form} onFieldsChange={handelFieldsChange}>
<DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
<SortableContext
items={dataSource.map((i) => i.key)}
strategy={verticalListSortingStrategy}
>
<Table
components={{
body: {
row: Row,
},
}}
pagination={false}
rowKey="key"
columns={columns}
dataSource={dataSource}
/>
</SortableContext>
</DndContext>
</Form>
<Modal
open={includeColModalOpen}
width={800}
title={i18n('editTable.label.includeColumn')}
onOk={getIncludeColInfo}
onCancel={() => { setIncludeColModalOpen(false) }}
maskClosable={false}
destroyOnClose={true}
>
<IncludeCol includedColumnList={indexIncludedColumnList} ref={includeColRef} />
</Modal>
</div >
})
export default IndexList

View File

@ -0,0 +1,66 @@
@import '../../styles/var.less';
.box {
height: 100%;
display: flex;
flex-direction: column;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 10px 0px 10px;
box-sizing: border-box;
}
.tabList {
display: flex;
border-bottom: 0;
font-size: 14px;
}
.tabItem {
position: relative;
height: 28px;
line-height: 28px;
padding: 0px 18px;
border: 1px solid var(--color-border);
margin-right: 1px;
cursor: pointer;
}
.currentTab {
font-weight: 500;
color: var(--color-primary);
&::before {
content: '';
position: absolute;
left: 0;
bottom: -1px;
right: 0px;
height: 2px;
background-color: var(--color-primary);
}
}
.main {
flex: 1;
position: relative;
overflow: hidden;
}
.tab {
z-index: 2;
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
.hidden {
z-index: 1;
opacity: 0;
}

View File

@ -0,0 +1,147 @@
import React, { memo, useRef, useState, createContext, useEffect, forwardRef, useMemo } from 'react';
import { Button, Form } from 'antd';
import styles from './index.less';
import classnames from 'classnames';
import IndexList, { IIndexListRef } from './IndexList';
import ColumnList, { IColumnListRef } from './ColumnList';
import BaseInfo, { IBaseInfoRef } from './BaseInfo';
import sqlService, { IModifyTableSqlParams } from '@/service/sql';
import { IEditTableInfo } from '@/typings';
import i18n from '@/i18n';
interface IProps {
dataSourceId: number,
databaseName: string,
schemaName: string | undefined,
tableName?: string
}
interface ITabItem {
title: string;
key: string;
component: any; // TODO: 组件的Ts是什么
}
interface IContext extends IProps {
tableDetails: IEditTableInfo;
baseInfoRef: React.RefObject<IBaseInfoRef>;
columnListRef: React.RefObject<IColumnListRef>;
indexListRef: React.RefObject<IIndexListRef>;
}
export const Context = createContext<IContext>({} as any);
export default memo<IProps>(function DatabaseTableEditor(props) {
const { databaseName, dataSourceId, tableName, schemaName } = props;
const [tableDetails, setTableDetails] = useState<IEditTableInfo>({} as any);
const [oldTableDetails, setOldTableDetails] = useState<IEditTableInfo>({} as any);
const baseInfoRef = useRef<IBaseInfoRef>(null);
const columnListRef = useRef<IColumnListRef>(null);
const indexListRef = useRef<IIndexListRef>(null);
const tabList = useMemo(() => {
return [
{
title: i18n('editTable.tab.basicInfo'),
key: 'basic',
component: <BaseInfo ref={baseInfoRef} />
},
{
title: i18n('editTable.tab.columnInfo'),
key: 'column',
component: <ColumnList ref={columnListRef} />
},
{
title: i18n('editTable.tab.indexInfo'),
key: 'index',
component: <IndexList ref={indexListRef} />
},
]
}, [])
const [currentTab, setCurrentTab] = useState<ITabItem>(tabList[0]);
function changeTab(item: ITabItem) {
setCurrentTab(item)
}
useEffect(() => {
if (tableName) {
let params = {
databaseName,
dataSourceId,
tableName,
schemaName,
refresh: true
}
sqlService.getTableDetails(params).then(res => {
setTableDetails(res || {})
setOldTableDetails(res)
})
}
}, [])
function submit() {
if (baseInfoRef.current && columnListRef.current && indexListRef.current) {
const newTable = {
...baseInfoRef.current.getBaseInfo(),
columnList: columnListRef.current.getColumnListInfo()!,
indexList: indexListRef.current.getIndexListInfo()!
}
let params: IModifyTableSqlParams = {
databaseName,
dataSourceId,
schemaName,
refresh: true,
newTable: JSON.stringify(newTable),
}
if (tableName) {
params.tableName = tableName;
params.oldTable = JSON.stringify(oldTableDetails);
}
console.log(newTable);
sqlService.getModifyTableSql(params).then(res => {
console.log(res)
})
}
}
return <Context.Provider value={{
...props,
tableDetails,
baseInfoRef,
columnListRef,
indexListRef
}}>
<div className={classnames(styles.box)}>
<div className={styles.header}>
<div className={styles.tabList}>
{
tabList.map((item, index) => {
return <div
key={item.key}
onClick={changeTab.bind(null, item)}
className={classnames(styles.tabItem, currentTab.key == item.key ? styles.currentTab : '')}
>
{item.title}
</div>
})
}
</div>
<div className={styles.saveButton}>
<Button type="primary" onClick={submit}>{i18n('common.button.save')}</Button>
</div>
</div>
<div className={styles.main}>
{
tabList.map(t => {
return <div key={t.key} className={classnames(styles.tab, { [styles.hidden]: currentTab.key !== t.key })}>
{t.component}
</div>
})
}
</div>
</div>
</Context.Provider>
})

View File

@ -1,4 +1,4 @@
@import '../../../../../styles/var.less';
@import '../../styles/var.less';
.box {
height: 100%;

View File

@ -7,7 +7,7 @@ import Console, { IAppendValue } from '@/components/Console';
import SearchResult from '@/components/SearchResult';
import { DatabaseTypeCode, ConsoleStatus, TreeNodeType } from '@/constants';
import { IManageResultData, IResultConfig } from '@/typings';
import { IWorkspaceModelState } from '@/models/workspace';
import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace';
import historyServer, { IGetSavedListParams, ISaveBasicInfo } from '@/service/history';
import { IAIState } from '@/models/ai';
import sqlServer, { IExecuteSqlParams, IExportParams } from '@/service/sql';
@ -41,7 +41,7 @@ const defaultResultConfig: IResultConfig = {
hasNextPage: true,
};
const WorkspaceRightItem = memo<IProps>(function (props) {
const SQLExecute = memo<IProps>(function (props) {
const { data, workspaceModel, aiModel, isActive, dispatch } = props;
const draggableRef = useRef<any>();
const [appendValue, setAppendValue] = useState<IAppendValue>();
@ -218,4 +218,9 @@ const WorkspaceRightItem = memo<IProps>(function (props) {
);
});
export default WorkspaceRightItem;
const dvaModel = connect(({ workspace, ai }: { workspace: IWorkspaceModelType; ai: IAIState }) => ({
workspaceModel: workspace,
aiModel: ai,
}));
export default dvaModel(SQLExecute);

View File

@ -44,7 +44,7 @@
.menus {
width: 200px;
background-color: var(--color-bg-elevated);
// background-color: var(--color-bg-subtle);
padding: 20px;
position: sticky;
top: 0;

View File

@ -47,13 +47,13 @@
border-radius: 4px 12px;
&:hover {
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
}
}
.remainBlock {
border-radius: 16px;
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
padding: 4px 12px;
&:hover {

View File

@ -48,7 +48,7 @@
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--color-border-secondary);
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
padding: 0 16px;
height: 30px;
}
@ -77,7 +77,7 @@
justify-content: start;
align-items: center;
border-top: 1px solid var(--color-border-secondary);
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
& > span {
margin-right: 16px;
@ -114,7 +114,7 @@
display: none;
align-items: center;
// position: absolute;
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
// top: 0;
// right: 0;
// bottom: 0;

View File

@ -36,7 +36,7 @@ interface IViewTableCellData {
const SupportBaseTable: any = styled(BaseTable)`
&.supportBaseTable {
--bgcolor: var(--color-bg-base);
--header-bgcolor: var(--color-bg-elevated);
--header-bgcolor: var(--color-bg-subtle);
--hover-bgcolor: var(--color-hover-bg);
--header-hover-bgcolor: var(--color-hover-bg);
--highlight-bgcolor: var(--color-hover-bg);

View File

@ -138,7 +138,7 @@ export default memo<IProps>(function SearchResult(props) {
type="line"
onEdit={onEdit}
onChange={onChange}
tabs={tabs}
items={tabs}
className={styles.tabs}
activeTab={currentTab}
/>

View File

@ -1,7 +1,7 @@
@import '../../styles/var.less';
.tab-focus() {
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
// 添加内阴影
border-bottom: 1px solid var(--color-primary);
.icon {
@ -11,7 +11,7 @@
.tab-focus-line() {
color: var(--color-primary);
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
.icon {
display: flex;
}
@ -155,7 +155,7 @@
outline: none;
font-size: 12px;
font-weight: 400;
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
input:focus {
outline: none;
}

View File

@ -17,7 +17,7 @@ export interface IOnchangeProps {
interface IProps {
className?: string;
tabs: IOption[] | undefined;
items: IOption[] | undefined;
activeTab?: number | string;
onChange: (key: IOption['value']) => void;
onEdit?: (action: 'add' | 'remove', key?: IOption['value']) => void;
@ -28,8 +28,8 @@ interface IProps {
}
export default memo<IProps>(function Tab(props) {
const { className, tabs, onChange, onEdit, activeTab, hideAdd, type, editableName, editableNameOnBlur } = props;
const [internalTabs, setInternalTabs] = useState<IOption[]>(lodash.cloneDeep(tabs || []));
const { className, items, onChange, onEdit, activeTab, hideAdd, type, editableName, editableNameOnBlur } = props;
const [internalTabs, setInternalTabs] = useState<IOption[]>(lodash.cloneDeep(items || []));
const [internalActiveTab, setInternalActiveTab] = useState<number | string | undefined>(internalTabs[0]?.value);
const [editingTab, setEditingTab] = useState<IOption['value'] | undefined>();
@ -38,8 +38,8 @@ export default memo<IProps>(function Tab(props) {
}, [activeTab])
useEffect(() => {
setInternalTabs(lodash.cloneDeep(tabs || []));
}, [tabs])
setInternalTabs(lodash.cloneDeep(items || []));
}, [items])
function deleteTab(data: IOption) {
const newTabs = internalTabs?.filter(t => t.value !== data.value);

View File

@ -0,0 +1,194 @@
@import '../../styles/var.less';
.tab-focus() {
background-color: var(--color-bg-subtle);
// 添加内阴影
border-bottom: 1px solid var(--color-primary);
.icon {
display: flex;
}
}
.tab-focus-line() {
color: var(--color-primary);
background-color: var(--color-bg-subtle);
.icon {
display: flex;
}
}
.tabBox {
display: flex;
flex-direction: column;
}
.tabsNav {
display: flex;
overflow: auto;
height: 32px;
background-color: var(--color-bg-base);
border-bottom: 1px solid var(--color-border);
&::-webkit-scrollbar {
display: none;
}
}
.tabsContent {
flex: 1;
.tabsContentItem {
height: 100%;
width: 100%;
display: none;
}
.tabsContentItemActive {
display: block;
}
}
.activeContent {
display: block;
}
.tabList {
display: flex;
}
.tabItem {
position: relative;
display: flex;
align-items: center;
padding: 0px 10px;
line-height: 32px;
height: 32px;
width: 120px;
cursor: pointer;
user-select: none;
border-right: 1px solid var(--color-border);
.textBox {
flex: 1;
display: flex;
align-items: center;
}
.text {
flex: 1;
width: 0;
.f-single-line();
}
.icon {
display: none;
align-items: center;
flex-shrink: 0;
height: 16px;
padding: 0px 2px;
margin-left: 2px;
border-radius: 2px;
cursor: pointer;
color: var(--color-text-secondary);
i {
font-size: 12px;
}
&:hover {
color: var(--color-primary);
background-color: var(--color-hover-bg);
}
}
&:hover {
.tab-focus();
color: var(--color-primary);
}
}
.tabItemLine {
position: relative;
display: flex;
align-items: center;
padding: 0px 10px;
line-height: 20px;
height: 28px;
width: 100px;
cursor: pointer;
border-right: 1px solid var(--color-border);
.textBox {
flex: 1;
display: flex;
align-items: center;
}
.text {
flex: 1;
width: 0;
.f-single-line();
}
.text {
flex: 1;
width: 0;
.f-single-line();
}
.icon {
display: none;
align-items: center;
flex-shrink: 0;
height: 16px;
padding: 0px 2px;
margin-left: 2px;
border-radius: 2px;
color: var(--color-text-secondary);
cursor: pointer;
i {
font-size: 12px;
}
&:hover {
color: var(--color-primary);
background-color: var(--color-hover-bg);
}
}
&:hover {
.tab-focus-line();
color: var(--color-primary);
}
}
.activeTab {
.tab-focus();
}
.activeTabLine {
.tab-focus-line();
}
.rightBox {
flex: 1;
}
.addIcon {
width: 30px;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
&:hover {
color: var(--color-primary);
}
}
.input {
border: 0;
width: 86px;
flex: 1;
height: 20px;
outline: none;
font-size: 12px;
font-weight: 400;
background-color: var(--color-bg-subtle);
input:focus {
outline: none;
}
}
.prefixIcon {
flex-shrink: 0;
margin-right: 4px;
}
.activeContent {
}

View File

@ -0,0 +1,154 @@
import React, { memo, useEffect, useState, Fragment } from 'react';
import classnames from 'classnames';
import Iconfont from '@/components/Iconfont';
import styles from './index.less';
export interface ITabItem {
prefixIcon?: string;
label: React.ReactNode;
key: number | string;
children?: React.ReactNode;
}
export interface IOnchangeProps {
type: 'add' | 'delete' | 'switch';
data?: ITabItem;
}
interface IProps {
className?: string;
items: ITabItem[] | undefined;
activeKey?: number | string;
onChange?: (key: string | number | undefined) => void;
onEdit?: (action: 'add' | 'remove', data?: ITabItem) => void;
hideAdd?: boolean;
type?: 'line';
editableName?: boolean;
editableNameOnBlur?: (option: ITabItem) => void;
}
export default memo<IProps>(function Tabs(props) {
const { className, items, onChange, onEdit, activeKey, hideAdd, type, editableName, editableNameOnBlur } = props;
const [internalTabs, setInternalTabs] = useState<ITabItem[]>([]);
const [internalActiveTab, setInternalActiveTab] = useState<number | string | undefined>();
const [editingTab, setEditingTab] = useState<ITabItem['key'] | undefined>();
useEffect(() => {
if (activeKey !== null && activeKey !== undefined) {
setInternalActiveTab(activeKey);
}
}, [activeKey])
useEffect(() => {
setInternalTabs(items || []);
if (items?.length && internalActiveTab === undefined) {
setInternalActiveTab(items[0].key);
}
}, [items])
useEffect(() => {
onChange?.(internalActiveTab);
}, [internalActiveTab])
function deleteTab(data: ITabItem) {
const newInternalTabs = internalTabs?.filter(t => t.key !== data.key);
let activeKey = internalActiveTab;
// 删掉的是当前激活的tab那么就切换到前一个,如果前一个没有就切换到后一个
if (data.key === internalActiveTab) {
const index = internalTabs.findIndex(t => t.key === data.key);
if (index === 0) {
activeKey = internalTabs[1]?.key
} else {
activeKey = internalTabs[index - 1]?.key
}
}
changeTab(activeKey);
setInternalTabs(newInternalTabs);
onEdit?.('remove', data)
}
function changeTab(key: string | number | undefined) {
setInternalActiveTab(key);
}
function handelAdd() {
onEdit?.('add')
}
function onDoubleClick(t: ITabItem) {
if (editableName) {
setEditingTab(t.key)
}
}
function renderTabItem(t: ITabItem, index: number) {
function inputOnChange(value: string) {
internalTabs[index].label = value
setInternalTabs([...internalTabs])
}
function onBlur() {
editableNameOnBlur?.(t);
setEditingTab(undefined);
}
return <div
onDoubleClick={() => { onDoubleClick(t) }}
key={t.key}
className={
classnames(
{ [styles.tabItem]: type !== 'line' },
{ [styles.tabItemLine]: type === 'line' },
{ [styles.activeTabLine]: t.key === internalActiveTab && type === 'line' },
{ [styles.activeTab]: t.key === internalActiveTab && type !== 'line' },
)
}
>
{
t.key === editingTab ?
<input value={t.label as string} onChange={(e) => { inputOnChange(e.target.value) }} className={styles.input} autoFocus onBlur={onBlur} type="text" />
:
<div className={styles.textBox} key={t.key} onClick={changeTab.bind(null, t.key)}>
{t.prefixIcon && <Iconfont className={styles.prefixIcon} code={t.prefixIcon} />}
<div className={styles.text}>
{t.label}
</div>
</div>
}
<div className={styles.icon} onClick={deleteTab.bind(null, t)}>
<Iconfont code='&#xe634;' />
</div>
</div>
}
return <div className={classnames(styles.tabBox, className)}>
<div className={styles.tabsNav}>
{
!!internalTabs?.length &&
<div className={styles.tabList}>
{
internalTabs.map((t, index) => {
return renderTabItem(t, index)
})
}
</div>
}
{
!hideAdd && <div className={styles.rightBox}>
<div className={styles.addIcon} onClick={handelAdd}>
<Iconfont code='&#xe631;'></Iconfont>
</div>
</div>
}
</div>
<div className={styles.tabsContent}>
{
internalTabs?.map(t => {
return <div key={t.key} className={classnames(styles.tabsContentItem, { [styles.tabsContentItemActive]: t.key === internalActiveTab })}>
{t.children}
</div>
})
}
</div>
</div >
})

View File

@ -33,37 +33,6 @@ export enum OSType {
RESTS = 'rests',
}
export enum OperationType {
CONSOLE = 'console',
FUNCTION = 'function',
PROCEDURE = 'procedure',
VIEW = 'view',
TRIGGER = 'trigger',
}
export const operationTypeConfig: {
[key in OperationType]: {
icon: string
};
} = {
[OperationType.CONSOLE]: {
icon: '\uec83'
},
[OperationType.VIEW]: {
icon: '\ue70c'
},
[OperationType.FUNCTION]: {
icon: '\ue76a'
},
[OperationType.PROCEDURE]: {
icon: '\ue73c'
},
[OperationType.TRIGGER]: {
icon: '\ue64a'
}
}
export enum ConnectionKind {
Private = 'PRIVATE',
Shared = 'SHARED'

View File

@ -0,0 +1,11 @@
// 索引类型
export enum IndexesType {
// 普通索引
Normal = 'normal',
// 唯一索引
Unique = 'unique',
// 全文索引
Fulltext = 'fulltext',
// 空间索引
Spatial = 'spatial',
}

View File

@ -6,4 +6,6 @@ export * from './monacoEditor';
export * from './table';
export * from './theme';
export * from './tree';
export * from './workspace';
export * from './editTable';

View File

@ -0,0 +1,46 @@
export enum CreateTabIntroType {
EditorTable = 'EditorTable',
OpenTable = 'openTable',
}
// 工作台Tab的类型
export enum WorkspaceTabType {
CONSOLE = 'console',
FUNCTION = 'function',
PROCEDURE = 'procedure',
VIEW = 'view',
TRIGGER = 'trigger',
EditTable = 'editTable',
OpenTable = 'openTable',
}
// 工作台Tab的类型对应的一些配置
export const workspaceTabConfig: {
[key in WorkspaceTabType]: {
icon: string
};
} = {
[WorkspaceTabType.CONSOLE]: {
icon: '\uec83'
},
[WorkspaceTabType.VIEW]: {
icon: '\ue70c'
},
[WorkspaceTabType.FUNCTION]: {
icon: '\ue76a'
},
[WorkspaceTabType.PROCEDURE]: {
icon: '\ue73c'
},
[WorkspaceTabType.TRIGGER]: {
icon: '\ue64a'
},
[WorkspaceTabType.EditTable]: {
icon: '\ue6b6'
},
[WorkspaceTabType.OpenTable]: {
icon: '\ue618'
}
}

View File

@ -0,0 +1,21 @@
export default {
'editTable.tab.basicInfo': 'Basic info',
'editTable.tab.columnInfo': 'Column info',
'editTable.tab.indexInfo': 'Index info',
'editTable.label.tableName': 'Table name',
'editTable.label.comment': 'Comment',
'editTable.button.add': 'Add',
'editTable.button.delete': 'Delete',
'editTable.button.up': 'Up',
'editTable.button.down': 'Down',
'editTable.label.indexName': 'Index name',
'editTable.label.indexType': 'Index type',
'editTable.label.includeColumn': 'Include column',
'editTable.button.createTable': 'Create table',
'editTable.label.index': 'Index',
'editTable.label.columnName': 'Column name',
'editTable.label.columnSize': 'Size',
'editTable.label.columnType': 'Type',
'editTable.label.nullable': 'Nullable',
'editTable.label.prefixLength': 'Prefix length',
};

View File

@ -7,6 +7,7 @@ import dashboard from './dashboard';
import chat from './chat';
import team from './team'
import login from './login';
import editTable from './editTable';
export default {
lang: 'en',
@ -18,5 +19,6 @@ export default {
...dashboard,
...chat,
...team,
...login
...login,
...editTable
};

View File

@ -4,6 +4,7 @@ export default {
'workspace.title.saved': 'Saved',
'workspace.menu.exportDDL': 'Export DDL',
'workspace.menu.deleteTable': 'Delete Table',
'workspace.menu.editTable': 'Edit Table',
'workspace.menu.pin': 'Pin',
'workspace.menu.unPin': 'Unpin',
'workspace.menu.deleteTablePlaceHolder': 'Please enter the name of the table you want to delete',

View File

@ -0,0 +1,21 @@
export default {
'editTable.tab.basicInfo': '基本信息',
'editTable.tab.columnInfo': '列信息',
'editTable.tab.indexInfo': '索引信息',
'editTable.label.tableName': '表名',
'editTable.label.comment': '注释',
'editTable.button.add': '新增',
'editTable.button.delete': '删除',
'editTable.button.up': '上移',
'editTable.button.down': '下移',
'editTable.label.indexName': '索引名称',
'editTable.label.indexType': '索引类型',
'editTable.label.includeColumn': '包含列',
'editTable.button.createTable': '新建表',
'editTable.label.index': '序号',
'editTable.label.columnName': '列名',
'editTable.label.columnSize': '长度',
'editTable.label.columnType': '类型',
'editTable.label.nullable': '可空',
'editTable.label.prefixLength': '前缀长度',
};

View File

@ -8,6 +8,7 @@ import dashboard from './dashboard';
import chat from './chat';
import team from './team'
import login from './login';
import editTable from './editTable';
export default {
lang: LangType.ZH_CN,
@ -20,5 +21,6 @@ export default {
...dashboard,
...chat,
...team,
...login
...login,
...editTable
};

View File

@ -4,6 +4,7 @@ export default {
'workspace.title.saved': '保存记录',
'workspace.menu.exportDDL': '导出DDL',
'workspace.menu.deleteTable': '删除表',
'workspace.menu.editTable': '编辑表',
'workspace.menu.pin': '置顶',
'workspace.menu.unPin': '取消置顶',
'workspace.menu.deleteTablePlaceHolder': '请输入你要删除的表名',
@ -18,5 +19,4 @@ export default {
'workspace.tree.trigger': '触发器',
'workspace.tree.function': '函数',
'workspace.tree.procedure': '存储过程',
};

View File

@ -3,7 +3,7 @@ import sqlService, { MetaSchemaVO } from '@/service/sql';
import historyService from '@/service/history';
import { DatabaseTypeCode, ConsoleStatus, TreeNodeType } from '@/constants';
import { Effect, Reducer } from 'umi';
import { ITreeNode, IConsole, IPageResponse } from '@/typings';
import { ITreeNode, IConsole, IPageResponse, ICreateTabIntro, IWorkspaceTab } from '@/typings';
import { treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig';
export type ICurWorkspaceParams = {
@ -25,7 +25,10 @@ export interface IWorkspaceModelState {
curConsoleId: number | null;
openConsoleList: IConsole[];
curTableList: ITreeNode[];
curViewList: ITreeNode[];
// 触发tab编辑表或打开表
createTabIntro: ICreateTabIntro | undefined;
// 触发新增console
createConsoleIntro: IWorkspaceTab | undefined;
}
export interface IWorkspaceModelType {
@ -40,7 +43,8 @@ export interface IWorkspaceModelType {
setOpenConsoleList: Reducer<IWorkspaceModelState>;
setCurConsoleId: Reducer<IWorkspaceModelState>;
setCurTableList: Reducer<IWorkspaceModelState>;
setCurViewList: Reducer<IWorkspaceModelState>;
setCreateTabIntro: Reducer<IWorkspaceModelState>;
setCreateConsoleIntro: Reducer<IWorkspaceModelState>;
};
effects: {
fetchDatabaseAndSchema: Effect;
@ -61,8 +65,9 @@ const WorkspaceModel: IWorkspaceModelType = {
consoleList: [],
openConsoleList: [],
curTableList: [],
curViewList: [],
curConsoleId: null
curConsoleId: null,
createTabIntro: undefined,
createConsoleIntro: undefined,
},
reducers: {
@ -117,14 +122,21 @@ const WorkspaceModel: IWorkspaceModelType = {
curTableList: payload,
};
},
// 视图列表
setCurViewList(state, { payload }) {
// 创建tab的引子
setCreateTabIntro(state, { payload }) {
return {
...state,
curViewList: payload,
createTabIntro: payload,
};
},
// 创建console的引子
setCreateConsoleIntro(state, { payload }) {
return {
...state,
createConsoleIntro: payload,
};
}
},
effects: {

View File

@ -13,7 +13,7 @@
flex-direction: column;
width: 220px;
overflow: hidden;
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
border-right: 1px solid var(--color-border-secondary);
border-top: 0px;
border-bottom: 0px;

View File

@ -16,14 +16,13 @@
font-size: 14px;
}
.title {}
.title {
}
.edit {
cursor: pointer;
}
.left_overlay_add {
position: absolute;
top: 0;
@ -33,7 +32,6 @@
cursor: pointer;
}
.right_overlay_add {
position: absolute;
top: 0;
@ -68,7 +66,6 @@
opacity: 1;
}
.add_chart_icon {
opacity: 0;
width: 16px;
@ -97,7 +94,6 @@
filter: var(--filter-color-gray-100);
}
.emptyChartBlock {
width: 100%;
padding: 20px 0;
@ -122,7 +118,7 @@
.editBlock {
border-radius: var(--border-radius-l-g);
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
max-height: 600px;
}

View File

@ -19,7 +19,7 @@
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
padding: 10px 8px 0px;
box-sizing: border-box;
min-width: 200px;

View File

@ -15,7 +15,7 @@
justify-content: space-between;
align-items: center;
width: 68px;
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
border-right: 1px solid var(--color-border-secondary);
user-select: none;
overflow: hidden;

View File

@ -1,14 +1,14 @@
import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'react';
import React, { memo, useState, useEffect, useRef } from 'react';
import classnames from 'classnames';
import i18n from '@/i18n';
import { connect } from 'umi';
import { Cascader, Divider, Input, Dropdown, Button, Spin } from 'antd';
import { Input, Dropdown } from 'antd';
import Iconfont from '@/components/Iconfont';
import LoadingContent from '@/components/Loading/LoadingContent';
import { IConnectionModelType } from '@/models/connection';
import { IWorkspaceModelType } from '@/models/workspace';
import historyServer from '@/service/history';
import { ConsoleStatus, ConsoleOpenedStatus } from '@/constants';
import { ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType } from '@/constants';
import { IConsole, ITreeNode } from '@/typings';
import styles from './index.less';
import { approximateList } from '@/utils';
@ -17,16 +17,11 @@ interface IProps {
className?: string;
workspaceModel: IWorkspaceModelType['state'],
dispatch: any;
tableLoading: boolean;
databaseLoading: boolean;
}
const dvaModel = connect(
({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({
connectionModel: connection,
({ workspace }: { workspace: IWorkspaceModelType }) => ({
workspaceModel: workspace,
tableLoading: loading.effects['workspace/fetchGetCurTableList'],
databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'],
}),
);
@ -59,7 +54,6 @@ const SaveList = dvaModel(function (props: any) {
});
}, [curWorkspaceParams]);
useEffect(() => {
if (searching) {
inputRef.current!.focus({
@ -85,31 +79,19 @@ const SaveList = dvaModel(function (props: any) {
}
function openConsole(data: IConsole) {
let p: any = {
id: data.id,
tabOpened: ConsoleOpenedStatus.IS_OPEN
};
historyServer.updateSavedConsole(p).then((res) => {
dispatch({
type: 'workspace/setCurConsoleId',
payload: data.id,
});
dispatch({
type: 'workspace/fetchGetSavedConsole',
type: 'workspace/setCreateConsoleIntro',
payload: {
orderByDesc: false,
tabOpened: ConsoleOpenedStatus.IS_OPEN,
...curWorkspaceParams
id: data.id,
type: WorkspaceTabType.CONSOLE,
title: data.name,
uniqueData: data,
},
callback: (res: any) => {
dispatch({
type: 'workspace/setOpenConsoleList',
payload: res.data,
})
}
})
});
}
@ -119,20 +101,20 @@ const SaveList = dvaModel(function (props: any) {
id: data.id,
};
historyServer.deleteSavedConsole(p).then((res) => {
dispatch({
type: 'workspace/fetchGetSavedConsole',
payload: {
orderByDesc: true,
tabOpened: ConsoleOpenedStatus.IS_OPEN,
...curWorkspaceParams
},
callback: (res: any) => {
dispatch({
type: 'workspace/setOpenConsoleList',
payload: res.data,
})
}
})
// dispatch({
// type: 'workspace/fetchGetSavedConsole',
// payload: {
// orderByDesc: true,
// tabOpened: ConsoleOpenedStatus.IS_OPEN,
// ...curWorkspaceParams
// },
// callback: (res: any) => {
// dispatch({
// type: 'workspace/setOpenConsoleList',
// payload: res.data,
// })
// }
// })
dispatch({
type: 'workspace/fetchGetSavedConsole',
payload: {

View File

@ -25,9 +25,15 @@
.iconBox {
display: flex;
align-items: center;
margin: 0px -5px;
}
.itemIcon {
margin: 0px 5px;
}
.refreshIcon {
margin-right: 10px;
}
.moreIcon {
transform: rotate(90deg);
}
.refreshIcon,
.searchIcon {
@ -49,16 +55,16 @@
margin: 0px -10px;
}
.refreshIconBox{
.refreshIconBox {
cursor: pointer;
&:hover{
color: var(--color-primary)
&:hover {
color: var(--color-primary);
}
}
.cascaderPopup{
:global{
.ant-cascader-menu{
.cascaderPopup {
:global {
.ant-cascader-menu {
height: auto;
}
}

View File

@ -10,10 +10,11 @@ import { IWorkspaceModelType } from '@/models/workspace';
import Tree from '../Tree';
import { treeConfig } from '../Tree/treeConfig';
import { ITreeNode } from '@/typings';
import { TreeNodeType } from '@/constants';
import { TreeNodeType, CreateTabIntroType, WorkspaceTabType } from '@/constants';
import styles from './index.less';
import { approximateTreeNode } from '@/utils';
import { useUpdateEffect } from '@/hooks/useUpdateEffect';
import { v4 as uuidV4 } from 'uuid';
interface IOption {
value: TreeNodeType;
@ -32,16 +33,21 @@ const optionsList: IOption[] = [
]
const dvaModel = connect(
({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({
({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({
connectionModel: connection,
workspaceModel: workspace,
databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'],
}),
);
interface Option {
value: string;
label: string;
children?: Option[];
}
const TableList = dvaModel(function (props: any) {
const { workspaceModel, dispatch } = props;
const { curWorkspaceParams, curTableList, curViewList } = workspaceModel;
const { curWorkspaceParams } = workspaceModel;
const [searching, setSearching] = useState<boolean>(false);
const inputRef = useRef<any>();
const [searchedTableList, setSearchedTableList] = useState<ITreeNode[] | undefined>();
@ -62,8 +68,6 @@ const TableList = dvaModel(function (props: any) {
}
}, [curWorkspaceParams]);
useEffect(() => {
if (searching) {
inputRef.current!.focus({
@ -118,6 +122,27 @@ const TableList = dvaModel(function (props: any) {
setCurType(selectedOptions[0]);
}
const cascaderOnChange: any = (_: string[], selectedOptions: Option[]) => {
dispatch({
type: 'workspace/setCreateConsoleIntro',
payload: {
id: uuidV4(),
type: WorkspaceTabType.EditTable,
title: 'create-table',
uniqueData: {
}
},
})
};
const options = [
{
value: 'createTable',
label: i18n('editTable.button.createTable'),
}
]
return (
<div className={styles.tableModule}>
<div className={styles.leftModuleTitle}>
@ -148,12 +173,20 @@ const TableList = dvaModel(function (props: any) {
</div>
</Cascader>
<div className={styles.iconBox} >
<div className={styles.refreshIcon} onClick={() => refreshTableList()}>
<div className={classnames(styles.refreshIcon, styles.itemIcon)} onClick={() => refreshTableList()}>
<Iconfont code="&#xec08;" />
</div>
<div className={styles.searchIcon} onClick={() => openSearch()}>
<div className={classnames(styles.searchIcon, styles.itemIcon)} onClick={() => openSearch()}>
<Iconfont code="&#xe600;" />
</div>
{
curType.value === TreeNodeType.TABLES &&
<Cascader options={options} onChange={cascaderOnChange}>
<div className={classnames(styles.moreIcon, styles.itemIcon)}>
<Iconfont code="&#xe601;" />
</div>
</Cascader>
}
</div>
</div>
}
@ -161,7 +194,7 @@ const TableList = dvaModel(function (props: any) {
<LoadingContent className={styles.treeBox} isLoading={tableLoading}>
<Tree className={styles.tree} initialData={searchedTableList || curList}></Tree>
</LoadingContent>
</div>
</div >
);
});

View File

@ -4,21 +4,16 @@ import classnames from 'classnames';
import styles from './index.less';
import Iconfont from '@/components/Iconfont';
import { MenuProps, message, Modal, Input, Dropdown, notification } from 'antd';
import { TreeNodeType, DatabaseTypeCode } from '@/constants';
import { TreeNodeType, CreateTabIntroType, WorkspaceTabType } from '@/constants';
import { ITreeConfigItem, ITreeConfig, treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig';
import { ITreeNode } from '@/typings';
import connectionServer from '@/service/connection';
import historyService from '@/service/history';
import mysqlServer from '@/service/sql';
import { OperationColumn } from '../treeConfig';
import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource';
import { IConnectionConfig } from '@/components/ConnectionEdit/config/types';
import { IWorkspaceModelType } from '@/models/workspace';
import EditDialog from '@/components/EditDialog';
import { ConsoleStatus, ConsoleOpenedStatus } from '@/constants';
import MonacoEditor, { IExportRefFunction, IRangeType } from '@/components/Console/MonacoEditor';
type MenuItem = Required<MenuProps>['items'][number];
import MonacoEditor from '@/components/Console/MonacoEditor';
export type IProps = {
className?: string;
@ -116,6 +111,22 @@ function TreeNodeRightClick(props: IProps) {
}
}
},
[OperationColumn.EditTable]: (data) => {
return {
text: i18n('workspace.menu.editTable'),
icon: '\ue602',
handle: () => {
dispatch({
type: 'workspace/setCreateTabIntro',
payload: {
type: CreateTabIntroType.EditorTable,
workspaceTabType: WorkspaceTabType.EditTable,
treeNodeData: data,
},
})
}
}
},
[OperationColumn.EditSource]: (data) => {
return {
text: '编辑数据源',
@ -148,6 +159,7 @@ function TreeNodeRightClick(props: IProps) {
payload: {
...curWorkspaceParams,
extraParams: curWorkspaceParams,
refresh: true,
},
callback: () => {
message.success(i18n('common.text.submittedSuccessfully'))

View File

@ -62,7 +62,8 @@ export enum OperationColumn {
DeleteTable = 'deleteTable',
ExportDDL = 'exportDDL',
EditSource = 'editSource',
Top = 'top'
Top = 'top',
EditTable = 'editTable',
}
export interface ITreeConfigItem {
@ -104,7 +105,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = {
getChildren: (params) => {
return new Promise((r: (value: ITreeNode[]) => void, j) => {
connectionService.getDBList(params).then(res => {
const data: ITreeNode[] = res.map((t:any)=> {
const data: ITreeNode[] = res.map((t: any) => {
return {
key: t.name,
name: t.name,
@ -238,7 +239,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = {
})
},
operationColumn: [
OperationColumn.ExportDDL, OperationColumn.DeleteTable, OperationColumn.Top
OperationColumn.Top, OperationColumn.ExportDDL, OperationColumn.EditTable, OperationColumn.DeleteTable,
],
},

View File

@ -1,25 +1,47 @@
@import '../../../../../styles/var.less';
.workspaceHeader{
.workspaceHeader {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 10px;
flex-shrink: 0;
font-size: 14px;
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
border-bottom: 1px solid var(--color-border-secondary);
font-weight: bold;
height: 36px;
}
.changeBox{
.workspaceHeaderRight{
width: 0px;
flex: 1;
flex-shrink: 0;
overflow: hidden;
}
.workspaceHeaderLeft{
display: flex;
align-items: center;
flex: 1;
}
.refreshBox{
.workspaceHeaderCenter{
flex-shrink: 0;
display: flex;
justify-content: center;
padding: 0px 40px;
}
.databaseTypeIcon{
margin-right: 10px;
font-weight: 400;
color: var(--color-primary);
}
.refreshBox {
flex-shrink: 0;
margin-left: 10px;
overflow: hidden;
height: 18px;
@ -30,52 +52,53 @@
font-weight: normal;
cursor: pointer;
color: var(--color-text-secondary);
&:hover{
&:hover {
color: var(--color-primary);
}
i{
i {
font-size: 14px;
}
.spin{
.spin {
transform: scale(0.6);
}
}
.crumbsItem{
.crumbsItem {
display: flex;
align-items: center;
height: 26px;
cursor: pointer;
padding: 0px 6px;
border-radius: 6px;
.f-single-line();
&:hover{
&:hover {
color: var(--color-primary);
background-color: var(--color-hover-bg);
}
.typeIcon{
.typeIcon {
margin-right: 6px;
}
}
.arrow{
flex-shrink: 0;
font-size: 10px;
margin: 0px 4px;
transform: translateY(1px);
}
.refreshIcon{
.refreshIcon {
margin-left: 20px;
cursor: pointer;
}
.noConnectionModal{
.noConnectionModal {
display: flex;
justify-content: space-between;
.mainText{
.mainText {
}
}

View File

@ -9,7 +9,7 @@ import { IMainPageType } from '@/models/mainPage';
import { Cascader, Spin, Modal, Button, Tag } from 'antd';
import { databaseMap, TreeNodeType } from '@/constants';
import { treeConfig } from '../Tree/treeConfig';
import { useUpdateEffect } from '@/hooks/useUpdateEffect'
import { useUpdateEffect } from '@/hooks/useUpdateEffect';
import styles from './index.less';
interface IProps {
@ -39,20 +39,20 @@ const WorkspaceHeader = memo<IProps>((props) => {
useEffect(() => {
if (curPage !== 'workspace') {
return
return;
}
// 如果没有curConnection默认选第一个
if (!curConnection?.id && connectionList.length) {
connectionChange([connectionList[0].id], [connectionList[0]]);
return
return;
}
// 如果都有的话
if (curConnection?.id && connectionList.length) {
// 如果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) {
connectionChange([connectionList[0].id], [connectionList[0]]);
return
return;
}
// 如果切换了curConnection 导致curWorkspaceParams与curConnection不同
@ -61,9 +61,9 @@ const WorkspaceHeader = memo<IProps>((props) => {
dataSourceId: curConnection.id,
dataSourceName: curConnection.alias,
databaseType: curConnection.type,
})
setCurDBOptions([])
setCurSchemaOptions([])
});
setCurDBOptions([]);
setCurSchemaOptions([]);
}
// 获取database列表
@ -71,104 +71,120 @@ const WorkspaceHeader = memo<IProps>((props) => {
setIsRefresh(false);
}
// connectionList转换成可用的ConnectionOptions
setConnectionOptions(connectionList?.map(t => {
return {
value: t.id,
label: t.alias,
}
}));
}, [connectionList, curConnection, curPage])
setConnectionOptions(
connectionList?.map((t) => {
return {
value: t.id,
label: t.alias,
};
}),
);
}, [connectionList, curConnection, curPage]);
useUpdateEffect(() => {
if (!connectionList.length) {
dispatch({
type: 'workspace/setCurWorkspaceParams',
payload: {}
})
payload: {},
});
dispatch({
type: 'connection/setCurConnection',
payload: {}
})
payload: {},
});
}
}, [connectionList])
}, [connectionList]);
function getDatabaseList(refresh = false) {
setCascaderLoading(true);
if (!curConnection?.id) {
return
return;
}
treeConfig[TreeNodeType.DATA_SOURCE].getChildren?.({
dataSourceId: curConnection.id,
refresh,
extraParams: {
databaseType: curConnection.type,
treeConfig[TreeNodeType.DATA_SOURCE]
.getChildren?.({
dataSourceId: curConnection.id,
dataSourceName: curConnection.name,
}
}).then(res => {
const dbList = res?.map((t) => {
return {
value: t.key,
label: t.name,
}
}) || []
setCurDBOptions(dbList);
// 如果是切换那么就默认取列表的第一个database 如果不是切换那么就取缓存的如果缓存没有还是取列表第一个这里是兜底如果原先他并没有database后来他加了database如果还是取缓存的空就不对了
const databaseName = curWorkspaceParams.dataSourceId !== curConnection?.id ? dbList[0]?.label : curWorkspaceParams.databaseName || dbList[0]?.label
getSchemaList(databaseName, refresh);
}).catch(error => {
setCascaderLoading(false);
})
refresh,
extraParams: {
databaseType: curConnection.type,
dataSourceId: curConnection.id,
dataSourceName: curConnection.name,
},
})
.then((res) => {
const dbList =
res?.map((t) => {
return {
value: t.key,
label: t.name,
};
}) || [];
setCurDBOptions(dbList);
// 如果是切换那么就默认取列表的第一个database 如果不是切换那么就取缓存的如果缓存没有还是取列表第一个这里是兜底如果原先他并没有database后来他加了database如果还是取缓存的空就不对了
const databaseName =
curWorkspaceParams.dataSourceId !== curConnection?.id
? dbList[0]?.label
: curWorkspaceParams.databaseName || dbList[0]?.label;
getSchemaList(databaseName, refresh);
})
.catch((error) => {
setCascaderLoading(false);
});
}
function getSchemaList(databaseName: string | null | undefined, refresh = false) {
if (!curConnection?.id) {
return
return;
}
treeConfig[TreeNodeType.DATABASE].getChildren?.({
dataSourceId: curConnection.id,
databaseName: databaseName,
refresh,
extraParams: {
treeConfig[TreeNodeType.DATABASE]
.getChildren?.({
dataSourceId: curConnection.id,
databaseName: databaseName,
databaseType: curConnection.type,
dataSourceId: curConnection.id,
dataSourceName: curConnection.name,
}
}).then(res => {
const schemaList = res?.map((t) => {
return {
value: t.key,
label: t.name,
}
}) || []
setCurSchemaOptions(schemaList);
const schemaName = curWorkspaceParams.dataSourceId !== curConnection?.id ? schemaList[0]?.label : curWorkspaceParams.schemaName || schemaList[0]?.label
const data: any = {
dataSourceId: curConnection.id,
dataSourceName: curConnection.alias,
databaseType: curConnection.type,
databaseName: databaseName || null,
schemaName: schemaName || null
}
setCurWorkspaceParams(data)
}).catch(() => {
setCurWorkspaceParams({
dataSourceId: curConnection.id,
dataSourceName: curConnection.alias,
databaseType: curConnection.type,
databaseName: databaseName || null,
refresh,
extraParams: {
databaseName: databaseName,
databaseType: curConnection.type,
dataSourceId: curConnection.id,
dataSourceName: curConnection.name,
},
})
}).finally(() => {
setCascaderLoading(false)
})
.then((res) => {
const schemaList =
res?.map((t) => {
return {
value: t.key,
label: t.name,
};
}) || [];
setCurSchemaOptions(schemaList);
const schemaName =
curWorkspaceParams.dataSourceId !== curConnection?.id
? schemaList[0]?.label
: curWorkspaceParams.schemaName || schemaList[0]?.label;
const data: any = {
dataSourceId: curConnection.id,
dataSourceName: curConnection.alias,
databaseType: curConnection.type,
databaseName: databaseName || null,
schemaName: schemaName || null,
};
setCurWorkspaceParams(data);
})
.catch(() => {
setCurWorkspaceParams({
dataSourceId: curConnection.id,
dataSourceName: curConnection.alias,
databaseType: curConnection.type,
databaseName: databaseName || null,
});
})
.finally(() => {
setCascaderLoading(false);
});
}
function setCurWorkspaceParams(payload: IWorkspaceModelType['state']['curWorkspaceParams']) {
if (lodash.isEqual(curWorkspaceParams, payload)) {
return
return;
}
dispatch({
@ -183,21 +199,21 @@ const WorkspaceHeader = memo<IProps>((props) => {
dispatch({
type: 'connection/fetchConnectionList',
payload: {
refresh: true
refresh: true,
},
});
};
// 连接切换
function connectionChange(id: any, data: any) {
connectionList.map(t => {
connectionList.map((t) => {
if (t.id === id[0] && curWorkspaceParams.dataSourceId !== id[0]) {
dispatch({
type: 'connection/setCurConnection',
payload: t
payload: t,
});
}
})
});
}
// 数据库切换
@ -205,12 +221,12 @@ const WorkspaceHeader = memo<IProps>((props) => {
if (selectedOptions[0].label !== curWorkspaceParams.databaseName) {
getSchemaList(selectedOptions[0].label);
}
};
}
// schema切换
function schemaChange(valueArr: any, selectedOptions: any) {
if (selectedOptions[0].label !== curWorkspaceParams.schemaName) {
setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value })
setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value });
}
}
@ -218,89 +234,108 @@ const WorkspaceHeader = memo<IProps>((props) => {
getConnectionList();
}
return <>
{
!!connectionList.length &&
<div className={styles.workspaceHeader}>
<div className={styles.changeBox}>
<Cascader
popupClassName={styles.cascaderPopup}
options={connectionOptions}
onChange={connectionChange}
bordered={false}
defaultValue={[curConnection?.id || '']}
>
<div className={styles.crumbsItem}>
<div className={styles.connectionTag}>
{(curConnection?.id && curConnection?.environment?.shortName) && <Tag color={curConnection?.environment?.color?.toLocaleLowerCase()}>{curConnection?.environment?.shortName}</Tag>}
return (
<>
{!!connectionList.length && (
<div className={styles.workspaceHeader}>
<div className={styles.workspaceHeaderLeft}>
<Cascader
popupClassName={styles.cascaderPopup}
options={connectionOptions}
onChange={connectionChange}
bordered={false}
defaultValue={[curConnection?.id || '']}
>
<div className={styles.crumbsItem}>
<Iconfont className={styles.databaseTypeIcon} code={databaseMap[curWorkspaceParams.databaseType]?.icon} />
<div className={styles.text}>{curWorkspaceParams.dataSourceName}</div>
</div>
<div className={styles.text}>{curWorkspaceParams.dataSourceName}</div>
</Cascader>
<Iconfont className={styles.arrow} code="&#xe641;" />
{!!curDBOptions?.length && (
<Cascader
popupClassName={styles.cascaderPopup}
options={curDBOptions}
onChange={databaseChange}
bordered={false}
defaultValue={[curWorkspaceParams?.databaseName || '']}
>
<div className={styles.crumbsItem}>
<div className={styles.text}>{curWorkspaceParams.databaseName}</div>
</div>
</Cascader>
)}
{!!curSchemaOptions.length && <Iconfont className={styles.arrow} code="&#xe641;" />}
{!!curSchemaOptions.length && (
<Cascader
popupClassName={styles.cascaderPopup}
options={curSchemaOptions}
onChange={schemaChange}
bordered={false}
defaultValue={[curWorkspaceParams?.schemaName || '']}
>
<div className={styles.crumbsItem}>
<div className={styles.text}>{curWorkspaceParams.schemaName}</div>
</div>
</Cascader>
)}
<div className={styles.refreshBox} onClick={handelRefresh}>
{cascaderLoading ? (
<Spin className={styles.spin} />
) : (
<Iconfont className={styles.typeIcon} code="&#xec08;" />
)}
</div>
</Cascader>
<Iconfont className={styles.arrow} code="&#xe641;" />
{
!!curDBOptions?.length &&
<Cascader
popupClassName={styles.cascaderPopup}
options={curDBOptions}
onChange={databaseChange}
bordered={false}
defaultValue={[curWorkspaceParams?.databaseName || '']}
>
<div className={styles.crumbsItem}>
<div className={styles.text}>{curWorkspaceParams.databaseName}</div>
</div>
</Cascader>
}
{
!!curSchemaOptions.length && <Iconfont className={styles.arrow} code="&#xe608;" />
}
{
!!curSchemaOptions.length &&
<Cascader
popupClassName={styles.cascaderPopup}
options={curSchemaOptions}
onChange={schemaChange}
bordered={false}
defaultValue={[curWorkspaceParams?.schemaName || '']}
>
<div className={styles.crumbsItem}>
<div className={styles.text}>{curWorkspaceParams.schemaName}</div>
</div>
</Cascader>
}
<div className={styles.refreshBox} onClick={handelRefresh}>
{cascaderLoading ? <Spin className={styles.spin} /> : <Iconfont className={styles.typeIcon} code='&#xec08;' />}
</div>
<div className={classnames(styles.connectionTag,styles.workspaceHeaderCenter)}>
{curConnection?.id && curConnection?.environment?.shortName && (
<Tag color={curConnection?.environment?.color?.toLocaleLowerCase()}>
{curConnection?.environment?.shortName}
</Tag>
)}
</div>
<div className={styles.workspaceHeaderRight}></div>
</div>
</div >
}
<Modal
open={noConnectionModal}
closeIcon={<></>}
keyboard={false}
maskClosable={false}
title='温馨提示'
footer={[]}
>
<div className={styles.noConnectionModal}>
<div className={styles.mainText}>
)}
<Modal
open={noConnectionModal}
closeIcon={<></>}
keyboard={false}
maskClosable={false}
title="温馨提示"
footer={[]}
>
<div className={styles.noConnectionModal}>
<div className={styles.mainText}></div>
<Button
type="primary"
className={styles.createButton}
onClick={() => {
setNoConnectionModal(false);
dispatch({
type: 'mainPage/updateCurPage',
payload: 'connections',
});
}}
>
</Button>
</div>
<Button type='primary' className={styles.createButton} onClick={() => {
setNoConnectionModal(false);
dispatch({
type: 'mainPage/updateCurPage',
payload: 'connections',
});
}}></Button>
</div>
</Modal>
</>
})
</Modal>
</>
);
});
export default connect(
({ connection, workspace, mainPage }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, mainPage: IMainPageType }) => ({
({
connection,
workspace,
mainPage,
}: {
connection: IConnectionModelType;
workspace: IWorkspaceModelType;
mainPage: IMainPageType;
}) => ({
connectionModel: connection,
workspaceModel: workspace,
mainPageModel: mainPage,

View File

@ -4,7 +4,7 @@
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--color-bg-elevated);
background-color: var(--color-bg-subtle);
padding: 10px 20px 0px;
box-sizing: border-box;
min-width: 200px;

View File

@ -1,18 +1,13 @@
import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'react';
import React, { memo } from 'react';
import classnames from 'classnames';
import i18n from '@/i18n';
import { connect } from 'umi';
import { Cascader, Divider, Input, Dropdown, Button, Spin } from 'antd';
import { Divider, Button } from 'antd';
import Iconfont from '@/components/Iconfont';
import LoadingContent from '@/components/Loading/LoadingContent';
import { IConnectionModelType } from '@/models/connection';
import { IWorkspaceModelType } from '@/models/workspace';
import historyServer from '@/service/history';
import Tree from '../Tree';
import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus, OperationType } from '@/constants';
import { IConsole, ITreeNode, ICreateConsole } from '@/typings';
import { ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType } from '@/constants';
import styles from './index.less';
import { approximateTreeNode, approximateList } from '@/utils';
import historyService from '@/service/history';
import TableList from '../TableList';
import SaveList from '../SaveList';
@ -21,16 +16,12 @@ interface IProps {
className?: string;
workspaceModel: IWorkspaceModelType['state'],
dispatch: any;
tableLoading: boolean;
databaseLoading: boolean;
}
const dvaModel = connect(
({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({
({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({
connectionModel: connection,
workspaceModel: workspace,
tableLoading: loading.effects['workspace/fetchGetCurTableList'],
databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'],
}),
);
@ -38,31 +29,10 @@ const WorkspaceLeft = memo<IProps>(function (props) {
const { className, workspaceModel, dispatch } = props;
const { curWorkspaceParams, openConsoleList } = workspaceModel;
function getConsoleList() {
let p: any = {
pageNo: 1,
pageSize: 999,
orderByDesc: false,
tabOpened: ConsoleOpenedStatus.IS_OPEN,
...curWorkspaceParams,
};
dispatch({
type: 'workspace/fetchGetSavedConsole',
payload: p,
callback: (res: any) => {
dispatch({
type: 'workspace/setOpenConsoleList',
payload: res.data,
})
}
})
}
const addConsole = (params?: ICreateConsole) => {
const addConsole = () => {
const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams
let p = {
name: `new console${openConsoleList?.length || ''}`,
let params = {
name: `new console`,
ddl: '',
dataSourceId: dataSourceId!,
databaseName: databaseName!,
@ -70,29 +40,31 @@ const WorkspaceLeft = memo<IProps>(function (props) {
type: databaseType,
status: ConsoleStatus.DRAFT,
tabOpened: ConsoleOpenedStatus.IS_OPEN,
operationType: OperationType.CONSOLE
operationType: WorkspaceTabType.CONSOLE,
tabType: WorkspaceTabType.CONSOLE,
}
historyService.saveConsole(params || p).then(res => {
historyService.saveConsole(params).then(res => {
dispatch({
type: 'workspace/setCurConsoleId',
payload: res,
});
getConsoleList();
type: 'workspace/setCreateConsoleIntro',
payload: {
id: res,
type: WorkspaceTabType.CONSOLE,
title: params.name,
uniqueData: params,
},
})
})
}
function createConsole() {
addConsole();
}
useEffect(() => {
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 't') {
e.preventDefault();
addConsole();
}
}, false)
}, [])
// useEffect(() => {
// document.addEventListener('keydown', (e) => {
// if ((e.ctrlKey || e.metaKey) && e.key === 't') {
// e.preventDefault();
// addConsole();
// }
// }, false)
// }, [])
return (
<div className={classnames(styles.box, className)}>
@ -100,7 +72,7 @@ const WorkspaceLeft = memo<IProps>(function (props) {
<Divider className={styles.divider} />
<TableList />
<div className={styles.createButtonBox}>
<Button className={styles.createButton} type="primary" onClick={createConsole}>
<Button className={styles.createButton} type="primary" onClick={addConsole}>
<Iconfont code="&#xe63a;" />
{i18n('common.button.createConsole')}
</Button>

View File

@ -1,457 +0,0 @@
import React, { memo, useRef, useEffect, useState } from 'react';
import { connect } from 'umi';
import styles from './index.less';
import classnames from 'classnames';
import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, TreeNodeType, operationTypeConfig, OperationType } from '@/constants';
import { IConsole, ICreateConsole } from '@/typings';
import historyService from '@/service/history';
import sqlService from '@/service/sql';
import Tabs, { IOption } from '@/components/Tabs';
import LoadingContent from '@/components/Loading/LoadingContent';
import ShortcutKey from '@/components/ShortcutKey';
import WorkspaceRightItem from '../WorkspaceRightItem';
import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace';
import { IAIState } from '@/models/ai';
import { handleLocalStorageSavedConsole } from '@/utils';
import { useUpdateEffect } from '@/hooks/useUpdateEffect';
import Tree from 'antd/es/tree/Tree';
import Iconfont from '@/components/Iconfont';
interface IProps {
className?: string;
workspaceModel: IWorkspaceModelState;
aiModel: IAIState;
dispatch: any;
}
const WorkspaceRight = memo<IProps>(function (props) {
const [activeConsoleId, setActiveConsoleId] = useState<number>();
const { className, aiModel, workspaceModel, dispatch } = props;
const { curWorkspaceParams, doubleClickTreeNodeData, openConsoleList, curConsoleId } = workspaceModel;
const openConsoleListRef = useRef(openConsoleList);
useEffect(() => {
if (!doubleClickTreeNodeData) {
return;
}
dispatch({
type: 'workspace/setConsoleList',
payload: [],
})
// 打开视图
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.VIEW) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, tableName, dataSourceId } = extraParams || {};
const callback = (consoleId: number) => {
sqlService.getViewDetail({
dataSourceId: dataSourceId!,
databaseName: databaseName!,
tableName: tableName!,
schemaName,
}).then(res => {
// 更新ddl
const newList = openConsoleListRef.current?.map(t => {
if (t.id === consoleId) {
return {
...t,
ddl: res.ddl
}
}
return t
})
dispatch({
type: 'workspace/setOpenConsoleList',
payload: newList,
});
})
}
const name = doubleClickTreeNodeData.name;
createConsole({
doubleClickTreeNodeData,
name,
callback
});
}
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TRIGGER) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, triggerName, dataSourceId, } = extraParams || {};
const name = doubleClickTreeNodeData.name
const callback = (consoleId: number) => {
sqlService.getTriggerDetail({
dataSourceId: dataSourceId!,
databaseName: databaseName!,
triggerName: triggerName!,
schemaName,
}).then(res => {
// 更新ddl
const newList = openConsoleListRef.current?.map(t => {
if (t.id === consoleId) {
return {
...t,
ddl: res.triggerBody
}
}
return t
})
dispatch({
type: 'workspace/setOpenConsoleList',
payload: newList,
});
})
}
createConsole({
doubleClickTreeNodeData,
name,
callback
});
}
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.PROCEDURE) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, procedureName, dataSourceId } = extraParams || {};
const name = doubleClickTreeNodeData.name
const callback = (consoleId: number) => {
sqlService.getProcedureDetail({
dataSourceId: dataSourceId!,
databaseName: databaseName!,
procedureName: procedureName!,
schemaName,
}).then(res => {
// 更新ddl
const newList = openConsoleListRef.current?.map(t => {
if (t.id === consoleId) {
return {
...t,
ddl: res.procedureBody
}
}
return t
})
dispatch({
type: 'workspace/setOpenConsoleList',
payload: newList,
});
})
}
createConsole({
doubleClickTreeNodeData,
name,
callback
});
}
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.FUNCTION) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, dataSourceId, functionName } = extraParams || {};
const name = doubleClickTreeNodeData.name
const callback = (consoleId: number) => {
sqlService.getFunctionDetail({
dataSourceId: dataSourceId!,
databaseName: databaseName!,
functionName: functionName!,
schemaName,
}).then(res => {
// 更新ddl
const newList = openConsoleListRef.current?.map(t => {
if (t.id === consoleId) {
return {
...t,
ddl: res.functionBody
}
}
return t
})
dispatch({
type: 'workspace/setOpenConsoleList',
payload: newList,
});
})
}
createConsole({
doubleClickTreeNodeData,
name,
callback
});
}
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE && !openConsoleList?.length) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, tableName, } = extraParams || {};
const ddl = `SELECT * FROM ${tableName};\n`;
const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-');
createConsole({
doubleClickTreeNodeData,
name,
ddl
});
}
dispatch({
type: 'workspace/setDoubleClickTreeNodeData',
payload: '',
});
}, [doubleClickTreeNodeData]);
useUpdateEffect(() => {
if (activeConsoleId) {
localStorage.setItem('active-console-id', activeConsoleId.toString())
} else {
localStorage.removeItem('active-console-id')
}
}, [activeConsoleId])
useEffect(() => {
openConsoleListRef.current = openConsoleList;
const newActiveConsoleId = curConsoleId || activeConsoleId || Number(localStorage.getItem('active-console-id') || 0);
// 用完之后就清掉curConsoleId
if (!openConsoleList?.length) {
setActiveConsoleId(undefined);
} else if (!newActiveConsoleId) {
setActiveConsoleId(openConsoleList[0].id);
} else {
// 如果你指定了让我打开哪个那我就打开哪个
if (curConsoleId) {
setActiveConsoleId(curConsoleId);
dispatch({
type: 'workspace/setCurConsoleId',
payload: null,
});
return
}
let flag = false;
openConsoleList?.forEach((t) => {
if (t.id === newActiveConsoleId) {
flag = true;
}
});
if (flag) {
setActiveConsoleId(newActiveConsoleId);
} else {
// 如果发现当前列表里并没有newActiveConsoleId
setActiveConsoleId(openConsoleList?.[openConsoleList?.length - 1].id);
}
}
}, [openConsoleList]);
function createConsole(params: {
doubleClickTreeNodeData: any,
name: string,
callback?: Function,
ddl?: string,
}) {
const { doubleClickTreeNodeData, name, callback, ddl } = params;
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType } = extraParams || {};
let p: any = {
name,
type: databaseType!,
dataSourceId: dataSourceId!,
databaseName: databaseName,
schemaName: schemaName,
dataSourceName: dataSourceName!,
status: ConsoleStatus.DRAFT,
operationType: doubleClickTreeNodeData.treeNodeType,
ddl: ddl || '',
tabOpened: ConsoleOpenedStatus.IS_OPEN,
};
addConsole({
newConsole: p,
callback,
});
}
function getConsoleList(callback?: Function) {
let p: any = {
pageNo: 1,
pageSize: 999,
tabOpened: ConsoleOpenedStatus.IS_OPEN,
...curWorkspaceParams,
};
dispatch({
type: 'workspace/fetchGetSavedConsole',
payload: p,
callback: (res: any) => {
dispatch({
type: 'workspace/setOpenConsoleList',
payload: res.data,
});
callback?.();
},
});
}
function onChange(key: number | string) {
setActiveConsoleId(+key);
}
const onEdit = (action: 'add' | 'remove', key?: number) => {
if (action === 'remove') {
closeWindowTab(key!);
}
if (action === 'add') {
addConsole();
}
};
const addConsole = (params?: {
newConsole?: ICreateConsole;
callback?: Function;
}) => {
const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams;
let p = {
name: `new console${openConsoleList?.length}`,
ddl: '',
dataSourceId: dataSourceId!,
databaseName: databaseName!,
schemaName: schemaName!,
type: databaseType,
status: ConsoleStatus.DRAFT,
tabOpened: ConsoleOpenedStatus.IS_OPEN,
operationType: OperationType.CONSOLE,
};
historyService.saveConsole(params?.newConsole || p).then((res) => {
params?.callback?.(res);
const callback = () => {
dispatch({
type: 'workspace/setCurConsoleId',
payload: res,
});
}
getConsoleList(callback);
});
};
const closeWindowTab = (key: number) => {
let newActiveKey = activeConsoleId;
let lastIndex = -1;
openConsoleList?.forEach((item, i) => {
if (item.id === key) {
lastIndex = i - 1;
}
});
const newPanes = openConsoleList?.filter((item) => item.id !== key) || [];
if (newPanes.length && newActiveKey === key) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].id;
} else {
newActiveKey = newPanes[0].id;
}
}
dispatch({
type: 'workspace/setOpenConsoleList',
payload: newPanes,
});
setActiveConsoleId(newActiveKey);
let p: any = {
id: key,
tabOpened: 'n',
};
const window = openConsoleList?.find((t) => t.id === key);
if (!window?.status) {
return;
}
// if (window!.status === 'DRAFT') {
// historyService.deleteSavedConsole({ id: window!.id });
// } else {
historyService.updateSavedConsole(p).then(() => {
handleLocalStorageSavedConsole(p.id, 'delete');
});
// }
};
function renderEmpty() {
return <div className={styles.ears}><ShortcutKey /></div>;
}
function editableNameOnBlur(t: IOption) {
let p: any = {
id: t.value,
name: t.label
}
historyService.updateSavedConsole(p).then(() => {
getConsoleList();
dispatch({
type: 'workspace/fetchGetSavedConsole',
payload: {
pageNo: 1,
pageSize: 999,
orderByDesc: true,
status: ConsoleStatus.RELEASE,
...curWorkspaceParams,
},
callback: (res: any) => {
dispatch({
type: 'workspace/setConsoleList',
payload: res.data,
})
}
});
});
}
return (
<div className={classnames(styles.box, className)}>
<LoadingContent data={openConsoleList} handleEmpty empty={renderEmpty()}>
<div className={styles.tabBox}>
<Tabs
className={styles.tabs}
onChange={onChange}
onEdit={onEdit as any}
editableName={true}
editableNameOnBlur={editableNameOnBlur}
activeTab={activeConsoleId}
tabs={(openConsoleList || [])?.map((t, i) => {
return {
prefixIcon: operationTypeConfig[t.operationType]?.icon || operationTypeConfig.console.icon,
label: t.name,
value: t.id,
};
})}
/>
</div>
{openConsoleList?.map((t, index) => {
return (
<div
key={t.id}
className={classnames(styles.consoleBox, { [styles.activeConsoleBox]: activeConsoleId === t.id })}
>
<WorkspaceRightItem
isActive={activeConsoleId === t.id}
data={{
initDDL: t.ddl,
databaseName: curWorkspaceParams.databaseName!,
dataSourceId: curWorkspaceParams.dataSourceId!,
type: curWorkspaceParams.databaseType!,
schemaName: curWorkspaceParams?.schemaName!,
consoleId: t.id,
consoleName: t.name,
}}
workspaceModel={workspaceModel}
aiModel={aiModel}
dispatch={dispatch}
/>
</div>
);
})}
</LoadingContent>
</div >
);
});
const dvaModel = connect(({ workspace, ai }: { workspace: IWorkspaceModelType; ai: IAIState }) => ({
workspaceModel: workspace,
aiModel: ai,
}));
export default dvaModel(WorkspaceRight);

View File

@ -1,6 +1,6 @@
@import '../../../../../styles/var.less';
.box {
.workspaceRight {
position: absolute;
top: 0;
left: 0;
@ -15,21 +15,16 @@
}
.tabs {
}
.consoleBox {
position: absolute;
top: 32px;
left: 0;
right: 0;
bottom: 0;
display: none;
height: 100%;
width: 100%;
}
.tabBox {
height: 32px;
background-color: var(--color-bg-base);
border-bottom: 1px solid var(--color-border);
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
}
.activeConsoleBox {
@ -48,7 +43,7 @@
overflow: hidden;
}
.tab-item{
.tab-item {
display: flex;
align-items: center;
}

View File

@ -0,0 +1,513 @@
import React, { memo, useEffect, useState, useMemo } from 'react';
import { connect } from 'umi';
import styles from './index.less';
import classnames from 'classnames';
import { ConsoleOpenedStatus, ConsoleStatus, TreeNodeType } from '@/constants';
import historyService from '@/service/history';
import sqlService from '@/service/sql';
import TabsNew, { ITabItem } from '@/components/TabsNew';
import LoadingContent from '@/components/Loading/LoadingContent';
import ShortcutKey from '@/components/ShortcutKey';
import DatabaseTableEditor from '@/blocks/DatabaseTableEditor';
import SQLExecute from '@/blocks/SQLExecute';
import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace';
import { IAIState } from '@/models/ai';
import { handleLocalStorageSavedConsole } from '@/utils';
import { useUpdateEffect } from '@/hooks/useUpdateEffect';
import { v4 as uuidV4 } from 'uuid';
import { IWorkspaceTab } from '@/typings'
import { WorkspaceTabType, workspaceTabConfig } from '@/constants';
interface IProps {
className?: string;
workspaceModel: IWorkspaceModelState;
aiModel: IAIState;
dispatch: any;
}
const WorkspaceRight = memo<IProps>(function (props) {
const { className, aiModel, workspaceModel, dispatch } = props;
// 活跃的TabID
const [activeConsoleId, setActiveConsoleId] = useState<number | string>();
// 工作台tab列表
const [workspaceTabList, setWorkspaceTabList] = useState<IWorkspaceTab[]>([]);
const { curWorkspaceParams, doubleClickTreeNodeData, createTabIntro, openConsoleList, curConsoleId, createConsoleIntro } = workspaceModel;
// 根据保存的console列表生成tab列表
useEffect(() => {
const newTabList = openConsoleList?.map(t => {
return {
id: t.id,
title: t.name,
type: t.operationType,
uniqueData: t
}
})
setWorkspaceTabList(newTabList || [])
}, [openConsoleList])
useEffect(() => {
if (createConsoleIntro) {
if (workspaceTabList.findIndex(t => t.id === createConsoleIntro.id) === -1) {
setWorkspaceTabList([...workspaceTabList, createConsoleIntro])
}
setActiveConsoleId(createConsoleIntro.id)
}
dispatch({
type: 'workspace/setCreateConsoleIntro',
payload: undefined,
})
}, [createConsoleIntro])
// 监听编辑表事件
useEffect(() => {
if (createTabIntro) {
// 如果已经打开了这个表的编辑页面,那么就切换到这个页面
const flag = workspaceTabList?.find(t => t.uniqueData?.tableName === createTabIntro.treeNodeData.name);
if (flag) {
setActiveConsoleId(flag.id);
return
}
const id = uuidV4();
const newData = {
id,
type: createTabIntro.workspaceTabType,
title: `edit-${createTabIntro.treeNodeData.name}`,
uniqueData: {
tableName: createTabIntro.treeNodeData.name,
}
}
setWorkspaceTabList([...workspaceTabList, newData])
setActiveConsoleId(id);
// 用完之后就清掉createTabIntro
dispatch({
type: 'workspace/setCreateTabIntro',
payload: null,
})
}
}, [createTabIntro])
// 监听双击树节点事件 生成console
useEffect(() => {
if (!doubleClickTreeNodeData) {
return;
}
// 打开视图
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.VIEW) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, tableName, dataSourceId } = extraParams || {};
const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => {
sqlService.getViewDetail({
dataSourceId: dataSourceId!,
databaseName: databaseName!,
tableName: tableName!,
schemaName,
}).then(res => {
// 更新ddl
const newList = workspaceTabList.map(t => {
if (t.id === consoleId) {
return {
...t,
uniqueData: {
...t.uniqueData,
ddl: res.ddl
}
}
}
return t
})
setWorkspaceTabList(newList || [])
})
}
const name = doubleClickTreeNodeData.name;
createConsole({
doubleClickTreeNodeData,
workSpaceTabType: WorkspaceTabType.VIEW,
name,
callback
});
}
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TRIGGER) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, triggerName, dataSourceId } = extraParams || {};
const name = doubleClickTreeNodeData.name
const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => {
sqlService.getTriggerDetail({
dataSourceId: dataSourceId!,
databaseName: databaseName!,
triggerName: triggerName!,
schemaName,
}).then(res => {
// 更新ddl
const newList = workspaceTabList.map(t => {
if (t.id === consoleId) {
return {
...t,
uniqueData: {
...t.uniqueData,
ddl: res.triggerBody
}
}
}
return t
})
setWorkspaceTabList(newList || [])
})
}
createConsole({
doubleClickTreeNodeData,
workSpaceTabType: WorkspaceTabType.TRIGGER,
name,
callback
});
}
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.PROCEDURE) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, procedureName, dataSourceId } = extraParams || {};
const name = doubleClickTreeNodeData.name
const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => {
sqlService.getProcedureDetail({
dataSourceId: dataSourceId!,
databaseName: databaseName!,
procedureName: procedureName!,
schemaName,
}).then(res => {
// 更新ddl
const newList = workspaceTabList.map(t => {
if (t.id === consoleId) {
return {
...t,
uniqueData: {
...t.uniqueData,
ddl: res.procedureBody
}
}
}
return t
})
setWorkspaceTabList(newList || [])
})
}
createConsole({
doubleClickTreeNodeData,
workSpaceTabType: WorkspaceTabType.PROCEDURE,
name,
callback
});
}
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.FUNCTION) {
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, dataSourceId, functionName } = extraParams || {};
const name = doubleClickTreeNodeData.name
const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => {
sqlService.getFunctionDetail({
dataSourceId: dataSourceId!,
databaseName: databaseName!,
functionName: functionName!,
schemaName,
}).then(res => {
// 更新ddl
const newList = workspaceTabList?.map(t => {
if (t.id === consoleId) {
return {
...t,
uniqueData: {
...t.uniqueData,
ddl: res.functionBody
}
}
}
return t
})
setWorkspaceTabList(newList || [])
})
}
createConsole({
doubleClickTreeNodeData,
workSpaceTabType: WorkspaceTabType.FUNCTION,
name,
callback
});
}
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) {
// 如果workspaceTabList没有可以添加select * from table的地方那么才需要创建
const flag = workspaceTabList.some(t => [WorkspaceTabType.CONSOLE, WorkspaceTabType.FUNCTION, WorkspaceTabType.PROCEDURE, WorkspaceTabType.TRIGGER, WorkspaceTabType.VIEW].includes(t.type))
if (flag) {
return
}
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, tableName, } = extraParams || {};
const ddl = `SELECT * FROM ${tableName};\n`;
const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-');
createConsole({
doubleClickTreeNodeData,
workSpaceTabType: WorkspaceTabType.CONSOLE,
name,
ddl
});
}
dispatch({
type: 'workspace/setDoubleClickTreeNodeData',
payload: '',
});
}, [doubleClickTreeNodeData]);
useUpdateEffect(() => {
if (activeConsoleId) {
localStorage.setItem('active-console-id', activeConsoleId.toString())
} else {
localStorage.removeItem('active-console-id')
}
}, [activeConsoleId])
// useEffect(() => {
// openConsoleListRef.current = openConsoleList;
// const newActiveConsoleId = curConsoleId || activeConsoleId || Number(localStorage.getItem('active-console-id') || 0);
// // 用完之后就清掉curConsoleId
// if (!openConsoleList?.length) {
// setActiveConsoleId(undefined);
// } else if (!newActiveConsoleId) {
// setActiveConsoleId(openConsoleList[0].id);
// } else {
// // 如果你指定了让我打开哪个那我就打开哪个
// if (curConsoleId) {
// setActiveConsoleId(curConsoleId);
// dispatch({
// type: 'workspace/setCurConsoleId',
// payload: null,
// });
// return
// }
// let flag = false;
// openConsoleList?.forEach((t) => {
// if (t.id === newActiveConsoleId) {
// flag = true;
// }
// });
// if (flag) {
// setActiveConsoleId(newActiveConsoleId);
// } else {
// // 如果发现当前列表里并没有newActiveConsoleId
// setActiveConsoleId(openConsoleList?.[openConsoleList?.length - 1].id);
// }
// }
// }, [openConsoleList]);
function createConsole(params: {
doubleClickTreeNodeData: any,
workSpaceTabType: WorkspaceTabType,
name: string,
callback?: Function,
ddl?: string,
}) {
const { doubleClickTreeNodeData, workSpaceTabType, name, callback, ddl } = params;
const { extraParams } = doubleClickTreeNodeData;
const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType } = extraParams || {};
let newConsole: any = {
name,
type: databaseType!,
dataSourceId: dataSourceId!,
databaseName: databaseName,
schemaName: schemaName,
dataSourceName: dataSourceName!,
status: ConsoleStatus.DRAFT,
operationType: workSpaceTabType,
ddl: ddl || '',
tabOpened: ConsoleOpenedStatus.IS_OPEN,
};
historyService.saveConsole(newConsole).then(res => {
const newList = [...workspaceTabList, {
id: res,
title: newConsole.name,
type: workSpaceTabType,
uniqueData: newConsole
}]
setWorkspaceTabList(newList)
callback?.(res, newList);
setActiveConsoleId(res);
});
}
function getConsoleList(callback?: Function) {
let p: any = {
pageNo: 1,
pageSize: 999,
tabOpened: ConsoleOpenedStatus.IS_OPEN,
...curWorkspaceParams,
};
dispatch({
type: 'workspace/fetchGetSavedConsole',
payload: p,
callback: (res: any) => {
dispatch({
type: 'workspace/setOpenConsoleList',
payload: res.data,
});
callback?.();
},
});
}
// 切换tab
function onTabChange(key: string | number | undefined) {
setActiveConsoleId(key);
}
// 删除 新增tab
const onEdit = (action: 'add' | 'remove', data: ITabItem) => {
if (action === 'remove') {
setWorkspaceTabList(workspaceTabList.filter(t => t.id !== data.key));
const editData = workspaceTabList?.find(t => t.id === data.key);
if (editData?.type !== WorkspaceTabType.EditTable) {
closeWindowTab(data.key as number);
}
}
if (action === 'add') {
addConsole();
}
};
const addConsole = () => {
const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams;
let newConsole = {
name: `new console`,
ddl: '',
dataSourceId: dataSourceId!,
databaseName: databaseName!,
schemaName: schemaName!,
type: databaseType,
status: ConsoleStatus.DRAFT,
tabOpened: ConsoleOpenedStatus.IS_OPEN,
operationType: WorkspaceTabType.CONSOLE,
};
historyService.saveConsole(newConsole).then((res) => {
const newList = [...workspaceTabList, {
id: res,
title: newConsole.name,
type: newConsole.operationType,
uniqueData: newConsole
}]
setWorkspaceTabList(newList)
setActiveConsoleId(res);
});
};
const closeWindowTab = (key: number) => {
let p: any = {
id: key,
tabOpened: 'n',
};
// 这行干嘛的TODO:
// const window = openConsoleList?.find((t) => t.id === key);
// if (!window?.status) {
// return;
// }
historyService.updateSavedConsole(p).then(() => {
handleLocalStorageSavedConsole(p.id, 'delete');
});
};
function renderEmpty() {
return <div className={styles.ears}><ShortcutKey /></div>;
}
function editableNameOnBlur(t: ITabItem) {
let p: any = {
id: t.key,
name: t.label
}
historyService.updateSavedConsole(p).then(() => {
getConsoleList();
dispatch({
type: 'workspace/fetchGetSavedConsole',
payload: {
pageNo: 1,
pageSize: 999,
orderByDesc: true,
status: ConsoleStatus.RELEASE,
...curWorkspaceParams,
},
callback: (res: any) => {
dispatch({
type: 'workspace/setConsoleList',
payload: res.data,
})
}
});
});
}
const tabsList = useMemo(() => {
return workspaceTabList.map(t => {
const { uniqueData } = t;
return {
prefixIcon: workspaceTabConfig[t.type]?.icon,
label: t.title,
key: t.id,
// 这里还缺一个参数 是否可编辑tab名称, 编辑表不可编辑名称 TODO:
children: <>
{
[WorkspaceTabType.CONSOLE, WorkspaceTabType.FUNCTION, WorkspaceTabType.PROCEDURE, WorkspaceTabType.TRIGGER, WorkspaceTabType.VIEW].includes(t.type) && <SQLExecute
isActive={activeConsoleId === t.id}
data={{
initDDL: uniqueData?.ddl,
databaseName: curWorkspaceParams.databaseName!,
dataSourceId: curWorkspaceParams.dataSourceId!,
type: curWorkspaceParams.databaseType!,
schemaName: curWorkspaceParams?.schemaName!,
consoleId: t.id as number,
consoleName: uniqueData.name,
}}
/>
}
{
t.type === WorkspaceTabType.EditTable && <DatabaseTableEditor
dataSourceId={curWorkspaceParams.dataSourceId}
databaseName={curWorkspaceParams.databaseName!}
schemaName={curWorkspaceParams?.schemaName!}
tableName={uniqueData.tableName}
/>
}
</>
};
})
}, [workspaceTabList, activeConsoleId, curWorkspaceParams])
return (
<div className={classnames(styles.workspaceRight, className)}>
<LoadingContent data={workspaceTabList} handleEmpty empty={renderEmpty()}>
<div className={styles.tabBox}>
<TabsNew
className={styles.tabs}
onChange={onTabChange}
onEdit={onEdit as any}
editableName={true}
activeKey={activeConsoleId}
editableNameOnBlur={editableNameOnBlur}
items={tabsList}
/>
</div>
</LoadingContent>
</div >
);
});
const dvaModel = connect(({ workspace, ai }: { workspace: IWorkspaceModelType; ai: IAIState }) => ({
workspaceModel: workspace,
aiModel: ai,
}));
export default dvaModel(WorkspaceRight);

View File

@ -3,7 +3,7 @@ import { connect } from 'umi';
import styles from './index.less';
import DraggableContainer from '@/components/DraggableContainer';
import WorkspaceLeft from './components/WorkspaceLeft';
import WorkspaceRight from './components/WorkspaceRight';
import WorkspaceRightNew from './components/WorkspaceRightNew';
import WorkspaceHeader from './components/WorkspaceHeader';
import { IConnectionModelType } from '@/models/connection';
import { IWorkspaceModelType } from '@/models/workspace';
@ -27,20 +27,13 @@ const dvaModel = connect(
}),
);
interface Option {
value: string;
label: string;
children?: Option[];
}
const workspace = memo<IProps>((props) => {
const workspacePage = memo<IProps>((props) => {
const draggableRef = useRef<any>();
const { workspaceModel, connectionModel, dispatch, pageLoading } = props;
const { curConnection } = connectionModel;
const { curWorkspaceParams } = workspaceModel;
const [loading, setLoading] = useState(true);
const isReady = curWorkspaceParams?.dataSourceId && ((curWorkspaceParams?.databaseName || curWorkspaceParams?.schemaName) || (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName == null))
useEffect(() => {
if (pageLoading === true) {
setLoading(true);
@ -107,7 +100,7 @@ const workspace = memo<IProps>((props) => {
<WorkspaceLeft />
</div>
<div className={styles.boxRight}>
<WorkspaceRight />
<WorkspaceRightNew />
</div>
</DraggableContainer>
</LoadingContent >
@ -115,4 +108,4 @@ const workspace = memo<IProps>((props) => {
);
});
export default dvaModel(workspace)
export default dvaModel(workspacePage)

View File

@ -1,5 +1,5 @@
import createRequest from './base';
import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines } from '@/typings';
import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines, IDatabaseFieldType, IEditTableInfo } from '@/typings';
import { DatabaseTypeCode } from '@/constants';
import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable';
@ -174,12 +174,43 @@ const getProcedureDetail = createRequest<{
}, { procedureBody: string }>('/api/rdb/procedure/detail', { method: 'get' });
/** 格式化sql */
const sqlFormat = createRequest<{
const sqlFormat = createRequest<{
sql: string;
dbType: DatabaseTypeCode;
}, string>('/api/sql/format', { method: 'get' });
/** 数据库支持的数据类型 */
const getDatabaseFieldTypeList = createRequest<{
dataSourceId: number;
databaseName: string;
}, IDatabaseFieldType[]>('/api/rdb/table/type_list', { method: 'get' });
/** 数据库支持的数据类型 */
const getTableDetails = createRequest<{
dataSourceId: number;
databaseName: string;
schemaName?: string;
tableName: string;
refresh: boolean;
}, IEditTableInfo>('/api/rdb/table/query', { method: 'get' });
export interface IModifyTableSqlParams {
dataSourceId: number;
databaseName: string;
schemaName?: string;
tableName?: string;
oldTable?: string;
newTable: string;
refresh: boolean;
}
/** 数据库支持的数据类型 */
const getModifyTableSql = createRequest<IModifyTableSqlParams, string>('/api/rdb/table/modify/sql', { method: 'post' });
export default {
getModifyTableSql,
getTableDetails,
getDatabaseFieldTypeList,
sqlFormat,
getTriggerDetail,
getProcedureDetail,
@ -208,3 +239,5 @@ export default {
getDMLCount,
// exportResultTable
};

View File

@ -1,19 +1,34 @@
:root {
:global {
// There is some animation when switching the theme color causing a delay in switching background
.ant-input,
.ant-input-password {
transition: all 0.2s, background-color 0s;
}
.ant-btn {
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), background-color 0s;
}
.ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), background-color 0s;
}
.ant-modal-header {
border-bottom: 0px;
}
.ant-modal-content {
background-color: var(--color-bg-elevated) !important;
.ant-modal-confirm-title {
color: var(--color-text) !important;
}
// .ant-modal-content {
// background-color: var(--color-bg-elevated) !important;
// .ant-modal-confirm-title {
// color: var(--color-text) !important;
// }
// .ant-modal-confirm-content {
// color: var(--color-text) !important;
// }
// }
.ant-modal-confirm-content {
color: var(--color-text) !important;
}
}
.ant-modal-footer {
border-top: 0px;
padding: 8px;
@ -42,5 +57,10 @@
// top: 10px !important;
// }
// }
.ant-cascader-dropdown .ant-cascader-menu {
height: auto;
max-height: 80vh;
}
}
}

View File

@ -17,29 +17,29 @@ input:-webkit-autofill {
caret-color: var(--color-text); // 光标的颜色
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
// ::-webkit-scrollbar {
// width: 6px;
// height: 6px;
// }
::-webkit-scrollbar-thumb {
background-color: var(--color-fill);
border-radius: 3px;
}
// ::-webkit-scrollbar-thumb {
// background-color: var(--color-fill);
// border-radius: 3px;
// }
::-webkit-scrollbar-button {
width: 0;
height: 0;
background: transparent;
}
// ::-webkit-scrollbar-button {
// width: 0;
// height: 0;
// background: transparent;
// }
::-webkit-scrollbar-track {
background: transparent;
}
// ::-webkit-scrollbar-track {
// background: transparent;
// }
::-webkit-scrollbar-corner {
background: transparent;
}
// ::-webkit-scrollbar-corner {
// background: transparent;
// }
html,
body,
@ -133,9 +133,8 @@ li {
list-style: none;
}
// 覆盖antd 的一些样式
button{
button {
box-shadow: none !important;
}

View File

@ -305,7 +305,7 @@ html[primary-color='polar-blue'] {
--lime-9: #e4f88b;
--lime9: #e4f88b;
--lime-10: #f0fab5;
--lime10: #f0fab5;
--lime10: #1c2128;
--color-text: rgba(255, 255, 255, 0.85);
--color-text-secondary: rgba(255, 255, 255, 0.65);
--color-text-tertiary: rgba(255, 255, 255, 0.45);

View File

@ -44,7 +44,8 @@ const antDarkTheme = {
colorBgBase: '#0a0b0c',
colorHoverBg: 'hsla(0, 0%, 100%, 0.03)',
colorBgContainer: '#0a0b0c',
colorBgElevated: '#131418',
colorBgSubtle: '#131418',
colorBgElevated: '#0a0b0c',
colorBorder: 'rgba(54, 55, 58,0.4)',
colorBorderSecondary: 'rgba(54, 55, 58,0.4)',
},

View File

@ -41,13 +41,14 @@ const antdLightTheme = {
token: {
...commonToken,
colorTextBase: 'rgb(241, 241, 244)',
colorBgBase: 'rgb(28, 33, 40)',
colorBgBase: '#1c2128',
colorHoverBg: 'hsla(0, 0%, 100%, 0.03)',
colorBgContainer: 'rgb(28, 33, 40)',
colorBgElevated: 'rgb(34, 39, 46)',
colorBgContainer: '#1c2128',
colorBgSubtle: '#22272e',
colorBgElevated: '#1c2128',
colorBorder: 'rgba(55, 62, 71, 0.4)',
colorBorderSecondary: 'rgba(55, 62, 71, 0.4)',
controlItemBgActive: 'rgba(241, 241, 244, 0.08);',
controlItemBgActive: 'rgba(241, 241, 244, 0.08)',
// ...commonToken,
// colorText: "rgb(241, 241, 244)",
// colorBgBase: '#191a23',

View File

@ -44,7 +44,8 @@ const antdLightTheme = {
colorBgBase: '#fff',
colorHoverBg: 'rgba(0, 0, 0, 0.03)',
colorBgContainer: '#fff',
colorBgElevated: '#f6f8fa',
colorBgSubtle: '#f6f8fa',
colorBgElevated: '#fff',
colorBorder: 'rgba(211, 211, 212, 0.4)',
colorBorderSecondary: 'rgba(211, 211, 212, 0.4)',
},

View File

@ -1,3 +1,3 @@
html[theme='light'] {
--custom-color-icon: #383D4B;
--custom-color-icon: #383d4b;
}

View File

@ -1,4 +1,4 @@
import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, OperationType } from '@/constants';
import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, WorkspaceTabType } from '@/constants';
export interface IPageResponse<T> {
data: T[];
@ -27,7 +27,7 @@ export interface IConsole {
status: ConsoleStatus; // 控制台状态
connectable: boolean; // 是否可连接
tabOpened?: ConsoleOpenedStatus; // 控制台tab是否打开
operationType: OperationType; // 操作类型
operationType: WorkspaceTabType; // 操作类型
}
export interface Option {

View File

@ -34,3 +34,8 @@ export interface IResultConfig {
total: number | string;
hasNextPage: boolean;
}
/** 不同数据库支持的列字段类型*/
export interface IDatabaseFieldType {
typeName: string;
}

View File

@ -0,0 +1,60 @@
import { IndexesType } from '@/constants';
// 编辑表时表的基础数据
export interface IBaseInfo {
name: string;
comment?: string;
}
// 编辑表时列的数据结构
export interface IColumnItem {
key?: string; // 列的key 前端自己给的
name: string | null; // 列名
columnType: string | null; // 列的类型 比如 varchar(100) ,double(10,6)
columnSize: number | null; // 列的长度
nullable: number | null; // 是否为空
primaryKey: boolean | null; // 是否主键
defaultValue: string | null; // 默认值
dataType: string | null; // 数据类型
autoIncrement: boolean | null; // 是否自增
numericPrecision: number | null; // 数字精度
numericScale: number | null; // 数字比例
characterMaximumLength: number | null; // 字符串最大长度
comment: string | null; // 注释
}
export interface IIndexIncludeColumnItem {
key?: string; // 列的key 前端自己给的
ascOrDesc: string | null; // 升序还是降序
cardinality: number | null; // 基数
collation: string | null; // 排序规则
columnName: string | null; // 列名
comment: string | null; // 注释
databaseName: string | null; // 数据库名
filterCondition: string | null; // 过滤条件
indexName: string | null; // 索引名
indexQualifier: string | null; // 索引限定符
nonUnique: boolean | null; // 是否唯一
ordinalPosition: number | null; // 位置
schemaName: string | null; // 模式名
tableName: string | null; // 表名
type: string | null; // 类型
pages: number | null; // 页数
prefixLength: number | null; //
}
// 编辑表时索引的数据结构
export interface IIndexItem {
key?: string;
name: string | null;
columns: string | null;
comment?: string | null;
type: IndexesType | null;
columnList: IIndexIncludeColumnItem[];
}
// 编辑表时整体的数据结构
export interface IEditTableInfo extends IBaseInfo {
columnList: IColumnItem[];
indexList: IIndexItem[];
}

View File

@ -5,5 +5,7 @@ export * from './database';
export * from './main';
export * from './theme';
export * from './tree';
export * from './setting'
export * from './team'
export * from './setting';
export * from './team';
export * from './workspace';
export * from './editTable';

View File

@ -0,0 +1,17 @@
import { CreateTabIntroType, WorkspaceTabType } from '@/constants';
import { ITreeNode } from '@/typings';
export interface ICreateTabIntro {
type: CreateTabIntroType;
workspaceTabType: WorkspaceTabType;
treeNodeData: ITreeNode;
}
export interface IWorkspaceTab {
id: number | string; // Tab的id
type: WorkspaceTabType; // 工作区tab的类型
title: string; // 工作区tab的名称
uniqueData?: any;
}

View File

@ -1532,6 +1532,14 @@
"@dnd-kit/utilities" "^3.2.1"
tslib "^2.0.0"
"@dnd-kit/modifiers@^6.0.1":
version "6.0.1"
resolved "https://registry.npmmirror.com/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz#9e39b25fd6e323659604cc74488fe044d33188c8"
integrity sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==
dependencies:
"@dnd-kit/utilities" "^3.2.1"
tslib "^2.0.0"
"@dnd-kit/sortable@^7.0.2":
version "7.0.2"
resolved "https://registry.npmmirror.com/@dnd-kit/sortable/-/sortable-7.0.2.tgz#791d550872457f3f3c843e00d159b640f982011c"
@ -1547,21 +1555,6 @@
dependencies:
tslib "^2.0.0"
"@electron/get@^2.0.0":
version "2.0.2"
resolved "https://registry.npmmirror.com/@electron/get/-/get-2.0.2.tgz#ae2a967b22075e9c25aaf00d5941cd79c21efd7e"
integrity sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==
dependencies:
debug "^4.1.1"
env-paths "^2.2.0"
fs-extra "^8.1.0"
got "^11.8.5"
progress "^2.0.3"
semver "^6.2.0"
sumchecker "^3.0.1"
optionalDependencies:
global-agent "^3.0.0"
"@electron/universal@1.2.1":
version "1.2.1"
resolved "https://registry.npmmirror.com/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339"
@ -2108,11 +2101,6 @@
resolved "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
"@sindresorhus/is@^4.0.0":
version "4.6.0"
resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
"@stylelint/postcss-css-in-js@^0.38.0":
version "0.38.0"
resolved "https://registry.npmmirror.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.38.0.tgz#eabb061df932744db766f11a153ae1c465b6263c"
@ -2212,13 +2200,6 @@
deepmerge "^4.2.2"
svgo "^2.8.0"
"@szmarczak/http-timer@^4.0.5":
version "4.0.6"
resolved "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807"
integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==
dependencies:
defer-to-connect "^2.0.0"
"@tanstack/match-sorter-utils@^8.7.0":
version "8.8.4"
resolved "https://registry.npmmirror.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457"
@ -2291,16 +2272,6 @@
dependencies:
"@babel/types" "^7.20.7"
"@types/cacheable-request@^6.0.1":
version "6.0.3"
resolved "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183"
integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==
dependencies:
"@types/http-cache-semantics" "*"
"@types/keyv" "^3.1.4"
"@types/node" "*"
"@types/responselike" "^1.0.0"
"@types/classnames@^2.2.9":
version "2.3.1"
resolved "https://registry.npmmirror.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd"
@ -2360,11 +2331,6 @@
resolved "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==
"@types/http-cache-semantics@*":
version "4.0.1"
resolved "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812"
integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==
"@types/invariant@^2.2.31":
version "2.2.35"
resolved "https://registry.npmmirror.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be"
@ -2399,13 +2365,6 @@
resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
"@types/keyv@^3.1.4":
version "3.1.4"
resolved "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==
dependencies:
"@types/node" "*"
"@types/lodash@^4.14.195":
version "4.14.195"
resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
@ -2426,11 +2385,6 @@
resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9"
integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==
"@types/node@^16.11.26":
version "16.18.39"
resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.39.tgz#aa39a1a87a40ef6098ee69689a1acb0c1b034832"
integrity sha512-8q9ZexmdYYyc5/cfujaXb4YOucpQxAV4RMG0himLyDUOEr8Mr79VrqsFI+cQ2M2h89YIuy95lbxuYjxT4Hk4kQ==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@ -2465,13 +2419,6 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/responselike@^1.0.0":
version "1.0.0"
resolved "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
dependencies:
"@types/node" "*"
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
@ -2521,13 +2468,6 @@
dependencies:
"@types/yargs-parser" "*"
"@types/yauzl@^2.9.1":
version "2.10.0"
resolved "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@5.48.1":
version "5.48.1"
resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.1.tgz#deee67e399f2cb6b4608c935777110e509d8018c"
@ -3592,11 +3532,6 @@ boolbase@^1.0.0:
resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
boolean@^3.0.1:
version "3.2.0"
resolved "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b"
integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
bplist-parser@^0.2.0:
version "0.2.0"
resolved "https://registry.npmmirror.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e"
@ -3722,11 +3657,6 @@ buffer-alloc@^1.2.0:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
buffer-equal@1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
@ -3807,24 +3737,6 @@ bundle-name@^3.0.0:
dependencies:
run-applescript "^5.0.0"
cacheable-lookup@^5.0.3:
version "5.0.4"
resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
cacheable-request@^7.0.2:
version "7.0.4"
resolved "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817"
integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==
dependencies:
clone-response "^1.0.2"
get-stream "^5.1.0"
http-cache-semantics "^4.0.0"
keyv "^4.0.0"
lowercase-keys "^2.0.0"
normalize-url "^6.0.1"
responselike "^2.0.0"
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@ -3969,13 +3881,6 @@ cliui@^8.0.1:
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
clone-response@^1.0.2:
version "1.0.3"
resolved "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3"
integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==
dependencies:
mimic-response "^1.0.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -4386,13 +4291,6 @@ decode-uri-component@^0.2.0:
resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
decompress-response@^6.0.0:
version "6.0.0"
resolved "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
dependencies:
mimic-response "^3.1.0"
deepmerge@^4.2.2:
version "4.3.1"
resolved "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
@ -4416,11 +4314,6 @@ default-browser@^4.0.0:
execa "^7.1.1"
titleize "^3.0.0"
defer-to-connect@^2.0.0:
version "2.0.1"
resolved "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
define-lazy-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
@ -4767,15 +4660,6 @@ electron-to-chromium@^1.4.431:
resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.464.tgz#2f94bad78dff34e527aacbfc5d0b1a33cf046507"
integrity sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA==
electron@^22.3.0:
version "22.3.18"
resolved "https://registry.npmmirror.com/electron/-/electron-22.3.18.tgz#5ee55633b3912fec9df6d8f039acf2c016274cfc"
integrity sha512-JgjB966ghTBszAX/GgVgDY/2CktWCjTZWGJI0WISRHDudBZ8/WPkI/hIjsMiLQLe0wSTk6S+WHOYbIqyw0I/sg==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^16.11.26"
extract-zip "^2.0.1"
elliptic@^6.5.3:
version "6.5.4"
resolved "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@ -4806,7 +4690,7 @@ encoding@^0.1.11:
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@ -4831,11 +4715,6 @@ entities@^4.4.0:
resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
errno@^0.1.1:
version "0.1.8"
resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
@ -4956,11 +4835,6 @@ es5-imcompatible-versions@^0.1.78:
resolved "https://registry.npmmirror.com/es5-imcompatible-versions/-/es5-imcompatible-versions-0.1.86.tgz#e9583ad3a7a93c1b13835fb804a3bbd05e30a662"
integrity sha512-Lbrsn5bCL4iVMBdundiFVNIKlnnoBiIMrjtLRe1Snt92s60WHotw83S2ijp5ioqe6pDil3iBPY634VDwBcb1rg==
es6-error@^4.1.1:
version "4.1.1"
resolved "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
es6-iterator@^2.0.3:
version "2.0.3"
resolved "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
@ -5016,11 +4890,6 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-plugin-jest@27.2.1:
version "27.2.1"
resolved "https://registry.npmmirror.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz#b85b4adf41c682ea29f1f01c8b11ccc39b5c672c"
@ -5169,17 +5038,6 @@ ext@^1.1.2:
dependencies:
type "^2.7.2"
extract-zip@^2.0.1:
version "2.0.1"
resolved "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
dependencies:
debug "^4.1.1"
get-stream "^5.1.0"
yauzl "^2.10.0"
optionalDependencies:
"@types/yauzl" "^2.9.1"
extsprintf@^1.2.0:
version "1.4.1"
resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
@ -5247,13 +5105,6 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
dependencies:
pend "~1.2.0"
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
version "3.2.0"
resolved "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
@ -5362,15 +5213,6 @@ fs-extra@^10.0.0, fs-extra@^10.1.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^9.0.0, fs-extra@^9.0.1:
version "9.1.0"
resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
@ -5458,13 +5300,6 @@ get-stdin@^9.0.0:
resolved "https://registry.npmmirror.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575"
integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
dependencies:
pump "^3.0.0"
get-stream@^6.0.0, get-stream@^6.0.1:
version "6.0.1"
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
@ -5528,18 +5363,6 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
once "^1.3.0"
path-is-absolute "^1.0.0"
global-agent@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6"
integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==
dependencies:
boolean "^3.0.1"
es6-error "^4.1.1"
matcher "^3.0.0"
roarr "^2.15.3"
semver "^7.3.2"
serialize-error "^7.0.1"
global@^4.3.2:
version "4.4.0"
resolved "https://registry.npmmirror.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
@ -5553,7 +5376,7 @@ globals@^11.1.0:
resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globalthis@^1.0.1, globalthis@^1.0.3:
globalthis@^1.0.3:
version "1.0.3"
resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
@ -5590,23 +5413,6 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
got@^11.8.5:
version "11.8.6"
resolved "https://registry.npmmirror.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a"
integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==
dependencies:
"@sindresorhus/is" "^4.0.0"
"@szmarczak/http-timer" "^4.0.5"
"@types/cacheable-request" "^6.0.1"
"@types/responselike" "^1.0.0"
cacheable-lookup "^5.0.3"
cacheable-request "^7.0.2"
decompress-response "^6.0.0"
http2-wrapper "^1.0.0-beta.5.2"
lowercase-keys "^2.0.0"
p-cancelable "^2.0.0"
responselike "^2.0.0"
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.11"
resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
@ -5779,11 +5585,6 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
http-cache-semantics@^4.0.0:
version "4.1.1"
resolved "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
http-deceiver@^1.2.7:
version "1.2.7"
resolved "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
@ -5798,14 +5599,6 @@ http-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
http2-wrapper@^1.0.0-beta.5.2:
version "1.0.3"
resolved "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==
dependencies:
quick-lru "^5.1.1"
resolve-alpn "^1.0.0"
https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@ -6438,11 +6231,6 @@ jsesc@~0.5.0:
resolved "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
@ -6453,11 +6241,6 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-stringify-safe@^5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
json2mq@^0.2.0:
version "0.2.0"
resolved "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a"
@ -6470,13 +6253,6 @@ json5@^2.1.2, json5@^2.2.0, json5@^2.2.2:
resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
optionalDependencies:
graceful-fs "^4.1.6"
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
@ -6506,13 +6282,6 @@ keyboardevents-areequal@^0.2.1:
resolved "https://registry.npmmirror.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194"
integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==
keyv@^4.0.0:
version "4.5.3"
resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25"
integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==
dependencies:
json-buffer "3.0.1"
kolorist@^1.6.0:
version "1.8.0"
resolved "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
@ -6673,11 +6442,6 @@ lower-case@^2.0.2:
dependencies:
tslib "^2.0.3"
lowercase-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@ -6712,13 +6476,6 @@ markdown-it-link-attributes@^4.0.1:
resolved "https://registry.npmmirror.com/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz#25751f2cf74fd91f0a35ba7b3247fa45f2056d88"
integrity sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ==
matcher@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
dependencies:
escape-string-regexp "^4.0.0"
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@ -6798,16 +6555,6 @@ mimic-fn@^4.0.0:
resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
mimic-response@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
mimic-response@^3.1.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.npmmirror.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
@ -7055,11 +6802,6 @@ normalize-range@^0.1.2:
resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
normalize-url@^6.0.1:
version "6.1.0"
resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@ -7171,7 +6913,7 @@ on-exit-leak-free@^0.2.0:
resolved "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209"
integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==
once@^1.3.0, once@^1.3.1, once@^1.4.0:
once@^1.3.0, once@^1.4.0:
version "1.4.0"
resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
@ -7216,11 +6958,6 @@ os-browserify@^0.3.0:
resolved "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==
p-cancelable@^2.0.0:
version "2.1.1"
resolved "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf"
integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@ -7366,11 +7103,6 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
@ -7824,11 +7556,6 @@ process@^0.11.10:
resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
progress@^2.0.3:
version "2.0.3"
resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
prop-types@^15.5.10, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
@ -7860,14 +7587,6 @@ public-encrypt@^4.0.0:
randombytes "^2.0.1"
safe-buffer "^5.1.2"
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
@ -7925,11 +7644,6 @@ quick-format-unescaped@^4.0.3:
resolved "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7"
integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==
quick-lru@^5.1.1:
version "5.1.1"
resolved "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
railroad-diagrams@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
@ -8623,11 +8337,6 @@ resize-observer-polyfill@^1.5.1:
resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-alpn@^1.0.0:
version "1.2.1"
resolved "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@ -8670,13 +8379,6 @@ resolve@^2.0.0-next.4:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
responselike@^2.0.0:
version "2.0.1"
resolved "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc"
integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==
dependencies:
lowercase-keys "^2.0.0"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@ -8702,18 +8404,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
roarr@^2.15.3:
version "2.15.4"
resolved "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
dependencies:
boolean "^3.0.1"
detect-node "^2.0.4"
globalthis "^1.0.1"
json-stringify-safe "^5.0.1"
semver-compare "^1.0.0"
sprintf-js "^1.1.2"
rollup-plugin-visualizer@5.9.0:
version "5.9.0"
resolved "https://registry.npmmirror.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b"
@ -8843,22 +8533,17 @@ select-hose@^2.0.0:
resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
semver@^5.6.0:
version "5.7.2"
resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
semver@^6.2.0, semver@^6.3.0, semver@^6.3.1:
semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.2, semver@^7.3.5, semver@^7.3.7:
semver@^7.3.5, semver@^7.3.7:
version "7.5.4"
resolved "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
@ -8870,13 +8555,6 @@ semver@~7.0.0:
resolved "https://registry.npmmirror.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
serialize-error@^7.0.1:
version "7.0.1"
resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
dependencies:
type-fest "^0.13.1"
setimmediate@^1.0.4:
version "1.0.5"
resolved "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
@ -9075,11 +8753,6 @@ split2@^4.0.0:
resolved "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
sprintf-js@^1.1.2:
version "1.1.2"
resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@ -9322,13 +8995,6 @@ sucrase@^3.32.0:
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
sumchecker@^3.0.1:
version "3.0.1"
resolved "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==
dependencies:
debug "^4.1.0"
superjson@^1.10.0:
version "1.13.1"
resolved "https://registry.npmmirror.com/superjson/-/superjson-1.13.1.tgz#a0b6ab5d22876f6207fcb9d08b0cb2acad8ee5cd"
@ -9611,11 +9277,6 @@ tty-browserify@0.0.0:
resolved "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==
type-fest@^0.13.1:
version "0.13.1"
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
type@^1.0.1:
version "1.2.0"
resolved "https://registry.npmmirror.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
@ -9734,11 +9395,6 @@ unicode-property-aliases-ecmascript@^2.0.0:
resolved "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
@ -10034,14 +9690,6 @@ yargs@^17.5.1, yargs@^17.7.2:
y18n "^5.0.5"
yargs-parser "^21.1.1"
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"

View File

@ -166,7 +166,7 @@ public class TableController {
* @param request
* @return
*/
@GetMapping("/modify/sql")
@PostMapping("/modify/sql")
public ListResult<SqlVO> modifySql(@Valid TableModifySqlRequest request) {
return tableService.buildSql(
rdbWebConverter.tableRequest2param(request.getOldTable()),

View File

@ -2,6 +2,7 @@ package ai.chat2db.server.web.api.controller.rdb.vo;
import ai.chat2db.spi.enums.ColumnTypeEnum;
import com.fasterxml.jackson.annotation.JsonAlias;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -18,27 +19,52 @@ import lombok.experimental.SuperBuilder;
@AllArgsConstructor
public class ColumnVO {
/**
* 名称
* 旧的列名,在修改列的时候需要这个参数
* 在返回的时候oldName=name
*/
private String oldName;
/**
* 列名
*/
private String name;
/**
* 列的类型
*
* @see ColumnTypeEnum
* 表名
*/
private String dataType;
private String tableName;
/**
* 列的类型
* 比如 varchar(100) ,double(10,6)
*/
private String columnType;
/**
* 是否为空
* 列的数据类型
* 比如 varchar ,double
*/
private Integer nullable;
private Integer dataType;
/**
* 默认值
*/
private String defaultValue;
/**
* 是否自增
* 为空 代表没有值 数据库的实际语义是 false
*/
private Boolean autoIncrement;
/**
* 注释
*/
private String comment;
/**
* 是否主键
@ -46,33 +72,85 @@ public class ColumnVO {
private Boolean primaryKey;
/**
* 默认值
* 空间名
*/
private String defaultValue;
private String schemaName;
/**
* 是否自增
* 数据库名
*/
private Boolean autoIncrement;
private String databaseName;
/**
* 数字精度
* Data source dependent type name, for a UDT the type name is fully qualified
*/
private Integer numericPrecision;
private String typeName;
/**
* 数字比例
* column size.
*/
private Integer numericScale;
private Integer columnSize;
/**
* 字符串最大长度
* is not used.
*/
private Integer characterMaximumLength;
private Integer bufferLength;
/**
* 注释
* the number of fractional digits. Null is returned for data types where DECIMAL_DIGITS is not applicable.
*/
private String comment;
private Integer decimalDigits;
/**
* Radix (typically either 10 or 2)
*/
private Integer numPrecRadix;
/**
* is NULL allowed.
* columnNoNulls - might not allow NULL values
* columnNullable - definitely allows NULL values
* columnNullableUnknown - nullability unknown
*/
private Integer nullableInt;
/**
* unused
*/
private Integer sqlDataType;
/**
* unused
*/
private Integer sqlDatetimeSub;
/**
* for char types the maximum number of bytes in the column
*/
private Integer charOctetLength;
/**
* index of column in table (starting at 1)
*/
private Integer ordinalPosition;
/**
* ISO rules are used to determine the nullability for a column.
*/
private Integer nullable;
/**
* String => Indicates whether this is a generated column
* * YES --- if this a generated column
* * NO --- if this not a generated column
*/
private Boolean generatedColumn;
}

View File

@ -0,0 +1,99 @@
package ai.chat2db.server.web.api.controller.rdb.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 列信息
*
* @author Jiaju Zhuang
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class IndexColumnVO {
/**
* 索引名称
*/
private String indexName;
/**
* 表名
*/
private String tableName;
/**
* 索引类型
*
* @see
*/
private String type;
/**
* 注释
*/
private String comment;
/**
* 列名
*/
private String columnName;
/**
* 顺序
*/
private Short ordinalPosition;
/**
* 排序
*
*/
private String collation;
/**
* 索引所属schema
*/
private String schemaName;
/**
* 数据库名
*/
private String databaseName;
/**
* 是否唯一
*/
private Boolean nonUnique;
/**
* index catalog (may be null); null when TYPE is tableIndexStatistic
*/
private String indexQualifier;
/**
* ASC_OR_DESC String => column sort sequence, "A" => ascending, "D" => descending, may be null if sort sequence is not supported; null when TYPE is tableIndexStatistic
*/
private String ascOrDesc;
/**
* CARDINALITY long => When TYPE is tableIndexStatistic, then this is the number of rows in the table; otherwise, it is the number of unique values in the index.
*/
private Long cardinality;
/**
* When TYPE is tableIndexStatistic then this is the number of pages used for the table, otherwise it is the number of pages used for the current index.
*/
private Long pages;
/**
* Filter condition, if any. (may be null)
*/
private String filterCondition;
}

View File

@ -39,5 +39,5 @@ public class IndexVO {
/**
* 索引包含的列
*/
private List<ColumnVO> columnList;
private List<IndexColumnVO> columnList;
}