feat: Admin 设用户等级并展示徽章于用户页
补齐 SetUserLevel 表单;用户主页/资料页展示徽章并触发 EvaluateAuto;勾选 §H Exp 与防刷。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -30,15 +30,16 @@ type adminUserRow struct {
|
||||
|
||||
type adminUsersData struct {
|
||||
AdminChrome
|
||||
Users []adminUserRow
|
||||
Keyword string
|
||||
Filter string
|
||||
Page int
|
||||
HasPrev bool
|
||||
HasMore bool
|
||||
PrevPage int
|
||||
NextPage int
|
||||
Users []adminUserRow
|
||||
Keyword string
|
||||
Filter string
|
||||
Page int
|
||||
HasPrev bool
|
||||
HasMore bool
|
||||
PrevPage int
|
||||
NextPage int
|
||||
QuerySuffix string
|
||||
LevelOptions []int
|
||||
}
|
||||
|
||||
// AdminUsersGet 用户列表
|
||||
@@ -71,6 +72,7 @@ func (d Deps) renderAdminUsers(ctx *webctx.Context, errMsg string) {
|
||||
PrevPage: page - 1,
|
||||
NextPage: page + 1,
|
||||
HasPrev: page > 1,
|
||||
LevelOptions: adminLevelOptions(),
|
||||
}
|
||||
q := url.Values{}
|
||||
if keyword != "" {
|
||||
@@ -120,6 +122,15 @@ func adminUserRoleLabel(role models.Role) string {
|
||||
}
|
||||
}
|
||||
|
||||
func adminLevelOptions() []int {
|
||||
n := models.MaxLevel()
|
||||
out := make([]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
out[i] = i + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// AdminUserBanPost 禁言 / 解禁
|
||||
func (d Deps) AdminUserBanPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
@@ -192,3 +203,24 @@ func (d Deps) AdminUserPointsPost(c *gin.Context) {
|
||||
ctx.SetFlash(fmt.Sprintf("积分已调整,当前余额 %d", bal))
|
||||
ctx.Redirect("/admin/users")
|
||||
}
|
||||
|
||||
// AdminUserLevelPost 设定等级(同步 Exp 到门槛)
|
||||
func (d Deps) AdminUserLevelPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminUsers(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
id64, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
level, err := strconv.Atoi(strings.TrimSpace(c.PostForm("level")))
|
||||
if err != nil {
|
||||
d.renderAdminUsers(ctx, "请选择有效等级")
|
||||
return
|
||||
}
|
||||
if err := services.SetUserLevel(uint(id64), level); err != nil {
|
||||
d.renderAdminUsers(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.SetFlash(fmt.Sprintf("已设为 Lv%d(经验门槛 %d)", level, models.ExpForLevel(level)))
|
||||
ctx.Redirect("/admin/users")
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
admin.POST("/users/:id/ban", deps.AdminUserBanPost)
|
||||
admin.POST("/users/:id/verify", deps.AdminUserVerifyPost)
|
||||
admin.POST("/users/:id/points", deps.AdminUserPointsPost)
|
||||
admin.POST("/users/:id/level", deps.AdminUserLevelPost)
|
||||
admin.GET("/badges", deps.AdminBadgesGet)
|
||||
admin.POST("/badges", deps.AdminBadgeCreate)
|
||||
admin.POST("/badges/award", deps.AdminBadgeAwardPost)
|
||||
|
||||
@@ -33,6 +33,7 @@ type profileData struct {
|
||||
Level int
|
||||
Exp int
|
||||
Points int
|
||||
Badges []userPublicBadge
|
||||
PostCount int64
|
||||
CommentCount int64
|
||||
FavoriteCount int64
|
||||
@@ -112,6 +113,14 @@ func (d Deps) renderProfile(ctx *webctx.Context, errMsg string) {
|
||||
AvatarMaxMB: d.Settings.AvatarMaxMB(),
|
||||
SignatureMax: d.Settings.SignatureMax(),
|
||||
}
|
||||
if d.Badge != nil {
|
||||
_ = d.Badge.EvaluateAuto(uid)
|
||||
if rows, err := d.Badge.ListUserBadges(uid); err == nil {
|
||||
for _, v := range services.BadgeViews(rows, 0) {
|
||||
data.Badges = append(data.Badges, userPublicBadge{Name: v.Name, Description: v.Description})
|
||||
}
|
||||
}
|
||||
}
|
||||
if d.Points != nil {
|
||||
data.CheckIn, _ = d.Points.GetCheckInStatus(uid)
|
||||
data.Lottery, _ = d.Points.GetLotteryStatus(uid)
|
||||
|
||||
@@ -10,6 +10,11 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type userPublicBadge struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type userPublicData struct {
|
||||
PageChrome
|
||||
UserID uint
|
||||
@@ -20,6 +25,7 @@ type userPublicData struct {
|
||||
Level int
|
||||
Exp int
|
||||
Points int
|
||||
Badges []userPublicBadge
|
||||
PostCount int64
|
||||
CommentCount int64
|
||||
FavoriteCount int64
|
||||
@@ -75,6 +81,15 @@ func (d Deps) UserPublic(c *gin.Context) {
|
||||
nick := displayName(user)
|
||||
chrome := d.chrome(ctx, nick+" · "+d.Settings.SiteBranding().Name, strings.TrimSpace(user.Signature), "")
|
||||
hasMore := int64(page*size) < total
|
||||
badges := []userPublicBadge{}
|
||||
if d.Badge != nil {
|
||||
_ = d.Badge.EvaluateAuto(user.ID)
|
||||
if rows, err := d.Badge.ListUserBadges(user.ID); err == nil {
|
||||
for _, v := range services.BadgeViews(rows, 0) {
|
||||
badges = append(badges, userPublicBadge{Name: v.Name, Description: v.Description})
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "user/view", userPublicData{
|
||||
PageChrome: chrome,
|
||||
UserID: user.ID,
|
||||
@@ -85,6 +100,7 @@ func (d Deps) UserPublic(c *gin.Context) {
|
||||
Level: models.LevelFromExp(user.Exp),
|
||||
Exp: user.Exp,
|
||||
Points: user.Points,
|
||||
Badges: badges,
|
||||
PostCount: st.PostCount, CommentCount: st.CommentCount,
|
||||
FavoriteCount: st.FavoriteCount, LikeReceived: st.LikeReceived,
|
||||
IsSelf: ctx.UserID() == user.ID,
|
||||
|
||||
Reference in New Issue
Block a user