diff --git a/internal/common/pagination.go b/internal/common/pagination.go index ae2ff22..f566d71 100644 --- a/internal/common/pagination.go +++ b/internal/common/pagination.go @@ -23,5 +23,31 @@ func (p *Pagination) Offset() int { return (p.Page - 1) * p.PageSize } +// TotalPages 根据 total 计算总页数 +func (p *Pagination) TotalPages(total int64) int { + if total == 0 { + return 0 + } + return int((total + int64(p.PageSize) - 1) / int64(p.PageSize)) +} + +// PrevPage 上一页(最小为 1) +func (p *Pagination) PrevPage() int { + prev := p.Page - 1 + if prev < 1 { + return 1 + } + return prev +} + +// NextPage 下一页(最大为 totalPages) +func (p *Pagination) NextPage(totalPages int) int { + next := p.Page + 1 + if next > totalPages { + return totalPages + } + return next +} + // 固定时间格式,前后端统一 const TimeFormat = time.RFC3339 diff --git a/internal/controller/admin/admin_post_controller.go b/internal/controller/admin/admin_post_controller.go index 919ac14..2b05ad4 100644 --- a/internal/controller/admin/admin_post_controller.go +++ b/internal/controller/admin/admin_post_controller.go @@ -43,11 +43,7 @@ func (ctrl *AdminPostController) PostsPage(c *gin.Context) { p := common.Pagination{Page: page, PageSize: pageSize} p.DefaultPagination() - totalPages := int((total + int64(p.PageSize) - 1) / int64(p.PageSize)) - prevPage := p.Page - 1 - if prevPage < 1 { prevPage = 1 } - nextPage := p.Page + 1 - if nextPage > totalPages { nextPage = totalPages } + totalPages := p.TotalPages(total) c.HTML(http.StatusOK, "admin/posts/index.html", common.BuildAdminPageData(c, gin.H{ "Title": "内容管理", @@ -55,8 +51,8 @@ func (ctrl *AdminPostController) PostsPage(c *gin.Context) { "Total": total, "Page": p.Page, "TotalPages": totalPages, - "PrevPage": prevPage, - "NextPage": nextPage, + "PrevPage": p.PrevPage(), + "NextPage": p.NextPage(totalPages), "Keyword": keyword, "Status": status, "StatusNames": model.PostStatusDisplayNames, diff --git a/internal/controller/post_controller.go b/internal/controller/post_controller.go index df14921..4b9d65c 100644 --- a/internal/controller/post_controller.go +++ b/internal/controller/post_controller.go @@ -49,11 +49,7 @@ func (ctrl *PostController) ListPage(c *gin.Context) { p := common.Pagination{Page: page, PageSize: pageSize} p.DefaultPagination() - totalPages := int((total + int64(p.PageSize) - 1) / int64(p.PageSize)) - prevPage := p.Page - 1 - if prevPage < 1 { prevPage = 1 } - nextPage := p.Page + 1 - if nextPage > totalPages { nextPage = totalPages } + totalPages := p.TotalPages(total) c.HTML(http.StatusOK, "posts/index.html", common.BuildPageData(c, gin.H{ "Title": "社区帖子", @@ -61,8 +57,8 @@ func (ctrl *PostController) ListPage(c *gin.Context) { "Total": total, "Page": p.Page, "TotalPages": totalPages, - "PrevPage": prevPage, - "NextPage": nextPage, + "PrevPage": p.PrevPage(), + "NextPage": p.NextPage(totalPages), "Keyword": keyword, "ExtraCSS": "/static/css/posts.css", })) @@ -276,12 +272,30 @@ func (ctrl *PostController) Delete(c *gin.Context) { // Submit API 提交审核 func (ctrl *PostController) Submit(c *gin.Context) { + uidObj, _ := c.Get("uid") + uid, ok := uidObj.(uint) + if !ok { + common.Error(c, http.StatusUnauthorized, "请先登录") + return + } + id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { common.Error(c, http.StatusBadRequest, "无效的帖子 ID") return } + // 权限检查:仅帖子作者可提交审核 + post, err := ctrl.postService.GetByID(uint(id)) + if err != nil { + common.Error(c, http.StatusNotFound, "帖子不存在") + return + } + if post.UserID != uid { + common.Error(c, http.StatusForbidden, "无权操作此帖子") + return + } + if err := ctrl.postService.SubmitForAudit(uint(id)); err != nil { if errors.Is(err, common.ErrPostCannotSubmit) || errors.Is(err, common.ErrPostNotFound) { common.Error(c, http.StatusBadRequest, err.Error()) @@ -312,13 +326,12 @@ func (ctrl *PostController) ListAPI(c *gin.Context) { p := common.Pagination{Page: page, PageSize: pageSize} p.DefaultPagination() - totalPages := int((total + int64(p.PageSize) - 1) / int64(p.PageSize)) common.Ok(c, model.PostListResult{ Items: posts, Total: total, Page: p.Page, - TotalPages: totalPages, + TotalPages: p.TotalPages(total), }) } diff --git a/templates/MetaLab-2026/static/js/editor.js b/templates/MetaLab-2026/static/js/editor.js index 971d0af..333bd75 100644 --- a/templates/MetaLab-2026/static/js/editor.js +++ b/templates/MetaLab-2026/static/js/editor.js @@ -17,6 +17,7 @@ import { common, createLowlight } from 'lowlight' let editor = null let currentHTML = '' let draftTimer = null +let submitting = false // ---- 初始化 ---- document.addEventListener('DOMContentLoaded', () => { @@ -337,25 +338,34 @@ function handleKeyboard(e) { async function handleSubmit(e) { e.preventDefault() - const postId = document.getElementById('postId')?.value - const title = document.getElementById('postTitle')?.value.trim() + if (submitting) return + submitting = true - if (!title) { alert('请输入标题'); return } - - // 获取编辑器 HTML 内容 - const body = editor ? editor.getHTML() : '' - // 忽略空段落 - const cleanBody = body === '

' ? '' : body - if (!cleanBody.trim()) { alert('请输入正文'); return } - - const isEdit = !!postId - const url = isEdit ? '/api/posts/' + postId : '/api/posts' - const method = isEdit ? 'PUT' : 'POST' - - const csrfMeta = document.querySelector('meta[name="csrf-token"]') - const csrf = csrfMeta ? csrfMeta.getAttribute('content') : '' + const submitBtn = document.querySelector('.btn-submit') + if (submitBtn) { + submitBtn.disabled = true + submitBtn.textContent = '提交中...' + } try { + const postId = document.getElementById('postId')?.value + const title = document.getElementById('postTitle')?.value.trim() + + if (!title) { alert('请输入标题'); return } + + // 获取编辑器 HTML 内容 + const body = editor ? editor.getHTML() : '' + // 忽略空段落 + const cleanBody = body === '

' ? '' : body + if (!cleanBody.trim()) { alert('请输入正文'); return } + + const isEdit = !!postId + const url = isEdit ? '/api/posts/' + postId : '/api/posts' + const method = isEdit ? 'PUT' : 'POST' + + const csrfMeta = document.querySelector('meta[name="csrf-token"]') + const csrf = csrfMeta ? csrfMeta.getAttribute('content') : '' + const resp = await fetch(url, { method, headers: { @@ -380,5 +390,12 @@ async function handleSubmit(e) { } else { alert('请求失败,请重试') } + } finally { + submitting = false + if (submitBtn) { + submitBtn.disabled = false + submitBtn.textContent = submitBtn.textContent === '提交中...' ? + (document.getElementById('postId')?.value ? '保存修改' : '发布帖子') : submitBtn.textContent + } } }