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 @@
+ +