feat: 分类系统 + 标签系统 — Phase 4 完成
分类:Model/Repo/Service/Controller + 管理后台树形页面 + 首页导航栏
标签:Model/Repo/Service/Controller + /tags/{slug} 落地页 + 写文章页输入
Post 加 CategoryID,Create/Update 支持分类和标签
SQL 迁移含 categories/tags/post_tags 表及预设未分类
This commit is contained in:
@ -29,6 +29,16 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .CategoryNav}}
|
||||
<!-- Category Nav -->
|
||||
<div class="category-nav">
|
||||
<a href="/" class="category-tag active">全部</a>
|
||||
{{range .CategoryNav}}
|
||||
<a href="/posts?category={{.ID}}" class="category-tag">{{.Name}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container home-main">
|
||||
<div class="home-grid">
|
||||
|
||||
@ -64,6 +64,19 @@
|
||||
<span class="meta-label"></span>
|
||||
<label class="meta-checkbox"><input type="checkbox" id="reprintProhibited"> 未经作者许可,禁止转载</label>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">分类</span>
|
||||
<select id="categoryId" style="width:auto">
|
||||
<option value="">(未分类)</option>
|
||||
{{range .Categories}}
|
||||
<option value="{{.ID}}" {{if and $.Post $.Post.CategoryID}}{{if eq .ID (derefUint $.Post.CategoryID)}}selected{{end}}{{end}}>{{.Name}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">标签</span>
|
||||
<input type="text" id="tagInput" placeholder="逗号分隔,最多 10 个" style="width:300px">
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">声明</span>
|
||||
<select id="declaration" style="width:auto">
|
||||
|
||||
57
templates/MetaLab-2026/html/tags/detail.html
Normal file
57
templates/MetaLab-2026/html/tags/detail.html
Normal file
@ -0,0 +1,57 @@
|
||||
{{template "layout/header.html" .}}
|
||||
<body>
|
||||
|
||||
{{template "layout/nav.html" .}}
|
||||
|
||||
<div class="container home-main">
|
||||
<div class="home-grid">
|
||||
<main class="home-feed">
|
||||
<div class="feed-header">
|
||||
<h2 class="feed-title">标签:{{.Tag.Name}}</h2>
|
||||
<span class="feed-count">{{.Total}} 篇文章</span>
|
||||
</div>
|
||||
|
||||
{{if .Posts}}
|
||||
<div class="article-list">
|
||||
{{range .Posts}}
|
||||
<article class="article-card">
|
||||
<div class="article-body">
|
||||
<h3 class="article-title">
|
||||
<a href="/posts/{{.ID}}">{{.Title}}</a>
|
||||
</h3>
|
||||
<p class="article-excerpt">{{.Excerpt}}</p>
|
||||
<div class="article-meta">
|
||||
<span class="article-author">{{.AuthorName}}</span>
|
||||
<span class="meta-dot">·</span>
|
||||
<span class="article-time">{{.CreatedAt.Format "2006-01-02"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-feed">暂无相关文章</div>
|
||||
{{end}}
|
||||
|
||||
{{if gt .TotalPages 1}}
|
||||
<div class="pagination">
|
||||
{{if gt .Page 1}}<a href="?page={{.PrevPage}}" class="page-btn">上一页</a>{{end}}
|
||||
<span class="page-info">{{.Page}} / {{.TotalPages}}</span>
|
||||
{{if lt .Page .TotalPages}}<a href="?page={{.NextPage}}" class="page-btn">下一页</a>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</main>
|
||||
|
||||
<aside class="home-sidebar">
|
||||
<div class="sidebar-card links-card">
|
||||
<h3 class="links-title">快捷入口</h3>
|
||||
<a href="/" class="links-item">← 返回首页</a>
|
||||
<a href="/posts" class="links-item">浏览全部文章</a>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "layout/footer.html" .}}
|
||||
</body>
|
||||
</html>
|
||||
@ -423,6 +423,32 @@
|
||||
}
|
||||
.announcement-error .announcement-title { color: #dc2626; }
|
||||
|
||||
/* --- Category Nav --- */
|
||||
.category-nav {
|
||||
max-width: 900px;
|
||||
margin: 0 auto 1.5rem;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.category-tag {
|
||||
padding: 4px 14px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
background: #f3f4f6;
|
||||
text-decoration: none;
|
||||
transition: all .15s;
|
||||
}
|
||||
|
||||
.category-tag:hover,
|
||||
.category-tag.active {
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ---------- Responsive ---------- */
|
||||
@media (max-width: 960px) {
|
||||
.home-grid {
|
||||
|
||||
@ -190,6 +190,8 @@ async function handleSubmit(e) {
|
||||
const reprintSource = document.getElementById('reprintSource')?.value.trim() || ''
|
||||
const declaration = document.getElementById('declaration')?.value || ''
|
||||
const reprintProhibited = document.getElementById('reprintProhibited')?.checked || false
|
||||
const categoryId = document.getElementById('categoryId')?.value || ''
|
||||
const tags = document.getElementById('tagInput')?.value.trim() || ''
|
||||
|
||||
const isEdit = !!postId
|
||||
const url = isEdit ? '/api/studio/posts/' + postId : '/api/studio/posts'
|
||||
@ -207,6 +209,8 @@ async function handleSubmit(e) {
|
||||
reprint_source: reprintSource,
|
||||
declaration,
|
||||
reprint_prohibited: reprintProhibited,
|
||||
category_id: categoryId,
|
||||
tags,
|
||||
}),
|
||||
})
|
||||
const data = await resp.json()
|
||||
|
||||
Reference in New Issue
Block a user