feat: 增加标签页面路由与内容列表查询支持

This commit is contained in:
seahi committed 2026-08-07 12:53:43 +08:00
1 parent e54ef0202d
commit 1e2775fda2
2 files changed
+200

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
import { error, isHttpError } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
const PAGE_SIZE = 20;
type TagPostRow = {
id: number;
slug: string;
title: string;
cover: string | null;
date: string;
excerpt_source: string;
};
function createExcerpt(source: string): string {
const plainText = source
.replace(/\{\{<[\s\S]*?>\}\}/g, '')
.replace(/<[^>]+>/g, '')
.replace(/[#*`>~[\]()-]/g, '')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 150)
.trim();
return plainText ? `${plainText}...` : '';
}
export const load: PageServerLoad = async ({ platform, params, url }) => {
const db = platform?.env.DB;
if (!db) throw error(500, '数据库还未成功相连');
try {
const tag = await db
.prepare('SELECT id, name FROM tags WHERE name = ? COLLATE NOCASE')
.bind(params.tag)
.first<{ id: number; name: string }>();
if (!tag) throw error(404, '尚未录入此标签或原归类已被卸除');
const countResult = await db
.prepare('SELECT COUNT(*) AS total FROM post_tags WHERE tag_id = ?')
.bind(tag.id)
.first<{ total: number }>();
const total = countResult?.total ?? 0;
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
const requestedPage = Number(url.searchParams.get('page') ?? '1');
const page = Number.isInteger(requestedPage)
? Math.min(Math.max(requestedPage, 1), totalPages)
: 1;
const { results } = await db
.prepare(
`SELECT
p.id,
p.slug,
p.title,
p.cover,
p.published_at AS date,
CASE
WHEN instr(p.content, '<!--more-->') > 0
THEN substr(p.content, 1, instr(p.content, '<!--more-->') - 1)
ELSE substr(p.content, 1, 500)
END AS excerpt_source
FROM posts p
INNER JOIN post_tags pt ON p.id = pt.post_id
WHERE pt.tag_id = ?
ORDER BY p.published_at DESC, p.id DESC
LIMIT ? OFFSET ?`
)
.bind(tag.id, PAGE_SIZE, (page - 1) * PAGE_SIZE)
.all<TagPostRow>();
const posts = results.map((post) => {
const date = new Date(post.date);
return {
id: post.id,
title: post.title,
slug: post.slug,
cover: post.cover,
date: Number.isNaN(date.getTime())
? post.date
: date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
timeZone: 'UTC'
}),
excerpt: createExcerpt(post.excerpt_source || '')
};
});
return {
tag,
posts,
pagination: { page, pageSize: PAGE_SIZE, total, totalPages }
};
} catch (cause) {
if (isHttpError(cause, 404)) throw cause;
console.error('Error loading tag posts:', cause);
throw error(500, '读取分类标签对应内容列表受阻中断');
}
};
+99
View File
@@ -0,0 +1,99 @@
<script lang="ts">
import { page } from '$app/stores';
import { resolve } from '$app/paths';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<svelte:head>
<title
>{$page.data.aliases?.tag || '标签'}:{data.tag.name} - {$page.data.site?.title ||
'BlogFlare'}</title
>
</svelte:head>
<main id="pjax-container">
<div class="wrapper container narrow contents-wrap">
<article class="article-detail yue" style="margin-bottom: 2rem;">
<div class="banner-title">
<h1 class="post-title">{$page.data.aliases?.tag || '标签'}:{data.tag.name}</h1>
<p class="post-meta">
共归纳检索到 {data.pagination.total} 篇带有此标牌的{$page.data.aliases?.post || '文章'}
</p>
</div>
</article>
</div>
<section id="index-list">
<div class="post-list">
{#each data.posts as post (post.id)}
<a href={resolve(`/posts/${post.slug}`)} class="card-link">
<article class="card article-content" class:has-cover={!!post.cover}>
{#if post.cover}
<div class="post-cover">
<img src={post.cover} alt={post.title} loading="lazy" />
</div>
{/if}
<div class="content-wrap">
<div class="post-meta-index">
<time>{post.date}</time>
</div>
<h1 class="title">{post.title}</h1>
<p class="excerpt">{post.excerpt}</p>
</div>
</article>
</a>
{:else}
<p class="empty-notice">现阶段尚未在此专属便览下留有正式启发的文章归类。</p>
{/each}
</div>
{#if data.pagination.totalPages > 1}
<nav class="pagination" aria-label="标签文章分页">
{#if data.pagination.page > 1}
<a
href={resolve(
`/tags/${encodeURIComponent(data.tag.name)}?page=${data.pagination.page - 1}`
)}
rel="prev">上一页</a
>
{:else}
<span aria-hidden="true">上一页</span>
{/if}
<strong>{data.pagination.page} / {data.pagination.totalPages}</strong>
{#if data.pagination.page < data.pagination.totalPages}
<a
href={resolve(
`/tags/${encodeURIComponent(data.tag.name)}?page=${data.pagination.page + 1}`
)}
rel="next">下一页</a
>
{:else}
<span aria-hidden="true">下一页</span>
{/if}
</nav>
{/if}
</section>
</main>
<style>
.empty-notice {
text-align: center;
color: var(--text-muted, rgba(0, 0, 0, 0.5));
padding: 3rem 1rem;
font-size: 1rem;
}
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 1.25rem;
padding: 1.5rem 1rem 3rem;
}
.pagination span {
opacity: 0.35;
}
</style>