From 59e036af0e158b6bd7d1e65777cc22e747e4fe34 Mon Sep 17 00:00:00 2001 From: seaHi Date: Wed, 29 Jul 2026 23:03:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E7=89=B9=E8=89=B2=E5=9B=BE=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=90=8E=E5=8F=B0=E5=9B=BE=E5=BA=93=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- schema.sql | 1 + src/app.css | 35 +++ .../admin/FeaturedImagePicker.svelte | 264 ++++++++++++++++++ src/lib/components/admin/Tabs.svelte | 85 ++++++ src/routes/+page.server.ts | 2 +- src/routes/+page.svelte | 7 +- src/routes/admin/api/images/+server.ts | 53 ++++ src/routes/admin/api/upload/+server.ts | 66 +++++ src/routes/admin/files/+page.svelte | 17 +- src/routes/admin/posts/[id]/+page.server.ts | 9 +- src/routes/admin/posts/[id]/+page.svelte | 11 +- src/routes/admin/posts/new/+page.server.ts | 7 +- src/routes/admin/posts/new/+page.svelte | 15 +- src/routes/admin/settings/+page.svelte | 44 +-- src/routes/posts/[slug]/+page.server.ts | 13 +- src/routes/posts/[slug]/+page.svelte | 5 + 16 files changed, 568 insertions(+), 66 deletions(-) create mode 100644 src/lib/components/admin/FeaturedImagePicker.svelte create mode 100644 src/lib/components/admin/Tabs.svelte create mode 100644 src/routes/admin/api/images/+server.ts create mode 100644 src/routes/admin/api/upload/+server.ts diff --git a/schema.sql b/schema.sql index 6e13c23..0755e31 100644 --- a/schema.sql +++ b/schema.sql @@ -29,6 +29,7 @@ CREATE TABLE posts ( category_id INTEGER, published_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + cover TEXT, FOREIGN KEY (author_id) REFERENCES users(id), FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL ); diff --git a/src/app.css b/src/app.css index b8424fd..8f37ef6 100644 --- a/src/app.css +++ b/src/app.css @@ -1189,3 +1189,38 @@ footer section p:first-child { content: "#"; margin-right: 2px; } + +/* Featured Image Styles */ +.post-cover { + width: 100%; + height: 300px; + overflow: hidden; + border-bottom: 1px dashed var(--line-color, rgba(0,0,0,0.1)); +} + +.post-cover img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: transform 0.5s ease; +} + +.card-link:hover .post-cover img { + transform: scale(1.03); +} + +.article-content.has-cover .content-wrap { + padding-top: 20px; +} + +.detail-cover { + height: 400px; + border-bottom: none; + border-radius: 8px; + margin-bottom: 30px; +} + +.detail-cover img { + border-radius: 8px; +} diff --git a/src/lib/components/admin/FeaturedImagePicker.svelte b/src/lib/components/admin/FeaturedImagePicker.svelte new file mode 100644 index 0000000..fd7563c --- /dev/null +++ b/src/lib/components/admin/FeaturedImagePicker.svelte @@ -0,0 +1,264 @@ + + + + + diff --git a/src/lib/components/admin/Tabs.svelte b/src/lib/components/admin/Tabs.svelte new file mode 100644 index 0000000..d2c9337 --- /dev/null +++ b/src/lib/components/admin/Tabs.svelte @@ -0,0 +1,85 @@ + + +
+ {#each tabs as tab} + + {/each} +
+ + diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index 6de9ad8..2b8b5b6 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -27,7 +27,7 @@ export const load: PageServerLoad = async ({ platform }) => { try { const { results } = await db.prepare( - `SELECT p.id, p.slug, p.title, p.content, p.published_at as date + `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(); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5c82df6..678ec4e 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -12,7 +12,12 @@
{#each data.posts as post} -
+
+ {#if post.cover} +
+ {post.title} +
+ {/if}
diff --git a/src/routes/admin/posts/[id]/+page.server.ts b/src/routes/admin/posts/[id]/+page.server.ts index 88c5136..19a0061 100644 --- a/src/routes/admin/posts/[id]/+page.server.ts +++ b/src/routes/admin/posts/[id]/+page.server.ts @@ -11,8 +11,8 @@ export const load: PageServerLoad = async ({ params, platform }) => { try { const post = await db.prepare( - 'SELECT id, title, slug, content, category_id FROM posts WHERE id = ?' - ).bind(postId).first<{ id: number; title: string; slug: string; content: string; category_id: number | null }>(); + 'SELECT id, title, slug, content, cover, category_id FROM posts WHERE id = ?' + ).bind(postId).first<{ id: number; title: string; slug: string; content: string; cover: string | null; category_id: number | null }>(); if (!post) { throw error(404, '文章不存在'); @@ -48,6 +48,7 @@ export const actions: Actions = { const title = formData.get('title')?.toString().trim(); const slug = formData.get('slug')?.toString().trim() || ''; const content = formData.get('content')?.toString() || ''; + const cover = formData.get('cover')?.toString().trim() || null; const categoryId = formData.get('category_id') ? Number(formData.get('category_id')) : null; const selectedTags = formData.getAll('tags').map(t => Number(t)); @@ -59,9 +60,9 @@ export const actions: Actions = { // 更新文章表 await db.prepare( `UPDATE posts - SET title = ?, slug = ?, content = ?, category_id = ?, updated_at = CURRENT_TIMESTAMP + SET title = ?, slug = ?, content = ?, cover = ?, category_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?` - ).bind(title, slug, content, categoryId, postId).run(); + ).bind(title, slug, content, cover, categoryId, postId).run(); // 清除原有标签并重新绑定 await db.prepare('DELETE FROM post_tags WHERE post_id = ?').bind(postId).run(); diff --git a/src/routes/admin/posts/[id]/+page.svelte b/src/routes/admin/posts/[id]/+page.svelte index 6c6c0e9..1562940 100644 --- a/src/routes/admin/posts/[id]/+page.svelte +++ b/src/routes/admin/posts/[id]/+page.svelte @@ -3,12 +3,14 @@ import type { PageData, ActionData } from './$types'; import { deserialize } from '$app/forms'; import MarkdownRenderer from '$lib/components/MarkdownRenderer.svelte'; + import FeaturedImagePicker from '$lib/components/admin/FeaturedImagePicker.svelte'; let { data, form }: { data: PageData; form: ActionData } = $props(); let title = $state(data.post.title); let slug = $state(data.post.slug); let content = $state(data.post.content || ''); + let cover = $state(data.post.cover || ''); let mode = $state<'edit' | 'preview'>('edit'); let selectedTags = $state(data.selectedTagIds || []); let tagsList = $state(data.tags || []); @@ -236,6 +238,8 @@
+ +