feat: SSR 三栏右栏热门帖与 aside widgets

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-29 15:19:32 +08:00
parent f76d3e3bf8
commit 959a2c3a2e
12 changed files with 386 additions and 35 deletions

View File

@@ -36,15 +36,16 @@ type adminFriendLinkApplyRow struct {
type adminFriendLinksData struct {
AdminChrome
BrandLinks []adminFriendLinkBrandRow
Applies []adminFriendLinkApplyRow
PendingCount int64
NavShow bool
FooterShow bool
ReciprocalCheck bool
BrandName string
BrandURL string
BrandLogo string
BrandLinks []adminFriendLinkBrandRow
Applies []adminFriendLinkApplyRow
PendingCount int64
NavShow bool
FooterShow bool
AsideShow bool
ReciprocalCheck bool
BrandName string
BrandURL string
BrandLogo string
}
// AdminFriendLinksGet 友链管理
@@ -60,6 +61,7 @@ func (d Deps) renderAdminFriendLinks(ctx *webctx.Context, errMsg string) {
AdminChrome: chrome,
NavShow: d.Settings.NavShowFriendLinks(),
FooterShow: d.Settings.FooterShowFriendLinks(),
AsideShow: d.Settings.AsideShowFriendLinks(),
ReciprocalCheck: d.Settings.FriendLinkReciprocalCheckEnabled(),
}
brand := d.Settings.SiteBranding()
@@ -105,6 +107,7 @@ func (d Deps) AdminFriendLinksSettingsPost(c *gin.Context) {
}
_ = d.Settings.SetNavShowFriendLinks(c.PostForm("nav_show") == "1" || c.PostForm("nav_show") == "on")
_ = d.Settings.SetFooterShowFriendLinks(c.PostForm("footer_show") == "1" || c.PostForm("footer_show") == "on")
_ = d.Settings.SetAsideFriendLinksEnabled(c.PostForm("aside_show") == "1" || c.PostForm("aside_show") == "on")
_ = d.Settings.SetFriendLinkReciprocalCheckEnabled(c.PostForm("reciprocal_check") == "1" || c.PostForm("reciprocal_check") == "on")
ctx.SetFlash("友链入口设置已保存")
ctx.Redirect("/admin/friend-links")

View File

@@ -34,27 +34,76 @@ type BoardView struct {
Name string
}
// RightAsideHotPost 右栏热门帖
type RightAsideHotPost struct {
ID uint
Title string
}
// RightAsideTag 右栏标签
type RightAsideTag struct {
Name string
Count int
}
// RightAsideComment 右栏最新评论
type RightAsideComment struct {
PostID uint
Author string
Excerpt string
PostTitle string
}
// RightAsideUser 右栏最新用户
type RightAsideUser struct {
ID uint
Nickname string
}
// RightAsideFriendLink 右栏友链
type RightAsideFriendLink struct {
Name string
URL string
Logo string
}
// RightAsideWidget 按 aside_widgets 顺序的一块(仅已开启)
type RightAsideWidget struct {
Kind string // tag_cloud | recent_comments | recent_users | friend_links
Tags []RightAsideTag
Comments []RightAsideComment
Users []RightAsideUser
FriendLinks []RightAsideFriendLink
}
// RightAsideData 右栏:固定热门 + 可配置 widgets
type RightAsideData struct {
HotPosts []RightAsideHotPost
Widgets []RightAsideWidget
}
// PageChrome 布局公共字段
type PageChrome struct {
Title string
Description string
SiteName string
Slogan string
LogoMark string
LoggedIn bool
IsAdmin bool
ViewerName string
Boards []BoardView
ActiveBoard uint
Path string // 当前请求路径,供侧栏高亮
Inner string // 保留字段;入口模板已固定组合,不再动态 template
CSRF string
Flash string
Error string
Title string
Description string
SiteName string
Slogan string
LogoMark string
LoggedIn bool
IsAdmin bool
ViewerName string
Boards []BoardView
ActiveBoard uint
Path string // 当前请求路径,供侧栏高亮
Inner string // 保留字段;入口模板已固定组合,不再动态 template
CSRF string
Flash string
Error string
UnreadCount int64 // 登录用户未读私信/通知总数;未登录为 0
ViewerPoints int // 登录用户当前积分;未登录为 0
ShowFriendLinksNav bool
ShowFriendLinksFooter bool
RightAside RightAsideData
}
func (d Deps) ctx(c *gin.Context) *webctx.Context {
@@ -111,9 +160,73 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
ViewerPoints: viewerPoints,
ShowFriendLinksNav: d.Settings.NavShowFriendLinks(),
ShowFriendLinksFooter: d.Settings.FooterShowFriendLinks(),
RightAside: d.loadRightAside(brand),
}
}
const (
rightAsideHotLimit = 5
rightAsideTagLimit = 24
rightAsideCommentLimit = 8
rightAsideUserLimit = 8
)
func (d Deps) loadRightAside(brand services.SiteBranding) RightAsideData {
out := RightAsideData{
HotPosts: []RightAsideHotPost{},
Widgets: []RightAsideWidget{},
}
if d.Post != nil {
if items, err := d.Post.HotPosts(rightAsideHotLimit); err == nil {
for _, it := range items {
out.HotPosts = append(out.HotPosts, RightAsideHotPost{ID: it.ID, Title: it.Title})
}
}
}
for _, w := range d.Settings.AsideWidgets() {
if !w.Enabled {
continue
}
block := RightAsideWidget{Kind: w.ID}
switch w.ID {
case services.AsideWidgetTagCloud:
if d.Post != nil {
if tags, err := d.Post.PopularTags(rightAsideTagLimit); err == nil {
for _, t := range tags {
block.Tags = append(block.Tags, RightAsideTag{Name: t.Name, Count: t.Count})
}
}
}
case services.AsideWidgetRecentComments:
if d.Comment != nil {
if list, err := d.Comment.ListRecentPublic(rightAsideCommentLimit); err == nil {
for _, c := range list {
block.Comments = append(block.Comments, RightAsideComment{
PostID: c.PostID, Author: c.Author, Excerpt: c.Excerpt, PostTitle: c.PostTitle,
})
}
}
}
case services.AsideWidgetRecentUsers:
if d.User != nil {
if list, err := d.User.ListRecentRegistered(rightAsideUserLimit); err == nil {
for _, u := range list {
block.Users = append(block.Users, RightAsideUser{ID: u.ID, Nickname: u.Nickname})
}
}
}
case services.AsideWidgetFriendLinks:
for _, l := range brand.FriendLinks {
block.FriendLinks = append(block.FriendLinks, RightAsideFriendLink{Name: l.Name, URL: l.URL, Logo: l.Logo})
}
default:
continue
}
out.Widgets = append(out.Widgets, block)
}
return out
}
func firstRuneOr(s, fallback string) string {
s = strings.TrimSpace(s)
if s == "" {