From 1e2775fda2183def9152148e251758e232df10de Mon Sep 17 00:00:00 2001 From: seaHi Date: Fri, 7 Aug 2026 12:53:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E8=B7=AF=E7=94=B1=E4=B8=8E=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=9F=A5=E8=AF=A2=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/tags/[tag]/+page.server.ts | 101 ++++++++++++++++++++++++++ src/routes/tags/[tag]/+page.svelte | 99 +++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 src/routes/tags/[tag]/+page.server.ts create mode 100644 src/routes/tags/[tag]/+page.svelte diff --git a/src/routes/tags/[tag]/+page.server.ts b/src/routes/tags/[tag]/+page.server.ts new file mode 100644 index 0000000..df86bfe --- /dev/null +++ b/src/routes/tags/[tag]/+page.server.ts @@ -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, '') > 0 + THEN substr(p.content, 1, instr(p.content, '') - 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(); + + 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, '读取分类标签对应内容列表受阻中断'); + } +}; diff --git a/src/routes/tags/[tag]/+page.svelte b/src/routes/tags/[tag]/+page.svelte new file mode 100644 index 0000000..c013142 --- /dev/null +++ b/src/routes/tags/[tag]/+page.svelte @@ -0,0 +1,99 @@ + + + + {$page.data.aliases?.tag || '标签'}:{data.tag.name} - {$page.data.site?.title || + 'BlogFlare'} + + +
+
+
+ +
+
+ +
+
+ {#each data.posts as post (post.id)} + +
+ {#if post.cover} +
+ {post.title} +
+ {/if} +
+ +

{post.title}

+

{post.excerpt}

+
+
+
+ {:else} +

现阶段尚未在此专属便览下留有正式启发的文章归类。

+ {/each} +
+ + {#if data.pagination.totalPages > 1} + + {/if} +
+
+ +