feat: Admin settings 侧栏 aside_widgets 开关与排序
让运营可在 /admin/settings 配置右栏组件,并与友链页 aside 开关共用同一份 JSON。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -404,19 +404,29 @@ func (d Deps) AdminCommentReject(c *gin.Context) {
|
||||
ctx.Redirect("/admin/moderation")
|
||||
}
|
||||
|
||||
type adminAsideWidgetRow struct {
|
||||
ID string
|
||||
Label string
|
||||
Enabled bool
|
||||
Index int
|
||||
CanMoveUp bool
|
||||
CanMoveDown bool
|
||||
}
|
||||
|
||||
type adminSettingsData struct {
|
||||
AdminChrome
|
||||
Brand services.SiteBranding
|
||||
RatePost int
|
||||
RateComment int
|
||||
RateReg int
|
||||
RateLogin int
|
||||
RateWindow int
|
||||
FilterWords string
|
||||
FilterCount int
|
||||
Mail services.MailConfig
|
||||
MailReady bool
|
||||
TestTo string
|
||||
Brand services.SiteBranding
|
||||
RatePost int
|
||||
RateComment int
|
||||
RateReg int
|
||||
RateLogin int
|
||||
RateWindow int
|
||||
FilterWords string
|
||||
FilterCount int
|
||||
Mail services.MailConfig
|
||||
MailReady bool
|
||||
TestTo string
|
||||
AsideWidgets []adminAsideWidgetRow
|
||||
}
|
||||
|
||||
// AdminSettingsGet 设置页
|
||||
@@ -429,23 +439,48 @@ func (d Deps) renderAdminSettings(ctx *webctx.Context, errMsg string) {
|
||||
words := d.Settings.FilterWordsContent()
|
||||
lim := d.Settings.Limits()
|
||||
mail := d.Settings.MailConfigPublic()
|
||||
widgets := d.Settings.AsideWidgets()
|
||||
asideRows := make([]adminAsideWidgetRow, 0, len(widgets))
|
||||
last := len(widgets) - 1
|
||||
for i, w := range widgets {
|
||||
asideRows = append(asideRows, adminAsideWidgetRow{
|
||||
ID: w.ID, Label: asideWidgetLabel(w.ID), Enabled: w.Enabled, Index: i,
|
||||
CanMoveUp: i > 0, CanMoveDown: i < last,
|
||||
})
|
||||
}
|
||||
data := adminSettingsData{
|
||||
AdminChrome: d.adminChrome(ctx, "站点设置", "settings"),
|
||||
Brand: d.Settings.SiteBranding(),
|
||||
RatePost: lim.RateLimitPost,
|
||||
RateComment: lim.RateLimitComment,
|
||||
RateReg: lim.RateLimitRegister,
|
||||
RateLogin: lim.RateLimitLogin,
|
||||
RateWindow: lim.RateLimitWindowSec,
|
||||
FilterWords: words,
|
||||
FilterCount: services.CountFilterWords(words),
|
||||
Mail: mail,
|
||||
MailReady: d.Settings.MailReady(),
|
||||
AdminChrome: d.adminChrome(ctx, "站点设置", "settings"),
|
||||
Brand: d.Settings.SiteBranding(),
|
||||
RatePost: lim.RateLimitPost,
|
||||
RateComment: lim.RateLimitComment,
|
||||
RateReg: lim.RateLimitRegister,
|
||||
RateLogin: lim.RateLimitLogin,
|
||||
RateWindow: lim.RateLimitWindowSec,
|
||||
FilterWords: words,
|
||||
FilterCount: services.CountFilterWords(words),
|
||||
Mail: mail,
|
||||
MailReady: d.Settings.MailReady(),
|
||||
AsideWidgets: asideRows,
|
||||
}
|
||||
data.Error = errMsg
|
||||
ctx.HTML(http.StatusOK, "admin/settings", data)
|
||||
}
|
||||
|
||||
func asideWidgetLabel(id string) string {
|
||||
switch id {
|
||||
case services.AsideWidgetTagCloud:
|
||||
return "标签云"
|
||||
case services.AsideWidgetRecentComments:
|
||||
return "最新评论"
|
||||
case services.AsideWidgetRecentUsers:
|
||||
return "最新用户"
|
||||
case services.AsideWidgetFriendLinks:
|
||||
return "友链"
|
||||
default:
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
// AdminSettingsBrandPost 品牌
|
||||
func (d Deps) AdminSettingsBrandPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
@@ -567,3 +602,70 @@ func (d Deps) AdminSettingsMailTestPost(c *gin.Context) {
|
||||
ctx.SetFlash("测试邮件已发送至 " + to)
|
||||
ctx.Redirect("/admin/settings")
|
||||
}
|
||||
|
||||
// AdminSettingsAsideWidgetsPost 侧栏组件开关与排序
|
||||
func (d Deps) AdminSettingsAsideWidgetsPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminSettings(ctx, "无效请求,请重试")
|
||||
return
|
||||
}
|
||||
order := c.PostFormArray("order")
|
||||
enabledSet := make(map[string]bool)
|
||||
for _, id := range c.PostFormArray("enabled") {
|
||||
id = strings.TrimSpace(id)
|
||||
if id != "" {
|
||||
enabledSet[id] = true
|
||||
}
|
||||
}
|
||||
widgets := make([]services.AsideWidget, 0, len(order))
|
||||
for _, id := range order {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
widgets = append(widgets, services.AsideWidget{ID: id, Enabled: enabledSet[id]})
|
||||
}
|
||||
widgets = services.NormalizeAsideWidgets(widgets)
|
||||
if move := strings.TrimSpace(c.PostForm("move")); move != "" {
|
||||
widgets = moveAsideWidget(widgets, move)
|
||||
}
|
||||
if err := d.Settings.UpdateAsideWidgets(widgets); err != nil {
|
||||
d.renderAdminSettings(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("侧栏组件已保存")
|
||||
ctx.Redirect("/admin/settings")
|
||||
}
|
||||
|
||||
// moveAsideWidget 解析 move 值「up:id」或「down:id」并交换相邻项
|
||||
func moveAsideWidget(widgets []services.AsideWidget, move string) []services.AsideWidget {
|
||||
parts := strings.SplitN(move, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return widgets
|
||||
}
|
||||
dir, id := parts[0], strings.TrimSpace(parts[1])
|
||||
idx := -1
|
||||
for i, w := range widgets {
|
||||
if w.ID == id {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
return widgets
|
||||
}
|
||||
switch dir {
|
||||
case "up":
|
||||
if idx == 0 {
|
||||
return widgets
|
||||
}
|
||||
widgets[idx-1], widgets[idx] = widgets[idx], widgets[idx-1]
|
||||
case "down":
|
||||
if idx >= len(widgets)-1 {
|
||||
return widgets
|
||||
}
|
||||
widgets[idx], widgets[idx+1] = widgets[idx+1], widgets[idx]
|
||||
}
|
||||
return widgets
|
||||
}
|
||||
|
||||
31
routers/web/aside_widgets_test.go
Normal file
31
routers/web/aside_widgets_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/services"
|
||||
)
|
||||
|
||||
func TestMoveAsideWidget(t *testing.T) {
|
||||
base := []services.AsideWidget{
|
||||
{ID: services.AsideWidgetTagCloud, Enabled: true},
|
||||
{ID: services.AsideWidgetRecentComments, Enabled: false},
|
||||
{ID: services.AsideWidgetRecentUsers, Enabled: false},
|
||||
{ID: services.AsideWidgetFriendLinks, Enabled: true},
|
||||
}
|
||||
|
||||
up := moveAsideWidget(append([]services.AsideWidget(nil), base...), "up:"+services.AsideWidgetRecentComments)
|
||||
if up[0].ID != services.AsideWidgetRecentComments || up[1].ID != services.AsideWidgetTagCloud {
|
||||
t.Fatalf("up move failed: %+v", up)
|
||||
}
|
||||
|
||||
down := moveAsideWidget(append([]services.AsideWidget(nil), base...), "down:"+services.AsideWidgetTagCloud)
|
||||
if down[0].ID != services.AsideWidgetRecentComments || down[1].ID != services.AsideWidgetTagCloud {
|
||||
t.Fatalf("down move failed: %+v", down)
|
||||
}
|
||||
|
||||
noop := moveAsideWidget(append([]services.AsideWidget(nil), base...), "up:"+services.AsideWidgetTagCloud)
|
||||
if noop[0].ID != services.AsideWidgetTagCloud {
|
||||
t.Fatalf("top up should noop: %+v", noop)
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
admin.POST("/settings/filter-words", deps.AdminSettingsFilterWordsPost)
|
||||
admin.POST("/settings/mail", deps.AdminSettingsMailPost)
|
||||
admin.POST("/settings/mail/test", deps.AdminSettingsMailTestPost)
|
||||
admin.POST("/settings/aside-widgets", deps.AdminSettingsAsideWidgetsPost)
|
||||
admin.GET("/friend-links", deps.AdminFriendLinksGet)
|
||||
admin.POST("/friend-links/settings", deps.AdminFriendLinksSettingsPost)
|
||||
admin.POST("/friend-links/brand", deps.AdminFriendLinksBrandAddPost)
|
||||
|
||||
Reference in New Issue
Block a user