mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-02 04:29:38 +08:00
✨ feat(frontend): 升级 DataGrid 组件并引入高性能拖拽交互
- 实现基于原生 DOM 事件的零渲染列宽拖拽,彻底解决卡顿与误触排序问题 - 查询编辑器集成 DataGrid,支持 SQL 结果直接编辑与事务提交 - 侧边栏新增上下文感知的 "新建查询" 快捷入口 - 优化 TabManager 渲染逻辑与全局布局,消除不必要的滚动条
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Layout, Button, ConfigProvider, theme } from 'antd';
|
||||
import { PlusOutlined, BulbOutlined, BulbFilled } from '@ant-design/icons';
|
||||
import { PlusOutlined, BulbOutlined, BulbFilled, ConsoleSqlOutlined } from '@ant-design/icons';
|
||||
import Sidebar from './components/Sidebar';
|
||||
import TabManager from './components/TabManager';
|
||||
import ConnectionModal from './components/ConnectionModal';
|
||||
@@ -11,27 +11,63 @@ const { Sider, Content } = Layout;
|
||||
|
||||
function App() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const { darkMode, toggleDarkMode } = useStore();
|
||||
const { darkMode, toggleDarkMode, addTab, activeContext } = useStore();
|
||||
|
||||
// Sidebar Resizing
|
||||
const [sidebarWidth, setSidebarWidth] = useState(300);
|
||||
const sidebarDragRef = React.useRef<{ startX: number, startWidth: number } | null>(null);
|
||||
const rafRef = React.useRef<number | null>(null);
|
||||
const ghostRef = React.useRef<HTMLDivElement>(null);
|
||||
const latestMouseX = React.useRef<number>(0); // Store latest mouse position
|
||||
|
||||
const handleSidebarMouseDown = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (ghostRef.current) {
|
||||
ghostRef.current.style.left = `${sidebarWidth}px`;
|
||||
ghostRef.current.style.display = 'block';
|
||||
}
|
||||
|
||||
sidebarDragRef.current = { startX: e.clientX, startWidth: sidebarWidth };
|
||||
latestMouseX.current = e.clientX; // Init
|
||||
document.addEventListener('mousemove', handleSidebarMouseMove);
|
||||
document.addEventListener('mouseup', handleSidebarMouseUp);
|
||||
};
|
||||
|
||||
const handleSidebarMouseMove = (e: MouseEvent) => {
|
||||
if (!sidebarDragRef.current) return;
|
||||
const delta = e.clientX - sidebarDragRef.current.startX;
|
||||
const newWidth = Math.max(200, Math.min(600, sidebarDragRef.current.startWidth + delta));
|
||||
setSidebarWidth(newWidth);
|
||||
|
||||
latestMouseX.current = e.clientX; // Always update latest pos
|
||||
|
||||
if (rafRef.current) return; // Schedule once per frame
|
||||
|
||||
rafRef.current = requestAnimationFrame(() => {
|
||||
if (!sidebarDragRef.current || !ghostRef.current) return;
|
||||
// Use latestMouseX.current instead of stale closure 'e.clientX'
|
||||
const delta = latestMouseX.current - sidebarDragRef.current.startX;
|
||||
const newWidth = Math.max(200, Math.min(600, sidebarDragRef.current.startWidth + delta));
|
||||
ghostRef.current.style.left = `${newWidth}px`;
|
||||
rafRef.current = null;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSidebarMouseUp = () => {
|
||||
const handleSidebarMouseUp = (e: MouseEvent) => {
|
||||
if (rafRef.current) {
|
||||
cancelAnimationFrame(rafRef.current);
|
||||
rafRef.current = null;
|
||||
}
|
||||
|
||||
if (sidebarDragRef.current) {
|
||||
// Use latest position for final commit too
|
||||
const delta = e.clientX - sidebarDragRef.current.startX;
|
||||
const newWidth = Math.max(200, Math.min(600, sidebarDragRef.current.startWidth + delta));
|
||||
setSidebarWidth(newWidth);
|
||||
}
|
||||
|
||||
if (ghostRef.current) {
|
||||
ghostRef.current.style.display = 'none';
|
||||
}
|
||||
|
||||
sidebarDragRef.current = null;
|
||||
document.removeEventListener('mousemove', handleSidebarMouseMove);
|
||||
document.removeEventListener('mouseup', handleSidebarMouseUp);
|
||||
@@ -53,12 +89,26 @@ function App() {
|
||||
algorithm: darkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||
}}
|
||||
>
|
||||
<Layout style={{ height: '100vh' }}>
|
||||
<Sider theme={darkMode ? "dark" : "light"} width={sidebarWidth} style={{ borderRight: darkMode ? '1px solid #303030' : '1px solid #f0f0f0', position: 'relative' }}>
|
||||
<Layout style={{ height: '100vh', overflow: 'hidden' }}>
|
||||
<Sider
|
||||
theme={darkMode ? "dark" : "light"}
|
||||
width={sidebarWidth}
|
||||
style={{
|
||||
borderRight: darkMode ? '1px solid #303030' : '1px solid #f0f0f0',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: '10px', borderBottom: darkMode ? '1px solid #303030' : '1px solid #f0f0f0', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ fontWeight: 'bold', paddingLeft: 8 }}>GoNavi</span>
|
||||
<div>
|
||||
<Button type="text" icon={darkMode ? <BulbFilled /> : <BulbOutlined />} onClick={toggleDarkMode} title="切换主题" />
|
||||
<Button type="text" icon={<ConsoleSqlOutlined />} onClick={() => addTab({
|
||||
id: `query-${Date.now()}`,
|
||||
title: '新建查询',
|
||||
type: 'query',
|
||||
connectionId: activeContext?.connectionId || '',
|
||||
dbName: activeContext?.dbName || ''
|
||||
})} title="新建查询" />
|
||||
<Button type="text" icon={<PlusOutlined />} onClick={() => setIsModalOpen(true)} title="新建连接" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,10 +130,26 @@ function App() {
|
||||
title="拖动调整宽度"
|
||||
/>
|
||||
</Sider>
|
||||
<Content style={{ background: darkMode ? '#141414' : '#fff' }}>
|
||||
<Content style={{ background: darkMode ? '#141414' : '#fff', overflow: 'hidden' }}>
|
||||
<TabManager />
|
||||
</Content>
|
||||
<ConnectionModal open={isModalOpen} onClose={() => setIsModalOpen(false)} />
|
||||
|
||||
{/* Ghost Resize Line */}
|
||||
<div
|
||||
ref={ghostRef}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '4px',
|
||||
background: 'rgba(24, 144, 255, 0.5)',
|
||||
zIndex: 9999,
|
||||
pointerEvents: 'none',
|
||||
display: 'none'
|
||||
}}
|
||||
/>
|
||||
</Layout>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user