feat: SSR 站点单页 /page/:slug 与 Admin CRUD
EOF Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
198
routers/web/admin_pages.go
Normal file
198
routers/web/admin_pages.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/modules/webctx"
|
||||
"git.iioio.com/freefire/jiang13-forum/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type adminPageRow struct {
|
||||
ID uint
|
||||
Title string
|
||||
Slug string
|
||||
Published bool
|
||||
SortOrder int
|
||||
ShowInNav bool
|
||||
ShowInFooter bool
|
||||
}
|
||||
|
||||
type adminPageForm struct {
|
||||
ID uint
|
||||
Title string
|
||||
Slug string
|
||||
Content string
|
||||
Published bool
|
||||
SortOrder int
|
||||
ShowInNav bool
|
||||
ShowInFooter bool
|
||||
IsEdit bool
|
||||
}
|
||||
|
||||
type adminPagesData struct {
|
||||
AdminChrome
|
||||
Pages []adminPageRow
|
||||
Form adminPageForm
|
||||
}
|
||||
|
||||
// AdminPagesGet 站点单页列表
|
||||
func (d Deps) AdminPagesGet(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
d.renderAdminPages(ctx, "", adminPageForm{})
|
||||
}
|
||||
|
||||
func (d Deps) renderAdminPages(ctx *webctx.Context, errMsg string, form adminPageForm) {
|
||||
chrome := d.adminChrome(ctx, "站点单页", "pages")
|
||||
chrome.Error = errMsg
|
||||
data := adminPagesData{AdminChrome: chrome, Form: form}
|
||||
if d.SitePage != nil {
|
||||
list, _ := d.SitePage.ListAll()
|
||||
data.Pages = make([]adminPageRow, 0, len(list))
|
||||
for _, p := range list {
|
||||
data.Pages = append(data.Pages, adminPageRow{
|
||||
ID: p.ID, Title: p.Title, Slug: p.Slug, Published: p.Published,
|
||||
SortOrder: p.SortOrder, ShowInNav: p.ShowInNav, ShowInFooter: p.ShowInFooter,
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "admin/pages", data)
|
||||
}
|
||||
|
||||
// AdminPageCreate 新建单页
|
||||
func (d Deps) AdminPageCreate(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
form := adminPageFormFrom(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminPages(ctx, "无效请求,请重试", form)
|
||||
return
|
||||
}
|
||||
if d.SitePage == nil {
|
||||
d.renderAdminPages(ctx, "单页服务未就绪", form)
|
||||
return
|
||||
}
|
||||
if _, err := d.SitePage.Create(adminPageInputFrom(form)); err != nil {
|
||||
d.renderAdminPages(ctx, err.Error(), form)
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("单页已创建")
|
||||
ctx.Redirect("/admin/pages")
|
||||
}
|
||||
|
||||
// AdminPageEditGet 编辑表单
|
||||
func (d Deps) AdminPageEditGet(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if d.SitePage == nil {
|
||||
d.renderAdminPages(ctx, "单页服务未就绪", adminPageForm{})
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
page, err := d.SitePage.GetByID(uint(id))
|
||||
if err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect("/admin/pages")
|
||||
return
|
||||
}
|
||||
d.renderAdminPages(ctx, "", adminPageForm{
|
||||
ID: page.ID, Title: page.Title, Slug: page.Slug, Content: page.Content,
|
||||
Published: page.Published, SortOrder: page.SortOrder,
|
||||
ShowInNav: page.ShowInNav, ShowInFooter: page.ShowInFooter, IsEdit: true,
|
||||
})
|
||||
}
|
||||
|
||||
// AdminPageUpdate 更新单页
|
||||
func (d Deps) AdminPageUpdate(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
form := adminPageFormFrom(c)
|
||||
form.ID = uint(id)
|
||||
form.IsEdit = true
|
||||
if !ctx.CheckCSRF() {
|
||||
d.renderAdminPages(ctx, "无效请求,请重试", form)
|
||||
return
|
||||
}
|
||||
if d.SitePage == nil {
|
||||
d.renderAdminPages(ctx, "单页服务未就绪", form)
|
||||
return
|
||||
}
|
||||
if err := d.SitePage.Update(uint(id), adminPageInputFrom(form)); err != nil {
|
||||
d.renderAdminPages(ctx, err.Error(), form)
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("单页已保存")
|
||||
ctx.Redirect("/admin/pages")
|
||||
}
|
||||
|
||||
// AdminPageDelete 删除
|
||||
func (d Deps) AdminPageDelete(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
ctx.SetFlash("无效请求,请重试")
|
||||
ctx.Redirect("/admin/pages")
|
||||
return
|
||||
}
|
||||
if d.SitePage == nil {
|
||||
ctx.SetFlash("单页服务未就绪")
|
||||
ctx.Redirect("/admin/pages")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err := d.SitePage.Delete(uint(id)); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect("/admin/pages")
|
||||
return
|
||||
}
|
||||
ctx.SetFlash("单页已删除")
|
||||
ctx.Redirect("/admin/pages")
|
||||
}
|
||||
|
||||
// AdminPagePublishPost 快捷发布/下架
|
||||
func (d Deps) AdminPagePublishPost(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if !ctx.CheckCSRF() {
|
||||
ctx.SetFlash("无效请求,请重试")
|
||||
ctx.Redirect("/admin/pages")
|
||||
return
|
||||
}
|
||||
if d.SitePage == nil {
|
||||
ctx.SetFlash("单页服务未就绪")
|
||||
ctx.Redirect("/admin/pages")
|
||||
return
|
||||
}
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
published := c.PostForm("published") == "1" || c.PostForm("published") == "on"
|
||||
if err := d.SitePage.SetPublished(uint(id), published); err != nil {
|
||||
ctx.SetFlash(err.Error())
|
||||
ctx.Redirect("/admin/pages")
|
||||
return
|
||||
}
|
||||
if published {
|
||||
ctx.SetFlash("已发布")
|
||||
} else {
|
||||
ctx.SetFlash("已下架")
|
||||
}
|
||||
ctx.Redirect("/admin/pages")
|
||||
}
|
||||
|
||||
func adminPageFormFrom(c *gin.Context) adminPageForm {
|
||||
sort, _ := strconv.Atoi(c.PostForm("sort_order"))
|
||||
return adminPageForm{
|
||||
Title: strings.TrimSpace(c.PostForm("title")),
|
||||
Slug: strings.TrimSpace(c.PostForm("slug")),
|
||||
Content: c.PostForm("content"),
|
||||
Published: c.PostForm("published") == "1" || c.PostForm("published") == "on",
|
||||
SortOrder: sort,
|
||||
ShowInNav: c.PostForm("show_in_nav") == "1" || c.PostForm("show_in_nav") == "on",
|
||||
ShowInFooter: c.PostForm("show_in_footer") == "1" || c.PostForm("show_in_footer") == "on",
|
||||
}
|
||||
}
|
||||
|
||||
func adminPageInputFrom(form adminPageForm) services.SitePageInput {
|
||||
return services.SitePageInput{
|
||||
Title: form.Title, Slug: form.Slug, Content: form.Content,
|
||||
Published: form.Published, SortOrder: form.SortOrder,
|
||||
ShowInNav: form.ShowInNav, ShowInFooter: form.ShowInFooter,
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,13 @@ type Deps struct {
|
||||
Points *services.PointsService
|
||||
FriendLink *services.FriendLinkApplyService
|
||||
Mail *services.MailService
|
||||
SitePage *services.SitePageService
|
||||
}
|
||||
|
||||
// SitePageLink 导航/页脚站点单页链接
|
||||
type SitePageLink struct {
|
||||
Title string
|
||||
Slug string
|
||||
}
|
||||
|
||||
// BoardView 侧栏
|
||||
@@ -110,6 +117,8 @@ type PageChrome struct {
|
||||
ViewerPoints int // 登录用户当前积分;未登录为 0
|
||||
ShowFriendLinksNav bool
|
||||
ShowFriendLinksFooter bool
|
||||
NavPages []SitePageLink
|
||||
FooterPages []SitePageLink
|
||||
RightAside RightAsideData
|
||||
}
|
||||
|
||||
@@ -149,6 +158,7 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
|
||||
if ctx.C != nil && ctx.C.Request != nil && ctx.C.Request.URL != nil {
|
||||
path = ctx.C.Request.URL.Path
|
||||
}
|
||||
navPages, footerPages := d.sitePageLinks()
|
||||
return PageChrome{
|
||||
Title: title,
|
||||
Description: desc,
|
||||
@@ -167,10 +177,33 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
|
||||
ViewerPoints: viewerPoints,
|
||||
ShowFriendLinksNav: d.Settings.NavShowFriendLinks(),
|
||||
ShowFriendLinksFooter: d.Settings.FooterShowFriendLinks(),
|
||||
NavPages: navPages,
|
||||
FooterPages: footerPages,
|
||||
RightAside: d.loadRightAside(ctx, brand),
|
||||
}
|
||||
}
|
||||
|
||||
func (d Deps) sitePageLinks() (nav, footer []SitePageLink) {
|
||||
nav, footer = []SitePageLink{}, []SitePageLink{}
|
||||
if d.SitePage == nil {
|
||||
return
|
||||
}
|
||||
list, err := d.SitePage.ListPublished()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, p := range list {
|
||||
link := SitePageLink{Title: p.Title, Slug: p.Slug}
|
||||
if p.ShowInNav {
|
||||
nav = append(nav, link)
|
||||
}
|
||||
if p.ShowInFooter {
|
||||
footer = append(footer, link)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
rightAsideHotLimit = 5
|
||||
rightAsideTagLimit = 24
|
||||
|
||||
@@ -66,6 +66,12 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
admin.POST("/friend-links/brand/delete", deps.AdminFriendLinksBrandDeletePost)
|
||||
admin.POST("/friend-links/applies/:id/approve", deps.AdminFriendLinkApprovePost)
|
||||
admin.POST("/friend-links/applies/:id/reject", deps.AdminFriendLinkRejectPost)
|
||||
admin.GET("/pages", deps.AdminPagesGet)
|
||||
admin.POST("/pages", deps.AdminPageCreate)
|
||||
admin.GET("/pages/:id/edit", deps.AdminPageEditGet)
|
||||
admin.POST("/pages/:id", deps.AdminPageUpdate)
|
||||
admin.POST("/pages/:id/delete", deps.AdminPageDelete)
|
||||
admin.POST("/pages/:id/publish", deps.AdminPagePublishPost)
|
||||
}
|
||||
|
||||
g.GET("/user/:id", deps.UserPublic)
|
||||
@@ -87,6 +93,7 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
g.POST("/links/logo", authMW.RequireAuth(), deps.LinksLogoUpload)
|
||||
g.GET("/projects", deps.PendingPage)
|
||||
g.GET("/boards", deps.BoardsGet)
|
||||
g.GET("/page/:slug", deps.SitePageGet)
|
||||
}
|
||||
|
||||
// HomePageData Feed
|
||||
|
||||
44
routers/web/page.go
Normal file
44
routers/web/page.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type sitePageViewData struct {
|
||||
PageChrome
|
||||
PageTitle string
|
||||
Slug string
|
||||
BodyHTML template.HTML
|
||||
}
|
||||
|
||||
// SitePageGet 公开站点单页
|
||||
func (d Deps) SitePageGet(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
if d.SitePage == nil {
|
||||
d.render404(ctx)
|
||||
return
|
||||
}
|
||||
slug := c.Param("slug")
|
||||
page, err := d.SitePage.GetBySlug(slug, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, services.ErrSitePageNotFound) {
|
||||
d.render404(ctx)
|
||||
return
|
||||
}
|
||||
c.String(http.StatusInternalServerError, "加载失败")
|
||||
return
|
||||
}
|
||||
html := services.SanitizePostHTML(page.Content)
|
||||
chrome := d.chrome(ctx, page.Title+" · "+d.Settings.SiteBranding().Name, "", "")
|
||||
ctx.HTML(http.StatusOK, "page/view", sitePageViewData{
|
||||
PageChrome: chrome,
|
||||
PageTitle: page.Title,
|
||||
Slug: page.Slug,
|
||||
BodyHTML: template.HTML(html),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user