feat(home-banner): 轮播支持 PC/移动两端分图与热区跳转,热区图悬浮放大;修复上传压缩压白透明 PNG

This commit is contained in:
yml2213
2026-10-16 16:02:29 +08:00
parent b5867e3c5e
commit 9f8c0f9cb2
21 changed files with 2217 additions and 272 deletions
@@ -1,9 +1,14 @@
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 {
@@ -26,11 +31,15 @@ func normalizeHomeBanners(items []HomeBannerItem) []HomeBannerItem {
item.Badge = strings.TrimSpace(item.Badge)
item.Pill = strings.TrimSpace(item.Pill)
item.Tone = strings.TrimSpace(item.Tone)
item.ImageURL = strings.TrimSpace(item.ImageURL)
if item.Title == "" && item.ImageURL == "" {
// 旧结构只有一张图,两端共用,避免历史配置升级后丢图
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" {
if item.Tone != "green" && item.Tone != "orange" && item.Tone != "gold" {
item.Tone = "blue"
}
normalized = append(normalized, item)
@@ -41,6 +50,88 @@ func normalizeHomeBanners(items []HomeBannerItem) []HomeBannerItem {
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)