更新website
This commit is contained in:
@@ -155,7 +155,9 @@ func (c *IndexWebsiteController) News() {
|
||||
cond := publishedArticleCond()
|
||||
if cateStr := strings.TrimSpace(c.GetString("category")); cateStr != "" {
|
||||
if cid, err := strconv.ParseUint(cateStr, 10, 64); err == nil && cid > 0 {
|
||||
cond = cond.And("cate_id", cid)
|
||||
// 选中父分类时把它整棵子树的文章一起返回,否则挂在子分类上的内容会查不到
|
||||
ids := append([]uint64{cid}, models.PlatformWebsiteCategoryDescendantMap()[cid]...)
|
||||
cond = cond.And("cate_id__in", ids)
|
||||
}
|
||||
}
|
||||
if kw := strings.TrimSpace(c.GetString("keyword")); kw != "" {
|
||||
@@ -221,20 +223,25 @@ func (c *IndexWebsiteController) NewsCategories() {
|
||||
if _, err := models.Orm.QueryTable(new(models.PlatformWebsiteCategory)).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("sort", "id").
|
||||
All(&cates, "ID", "Name", "Desc", "Image", "Sort"); err != nil {
|
||||
All(&cates, "ID", "Cid", "Name", "Desc", "Image", "Sort"); err != nil {
|
||||
c.fail(500, 500, "获取分类失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 父分类的数量包含其所有后代,与列表页按父分类筛选的口径保持一致
|
||||
descMap := models.PlatformWebsiteCategoryDescendantMap()
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(cates))
|
||||
for _, item := range cates {
|
||||
ids := append([]uint64{item.ID}, descMap[item.ID]...)
|
||||
cnt, _ := models.Orm.QueryTable(new(models.PlatformWebsiteArticle)).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status", articleStatusPublished).
|
||||
Filter("cate_id", item.ID).
|
||||
Filter("cate_id__in", ids).
|
||||
Count()
|
||||
list = append(list, map[string]interface{}{
|
||||
"id": item.ID,
|
||||
"cid": item.Cid,
|
||||
"name": item.Name,
|
||||
"desc": item.Desc,
|
||||
"image": item.Image,
|
||||
@@ -390,3 +397,89 @@ func (c *IndexWebsiteController) NewsView() {
|
||||
}
|
||||
c.ok(nil)
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// 站点公告 yz_platform_website_notice
|
||||
//
|
||||
// GET /site/notices 公告列表(?limit=6&popup=1 只取弹窗公告)
|
||||
// GET /site/notices/:id 公告详情(正文为 HTML,浏览量 +1)
|
||||
//
|
||||
// 仅返回「已发布」且当前时间处于有效期内的公告,条件见 noticeVisibleCond。
|
||||
// =============================================================
|
||||
|
||||
// noticeListItem 公告列表项(不含正文,减小响应体)
|
||||
func noticeListItem(r models.PlatformWebsiteNotice) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": r.ID,
|
||||
"title": r.Title,
|
||||
"summary": r.Summary,
|
||||
"type": r.Type,
|
||||
"link_url": r.LinkURL,
|
||||
"is_popup": r.IsPopup,
|
||||
"top": r.Top,
|
||||
"date": r.PublishTime,
|
||||
}
|
||||
}
|
||||
|
||||
// Notices GET /site/notices?limit=&popup=
|
||||
func (c *IndexWebsiteController) Notices() {
|
||||
limit, _ := c.GetInt("limit", 6)
|
||||
if limit < 1 {
|
||||
limit = 6
|
||||
}
|
||||
if limit > 50 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
cond := noticeVisibleCond()
|
||||
// popup 传 1 / true 时只取弹窗公告(首页弹窗、侧栏公告用)
|
||||
if pop := strings.TrimSpace(c.GetString("popup")); pop != "" && pop != "0" {
|
||||
cond = cond.And("is_popup", 1)
|
||||
}
|
||||
|
||||
var rows []models.PlatformWebsiteNotice
|
||||
if _, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).
|
||||
SetCond(cond).
|
||||
OrderBy("-top", "sort", "-publish_time", "-id").
|
||||
Limit(limit).
|
||||
All(&rows, "ID", "Title", "Summary", "Type", "LinkURL", "IsPopup", "Top",
|
||||
"Sort", "PublishTime", "CreateTime"); err != nil {
|
||||
c.fail(500, 500, "获取公告失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, noticeListItem(r))
|
||||
}
|
||||
c.ok(map[string]interface{}{"list": list, "total": len(list)})
|
||||
}
|
||||
|
||||
// NoticeDetail GET /site/notices/:id
|
||||
func (c *IndexWebsiteController) NoticeDetail() {
|
||||
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.fail(400, 400, "无效的公告ID")
|
||||
return
|
||||
}
|
||||
|
||||
var row models.PlatformWebsiteNotice
|
||||
if err = models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).
|
||||
SetCond(noticeVisibleCond()).
|
||||
Filter("id", id).
|
||||
One(&row); err != nil {
|
||||
c.fail(404, 404, "公告不存在或已下线")
|
||||
return
|
||||
}
|
||||
|
||||
// 浏览量 +1,失败不影响正文展示
|
||||
_, _ = models.Orm.Raw(
|
||||
"UPDATE yz_platform_website_notice SET views = views + 1 WHERE id = ?",
|
||||
id,
|
||||
).Exec()
|
||||
|
||||
item := noticeListItem(row)
|
||||
item["content"] = row.Content
|
||||
item["views"] = row.Views + 1
|
||||
c.ok(item)
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ func websiteID(c *beego.Controller) (uint64, bool) {
|
||||
return id, true
|
||||
}
|
||||
|
||||
// websiteStatusParam 解析 status 查询参数,未传时返回 -1(表示不过滤)
|
||||
func websiteStatusParam(c *beego.Controller) int {
|
||||
raw := strings.TrimSpace(c.GetString("status"))
|
||||
// websiteIntParam 解析整型查询参数,未传或非法时返回 -1(表示不过滤)
|
||||
func websiteIntParam(c *beego.Controller, key string) int {
|
||||
raw := strings.TrimSpace(c.GetString(key))
|
||||
if raw == "" {
|
||||
return -1
|
||||
}
|
||||
@@ -75,6 +75,11 @@ func websiteStatusParam(c *beego.Controller) int {
|
||||
return v
|
||||
}
|
||||
|
||||
// websiteStatusParam 解析 status 查询参数,未传时返回 -1(表示不过滤)
|
||||
func websiteStatusParam(c *beego.Controller) int {
|
||||
return websiteIntParam(c, "status")
|
||||
}
|
||||
|
||||
// websiteBind 解析 JSON 请求体到 target,失败时已写出 400
|
||||
func websiteBind(c *beego.Controller, target interface{}) bool {
|
||||
body, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
// =============================================================
|
||||
// 平台官网 - 站点公告 /platform/website/notice/*
|
||||
// 表:yz_platform_website_notice
|
||||
// 建表 SQL:docs/sql/create_platform_website_notice.sql
|
||||
//
|
||||
// 公告状态:0 草稿 / 1 已发布 / 2 已下线。
|
||||
// 前台 GET /site/notices 只返回「已发布」且当前时间处于有效期内的公告,
|
||||
// 见 index_website.go 的 noticeVisibleCond。
|
||||
// =============================================================
|
||||
|
||||
const (
|
||||
noticeStatusDraft int8 = 0
|
||||
noticeStatusPublished int8 = 1
|
||||
noticeStatusOffline int8 = 2
|
||||
)
|
||||
|
||||
type PlatformWebsiteNoticeController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type platformWebsiteNoticePayload struct {
|
||||
Title *string `json:"title"`
|
||||
Summary *string `json:"summary"`
|
||||
Content *string `json:"content"`
|
||||
Type *int8 `json:"type"`
|
||||
LinkURL *string `json:"link_url"`
|
||||
IsPopup *int8 `json:"is_popup"`
|
||||
Top *int8 `json:"top"`
|
||||
Sort *int `json:"sort"`
|
||||
Status *int8 `json:"status"`
|
||||
StartTime *string `json:"start_time"`
|
||||
EndTime *string `json:"end_time"`
|
||||
}
|
||||
|
||||
// parseNoticeTime 解析前端时间字符串,支持 "2006-01-02 15:04:05"、RFC3339、"2006-01-02";
|
||||
// 空串或无法识别时返回 nil(对应数据库 NULL,表示不限制时间)。
|
||||
func parseNoticeTime(raw *string) *time.Time {
|
||||
if raw == nil {
|
||||
return nil
|
||||
}
|
||||
v := strings.TrimSpace(*raw)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
for _, layout := range []string{"2006-01-02 15:04:05", time.RFC3339, "2006-01-02"} {
|
||||
if t, err := time.ParseInLocation(layout, v, time.Local); err == nil {
|
||||
return &t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// noticeVisibleCond 前台可见条件:未删除 + 已发布 + 当前时间落在 [start_time, end_time] 内
|
||||
// start_time / end_time 为 NULL 表示该侧不限制。
|
||||
func noticeVisibleCond() *orm.Condition {
|
||||
now := time.Now()
|
||||
return orm.NewCondition().
|
||||
And("delete_time__isnull", true).
|
||||
And("status", noticeStatusPublished).
|
||||
AndCond(orm.NewCondition().Or("start_time__isnull", true).Or("start_time__lte", now)).
|
||||
AndCond(orm.NewCondition().Or("end_time__isnull", true).Or("end_time__gte", now))
|
||||
}
|
||||
|
||||
// noticeFromPayload 依据请求体构造新公告,缺省:类型=通知公告、状态=草稿
|
||||
func noticeFromPayload(p platformWebsiteNoticePayload) models.PlatformWebsiteNotice {
|
||||
row := models.PlatformWebsiteNotice{
|
||||
Type: 1,
|
||||
Status: noticeStatusDraft,
|
||||
}
|
||||
if p.Title != nil {
|
||||
row.Title = strings.TrimSpace(*p.Title)
|
||||
}
|
||||
if p.Summary != nil {
|
||||
row.Summary = strings.TrimSpace(*p.Summary)
|
||||
}
|
||||
if p.Content != nil {
|
||||
row.Content = *p.Content
|
||||
}
|
||||
if p.Type != nil {
|
||||
row.Type = *p.Type
|
||||
}
|
||||
if p.LinkURL != nil {
|
||||
row.LinkURL = strings.TrimSpace(*p.LinkURL)
|
||||
}
|
||||
if p.IsPopup != nil {
|
||||
row.IsPopup = *p.IsPopup
|
||||
}
|
||||
if p.Top != nil {
|
||||
row.Top = *p.Top
|
||||
}
|
||||
if p.Sort != nil {
|
||||
row.Sort = *p.Sort
|
||||
}
|
||||
if p.Status != nil {
|
||||
row.Status = *p.Status
|
||||
}
|
||||
row.StartTime = parseNoticeTime(p.StartTime)
|
||||
row.EndTime = parseNoticeTime(p.EndTime)
|
||||
return row
|
||||
}
|
||||
|
||||
// noticeUpdateMap 把请求体转成待更新字段,只包含显式传入的键(未传的字段保持原值)
|
||||
func noticeUpdateMap(p platformWebsiteNoticePayload) map[string]interface{} {
|
||||
up := map[string]interface{}{}
|
||||
if p.Title != nil {
|
||||
up["title"] = strings.TrimSpace(*p.Title)
|
||||
}
|
||||
if p.Summary != nil {
|
||||
up["summary"] = strings.TrimSpace(*p.Summary)
|
||||
}
|
||||
if p.Content != nil {
|
||||
up["content"] = *p.Content
|
||||
}
|
||||
if p.Type != nil {
|
||||
up["type"] = *p.Type
|
||||
}
|
||||
if p.LinkURL != nil {
|
||||
up["link_url"] = strings.TrimSpace(*p.LinkURL)
|
||||
}
|
||||
if p.IsPopup != nil {
|
||||
up["is_popup"] = *p.IsPopup
|
||||
}
|
||||
if p.Top != nil {
|
||||
up["top"] = *p.Top
|
||||
}
|
||||
if p.Sort != nil {
|
||||
up["sort"] = *p.Sort
|
||||
}
|
||||
if p.Status != nil {
|
||||
up["status"] = *p.Status
|
||||
}
|
||||
if p.StartTime != nil {
|
||||
up["start_time"] = parseNoticeTime(p.StartTime)
|
||||
}
|
||||
if p.EndTime != nil {
|
||||
up["end_time"] = parseNoticeTime(p.EndTime)
|
||||
}
|
||||
return up
|
||||
}
|
||||
|
||||
// List GET /platform/website/notice/list
|
||||
// query: keyword(标题/摘要)、type、status、is_popup、page、pageSize
|
||||
func (c *PlatformWebsiteNoticeController) List() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
page, pageSize := websitePaging(&c.Controller)
|
||||
|
||||
cond := orm.NewCondition().And("delete_time__isnull", true)
|
||||
if kw := strings.TrimSpace(c.GetString("keyword")); kw != "" {
|
||||
cond = cond.AndCond(orm.NewCondition().
|
||||
Or("title__icontains", kw).
|
||||
Or("summary__icontains", kw))
|
||||
}
|
||||
if tp := websiteIntParam(&c.Controller, "type"); tp >= 0 {
|
||||
cond = cond.And("type", tp)
|
||||
}
|
||||
if st := websiteStatusParam(&c.Controller); st >= 0 {
|
||||
cond = cond.And("status", st)
|
||||
}
|
||||
if pop := websiteIntParam(&c.Controller, "is_popup"); pop >= 0 {
|
||||
cond = cond.And("is_popup", pop)
|
||||
}
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).SetCond(cond)
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
// 列表不返回富文本正文,减小响应体
|
||||
var rows []models.PlatformWebsiteNotice
|
||||
if _, err = qs.OrderBy("-top", "sort", "-id").
|
||||
Limit(pageSize, (page-1)*pageSize).
|
||||
All(&rows, "ID", "Title", "Summary", "Type", "LinkURL", "IsPopup", "Top",
|
||||
"Sort", "Status", "StartTime", "EndTime", "Views",
|
||||
"PublishTime", "CreateTime", "UpdateTime"); err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
websiteOK(&c.Controller, map[string]interface{}{"list": rows, "total": total})
|
||||
}
|
||||
|
||||
// Get GET /platform/website/notice/:id — 含正文,供编辑回显
|
||||
func (c *PlatformWebsiteNoticeController) Get() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
id, ok := websiteID(&c.Controller)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var row models.PlatformWebsiteNotice
|
||||
if err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
jsonErr(&c.Controller, 404, 404, "公告不存在")
|
||||
return
|
||||
}
|
||||
websiteOK(&c.Controller, row)
|
||||
}
|
||||
|
||||
// Create POST /platform/website/notice
|
||||
func (c *PlatformWebsiteNoticeController) Create() {
|
||||
claims, ok := websiteAuth(&c.Controller)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var p platformWebsiteNoticePayload
|
||||
if !websiteBind(&c.Controller, &p) {
|
||||
return
|
||||
}
|
||||
if p.Title == nil || strings.TrimSpace(*p.Title) == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "公告标题不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
row := noticeFromPayload(p)
|
||||
// 直接提交为已发布时同步补发布时间与发布人
|
||||
if row.Status == noticeStatusPublished {
|
||||
now := time.Now()
|
||||
uid := uint64(claims.UserID)
|
||||
row.PublishTime = &now
|
||||
row.PublisherID = &uid
|
||||
}
|
||||
|
||||
id, err := models.Orm.Insert(&row)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
websiteOK(&c.Controller, map[string]interface{}{"id": id})
|
||||
}
|
||||
|
||||
// Update POST /platform/website/notice/:id
|
||||
func (c *PlatformWebsiteNoticeController) Update() {
|
||||
claims, ok := websiteAuth(&c.Controller)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
id, ok := websiteID(&c.Controller)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var p platformWebsiteNoticePayload
|
||||
if !websiteBind(&c.Controller, &p) {
|
||||
return
|
||||
}
|
||||
if p.Title != nil && strings.TrimSpace(*p.Title) == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "公告标题不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 先取原记录:既用于 404 判断,也用于判断是否「首次转为已发布」
|
||||
var row models.PlatformWebsiteNotice
|
||||
if err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row, "ID", "Status"); err != nil {
|
||||
jsonErr(&c.Controller, 404, 404, "公告不存在")
|
||||
return
|
||||
}
|
||||
|
||||
up := noticeUpdateMap(p)
|
||||
if len(up) == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "无更新字段")
|
||||
return
|
||||
}
|
||||
if p.Status != nil && *p.Status == noticeStatusPublished && row.Status != noticeStatusPublished {
|
||||
// 首次发布才写发布时间,避免每次保存覆盖发布时间
|
||||
now := time.Now()
|
||||
uid := uint64(claims.UserID)
|
||||
up["publish_time"] = now
|
||||
up["publisher_id"] = uid
|
||||
}
|
||||
up["update_time"] = time.Now()
|
||||
|
||||
if _, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(up); err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
websiteOK(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// noticeToggle 更新单个开关字段(status / top / is_popup)
|
||||
func (c *PlatformWebsiteNoticeController) noticeToggle(claims *jwtutil.Claims, field string, value int8) {
|
||||
id, ok := websiteID(&c.Controller)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
up := map[string]interface{}{field: value, "update_time": time.Now()}
|
||||
// 发布时补发布时间与发布人(其余开关无需登录身份,claims 可能为 nil)
|
||||
if field == "status" && value == noticeStatusPublished {
|
||||
up["publish_time"] = time.Now()
|
||||
if claims != nil {
|
||||
up["publisher_id"] = uint64(claims.UserID)
|
||||
}
|
||||
}
|
||||
n, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(up)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "操作失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
jsonErr(&c.Controller, 404, 404, "公告不存在")
|
||||
return
|
||||
}
|
||||
websiteOK(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// Publish POST /platform/website/notice/:id/publish — 状态置为已发布
|
||||
func (c *PlatformWebsiteNoticeController) Publish() {
|
||||
claims, ok := websiteAuth(&c.Controller)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c.noticeToggle(claims, "status", noticeStatusPublished)
|
||||
}
|
||||
|
||||
// UnPublish POST /platform/website/notice/:id/unpublish — 状态置为已下线
|
||||
func (c *PlatformWebsiteNoticeController) UnPublish() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
c.noticeToggle(nil, "status", noticeStatusOffline)
|
||||
}
|
||||
|
||||
// Top POST /platform/website/notice/:id/top
|
||||
func (c *PlatformWebsiteNoticeController) Top() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
c.noticeToggle(nil, "top", 1)
|
||||
}
|
||||
|
||||
// UnTop POST /platform/website/notice/:id/untop
|
||||
func (c *PlatformWebsiteNoticeController) UnTop() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
c.noticeToggle(nil, "top", 0)
|
||||
}
|
||||
|
||||
// Popup POST /platform/website/notice/:id/popup — 标记为弹窗公告
|
||||
func (c *PlatformWebsiteNoticeController) Popup() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
c.noticeToggle(nil, "is_popup", 1)
|
||||
}
|
||||
|
||||
// UnPopup POST /platform/website/notice/:id/unpopup
|
||||
func (c *PlatformWebsiteNoticeController) UnPopup() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
c.noticeToggle(nil, "is_popup", 0)
|
||||
}
|
||||
|
||||
// Delete DELETE /platform/website/notice/:id — 软删除
|
||||
func (c *PlatformWebsiteNoticeController) Delete() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
id, ok := websiteID(&c.Controller)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
jsonErr(&c.Controller, 404, 404, "公告不存在")
|
||||
return
|
||||
}
|
||||
websiteOK(&c.Controller, nil)
|
||||
}
|
||||
|
||||
// BatchDelete POST /platform/website/notice/batchdelete — body { ids: [] }
|
||||
func (c *PlatformWebsiteNoticeController) BatchDelete() {
|
||||
if _, ok := websiteAuth(&c.Controller); !ok {
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
IDs []uint64 `json:"ids"`
|
||||
}
|
||||
if !websiteBind(&c.Controller, &p) || len(p.IDs) == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "请选择要删除的公告")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
if _, err := models.Orm.QueryTable(new(models.PlatformWebsiteNotice)).
|
||||
Filter("id__in", p.IDs).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now, "update_time": now}); err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
websiteOK(&c.Controller, nil)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
-- =============================================================
|
||||
-- 平台官网 - 站点公告
|
||||
-- 表:yz_platform_website_notice
|
||||
--
|
||||
-- 用途:平台端「官网管理 → 内容管理 → 站点公告」维护官网的通知 / 活动 /
|
||||
-- 系统维护类公告;前台经 GET /site/notices 读取(仅返回「已发布」
|
||||
-- 且处于有效期内的公告)。
|
||||
--
|
||||
-- 执行方式:人工在数据库执行(代码中不做任何自动建表 / 补列)。
|
||||
-- 说明:下方 DDL 幂等,可重复执行。
|
||||
-- =============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `yz_platform_website_notice` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`title` varchar(255) NOT NULL DEFAULT '' COMMENT '公告标题',
|
||||
`summary` varchar(500) NOT NULL DEFAULT '' COMMENT '公告摘要(前台卡片展示,留空则前台截取正文)',
|
||||
`content` mediumtext COMMENT '公告正文(富文本 HTML)',
|
||||
`type` tinyint NOT NULL DEFAULT 1 COMMENT '公告类型:1 通知公告 2 活动公告 3 系统维护',
|
||||
`link_url` varchar(500) NOT NULL DEFAULT '' COMMENT '点击跳转地址,站内相对路径或完整 URL;留空表示不可点击',
|
||||
`is_popup` tinyint NOT NULL DEFAULT 0 COMMENT '是否作为弹窗/侧栏公告:0 否 1 是',
|
||||
`top` tinyint NOT NULL DEFAULT 0 COMMENT '是否置顶:0 否 1 是',
|
||||
`sort` int NOT NULL DEFAULT 0 COMMENT '排序,数字越小越靠前',
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0 草稿 1 已发布 2 已下线',
|
||||
`start_time` datetime DEFAULT NULL COMMENT '生效开始时间,NULL 表示立即生效',
|
||||
`end_time` datetime DEFAULT NULL COMMENT '生效结束时间,NULL 表示永久有效',
|
||||
`views` int NOT NULL DEFAULT 0 COMMENT '浏览量',
|
||||
`publisher_id` bigint UNSIGNED DEFAULT NULL COMMENT '发布人(平台用户ID)',
|
||||
`publish_time` datetime DEFAULT NULL COMMENT '发布时间',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_status_sort` (`status`, `sort`, `id`),
|
||||
KEY `idx_popup` (`is_popup`, `status`),
|
||||
KEY `idx_publish_time` (`publish_time`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '平台官网-站点公告';
|
||||
|
||||
|
||||
-- =============================================================
|
||||
-- 平台菜单:把「站点公告」挂到「官网管理 → 内容管理」下
|
||||
--
|
||||
-- 菜单数据统一维护在 yz_system_menu(平台端 cid=1 的角色可见)。
|
||||
-- 父级不用写死自增ID,按现有「内容管理」的子菜单反查,可在任意环境重复执行。
|
||||
-- 若本次不需要自动挂菜单,可只执行上面的建表语句,手动在
|
||||
-- 「系统管理 → 菜单管理」里添加:路径 /website/content/notice,
|
||||
-- 组件 /website/content/notice/index.vue
|
||||
-- =============================================================
|
||||
|
||||
-- 父级定位一:取现有「内容管理」子菜单(文章管理)的 pid
|
||||
SET @notice_pid := (
|
||||
SELECT `pid` FROM `yz_system_menu`
|
||||
WHERE `delete_time` IS NULL AND `component_path` LIKE '/website/content/%'
|
||||
ORDER BY `id` ASC LIMIT 1
|
||||
);
|
||||
|
||||
-- 父级定位二:兜底按「内容管理」的路径或名称查
|
||||
SET @notice_pid := IFNULL(@notice_pid, (
|
||||
SELECT `id` FROM `yz_system_menu`
|
||||
WHERE `delete_time` IS NULL
|
||||
AND (`path` = '/website/content' OR `title` = '内容管理')
|
||||
ORDER BY `id` ASC LIMIT 1
|
||||
));
|
||||
|
||||
-- 已存在同路径菜单时跳过,避免重复插入
|
||||
INSERT INTO `yz_system_menu`
|
||||
(`pid`, `title`, `path`, `component_path`, `icon`, `sort`, `status`, `is_visible`, `views`, `type`, `creater`, `remark`, `create_time`)
|
||||
SELECT
|
||||
@notice_pid, '站点公告', '/website/content/notice', '/website/content/notice/index.vue',
|
||||
'Bell', 50, 1, 1, '[1]', 2, 'sql', '官网管理 - 站点公告', NOW()
|
||||
FROM (SELECT 1) AS `t`
|
||||
WHERE @notice_pid IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM (
|
||||
SELECT `id` FROM `yz_system_menu`
|
||||
WHERE `delete_time` IS NULL AND `path` = '/website/content/notice'
|
||||
LIMIT 1
|
||||
) AS `exist`
|
||||
);
|
||||
|
||||
|
||||
-- =============================================================
|
||||
-- 可选:一条示例公告,便于前台联调(不需要可整段跳过)
|
||||
-- =============================================================
|
||||
INSERT INTO `yz_platform_website_notice`
|
||||
(`title`, `summary`, `content`, `type`, `is_popup`, `top`, `sort`, `status`, `publish_time`)
|
||||
VALUES
|
||||
('云泽科技官网全新改版上线', '本次改版聚焦信息架构与访问体验,欢迎反馈建议。',
|
||||
'<p>云泽科技官网全新改版上线,本次改版聚焦信息架构与访问体验。</p>', 1, 1, 1, 0, 1, NOW());
|
||||
@@ -129,6 +129,8 @@ func Init(_ string) {
|
||||
new(PlatformWebsiteNav),
|
||||
new(PlatformWebsiteLink),
|
||||
new(PlatformWebsitePage),
|
||||
// 站点公告(docs/sql/create_platform_website_notice.sql)
|
||||
new(PlatformWebsiteNotice),
|
||||
|
||||
new(SystemReminderList),
|
||||
|
||||
|
||||
@@ -160,6 +160,33 @@ func (m *PlatformWebsitePage) TableName() string {
|
||||
return "yz_platform_website_page"
|
||||
}
|
||||
|
||||
// PlatformWebsiteNotice 平台官网-站点公告 yz_platform_website_notice
|
||||
// 建表 SQL:docs/sql/create_platform_website_notice.sql
|
||||
type PlatformWebsiteNotice struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Title string `orm:"column(title);size(255);default('')" json:"title"`
|
||||
Summary string `orm:"column(summary);size(500);default('')" json:"summary"`
|
||||
Content string `orm:"column(content);type(mediumtext);null" json:"content"`
|
||||
Type int8 `orm:"column(type);default(1)" json:"type"` // 1 通知公告 2 活动公告 3 系统维护
|
||||
LinkURL string `orm:"column(link_url);size(500);default('')" json:"link_url"`
|
||||
IsPopup int8 `orm:"column(is_popup);default(0)" json:"is_popup"` // 是否作为弹窗公告
|
||||
Top int8 `orm:"column(top);default(0)" json:"top"` // 是否置顶
|
||||
Sort int `orm:"column(sort);default(0)" json:"sort"`
|
||||
Status int8 `orm:"column(status);default(0)" json:"status"` // 0 草稿 1 已发布 2 已下线
|
||||
StartTime *time.Time `orm:"column(start_time);type(datetime);null" json:"start_time"`
|
||||
EndTime *time.Time `orm:"column(end_time);type(datetime);null" json:"end_time"`
|
||||
Views int `orm:"column(views);default(0)" json:"views"`
|
||||
PublisherID *uint64 `orm:"column(publisher_id);null" json:"publisher_id"`
|
||||
PublishTime *time.Time `orm:"column(publish_time);type(datetime);null" json:"publish_time"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *PlatformWebsiteNotice) TableName() string {
|
||||
return "yz_platform_website_notice"
|
||||
}
|
||||
|
||||
// ===================== 公共查询helper =====================
|
||||
|
||||
// PlatformWebsiteCategoryNameMap 分类ID -> 分类名称
|
||||
@@ -199,6 +226,50 @@ func PlatformWebsiteCategoryMap(ids []uint64) map[uint64]PlatformWebsiteCategory
|
||||
return out
|
||||
}
|
||||
|
||||
// PlatformWebsiteCategoryDescendantMap 分类ID -> 全部后代分类ID(不含自身)
|
||||
//
|
||||
// 官网前台按父分类筛选 / 统计数量时,需要把整棵子树都算进来:
|
||||
// 文章挂在子分类上时,点父分类也应该能看到。
|
||||
// 分类数据量很小,每次整体构建一次即可;存在环时靠 seen 截断。
|
||||
func PlatformWebsiteCategoryDescendantMap() map[uint64][]uint64 {
|
||||
out := make(map[uint64][]uint64)
|
||||
|
||||
var rows []PlatformWebsiteCategory
|
||||
if _, err := Orm.QueryTable(new(PlatformWebsiteCategory)).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&rows, "ID", "Cid"); err != nil {
|
||||
return out
|
||||
}
|
||||
|
||||
children := make(map[uint64][]uint64, len(rows))
|
||||
ids := make([]uint64, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
ids = append(ids, r.ID)
|
||||
if r.Cid > 0 && r.Cid != r.ID {
|
||||
children[r.Cid] = append(children[r.Cid], r.ID)
|
||||
}
|
||||
}
|
||||
|
||||
var walk func(id uint64, seen map[uint64]bool) []uint64
|
||||
walk = func(id uint64, seen map[uint64]bool) []uint64 {
|
||||
res := make([]uint64, 0, len(children[id]))
|
||||
for _, child := range children[id] {
|
||||
if seen[child] {
|
||||
continue
|
||||
}
|
||||
seen[child] = true
|
||||
res = append(res, child)
|
||||
res = append(res, walk(child, seen)...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
out[id] = walk(id, map[uint64]bool{id: true})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// PlatformWebsiteArticleTitleMap 文章ID -> 标题(评论列表展示所属文章用)
|
||||
func PlatformWebsiteArticleTitleMap(ids []uint64) map[uint64]string {
|
||||
out := make(map[uint64]string)
|
||||
|
||||
@@ -28,6 +28,10 @@ func Register() {
|
||||
beego.Router("/site/news/:id/view", &controllers.IndexWebsiteController{}, "post:NewsView")
|
||||
beego.Router("/site/news/:id", &controllers.IndexWebsiteController{}, "get:NewsDetail")
|
||||
|
||||
// 官网前台站点公告(平台「官网管理 - 站点公告」配置,仅返回已发布且在有效期内的)
|
||||
beego.Router("/site/notices", &controllers.IndexWebsiteController{}, "get:Notices")
|
||||
beego.Router("/site/notices/:id", &controllers.IndexWebsiteController{}, "get:NoticeDetail")
|
||||
|
||||
beego.Router("/index/headmenu", &controllers.IndexPortalController{}, "get:GetHeadMenu")
|
||||
beego.Router("/index/footerdata", &controllers.IndexPortalController{}, "get:GetFooterData")
|
||||
beego.Router("/index/newscentertop4", &controllers.IndexPortalController{}, "get:GetNewsCenterTop4")
|
||||
|
||||
@@ -402,4 +402,17 @@ func Register() {
|
||||
beego.Router("/platform/website/page/all", &controllers.PlatformWebsitePageController{}, "get:All")
|
||||
beego.Router("/platform/website/page", &controllers.PlatformWebsitePageController{}, "post:Create")
|
||||
beego.Router("/platform/website/page/:id", &controllers.PlatformWebsitePageController{}, "get:Get;post:Update;delete:Delete")
|
||||
|
||||
// 站点公告 yz_platform_website_notice
|
||||
// 注意:静态段(list / batchdelete)必须在 :id 之前注册
|
||||
beego.Router("/platform/website/notice/list", &controllers.PlatformWebsiteNoticeController{}, "get:List")
|
||||
beego.Router("/platform/website/notice/batchdelete", &controllers.PlatformWebsiteNoticeController{}, "post:BatchDelete")
|
||||
beego.Router("/platform/website/notice", &controllers.PlatformWebsiteNoticeController{}, "post:Create")
|
||||
beego.Router("/platform/website/notice/:id/publish", &controllers.PlatformWebsiteNoticeController{}, "post:Publish")
|
||||
beego.Router("/platform/website/notice/:id/unpublish", &controllers.PlatformWebsiteNoticeController{}, "post:UnPublish")
|
||||
beego.Router("/platform/website/notice/:id/top", &controllers.PlatformWebsiteNoticeController{}, "post:Top")
|
||||
beego.Router("/platform/website/notice/:id/untop", &controllers.PlatformWebsiteNoticeController{}, "post:UnTop")
|
||||
beego.Router("/platform/website/notice/:id/popup", &controllers.PlatformWebsiteNoticeController{}, "post:Popup")
|
||||
beego.Router("/platform/website/notice/:id/unpopup", &controllers.PlatformWebsiteNoticeController{}, "post:UnPopup")
|
||||
beego.Router("/platform/website/notice/:id", &controllers.PlatformWebsiteNoticeController{}, "get:Get;post:Update;delete:Delete")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user