feat: SSR 公开板块索引 /boards
EOF Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
|
||||
## C. 板块
|
||||
|
||||
- [x] 列出板块(含帖数等展示字段)— 前台侧栏 + Admin
|
||||
- [x] 列出板块(含帖数等展示字段)— SSR `/boards` + 侧栏 + Admin
|
||||
- [x] 管理员:创建 / 改 / 删板块 — SSR `/admin/boards`
|
||||
- [x] 板块名称、描述、图标、色板索引、排序
|
||||
- [x] 默认板块保障(空站可引导创建)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|------|------|-----|
|
||||
| `/` | Feed | 已迁 |
|
||||
| `/board/:id` | 板块 Feed | 已迁 |
|
||||
| `/boards` | 板块索引 | 已迁 |
|
||||
| `/post/:id` | 帖详情 + 评论(回复/赞/私密)/赞/藏 | 已迁 |
|
||||
| `/compose` | 发帖(normal;textarea + 图片 + 门控插入) | 已迁 |
|
||||
| `/post/:id/edit` | 编辑帖 | 已迁 |
|
||||
|
||||
@@ -88,6 +88,8 @@ web_src/ → public/assets/
|
||||
|
||||
私信:`/messages`、`/messages/with/:peerId`(系统 peer=0 只读;打开会话标已读)。
|
||||
|
||||
公开浏览:`/boards` 板块索引(链到 `/board/:id`)。
|
||||
|
||||
友链:`/links`(列表、登录申请/取消、Logo 上传);Admin `/admin/friend-links`(品牌增删、审核、入口开关)。
|
||||
|
||||
Admin:`/admin/dashboard`、`/admin/boards`、`/admin/moderation`、`/admin/settings`(品牌/限流/敏感词/SMTP)、`/admin/friend-links`。
|
||||
@@ -71,6 +71,7 @@ var parseGlobs = []string{
|
||||
"favorites/*.tmpl",
|
||||
"messages/*.tmpl",
|
||||
"links/*.tmpl",
|
||||
"boards/*.tmpl",
|
||||
}
|
||||
|
||||
// Load 解析全部模板(进程内一次)
|
||||
|
||||
@@ -610,3 +610,59 @@ points-only[data-locked="false"] {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.j13-boards { max-width: 800px; margin: 0 auto; padding: 1rem 1rem 2.5rem; }
|
||||
.j13-boards h1 { margin: 0 0 1rem; font-size: 1.4rem; }
|
||||
.j13-boards__grid {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 0.85rem;
|
||||
}
|
||||
.j13-boards__card {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-start;
|
||||
padding: 0.85rem 0.9rem;
|
||||
border: 1px solid var(--j13-border);
|
||||
border-radius: var(--j13-radius);
|
||||
background: var(--j13-bg);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
min-height: 5rem;
|
||||
}
|
||||
.j13-boards__card:hover { border-color: var(--j13-accent); }
|
||||
.j13-boards__dot {
|
||||
width: 0.65rem;
|
||||
height: 0.65rem;
|
||||
border-radius: 50%;
|
||||
margin-top: 0.35rem;
|
||||
flex-shrink: 0;
|
||||
background: var(--j13-accent);
|
||||
}
|
||||
.j13-boards__card[data-color="0"] .j13-boards__dot { background: #0f766e; }
|
||||
.j13-boards__card[data-color="1"] .j13-boards__dot { background: #b45309; }
|
||||
.j13-boards__card[data-color="2"] .j13-boards__dot { background: #1d4ed8; }
|
||||
.j13-boards__card[data-color="3"] .j13-boards__dot { background: #be123c; }
|
||||
.j13-boards__card[data-color="4"] .j13-boards__dot { background: #7c3aed; }
|
||||
.j13-boards__card[data-color="5"] .j13-boards__dot { background: #047857; }
|
||||
.j13-boards__card[data-color="6"] .j13-boards__dot { background: #c2410c; }
|
||||
.j13-boards__card[data-color="7"] .j13-boards__dot { background: #0369a1; }
|
||||
.j13-boards__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.j13-boards__body strong { font-size: 1rem; }
|
||||
.j13-boards__icon { font-size: 0.75rem; }
|
||||
.j13-boards__desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--j13-muted);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
53
routers/web/boards.go
Normal file
53
routers/web/boards.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/modules/webctx"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type boardsItemView struct {
|
||||
ID uint
|
||||
Name string
|
||||
Description string
|
||||
Icon string
|
||||
ColorIndex int
|
||||
PostCount int
|
||||
}
|
||||
|
||||
type boardsPageData struct {
|
||||
PageChrome
|
||||
Items []boardsItemView
|
||||
}
|
||||
|
||||
// BoardsGet 公开板块索引
|
||||
func (d Deps) BoardsGet(c *gin.Context) {
|
||||
ctx := d.ctx(c)
|
||||
d.renderBoards(ctx)
|
||||
}
|
||||
|
||||
func (d Deps) renderBoards(ctx *webctx.Context) {
|
||||
brand := d.Settings.SiteBranding()
|
||||
chrome := d.chrome(ctx, "板块 · "+brand.Name, "", "")
|
||||
list, _ := d.Board.ListWithStats()
|
||||
items := make([]boardsItemView, 0, len(list))
|
||||
for _, b := range list {
|
||||
color := b.ColorIndex
|
||||
if color < 0 {
|
||||
color = int(b.ID % 8)
|
||||
}
|
||||
items = append(items, boardsItemView{
|
||||
ID: b.ID,
|
||||
Name: b.Name,
|
||||
Description: b.Description,
|
||||
Icon: b.Icon,
|
||||
ColorIndex: color,
|
||||
PostCount: b.PostCount,
|
||||
})
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "boards/list", boardsPageData{
|
||||
PageChrome: chrome,
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
@@ -46,6 +46,7 @@ type PageChrome struct {
|
||||
ViewerName string
|
||||
Boards []BoardView
|
||||
ActiveBoard uint
|
||||
Path string // 当前请求路径,供侧栏高亮
|
||||
Inner string // 保留字段;入口模板已固定组合,不再动态 template
|
||||
CSRF string
|
||||
Flash string
|
||||
@@ -88,6 +89,10 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
|
||||
if ctx.IsSigned() && ctx.Doer != nil {
|
||||
viewerPoints = ctx.Doer.Points
|
||||
}
|
||||
path := ""
|
||||
if ctx.C != nil && ctx.C.Request != nil && ctx.C.Request.URL != nil {
|
||||
path = ctx.C.Request.URL.Path
|
||||
}
|
||||
return PageChrome{
|
||||
Title: title,
|
||||
Description: desc,
|
||||
@@ -98,6 +103,7 @@ func (d Deps) chrome(ctx *webctx.Context, title, desc, inner string) PageChrome
|
||||
IsAdmin: ctx.IsAdmin(),
|
||||
ViewerName: name,
|
||||
Boards: bv,
|
||||
Path: path,
|
||||
Inner: inner,
|
||||
CSRF: ctx.EnsureCSRF(),
|
||||
Flash: ctx.TakeFlash(),
|
||||
|
||||
@@ -83,7 +83,7 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
|
||||
g.POST("/links/apply/:id/cancel", authMW.RequireAuth(), deps.LinksApplyCancelPost)
|
||||
g.POST("/links/logo", authMW.RequireAuth(), deps.LinksLogoUpload)
|
||||
g.GET("/projects", deps.PendingPage)
|
||||
g.GET("/boards", deps.PendingPage)
|
||||
g.GET("/boards", deps.BoardsGet)
|
||||
}
|
||||
|
||||
// HomePageData Feed
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</span>
|
||||
</a>
|
||||
<nav class="j13-nav">
|
||||
<a href="/boards">板块</a>
|
||||
{{if .ShowFriendLinksNav}}<a href="/links">友链</a>{{end}}
|
||||
{{if .LoggedIn}}
|
||||
<a href="/compose">发帖</a>
|
||||
|
||||
28
templates/boards/list.tmpl
Normal file
28
templates/boards/list.tmpl
Normal file
@@ -0,0 +1,28 @@
|
||||
{{define "boards/list"}}
|
||||
{{template "base/head" .}}
|
||||
{{template "base/navbar" .}}
|
||||
<main class="j13-main j13-boards">
|
||||
<h1>板块</h1>
|
||||
{{template "base/alert" .}}
|
||||
{{if .Items}}
|
||||
<ul class="j13-boards__grid">
|
||||
{{range .Items}}
|
||||
<li>
|
||||
<a class="j13-boards__card" href="/board/{{.ID}}" data-color="{{.ColorIndex}}">
|
||||
<span class="j13-boards__dot" aria-hidden="true"></span>
|
||||
<span class="j13-boards__body">
|
||||
<strong>{{.Name}}</strong>
|
||||
{{if .Icon}}<span class="j13-muted j13-boards__icon">{{.Icon}}</span>{{end}}
|
||||
{{if .Description}}<span class="j13-boards__desc">{{.Description}}</span>{{end}}
|
||||
<span class="j13-muted">{{.PostCount}} 帖</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p class="j13-empty">暂无板块。{{if .IsAdmin}}请前往 <a href="/admin/boards">后台</a> 创建。{{end}}</p>
|
||||
{{end}}
|
||||
</main>
|
||||
{{template "base/footer" .}}
|
||||
{{end}}
|
||||
@@ -2,5 +2,5 @@ package templates
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *.tmpl base/*.tmpl home/*.tmpl post/*.tmpl shared/*.tmpl status/*.tmpl auth/*.tmpl admin/*.tmpl profile/*.tmpl user/*.tmpl favorites/*.tmpl messages/*.tmpl links/*.tmpl
|
||||
//go:embed *.tmpl base/*.tmpl home/*.tmpl post/*.tmpl shared/*.tmpl status/*.tmpl auth/*.tmpl admin/*.tmpl profile/*.tmpl user/*.tmpl favorites/*.tmpl messages/*.tmpl links/*.tmpl boards/*.tmpl
|
||||
var FS embed.FS
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{{define "shared/board_aside"}}
|
||||
<aside class="j13-aside j13-aside--left">
|
||||
<a class="j13-aside__link{{if eq .ActiveBoard 0}} is-active{{end}}" href="/">全部帖子</a>
|
||||
<a class="j13-aside__link{{if eq .Path "/boards"}} is-active{{end}}" href="/boards">全部板块</a>
|
||||
<a class="j13-aside__link{{if and (eq .ActiveBoard 0) (ne .Path "/boards")}} is-active{{end}}" href="/">全部帖子</a>
|
||||
{{range .Boards}}
|
||||
<a class="j13-aside__link{{if eq $.ActiveBoard .ID}} is-active{{end}}" href="/board/{{.ID}}">{{.Name}}</a>
|
||||
{{end}}
|
||||
|
||||
@@ -610,3 +610,59 @@ points-only[data-locked="false"] {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.j13-boards { max-width: 800px; margin: 0 auto; padding: 1rem 1rem 2.5rem; }
|
||||
.j13-boards h1 { margin: 0 0 1rem; font-size: 1.4rem; }
|
||||
.j13-boards__grid {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 0.85rem;
|
||||
}
|
||||
.j13-boards__card {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-start;
|
||||
padding: 0.85rem 0.9rem;
|
||||
border: 1px solid var(--j13-border);
|
||||
border-radius: var(--j13-radius);
|
||||
background: var(--j13-bg);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
min-height: 5rem;
|
||||
}
|
||||
.j13-boards__card:hover { border-color: var(--j13-accent); }
|
||||
.j13-boards__dot {
|
||||
width: 0.65rem;
|
||||
height: 0.65rem;
|
||||
border-radius: 50%;
|
||||
margin-top: 0.35rem;
|
||||
flex-shrink: 0;
|
||||
background: var(--j13-accent);
|
||||
}
|
||||
.j13-boards__card[data-color="0"] .j13-boards__dot { background: #0f766e; }
|
||||
.j13-boards__card[data-color="1"] .j13-boards__dot { background: #b45309; }
|
||||
.j13-boards__card[data-color="2"] .j13-boards__dot { background: #1d4ed8; }
|
||||
.j13-boards__card[data-color="3"] .j13-boards__dot { background: #be123c; }
|
||||
.j13-boards__card[data-color="4"] .j13-boards__dot { background: #7c3aed; }
|
||||
.j13-boards__card[data-color="5"] .j13-boards__dot { background: #047857; }
|
||||
.j13-boards__card[data-color="6"] .j13-boards__dot { background: #c2410c; }
|
||||
.j13-boards__card[data-color="7"] .j13-boards__dot { background: #0369a1; }
|
||||
.j13-boards__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.j13-boards__body strong { font-size: 1rem; }
|
||||
.j13-boards__icon { font-size: 0.75rem; }
|
||||
.j13-boards__desc {
|
||||
font-size: 0.85rem;
|
||||
color: var(--j13-muted);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user