From 33d6ac341bc9cb630961f977ea2cea7a2cad0f7d Mon Sep 17 00:00:00 2001 From: Ray <133741134+likeraylee@users.noreply.github.com> Date: Mon, 7 Sep 2026 15:00:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E7=9A=AE=E8=82=A4=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=20-=20=E4=BF=AE=E5=A4=8D=E6=8F=90=E7=A4=BA=E6=B5=AE?= =?UTF-8?q?=E5=B1=82=E6=96=87=E5=AD=97=E5=AF=B9=E6=AF=94=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/service/appearance_test.go | 48 +++++++++++++++++++++ web/src/styles/globals.css | 12 ++++++ web/test/site-appearance-and-skins.test.ts | 4 ++ 3 files changed, 64 insertions(+) diff --git a/backend/internal/service/appearance_test.go b/backend/internal/service/appearance_test.go index 31f08bf80..9454358db 100644 --- a/backend/internal/service/appearance_test.go +++ b/backend/internal/service/appearance_test.go @@ -3,12 +3,14 @@ package service import ( "bytes" "encoding/json" + "math" "mime/multipart" "net/http" "net/http/httptest" "net/textproto" "os" "path/filepath" + "strconv" "strings" "testing" @@ -48,6 +50,52 @@ func TestAppearanceDefaultsPreserveBuiltInBrand(t *testing.T) { } } +func TestBuiltInAppearanceSkinTooltipPairsMeetContrast(t *testing.T) { + for _, skin := range defaultAppearanceSkinThemes() { + modes := []struct { + name string + tokens AppearanceSkinModeTokens + }{ + {name: "light", tokens: skin.Tokens.Light}, + {name: "dark", tokens: skin.Tokens.Dark}, + } + for _, mode := range modes { + ratio := appearanceColorContrastRatio(t, mode.tokens.Overlay, mode.tokens.Text) + if ratio < 4.5 { + t.Errorf("skin %s %s tooltip contrast = %.2f, want at least 4.5", skin.ID, mode.name, ratio) + } + } + } +} + +func appearanceColorContrastRatio(t *testing.T, foreground, background string) float64 { + t.Helper() + contrastLuminance := func(color string) float64 { + if len(color) != 7 || color[0] != '#' { + t.Fatalf("unsupported test color %q", color) + } + channels := make([]float64, 3) + for index := range channels { + value, err := strconv.ParseUint(color[1+index*2:3+index*2], 16, 8) + if err != nil { + t.Fatalf("parse test color %q: %v", color, err) + } + srgb := float64(value) / 255 + if srgb <= 0.04045 { + channels[index] = srgb / 12.92 + } else { + channels[index] = math.Pow((srgb+0.055)/1.055, 2.4) + } + } + return 0.2126*channels[0] + 0.7152*channels[1] + 0.0722*channels[2] + } + foregroundLuminance := contrastLuminance(foreground) + backgroundLuminance := contrastLuminance(background) + lighter := math.Max(foregroundLuminance, backgroundLuminance) + darker := math.Min(foregroundLuminance, backgroundLuminance) + return (lighter + 0.05) / (darker + 0.05) +} + func TestAppearanceBackfillsVersionSixFieldsForExistingSetting(t *testing.T) { svc, db, _, _ := newAppearanceTestService(t) legacy := model.SystemSetting{Key: appearanceSettingKey, ValueJSON: `{"schemaVersion":1,"brandName":"旧品牌","skinId":"classic"}`} diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index 1a5250a74..c39c928a5 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -13015,6 +13015,18 @@ html:not(.dark) .creation-history-drawer-root { backdrop-filter: none !important; } +/* Tooltip 必须使用同一套浮层背景/前景语义色。Ant Design 会让它继承 + colorTextLightSolid(实心按钮文字色),深色主题下会形成深底深字。 */ +:where(.ant-tooltip) { + --ant-tooltip-arrow-background-color: var(--popover) !important; + --ant-tooltip-overlay-color: var(--popover-foreground) !important; +} + +:where(.ant-tooltip-container, .ant-tooltip-inner) { + background: var(--popover) !important; + color: var(--popover-foreground) !important; +} + :where(.ant-select-item-option, .ant-dropdown-menu-item, .ant-dropdown-menu-submenu-title, .ant-cascader-menu-item, .ant-mentions-dropdown-menu-item) { min-height: 36px; margin-block: 2px; diff --git a/web/test/site-appearance-and-skins.test.ts b/web/test/site-appearance-and-skins.test.ts index ed36f5750..5130b7d96 100644 --- a/web/test/site-appearance-and-skins.test.ts +++ b/web/test/site-appearance-and-skins.test.ts @@ -155,6 +155,10 @@ describe("site appearance and editable skin library", () => { expect(editorSource).toContain("后台菜单"); expect(globalStyles).toContain("--control-switch-checked-bg: #16a34a"); expect(globalStyles).toContain("--plugin-switch-checked-bg: var(--control-switch-checked-bg)"); + expect(globalStyles).toContain("--ant-tooltip-arrow-background-color: var(--popover) !important"); + expect(globalStyles).toContain("--ant-tooltip-overlay-color: var(--popover-foreground) !important"); + expect(globalStyles).toContain(":where(.ant-tooltip-container, .ant-tooltip-inner)"); + expect(globalStyles).toContain("color: var(--popover-foreground) !important"); expect(adminStyles).toContain("--admin-status-warning: var(--palette-status-warning)"); expect(adminStyles).toContain("border-radius: var(--menu-radius);"); });