🐛 fix(redis): 修复命令页暗色主题显示异常

- 主题适配:Redis 命令输入区、工具栏、拖拽条和输出区统一接入 workbench 主题
- 编辑器修复:Monaco 命令输入框按暗色/亮色切换 transparent 主题
- 输出修复:暗色主题下输出区使用深色背景与可见文字颜色
- 布局修复:限制输入区拖拽高度,避免压缩底部输出区
- 测试覆盖:新增 Redis 命令页布局回归测试
This commit is contained in:
Syngnat
2026-05-26 09:29:52 +08:00
parent 98418ec5c3
commit 0d9344ff19
2 changed files with 242 additions and 26 deletions

View File

@@ -1,9 +1,16 @@
import React, { useState, useCallback, useRef, useEffect } from 'react';
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { Button, Space, message } from 'antd';
import { PlayCircleOutlined, ClearOutlined } from '@ant-design/icons';
import { useStore } from '../store';
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
import Editor, { type OnMount } from './MonacoEditor';
import {
isMacLikePlatform,
normalizeBlurForPlatform,
normalizeOpacityForPlatform,
resolveAppearanceValues,
} from '../utils/appearance';
import { buildRedisWorkbenchTheme } from './redisViewerWorkbenchTheme';
interface RedisCommandEditorProps {
connectionId: string;
@@ -18,6 +25,26 @@ interface CommandResult {
durationMs: number;
}
export const REDIS_COMMAND_EDITOR_MIN_HEIGHT = 120;
export const REDIS_COMMAND_OUTPUT_MIN_HEIGHT = 240;
export const REDIS_COMMAND_RESIZER_HEIGHT = 8;
export const clampRedisCommandEditorHeight = (
requestedHeight: number,
containerHeight: number | undefined,
): number => {
const minHeight = REDIS_COMMAND_EDITOR_MIN_HEIGHT;
const fallbackMaxHeight = 800;
const maxHeight = containerHeight
? Math.max(
minHeight,
containerHeight - REDIS_COMMAND_OUTPUT_MIN_HEIGHT - REDIS_COMMAND_RESIZER_HEIGHT,
)
: fallbackMaxHeight;
return Math.min(Math.max(requestedHeight, minHeight), maxHeight);
};
// 智能解析 Redis 脚本块,保护多行引号内的换行符
function parseRedisScriptBlocks(script: string): string[] {
const blocks: string[] = [];
@@ -79,8 +106,19 @@ function parseRedisScriptBlocks(script: string): string[] {
}
const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, redisDB }) => {
const { connections } = useStore();
const connections = useStore(state => state.connections);
const theme = useStore(state => state.theme);
const appearance = useStore(state => state.appearance);
const connection = connections.find(c => c.id === connectionId);
const darkMode = theme === 'dark';
const resolvedAppearance = resolveAppearanceValues(appearance);
const opacity = normalizeOpacityForPlatform(resolvedAppearance.opacity);
const blur = normalizeBlurForPlatform(resolvedAppearance.blur);
const disableLocalBackdropFilter = isMacLikePlatform();
const workbenchTheme = useMemo(
() => buildRedisWorkbenchTheme({ darkMode, opacity, blur, disableBackdropFilter: disableLocalBackdropFilter }),
[blur, darkMode, disableLocalBackdropFilter, opacity, appearance.uiVersion],
);
const [command, setCommand] = useState('');
const [results, setResults] = useState<CommandResult[]>([]);
@@ -294,11 +332,11 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
const delta = e.clientY - dragRef.current.startY;
let newHeight = dragRef.current.startHeight + delta;
// 限制高度
const minHeight = 100;
const maxHeight = containerRef.current ? containerRef.current.clientHeight - 100 : 800;
if (newHeight < minHeight) newHeight = minHeight;
if (newHeight > maxHeight) newHeight = maxHeight;
// 限制输入区高度,避免拖拽后压缩掉底部输出区。
newHeight = clampRedisCommandEditorHeight(
newHeight,
containerRef.current?.clientHeight,
);
setEditorHeight(newHeight);
@@ -323,13 +361,36 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
}
return (
<div ref={containerRef} style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', background: '#fff' }}>
<div
ref={containerRef}
data-redis-command-editor="true"
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
overflow: 'hidden',
background: workbenchTheme.appBg,
color: workbenchTheme.textPrimary,
backdropFilter: workbenchTheme.backdropFilter,
WebkitBackdropFilter: workbenchTheme.backdropFilter,
}}
>
{/* Editor Top Pane */}
<div style={{ height: editorHeight, minHeight: 100, display: 'flex', flexDirection: 'column' }}>
<div style={{ padding: '8px 12px', borderBottom: '1px solid #f0f0f0', display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: '#fdfdfd' }}>
<div
data-redis-command-input-pane="true"
style={{
height: editorHeight,
minHeight: REDIS_COMMAND_EDITOR_MIN_HEIGHT,
display: 'flex',
flexDirection: 'column',
background: workbenchTheme.panelBg,
borderBottom: workbenchTheme.panelBorder,
}}
>
<div style={{ padding: '8px 12px', borderBottom: workbenchTheme.panelBorder, display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: workbenchTheme.panelBgStrong }}>
<Space>
<span style={{ fontWeight: 600 }}>Redis Console</span>
<span style={{ color: '#888', fontSize: 13, background: '#f0f0f0', padding: '2px 8px', borderRadius: 12 }}>db{redisDB}</span>
<span style={{ fontWeight: 600, color: workbenchTheme.textPrimary }}>Redis Console</span>
<span style={{ color: workbenchTheme.textSecondary, fontSize: 13, background: workbenchTheme.statusTagMutedBg, border: workbenchTheme.statusTagMutedBorder, padding: '2px 8px', borderRadius: 12 }}>db{redisDB}</span>
</Space>
<Space>
<Button
@@ -342,8 +403,9 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
</Button>
</Space>
</div>
<div style={{ flex: 1, position: 'relative' }}>
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
<Editor
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
defaultLanguage="redis"
language="redis"
value={command}
@@ -366,34 +428,57 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
{/* Resizer Handle */}
<div
className="horizontal-resizer"
data-redis-command-resizer="true"
onMouseDown={handleDragStart}
style={{
height: 8,
height: REDIS_COMMAND_RESIZER_HEIGHT,
cursor: 'row-resize',
background: '#f0f0f0',
borderTop: '1px solid #e0e0e0',
borderBottom: '1px solid #e0e0e0',
background: workbenchTheme.panelBgStrong,
borderTop: workbenchTheme.panelBorder,
borderBottom: workbenchTheme.panelBorder,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 10
}}
>
<div style={{ width: 40, height: 4, background: '#ccc', borderRadius: 2 }} />
<div style={{ width: 40, height: 4, background: workbenchTheme.textMuted, borderRadius: 2, opacity: 0.6 }} />
</div>
{/* Results Terminal Bottom Pane */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<div style={{ padding: '4px 12px', background: '#252526', display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid #333' }}>
<span style={{ color: '#ccc', fontSize: 12 }}>Execution Output</span>
<Button type="text" size="small" icon={<ClearOutlined />} onClick={handleClear} style={{ color: '#aaa' }}></Button>
<div
data-redis-command-output-pane="true"
style={{
flex: 1,
minHeight: REDIS_COMMAND_OUTPUT_MIN_HEIGHT,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
background: darkMode ? '#111418' : workbenchTheme.panelBg,
}}
>
<div style={{ padding: '4px 12px', background: darkMode ? '#1b1f27' : workbenchTheme.panelBgStrong, display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: workbenchTheme.panelBorder }}>
<span style={{ color: workbenchTheme.textSecondary, fontSize: 12 }}>Execution Output</span>
<Button type="text" size="small" icon={<ClearOutlined />} onClick={handleClear} style={{ color: workbenchTheme.textSecondary }}></Button>
</div>
<div style={{ flex: 1, overflow: 'auto', background: '#1e1e1e', color: '#d4d4d4', fontFamily: '"Consolas", "Courier New", monospace', fontSize: 13, padding: 12 }}>
<div
data-redis-command-output-terminal="true"
style={{
flex: 1,
minHeight: 0,
overflow: 'auto',
background: darkMode ? '#111418' : '#f8fafc',
color: darkMode ? '#d4d4d4' : '#0f172a',
fontFamily: '"Consolas", "Courier New", monospace',
fontSize: 13,
padding: 12,
}}
>
{results.length === 0 ? (
<div style={{ color: '#666', textAlign: 'center', marginTop: 40 }}>
<div style={{ color: workbenchTheme.textMuted, textAlign: 'center', marginTop: 40 }}>
<div></div>
<div style={{ fontSize: 12, marginTop: 12 }}>
Tips: <code></code> <code style={{ color: '#999' }}>Ctrl + Enter</code>
Tips: <code></code> <code style={{ color: workbenchTheme.textSecondary }}>Ctrl + Enter</code>
</div>
</div>
) : (
@@ -402,7 +487,7 @@ const RedisCommandEditor: React.FC<RedisCommandEditorProps> = ({ connectionId, r
<div style={{ color: '#569cd6', marginBottom: 6, fontWeight: 'bold' }}>
<span style={{ color: '#4CAF50', marginRight: 8 }}></span>
{item.command}
<span style={{ color: '#666', fontSize: 11, marginLeft: 12, fontWeight: 'normal' }}>[{item.durationMs}ms]</span>
<span style={{ color: workbenchTheme.textMuted, fontSize: 11, marginLeft: 12, fontWeight: 'normal' }}>[{item.durationMs}ms]</span>
</div>
<div style={{ paddingLeft: 20 }}>