diff --git a/frontend/src/pages/admin/AdminSettingsPage.tsx b/frontend/src/pages/admin/AdminSettingsPage.tsx
index 3532c18..98ecf55 100644
--- a/frontend/src/pages/admin/AdminSettingsPage.tsx
+++ b/frontend/src/pages/admin/AdminSettingsPage.tsx
@@ -299,6 +299,8 @@ export default function AdminSettingsPage() {
aside_show_recent_comments: false,
aside_show_friend_links: true,
aside_widgets: loadedAsideWidgets,
+ nav_show_friend_links: true,
+ footer_show_friend_links: true,
feed_list_style: 'title',
permalink_enabled: false,
permalink_ext: 'html',
diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css
index 93f5039..e1ccc4e 100644
--- a/frontend/src/styles/global.css
+++ b/frontend/src/styles/global.css
@@ -13826,6 +13826,25 @@ button.post-poll__option,
gap: 8px;
align-items: center;
}
+.admin-links-entry-card {
+ margin-bottom: 16px;
+}
+.admin-links-entry-body {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px 28px;
+}
+.admin-links-entry-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 13px;
+ color: var(--foreground, #334155);
+}
+.admin-links-entry-row .admin-settings-switch:disabled {
+ opacity: 0.55;
+ cursor: not-allowed;
+}
.admin-links-columns {
display: flex;
flex-direction: column;
diff --git a/handler/friend_link.go b/handler/friend_link.go
index ba842f2..a1beca5 100644
--- a/handler/friend_link.go
+++ b/handler/friend_link.go
@@ -100,27 +100,64 @@ func (h *Handlers) APIAdminFriendLinkApplies(c *gin.Context) {
})
}
-// APIAdminUpdateFriendLinkSettings 更新友链回链检测开关
+// APIAdminUpdateFriendLinkSettings 更新友链相关开关(回链检测 / 入口展示)
func (h *Handlers) APIAdminUpdateFriendLinkSettings(c *gin.Context) {
var req struct {
ReciprocalCheckEnabled *bool `json:"reciprocal_check_enabled"`
+ NavShowFriendLinks *bool `json:"nav_show_friend_links"`
+ FooterShowFriendLinks *bool `json:"footer_show_friend_links"`
+ AsideShowFriendLinks *bool `json:"aside_show_friend_links"`
}
- if err := c.ShouldBindJSON(&req); err != nil || req.ReciprocalCheckEnabled == nil {
+ if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
- if err := h.Settings.SetFriendLinkReciprocalCheckEnabled(*req.ReciprocalCheckEnabled); err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ if req.ReciprocalCheckEnabled == nil && req.NavShowFriendLinks == nil &&
+ req.FooterShowFriendLinks == nil && req.AsideShowFriendLinks == nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
- enabled := *req.ReciprocalCheckEnabled
- msg := "已开启回链检测"
- if !enabled {
- msg = "已关闭回链检测"
+ if req.ReciprocalCheckEnabled != nil {
+ if err := h.Settings.SetFriendLinkReciprocalCheckEnabled(*req.ReciprocalCheckEnabled); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
}
+ if req.NavShowFriendLinks != nil {
+ if err := h.Settings.SetNavShowFriendLinks(*req.NavShowFriendLinks); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+ }
+ if req.FooterShowFriendLinks != nil {
+ if err := h.Settings.SetFooterShowFriendLinks(*req.FooterShowFriendLinks); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+ }
+ if req.AsideShowFriendLinks != nil {
+ if err := h.Settings.SetAsideFriendLinksEnabled(*req.AsideShowFriendLinks); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+ }
+
+ msg := "设置已保存"
+ if req.ReciprocalCheckEnabled != nil && req.NavShowFriendLinks == nil &&
+ req.FooterShowFriendLinks == nil && req.AsideShowFriendLinks == nil {
+ if *req.ReciprocalCheckEnabled {
+ msg = "已开启回链检测"
+ } else {
+ msg = "已关闭回链检测"
+ }
+ }
+
c.JSON(http.StatusOK, gin.H{
- "message": msg,
- "reciprocal_check_enabled": enabled,
+ "message": msg,
+ "reciprocal_check_enabled": h.Settings.FriendLinkReciprocalCheckEnabled(),
+ "nav_show_friend_links": h.Settings.NavShowFriendLinks(),
+ "footer_show_friend_links": h.Settings.FooterShowFriendLinks(),
+ "aside_show_friend_links": h.Settings.AsideShowFriendLinks(),
})
}
diff --git a/service/settings.go b/service/settings.go
index ebd24a1..42b2d51 100644
--- a/service/settings.go
+++ b/service/settings.go
@@ -43,6 +43,8 @@ const (
SettingAsideShowRecentComments = "aside_show_recent_comments"
SettingAsideShowFriendLinks = "aside_show_friend_links"
SettingAsideWidgets = "aside_widgets"
+ SettingNavShowFriendLinks = "nav_show_friend_links"
+ SettingFooterShowFriendLinks = "footer_show_friend_links"
SettingFeedListStyle = "feed_list_style"
// 伪静态键名见 permalink.go:SettingPermalinkEnabled / SettingPermalinkExt
@@ -128,6 +130,8 @@ type ForumLimits struct {
AsideShowRecentComments bool `json:"aside_show_recent_comments"`
AsideShowFriendLinks bool `json:"aside_show_friend_links"`
AsideWidgets []AsideWidget `json:"aside_widgets"`
+ NavShowFriendLinks bool `json:"nav_show_friend_links"`
+ FooterShowFriendLinks bool `json:"footer_show_friend_links"`
FeedListStyle string `json:"feed_list_style"`
@@ -177,6 +181,8 @@ type ForumLimitsPublic struct {
AsideShowRecentComments bool `json:"aside_show_recent_comments"`
AsideShowFriendLinks bool `json:"aside_show_friend_links"`
AsideWidgets []AsideWidget `json:"aside_widgets"`
+ NavShowFriendLinks bool `json:"nav_show_friend_links"`
+ FooterShowFriendLinks bool `json:"footer_show_friend_links"`
FeedListStyle string `json:"feed_list_style"`
@@ -272,6 +278,8 @@ var storageSettingDefaults = map[string]string{
var friendLinkSettingDefaults = map[string]string{
SettingFriendLinkReciprocalCheck: "0", // 默认关闭回链检测
+ SettingNavShowFriendLinks: "1",
+ SettingFooterShowFriendLinks: "1",
}
var siteBrandingDefaults = map[string]string{
@@ -538,6 +546,8 @@ func (s *ForumSettingsService) Limits() ForumLimits {
AsideShowRecentComments: bools.recentComments,
AsideShowFriendLinks: bools.friendLinks,
AsideWidgets: widgets,
+ NavShowFriendLinks: s.NavShowFriendLinks(),
+ FooterShowFriendLinks: s.FooterShowFriendLinks(),
FeedListStyle: s.FeedListStyle(),
@@ -569,6 +579,8 @@ func (s *ForumSettingsService) PublicLimits() ForumLimitsPublic {
AsideShowRecentComments: limits.AsideShowRecentComments,
AsideShowFriendLinks: limits.AsideShowFriendLinks,
AsideWidgets: limits.AsideWidgets,
+ NavShowFriendLinks: limits.NavShowFriendLinks,
+ FooterShowFriendLinks: limits.FooterShowFriendLinks,
FeedListStyle: limits.FeedListStyle,
@@ -616,6 +628,8 @@ func (s *ForumSettingsService) UpdateLimits(in ForumLimits) error {
SettingAsideShowTagCloud: bools.tagCloud,
SettingAsideShowRecentComments: bools.recentComments,
SettingAsideShowFriendLinks: bools.friendLinks,
+ SettingNavShowFriendLinks: in.NavShowFriendLinks,
+ SettingFooterShowFriendLinks: in.FooterShowFriendLinks,
SettingPermalinkEnabled: in.PermalinkEnabled,
}
for key, on := range boolUpdates {
@@ -705,6 +719,16 @@ func (s *ForumSettingsService) FriendLinkReciprocalCheckEnabled() bool {
return s.getString(SettingFriendLinkReciprocalCheck, "0") == "1"
}
+// NavShowFriendLinks 左侧栏「站点」是否展示友链入口;缺省开启
+func (s *ForumSettingsService) NavShowFriendLinks() bool {
+ return s.getString(SettingNavShowFriendLinks, "1") == "1"
+}
+
+// FooterShowFriendLinks 页脚是否展示友链入口;缺省开启
+func (s *ForumSettingsService) FooterShowFriendLinks() bool {
+ return s.getString(SettingFooterShowFriendLinks, "1") == "1"
+}
+
func (s *ForumSettingsService) SetFriendLinkReciprocalCheckEnabled(enabled bool) error {
v := "0"
if enabled {
@@ -713,6 +737,52 @@ func (s *ForumSettingsService) SetFriendLinkReciprocalCheckEnabled(enabled bool)
return s.setString(SettingFriendLinkReciprocalCheck, v)
}
+func (s *ForumSettingsService) SetNavShowFriendLinks(enabled bool) error {
+ v := "0"
+ if enabled {
+ v = "1"
+ }
+ return s.setString(SettingNavShowFriendLinks, v)
+}
+
+func (s *ForumSettingsService) SetFooterShowFriendLinks(enabled bool) error {
+ v := "0"
+ if enabled {
+ v = "1"
+ }
+ return s.setString(SettingFooterShowFriendLinks, v)
+}
+
+// SetAsideFriendLinksEnabled 更新右侧栏友链组件开关(与 aside_widgets 同步)
+func (s *ForumSettingsService) SetAsideFriendLinksEnabled(enabled bool) error {
+ widgets := s.AsideWidgets()
+ found := false
+ for i := range widgets {
+ if widgets[i].ID == AsideWidgetFriendLinks {
+ widgets[i].Enabled = enabled
+ found = true
+ break
+ }
+ }
+ if !found {
+ widgets = append(widgets, AsideWidget{ID: AsideWidgetFriendLinks, Enabled: enabled})
+ }
+ widgets = NormalizeAsideWidgets(widgets)
+ bools := asideBoolsFromWidgets(widgets)
+ payload, err := json.Marshal(widgets)
+ if err != nil {
+ return err
+ }
+ if err := s.setString(SettingAsideWidgets, string(payload)); err != nil {
+ return err
+ }
+ v := "0"
+ if bools.friendLinks {
+ v = "1"
+ }
+ return s.setString(SettingAsideShowFriendLinks, v)
+}
+
type asideWidgetBools struct {
tagCloud bool
recentComments bool