feat: 用户徽章与等级体系,积分经济管理重构
- 新增徽章系统:badge 服务与 handler、管理端配置页、BadgeChip/LevelBadge 展示组件 - 新增等级模型:model/level 及前端 levels 元数据 - 管理端经济模块拆分为 points/badges/levels 独立页面,EconomySettings 迁入设置 - 积分、签到、评论等服务适配等级与徽章逻辑
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/freefire/jiang13-bbs/model"
|
||||
"github.com/freefire/jiang13-bbs/realtime"
|
||||
"github.com/freefire/jiang13-bbs/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -57,6 +58,18 @@ type updateSettingsRequest struct {
|
||||
BrandLogoSize *string `json:"brand_logo_size"`
|
||||
BrandLogoFit *string `json:"brand_logo_fit"`
|
||||
FooterLinks *[]service.FooterLink `json:"footer_links"`
|
||||
|
||||
PointsCheckinBase *int `json:"points_checkin_base"`
|
||||
PointsStreakEveryDays *int `json:"points_streak_every_days"`
|
||||
PointsStreakBonus *int `json:"points_streak_bonus"`
|
||||
PointsPostReward *int `json:"points_post_reward"`
|
||||
PointsPostDailyCap *int `json:"points_post_daily_cap"`
|
||||
PointsReplyReward *int `json:"points_reply_reward"`
|
||||
PointsReplyDailyCap *int `json:"points_reply_daily_cap"`
|
||||
PointsRecommendReward *int `json:"points_recommend_reward"`
|
||||
|
||||
Levels *[]model.LevelDef `json:"levels"`
|
||||
LevelsFx *bool `json:"levels_fx"`
|
||||
}
|
||||
|
||||
func settingsPayload(saved service.PublicSiteSettings) gin.H {
|
||||
@@ -97,6 +110,18 @@ func settingsPayload(saved service.PublicSiteSettings) gin.H {
|
||||
"brand_logo_size": saved.BrandLogoSize,
|
||||
"brand_logo_fit": saved.BrandLogoFit,
|
||||
"footer_links": saved.FooterLinks,
|
||||
|
||||
"points_checkin_base": saved.PointsCheckinBase,
|
||||
"points_streak_every_days": saved.PointsStreakEveryDays,
|
||||
"points_streak_bonus": saved.PointsStreakBonus,
|
||||
"points_post_reward": saved.PointsPostReward,
|
||||
"points_post_daily_cap": saved.PointsPostDailyCap,
|
||||
"points_reply_reward": saved.PointsReplyReward,
|
||||
"points_reply_daily_cap": saved.PointsReplyDailyCap,
|
||||
"points_recommend_reward": saved.PointsRecommendReward,
|
||||
|
||||
"levels": saved.Levels,
|
||||
"levels_fx": saved.LevelsFx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +138,11 @@ func (req *updateSettingsRequest) hasAny() bool {
|
||||
req.SiteWordmark != nil || req.SiteSlogan != nil || req.SiteKeywords != nil ||
|
||||
req.LogoLightURL != nil || req.LogoDarkURL != nil || req.FaviconURL != nil ||
|
||||
req.BrandMark != nil || req.BrandLogoSize != nil || req.BrandLogoFit != nil ||
|
||||
req.FooterLinks != nil
|
||||
req.FooterLinks != nil ||
|
||||
req.PointsCheckinBase != nil || req.PointsStreakEveryDays != nil || req.PointsStreakBonus != nil ||
|
||||
req.PointsPostReward != nil || req.PointsPostDailyCap != nil ||
|
||||
req.PointsReplyReward != nil || req.PointsReplyDailyCap != nil ||
|
||||
req.PointsRecommendReward != nil || req.Levels != nil || req.LevelsFx != nil
|
||||
}
|
||||
|
||||
// AdminGetSettings 超管读取站点设置(与公开 payload 字段一致)
|
||||
@@ -218,6 +247,49 @@ func (h *Handlers) UpdateSettings(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
pointsRules := []struct {
|
||||
req *int
|
||||
set func(int) error
|
||||
hint string
|
||||
}{
|
||||
{req.PointsCheckinBase, h.Setting.SetPointsCheckinBase, "签到基础分须为 0–100"},
|
||||
{req.PointsStreakEveryDays, h.Setting.SetPointsStreakEveryDays, "连续签到触发天数须为 0–100"},
|
||||
{req.PointsStreakBonus, h.Setting.SetPointsStreakBonus, "连续签到加成分须为 0–100"},
|
||||
{req.PointsPostReward, h.Setting.SetPointsPostReward, "发帖奖励须为 0–100"},
|
||||
{req.PointsPostDailyCap, h.Setting.SetPointsPostDailyCap, "发帖奖励每日上限须为 0–100"},
|
||||
{req.PointsReplyReward, h.Setting.SetPointsReplyReward, "回复奖励须为 0–100"},
|
||||
{req.PointsReplyDailyCap, h.Setting.SetPointsReplyDailyCap, "回复奖励每日上限须为 0–100"},
|
||||
{req.PointsRecommendReward, h.Setting.SetPointsRecommendReward, "推荐奖励须为 0–100"},
|
||||
}
|
||||
for _, r := range pointsRules {
|
||||
if r.req == nil {
|
||||
continue
|
||||
}
|
||||
if err := r.set(*r.req); err != nil {
|
||||
if errors.Is(err, service.ErrInvalidSiteSetting) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": r.hint})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"})
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.Levels != nil {
|
||||
if err := h.Setting.SetLevels(*req.Levels); err != nil {
|
||||
if errors.Is(err, service.ErrInvalidSiteSetting) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "等级配置无效(2–12 级,阈值须非负且严格递增、首级为 0,颜色须在色板内)"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"})
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.LevelsFx != nil {
|
||||
if err := h.Setting.SetLevelsFx(*req.LevelsFx); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"})
|
||||
return
|
||||
}
|
||||
}
|
||||
if req.CodeBlockAutoFold != nil {
|
||||
if err := h.Setting.SetCodeBlockAutoFold(*req.CodeBlockAutoFold); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"})
|
||||
|
||||
Reference in New Issue
Block a user