feat: add announcement feature with admin management and home page display
Build and Push Docker Image / build-and-push (push) Successful in 1m3s
Build and Push Docker Image / deploy (push) Successful in 7s

This commit is contained in:
nuyue
2026-07-16 15:28:37 +08:00
parent c94641cda0
commit 43009ec3b9
9 changed files with 426 additions and 5 deletions
@@ -0,0 +1,123 @@
package handlers
import (
"net/http"
"strconv"
"sale/internal/models"
"sale/internal/utils"
"github.com/gin-gonic/gin"
)
type AnnouncementHandler struct{}
func NewAnnouncementHandler() *AnnouncementHandler {
return &AnnouncementHandler{}
}
// List 用户端接口 - 只返回激活的公告
func (h *AnnouncementHandler) List(c *gin.Context) {
var announcements []models.Announcement
utils.DB.Where("is_active = ?", true).
Order("sort_order ASC, created_at DESC").
Find(&announcements)
c.JSON(http.StatusOK, gin.H{"data": announcements})
}
// AdminList 管理端接口 - 返回所有公告
func (h *AnnouncementHandler) AdminList(c *gin.Context) {
var announcements []models.Announcement
utils.DB.Order("sort_order ASC, created_at DESC").Find(&announcements)
c.JSON(http.StatusOK, gin.H{"data": announcements})
}
// Create 创建公告
func (h *AnnouncementHandler) Create(c *gin.Context) {
var req struct {
Title string `json:"title" binding:"required"`
Content string `json:"content"`
SortOrder int `json:"sort_order"`
IsActive bool `json:"is_active"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "标题不能为空"})
return
}
announcement := models.Announcement{
Title: req.Title,
Content: req.Content,
SortOrder: req.SortOrder,
IsActive: req.IsActive,
}
utils.DB.Create(&announcement)
c.JSON(http.StatusCreated, gin.H{"data": announcement})
}
// Update 更新公告
func (h *AnnouncementHandler) Update(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
return
}
var announcement models.Announcement
if err := utils.DB.First(&announcement, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
return
}
var req struct {
Title string `json:"title"`
Content string `json:"content"`
SortOrder int `json:"sort_order"`
IsActive bool `json:"is_active"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
announcement.Title = req.Title
announcement.Content = req.Content
announcement.SortOrder = req.SortOrder
announcement.IsActive = req.IsActive
utils.DB.Save(&announcement)
c.JSON(http.StatusOK, gin.H{"data": announcement})
}
// Delete 删除公告
func (h *AnnouncementHandler) Delete(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
return
}
if err := utils.DB.Delete(&models.Announcement{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}
// ToggleActive 切换激活状态
func (h *AnnouncementHandler) ToggleActive(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的ID"})
return
}
var announcement models.Announcement
if err := utils.DB.First(&announcement, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "公告不存在"})
return
}
announcement.IsActive = !announcement.IsActive
utils.DB.Save(&announcement)
c.JSON(http.StatusOK, gin.H{"data": announcement})
}
+11
View File
@@ -28,6 +28,7 @@ func SetupRoutes(r *gin.Engine) {
logHandler := handlers.NewLogHandler()
purchaseGroupHandler := handlers.NewPurchaseGroupHandler()
shippingTemplateHandler := handlers.NewShippingTemplateHandler()
announcementHandler := handlers.NewAnnouncementHandler()
r.Use(middlewares.CORSMiddleware())
@@ -62,6 +63,7 @@ func SetupRoutes(r *gin.Engine) {
}
api.GET("/banners", bannerHandler.List)
api.GET("/announcements", announcementHandler.List)
api.GET("/categories", categoryHandler.List)
api.GET("/brands", brandHandler.List)
@@ -262,6 +264,15 @@ func SetupRoutes(r *gin.Engine) {
adminShippingTemplates.PUT("/:id", shippingTemplateHandler.Update)
adminShippingTemplates.DELETE("/:id", shippingTemplateHandler.Delete)
}
adminAnnouncements := admin.Group("/announcements")
{
adminAnnouncements.GET("", announcementHandler.AdminList)
adminAnnouncements.POST("", announcementHandler.Create)
adminAnnouncements.PUT("/:id", announcementHandler.Update)
adminAnnouncements.DELETE("/:id", announcementHandler.Delete)
adminAnnouncements.PUT("/:id/toggle", announcementHandler.ToggleActive)
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
package models
import (
"time"
"gorm.io/gorm"
)
type Announcement struct {
ID uint `gorm:"primaryKey" json:"id"`
Title string `gorm:"size:255;not null" json:"title"`
Content string `gorm:"type:text" json:"content"`
SortOrder int `gorm:"default:0" json:"sort_order"`
IsActive bool `gorm:"default:true" json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
func (Announcement) TableName() string {
return "announcements"
}
+1
View File
@@ -82,6 +82,7 @@ func AutoMigrate() {
&models.Article{},
&models.Banner{},
&models.PaymentChannel{},
&models.Announcement{},
)
if err != nil {
log.Fatalf("Failed to migrate database: %v", err)