feat(TableDesigner): 优化设计表滚动体验并支持索引批量删除

- 滚动优化:修复 Tab 切换时 ResizeObserver 高度归零导致表格异常滚动
- 零高度守卫:移除 activeKey 依赖,跳过 display:none 时的零高度观测
- 触发器统一:触发器 Tab 补充 scroll={{ y: tableHeight }} 与索引/外键保持一致
- 批量删除:handleDeleteIndex 支持多选索引批量生成 DROP SQL 合并执行
- 交互优化:删除确认弹窗展示选中索引数量和名称列表
- 状态清理:批量删除成功后自动清空 selectedIndexKeys
- refs #273
This commit is contained in:
Syngnat
2026-03-20 15:21:47 +08:00
parent 7ddef7096b
commit 84579b83c9

View File

@@ -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<any[]>([]);
@@ -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: <ExclamationCircleOutlined />,
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: <Empty description="该表暂无触发器" image={Empty.PRESENTED_IMAGE_SIMPLE} /> }}
rowSelection={{
type: 'radio',