package systemconfig import ( "math" "strings" ) // 热区悬浮放大倍数:与前端 BANNER_HOTSPOT_DEFAULT_HOVER_SCALE 保持一致。 const defaultBannerHotspotHoverScale = 1.06 const maxBannerHotspotHoverScale = 1.5 func normalizeAnnouncements(items []string) []string { normalized := make([]string, 0, len(items)) for _, item := range items { item = strings.TrimSpace(item) if item != "" { normalized = append(normalized, item) } } if len(normalized) == 0 { return defaultHomeAnnouncements() } return normalized } func normalizeHomeBanners(items []HomeBannerItem) []HomeBannerItem { normalized := make([]HomeBannerItem, 0, len(items)) for _, item := range items { item.Eyebrow = strings.TrimSpace(item.Eyebrow) item.Title = strings.TrimSpace(item.Title) item.Badge = strings.TrimSpace(item.Badge) item.Pill = strings.TrimSpace(item.Pill) item.Tone = strings.TrimSpace(item.Tone) // 旧结构只有一张图,两端共用,避免历史配置升级后丢图 legacyImageURL := strings.TrimSpace(item.ImageURL) item.ImageURL = "" item.PC = normalizeBannerVisual(item.PC, legacyImageURL) item.Mobile = normalizeBannerVisual(item.Mobile, legacyImageURL) if item.Title == "" && item.PC.ImageURL == "" && item.Mobile.ImageURL == "" { continue } if item.Tone != "green" && item.Tone != "orange" && item.Tone != "gold" { item.Tone = "blue" } normalized = append(normalized, item) } if len(normalized) == 0 { return defaultHomeBanners() } return normalized } func normalizeBannerVisual(visual HomeBannerVisual, legacyImageURL string) HomeBannerVisual { visual.ImageURL = strings.TrimSpace(visual.ImageURL) if visual.ImageURL == "" { visual.ImageURL = legacyImageURL } visual.Alt = strings.TrimSpace(visual.Alt) visual.Action = normalizeBannerAction(visual.Action) hotspots := make([]HomeBannerHotspot, 0, len(visual.Hotspots)) for _, hotspot := range visual.Hotspots { item, ok := normalizeBannerHotspot(hotspot) if !ok { continue } hotspots = append(hotspots, item) } visual.Hotspots = hotspots return visual } // normalizeBannerHotspot 收敛热区坐标:宽高为 0 的热区无意义直接丢弃,其余保证不越界。 func normalizeBannerHotspot(hotspot HomeBannerHotspot) (HomeBannerHotspot, bool) { width := clampBannerPercent(hotspot.Width, 100) height := clampBannerPercent(hotspot.Height, 100) if width <= 0 || height <= 0 { return HomeBannerHotspot{}, false } hotspot.ID = strings.TrimSpace(hotspot.ID) hotspot.Label = strings.TrimSpace(hotspot.Label) hotspot.ImageURL = strings.TrimSpace(hotspot.ImageURL) hotspot.HoverScale = normalizeHotspotHoverScale(hotspot.HoverScale) hotspot.Width = width hotspot.Height = height hotspot.X = clampBannerPercent(hotspot.X, 100-width) hotspot.Y = clampBannerPercent(hotspot.Y, 100-height) hotspot.Action = normalizeBannerAction(hotspot.Action) return hotspot, true } // normalizeHotspotHoverScale 悬浮放大倍数:缺失/非法回落默认值,上限 1.5 防止热区盖住整张图。 func normalizeHotspotHoverScale(value float64) float64 { if value <= 0 { return defaultBannerHotspotHoverScale } if value > maxBannerHotspotHoverScale { value = maxBannerHotspotHoverScale } else if value < 1 { value = 1 } return math.Round(value*100) / 100 } // normalizeBannerAction 目标缺失的跳转会变成「点了没反应」的死热区,统一退化为 none。 func normalizeBannerAction(action HomeBannerAction) HomeBannerAction { action.Type = strings.TrimSpace(action.Type) action.Target = strings.TrimSpace(action.Target) switch action.Type { case "route", "link": if action.Target == "" { return HomeBannerAction{Type: "none"} } case "preview": action.Target = "" action.OpenNewTab = false case "none": action.Target = "" action.OpenNewTab = false default: return HomeBannerAction{Type: "none"} } return action } func clampBannerPercent(value float64, max float64) float64 { if value < 0 { value = 0 } if value > max { value = max } return math.Round(value*100) / 100 } func normalizeOrderAgreements(agreements *OrderAgreementsDTO) { defaults := DefaultOrderAgreements() agreements.VirtualAssetPurchase.Title = strings.TrimSpace(agreements.VirtualAssetPurchase.Title) agreements.VirtualAssetPurchase.Content = strings.TrimSpace(agreements.VirtualAssetPurchase.Content) agreements.RenterAgreement.Title = strings.TrimSpace(agreements.RenterAgreement.Title) agreements.RenterAgreement.Content = strings.TrimSpace(agreements.RenterAgreement.Content) if agreements.VirtualAssetPurchase.Title == "" { agreements.VirtualAssetPurchase.Title = defaults.VirtualAssetPurchase.Title } if agreements.VirtualAssetPurchase.Content == "" { agreements.VirtualAssetPurchase.Content = defaults.VirtualAssetPurchase.Content } if agreements.RenterAgreement.Title == "" { agreements.RenterAgreement.Title = defaults.RenterAgreement.Title } if agreements.RenterAgreement.Content == "" { agreements.RenterAgreement.Content = defaults.RenterAgreement.Content } } func normalizeListingPublishAgreements(agreements *ListingPublishAgreementsDTO) { defaults := DefaultListingPublishAgreements() agreements.VirtualAssetSale.Title = strings.TrimSpace(agreements.VirtualAssetSale.Title) agreements.VirtualAssetSale.Content = strings.TrimSpace(agreements.VirtualAssetSale.Content) agreements.SellerAgreement.Title = strings.TrimSpace(agreements.SellerAgreement.Title) agreements.SellerAgreement.Content = strings.TrimSpace(agreements.SellerAgreement.Content) if agreements.VirtualAssetSale.Title == "" { agreements.VirtualAssetSale.Title = defaults.VirtualAssetSale.Title } if agreements.VirtualAssetSale.Content == "" { agreements.VirtualAssetSale.Content = defaults.VirtualAssetSale.Content } if agreements.SellerAgreement.Title == "" { agreements.SellerAgreement.Title = defaults.SellerAgreement.Title } if agreements.SellerAgreement.Content == "" { agreements.SellerAgreement.Content = defaults.SellerAgreement.Content } } func normalizePostRentalNotice(notice *PostRentalNoticeDTO) { defaults := DefaultPostRentalNotice() notice.Title = strings.TrimSpace(notice.Title) notice.Content = strings.TrimSpace(notice.Content) if notice.Title == "" { notice.Title = defaults.Title } if notice.Content == "" { notice.Content = defaults.Content } } func normalizePublishOptions(options *PublishOptionsDTO) { defaults := DefaultPublishOptions() if len(options.ServerOptions) == 0 { options.ServerOptions = defaults.ServerOptions } if len(options.FaceOptions) == 0 { options.FaceOptions = defaults.FaceOptions } if len(options.RankOptions) == 0 { options.RankOptions = defaults.RankOptions } if len(options.InsuranceOptions) == 0 { options.InsuranceOptions = defaults.InsuranceOptions } if len(options.LevelOptions) == 0 { options.LevelOptions = defaults.LevelOptions } if len(options.LoginMethodOptions) == 0 { options.LoginMethodOptions = defaults.LoginMethodOptions } if len(options.RegionOptions) == 0 { options.RegionOptions = defaults.RegionOptions } if len(options.SkinGroups) == 0 { options.SkinGroups = defaults.SkinGroups } if len(options.QuantityItems) == 0 { options.QuantityItems = defaults.QuantityItems } if len(options.ScreenshotSlots) == 0 { options.ScreenshotSlots = defaults.ScreenshotSlots } if len(options.BanRecordOptions) == 0 { options.BanRecordOptions = defaults.BanRecordOptions } if len(options.BanEvidenceOptions) == 0 { options.BanEvidenceOptions = defaults.BanEvidenceOptions } if options.FireLevelMin <= 0 { options.FireLevelMin = defaults.FireLevelMin } if options.PriceConfig.DepositPlaceholder == "" { options.PriceConfig.DepositPlaceholder = defaults.PriceConfig.DepositPlaceholder } if options.PriceConfig.DepositHint == "" { options.PriceConfig.DepositHint = defaults.PriceConfig.DepositHint } if options.PriceConfig.PricePlaceholder == "" { options.PriceConfig.PricePlaceholder = defaults.PriceConfig.PricePlaceholder } if options.PriceConfig.RatioDescription == "" { options.PriceConfig.RatioDescription = defaults.PriceConfig.RatioDescription } if options.DepositRecommend.BaseAmount <= 0 { options.DepositRecommend.BaseAmount = defaults.DepositRecommend.BaseAmount } if len(options.DepositRecommend.SkinGroupRules) == 0 { options.DepositRecommend.SkinGroupRules = defaults.DepositRecommend.SkinGroupRules } if len(options.RatioConfig.InsuranceBaseRatios) == 0 { options.RatioConfig.InsuranceBaseRatios = defaults.RatioConfig.InsuranceBaseRatios } if len(options.RatioConfig.ConfigItems) == 0 { options.RatioConfig.ConfigItems = defaults.RatioConfig.ConfigItems } if len(options.RatioConfig.CoinCorrections) == 0 { options.RatioConfig.CoinCorrections = defaults.RatioConfig.CoinCorrections } } func normalizeSalePriceConfig(config *PublishSalePriceConfig) { defaults := DefaultSalePriceConfig() if len(config.FixedMarkupRules) == 0 { config.FixedMarkupRules = defaults.FixedMarkupRules } if len(config.RatioAdjustmentRules) == 0 { config.RatioAdjustmentRules = defaults.RatioAdjustmentRules } }