From 18430f37170f223f1a9da93585ed90708e8a0d48 Mon Sep 17 00:00:00 2001 From: logan Date: Thu, 28 May 2026 15:09:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Redis=E6=9F=A5=E8=AF=A2=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E4=BC=98=E5=8C=96=20=E2=80=94=20=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E6=8C=89=E9=92=AE+JSON=E8=87=AA=E5=8A=A8=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96+=E8=AF=AD=E4=B9=89=E5=8C=96=E5=88=97=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. GET/HGET/TTL等单值命令: columns改为[key,value]替代[command,result] 2. DBSIZE等无key命令保持[command,result]格式 3. 前端添加复制按钮(Clipboard API), 2秒反馈 4. JSON值自动检测并格式化显示(whitespace-pre-wrap) 5. 结果区最大高度350px→500px 6. cost_ms字段兼容处理 --- backend/internal/handler/instance_detail.go | 24 ++++++-- frontend/src/pages/QueriesPage.tsx | 67 ++++++++++++++++++--- 2 files changed, 77 insertions(+), 14 deletions(-) diff --git a/backend/internal/handler/instance_detail.go b/backend/internal/handler/instance_detail.go index dce6ac4..b102a90 100644 --- a/backend/internal/handler/instance_detail.go +++ b/backend/internal/handler/instance_detail.go @@ -702,11 +702,25 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string } columns = []string{"field", "value"} } else { - rows = append(rows, map[string]interface{}{ - "command": command, - "result": fmt.Sprintf("%v", v), - }) - columns = []string{"command", "result"} + // Single value result (GET, HGET, TYPE, TTL, etc.) + // Try to extract key name from command + keyName := "" + if len(parts) > 1 { + keyName = parts[1] + } + if keyName != "" { + rows = append(rows, map[string]interface{}{ + "key": keyName, + "value": fmt.Sprintf("%v", v), + }) + columns = []string{"key", "value"} + } else { + rows = append(rows, map[string]interface{}{ + "command": command, + "result": fmt.Sprintf("%v", v), + }) + columns = []string{"command", "result"} + } } } diff --git a/frontend/src/pages/QueriesPage.tsx b/frontend/src/pages/QueriesPage.tsx index 261d8ee..5e72cef 100644 --- a/frontend/src/pages/QueriesPage.tsx +++ b/frontend/src/pages/QueriesPage.tsx @@ -5,7 +5,7 @@ import { StarOff, Download, Search, ArrowUpDown, Database, Table2, ArrowUp, ArrowDown, Eye, Hash, PanelLeftClose, Columns3, KeyRound, FolderOpen, Server, Zap, ClipboardPaste, - History, PanelRightClose, PanelRightOpen, + History, PanelRightClose, PanelRightOpen, Copy, Check, } from 'lucide-react'; // Card components replaced by design system utility classes import { useActionPermission } from '@/hooks/useAuth'; @@ -264,6 +264,46 @@ export default function QueriesPage() { downloadCSV(`query_result_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.csv`, exportToCSV(result.columns, result.rows)); }, [result]); + /* ---- Copy Result ---- */ + const [copied, setCopied] = useState(false); + const handleCopyResult = useCallback(() => { + if (!result) return; + let text = ''; + if (result.rows?.length && result.columns?.length) { + // Format as readable text + const lines = result.rows.map((row: Record) => + result.columns.map((col: string) => { + const val = String(row[col] ?? ''); + // Try to pretty-format JSON values + try { + const parsed = JSON.parse(val); + if (typeof parsed === 'object') return JSON.stringify(parsed, null, 2); + } catch { /* not JSON */ } + return val; + }).join('\t') + ); + text = [result.columns.join('\t'), ...lines].join('\n'); + } else { + text = JSON.stringify(result, null, 2); + } + navigator.clipboard.writeText(text).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }, [result]); + + /* ---- Smart cell value formatter (detect JSON) ---- */ + const formatCellValue = useCallback((val: unknown) => { + const str = String(val ?? ''); + try { + const parsed = JSON.parse(str); + if (typeof parsed === 'object' && parsed !== null) { + return { text: JSON.stringify(parsed, null, 2), isJson: true }; + } + } catch { /* not JSON */ } + return { text: str, isJson: false }; + }, []); + /* ---- History ---- */ const toggleFavorite = useCallback((id: number) => { setHistory(prev => { @@ -439,21 +479,27 @@ export default function QueriesPage() { 查询结果
- {result.execution_time_ms}ms + {result.execution_time_ms || result.cost_ms || 0}ms {result.rows?.length || 0} 行 {result.columns?.length || 0} 列 {result.rows && result.rows.length > 0 && ( - + <> + + + )}
{result.columns && result.columns.length > 0 ? ( -
+
@@ -481,9 +527,12 @@ export default function QueriesPage() { {result.columns.map(col => { const isSensitive = sensitiveKeywords.some(k => col.toLowerCase().includes(k)); const cellValue = row[col] ?? ''; + const { text: displayText, isJson } = formatCellValue(cellValue); return ( - ); })}
- {isSensitive ? {maskData(String(cellValue))} : String(cellValue)} + + {isSensitive ? {maskData(String(cellValue))} : + isJson ? {displayText} : + String(cellValue)}