feat: Admin 徽章定义 CRUD 与限定颁发收回

SSR /admin/badges 复用 UpsertDef/AwardLimited/Revoke,并新增 DeleteDef;指针指向 media。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 03:42:41 +08:00
parent 02a5ad0c30
commit 0c5fe8a1b4
13 changed files with 518 additions and 21 deletions

319
routers/web/admin_badges.go Normal file
View File

@@ -0,0 +1,319 @@
package web
import (
"errors"
"net/http"
"strconv"
"strings"
"git.iioio.com/freefire/jiang13-forum/models"
"git.iioio.com/freefire/jiang13-forum/modules/webctx"
"github.com/gin-gonic/gin"
)
type adminBadgeRow struct {
ID uint
Code string
Name string
Description string
Icon string
Kind string
KindLabel string
Metric string
Threshold int
SortOrder int
Enabled bool
IsLimited bool
}
type adminBadgeForm struct {
ID uint
Code string
Name string
Description string
Icon string
Kind string
Metric string
Threshold int
SortOrder int
Enabled bool
IsEdit bool
}
type adminBadgesData struct {
AdminChrome
Badges []adminBadgeRow
LimitedBadges []adminBadgeRow
Form adminBadgeForm
AwardUser string
AwardBadgeID uint
}
// AdminBadgesGet 徽章定义列表
func (d Deps) AdminBadgesGet(c *gin.Context) {
ctx := d.ctx(c)
d.renderAdminBadges(ctx, "", adminBadgeForm{Kind: models.BadgeKindLimited, Enabled: true}, "", 0)
}
func (d Deps) renderAdminBadges(ctx *webctx.Context, errMsg string, form adminBadgeForm, awardUser string, awardBadgeID uint) {
chrome := d.adminChrome(ctx, "徽章", "badges")
chrome.Error = errMsg
if form.Kind == "" {
form.Kind = models.BadgeKindLimited
}
data := adminBadgesData{
AdminChrome: chrome,
Form: form,
AwardUser: awardUser,
AwardBadgeID: awardBadgeID,
}
if d.Badge != nil {
list, err := d.Badge.ListDefs(true)
if err != nil {
chrome.Error = err.Error()
data.AdminChrome = chrome
} else {
data.Badges = make([]adminBadgeRow, 0, len(list))
data.LimitedBadges = make([]adminBadgeRow, 0)
for _, b := range list {
row := adminBadgeRowFrom(b)
data.Badges = append(data.Badges, row)
if row.IsLimited && row.Enabled {
data.LimitedBadges = append(data.LimitedBadges, row)
}
}
}
}
ctx.HTML(http.StatusOK, "admin/badges", data)
}
func adminBadgeRowFrom(b models.BadgeDef) adminBadgeRow {
label := "自动"
if b.Kind == models.BadgeKindLimited {
label = "限定"
}
return adminBadgeRow{
ID: b.ID, Code: b.Code, Name: b.Name, Description: b.Description, Icon: b.Icon,
Kind: b.Kind, KindLabel: label, Metric: b.Metric, Threshold: b.Threshold,
SortOrder: b.SortOrder, Enabled: b.Enabled, IsLimited: b.Kind == models.BadgeKindLimited,
}
}
func adminBadgeFormFrom(c *gin.Context) adminBadgeForm {
th, _ := strconv.Atoi(strings.TrimSpace(c.PostForm("threshold")))
so, _ := strconv.Atoi(strings.TrimSpace(c.PostForm("sort_order")))
kind := strings.TrimSpace(c.PostForm("kind"))
if kind == "" {
kind = models.BadgeKindLimited
}
return adminBadgeForm{
Code: strings.TrimSpace(c.PostForm("code")),
Name: strings.TrimSpace(c.PostForm("name")),
Description: strings.TrimSpace(c.PostForm("description")),
Icon: strings.TrimSpace(c.PostForm("icon")),
Kind: kind,
Metric: strings.TrimSpace(c.PostForm("metric")),
Threshold: th,
SortOrder: so,
Enabled: c.PostForm("enabled") == "1" || c.PostForm("enabled") == "on",
}
}
func (f adminBadgeForm) toDef() models.BadgeDef {
return models.BadgeDef{
Code: f.Code, Name: f.Name, Description: f.Description, Icon: f.Icon,
Kind: f.Kind, Metric: f.Metric, Threshold: f.Threshold,
SortOrder: f.SortOrder, Enabled: f.Enabled,
}
}
// AdminBadgeCreate 新建徽章定义
func (d Deps) AdminBadgeCreate(c *gin.Context) {
ctx := d.ctx(c)
form := adminBadgeFormFrom(c)
if !ctx.CheckCSRF() {
d.renderAdminBadges(ctx, "无效请求,请重试", form, "", 0)
return
}
if d.Badge == nil {
d.renderAdminBadges(ctx, "徽章服务未就绪", form, "", 0)
return
}
def := form.toDef()
if err := d.Badge.UpsertDef(&def); err != nil {
d.renderAdminBadges(ctx, err.Error(), form, "", 0)
return
}
ctx.SetFlash("徽章已保存")
ctx.Redirect("/admin/badges")
}
// AdminBadgeEditGet 编辑表单
func (d Deps) AdminBadgeEditGet(c *gin.Context) {
ctx := d.ctx(c)
if d.Badge == nil {
d.renderAdminBadges(ctx, "徽章服务未就绪", adminBadgeForm{Kind: models.BadgeKindLimited, Enabled: true}, "", 0)
return
}
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
list, err := d.Badge.ListDefs(true)
if err != nil {
d.renderAdminBadges(ctx, err.Error(), adminBadgeForm{}, "", 0)
return
}
for _, b := range list {
if b.ID == uint(id64) {
d.renderAdminBadges(ctx, "", adminBadgeForm{
ID: b.ID, Code: b.Code, Name: b.Name, Description: b.Description, Icon: b.Icon,
Kind: b.Kind, Metric: b.Metric, Threshold: b.Threshold, SortOrder: b.SortOrder,
Enabled: b.Enabled, IsEdit: true,
}, "", 0)
return
}
}
ctx.SetFlash("徽章不存在")
ctx.Redirect("/admin/badges")
}
// AdminBadgeUpdate 更新徽章定义
func (d Deps) AdminBadgeUpdate(c *gin.Context) {
ctx := d.ctx(c)
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
form := adminBadgeFormFrom(c)
form.ID = uint(id64)
form.IsEdit = true
if !ctx.CheckCSRF() {
d.renderAdminBadges(ctx, "无效请求,请重试", form, "", 0)
return
}
if d.Badge == nil {
d.renderAdminBadges(ctx, "徽章服务未就绪", form, "", 0)
return
}
// 编辑时 code 不可改:从已有定义取 code避免误覆盖其它徽章
list, err := d.Badge.ListDefs(true)
if err != nil {
d.renderAdminBadges(ctx, err.Error(), form, "", 0)
return
}
var found *models.BadgeDef
for i := range list {
if list[i].ID == uint(id64) {
found = &list[i]
break
}
}
if found == nil {
d.renderAdminBadges(ctx, "徽章不存在", form, "", 0)
return
}
form.Code = found.Code
def := form.toDef()
if err := d.Badge.UpsertDef(&def); err != nil {
d.renderAdminBadges(ctx, err.Error(), form, "", 0)
return
}
ctx.SetFlash("徽章已更新")
ctx.Redirect("/admin/badges")
}
// AdminBadgeDelete 删除徽章定义
func (d Deps) AdminBadgeDelete(c *gin.Context) {
ctx := d.ctx(c)
if !ctx.CheckCSRF() {
d.renderAdminBadges(ctx, "无效请求,请重试", adminBadgeForm{Kind: models.BadgeKindLimited, Enabled: true}, "", 0)
return
}
if d.Badge == nil {
d.renderAdminBadges(ctx, "徽章服务未就绪", adminBadgeForm{Kind: models.BadgeKindLimited, Enabled: true}, "", 0)
return
}
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
if err := d.Badge.DeleteDef(uint(id64)); err != nil {
d.renderAdminBadges(ctx, err.Error(), adminBadgeForm{Kind: models.BadgeKindLimited, Enabled: true}, "", 0)
return
}
ctx.SetFlash("徽章已删除")
ctx.Redirect("/admin/badges")
}
func (d Deps) resolveAwardUser(raw string) (uint, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return 0, errors.New("请填写用户名或用户 ID")
}
if id, err := strconv.ParseUint(raw, 10, 64); err == nil && id > 0 {
u, e := d.User.GetByID(uint(id))
if e != nil || u == nil {
return 0, errors.New("用户不存在")
}
return u.ID, nil
}
u, err := d.User.GetByUsername(raw)
if err != nil || u == nil {
return 0, errors.New("用户不存在")
}
return u.ID, nil
}
// AdminBadgeAwardPost 颁发限定徽章
func (d Deps) AdminBadgeAwardPost(c *gin.Context) {
ctx := d.ctx(c)
userRaw := strings.TrimSpace(c.PostForm("user"))
badgeID, _ := strconv.ParseUint(strings.TrimSpace(c.PostForm("badge_id")), 10, 64)
form := adminBadgeForm{Kind: models.BadgeKindLimited, Enabled: true}
if !ctx.CheckCSRF() {
d.renderAdminBadges(ctx, "无效请求,请重试", form, userRaw, uint(badgeID))
return
}
if d.Badge == nil || d.User == nil {
d.renderAdminBadges(ctx, "服务未就绪", form, userRaw, uint(badgeID))
return
}
uid, err := d.resolveAwardUser(userRaw)
if err != nil {
d.renderAdminBadges(ctx, err.Error(), form, userRaw, uint(badgeID))
return
}
if badgeID == 0 {
d.renderAdminBadges(ctx, "请选择徽章", form, userRaw, 0)
return
}
if err := d.Badge.AwardLimited(uid, uint(badgeID), ctx.UserID()); err != nil {
d.renderAdminBadges(ctx, err.Error(), form, userRaw, uint(badgeID))
return
}
ctx.SetFlash("已颁发徽章")
ctx.Redirect("/admin/badges")
}
// AdminBadgeRevokePost 收回徽章
func (d Deps) AdminBadgeRevokePost(c *gin.Context) {
ctx := d.ctx(c)
userRaw := strings.TrimSpace(c.PostForm("user"))
badgeID, _ := strconv.ParseUint(strings.TrimSpace(c.PostForm("badge_id")), 10, 64)
form := adminBadgeForm{Kind: models.BadgeKindLimited, Enabled: true}
if !ctx.CheckCSRF() {
d.renderAdminBadges(ctx, "无效请求,请重试", form, userRaw, uint(badgeID))
return
}
if d.Badge == nil || d.User == nil {
d.renderAdminBadges(ctx, "服务未就绪", form, userRaw, uint(badgeID))
return
}
uid, err := d.resolveAwardUser(userRaw)
if err != nil {
d.renderAdminBadges(ctx, err.Error(), form, userRaw, uint(badgeID))
return
}
if badgeID == 0 {
d.renderAdminBadges(ctx, "请选择徽章", form, userRaw, 0)
return
}
if err := d.Badge.Revoke(uid, uint(badgeID)); err != nil {
d.renderAdminBadges(ctx, err.Error(), form, userRaw, uint(badgeID))
return
}
ctx.SetFlash("已收回徽章")
ctx.Redirect("/admin/badges")
}