🐛 fix(workbench): 统一 v2 工具中心与工作区主题

- 显式传递 uiVersion 构建 overlay theme,避免设置中心和工具中心混用旧版黄蓝色
- 统一 v2 模式下 Ant Design 主色和选中态为绿色语义
- 移除工具中心详情头部重复的返回上一步按钮
- 修复侧边树对象筛选反复切换时的 rc-tree 复用漂移
- 调整 v2 左侧树横向滚动条与首页空态布局,释放工作区展示空间
- 补充工具中心、主题 token 和侧边树 remount 的回归测试
This commit is contained in:
Syngnat
2026-07-01 18:11:08 +08:00
parent 749e543e37
commit 04a61a50dc
9 changed files with 205 additions and 99 deletions

View File

@@ -482,8 +482,9 @@ const ConnectionModal: React.FC<{
() =>
buildOverlayWorkbenchTheme(darkMode, {
disableBackdropFilter: disableLocalBackdropFilter,
uiVersion: appearance.uiVersion,
}),
[darkMode, disableLocalBackdropFilter, appearance.uiVersion],
[appearance.uiVersion, darkMode, disableLocalBackdropFilter],
);
const tunnelSectionStyle: React.CSSProperties = {

View File

@@ -1066,10 +1066,11 @@ describe('Sidebar locate toolbar', () => {
expect(css).toMatch(/\.gn-v2-tree-count \{[^}]*font-size: clamp\(10px, calc\(var\(--gn-sidebar-tree-font-size, var\(--gn-font-size-sm, 12px\)\) - 1px\), 16px\);/s);
expect(css).toMatch(/\.gn-v2-tree-title\.is-redis-db \.gn-v2-tree-label \{[^}]*display: inline-flex;[^}]*gap: 6px;/s);
expect(css).toMatch(/\.gn-v2-redis-db-alias \{[^}]*color: var\(--gn-fg-5\);[^}]*opacity: 0\.78;/s);
expect(css).toMatch(/\.gn-v2-connection-rail \{[^}]*width: calc\(38px \* var\(--gn-ui-scale, 1\)\);[^}]*flex: 0 0 calc\(38px \* var\(--gn-ui-scale, 1\)\);/s);
expect(css).toMatch(/\.gn-v2-rail-item,\s*body\[data-ui-version="v2"\] \.gn-v2-rail-tool \{[^}]*width: calc\(36px \* var\(--gn-ui-scale, 1\)\);[^}]*height: calc\(38px \* var\(--gn-ui-scale, 1\)\);[^}]*font-size: var\(--gn-font-size-sm, 12px\);/s);
expect(css).toMatch(/\.gn-v2-rail-tool \{[^}]*height: calc\(32px \* var\(--gn-ui-scale, 1\)\);/s);
expect(css).toMatch(/\.gn-v2-rail-tool \{[^}]*width: calc\(24px \* var\(--gn-ui-scale, 1\)\);/s);
expect(css).toContain('--gn-v2-rail-scale: calc(var(--gn-ui-scale, 1) * var(--gn-sidebar-rail-scale, 1));');
expect(css).toMatch(/\.gn-v2-connection-rail \{[^}]*width: calc\(38px \* var\(--gn-v2-rail-scale\)\);[^}]*flex: 0 0 calc\(38px \* var\(--gn-v2-rail-scale\)\);/s);
expect(css).toMatch(/body\[data-ui-version="v2"\] \.gn-v2-rail-item,\s*body\[data-ui-version="v2"\] \.gn-v2-rail-tool \{[^}]*width: calc\(36px \* var\(--gn-v2-rail-scale\)\);[^}]*height: calc\(38px \* var\(--gn-v2-rail-scale\)\);[^}]*font-size: calc\(var\(--gn-font-size-sm, 12px\) \* var\(--gn-sidebar-rail-scale, 1\)\);/s);
expect(css).toMatch(/\.gn-v2-rail-tool \{[^}]*height: calc\(32px \* var\(--gn-v2-rail-scale\)\);/s);
expect(css).toMatch(/\.gn-v2-rail-tool \{[^}]*width: calc\(24px \* var\(--gn-v2-rail-scale\)\);/s);
expect(css).toMatch(/\.gn-v2-active-connection-trigger \{[^}]*height: 34px;[^}]*border: 0;[^}]*background: transparent;/s);
expect(css).toMatch(/\.gn-v2-active-connection-query-action \{[^}]*max-width: 96px;[^}]*font-size: 12px;/s);
expect(css).not.toContain('.gn-v2-active-connection-trigger:hover');
@@ -1155,25 +1156,22 @@ describe('Sidebar locate toolbar', () => {
expect(source).not.toContain('treeHeight - V2_TREE_HORIZONTAL_SCROLL_BOTTOM_RESERVE');
expect(source).toContain('height={effectiveTreeHeight}');
expect(source).toContain('treeData={isV2Ui ? v2VisibleTreeData : displayTreeData}');
expect(source).not.toContain('__v2-tree-horizontal-scroll-spacer__');
expect(source).not.toContain('v2TreeDataWithScrollSpacer');
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \{[^}]*--gn-v2-tree-horizontal-scroll-reserve: 32px;[^}]*overflow: hidden !important;/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.sidebar-tree-scroll-content \{[^}]*display: flex;[^}]*height: 100%;[^}]*padding: 4px 0 0;/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree \{[^}]*flex: 1 1 auto;[^}]*width: 100%;[^}]*min-width: 0;[^}]*height: 100%;/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list \{[^}]*position: relative;[^}]*height: 100%;[^}]*min-height: 0;[^}]*box-sizing: border-box;[^}]*padding-bottom: var\(--gn-v2-tree-horizontal-scroll-reserve\);/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list \{[^}]*position: relative;[^}]*height: 100%;[^}]*min-height: 0;[^}]*box-sizing: border-box;/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder-inner \{[^}]*width: 100%;[^}]*min-width: 100%;/s);
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder-inner \{[^}]*width: max-content;/s);
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list \{[^}]*position: static !important;/s);
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder \{[^}]*calc\(100% - var\(--gn-v2-tree-horizontal-scroll-reserve\)\)/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder \{[^}]*height: calc\(100% - var\(--gn-v2-tree-horizontal-scroll-reserve\)\);[^}]*max-height: calc\(100% - var\(--gn-v2-tree-horizontal-scroll-reserve\)\) !important;/s);
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder \{[^}]*overflow-x: auto !important;/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder \{[^}]*padding-bottom: var\(--gn-v2-tree-horizontal-scroll-reserve\);/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \{[^}]*height: 12px !important;[^}]*bottom: calc\(\(var\(--gn-v2-tree-horizontal-scroll-reserve\) - 12px\) \/ 2\) !important;/s);
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \{[^}]*bottom: calc\(\(var\(--gn-v2-tree-horizontal-scroll-reserve\) - 12px\) \* -1\) !important;/s);
expect(css).not.toContain('.gn-v2-tree-horizontal-scroll-spacer');
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-holder \{[^}]*padding-bottom: var\(--gn-v2-tree-horizontal-scroll-reserve\);/s);
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \{[^}]*height: 12px !important;[^}]*bottom: 0 !important;/s);
expect(css).not.toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \{[^}]*bottom: calc\(\(var\(--gn-v2-tree-horizontal-scroll-reserve\) - 12px\) \/ 2\) !important;/s);
const horizontalScrollbarCss = readCssRuleBlock(css, 'body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-list-scrollbar-horizontal');
expect(horizontalScrollbarCss).toContain('border-radius: 999px !important;');
expect(horizontalScrollbarCss).toContain('background: color-mix(in srgb, var(--gn-bg-panel-2) 88%, var(--gn-bg-panel) 12%) !important;');
expect(horizontalScrollbarCss).toContain('box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gn-br-2) 40%, transparent) !important;');
expect(horizontalScrollbarCss).toContain('background: transparent !important;');
expect(horizontalScrollbarCss).toContain('box-shadow: none !important;');
expect(css).toMatch(/\.gn-v2-explorer-tree-shell \.ant-tree-list-scrollbar-horizontal \.ant-tree-list-scrollbar-thumb \{[^}]*height: 8px !important;/s);
const treeContentWrapperCss = readCssRuleBlock(css, 'body[data-ui-version="v2"] .gn-v2-explorer-tree-shell .ant-tree-node-content-wrapper');
expect(treeContentWrapperCss).toContain('min-width: 100%;');
@@ -1339,10 +1337,18 @@ describe('Sidebar locate toolbar', () => {
const source = readSidebarSource();
expect(source).toContain("if (v2ExplorerFilter === 'all') {");
expect(source).toContain('return displayTreeData;');
expect(source).toContain('gn-v2-tree-connection-copy');
expect(source).not.toContain('gn-v2-tree-connection-meta');
});
it('remounts the v2 tree when object filters change to avoid rc-tree node reuse drift', () => {
const source = readSidebarSource();
expect(source).toContain("key={isV2Ui ? `v2-tree-${v2ExplorerFilter}` : 'legacy-tree'}");
expect(source).toContain('treeData={isV2Ui ? v2VisibleTreeData : displayTreeData}');
});
it('reorders dragged connections instead of only moving them between groups', () => {
const source = readSidebarSource();
const utilsSource = readFileSync(new URL('./sidebarV2Utils.ts', import.meta.url), 'utf8');

View File

@@ -457,8 +457,11 @@ const Sidebar: React.FC<{
};
const bgMain = getBg('#141414');
const overlayTheme = useMemo(
() => buildOverlayWorkbenchTheme(darkMode, { disableBackdropFilter: disableLocalBackdropFilter }),
[darkMode, disableLocalBackdropFilter, appearance.uiVersion],
() => buildOverlayWorkbenchTheme(darkMode, {
disableBackdropFilter: disableLocalBackdropFilter,
uiVersion: isV2Ui ? 'v2' : 'legacy',
}),
[darkMode, disableLocalBackdropFilter, isV2Ui],
);
const modalPanelStyle = useMemo(() => ({
background: overlayTheme.shellBg,
@@ -2920,6 +2923,7 @@ const Sidebar: React.FC<{
>
<div className="sidebar-tree-scroll-content">
<Tree
key={isV2Ui ? `v2-tree-${v2ExplorerFilter}` : 'legacy-tree'}
ref={treeRef}
showIcon
draggable={{