From ad101e15a82bff8a8ad8cf14fcab81ecd3f5fe3a Mon Sep 17 00:00:00 2001 From: seaHi Date: Fri, 25 Sep 2026 08:54:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=E5=90=8E=E5=8F=B0=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=AD=E6=96=B0=E5=A2=9E=20Cloudflare=20=E6=B5=81?= =?UTF-8?q?=E9=87=8F=E7=BB=9F=E8=AE=A1=E4=B8=8E=E7=AB=99=E7=82=B9=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/admin/AnalyticsPanel.svelte | 266 ++++++++++++++++++ src/routes/admin/settings/+page.server.ts | 22 +- src/routes/admin/settings/+page.svelte | 6 +- 3 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 src/lib/components/admin/AnalyticsPanel.svelte diff --git a/src/lib/components/admin/AnalyticsPanel.svelte b/src/lib/components/admin/AnalyticsPanel.svelte new file mode 100644 index 0000000..b76b9fd --- /dev/null +++ b/src/lib/components/admin/AnalyticsPanel.svelte @@ -0,0 +1,266 @@ + + +
+
+
+
文章总数
+
{dbStats.posts}
+
+
+
页面总数
+
{dbStats.pages}
+
+
+
分类 / 标签
+
{dbStats.categories} / {dbStats.tags}
+
+
+ + {#if loading} +
+
+

正在从 Cloudflare 获取最近 30 天流量数据...

+
+ {:else if error} +
+ {error} +
+ {:else if !analyticsData || analyticsData.length === 0} +
+ 没有获取到有效的流量数据,请确保 CF_API_TOKEN 和 CF_ZONE_ID 配置正确,且网站已有访问。 +
+ {:else} +
+
+
总请求数 (30天)
+
{totalRequests.toLocaleString()}
+
+
+
总下发流量 (30天)
+
{formatBytes(totalBytes)}
+
+
+
缓存命中率
+
{hitRate.toFixed(1)}%
+
+
+
+
+
+ +
+

流量与缓存趋势 (近30天)

+
+ +
+
+ {/if} +
+ + diff --git a/src/routes/admin/settings/+page.server.ts b/src/routes/admin/settings/+page.server.ts index 024935a..a09d1ab 100644 --- a/src/routes/admin/settings/+page.server.ts +++ b/src/routes/admin/settings/+page.server.ts @@ -2,26 +2,44 @@ import { fail } from '@sveltejs/kit'; import type { PageServerLoad, Actions } from './$types'; +import { fetchAnalytics } from '$lib/server/cloudflare'; export const load: PageServerLoad = async ({ locals, platform }) => { const db = platform?.env?.DB; const settings: Record = {}; + let dbStats = { posts: 0, pages: 0, tags: 0, categories: 0 }; if (db) { try { const { results } = await db.prepare('SELECT key, value FROM settings').all(); results.forEach((row: any) => { settings[row.key] = row.value; }); + + const [postsRes, pagesRes, tagsRes, categoriesRes] = await db.batch([ + db.prepare('SELECT COUNT(*) as count FROM posts'), + db.prepare('SELECT COUNT(*) as count FROM pages'), + db.prepare('SELECT COUNT(*) as count FROM tags'), + db.prepare('SELECT COUNT(*) as count FROM categories') + ]); + + dbStats = { + posts: (postsRes.results[0] as any).count || 0, + pages: (pagesRes.results[0] as any).count || 0, + tags: (tagsRes.results[0] as any).count || 0, + categories: (categoriesRes.results[0] as any).count || 0 + }; } catch (err: any) { - console.error('Failed to load settings', err); + console.error('Failed to load settings or stats', err); } } return { user: locals.user, platformEnv: db ? 'Cloudflare D1 (Connected)' : 'Local Mock', - settings + settings, + dbStats, + analytics: fetchAnalytics(platform) // Streaming promise }; }; diff --git a/src/routes/admin/settings/+page.svelte b/src/routes/admin/settings/+page.svelte index f44c83b..204a380 100644 --- a/src/routes/admin/settings/+page.svelte +++ b/src/routes/admin/settings/+page.svelte @@ -7,6 +7,7 @@ import DatePicker from '$lib/components/admin/DatePicker.svelte'; import TextInput from '$lib/components/admin/TextInput.svelte'; import Button from '$lib/components/admin/Button.svelte'; + import AnalyticsPanel from '$lib/components/admin/AnalyticsPanel.svelte'; import { untrack } from 'svelte'; let { data, form }: { data: PageData; form: ActionData } = $props(); @@ -44,7 +45,8 @@ tabs={[ { id: 'basic', label: '基础信息' }, { id: 'appearance', label: '外观与主页' }, - { id: 'terms', label: '术语设定' } + { id: 'terms', label: '术语设定' }, + { id: 'analytics', label: '数据统计' } ]} bind:activeTab /> @@ -318,6 +320,8 @@ + {:else if activeTab === 'analytics'} + {/if}