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:
logan committed 2026-05-28 15:20:22 +08:00
1 parent 18430f3717
commit 03ff845485
2 files changed
+62 -17

No files matched your search

+38 -4
View File
@@ -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>
<span>{result.rows?.length || 0} 行</span>
<span>{result.columns?.length || 0} 列</span>
{result.rows && result.rows.length > 0 && (
{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?.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">