From 19520f5efb11de15b4af7ab0462b384881fc06ba Mon Sep 17 00:00:00 2001 From: seaHi Date: Sun, 26 Jul 2026 17:52:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E4=B8=8E=E6=A0=87=E7=AD=BE=E7=9A=84=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/admin/categories/+page.server.ts | 115 ++++++++ src/routes/admin/categories/+page.svelte | 279 ++++++++++++++++++++ src/routes/admin/tags/+page.server.ts | 71 +++++ src/routes/admin/tags/+page.svelte | 259 ++++++++++++++++++ 4 files changed, 724 insertions(+) create mode 100644 src/routes/admin/categories/+page.server.ts create mode 100644 src/routes/admin/categories/+page.svelte create mode 100644 src/routes/admin/tags/+page.server.ts create mode 100644 src/routes/admin/tags/+page.svelte diff --git a/src/routes/admin/categories/+page.server.ts b/src/routes/admin/categories/+page.server.ts new file mode 100644 index 0000000..f496d57 --- /dev/null +++ b/src/routes/admin/categories/+page.server.ts @@ -0,0 +1,115 @@ +async function ensureCategoriesTable(db: any) { + try { + await db.prepare(` + CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + slug TEXT NOT NULL UNIQUE + ) + `).run(); + try { + await db.prepare('ALTER TABLE posts ADD COLUMN category_id INTEGER').run(); + } catch { + // Column might already exist + } + } catch (e) { + console.error('Failed to ensure categories table:', e); + } +} + +export const load: PageServerLoad = async ({ platform }) => { + const db = platform?.env?.DB; + + if (!db) { + return { categories: [] }; + } + + try { + const { results } = await db.prepare( + `SELECT c.id, c.name, c.slug, COUNT(p.id) as post_count + FROM categories c + LEFT JOIN posts p ON c.id = p.category_id + GROUP BY c.id, c.name, c.slug + ORDER BY c.name ASC` + ).all(); + + return { + categories: results as unknown as { id: number; name: string; slug: string; post_count: number }[] + }; + } catch (err: any) { + if (err?.message?.includes('no such table')) { + await ensureCategoriesTable(db); + try { + const { results } = await db.prepare( + `SELECT c.id, c.name, c.slug, COUNT(p.id) as post_count + FROM categories c + LEFT JOIN posts p ON c.id = p.category_id + GROUP BY c.id, c.name, c.slug + ORDER BY c.name ASC` + ).all(); + return { categories: results as unknown as { id: number; name: string; slug: string; post_count: number }[] }; + } catch (e) { + return { categories: [] }; + } + } + console.error('Error fetching categories:', err); + return { categories: [] }; + } +}; + +export const actions: Actions = { + create: async ({ request, platform }) => { + const db = platform?.env?.DB; + if (!db) return fail(500, { error: '数据库未连接' }); + + const formData = await request.formData(); + const name = formData.get('name')?.toString().trim(); + let slug = formData.get('slug')?.toString().trim(); + + if (!name) { + return fail(400, { error: '分类名称不能为空' }); + } + + if (!slug) { + slug = name.toLowerCase().replace(/\s+/g, '-'); + } + + try { + await db.prepare('INSERT INTO categories (name, slug) VALUES (?, ?)').bind(name, slug).run(); + return { success: true }; + } catch (err: any) { + if (err?.message?.includes('no such table')) { + await ensureCategoriesTable(db); + try { + await db.prepare('INSERT INTO categories (name, slug) VALUES (?, ?)').bind(name, slug).run(); + return { success: true }; + } catch (e: any) { + return fail(500, { error: '创建分类失败: ' + e.message }); + } + } + console.error('Error creating category:', err); + if (err?.message?.includes('UNIQUE constraint failed')) { + return fail(400, { error: '分类名称或 URL 别名 (Slug) 已存在' }); + } + return fail(500, { error: '创建分类失败' }); + } + }, + + delete: async ({ request, platform }) => { + const db = platform?.env?.DB; + if (!db) return fail(500, { error: '数据库未连接' }); + + const formData = await request.formData(); + const id = formData.get('id'); + + if (!id) return fail(400, { error: '缺少分类 ID' }); + + try { + await db.prepare('DELETE FROM categories WHERE id = ?').bind(id).run(); + return { success: true }; + } catch (err) { + console.error('Error deleting category:', err); + return { success: false, error: '删除分类失败' }; + } + } +}; diff --git a/src/routes/admin/categories/+page.svelte b/src/routes/admin/categories/+page.svelte new file mode 100644 index 0000000..896d4d6 --- /dev/null +++ b/src/routes/admin/categories/+page.svelte @@ -0,0 +1,279 @@ + + +
+

分类管理

+
+ +{#if form?.error} +
+ {form.error} +
+{/if} + +
+ +
+

新分类

+
+
+ + +
+
+ + +
+ +
+
+ + +
+ {#if data.categories.length === 0} +
+

暂无任何分类,在左侧添加一个吧!

+
+ {:else} + + + + + + + + + + + + {#each data.categories as category} + + + + + + + + {/each} + +
ID分类名称别名 (Slug)关联文章数操作
{category.id}{category.name}{category.slug}{category.post_count} 篇文章 +
{ + if(!confirm(`确定要删除分类 "${category.name}" 吗?删除后相关文章将归为未分类。`)) e.preventDefault(); + }}> + + +
+
+ {/if} +
+
+ + diff --git a/src/routes/admin/tags/+page.server.ts b/src/routes/admin/tags/+page.server.ts new file mode 100644 index 0000000..a02ff97 --- /dev/null +++ b/src/routes/admin/tags/+page.server.ts @@ -0,0 +1,71 @@ +import { fail } from '@sveltejs/kit'; +import type { Actions, PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ platform }) => { + const db = platform?.env?.DB; + + if (!db) { + return { tags: [] }; + } + + try { + // 查询所有标签以及对应的文章数量 + const { results } = await db.prepare( + `SELECT t.id, t.name, COUNT(pt.post_id) as post_count + FROM tags t + LEFT JOIN post_tags pt ON t.id = pt.tag_id + GROUP BY t.id, t.name + ORDER BY t.name ASC` + ).all(); + + return { + tags: results as unknown as { id: number; name: string; post_count: number }[] + }; + } catch (err) { + console.error('Error fetching tags:', err); + return { tags: [] }; + } +}; + +export const actions: Actions = { + create: async ({ request, platform }) => { + const db = platform?.env?.DB; + if (!db) return fail(500, { error: '数据库未连接' }); + + const formData = await request.formData(); + const name = formData.get('name')?.toString().trim(); + + if (!name) { + return fail(400, { error: '标签名称不能为空' }); + } + + try { + await db.prepare('INSERT INTO tags (name) VALUES (?)').bind(name).run(); + return { success: true }; + } catch (err: any) { + console.error('Error creating tag:', err); + if (err?.message?.includes('UNIQUE constraint failed')) { + return fail(400, { error: '标签名称已存在' }); + } + return fail(500, { error: '创建标签失败' }); + } + }, + + delete: async ({ request, platform }) => { + const db = platform?.env?.DB; + if (!db) return fail(500, { error: '数据库未连接' }); + + const formData = await request.formData(); + const id = formData.get('id'); + + if (!id) return fail(400, { error: '缺少标签 ID' }); + + try { + await db.prepare('DELETE FROM tags WHERE id = ?').bind(id).run(); + return { success: true }; + } catch (err) { + console.error('Error deleting tag:', err); + return { success: false, error: '删除标签失败' }; + } + } +}; diff --git a/src/routes/admin/tags/+page.svelte b/src/routes/admin/tags/+page.svelte new file mode 100644 index 0000000..8049796 --- /dev/null +++ b/src/routes/admin/tags/+page.svelte @@ -0,0 +1,259 @@ + + +
+

标签管理

+
+ +{#if form?.error} +
+ {form.error} +
+{/if} + +
+ +
+

新标签

+
+
+ + +
+ +
+
+ + +
+ {#if data.tags.length === 0} +
+

暂无任何标签,在左侧添加一个吧!

+
+ {:else} + + + + + + + + + + + {#each data.tags as tag} + + + + + + + {/each} + +
ID标签名称关联文章数操作
{tag.id}{tag.name}{tag.post_count} 篇文章 +
{ + if(!confirm(`确定要删除标签 "${tag.name}" 吗?`)) e.preventDefault(); + }}> + + +
+
+ {/if} +
+
+ +