refactor: 优化前端路由数据加载逻辑与显示效果

This commit is contained in:
seahi committed 2026-08-07 12:53:43 +08:00
1 parent 1e2775fda2
commit c902e5858d
12 files changed
+275 -151

No files matched your search

+11 -9
View File
@@ -1,9 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ platform }) => {
const db = platform?.env?.DB;
let aliases = {
const aliases = {
post: '文章',
page: '页面',
category: '分类',
@@ -11,7 +13,7 @@ export const load: LayoutServerLoad = async ({ platform }) => {
series: '系列'
};
let site = {
const site = {
title: 'BlogFlare',
description: 'A high-performance blog powered by Cloudflare D1, R2, and KV.',
avatar_enabled: 'true',
@@ -20,10 +22,10 @@ export const load: LayoutServerLoad = async ({ platform }) => {
if (db) {
try {
const { results } = await db.prepare(
"SELECT key, value FROM settings WHERE key LIKE 'alias.%' OR key LIKE 'site.%'"
).all();
const { results } = await db
.prepare("SELECT key, value FROM settings WHERE key LIKE 'alias.%' OR key LIKE 'site.%'")
.all();
if (results && results.length > 0) {
results.forEach((row: any) => {
if (row.key.startsWith('alias.')) {
@@ -39,8 +41,8 @@ export const load: LayoutServerLoad = async ({ platform }) => {
}
});
}
} catch (e) {
console.error('Failed to load settings', e);
} catch (err: any) {
console.error('Failed to load settings', err);
}
}
+105 -57
View File
@@ -1,9 +1,10 @@
<script lang="ts">
import { resolve } from '$app/paths';
import '../app.css';
let { children } = $props();
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { page } from '$app/stores';
let scrolled = $state(false);
@@ -12,7 +13,7 @@
scrolled = window.scrollY > 50;
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
@@ -21,69 +22,116 @@
function scrollToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function toggleSingleCol() {
document.body.classList.toggle('single-col');
}
</script>
<svelte:head>
<title>{$page.data.site?.title || 'BlogFlare'} - {$page.data.site?.description || 'A Pure Cloudflare Blog'}</title>
<title
>{$page.data.site?.title || 'BlogFlare'} - {$page.data.site?.description ||
'A Pure Cloudflare Blog'}</title
>
</svelte:head>
{#if $page.url.pathname.startsWith('/admin')}
{@render children()}
{:else}
<div class="frontend-layout">
<header class="header {scrolled ? 'scrolled' : ''}">
<div class="container wide">
<a href="/" class="brand">{$page.data.site?.title || 'BlogFlare'}</a>
<nav>
<a href="/" class={$page.url.pathname === '/' ? 'active' : ''}>首页</a>
<a href="/archive" class={$page.url.pathname.startsWith('/posts') || $page.url.pathname.startsWith('/archive') ? 'active' : ''}>{$page.data.aliases?.post || '文章'}</a>
<a href="/series" class={$page.url.pathname.startsWith('/series') ? 'active' : ''}>{$page.data.aliases?.series || '系列'}</a>
<a href="/about" class={$page.url.pathname.startsWith('/about') ? 'active' : ''}>关于</a>
</nav>
</div>
</header>
{#if $page.url.pathname === '/'}
<div class="home-profile">
{#if $page.data.site?.avatar_enabled !== 'false'}
<div class="home-avatar">
<img src={$page.data.site?.avatar_url || "data:image/png;base64,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 truncated
</div>
<div class="frontend-layout">
<header class="header {scrolled ? 'scrolled' : ''}">
<div class="container wide">
<a href={resolve('/')} class="brand">{$page.data.site?.title || 'BlogFlare'}</a>
<nav>
<a href={resolve('/')} class={$page.url.pathname === '/' ? 'active' : ''}>首页</a>
<a
href={resolve('/archive')}
class={$page.url.pathname.startsWith('/posts') ||
$page.url.pathname.startsWith('/archive')
? 'active'
: ''}>{$page.data.aliases?.post || '文章'}</a
>
<a
href={resolve('/series')}
class={$page.url.pathname.startsWith('/series') ? 'active' : ''}
>{$page.data.aliases?.series || '系列'}</a
>
<a
href={resolve('/about')}
class={$page.url.pathname.startsWith('/about') ? 'active' : ''}>关于</a
>
</nav>
</div>
</header>
{#if $page.url.pathname === '/'}
<div class="home-profile">
{#if $page.data.site?.avatar_enabled !== 'false'}
<div class="home-avatar">
<img
src={$page.data.site?.avatar_url ||
'data:image/png;base64,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 truncated
alt="avatar"
/>
</div>
{/if}
<h1 class="home-title">{$page.data.site?.title || 'BlogFlare'}</h1>
<p class="home-subtitle">
{$page.data.site?.description ||
'A high-performance blog powered by Cloudflare D1, R2, and KV.'}
</p>
</div>
{/if}
<h1 class="home-title">{$page.data.site?.title || 'BlogFlare'}</h1>
<p class="home-subtitle">{$page.data.site?.description || 'A high-performance blog powered by Cloudflare D1, R2, and KV.'}</p>
<main id="pjax-container" style="padding-top: {$page.url.pathname === '/' ? '30px' : '58px'}">
<div class="wrapper container wide">
{@render children()}
</div>
</main>
<footer>
<div class="container wide">
<section>
<p>
© {new Date().getFullYear()}
<span class="brand">{$page.data.site?.title || 'BlogFlare'}</span>
</p>
<p>感谢陪伴:<span id="uptime"></span></p>
</section>
<section>
<p>
Powered by <a href="https://kit.svelte.dev/">SvelteKit</a> &
<a href="https://developers.cloudflare.com/">Cloudflare</a>
</p>
<p>
Theme inspired by <a href="https://github.com/AlanDecode/Typecho-Theme-VOID">VOID</a> &
<a href="https://github.com/HEIGE-PCloud/DoIt">DoIt</a>
</p>
</section>
</div>
</footer>
<!-- Floating Controllers (VOID style) -->
<aside id="ctrler-panel">
<div class="ctrler-item" title="返回顶部">
<a
href="#top"
aria-label="返回顶部"
onclick={(e) => {
e.preventDefault();
scrollToTop();
}}
>
<svg
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
width="16"
height="16"
><path
fill="currentColor"
d="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"
></path></svg
>
</a>
</div>
</aside>
</div>
{/if}
<main id="pjax-container" style="padding-top: {$page.url.pathname === '/' ? '30px' : '58px'}">
<div class="wrapper container wide">
{@render children()}
</div>
</main>
<footer>
<div class="container wide">
<section>
<p>© {new Date().getFullYear()} <span class="brand">{$page.data.site?.title || 'BlogFlare'}</span></p>
<p>感谢陪伴:<span id="uptime"></span></p>
</section>
<section>
<p>Powered by <a href="https://kit.svelte.dev/">SvelteKit</a> & <a href="https://developers.cloudflare.com/">Cloudflare</a></p>
<p>Theme inspired by <a href="https://github.com/AlanDecode/Typecho-Theme-VOID">VOID</a> & <a href="https://github.com/HEIGE-PCloud/DoIt">DoIt</a></p>
</section>
</div>
</footer>
<!-- Floating Controllers (VOID style) -->
<aside id="ctrler-panel">
<div class="ctrler-item" title="返回顶部">
<a href="#top" aria-label="返回顶部" onclick={(e) => { e.preventDefault(); scrollToTop(); }}>
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16"><path fill="currentColor" d="M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z"></path></svg>
</a>
</div>
</aside>
</div>
{/if}
+31 -21
View File
@@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ platform }) => {
const db = platform?.env?.DB;
if (!db) {
// Fallback data if DB is not available (e.g. during dev without wrangler, or missing bindings)
return {
@@ -10,60 +12,68 @@ export const load: PageServerLoad = async ({ platform }) => {
{
id: 1,
title: 'Hello, BlogFlare! (Fallback)',
excerpt: 'This is my first post on BlogFlare. Welcome to my new blog powered by SvelteKit.',
excerpt:
'This is my first post on BlogFlare. Welcome to my new blog powered by SvelteKit.',
date: 'Jul 12, 2026',
slug: 'hello-world'
slug: 'hello-world',
cover: null
},
{
id: 2,
title: 'The Beauty of Typecho Theme VOID',
excerpt: 'VOID is a minimalist, typography-oriented theme that brings elegance to your reading experience.',
excerpt:
'VOID is a minimalist, typography-oriented theme that brings elegance to your reading experience.',
date: 'Jul 11, 2026',
slug: 'beauty-of-void'
slug: 'beauty-of-void',
cover: null
}
]
};
}
try {
const { results } = await db.prepare(
`SELECT p.id, p.slug, p.title, p.content, p.cover, p.published_at as date
const { results } = await db
.prepare(
`SELECT p.id, p.slug, p.title, p.content, p.cover, p.published_at as date
FROM posts p
ORDER BY p.published_at DESC LIMIT 10`
).all();
)
.all();
// Convert date to string for serialization
const formattedPosts = results.map(post => {
const formattedPosts = results.map((post) => {
const d = new Date(post.date as string);
const month = d.toLocaleString('en-US', { month: 'short' });
const day = d.getDate();
const year = d.getFullYear();
let excerpt = '';
let contentStr = post.content as string || '';
let excerpt;
let contentStr = (post.content as string) || '';
// Filter out Hugo shortcodes like {{< admonition abstract "标题" >}}
contentStr = contentStr.replace(/\{\{<[\s\S]*?>\}\}/g, '');
const parts = contentStr.split('<!--more-->');
if (parts.length > 1) {
excerpt = parts[0].trim();
} else {
// Strip basic markdown syntax for the automatic excerpt
let plainText = contentStr.replace(/[#*\`>~\\[\\]\\(\\)\\-]/g, '').trim();
const plainText = contentStr.replace(/[#*`>~[\]()-]/g, '').trim();
excerpt = plainText.substring(0, 150).trim();
}
// Append ... to indicate there is more to read
if (excerpt && !excerpt.endsWith('...')) {
excerpt += '...';
}
// Do not send full content to the list view to save bandwidth
const { content, ...restPost } = post as any;
return {
...restPost,
id: post.id as number,
title: post.title as string,
slug: post.slug as string,
cover: post.cover as string | null,
date: `${month} ${day}, ${year}`,
excerpt
};
@@ -72,7 +82,7 @@ export const load: PageServerLoad = async ({ platform }) => {
return {
posts: formattedPosts
};
} catch (err) {
} catch (err: any) {
console.error('Error fetching posts from D1:', err);
return { posts: [] };
}
+7 -3
View File
@@ -1,17 +1,21 @@
<script lang="ts">
import { resolve } from '$app/paths';
import type { PageData } from './$types';
import { page } from '$app/stores';
let { data }: { data: PageData } = $props();
</script>
<svelte:head>
<title>{$page.data.site?.title || 'BlogFlare'} - {$page.data.site?.description || 'A Pure Cloudflare Blog'}</title>
<title
>{$page.data.site?.title || 'BlogFlare'} - {$page.data.site?.description ||
'A Pure Cloudflare Blog'}</title
>
</svelte:head>
<section id="index-list">
<div class="post-list">
{#each data.posts as post}
<a href="/posts/{post.slug}" class="card-link">
{#each data.posts as post (post.id || post)}
<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">
+16 -5
View File
@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
@@ -10,11 +12,20 @@ export const load: PageServerLoad = async ({ params, platform }) => {
}
try {
const page = await db.prepare(
`SELECT title, slug, content, published_at, updated_at
const page = await db
.prepare(
`SELECT title, slug, content, published_at, updated_at
FROM pages
WHERE slug = ?`
).bind(slug).first<{ title: string; slug: string; content: string; published_at: string; updated_at: string }>();
)
.bind(slug)
.first<{
title: string;
slug: string;
content: string;
published_at: string;
updated_at: string;
}>();
if (!page) {
throw error(404, '该页面不存在');
@@ -23,8 +34,8 @@ export const load: PageServerLoad = async ({ params, platform }) => {
return {
page
};
} catch (err: any) {
if (err?.status === 404) throw err;
} catch (err: unknown) {
if ((err as any)?.status === 404) throw err;
console.error('Error loading page:', err);
throw error(500, '加载页面失败');
}
+2 -4
View File
@@ -2,7 +2,7 @@
import { page } from '$app/stores';
import type { PageData } from './$types';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
let { data }: { data: PageData } = $props();
</script>
@@ -16,10 +16,8 @@
<div class="banner-title">
<h1 class="post-title">{data.page.title}</h1>
</div>
<MarkdownRenderer content={data.page.content} />
</article>
</div>
</main>
+10 -6
View File
@@ -9,21 +9,25 @@ export const load: PageServerLoad = async ({ platform }) => {
try {
// Fetch all posts, ordered by published_at descending
const result = await db.prepare(`
const result = await db
.prepare(
`
SELECT slug, title, published_at
FROM posts
ORDER BY published_at DESC
`).all();
`
)
.all();
const posts = result.results as Array<{ slug: string; title: string; published_at: string }>;
// Group posts by year
const archives: Record<string, typeof posts> = {};
posts.forEach(post => {
posts.forEach((post) => {
const date = new Date(post.published_at);
const year = date.getFullYear().toString();
if (!archives[year]) {
archives[year] = [];
}
@@ -37,7 +41,7 @@ export const load: PageServerLoad = async ({ platform }) => {
archives,
years
};
} catch (err: any) {
} catch (err: unknown) {
console.error('Error fetching archives:', err);
throw error(500, 'Failed to fetch archives');
}
+8 -5
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import type { PageData } from './$types';
import { page } from '$app/stores';
import { resolve } from '$app/paths';
let { data }: { data: PageData } = $props();
@@ -23,14 +24,14 @@
</div>
<div class="archive-timeline">
{#each data.years as year}
{#each data.years as year (year)}
<div class="archive-year-group">
<h2 class="archive-year">{year}</h2>
<ul class="archive-list">
{#each data.archives[year] as post}
{#each data.archives[year] as post (post.slug)}
<li class="archive-item">
<time class="archive-date">{formatDate(post.published_at)}</time>
<a href="/posts/{post.slug}" class="archive-title">{post.title}</a>
<a href={resolve(`/posts/${post.slug}`)} class="archive-title">{post.title}</a>
</li>
{/each}
</ul>
@@ -70,7 +71,9 @@
align-items: baseline;
padding: 0.8rem 0;
border-bottom: 1px dashed rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, border-bottom-color 0.3s ease;
transition:
transform 0.3s ease,
border-bottom-color 0.3s ease;
}
.archive-item:hover {
@@ -106,7 +109,7 @@
.archive-item {
flex-direction: column;
}
.archive-date {
margin-bottom: 0.3rem;
font-size: 0.85rem;
+22 -10
View File
@@ -47,7 +47,10 @@ export const load: PageServerLoad = async ({ platform, params }) => {
post: {
id: 1,
slug,
title: slug === 'hello-world' ? 'Hello, BlogFlare! (Fallback)' : 'The Beauty of Typecho Theme VOID',
title:
slug === 'hello-world'
? 'Hello, BlogFlare! (Fallback)'
: 'The Beauty of Typecho Theme VOID',
content: dummyMarkdown,
cover: null,
date: '2026-07-12',
@@ -63,8 +66,9 @@ export const load: PageServerLoad = async ({ platform, params }) => {
}
try {
const post = await db.prepare(
`SELECT p.id, p.slug, p.title, p.content, p.cover, p.published_at as date,
const post = await db
.prepare(
`SELECT p.id, p.slug, p.title, p.content, p.cover, p.published_at as date,
c.name as category_name, c.slug as category_slug,
u.username as author,
s.id as series_id, s.name as series_name, s.slug as series_slug,
@@ -74,18 +78,23 @@ export const load: PageServerLoad = async ({ platform, params }) => {
LEFT JOIN users u ON p.author_id = u.id
LEFT JOIN series s ON p.series_id = s.id
WHERE p.slug = ?`
).bind(slug).first<PostRow>();
)
.bind(slug)
.first<PostRow>();
if (!post) {
error(404, 'Post not found');
}
const tagsQuery = await db.prepare(
`SELECT t.name, t.id
const tagsQuery = await db
.prepare(
`SELECT t.name, t.id
FROM tags t
JOIN post_tags pt ON t.id = pt.tag_id
WHERE pt.post_id = ?`
).bind(post.id).all<{ name: string; id: number }>();
)
.bind(post.id)
.all<{ name: string; id: number }>();
const tags = tagsQuery.results || [];
let series: {
id: number;
@@ -107,12 +116,15 @@ export const load: PageServerLoad = async ({ platform, params }) => {
slug: post.series_slug,
description: post.series_description
};
const seriesQuery = await db.prepare(
`SELECT id, slug, title, series_weight, published_at
const seriesQuery = await db
.prepare(
`SELECT id, slug, title, series_weight, published_at
FROM posts
WHERE series_id = ?
ORDER BY series_weight IS NULL, series_weight ASC, published_at ASC, id ASC`
).bind(post.series_id).all();
)
.bind(post.series_id)
.all();
seriesPosts = seriesQuery.results as unknown as typeof seriesPosts;
}
+60 -27
View File
@@ -5,17 +5,25 @@
import { onMount, tick } from 'svelte';
import tocbot from 'tocbot';
import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte';
let sidebarShow = $state(true);
let hasToc = $state(false);
let { data }: { data: PageData } = $props();
let currentSeriesIndex = $derived(data.seriesPosts.findIndex((post) => post.id === data.post.id));
let previousSeriesPost = $derived(currentSeriesIndex > 0 ? data.seriesPosts[currentSeriesIndex - 1] : null);
let nextSeriesPost = $derived(currentSeriesIndex >= 0 && currentSeriesIndex < data.seriesPosts.length - 1 ? data.seriesPosts[currentSeriesIndex + 1] : null);
let previousSeriesPost = $derived(
currentSeriesIndex > 0 ? data.seriesPosts[currentSeriesIndex - 1] : null
);
let nextSeriesPost = $derived(
currentSeriesIndex >= 0 && currentSeriesIndex < data.seriesPosts.length - 1
? data.seriesPosts[currentSeriesIndex + 1]
: null
);
onMount(() => {
const headers = document.querySelectorAll('.js-toc-content h1, .js-toc-content h2, .js-toc-content h3, .js-toc-content h4, .js-toc-content h5, .js-toc-content h6');
const headers = document.querySelectorAll(
'.js-toc-content h1, .js-toc-content h2, .js-toc-content h3, .js-toc-content h4, .js-toc-content h5, .js-toc-content h6'
);
if (headers.length > 0) {
hasToc = true;
tick().then(() => {
@@ -47,7 +55,7 @@
} else {
document.body.classList.remove('sidebar-show');
}
return () => {
document.body.classList.remove('sidebar-show');
};
@@ -57,7 +65,6 @@
if (hasToc) tocbot.destroy();
};
});
</script>
<svelte:head>
@@ -83,7 +90,9 @@
{#if data.series && data.seriesPosts.length > 0}
<section class="series-navigation" aria-labelledby="series-navigation-title">
<h2 id="series-navigation-title">
{$page.data.aliases?.series || '系列'}:<a href={resolve('/series/[slug]', { slug: data.series.slug })}>{data.series.name}</a>
{$page.data.aliases?.series || '系列'}:<a
href={resolve('/series/[slug]', { slug: data.series.slug })}>{data.series.name}</a
>
</h2>
<ol>
{#each data.seriesPosts as seriesPost (seriesPost.id)}
@@ -91,7 +100,9 @@
{#if seriesPost.id === data.post.id}
<span aria-current="page">{seriesPost.title}</span>
{:else}
<a href={resolve('/posts/[slug]', { slug: seriesPost.slug })}>{seriesPost.title}</a>
<a href={resolve('/posts/[slug]', { slug: seriesPost.slug })}
>{seriesPost.title}</a
>
{/if}
</li>
{/each}
@@ -99,22 +110,28 @@
{#if previousSeriesPost || nextSeriesPost}
<nav class="series-prev-next" aria-label="系列文章翻页">
{#if previousSeriesPost}
<a href={resolve('/posts/[slug]', { slug: previousSeriesPost.slug })}>← {previousSeriesPost.title}</a>
<a href={resolve('/posts/[slug]', { slug: previousSeriesPost.slug })}
>← {previousSeriesPost.title}</a
>
{:else}<span></span>{/if}
{#if nextSeriesPost}
<a href={resolve('/posts/[slug]', { slug: nextSeriesPost.slug })}>{nextSeriesPost.title} →</a>
<a href={resolve('/posts/[slug]', { slug: nextSeriesPost.slug })}
>{nextSeriesPost.title} →</a
>
{/if}
</nav>
{/if}
</section>
{/if}
<MarkdownRenderer content={data.post.content} />
{#if data.tags && data.tags.length > 0}
<section class="tags">
{#each data.tags as tag (tag.id)}
<a href={resolve(`/tags/${tag.name}`)} rel="tag" class="tag-item">{tag.name}</a>
<a href={resolve(`/tags/${encodeURIComponent(tag.name)}`)} rel="tag" class="tag-item"
>{tag.name}</a
>
{/each}
</section>
{/if}
@@ -122,19 +139,31 @@
</div>
{#if hasToc}
<!-- TOC Overlay and Sidebar -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="toc-mask" onclick={() => sidebarShow = false}></div>
<div class="TOC" aria-label="{$page.data.aliases?.post || '文章'}目录">
<div class="js-toc"></div>
</div>
<!-- TOC Overlay and Sidebar -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="toc-mask" onclick={() => (sidebarShow = false)}></div>
<div class="TOC" aria-label="{$page.data.aliases?.post || '文章'}目录">
<div class="js-toc"></div>
</div>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div id="toggle-toc" class="ctrler-item" onclick={() => sidebarShow = !sidebarShow}>
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16"><path fill="currentColor" d="M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L27 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 120.8c9.8 9.3 10 24.8.4 34.3z"></path></svg>
</div>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div id="toggle-toc" class="ctrler-item" onclick={() => (sidebarShow = !sidebarShow)}>
<svg
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
width="16"
height="16"
><path
fill="currentColor"
d="M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L27 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 120.8c9.8 9.3 10 24.8.4 34.3z"
></path></svg
>
</div>
{/if}
</main>
@@ -194,7 +223,11 @@
}
@media (max-width: 640px) {
.series-prev-next { grid-template-columns: 1fr; }
.series-prev-next a:last-child { text-align: left; }
.series-prev-next {
grid-template-columns: 1fr;
}
.series-prev-next a:last-child {
text-align: left;
}
}
</style>
+3 -2
View File
@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ platform }) => {
const db = platform?.env?.DB;
if (!db) throw error(500, '数据库未连接');
try {
const { results } = await db
.prepare(
@@ -24,7 +25,7 @@ export const load: PageServerLoad = async ({ platform }) => {
post_count: number;
}>
};
} catch (err) {
} catch (err: any) {
console.error('Error fetching public series list:', err);
throw error(500, '读取系列列表失败');
}
-2
View File
@@ -4,14 +4,12 @@ import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ platform, params }) => {
const db = platform?.env?.DB;
if (!db) throw error(500, '数据库未连接');
try {
const series = await db
.prepare('SELECT id, name, slug, description FROM series WHERE slug = ?')
.bind(params.slug)
.first<{ id: number; name: string; slug: string; description: string | null }>();
if (!series) throw error(404, '系列不存在');
const { results } = await db
.prepare(
`SELECT id, slug, title, series_weight, published_at