@@ -62,28 +73,54 @@
别名 (Slug) |
作者 |
发布时间 |
-
- {#each data.posts as post}
+ {#each data.posts as post (post.id || post)}
|
-
+
|
{post.id} |
- {post.title}
-
-
+ {post.title}
+
+
|
- {post.category || '未分类'} |
- {post.series || '—'} |
+ {post.category || '未分类'} |
+ {post.series || '—'} |
{post.slug} |
{post.author || '未知'} |
{post.dateStr} |
-
{/each}
@@ -144,9 +181,10 @@
text-align: left;
}
- .data-table th, .data-table td {
+ .data-table th,
+ .data-table td {
padding: 10px 16px;
- border-bottom: 1px solid var(--border-color, rgba(0,0,0,0.08));
+ border-bottom: 1px solid var(--border-color, rgba(0, 0, 0, 0.08));
}
.data-table th {
@@ -162,7 +200,7 @@
}
.data-table tbody tr:hover {
- background-color: rgba(0,0,0,0.02);
+ background-color: rgba(0, 0, 0, 0.02);
}
.preview-link {
@@ -178,12 +216,10 @@
opacity: 1;
}
-
-
.empty-state {
padding: 50px 20px;
text-align: center;
- color: var(--text-muted, rgba(0,0,0,0.6));
+ color: var(--text-muted, rgba(0, 0, 0, 0.6));
}
.batch-actions {
@@ -192,7 +228,7 @@
gap: 15px;
margin-bottom: 15px;
padding: 10px 15px;
- background: rgba(0,0,0,0.02);
+ background: rgba(0, 0, 0, 0.02);
height: 54px;
box-sizing: border-box;
}
@@ -204,7 +240,7 @@
}
.selected-count.empty {
- color: var(--text-muted, rgba(0,0,0,0.4));
+ color: var(--text-muted, rgba(0, 0, 0, 0.4));
font-weight: normal;
}
@@ -213,7 +249,7 @@
text-align: center;
}
- .checkbox-col input[type="checkbox"] {
+ .checkbox-col input[type='checkbox'] {
width: 16px;
height: 16px;
accent-color: var(--accent-color, #419b38);
@@ -242,10 +278,9 @@
background-color: #d9534f;
color: white;
}
-
+
.btn-sm {
padding: 4px 12px;
font-size: 0.85rem;
}
-
diff --git a/src/routes/admin/posts/[id]/+page.server.ts b/src/routes/admin/posts/[id]/+page.server.ts
index bd47b07..0dcc8a7 100644
--- a/src/routes/admin/posts/[id]/+page.server.ts
+++ b/src/routes/admin/posts/[id]/+page.server.ts
@@ -1,3 +1,5 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
import { error, fail, isHttpError, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
import {
@@ -16,19 +18,42 @@ export const load: PageServerLoad = async ({ params, platform }) => {
}
try {
- const post = await db.prepare(
- 'SELECT id, title, slug, content, cover, category_id, series_id, series_weight, published_at, updated_at FROM posts WHERE id = ?'
- ).bind(postId).first<{ id: number; title: string; slug: string; content: string; cover: string | null; category_id: number | null; series_id: number | null; series_weight: number | null; published_at?: string; updated_at?: string }>();
+ const post = await db
+ .prepare(
+ 'SELECT id, title, slug, content, cover, category_id, series_id, series_weight, published_at, updated_at FROM posts WHERE id = ?'
+ )
+ .bind(postId)
+ .first<{
+ id: number;
+ title: string;
+ slug: string;
+ content: string;
+ cover: string | null;
+ category_id: number | null;
+ series_id: number | null;
+ series_weight: number | null;
+ published_at?: string;
+ updated_at?: string;
+ }>();
if (!post) {
throw error(404, '文章不存在');
}
- const { results: allTags } = await db.prepare('SELECT id, name FROM tags ORDER BY name ASC').all();
- const { results: allCategories } = await db.prepare('SELECT id, name FROM categories ORDER BY name ASC').all();
- const { results: allSeries } = await db.prepare('SELECT id, name, slug FROM series ORDER BY name COLLATE NOCASE ASC').all();
+ const { results: allTags } = await db
+ .prepare('SELECT id, name FROM tags ORDER BY name ASC')
+ .all();
+ const { results: allCategories } = await db
+ .prepare('SELECT id, name FROM categories ORDER BY name ASC')
+ .all();
+ const { results: allSeries } = await db
+ .prepare('SELECT id, name, slug FROM series ORDER BY name COLLATE NOCASE ASC')
+ .all();
- const { results: selectedTagRows } = await db.prepare('SELECT tag_id FROM post_tags WHERE post_id = ?').bind(postId).all<{ tag_id: number }>();
+ const { results: selectedTagRows } = await db
+ .prepare('SELECT tag_id FROM post_tags WHERE post_id = ?')
+ .bind(postId)
+ .all<{ tag_id: number }>();
const selectedTagIds = selectedTagRows.map((row) => row.tag_id);
return {
@@ -58,7 +83,7 @@ export const actions: Actions = {
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));
+ const selectedTags = formData.getAll('tags').map((t) => Number(t));
const publishedAt = formData.get('published_at')?.toString().trim();
const updatedAt = formData.get('updated_at')?.toString().trim() || new Date().toISOString();
@@ -76,35 +101,68 @@ export const actions: Actions = {
parseSeriesWeight(formData.get('series_weight')),
postId
);
- } catch (err) {
+ } catch (err: any) {
if (err instanceof SeriesValidationError) return fail(400, { error: err.message });
throw err;
}
try {
- // 更新文章表
+ // 改为 D1 batch 事务批量更新正文、清除老标签并原子绑定新标签
+ const stmts = [];
if (publishedAt) {
- await db.prepare(
- `UPDATE posts
- SET title = ?, slug = ?, content = ?, cover = ?, category_id = ?, series_id = ?, series_weight = ?, published_at = ?, updated_at = ?
- WHERE id = ?`
- ).bind(title, slug, content, cover, categoryId, seriesId, seriesWeight, publishedAt, updatedAt, postId).run();
+ stmts.push(
+ db
+ .prepare(
+ `UPDATE posts
+ SET title = ?, slug = ?, content = ?, cover = ?, category_id = ?, series_id = ?, series_weight = ?, published_at = ?, updated_at = ?
+ WHERE id = ?`
+ )
+ .bind(
+ title,
+ slug,
+ content,
+ cover,
+ categoryId,
+ seriesId,
+ seriesWeight,
+ publishedAt,
+ updatedAt,
+ postId
+ )
+ );
} else {
- await db.prepare(
- `UPDATE posts
- SET title = ?, slug = ?, content = ?, cover = ?, category_id = ?, series_id = ?, series_weight = ?, updated_at = ?
- WHERE id = ?`
- ).bind(title, slug, content, cover, categoryId, seriesId, seriesWeight, updatedAt, postId).run();
+ stmts.push(
+ db
+ .prepare(
+ `UPDATE posts
+ SET title = ?, slug = ?, content = ?, cover = ?, category_id = ?, series_id = ?, series_weight = ?, updated_at = ?
+ WHERE id = ?`
+ )
+ .bind(
+ title,
+ slug,
+ content,
+ cover,
+ categoryId,
+ seriesId,
+ seriesWeight,
+ updatedAt,
+ postId
+ )
+ );
}
- // 清除原有标签并重新绑定
- await db.prepare('DELETE FROM post_tags WHERE post_id = ?').bind(postId).run();
-
+ stmts.push(db.prepare('DELETE FROM post_tags WHERE post_id = ?').bind(postId));
+
if (selectedTags.length > 0) {
for (const tagId of selectedTags) {
- await db.prepare('INSERT INTO post_tags (post_id, tag_id) VALUES (?, ?)').bind(postId, tagId).run();
+ stmts.push(
+ db.prepare('INSERT INTO post_tags (post_id, tag_id) VALUES (?, ?)').bind(postId, tagId)
+ );
}
}
+
+ await db.batch(stmts);
} catch (err: unknown) {
console.error('Error updating post:', err);
if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
diff --git a/src/routes/admin/posts/[id]/+page.svelte b/src/routes/admin/posts/[id]/+page.svelte
index d9fb00d..da5d8a7 100644
--- a/src/routes/admin/posts/[id]/+page.svelte
+++ b/src/routes/admin/posts/[id]/+page.svelte
@@ -1,4 +1,7 @@
-
+
{#if form?.error}