diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index a7661c0..0f8f4fe 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -d0f9366af59a6367ad3c7e2d4185ead4 \ No newline at end of file +5b8157374dae5f9340e31b2d0bd2c00e \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2563846..c684af1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -705,7 +705,7 @@ function App() { - + - -
+ +
{isLogPanelOpen && ( diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index 707feaa..f64b155 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -43,6 +43,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal const [redisDbList, setRedisDbList] = useState([]); // Redis databases 0-15 const [mongoMembers, setMongoMembers] = useState([]); const [discoveringMembers, setDiscoveringMembers] = useState(false); + const [uriFeedback, setUriFeedback] = useState<{ type: 'success' | 'warning' | 'error'; message: string } | null>(null); const testInFlightRef = useRef(false); const testTimerRef = useRef(null); const addConnection = useStore((state) => state.addConnection); @@ -393,9 +394,9 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal const values = form.getFieldsValue(true); const uri = buildUriFromValues(values); form.setFieldValue('uri', uri); - message.success('URI 已生成'); + setUriFeedback({ type: 'success', message: 'URI 已生成' }); } catch { - message.error('生成 URI 失败'); + setUriFeedback({ type: 'error', message: '生成 URI 失败' }); } }; @@ -404,21 +405,21 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal const uriText = String(form.getFieldValue('uri') || '').trim(); const type = String(form.getFieldValue('type') || dbType).trim().toLowerCase(); if (!uriText) { - message.warning('请先输入 URI'); + setUriFeedback({ type: 'warning', message: '请先输入 URI' }); return; } const parsedValues = parseUriToValues(uriText, type); if (!parsedValues) { - message.error('当前 URI 与数据源类型不匹配,或 URI 格式不支持'); + setUriFeedback({ type: 'error', message: '当前 URI 与数据源类型不匹配,或 URI 格式不支持' }); return; } form.setFieldsValue({ ...parsedValues, uri: uriText }); if (testResult) { setTestResult(null); } - message.success('已根据 URI 回填连接参数'); + setUriFeedback({ type: 'success', message: '已根据 URI 回填连接参数' }); } catch { - message.error('URI 解析失败,请检查格式后重试'); + setUriFeedback({ type: 'error', message: 'URI 解析失败,请检查格式后重试' }); } }; @@ -430,14 +431,14 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal form.setFieldValue('uri', uriText); } if (!uriText) { - message.warning('没有可复制的 URI'); + setUriFeedback({ type: 'warning', message: '没有可复制的 URI' }); return; } try { await navigator.clipboard.writeText(uriText); - message.success('URI 已复制'); + setUriFeedback({ type: 'success', message: 'URI 已复制' }); } catch { - message.error('复制失败'); + setUriFeedback({ type: 'error', message: '复制失败' }); } }; @@ -448,6 +449,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal setDbList([]); setRedisDbList([]); setMongoMembers([]); + setUriFeedback(null); if (initialValues) { // Edit mode: Go directly to step 2 setStep(2); @@ -925,6 +927,9 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal setTestResult(null); // Clear result on change setTestErrorLogOpen(false); } + if (changed.uri !== undefined || changed.type !== undefined) { + setUriFeedback(null); + } if (changed.useSSH !== undefined) setUseSSH(changed.useSSH); // Type change handled by step 1, but keep sync if select changes (hidden now) if (changed.type !== undefined) setDbType(changed.type); @@ -958,6 +963,16 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal + {uriFeedback && ( + setUriFeedback(null)} + style={{ marginBottom: 12 }} + /> + )} {isCustom ? ( <> diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index 6bf7aaa..47604c9 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -9,7 +9,7 @@ import ImportPreviewModal from './ImportPreviewModal'; import { useStore } from '../store'; import { v4 as uuidv4 } from 'uuid'; import 'react-resizable/css/styles.css'; -import { buildOrderBySQL, buildWhereSQL, escapeLiteral, quoteIdentPart, quoteQualifiedIdent, type FilterCondition } from '../utils/sql'; +import { buildOrderBySQL, buildWhereSQL, escapeLiteral, quoteIdentPart, quoteQualifiedIdent, withSortBufferTuningSQL, type FilterCondition } from '../utils/sql'; import { isMacLikePlatform, normalizeOpacityForPlatform } from '../utils/appearance'; // --- Error Boundary --- @@ -496,6 +496,7 @@ interface DataGridProps { onSort?: (field: string, order: string) => void; onPageChange?: (page: number, size: number) => void; pagination?: { current: number, pageSize: number, total: number, totalKnown?: boolean }; + sortInfoExternal?: { columnKey: string, order: string } | null; // Filtering showFilter?: boolean; onToggleFilter?: () => void; @@ -514,7 +515,7 @@ type GridViewMode = 'table' | 'json' | 'text'; const DataGrid: React.FC = ({ data, columnNames, loading, tableName, dbName, connectionId, pkColumns = [], readOnly = false, - onReload, onSort, onPageChange, pagination, showFilter, onToggleFilter, onApplyFilter + onReload, onSort, onPageChange, pagination, sortInfoExternal, showFilter, onToggleFilter, onApplyFilter }) => { const connections = useStore(state => state.connections); const addSqlLog = useStore(state => state.addSqlLog); @@ -661,6 +662,21 @@ const DataGrid: React.FC = ({ const [sortInfo, setSortInfo] = useState<{ columnKey: string, order: string } | null>(null); const [columnWidths, setColumnWidths] = useState>({}); + useEffect(() => { + const nextOrder = sortInfoExternal?.order === 'ascend' || sortInfoExternal?.order === 'descend' + ? sortInfoExternal.order + : ''; + const nextColumn = nextOrder ? String(sortInfoExternal?.columnKey || '') : ''; + const currColumn = String(sortInfo?.columnKey || ''); + const currOrder = sortInfo?.order === 'ascend' || sortInfo?.order === 'descend' ? sortInfo.order : ''; + if (nextColumn === currColumn && nextOrder === currOrder) return; + if (!nextColumn || !nextOrder) { + setSortInfo(null); + } else { + setSortInfo({ columnKey: nextColumn, order: nextOrder }); + } + }, [sortInfoExternal, sortInfo]); + const closeCellEditor = useCallback(() => { setCellEditorOpen(false); setCellEditorMeta(null); @@ -1113,9 +1129,16 @@ const DataGrid: React.FC = ({ const handleTableChange = (pag: any, filtersArg: any, sorter: any) => { if (isResizingRef.current) return; // Block sort if resizing if (sorter.field) { + const field = String(sorter.field); const order = sorter.order as string; - setSortInfo({ columnKey: sorter.field as string, order }); - if (onSort) onSort(sorter.field, order); + const normalizedOrder = order === 'ascend' || order === 'descend' ? order : ''; + if (!normalizedOrder) { + setSortInfo(null); + if (onSort) onSort('', ''); + return; + } + setSortInfo({ columnKey: field, order: normalizedOrder }); + if (onSort) onSort(field, normalizedOrder); } else { setSortInfo(null); if (onSort) onSort('', ''); @@ -1820,6 +1843,11 @@ const DataGrid: React.FC = ({ const whereSQL = buildWhereSQL(dbType, filterConditions); let sql = `SELECT * FROM ${quoteQualifiedIdent(dbType, tableName)} ${whereSQL}`; sql += buildOrderBySQL(dbType, sortInfo, pkColumns); + const normalizedType = String(dbType || '').trim().toLowerCase(); + const hasExplicitSort = !!sortInfo?.columnKey && (sortInfo?.order === 'ascend' || sortInfo?.order === 'descend'); + if (hasExplicitSort && (normalizedType === 'mysql' || normalizedType === 'mariadb')) { + sql = withSortBufferTuningSQL(normalizedType, sql, 32 * 1024 * 1024); + } const offset = (pagination.current - 1) * pagination.pageSize; sql += ` LIMIT ${pagination.pageSize} OFFSET ${offset}`; return sql; @@ -2034,9 +2062,9 @@ const DataGrid: React.FC = ({ }, [viewMode, totalWidth, mergedDisplayData.length, recalculateTableMetrics]); return ( -
- {/* Toolbar */} -
+
+ {/* Toolbar */} +
{onReload &&
{/* Filter Panel */} {showFilter && ( @@ -2448,8 +2478,8 @@ const DataGrid: React.FC = ({ />
- ) : ( -
+ ) : ( +
)}
-
+
{currentTextRow ? columnNames.map((col) => (
@@ -2672,8 +2702,21 @@ const DataGrid: React.FC = ({
)} -