'use client'; import * as React from 'react'; import { useRef, useState } from 'react'; import { useTheme } from 'next-themes'; import CodeMirror from '@uiw/react-codemirror'; import { sql } from '@codemirror/lang-sql'; import { toast } from 'sonner'; import { ArrowLeft, Play, RefreshCw, Terminal, Trash2 } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import type { ExecuteSQLResponse } from '@/lib/services/db-manage'; import { DbManageService } from '@/lib/services/db-manage'; /** * 格式化表格单元格内容 */ const formatCellValue = (val: unknown): string => { if (val === null || val === undefined) return '-'; if (typeof val === 'boolean') return val ? 'true' : 'false'; if (typeof val === 'object') { try { return JSON.stringify(val); } catch { return '[Object]'; } } return String(val); }; interface SQLConsoleProps { dbType?: string; // "postgres" | "sqlite" onClose: () => void; } export function SQLConsole({ dbType, onClose }: SQLConsoleProps) { const t = useTranslations('admin.database.sql'); // SQL 控制台状态 const [sqlQuery, setSqlQuery] = useState(''); const [executingSQL, setExecutingSQL] = useState(false); const [sqlResult, setSqlResult] = useState(null); const [sqlError, setSqlError] = useState(null); // 拖拽与主题状态 const containerRef = useRef(null); const [editorHeight, setEditorHeight] = useState(240); const { resolvedTheme } = useTheme(); const cmTheme = resolvedTheme === 'dark' ? 'dark' : 'light'; const handleMouseDown = (e: React.MouseEvent) => { e.preventDefault(); const startY = e.clientY; const startHeight = editorHeight; const handleMouseMove = (moveEvent: MouseEvent) => { if (!containerRef.current) return; const deltaY = moveEvent.clientY - startY; const containerHeight = containerRef.current.getBoundingClientRect().height; const newHeight = startHeight + deltaY; // Limit editor height between 80px and containerHeight - 80px if (newHeight > 80 && newHeight < containerHeight - 80) { setEditorHeight(newHeight); } }; const handleMouseUp = () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }; // 执行 SQL 查询 const handleExecuteSQL = async () => { if (!sqlQuery.trim()) return; setExecutingSQL(true); setSqlResult(null); setSqlError(null); try { const result = await DbManageService.executeSQL(sqlQuery); setSqlResult(result); toast.success(t('sqlExecSuccess')); } catch (err) { setSqlError(err instanceof Error ? err.message : '未知执行错误'); toast.error(t('sqlExecFailed')); } finally { setExecutingSQL(false); } }; // Preset SQL 选择器 const handlePresetSQLChange = (query: string) => { setSqlQuery(query); }; const handleClose = () => { setSqlResult(null); setSqlError(null); onClose(); }; return (
{/* 顶部控制与标题 */}

{t('sqlQueryTerminal')}

{dbType === 'postgres' ? 'PostgreSQL Connected' : 'SQLite Connected'}
{/* 类似于 VS Code 的单个整体编辑器+结果区域,固定高度,有分水岭拖拽调整大小 */}
{/* 顶部编辑工具栏 */}
{t('sqlEditor')}
{/* 快速模板 */}
{t('quickTemplates')}
{/* 控制按钮 */}
{/* 上半区:编辑器输入框 */}
setSqlQuery(value)} className='h-full text-xs font-mono' basicSetup={{ lineNumbers: true, foldGutter: true, dropCursor: true, allowMultipleSelections: false, indentOnInput: true, }} />
{/* 拖动分割线 */}
{/* 下半区:查看结果 (可滑动查看) */}
{/* 结果栏工具提示 */}
{t('executionOutput')} {sqlResult && ( {t('type')}: {sqlResult.type.toUpperCase()} | {t('duration')}:{' '} {sqlResult.execution_time_ms} ms )}
{/* 滚动结果内容 */}
{executingSQL && (
{t('executingQuery')}
)} {sqlError && (

SQL 执行报错 (Error):

{sqlError}
)} {sqlResult && (
{sqlResult.type === 'select' && sqlResult.columns && sqlResult.columns.length > 0 && (
{sqlResult.columns.map((col) => ( {col} ))} {sqlResult.results && sqlResult.results.length > 0 ? ( sqlResult.results.map((row, rIndex) => ( {sqlResult.columns!.map((col) => ( {formatCellValue(row[col])} ))} )) ) : ( {t('queryResultEmpty')} )}
)} {sqlResult.type === 'exec' && (

{t('sqlExecSuccessTitle')}

{t('affectedRows', { rows: sqlResult.affected_rows, ms: sqlResult.execution_time_ms, })}

)}
)} {!executingSQL && !sqlResult && !sqlError && (
{t('editorReady')}
)}
); }