feat: 后台文件管理与标签管理增加分页功能
continuous-integration/drone/push Build is passing

- 新增可复用的后台分页组件 `Pagination.svelte`
- 文件管理 (`/admin/files`) 服务端查询增加分页逻辑(默认 24 条/页),前端接入分页导航
- 标签管理 (`/admin/tags`) 服务端查询增加分页逻辑(默认 20 条/页),前端接入分页导航
This commit is contained in:
seahi committed 2026-09-26 15:01:17 +08:00
1 parent f92abc126d
commit 19606301de
5 files changed
+114 -9

No files matched your search

@@ -0,0 +1,60 @@
<script lang="ts">
import Button from './Button.svelte';
let {
page,
totalPages,
path = ''
}: {
page: number;
totalPages: number;
path?: string;
} = $props();
</script>
{#if totalPages > 1}
<div class="pagination">
<Button
variant="secondary"
href={page > 1 ? `${path}?page=${page - 1}` : undefined}
class={page <= 1 ? 'disabled' : ''}
aria-disabled={page <= 1}
>
上一页
</Button>
<span class="page-info">{page} / {totalPages}</span>
<Button
variant="secondary"
href={page < totalPages ? `${path}?page=${page + 1}` : undefined}
class={page >= totalPages ? 'disabled' : ''}
aria-disabled={page >= totalPages}
>
下一页
</Button>
</div>
{/if}
<style>
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
margin-top: 30px;
}
.page-info {
font-weight: 600;
font-size: 0.95rem;
min-width: 60px;
text-align: center;
}
:global(.pagination .btn.disabled) {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
</style>
+23 -4
View File
@@ -11,30 +11,49 @@ type Attachment = {
created_at: string; created_at: string;
}; };
export const load: PageServerLoad = async ({ platform }) => { export const load: PageServerLoad = async ({ url, platform }) => {
const db = platform?.env.DB; const db = platform?.env.DB;
if (!db) return { files: [], r2Domain: '' }; if (!db)
return { files: [], pagination: { page: 1, totalPages: 0, totalItems: 0 }, r2Domain: '' };
const page = Number(url.searchParams.get('page')) || 1;
const limit = 24;
const offset = (page - 1) * limit;
try { try {
const domainResult = await db const domainResult = await db
.prepare('SELECT value FROM settings WHERE key = ?') .prepare('SELECT value FROM settings WHERE key = ?')
.bind('r2.public_domain') .bind('r2.public_domain')
.first<{ value: string }>(); .first<{ value: string }>();
const countResult = await db
.prepare('SELECT COUNT(*) as count FROM attachments')
.first<{ count: number }>();
const totalItems = countResult?.count || 0;
const totalPages = Math.ceil(totalItems / limit);
const { results } = await db const { results } = await db
.prepare( .prepare(
`SELECT id, filename, key, mime_type, size, created_at `SELECT id, filename, key, mime_type, size, created_at
FROM attachments FROM attachments
ORDER BY created_at DESC` ORDER BY created_at DESC
LIMIT ? OFFSET ?`
) )
.bind(limit, offset)
.all<Attachment>(); .all<Attachment>();
return { return {
files: results, files: results,
pagination: { page, totalPages, totalItems },
r2Domain: domainResult?.value || 'https://assets.yourdomain.com' r2Domain: domainResult?.value || 'https://assets.yourdomain.com'
}; };
} catch (cause) { } catch (cause) {
console.error('Error fetching attachments:', cause); console.error('Error fetching attachments:', cause);
return { files: [], r2Domain: 'https://assets.yourdomain.com' }; return {
files: [],
pagination: { page: 1, totalPages: 0, totalItems: 0 },
r2Domain: 'https://assets.yourdomain.com'
};
} }
}; };
+7
View File
@@ -7,6 +7,7 @@
import { addToast } from '$lib/stores/toast.svelte'; import { addToast } from '$lib/stores/toast.svelte';
import { confirmState } from '$lib/stores/confirm.svelte'; import { confirmState } from '$lib/stores/confirm.svelte';
import { prepareUpload } from '$lib/uploads'; import { prepareUpload } from '$lib/uploads';
import Pagination from '$lib/components/admin/Pagination.svelte';
let { data, form } = $props<{ data: PageData; form: ActionData }>(); let { data, form } = $props<{ data: PageData; form: ActionData }>();
@@ -213,6 +214,12 @@
{/if} {/if}
</div> </div>
<Pagination
page={data.pagination.page}
totalPages={data.pagination.totalPages}
path="/admin/files"
/>
<style> <style>
.admin-page-header { .admin-page-header {
display: flex; display: flex;
+18 -5
View File
@@ -3,14 +3,24 @@
import { fail } from '@sveltejs/kit'; import { fail } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types'; import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ platform }) => { export const load: PageServerLoad = async ({ url, platform }) => {
const db = platform?.env?.DB; const db = platform?.env?.DB;
if (!db) { if (!db) {
return { tags: [] }; return { tags: [], pagination: { page: 1, totalPages: 0, totalItems: 0 } };
} }
const page = Number(url.searchParams.get('page')) || 1;
const limit = 20;
const offset = (page - 1) * limit;
try { try {
const countResult = await db
.prepare('SELECT COUNT(*) as count FROM tags')
.first<{ count: number }>();
const totalItems = countResult?.count || 0;
const totalPages = Math.ceil(totalItems / limit);
// 查询所有标签以及对应的文章数量 // 查询所有标签以及对应的文章数量
const { results } = await db const { results } = await db
.prepare( .prepare(
@@ -18,16 +28,19 @@ export const load: PageServerLoad = async ({ platform }) => {
FROM tags t FROM tags t
LEFT JOIN post_tags pt ON t.id = pt.tag_id LEFT JOIN post_tags pt ON t.id = pt.tag_id
GROUP BY t.id, t.name GROUP BY t.id, t.name
ORDER BY t.name ASC` ORDER BY t.name ASC
LIMIT ? OFFSET ?`
) )
.bind(limit, offset)
.all(); .all();
return { return {
tags: results as unknown as { id: number; name: string; post_count: number }[] tags: results as unknown as { id: number; name: string; post_count: number }[],
pagination: { page, totalPages, totalItems }
}; };
} catch (err: any) { } catch (err: any) {
console.error('Error fetching tags:', err); console.error('Error fetching tags:', err);
return { tags: [] }; return { tags: [], pagination: { page: 1, totalPages: 0, totalItems: 0 } };
} }
}; };
+6
View File
@@ -6,6 +6,7 @@
import Button from '$lib/components/admin/Button.svelte'; import Button from '$lib/components/admin/Button.svelte';
import DataTable from '$lib/components/admin/DataTable.svelte'; import DataTable from '$lib/components/admin/DataTable.svelte';
import Alert from '$lib/components/admin/Alert.svelte'; import Alert from '$lib/components/admin/Alert.svelte';
import Pagination from '$lib/components/admin/Pagination.svelte';
let { data, form }: { data: PageData; form: ActionData } = $props(); let { data, form }: { data: PageData; form: ActionData } = $props();
@@ -125,6 +126,11 @@
{/each} {/each}
</tbody> </tbody>
</DataTable> </DataTable>
<Pagination
page={data.pagination.page}
totalPages={data.pagination.totalPages}
path="/admin/tags"
/>
{/if} {/if}
</div> </div>