🎨 style(workbench): 统一 V2 工作台主题表面与结果筛选

- 统一 Redis、Nacos、对象区和查询结果区域的主题背景

- 让手动查询条件、筛选控件和数据表格铺满工作台表面

- 强化结果视图选中态并移除多余卡片边框

- 补充主题表面与布局回归测试
This commit is contained in:
Syngnat
2026-07-31 16:22:08 +08:00
parent 4402f4ae1b
commit cf891504f6
5 changed files with 194 additions and 43 deletions

View File

@@ -1327,6 +1327,10 @@ describe('DataGrid layout', () => {
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-commit-button:disabled {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-commit-button .gn-v2-toolbar-kbd {'),
);
const resultViewSelectionCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid-result-switcher .ant-segmented-item-selected,'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid-page-find-overlay {'),
);
expect(iconActionCss).toContain('width: 28px !important;');
expect(iconActionCss).toContain('min-width: 28px !important;');
expect(iconActionCss).toContain('padding-inline: 0 !important;');
@@ -1336,6 +1340,10 @@ describe('DataGrid layout', () => {
expect(commitDisabledCss).toContain('background: var(--gn-bg-active) !important;');
expect(commitDisabledCss).toContain('color: var(--gn-fg-5) !important;');
expect(commitDisabledCss).not.toContain('var(--gn-accent-soft)');
expect(resultViewSelectionCss).toContain('background: var(--gn-accent-strong, var(--gn-accent)) !important;');
expect(resultViewSelectionCss).toContain('border: 0 !important;');
expect(resultViewSelectionCss).toContain('box-shadow: none !important;');
expect(resultViewSelectionCss).toContain('color: var(--gn-on-accent, #fff) !important;');
expect(css).toContain(
'body[data-ui-version="v2"] .gn-v2-data-grid-statusbar .gn-v2-data-grid-toolbar-action.ant-btn {',
);
@@ -1654,7 +1662,7 @@ describe('DataGrid layout', () => {
expect(markup.match(/data-grid-query-copy-action="true"/g)?.length).toBe(1);
});
it('renders a quick WHERE condition editor when table filters are visible', () => {
it('renders a manual query condition editor when table filters are visible', () => {
const markup = renderDataGridWithI18n(
<DataGrid
data={[
@@ -1675,7 +1683,11 @@ describe('DataGrid layout', () => {
expect(markup).toContain('data-grid-quick-where="true"');
expect(markup).toContain('data-grid-quick-where-input="true"');
expect(markup).toContain('WHERE');
expect(markup).toContain('data-grid-quick-where-label="true"');
expect(markup).toContain('手动查询条件');
const manualConditionLabel = markup.match(/<span data-grid-quick-where-label="true"([^>]*)>/)?.[1] ?? '';
expect(manualConditionLabel).not.toContain('border');
expect(manualConditionLabel).not.toContain('background');
const englishMarkup = renderDataGridWithI18n(
<DataGrid
data={[
@@ -1695,8 +1707,10 @@ describe('DataGrid layout', () => {
{ preference: 'en-US' },
);
expect(englishMarkup).toContain('Enter the condition after WHERE');
expect(englishMarkup).not.toContain('输入 WHERE 后面的条件');
expect(englishMarkup).toContain('Manual query condition');
expect(englishMarkup).toContain('Enter a query condition');
expect(englishMarkup).not.toContain('Enter the condition after WHERE');
expect(englishMarkup).not.toContain('输入查询条件');
});
it('keeps quick WHERE input clipboard editing isolated from grid shortcuts', () => {
@@ -1708,6 +1722,35 @@ describe('DataGrid layout', () => {
expect(css).toContain('user-select: text !important;');
});
it('keeps V2 filter controls on the query workbench theme surface', () => {
const css = readV2ThemeCss();
const toolbarCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid-toolbar-frame'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid-toolbar-title'),
);
const filterCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-smart-filter-panel'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid-table-shell'),
);
const tableSurfaceCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid-table-shell'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead'),
);
expect(toolbarCss).toContain('background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;');
expect(filterCss).toContain('background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;');
expect(filterCss).toContain('padding-inline: 0 !important;');
expect(filterCss).toContain('.gn-v2-smart-filter-manual-input');
expect(filterCss).toContain('max-width: 680px !important;');
expect(filterCss).toContain('.gn-v2-smart-filter-panel .ant-select-selector');
expect(filterCss).toContain('.gn-v2-smart-filter-panel .ant-input-affix-wrapper');
expect(filterCss).not.toContain('background: var(--gn-bg-input)');
expect(filterCss).toContain('[data-grid-quick-where="true"] {\n min-height: 38px;\n padding-inline: 0 !important;\n margin-bottom: 8px !important;\n border: 0 !important;\n border-radius: 0 !important;');
expect(tableSurfaceCss).toContain('background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;');
expect(tableSurfaceCss).toContain('.gn-v2-data-grid .ant-table-container');
expect(tableSurfaceCss).toContain('.gn-v2-data-grid .ant-table-tbody-virtual-holder');
});
it('keeps DataGrid scroll synchronization throttled to animation frames', () => {
const source = readDataGridSource();
const secondaryActionsSource = readFileSync(new URL('./DataGridSecondaryActions.tsx', import.meta.url), 'utf8');

View File

@@ -687,25 +687,21 @@ const DataGridToolbarFrame: React.FC<DataGridToolbarFrameProps> = ({
}}
>
<span
data-grid-quick-where-label="true"
style={{
flex: '0 0 auto',
minWidth: 58,
height: 28,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 999,
background: darkMode ? 'rgba(24,144,255,0.18)' : 'rgba(24,144,255,0.10)',
border: `1px solid ${darkMode ? 'rgba(24,144,255,0.32)' : 'rgba(24,144,255,0.22)'}`,
color: selectionAccentHex,
minWidth: 0,
color: isV2Ui ? 'var(--gn-fg-3)' : selectionAccentHex,
fontSize: 12,
fontWeight: 700,
letterSpacing: '0.03em',
fontWeight: 600,
lineHeight: '28px',
whiteSpace: 'nowrap',
}}
>
WHERE
{translate('data_grid.filter.manual_query_condition')}
</span>
<AutoComplete
className={isV2Ui ? 'gn-v2-smart-filter-manual-input' : undefined}
value={quickWhereDraft}
options={quickWhereSuggestionOptions}
onChange={onQuickWhereDraftChange}

View File

@@ -0,0 +1,68 @@
import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
const readWorkbenchCss = (): string => readFileSync(
new URL('../styles/v2-theme-workbench.css', import.meta.url),
'utf8',
);
const readV2ThemeCss = (): string => readFileSync(
new URL('../v2-theme.css', import.meta.url),
'utf8',
);
const readSection = (css: string, startMarker: string, endMarker: string): string => {
const start = css.indexOf(startMarker);
const end = css.indexOf(endMarker, start + startMarker.length);
expect(start).toBeGreaterThanOrEqual(0);
expect(end).toBeGreaterThan(start);
return css.slice(start, end);
};
describe('V2 workbench theme surfaces', () => {
it('keeps Redis and Nacos workbench backgrounds on the secondary theme panel', () => {
const css = readWorkbenchCss();
const v2ThemeCss = readV2ThemeCss();
const redisRootCss = readSection(
v2ThemeCss,
'body[data-ui-version="v2"] .redis-viewer-workbench',
'body[data-ui-version="v2"] .redis-tree-expander-button:hover',
);
const redisCss = readSection(
css,
'/* ─── V2 Redis workbench ─ */',
'/* ─── V2 Nacos workbench',
);
const nacosCss = readSection(
css,
'/* ─── V2 Nacos workbench',
'/* ─── Nacos service discovery:',
);
expect(redisCss).toContain('background: var(--gn-bg-panel-2)');
expect(redisCss).not.toMatch(/background:\s*var\(--gn-bg-panel\)(?:\s*!important)?;/);
expect(redisRootCss).toContain('background: var(--gn-bg-panel-2) !important;');
expect(redisRootCss).not.toContain('background: var(--gn-bg-app) !important;');
expect(nacosCss).toContain('background: var(--gn-bg-panel-2)');
expect(nacosCss).not.toMatch(/background:\s*var\(--gn-bg-panel\)(?:\s*!important)?;/);
});
it('keeps sidebar and Nacos filter inputs on their surrounding theme surface', () => {
const css = readWorkbenchCss();
const v2ThemeCss = readV2ThemeCss();
const explorerSearchCss = readSection(
v2ThemeCss,
'body[data-ui-version="v2"] .gn-v2-explorer-search',
'body[data-ui-version="v2"] .gn-v2-explorer-filter-tabs',
);
const nacosFilterCss = readSection(
css,
'/* Filter row: grow with left pane width so long DataId/Group names fit better */',
'.gn-nacos-selection-toolbar',
);
expect(explorerSearchCss).toContain('background: var(--gn-bg-panel-2)');
expect(explorerSearchCss).not.toContain('background: var(--gn-bg-input)');
expect(nacosFilterCss).toContain('background: var(--gn-bg-panel-2) !important;');
});
});

View File

@@ -801,7 +801,7 @@ body[data-ui-version="v2"] .gn-v2-redis-workbench {
grid-template-rows: max-content minmax(0, 1fr);
gap: 0 !important;
padding: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
}
body[data-ui-version="v2"] .gn-v2-redis-sidebar {
@@ -812,7 +812,7 @@ body[data-ui-version="v2"] .gn-v2-redis-sidebar {
width: 100% !important;
min-width: 0 !important;
gap: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
}
body[data-ui-version="v2"] .gn-v2-redis-header,
@@ -822,7 +822,7 @@ body[data-ui-version="v2"] .gn-v2-redis-value-card,
body[data-ui-version="v2"] .gn-v2-redis-empty-value {
border: 0 !important;
border-radius: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
box-shadow: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
@@ -859,13 +859,13 @@ body[data-ui-version="v2"] .gn-v2-redis-workbench .ant-tree-checkbox {
body[data-ui-version="v2"] .gn-v2-redis-value-pane {
display: contents !important;
min-width: 360px !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
}
body[data-ui-version="v2"] .gn-v2-redis-value-layout {
display: contents !important;
gap: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
}
body[data-ui-version="v2"] .gn-v2-redis-value-top {
@@ -875,7 +875,7 @@ body[data-ui-version="v2"] .gn-v2-redis-value-top {
height: 100%;
min-height: 0;
gap: 0 !important;
background: var(--gn-bg-panel);
background: var(--gn-bg-panel-2);
}
body[data-ui-version="v2"] .gn-v2-redis-value-header {
@@ -895,7 +895,7 @@ body[data-ui-version="v2"] .gn-v2-redis-value-subtoolbar,
body[data-ui-version="v2"] .gn-v2-redis-value-actionbar {
border: 0 !important;
border-radius: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
box-shadow: none !important;
}
@@ -957,7 +957,7 @@ body[data-ui-version="v2"] .gn-v2-nacos-workbench {
flex-direction: column !important;
padding: 0 !important;
gap: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
color: var(--gn-fg-1);
}
@@ -1064,7 +1064,7 @@ body[data-ui-version="v2"] .gn-v2-nacos-split {
flex: 1 1 auto !important;
min-height: 0 !important;
gap: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
}
body[data-ui-version="v2"] .gn-v2-nacos-list-pane {
@@ -1080,7 +1080,7 @@ body[data-ui-version="v2"] .gn-v2-nacos-list-pane {
border: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
@@ -1095,7 +1095,7 @@ body[data-ui-version="v2"] .gn-v2-nacos-detail-pane {
border: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
@@ -1115,7 +1115,7 @@ body[data-ui-version="v2"] .gn-v2-nacos-pane-header {
border: 0 !important;
border-bottom: 1px solid var(--gn-br-1) !important;
border-radius: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
box-shadow: none !important;
}
@@ -1146,6 +1146,11 @@ body[data-ui-version="v2"] .gn-nacos-filter-row .ant-select-selector {
width: 100% !important;
}
/* Keep the filter fields continuous with the flat Nacos workbench surface. */
body[data-ui-version="v2"] .gn-nacos-filter-row .ant-select-selector {
background: var(--gn-bg-panel-2) !important;
}
.gn-nacos-selection-toolbar {
min-height: 32px;
padding: 8px 4px 0;
@@ -1216,7 +1221,7 @@ body[data-ui-version="v2"] .gn-v2-nacos-pane-body {
display: flex;
flex-direction: column;
padding: 10px 12px !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-bg-panel-2) !important;
}
/* Full-height 1px divider like Redis */

View File

@@ -1867,7 +1867,7 @@ body[data-ui-version="v2"] .ant-modal-body input[type="number"] {
inline styles can't reach. */
body[data-ui-version="v2"] .redis-viewer-workbench {
background: var(--gn-bg-app) !important;
background: var(--gn-bg-panel-2) !important;
/* the inner cards still read workbenchTheme — those return v2 palette
once the workbench theme files honor uiVersion */
}
@@ -2403,7 +2403,7 @@ body[data-ui-version="v2"] .gn-v2-explorer-command-trigger {
border: 0.5px solid var(--gn-br-2);
border-radius: 7px;
padding: 0 8px;
background: var(--gn-bg-input);
background: var(--gn-bg-panel-2);
color: var(--gn-fg-4);
cursor: text;
font-size: 12px;
@@ -2434,7 +2434,7 @@ body[data-ui-version="v2"] .gn-v2-explorer-filter-action {
justify-content: center;
border: 0.5px solid var(--gn-br-2);
border-radius: 7px;
background: var(--gn-bg-input);
background: var(--gn-bg-panel-2);
color: var(--gn-fg-4);
cursor: pointer;
}
@@ -2739,7 +2739,7 @@ body[data-ui-version="v2"] .gn-v2-explorer-search .ant-input-affix-wrapper {
border-radius: 7px !important;
border: 0.5px solid var(--gn-br-2) !important;
padding: 0 8px !important;
background: var(--gn-bg-input) !important;
background: var(--gn-bg-panel-2) !important;
color: var(--gn-fg-2) !important;
font-size: 12px !important;
font-weight: 500 !important;
@@ -4064,14 +4064,14 @@ body[data-ui-version="v2"] .gn-v2-data-grid-toolbar-frame {
border-width: 0 0 0.5px !important;
border-color: var(--gn-br-1) !important;
border-radius: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid .data-grid-toolbar-scroll {
min-height: 44px;
padding: 7px 12px !important;
gap: 4px !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid .data-grid-toolbar-scroll .ant-btn {
@@ -4245,17 +4245,37 @@ body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-query-result-toolbar-hide:hov
}
body[data-ui-version="v2"] .gn-v2-smart-filter-panel {
background: var(--gn-bg-panel-2) !important;
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;
border-top: 0.5px solid var(--gn-br-1);
font-size: var(--gn-font-size, 14px);
}
body[data-ui-version="v2"] .gn-v2-smart-filter-panel [data-grid-quick-where="true"] {
min-height: 38px;
padding-inline: 0 !important;
margin-bottom: 8px !important;
border: 0.5px solid var(--gn-br-2) !important;
border-radius: 8px !important;
background: var(--gn-bg-panel) !important;
border: 0 !important;
border-radius: 0 !important;
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;
}
/* Keep manual query entry aligned with the condition checkbox and compact on wide screens. */
body[data-ui-version="v2"] .gn-v2-smart-filter-panel .gn-v2-smart-filter-manual-input {
flex: 1 1 360px !important;
max-width: 680px !important;
}
/* Filter controls stay on the result workbench surface instead of the global input well. */
body[data-ui-version="v2"] .gn-v2-smart-filter-panel .ant-input,
body[data-ui-version="v2"] .gn-v2-smart-filter-panel .ant-input-affix-wrapper,
body[data-ui-version="v2"] .gn-v2-smart-filter-panel .ant-input-textarea,
body[data-ui-version="v2"] .gn-v2-smart-filter-panel .ant-select-selector {
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;
}
body[data-ui-version="v2"] .gn-v2-smart-filter-panel .ant-input-affix-wrapper > input.ant-input,
body[data-ui-version="v2"] .gn-v2-smart-filter-panel .ant-input-affix-wrapper .ant-input {
background: transparent !important;
}
body[data-ui-version="v2"] .gn-v2-smart-filter-panel [data-grid-quick-where="true"],
@@ -4289,11 +4309,19 @@ body[data-ui-version="v2"] .gn-v2-smart-filter-panel [data-grid-quick-where-inpu
body[data-ui-version="v2"] .gn-v2-data-grid-table-shell {
border: none !important;
border-radius: 0 !important;
background: var(--gn-bg-panel) !important;
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-table-wrap {
background: var(--gn-bg-panel);
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2));
}
body[data-ui-version="v2"] .gn-v2-data-grid .ant-table,
body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-container,
body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-content,
body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-tbody-virtual-holder,
body[data-ui-version="v2"] .gn-v2-data-grid .rc-virtual-list-holder {
background: var(--gn-query-workbench-bg, var(--gn-bg-panel-2)) !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead > tr > th {
@@ -4788,8 +4816,19 @@ body[data-ui-version="v2"] .gn-v2-data-grid-visually-hidden {
border: 0 !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-result-switcher .ant-segmented-item-selected .ant-segmented-item-label {
color: var(--gn-fg-1);
/* Result format is a mode switch, so its selected segment uses the theme accent
without a border or floating-card treatment. */
body[data-ui-version="v2"] .gn-v2-data-grid-result-switcher .ant-segmented-item-selected,
body[data-ui-version="v2"] .gn-v2-data-grid-result-switcher .ant-segmented-thumb {
background: var(--gn-accent-strong, var(--gn-accent)) !important;
border: 0 !important;
border-radius: 5px !important;
box-shadow: none !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-result-switcher .ant-segmented-item-selected .ant-segmented-item-label,
body[data-ui-version="v2"] .gn-v2-data-grid-result-switcher .ant-segmented-item-selected .anticon {
color: var(--gn-on-accent, #fff) !important;
}
body[data-ui-version="v2"] .gn-v2-data-grid-page-find-overlay {