mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-02 20:49:48 +08:00
Compare commits
9 Commits
release/0.
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c49a4c3458 | ||
|
|
ca49b37dc7 | ||
|
|
c8c0c5f20a | ||
|
|
d61d7ec39b | ||
|
|
e964c8ecf8 | ||
|
|
7644462180 | ||
|
|
3bd02e2e09 | ||
|
|
22bd1c4c28 | ||
|
|
89c81823bc |
@@ -1179,7 +1179,8 @@ const ConnectionModal: React.FC<{
|
|||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
await form.validateFields();
|
||||||
|
const values = form.getFieldsValue(true);
|
||||||
const unavailableReason = await resolveDriverUnavailableReason(values.type);
|
const unavailableReason = await resolveDriverUnavailableReason(values.type);
|
||||||
if (unavailableReason) {
|
if (unavailableReason) {
|
||||||
message.warning(unavailableReason);
|
message.warning(unavailableReason);
|
||||||
@@ -1241,7 +1242,8 @@ const ConnectionModal: React.FC<{
|
|||||||
if (testInFlightRef.current) return;
|
if (testInFlightRef.current) return;
|
||||||
testInFlightRef.current = true;
|
testInFlightRef.current = true;
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
await form.validateFields();
|
||||||
|
const values = form.getFieldsValue(true);
|
||||||
const unavailableReason = await resolveDriverUnavailableReason(values.type);
|
const unavailableReason = await resolveDriverUnavailableReason(values.type);
|
||||||
if (unavailableReason) {
|
if (unavailableReason) {
|
||||||
const failMessage = buildTestFailureMessage(unavailableReason, '驱动未安装启用');
|
const failMessage = buildTestFailureMessage(unavailableReason, '驱动未安装启用');
|
||||||
@@ -1311,7 +1313,8 @@ const ConnectionModal: React.FC<{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
await form.validateFields();
|
||||||
|
const values = form.getFieldsValue(true);
|
||||||
setDiscoveringMembers(true);
|
setDiscoveringMembers(true);
|
||||||
const config = await buildConfig(values, false);
|
const config = await buildConfig(values, false);
|
||||||
const result = await MongoDiscoverMembers(config as any);
|
const result = await MongoDiscoverMembers(config as any);
|
||||||
|
|||||||
@@ -1202,11 +1202,11 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
|
|
||||||
// 直接操作 DOM 更新选中效果,避免 React 重渲染
|
// 直接操作 DOM 更新选中效果,避免 React 重渲染
|
||||||
const updateCellSelection = useCallback((newSelection: Set<string>) => {
|
const updateCellSelection = useCallback((newSelection: Set<string>) => {
|
||||||
const tableBody = containerRef.current?.querySelector('.ant-table-body');
|
const container = containerRef.current;
|
||||||
if (!tableBody) return;
|
if (!container) return;
|
||||||
|
|
||||||
// 只同步可见单元格(兼容 virtual 渲染 + 极大选区)
|
// 只同步可见单元格,严格限定 `.ant-table-cell`,避免虚拟列表中内嵌的 EditableCell 被重复获取并打上 selected 样式从而产生白边。
|
||||||
const visibleCells = tableBody.querySelectorAll('td[data-row-key][data-col-name]');
|
const visibleCells = container.querySelectorAll('.ant-table-cell[data-row-key][data-col-name]');
|
||||||
visibleCells.forEach((cell) => {
|
visibleCells.forEach((cell) => {
|
||||||
const el = cell as HTMLElement;
|
const el = cell as HTMLElement;
|
||||||
const rowKey = el.getAttribute('data-row-key');
|
const rowKey = el.getAttribute('data-row-key');
|
||||||
@@ -1334,10 +1334,10 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
|
|
||||||
const getCellInfo = (target: HTMLElement | null): { rowKey: string; colName: string } | null => {
|
const getCellInfo = (target: HTMLElement | null): { rowKey: string; colName: string } | null => {
|
||||||
if (!target) return null;
|
if (!target) return null;
|
||||||
const td = target.closest('td[data-row-key][data-col-name]') as HTMLElement;
|
const cell = target.closest('[data-row-key][data-col-name]') as HTMLElement;
|
||||||
if (!td) return null;
|
if (!cell) return null;
|
||||||
const rowKey = td.getAttribute('data-row-key');
|
const rowKey = cell.getAttribute('data-row-key');
|
||||||
const colName = td.getAttribute('data-col-name');
|
const colName = cell.getAttribute('data-col-name');
|
||||||
if (!rowKey || !colName) return null;
|
if (!rowKey || !colName) return null;
|
||||||
return { rowKey, colName };
|
return { rowKey, colName };
|
||||||
};
|
};
|
||||||
@@ -2105,16 +2105,9 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
closeRowEditor();
|
closeRowEditor();
|
||||||
}, [rowEditorRowKey, rowEditorForm, addedRows, columnNames, rowKeyStr, closeRowEditor]);
|
}, [rowEditorRowKey, rowEditorForm, addedRows, columnNames, rowKeyStr, closeRowEditor]);
|
||||||
|
|
||||||
const estimatedVisibleCellCount = mergedDisplayData.length * Math.max(columnNames.length, 1);
|
|
||||||
const enableLargeResultOptimizedEditing =
|
const enableVirtual = viewMode === 'table';
|
||||||
viewMode === 'table' && (
|
const enableInlineEditableCell = canModifyData;
|
||||||
mergedDisplayData.length >= 60 ||
|
|
||||||
estimatedVisibleCellCount >= 1600 ||
|
|
||||||
columnNames.length >= 36 ||
|
|
||||||
(isMacLike && columnNames.length >= 24)
|
|
||||||
);
|
|
||||||
const enableVirtual = enableLargeResultOptimizedEditing;
|
|
||||||
const enableInlineEditableCell = canModifyData && !enableLargeResultOptimizedEditing;
|
|
||||||
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return columnNames.map(key => ({
|
return columnNames.map(key => ({
|
||||||
@@ -2169,22 +2162,28 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
return {
|
return {
|
||||||
...col,
|
...col,
|
||||||
onCell: (record: Item) => {
|
onCell: (record: Item) => {
|
||||||
if (!enableInlineEditableCell) {
|
const rowKey = record?.[GONAVI_ROW_KEY];
|
||||||
const rowKey = record?.[GONAVI_ROW_KEY];
|
const cellProps: any = {
|
||||||
return {
|
'data-row-key': rowKey === undefined || rowKey === null ? undefined : String(rowKey),
|
||||||
'data-row-key': rowKey === undefined || rowKey === null ? undefined : String(rowKey),
|
'data-col-name': dataIndex,
|
||||||
'data-col-name': dataIndex,
|
|
||||||
onDoubleClick: () => handleVirtualCellActivate(record, dataIndex, dataIndex),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
record,
|
|
||||||
editable: col.editable,
|
|
||||||
dataIndex: col.dataIndex,
|
|
||||||
title: dataIndex,
|
|
||||||
handleSave: handleCellSave,
|
|
||||||
focusCell: openCellEditor,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!enableInlineEditableCell) {
|
||||||
|
cellProps.onDoubleClick = () => handleVirtualCellActivate(record, dataIndex, dataIndex);
|
||||||
|
cellProps.onContextMenu = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
showCellContextMenu(e, record, dataIndex, dataIndex);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
cellProps.record = record;
|
||||||
|
cellProps.editable = col.editable;
|
||||||
|
cellProps.dataIndex = col.dataIndex;
|
||||||
|
cellProps.title = dataIndex;
|
||||||
|
cellProps.handleSave = handleCellSave;
|
||||||
|
cellProps.focusCell = openCellEditor;
|
||||||
|
}
|
||||||
|
return cellProps;
|
||||||
},
|
},
|
||||||
render: (text: any, record: Item, index: number) => {
|
render: (text: any, record: Item, index: number) => {
|
||||||
const originalRenderContent = col.render ? (col.render as any)(text, record, index) : text;
|
const originalRenderContent = col.render ? (col.render as any)(text, record, index) : text;
|
||||||
@@ -2204,10 +2203,24 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
</EditableCell>
|
</EditableCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (enableVirtual) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ margin: -8, padding: '8px 8px 8px 8px' }}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
showCellContextMenu(e, record, dataIndex, dataIndex);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{originalRenderContent}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
return originalRenderContent;
|
return originalRenderContent;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}), [columns, enableInlineEditableCell, enableVirtual, handleCellSave, openCellEditor, handleVirtualCellActivate]);
|
}), [columns, enableInlineEditableCell, enableVirtual, handleCellSave, openCellEditor, handleVirtualCellActivate, showCellContextMenu]);
|
||||||
|
|
||||||
const handleAddRow = () => {
|
const handleAddRow = () => {
|
||||||
const newKey = `new-${Date.now()}`;
|
const newKey = `new-${Date.now()}`;
|
||||||
@@ -2746,7 +2759,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
handleExportSelected,
|
handleExportSelected,
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
tableName,
|
tableName,
|
||||||
enableRowContextMenu: !canModifyData,
|
enableRowContextMenu: false,
|
||||||
supportsCopyInsert,
|
supportsCopyInsert,
|
||||||
}), [handleCopyCsv, handleCopyInsert, handleCopyJson, handleExportSelected, copyToClipboard, tableName, canModifyData, supportsCopyInsert]);
|
}), [handleCopyCsv, handleCopyInsert, handleCopyJson, handleExportSelected, copyToClipboard, tableName, canModifyData, supportsCopyInsert]);
|
||||||
|
|
||||||
@@ -2764,7 +2777,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
const rowPropsFactory = useCallback((record: any) => ({ record } as any), []);
|
const rowPropsFactory = useCallback((record: any) => ({ record } as any), []);
|
||||||
|
|
||||||
const totalWidth = columns.reduce((sum, col) => sum + (Number(col.width) || 200), 0) + selectionColumnWidth;
|
const totalWidth = columns.reduce((sum, col) => sum + (Number(col.width) || 200), 0) + selectionColumnWidth;
|
||||||
const useContextMenuRow = !canModifyData;
|
const useContextMenuRow = false;
|
||||||
const tableScrollX = useMemo(() => {
|
const tableScrollX = useMemo(() => {
|
||||||
const baseWidth = Math.max(totalWidth, 1000);
|
const baseWidth = Math.max(totalWidth, 1000);
|
||||||
if (!isMacLike || tableViewportWidth <= 0) return baseWidth;
|
if (!isMacLike || tableViewportWidth <= 0) return baseWidth;
|
||||||
@@ -3756,6 +3769,8 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
{canModifyData && (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: '8px 12px',
|
padding: '8px 12px',
|
||||||
@@ -3789,6 +3804,8 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
填充到选中行 ({selectedRowKeys.length})
|
填充到选中行 ({selectedRowKeys.length})
|
||||||
</div>
|
</div>
|
||||||
<div style={{ height: 1, background: darkMode ? '#303030' : '#f0f0f0', margin: '4px 0' }} />
|
<div style={{ height: 1, background: darkMode ? '#303030' : '#f0f0f0', margin: '4px 0' }} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{supportsCopyInsert && (
|
{supportsCopyInsert && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -4021,12 +4038,13 @@ const DataGrid: React.FC<DataGridProps> = ({
|
|||||||
.${gridId} .ant-table-tbody .ant-table-row.row-added:hover > .ant-table-cell { background-color: ${rowAddedHover} !important; }
|
.${gridId} .ant-table-tbody .ant-table-row.row-added:hover > .ant-table-cell { background-color: ${rowAddedHover} !important; }
|
||||||
.${gridId} .ant-table-tbody > tr.row-modified:hover > td,
|
.${gridId} .ant-table-tbody > tr.row-modified:hover > td,
|
||||||
.${gridId} .ant-table-tbody .ant-table-row.row-modified:hover > .ant-table-cell { background-color: ${rowModHover} !important; }
|
.${gridId} .ant-table-tbody .ant-table-row.row-modified:hover > .ant-table-cell { background-color: ${rowModHover} !important; }
|
||||||
.${gridId}.cell-edit-mode .ant-table-tbody > tr > td[data-col-name],
|
.${gridId} .ant-table-tbody > tr > td[data-col-name],
|
||||||
.${gridId}.cell-edit-mode .ant-table-tbody .ant-table-row > .ant-table-cell[data-col-name] { user-select: none; -webkit-user-select: none; cursor: crosshair; }
|
.${gridId} .ant-table-tbody .ant-table-row > .ant-table-cell[data-col-name] { user-select: none; -webkit-user-select: none; cursor: crosshair; }
|
||||||
.${gridId}.cell-edit-mode .ant-table-tbody > tr > td[data-cell-selected="true"],
|
.${gridId} .ant-table-tbody > tr > td[data-cell-selected="true"],
|
||||||
.${gridId}.cell-edit-mode .ant-table-tbody .ant-table-row > .ant-table-cell[data-cell-selected="true"] {
|
.${gridId} .ant-table-tbody .ant-table-row > .ant-table-cell[data-cell-selected="true"],
|
||||||
box-shadow: inset 0 0 0 2px ${selectionAccentHex};
|
.${gridId} [data-cell-selected="true"] {
|
||||||
background-image: linear-gradient(${darkMode ? `rgba(${selectionAccentRgb}, 0.20)` : `rgba(${selectionAccentRgb}, 0.08)`}, ${darkMode ? `rgba(${selectionAccentRgb}, 0.20)` : `rgba(${selectionAccentRgb}, 0.08)`});
|
box-shadow: inset 0 0 0 2px ${selectionAccentHex} !important;
|
||||||
|
background-image: linear-gradient(${darkMode ? `rgba(${selectionAccentRgb}, 0.20)` : `rgba(${selectionAccentRgb}, 0.08)`}, ${darkMode ? `rgba(${selectionAccentRgb}, 0.20)` : `rgba(${selectionAccentRgb}, 0.08)`}) !important;
|
||||||
}
|
}
|
||||||
.${gridId} .ant-table-content,
|
.${gridId} .ant-table-content,
|
||||||
.${gridId} .ant-table-body {
|
.${gridId} .ant-table-body {
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ export const quoteIdentPart = (dbType: string, ident: string) => {
|
|||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SQL Server 使用 [bracket] 标识符
|
||||||
|
if (dbTypeLower === 'sqlserver' || dbTypeLower === 'mssql') {
|
||||||
|
return `[${raw.replace(/]/g, ']]')}]`;
|
||||||
|
}
|
||||||
|
|
||||||
// 其他数据库默认加双引号
|
// 其他数据库默认加双引号
|
||||||
return `"${raw.replace(/"/g, '""')}"`;
|
return `"${raw.replace(/"/g, '""')}"`;
|
||||||
};
|
};
|
||||||
@@ -160,7 +165,8 @@ export const buildPaginatedSelectSQL = (
|
|||||||
}
|
}
|
||||||
return `SELECT * FROM (SELECT "__gonavi_page__".*, ROWNUM "__gonavi_rn__" FROM (${orderedSql}) "__gonavi_page__" WHERE ROWNUM <= ${upperBound}) WHERE "__gonavi_rn__" > ${safeOffset}`;
|
return `SELECT * FROM (SELECT "__gonavi_page__".*, ROWNUM "__gonavi_rn__" FROM (${orderedSql}) "__gonavi_page__" WHERE ROWNUM <= ${upperBound}) WHERE "__gonavi_rn__" > ${safeOffset}`;
|
||||||
}
|
}
|
||||||
case 'sqlserver': {
|
case 'sqlserver':
|
||||||
|
case 'mssql': {
|
||||||
const effectiveOrderBy = orderBy.trim() ? orderBy : ' ORDER BY (SELECT NULL)';
|
const effectiveOrderBy = orderBy.trim() ? orderBy : ' ORDER BY (SELECT NULL)';
|
||||||
return `${base}${effectiveOrderBy} OFFSET ${safeOffset} ROWS FETCH NEXT ${safeLimit} ROWS ONLY`;
|
return `${base}${effectiveOrderBy} OFFSET ${safeOffset} ROWS FETCH NEXT ${safeLimit} ROWS ONLY`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,6 +251,11 @@ func (m *MongoDB) getURI(config connection.ConnectionConfig) string {
|
|||||||
params.Set("authMechanism", authMechanism)
|
params.Set("authMechanism", authMechanism)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单机模式且未指定副本集名称时,启用 directConnection 避免驱动自动跟随副本集成员发现
|
||||||
|
if strings.TrimSpace(config.Topology) != "replica" && strings.TrimSpace(config.ReplicaSet) == "" && !config.MongoSRV {
|
||||||
|
params.Set("directConnection", "true")
|
||||||
|
}
|
||||||
|
|
||||||
if encoded := params.Encode(); encoded != "" {
|
if encoded := params.Encode(); encoded != "" {
|
||||||
uri += "?" + encoded
|
uri += "?" + encoded
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,6 +252,11 @@ func (m *MongoDBV1) getURI(config connection.ConnectionConfig) string {
|
|||||||
params.Set("authMechanism", authMechanism)
|
params.Set("authMechanism", authMechanism)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单机模式且未指定副本集名称时,启用 directConnection 避免驱动自动跟随副本集成员发现
|
||||||
|
if strings.TrimSpace(config.Topology) != "replica" && strings.TrimSpace(config.ReplicaSet) == "" && !config.MongoSRV {
|
||||||
|
params.Set("directConnection", "true")
|
||||||
|
}
|
||||||
|
|
||||||
if encoded := params.Encode(); encoded != "" {
|
if encoded := params.Encode(); encoded != "" {
|
||||||
uri += "?" + encoded
|
uri += "?" + encoded
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user