diff --git a/backend/handler/setting.go b/backend/handler/setting.go index 12a3951..ceac5b0 100644 --- a/backend/handler/setting.go +++ b/backend/handler/setting.go @@ -26,6 +26,7 @@ type updateSettingsRequest struct { SiteName *string `json:"site_name"` SiteDescription *string `json:"site_description"` AllowRegister *bool `json:"allow_register"` + AllowComments *bool `json:"allow_comments"` PostCooldownHours *int `json:"post_cooldown_hours"` CodeBlockAutoFold *bool `json:"code_block_auto_fold"` CodeBlockFoldLines *int `json:"code_block_fold_lines"` @@ -42,6 +43,7 @@ func settingsPayload(saved service.PublicSiteSettings) gin.H { "site_name": saved.SiteName, "site_description": saved.SiteDescription, "allow_register": saved.AllowRegister, + "allow_comments": saved.AllowComments, "post_cooldown_hours": saved.PostCooldownHours, "code_block_auto_fold": saved.CodeBlockAutoFold, "code_block_fold_lines": saved.CodeBlockFoldLines, @@ -60,7 +62,7 @@ func (h *Handlers) UpdateSettings(c *gin.Context) { return } if req.Accent == nil && req.TrustReviewedPublish == nil && req.SiteName == nil && - req.SiteDescription == nil && req.AllowRegister == nil && + req.SiteDescription == nil && req.AllowRegister == nil && req.AllowComments == nil && req.PostCooldownHours == nil && req.CodeBlockAutoFold == nil && req.CodeBlockFoldLines == nil && req.UIAnimations == nil && req.AnimCodeFold == nil && req.AnimSmoothScroll == nil && req.AnimChrome == nil { @@ -110,6 +112,12 @@ func (h *Handlers) UpdateSettings(c *gin.Context) { return } } + if req.AllowComments != nil { + if err := h.Setting.SetAllowComments(*req.AllowComments); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "保存站点设置失败"}) + return + } + } if req.PostCooldownHours != nil { if err := h.Setting.SetPostCooldownHours(*req.PostCooldownHours); err != nil { if errors.Is(err, service.ErrInvalidSiteSetting) { diff --git a/backend/service/setting.go b/backend/service/setting.go index 39fb375..1aa74ba 100644 --- a/backend/service/setting.go +++ b/backend/service/setting.go @@ -20,6 +20,8 @@ const ( SettingKeySiteName = "site_name" SettingKeySiteDescription = "site_description" SettingKeyAllowRegister = "allow_register" + // SettingKeyAllowComments 全站开放评论;缺行视为开启(默认开) + SettingKeyAllowComments = "allow_comments" SettingKeyPostCooldownHours = "post_cooldown_hours" // SettingKeyCodeBlockAutoFold 代码块自动折叠;缺行视为开启 SettingKeyCodeBlockAutoFold = "code_block_auto_fold" @@ -62,6 +64,7 @@ type PublicSiteSettings struct { SiteName string `json:"site_name"` SiteDescription string `json:"site_description"` AllowRegister bool `json:"allow_register"` + AllowComments bool `json:"allow_comments"` PostCooldownHours int `json:"post_cooldown_hours"` CodeBlockAutoFold bool `json:"code_block_auto_fold"` CodeBlockFoldLines int `json:"code_block_fold_lines"` @@ -128,6 +131,7 @@ func (s *SettingService) Public() (PublicSiteSettings, error) { SiteDescription: DefaultSiteDescription, TrustReviewedPublish: true, AllowRegister: true, + AllowComments: true, PostCooldownHours: DefaultCooldownHours, CodeBlockAutoFold: true, CodeBlockFoldLines: DefaultCodeFoldLines, @@ -173,6 +177,12 @@ func (s *SettingService) Public() (PublicSiteSettings, error) { } out.AllowRegister = parseBoolDefaultTrue(reg, found) + comments, err := s.AllowComments() + if err != nil { + return out, err + } + out.AllowComments = comments + hours, err := s.PostCooldownHours() if err != nil { return out, err @@ -286,6 +296,19 @@ func (s *SettingService) SetAllowRegister(on bool) error { return s.setBoolDefaultTrue(SettingKeyAllowRegister, on) } +// AllowComments 是否允许全站发表评论/回复。缺行视为开启。 +func (s *SettingService) AllowComments() (bool, error) { + v, found, err := s.getValue(SettingKeyAllowComments) + if err != nil { + return true, err + } + return parseBoolDefaultTrue(v, found), nil +} + +// SetAllowComments 持久化评论开关;开启时删键保持「缺行=默认开」。 +func (s *SettingService) SetAllowComments(on bool) error { + return s.setBoolDefaultTrue(SettingKeyAllowComments, on) +} // PostCooldownHours 新用户发帖冷静期(小时)。缺行=24;0 表示关闭。 func (s *SettingService) PostCooldownHours() (int, error) { diff --git a/frontend/app/admin/settings/SettingsAdmin.tsx b/frontend/app/admin/settings/SettingsAdmin.tsx index 37fe1ab..6ed891b 100644 --- a/frontend/app/admin/settings/SettingsAdmin.tsx +++ b/frontend/app/admin/settings/SettingsAdmin.tsx @@ -5,6 +5,7 @@ import { Code2, Globe, Loader2, + MessageSquare, Save, Sparkles, UserPlus, @@ -49,6 +50,7 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings }) const [siteName, setSiteName] = useState(initial.site_name); const [siteDescription, setSiteDescription] = useState(initial.site_description); const [allowRegister, setAllowRegister] = useState(initial.allow_register); + const [allowComments, setAllowComments] = useState(initial.allow_comments !== false); const [codeAutoFold, setCodeAutoFold] = useState( initial.code_block_auto_fold !== false ); @@ -62,6 +64,7 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings }) const [savedDesc, setSavedDesc] = useState(initial.site_description); const [saving, setSaving] = useState(false); const [togglingReg, setTogglingReg] = useState(false); + const [togglingComments, setTogglingComments] = useState(false); const [togglingFold, setTogglingFold] = useState(false); const [savingFoldLines, setSavingFoldLines] = useState(false); const [uiAnimations, setUiAnimations] = useState(initial.ui_animations !== false); @@ -116,6 +119,20 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings }) } }; + const toggleComments = async () => { + if (togglingComments) return; + const next = !allowComments; + setTogglingComments(true); + try { + const res = await apiUpdateSiteSettings({ allow_comments: next }); + setAllowComments(res.allow_comments); + toast(res.allow_comments ? "已开放评论" : "已关闭评论", "ok"); + } catch (e) { + toast(e instanceof Error ? e.message : "保存失败"); + } finally { + setTogglingComments(false); + } + }; const toggleCodeFold = async () => { if (togglingFold) return; @@ -201,7 +218,7 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings }) 站点设置
- 配置站点名称、简介与注册开关;主题色请到「外观」页调整 + 配置站点名称、简介与注册/评论开关;主题色请到「外观」页调整
@@ -265,6 +282,23 @@ export default function SettingsAdmin({ initial }: { initial: PublicSettings }) /> ++ 关闭后全站禁止发表新评论与回复,已有评论仍可浏览。适用于 ICP/IPC + 备案审核等合规需要;前台仅提示「暂停评论」,不会展示备案事由。 +
+