fix: 查询结果固定布局 - 编辑器/sidebar不被结果挤走 + 结果区可折叠
This commit is contained in:
1 parent
471221fc47
commit
0afc9947fb
1 file changed
+169
-144
+169
-144
@@ -126,6 +126,7 @@ export default function QueriesPage() {
|
||||
const [templateSearch, setTemplateSearch] = useState('');
|
||||
const [templateCategory, setTemplateCategory] = useState('all');
|
||||
const [showSidebar, setShowSidebar] = useState(true);
|
||||
const [showResult, setShowResult] = useState(true);
|
||||
const [showHistory, setShowHistory] = useState(false);
|
||||
const [sortState, setSortState] = useState<SortState>({ column: null, direction: null });
|
||||
const [rowHeight] = useState(36);
|
||||
@@ -328,134 +329,140 @@ export default function QueriesPage() {
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-3rem)] gap-0">
|
||||
{/* ====== 主区域 ====== */}
|
||||
<div className="flex-1 flex flex-col min-w-0 overflow-y-auto p-5 space-y-4">
|
||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
|
||||
{/* -- 顶栏: 实例+库选择 -- */}
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
{selectedInstance && dbTypeIcon[dbType] && (
|
||||
<img src={dbTypeIcon[dbType]} alt="" className="w-5 h-5 object-contain" />
|
||||
)}
|
||||
<select value={instanceId} onChange={e => setInstanceId(e.target.value)}
|
||||
className="px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none min-w-[200px]">
|
||||
<option value="">选择实例</option>
|
||||
{instances.map(inst => (
|
||||
<option key={inst.id} value={inst.id}>
|
||||
{inst.name} ({dbTypeLabel[inst.db_type] || inst.db_type})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{dbType === 'redis' ? (
|
||||
<select value={database || '0'} onChange={e => setDatabase(e.target.value)}
|
||||
className="px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none w-24">
|
||||
{Array.from({ length: 16 }, (_, i) => <option key={i} value={String(i)}>DB{i}</option>)}
|
||||
<div className="shrink-0 px-5 pt-4 pb-2">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
{selectedInstance && dbTypeIcon[dbType] && (
|
||||
<img src={dbTypeIcon[dbType]} alt="" className="w-5 h-5 object-contain" />
|
||||
)}
|
||||
<select value={instanceId} onChange={e => setInstanceId(e.target.value)}
|
||||
className="px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none min-w-[200px]">
|
||||
<option value="">选择实例</option>
|
||||
{instances.map(inst => (
|
||||
<option key={inst.id} value={inst.id}>
|
||||
{inst.name} ({dbTypeLabel[inst.db_type] || inst.db_type})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<select value={database} onChange={e => setDatabase(e.target.value)}
|
||||
className="px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none min-w-[160px]">
|
||||
<option value="">{loadingDbs ? '加载中...' : '选择数据库'}</option>
|
||||
{databases.map(db => <option key={db} value={db}>{db}</option>)}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 右侧工具按钮组 */}
|
||||
<div className="flex items-center gap-2">
|
||||
{instanceId && (
|
||||
<button onClick={() => setShowSidebar(!showSidebar)}
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${showSidebar ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
<Database className="w-3.5 h-3.5" />
|
||||
{showSidebar ? '隐藏' : '导航'}
|
||||
{dbType === 'redis' ? (
|
||||
<select value={database || '0'} onChange={e => setDatabase(e.target.value)}
|
||||
className="px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none w-24">
|
||||
{Array.from({ length: 16 }, (_, i) => <option key={i} value={String(i)}>DB{i}</option>)}
|
||||
</select>
|
||||
) : (
|
||||
<select value={database} onChange={e => setDatabase(e.target.value)}
|
||||
className="px-3 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none min-w-[160px]">
|
||||
<option value="">{loadingDbs ? '加载中...' : '选择数据库'}</option>
|
||||
{databases.map(db => <option key={db} value={db}>{db}</option>)}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 右侧工具按钮组 */}
|
||||
<div className="flex items-center gap-2">
|
||||
{instanceId && (
|
||||
<button onClick={() => setShowSidebar(!showSidebar)}
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${showSidebar ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
<Database className="w-3.5 h-3.5" />
|
||||
{showSidebar ? '隐藏' : '导航'}
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setShowTemplates(!showTemplates)}
|
||||
className="dg-btn dg-btn-ghost dg-btn-sm">
|
||||
<Sparkles className="w-3.5 h-3.5 text-blue-400" />
|
||||
模板 ({templates.length})
|
||||
<ChevronDown className={`w-3 h-3 transition-transform ${showTemplates ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setShowTemplates(!showTemplates)}
|
||||
className="dg-btn dg-btn-ghost dg-btn-sm">
|
||||
<Sparkles className="w-3.5 h-3.5 text-blue-400" />
|
||||
模板 ({templates.length})
|
||||
<ChevronDown className={`w-3 h-3 transition-transform ${showTemplates ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
<button onClick={() => setShowHistory(!showHistory)}
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${showHistory ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
{showHistory ? <PanelRightClose className="w-3.5 h-3.5" /> : <PanelRightOpen className="w-3.5 h-3.5" />}
|
||||
历史 {history.length > 0 && <span className="text-[10px] px-1 py-0.5 rounded-full bg-[var(--dg-bg-primary)]">{history.length}</span>}
|
||||
</button>
|
||||
<button onClick={handleExecute} disabled={executing || !instanceId}
|
||||
className="dg-btn dg-btn-primary">
|
||||
{executing ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
|
||||
{executing ? '执行中...' : '执行'}
|
||||
</button>
|
||||
<button onClick={() => setShowHistory(!showHistory)}
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${showHistory ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
{showHistory ? <PanelRightClose className="w-3.5 h-3.5" /> : <PanelRightOpen className="w-3.5 h-3.5" />}
|
||||
历史 {history.length > 0 && <span className="text-[10px] px-1 py-0.5 rounded-full bg-[var(--dg-bg-primary)]">{history.length}</span>}
|
||||
</button>
|
||||
<button onClick={handleExecute} disabled={executing || !instanceId}
|
||||
className="dg-btn dg-btn-primary">
|
||||
{executing ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
|
||||
{executing ? '执行中...' : '执行'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* -- 模板选择器 -- */}
|
||||
{showTemplates && (
|
||||
<div className="dg-card p-3">
|
||||
<div className="flex items-center gap-2 mb-3 flex-wrap">
|
||||
{templateCategories.map(cat => (
|
||||
<button key={cat} onClick={() => setTemplateCategory(cat)}
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${templateCategory === cat ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
{catLabel[cat] || cat}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex-1" />
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 dg-text-muted" />
|
||||
<input value={templateSearch} onChange={e => setTemplateSearch(e.target.value)}
|
||||
className="pl-7 pr-3 py-1 bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-md text-xs focus:border-blue-400 focus:outline-none w-40"
|
||||
placeholder="搜索模板..." />
|
||||
</div>
|
||||
</div>
|
||||
{filteredTemplates.length === 0 ? (
|
||||
<div className="text-center py-4 text-xs dg-text-muted">无匹配模板</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 max-h-48 overflow-y-auto">
|
||||
{filteredTemplates.map(tpl => (
|
||||
<button key={tpl.id} onClick={() => handleApplyTemplate(tpl)}
|
||||
className="dg-card text-left p-2.5 hover:bg-[var(--dg-bg-hover)] group"
|
||||
title={tpl.description}>
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-400 font-medium">{tpl.category}</span>
|
||||
<span className="text-sm font-medium group-hover:text-blue-400 transition-colors truncate">{tpl.name}</span>
|
||||
</div>
|
||||
<p className="text-xs dg-text-muted line-clamp-1">{tpl.description}</p>
|
||||
<div className="shrink-0 px-5 pb-2">
|
||||
<div className="dg-card p-3">
|
||||
<div className="flex items-center gap-2 mb-3 flex-wrap">
|
||||
{templateCategories.map(cat => (
|
||||
<button key={cat} onClick={() => setTemplateCategory(cat)}
|
||||
className={`dg-btn dg-btn-ghost dg-btn-sm ${templateCategory === cat ? 'bg-blue-500/15 text-blue-400 border-blue-500/30' : ''}`}>
|
||||
{catLabel[cat] || cat}
|
||||
</button>
|
||||
))}
|
||||
<div className="flex-1" />
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3 h-3 dg-text-muted" />
|
||||
<input value={templateSearch} onChange={e => setTemplateSearch(e.target.value)}
|
||||
className="pl-7 pr-3 py-1 bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded-md text-xs focus:border-blue-400 focus:outline-none w-40"
|
||||
placeholder="搜索模板..." />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{filteredTemplates.length === 0 ? (
|
||||
<div className="text-center py-4 text-xs dg-text-muted">无匹配模板</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 max-h-48 overflow-y-auto">
|
||||
{filteredTemplates.map(tpl => (
|
||||
<button key={tpl.id} onClick={() => handleApplyTemplate(tpl)}
|
||||
className="dg-card text-left p-2.5 hover:bg-[var(--dg-bg-hover)] group"
|
||||
title={tpl.description}>
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-400 font-medium">{tpl.category}</span>
|
||||
<span className="text-sm font-medium group-hover:text-blue-400 transition-colors truncate">{tpl.name}</span>
|
||||
</div>
|
||||
<p className="text-xs dg-text-muted line-clamp-1">{tpl.description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* -- 编辑器 + 侧边栏 -- */}
|
||||
<div className="flex gap-3 flex-1 min-h-0">
|
||||
{/* Schema 侧边栏 */}
|
||||
{showSidebar && instanceId && (
|
||||
<div className="w-[280px] shrink-0">
|
||||
<SchemaSidebar instanceId={instanceId} database={database} dbType={dbType}
|
||||
clusterMode={selectedInstance?.cluster_mode}
|
||||
onInsert={(text) => {
|
||||
const sep = dbType === 'redis' ? (sql.trim() ? '\n' : '') : (sql.trim() ? ' ' : '');
|
||||
setSql(prev => prev + sep + text + (dbType === 'redis' ? '' : ' '));
|
||||
}}
|
||||
onDatabaseChange={setDatabase}
|
||||
singleDb={!!database} />
|
||||
</div>
|
||||
)}
|
||||
{/* -- 编辑器 + 侧边栏 (上半区,不随结果滚动) -- */}
|
||||
<div className="shrink-0 px-5 pb-2">
|
||||
<div className="flex gap-3">
|
||||
{/* Schema 侧边栏 */}
|
||||
{showSidebar && instanceId && (
|
||||
<div className="w-[280px] shrink-0">
|
||||
<SchemaSidebar instanceId={instanceId} database={database} dbType={dbType}
|
||||
clusterMode={selectedInstance?.cluster_mode}
|
||||
onInsert={(text) => {
|
||||
const sep = dbType === 'redis' ? (sql.trim() ? '\n' : '') : (sql.trim() ? ' ' : '');
|
||||
setSql(prev => prev + sep + text + (dbType === 'redis' ? '' : ' '));
|
||||
}}
|
||||
onDatabaseChange={setDatabase}
|
||||
singleDb={!!database} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SQL 编辑器 */}
|
||||
<div className="flex-1 min-w-0 flex flex-col">
|
||||
<div className="dg-card overflow-hidden flex-1 flex flex-col">
|
||||
<SqlEditor value={sql} onChange={setSql} placeholder={placeholderMap[dbType]}
|
||||
language={editorLang} height="240px" onExecute={handleExecute}
|
||||
schema={(dbType === 'mysql' || dbType === 'postgres' || dbType === 'postgresql') ? editorSchema : undefined} />
|
||||
<div className="flex items-center justify-between px-3 py-1.5 bg-[var(--dg-bg-tertiary)] border-t border-[var(--dg-border)] text-[11px] dg-text-muted">
|
||||
<span className="flex items-center gap-1.5">
|
||||
{dbTypeIcon[dbType] && <img src={dbTypeIcon[dbType]} alt="" className="w-3 h-3 object-contain" />}
|
||||
{dbTypeLabel[dbType]} 模式 · {sql.length} 字符
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-1.5 py-0.5 rounded bg-[var(--dg-bg-primary)] border border-[var(--dg-border)]">Ctrl+Enter 执行</span>
|
||||
<span className="px-1.5 py-0.5 rounded bg-[var(--dg-bg-primary)] border border-[var(--dg-border)]">Ctrl+Shift+F 格式化</span>
|
||||
{/* SQL 编辑器 */}
|
||||
<div className="flex-1 min-w-0 flex flex-col">
|
||||
<div className="dg-card overflow-hidden flex flex-col">
|
||||
<SqlEditor value={sql} onChange={setSql} placeholder={placeholderMap[dbType]}
|
||||
language={editorLang} height="200px" onExecute={handleExecute}
|
||||
schema={(dbType === 'mysql' || dbType === 'postgres' || dbType === 'postgresql') ? editorSchema : undefined} />
|
||||
<div className="flex items-center justify-between px-3 py-1.5 bg-[var(--dg-bg-tertiary)] border-t border-[var(--dg-border)] text-[11px] dg-text-muted">
|
||||
<span className="flex items-center gap-1.5">
|
||||
{dbTypeIcon[dbType] && <img src={dbTypeIcon[dbType]} alt="" className="w-3 h-3 object-contain" />}
|
||||
{dbTypeLabel[dbType]} 模式 · {sql.length} 字符
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-1.5 py-0.5 rounded bg-[var(--dg-bg-primary)] border border-[var(--dg-border)]">Ctrl+Enter 执行</span>
|
||||
<span className="px-1.5 py-0.5 rounded bg-[var(--dg-bg-primary)] border border-[var(--dg-border)]">Ctrl+Shift+F 格式化</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -464,41 +471,55 @@ export default function QueriesPage() {
|
||||
|
||||
{/* -- 错误提示 -- */}
|
||||
{error && (
|
||||
<div className="flex items-center gap-2 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-sm text-red-400">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0" />{error}
|
||||
<div className="shrink-0 px-5 pb-2">
|
||||
<div className="flex items-center gap-2 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-sm text-red-400">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0" />{error}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* -- 查询结果 -- */}
|
||||
{/* -- 查询结果 (下半区,独立滚动) -- */}
|
||||
{result && (
|
||||
<div className="dg-card overflow-hidden">
|
||||
<div className="border-b border-[var(--dg-border)] py-2.5 px-4">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<span className="text-sm font-semibold flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-400" />
|
||||
查询结果
|
||||
</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' : 'String'}</span>
|
||||
) : (
|
||||
<>
|
||||
<span>{result.total || result.rows?.length || 0} 总行</span>
|
||||
<span>{result.rows?.length || 0} 行/页</span>
|
||||
<span>{result.columns?.length || 0} 列</span>
|
||||
</>
|
||||
)}
|
||||
{result.rows?.length > 0 && (
|
||||
<button onClick={handleExportResult}
|
||||
className="dg-btn dg-btn-ghost dg-btn-sm">
|
||||
<Download className="w-3 h-3" />导出 CSV
|
||||
</button>
|
||||
)}
|
||||
<div className="flex-1 min-h-0 px-5 pb-4 overflow-hidden flex flex-col">
|
||||
<div className="dg-card overflow-hidden flex-1 flex flex-col">
|
||||
{/* 结果标题栏 + 折叠按钮 */}
|
||||
<div
|
||||
className="shrink-0 border-b border-[var(--dg-border)] py-2 px-4 cursor-pointer hover:bg-[var(--dg-bg-hover)] transition-colors"
|
||||
onClick={() => setShowResult(!showResult)}
|
||||
>
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<span className="text-sm font-semibold flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-400" />
|
||||
查询结果
|
||||
{showResult
|
||||
? <ChevronDown className="w-3.5 h-3.5 text-[var(--dg-text-muted)]" />
|
||||
: <ChevronRight className="w-3.5 h-3.5 text-[var(--dg-text-muted)]" />
|
||||
}
|
||||
</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' : 'String'}</span>
|
||||
) : (
|
||||
<>
|
||||
<span>{result.total || result.rows?.length || 0} 总行</span>
|
||||
<span>{result.rows?.length || 0} 行/页</span>
|
||||
<span>{result.columns?.length || 0} 列</span>
|
||||
</>
|
||||
)}
|
||||
{result.rows?.length > 0 && (
|
||||
<button onClick={(e) => { e.stopPropagation(); handleExportResult(); }}
|
||||
className="dg-btn dg-btn-ghost dg-btn-sm">
|
||||
<Download className="w-3 h-3" />导出 CSV
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-0">
|
||||
|
||||
{/* 结果内容(可折叠) */}
|
||||
{showResult && (
|
||||
<div className="flex-1 min-h-0 overflow-auto p-0">
|
||||
{result.raw_text !== undefined ? (
|
||||
/* Raw value display (Redis GET/HGET/TTL etc.) */
|
||||
<div className="p-4">
|
||||
@@ -517,7 +538,7 @@ export default function QueriesPage() {
|
||||
</div>
|
||||
</div>
|
||||
) : result.columns && result.columns.length > 0 ? (
|
||||
<div ref={resultContainerRef} onScroll={handleResultScroll} className="overflow-auto max-h-[500px]">
|
||||
<div ref={resultContainerRef} onScroll={handleResultScroll} className="overflow-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="sticky top-0 z-10">
|
||||
<tr className="dg-table-header">
|
||||
@@ -601,14 +622,18 @@ export default function QueriesPage() {
|
||||
{result.affected_rows > 0 && <span className="text-xs">影响行数: {result.affected_rows}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* -- 安全提示 -- */}
|
||||
<div className="flex items-center gap-2 px-3 py-2 bg-yellow-500/5 border border-yellow-500/15 rounded-lg text-xs text-[var(--dg-text-muted)]">
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-yellow-400 shrink-0" />
|
||||
查询结果中的敏感字段将自动脱敏。MySQL/PG 仅支持 SELECT 查询,Redis/MongoDB 支持只读命令。
|
||||
<div className="shrink-0 px-5 pb-3">
|
||||
<div className="flex items-center gap-2 px-3 py-2 bg-yellow-500/5 border border-yellow-500/15 rounded-lg text-xs text-[var(--dg-text-muted)]">
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-yellow-400 shrink-0" />
|
||||
查询结果中的敏感字段将自动脱敏。MySQL/PG 仅支持 SELECT 查询,Redis/MongoDB 支持只读命令。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user