fix(platform): 外观主题 - 保证经典黑白主题不可被修改或删除

This commit is contained in:
ddcat666
2026-09-24 23:59:12 +08:00
parent 3b254fce11
commit 1dc1b6f6b5
4 changed files with 41 additions and 25 deletions

View File

@@ -47,8 +47,12 @@ func TestAppearanceButtonFillRoundTripAndValidation(t *testing.T) {
})
}
input.SkinThemes[0].Tokens.Buttons.Light.Start = "#000000"
if _, err := svc.UpdateAppearance(admin, input); err == nil {
t.Fatal("classic fill mutation accepted")
overwritten, err := svc.UpdateAppearance(admin, input)
if err != nil {
t.Fatal(err)
}
if overwritten.SkinThemes[0].Tokens.Buttons != defaultClassicAppearanceSkin().Tokens.Buttons {
t.Fatal("classic fill mutation was stored")
}
}

View File

@@ -262,11 +262,20 @@ func normalizeAppearanceSkinThemes(themes []AppearanceSkinTheme) []AppearanceSki
result := make([]AppearanceSkinTheme, len(themes))
copy(result, themes)
builtins := defaultAppearanceSkinThemes()
classic := defaultClassicAppearanceSkin()
replacedClassic := false
for index := range result {
result[index].ID = strings.ToLower(strings.TrimSpace(result[index].ID))
if result[index].ID == defaultAppearanceSkinID {
// The system default is not editable. Whatever was submitted is
// discarded so a drifted client copy cannot reject the whole save.
result[index] = classic
replacedClassic = true
continue
}
result[index].Name = strings.TrimSpace(result[index].Name)
result[index].Description = strings.TrimSpace(result[index].Description)
result[index].Locked = result[index].ID == defaultAppearanceSkinID
result[index].Locked = false
// Only a wholly absent legacy button block is upgraded. Partial or
// malformed submitted parameters remain invalid on the write path.
if result[index].Tokens.Buttons == (AppearanceSkinButtons{}) {
@@ -285,6 +294,12 @@ func normalizeAppearanceSkinThemes(themes []AppearanceSkinTheme) []AppearanceSki
normalizeAppearanceSkinModeColors(&result[index].Tokens.Dark)
result[index].Tokens.Components.ShadowStyle = strings.ToLower(strings.TrimSpace(result[index].Tokens.Components.ShadowStyle))
}
if !replacedClassic {
if len(result) >= maxAppearanceSkinThemes {
result = result[:maxAppearanceSkinThemes-1]
}
result = append([]AppearanceSkinTheme{classic}, result...)
}
return result
}
@@ -329,8 +344,6 @@ func validateAppearanceSkinThemes(themes []AppearanceSkinTheme, selectedID strin
}
seen := make(map[string]struct{}, len(themes))
foundSelected := false
foundClassic := false
classic := defaultClassicAppearanceSkin()
for _, skin := range themes {
if !appearanceSkinIDPattern.MatchString(skin.ID) {
return BadAuthRequest("皮肤主题 ID 无效")
@@ -342,12 +355,6 @@ func validateAppearanceSkinThemes(themes []AppearanceSkinTheme, selectedID strin
if skin.ID == selectedID {
foundSelected = true
}
if skin.ID == defaultAppearanceSkinID {
foundClassic = true
if skin.Name != classic.Name || skin.Description != classic.Description || !reflect.DeepEqual(skin.Tokens, classic.Tokens) {
return BadAuthRequest("经典黑白为系统默认主题,不能修改或删除")
}
}
if err := validateAppearanceSkinText(skin.Name, "皮肤主题名称", 40, true); err != nil {
return err
}
@@ -369,9 +376,6 @@ func validateAppearanceSkinThemes(themes []AppearanceSkinTheme, selectedID strin
}
}
}
if !foundClassic {
return BadAuthRequest("经典黑白为系统默认主题,不能修改或删除")
}
if !foundSelected {
return BadAuthRequest("当前启用的皮肤主题不存在")
}

View File

@@ -452,17 +452,25 @@ func TestAppearanceSkinLibrarySupportsEditableCopiesAndProtectsClassic(t *testin
t.Fatalf("custom skin round trip = %#v", updated)
}
classic := defaultClassicAppearanceSkin()
withoutClassic := append([]AppearanceSkinTheme(nil), themes[1:]...)
_, err = svc.UpdateAppearance(admin, AppearanceSetting{BrandName: "HIMA Studio", BrandSlug: "hima-studio", AuthHeroTitle: defaultAppearanceHeroTitle, SkinID: custom.ID, SkinThemes: withoutClassic})
if err == nil || !strings.Contains(err.Error(), "不能修改或删除") {
t.Fatalf("missing classic error = %v", err)
restored, err := svc.UpdateAppearance(admin, AppearanceSetting{BrandName: "HIMA Studio", BrandSlug: "hima-studio", AuthHeroTitle: defaultAppearanceHeroTitle, SkinID: custom.ID, SkinThemes: withoutClassic})
if err != nil {
t.Fatal(err)
}
if restored.SkinThemes[0] != classic {
t.Fatalf("missing classic was not restored = %#v", restored.SkinThemes[0])
}
mutatedClassic := append([]AppearanceSkinTheme(nil), themes...)
mutatedClassic[0].Name = "改名"
_, err = svc.UpdateAppearance(admin, AppearanceSetting{BrandName: "HIMA Studio", BrandSlug: "hima-studio", AuthHeroTitle: defaultAppearanceHeroTitle, SkinID: custom.ID, SkinThemes: mutatedClassic})
if err == nil || !strings.Contains(err.Error(), "不能修改或删除") {
t.Fatalf("mutated classic error = %v", err)
mutatedClassic[0].Tokens.Light.Primary = "#000000"
overwritten, err := svc.UpdateAppearance(admin, AppearanceSetting{BrandName: "HIMA Studio", BrandSlug: "hima-studio", AuthHeroTitle: defaultAppearanceHeroTitle, SkinID: custom.ID, SkinThemes: mutatedClassic})
if err != nil {
t.Fatal(err)
}
if overwritten.SkinThemes[0] != classic || overwritten.Public.ActiveSkin.ID != custom.ID {
t.Fatalf("mutated classic was not overwritten = %#v", overwritten.SkinThemes[0])
}
invalidColor := append([]AppearanceSkinTheme(nil), themes...)

View File

@@ -336,10 +336,10 @@ export const DEFAULT_CLASSIC_SKIN: SkinDefinition = {
info: "#2563eb",
workspace: "#ffffff",
workspaceGrid: "#f3f3f3",
adminBackground: "#f5f5f5",
adminBackground: "#f3f4f6",
adminSurface: "#ffffff",
adminSubtle: "#f6f6f6",
adminStrong: "#ececec",
adminSubtle: "#f7f8fa",
adminStrong: "#eceff3",
authBackground: "#ffffff",
authPanel: "#f7f7f7",
authCard: "#ffffff",
@@ -388,9 +388,9 @@ export const DEFAULT_CLASSIC_SKIN: SkinDefinition = {
info: "#60a5fa",
workspace: "#181818",
workspaceGrid: "#222222",
adminBackground: "#0f0f0f",
adminBackground: "#101010",
adminSurface: "#181818",
adminSubtle: "#222222",
adminSubtle: "#202020",
adminStrong: "#2a2a2a",
authBackground: "#08090c",
authPanel: "#0b0c10",