From 84579b83c9d2cc6a8fca4052041f5eaa989c648f Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 20 Mar 2026 15:21:47 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(TableDesigner):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=AE=BE=E8=AE=A1=E8=A1=A8=E6=BB=9A=E5=8A=A8=E4=BD=93?= =?UTF-8?q?=E9=AA=8C=E5=B9=B6=E6=94=AF=E6=8C=81=E7=B4=A2=E5=BC=95=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 滚动优化:修复 Tab 切换时 ResizeObserver 高度归零导致表格异常滚动 - 零高度守卫:移除 activeKey 依赖,跳过 display:none 时的零高度观测 - 触发器统一:触发器 Tab 补充 scroll={{ y: tableHeight }} 与索引/外键保持一致 - 批量删除:handleDeleteIndex 支持多选索引批量生成 DROP SQL 合并执行 - 交互优化:删除确认弹窗展示选中索引数量和名称列表 - 状态清理:批量删除成功后自动清空 selectedIndexKeys - refs #273 --- frontend/src/components/TableDesigner.tsx | 43 +++++++++++++++++------ 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/TableDesigner.tsx b/frontend/src/components/TableDesigner.tsx index 8c7924a..ee2335f 100644 --- a/frontend/src/components/TableDesigner.tsx +++ b/frontend/src/components/TableDesigner.tsx @@ -292,17 +292,21 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => { // 透明 Monaco Editor 主题已在 main.tsx 全局注册(含 stickyScroll 不透明背景) + // 监听字段 Tab 容器高度,为所有 Tab 内表格计算 scroll.y + // 当 Tab 切换时,字段 Tab 被 display:none 导致 height=0,跳过该次更新保持有效值 useEffect(() => { if (!containerRef.current) return; const resizeObserver = new ResizeObserver(entries => { for (let entry of entries) { - const h = Math.max(200, entry.contentRect.height - 40); - setTableHeight(h); + const h = entry.contentRect.height; + // 跳过零高度观测(Tab 面板被隐藏时) + if (h <= 0) return; + setTableHeight(Math.max(200, h - 40)); } }); resizeObserver.observe(containerRef.current); return () => resizeObserver.disconnect(); - }, [activeKey]); // Re-attach when tab switches + }, []); // 不依赖 activeKey,仅挂载一次,通过零高度守卫避免 Tab 切换异常 // --- Resizable Columns State --- const [tableColumns, setTableColumns] = useState([]); @@ -1687,28 +1691,44 @@ END;`; }; const handleDeleteIndex = () => { - if (!selectedIndex) { - message.warning('请先选择一个索引'); + if (selectedIndexKeys.length === 0) { + message.warning('请先选择要删除的索引'); return; } if (!supportsIndexSchemaOps()) { message.warning('当前数据库暂不支持在此维护索引'); return; } + // 根据选中的 key 找到对应的索引对象 + const toDelete = groupedIndexes.filter(idx => selectedIndexKeys.includes(idx.key)); + if (toDelete.length === 0) { + message.warning('请先选择要删除的索引'); + return; + } + const names = toDelete.map(idx => `"${idx.name}"`).join('、'); Modal.confirm({ title: '确认删除索引', icon: , - content: `确定删除索引 "${selectedIndex.name}" 吗?`, + content: toDelete.length === 1 + ? `确定删除索引 ${names} 吗?` + : `确定删除以下 ${toDelete.length} 个索引吗?\n${names}`, okText: '删除', okType: 'danger', cancelText: '取消', onOk: async () => { - const sql = buildIndexDropSql(selectedIndex.name); - if (!sql) { - message.warning('当前数据库暂不支持删除该索引'); - return; + const sqls: string[] = []; + for (const idx of toDelete) { + const sql = buildIndexDropSql(idx.name); + if (!sql) { + message.warning(`当前数据库暂不支持删除索引 "${idx.name}"`); + return; + } + sqls.push(sql); + } + const ok = await executeSchemaSql(sqls.join('\n'), toDelete.length === 1 ? '索引删除成功' : `${toDelete.length} 个索引删除成功`); + if (ok) { + setSelectedIndexKeys([]); } - await executeSchemaSql(sql, '索引删除成功'); } }); }; @@ -2538,6 +2558,7 @@ END;`; size="small" pagination={false} loading={loading} + scroll={{ y: tableHeight }} locale={{ emptyText: }} rowSelection={{ type: 'radio',