feat: 新增站点公告系统与多项体验优化
此提交完成了以下核心功能与优化: 1. 新增站点公告全流程支持:包含公告模型、CRUD接口、管理后台页面、公告详情页与首页展示 2. 实现评论楼层化与回复嵌套:重构评论系统为树状结构,支持回复评论与层级限制 3. 添加用户在线状态追踪:异步更新用户最后活跃时间并做SQL限频 4. 优化用户体验:移动端浮动操作条、统一板块标签样式、用户页签到积分展示、注册即登录优化 5. 其他细节优化:SEO站点地图新增公告页、评论分页与统计、登录/注册表单增强等
This commit is contained in:
111
backend/handler/announcement.go
Normal file
111
backend/handler/announcement.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/freefire/jiang13-bbs/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ===== 公开接口 =====
|
||||
|
||||
// AnnouncementsList 已发布公告列表(首页右栏 / 公告页)
|
||||
func (h *Handlers) AnnouncementsList(c *gin.Context) {
|
||||
list, err := h.Announcement.ListPublished(10)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取公告失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"announcements": list})
|
||||
}
|
||||
|
||||
// AnnouncementDetail 已发布公告详情
|
||||
func (h *Handlers) AnnouncementDetail(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公告 ID"})
|
||||
return
|
||||
}
|
||||
a, err := h.Announcement.GetPublished(uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取公告失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"announcement": a})
|
||||
}
|
||||
|
||||
// ===== 管理接口(RequireAdmin 兜底,前端不做权限判定) =====
|
||||
|
||||
// AdminListAnnouncements 全部公告(含草稿)
|
||||
func (h *Handlers) AdminListAnnouncements(c *gin.Context) {
|
||||
list, err := h.Announcement.ListAll()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取公告失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"announcements": list})
|
||||
}
|
||||
|
||||
// AdminCreateAnnouncement 新建公告
|
||||
func (h *Handlers) AdminCreateAnnouncement(c *gin.Context) {
|
||||
var in service.AnnouncementInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
||||
return
|
||||
}
|
||||
a, err := h.Announcement.Create(&in)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{"announcement": a})
|
||||
}
|
||||
|
||||
// AdminUpdateAnnouncement 编辑公告
|
||||
func (h *Handlers) AdminUpdateAnnouncement(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公告 ID"})
|
||||
return
|
||||
}
|
||||
var in service.AnnouncementInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
||||
return
|
||||
}
|
||||
a, err := h.Announcement.Update(uint(id), &in)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"announcement": a})
|
||||
}
|
||||
|
||||
// AdminDeleteAnnouncement 删除公告
|
||||
func (h *Handlers) AdminDeleteAnnouncement(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的公告 ID"})
|
||||
return
|
||||
}
|
||||
if err := h.Announcement.Delete(uint(id)); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
Reference in New Issue
Block a user