feat: Redis单值结果直接展示+JSON中文格式化+悬浮复制
1. GET/HGET/TTL等单值命令: 直接展示raw值, 不再套table 2. JSON自动检测+格式化(中文正常显示, 2空格缩进) 3. 非JSON值(如false/数字)直接文本展示 4. 代码块右上角悬浮复制按钮(hover显示) 5. 工具栏显示值类型(JSON/String)替代行列数
This commit is contained in:
1 parent
18430f3717
commit
03ff845485
2 files changed
+60
-15
No files matched your search
@@ -14,6 +14,8 @@ import (
|
||||
"sql-platform/internal/middleware"
|
||||
"strings"
|
||||
"strconv"
|
||||
"encoding/json"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// GET /api/v1/instances/:id/detail/databases
|
||||
@@ -703,25 +705,34 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string
|
||||
columns = []string{"field", "value"}
|
||||
} else {
|
||||
// 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"}
|
||||
rawValue := fmt.Sprintf("%v", v)
|
||||
// Try to pretty-format JSON with Chinese characters preserved
|
||||
var prettyJSON string
|
||||
var parsed interface{}
|
||||
if json.Unmarshal([]byte(rawValue), &parsed) == nil {
|
||||
switch parsed.(type) {
|
||||
case map[string]interface{}, []interface{}:
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
enc.Encode(parsed)
|
||||
prettyJSON = strings.TrimRight(buf.String(), "\n")
|
||||
}
|
||||
}
|
||||
return gin.H{
|
||||
"columns": []string{},
|
||||
"rows": []interface{}{},
|
||||
"total": 0,
|
||||
"raw_value": prettyJSON != "",
|
||||
"raw_key": keyName,
|
||||
"raw_text": rawValue,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
|
||||
@@ -25,6 +25,11 @@ interface QueryResult {
|
||||
rows: Record<string, unknown>[];
|
||||
affected_rows: number;
|
||||
execution_time_ms: number;
|
||||
cost_ms?: number;
|
||||
raw_value?: boolean;
|
||||
raw_key?: string;
|
||||
raw_text?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface QueryHistoryItem {
|
||||
@@ -480,9 +485,15 @@ export default function QueriesPage() {
|
||||
</span>
|
||||
<div className="flex items-center gap-3 text-xs dg-text-secondary">
|
||||
<span className="flex items-center gap-1"><Clock className="w-3 h-3" />{result.execution_time_ms || result.cost_ms || 0}ms</span>
|
||||
{result.raw_text !== undefined ? (
|
||||
<span>{result.raw_value ? 'JSON' : typeof result.raw_text === 'string' ? 'String' : 'Value'}</span>
|
||||
) : (
|
||||
<>
|
||||
<span>{result.rows?.length || 0} 行</span>
|
||||
<span>{result.columns?.length || 0} 列</span>
|
||||
{result.rows && result.rows.length > 0 && (
|
||||
</>
|
||||
)}
|
||||
{(result.rows?.length > 0 || result.raw_text !== undefined) && (
|
||||
<>
|
||||
<button onClick={handleCopyResult}
|
||||
className="dg-btn dg-btn-ghost dg-btn-sm gap-1">
|
||||
@@ -498,7 +509,30 @@ export default function QueriesPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-0">
|
||||
{result.columns && result.columns.length > 0 ? (
|
||||
{result.raw_text !== undefined ? (
|
||||
/* Raw value display (Redis GET/HGET/TTL etc.) */
|
||||
<div className="p-4">
|
||||
{result.raw_key && (
|
||||
<div className="text-xs text-[var(--dg-text-muted)] mb-2 font-mono">{result.raw_key}</div>
|
||||
)}
|
||||
<div className="relative group">
|
||||
<pre className={`text-sm leading-relaxed overflow-auto max-h-[450px] p-3 rounded-lg font-mono ${
|
||||
result.raw_value
|
||||
? 'bg-[var(--dg-bg-primary)] text-blue-300 whitespace-pre-wrap break-all'
|
||||
: 'bg-[var(--dg-bg-primary)] text-[var(--dg-text-primary)] whitespace-pre-wrap break-all'
|
||||
}`}>{result.raw_value
|
||||
? (() => { try { return JSON.stringify(JSON.parse(String(result.raw_text)), null, 2); } catch { return String(result.raw_text); } })()
|
||||
: String(result.raw_text)
|
||||
}</pre>
|
||||
<button onClick={() => {
|
||||
const text = result.raw_value ? (() => { try { return JSON.stringify(JSON.parse(String(result.raw_text)), null, 2); } catch { return String(result.raw_text); } })() : String(result.raw_text);
|
||||
navigator.clipboard.writeText(text).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); });
|
||||
}} className="absolute top-2 right-2 dg-btn dg-btn-ghost dg-btn-sm opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
{copied ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : result.columns && result.columns.length > 0 ? (
|
||||
<div ref={resultContainerRef} onScroll={handleResultScroll} className="overflow-auto max-h-[500px]">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="sticky top-0 z-10">
|
||||
|
||||
Reference in new issue
Block a user