"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 {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" import services from "@/lib/services" /** * 格式化表格单元格内容 */ 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) { // 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 services.dbManage.executeSQL(sqlQuery) setSqlResult(result) toast.success("SQL 执行成功") } catch (err) { setSqlError(err instanceof Error ? err.message : "未知执行错误") toast.error("SQL 执行失败") } finally { setExecutingSQL(false) } } // Preset SQL 选择器 const handlePresetSQLChange = (query: string) => { setSqlQuery(query) } const handleClose = () => { setSqlResult(null) setSqlError(null) onClose() } return (
{/* 顶部控制与标题 */}

SQL 查询终端

{dbType === "postgres" ? "PostgreSQL Connected" : "SQLite Connected"}
{/* 类似于 VS Code 的单个整体编辑器+结果区域,固定高度,有分水岭拖拽调整大小 */}
{/* 顶部编辑工具栏 */}
SQL 编辑器
{/* 快速模板 */}
快速模板:
{/* 控制按钮 */}
{/* 上半区:编辑器输入框 */}
setSqlQuery(value)} className="h-full text-xs font-mono" basicSetup={{ lineNumbers: true, foldGutter: true, dropCursor: true, allowMultipleSelections: false, indentOnInput: true, }} />
{/* 拖动分割线 */}
{/* 下半区:查看结果 (可滑动查看) */}
{/* 结果栏工具提示 */}
执行输出 {sqlResult && ( 类型: {sqlResult.type.toUpperCase()} | 耗时: {sqlResult.execution_time_ms} ms )}
{/* 滚动结果内容 */}
{executingSQL && (
正在在数据库执行查询,请稍候...
)} {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])} ))} )) ) : ( 查询结果为空 )}
)} {sqlResult.type === "exec" && (

SQL 执行成功

受影响行数: {sqlResult.affected_rows} 行,耗时 {sqlResult.execution_time_ms} 毫秒。

)}
)} {!executingSQL && !sqlResult && !sqlError && (
编辑器就绪,请在上方编写 SQL 并运行查看结果
)}
) }