feat: 增加标签页面路由与内容列表查询支持
This commit is contained in:
1 parent
e54ef0202d
commit
1e2775fda2
2 files changed
+200
No files matched your search
@@ -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, '读取分类标签对应内容列表受阻中断');
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user