From c6b65adcaf9452c301552b777a39bab5f5d03ef7 Mon Sep 17 00:00:00 2001 From: seaHi Date: Fri, 25 Sep 2026 12:47:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E6=8E=A7=E5=88=B6=E9=9D=A2=E6=9D=BF=E7=9A=84=E5=88=86?= =?UTF-8?q?=E6=9E=90=E7=BB=9F=E8=AE=A1=E8=A7=86=E5=9B=BE=E5=B9=B6=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E8=B5=84=E6=BA=90=E9=85=8D=E9=A2=9D=E7=9B=91=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/admin/AnalyticsPanel.svelte | 359 +++++++++++++----- 1 file changed, 261 insertions(+), 98 deletions(-) diff --git a/src/lib/components/admin/AnalyticsPanel.svelte b/src/lib/components/admin/AnalyticsPanel.svelte index 854a978..01160ec 100644 --- a/src/lib/components/admin/AnalyticsPanel.svelte +++ b/src/lib/components/admin/AnalyticsPanel.svelte @@ -25,6 +25,14 @@ let d1RowsWritten = $state(0); let r2PayloadSize = $state(0); + let d1ReadDaily = $derived(d1Data.length > 0 ? d1RowsRead / 30 : 0); + let d1ReadPercent = $derived(d1Data.length > 0 ? (d1ReadDaily / 5000000) * 100 : 0); + let d1WriteDaily = $derived(d1Data.length > 0 ? d1RowsWritten / 30 : 0); + let d1WritePercent = $derived(d1Data.length > 0 ? (d1WriteDaily / 100000) * 100 : 0); + let r2Percent = $derived( + r2Data.length > 0 ? (r2PayloadSize / (10 * 1024 * 1024 * 1024)) * 100 : 0 + ); + $effect(() => { analyticsPromise .then((data: any) => { @@ -178,60 +186,124 @@ 没有获取到有效的流量数据,请确保 CF_API_TOKEN 和 CF_ZONE_ID 配置正确,且网站已有访问。 {:else} -
Cloudflare 资源消耗 (近30天)
- {#if missingEnvVars} -
- 💡 提示:如需查看 D1 数据库读写和 R2 存储用量,请在系统环境变量中补充 CF_ACCOUNT_ID, CF_D1_ID 和 CF_R2_BUCKET。 -
- {/if} -
-
-
D1 数据库读取行数
-
{d1Data.length > 0 ? formatNumber(d1RowsRead) : '-'}
-
-
-
D1 数据库写入行数
-
{d1Data.length > 0 ? formatNumber(d1RowsWritten) : '-'}
-
-
-
R2 存储空间用量
-
{r2Data.length > 0 ? formatBytes(r2PayloadSize) : '-'}
-
-
+
30天访问流量
-
网站流量统计 (近30天)
-
-
-
页面浏览量 (PV)
-
{totalPV.toLocaleString()}
+
+
+
+ +
-
-
独立访客 (UV)
-
{totalUV.toLocaleString()}
-
-
-
总请求数
-
{totalRequests.toLocaleString()}
-
-
-
总下发流量
-
{formatBytes(totalBytes)}
-
-
-
缓存命中率
-
{hitRate.toFixed(1)}%
-
-
+
+
+
页面浏览 (PV)
+
{totalPV.toLocaleString()}
+
+
+
独立访客 (UV)
+
{totalUV.toLocaleString()}
+
+
+
消耗总带宽
+
{formatBytes(totalBytes)}
+
+
+
缓存命中率
+
{hitRate.toFixed(1)}%
-
-

流量趋势 (近30天)

-
- +
Cloudflare 资源配额
+ {#if missingEnvVars} +
+ 提示:如需正常获取 D1 和 R2 数据,请确保在环境变量中配置 CF_ACCOUNT_ID, + CF_D1_ID + 和 CF_R2_BUCKET。 +
+ {/if} +
+ +
+
+
+ D1 数据库日均读取 基于近30天平均 +
+
+ {d1Data.length > 0 ? formatNumber(d1ReadDaily) : '-'} + / 5M +
+
+
+
+
+ +
+ + +
+
+
+ D1 数据库日均写入 基于近30天平均 +
+
+ {d1Data.length > 0 ? formatNumber(d1WriteDaily) : '-'} + / 100K +
+
+
+
+
+ +
+ + +
+
+
R2 存储空间用量 当前峰值
+
+ {r2Data.length > 0 ? formatBytes(r2PayloadSize) : '-'} + / 10 GB +
+
+
+
+
+
{/if} @@ -241,14 +313,17 @@ .analytics-panel { display: flex; flex-direction: column; - gap: 24px; + gap: 30px; } .section-title { - font-size: 1.2rem; - font-weight: 600; + font-size: 1.25rem; + font-weight: 700; color: var(--text-color); - margin-bottom: -10px; + margin-bottom: -15px; + display: flex; + align-items: center; + gap: 8px; } .info-alert { @@ -268,61 +343,22 @@ font-family: monospace; } - .stats-row { + .dashboard-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 20px; - } - - .stat-card { + grid-template-columns: 3fr 1fr; + gap: 0; border: 2px solid var(--text-color, #000); - padding: 20px; background: transparent; - display: flex; - flex-direction: column; - gap: 8px; } - .stat-title { - font-size: 0.9rem; - font-weight: 600; - color: var(--text-muted, rgba(0, 0, 0, 0.6)); - text-transform: uppercase; - letter-spacing: 0.5px; - } - - .stat-value { - font-size: 2rem; - font-weight: 800; - font-family: monospace; - } - - .progress-bar { - width: 100%; - height: 6px; - background: rgba(0, 0, 0, 0.1); - margin-top: 8px; - border-radius: 3px; - overflow: hidden; - } - - .progress-fill { - height: 100%; - background: #4caf50; - transition: width 1s ease-out; + @media (max-width: 768px) { + .dashboard-grid { + grid-template-columns: 1fr; + } } .chart-container { - border: 2px solid var(--text-color, #000); padding: 20px; - background: transparent; - } - - .chart-container h3 { - margin: 0 0 20px 0; - font-size: 1.1rem; - border-bottom: 1px solid var(--border-color, rgba(0, 0, 0, 0.1)); - padding-bottom: 10px; } .canvas-wrapper { @@ -331,6 +367,133 @@ width: 100%; } + .core-metrics { + display: flex; + flex-direction: column; + border-left: 2px solid var(--text-color, #000); + } + + @media (max-width: 768px) { + .core-metrics { + border-left: none; + border-top: 2px solid var(--text-color, #000); + flex-direction: row; + flex-wrap: wrap; + } + } + + .metric-item { + padding: 20px; + border-bottom: 2px solid var(--text-color, #000); + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + min-width: 140px; + } + + .metric-item:last-child { + border-bottom: none; + } + + .metric-title { + font-size: 0.85rem; + font-weight: 600; + color: var(--text-muted, rgba(0, 0, 0, 0.6)); + } + + .metric-value { + font-size: 1.8rem; + font-weight: 800; + font-family: monospace; + } + + .quota-container { + border: 2px solid var(--text-color, #000); + background: transparent; + display: flex; + flex-direction: column; + } + + .quota-item { + padding: 24px; + border-bottom: 2px solid var(--text-color, #000); + } + .quota-item:last-child { + border-bottom: none; + } + + .quota-header { + display: flex; + justify-content: space-between; + align-items: flex-end; + margin-bottom: 12px; + } + + .quota-title { + font-weight: 600; + font-size: 1rem; + display: flex; + align-items: center; + gap: 10px; + } + + .badge { + font-size: 0.75rem; + font-weight: 400; + background: rgba(0, 0, 0, 0.05); + border: 1px solid var(--text-color, #000); + padding: 2px 8px; + border-radius: 12px; + color: var(--text-muted, rgba(0, 0, 0, 0.6)); + } + + .quota-numbers { + font-size: 1.1rem; + font-weight: 700; + font-family: monospace; + } + + .quota-max { + color: var(--text-muted, rgba(0, 0, 0, 0.5)); + font-weight: 500; + font-size: 0.9rem; + } + + .quota-bar-bg { + width: 100%; + height: 10px; + background: rgba(0, 0, 0, 0.1); + border: 1px solid var(--text-color, #000); + border-radius: 6px; + overflow: hidden; + } + + .quota-bar-fill { + height: 100%; + transition: width 1s ease-out; + } + + .quota-bar-fill.safe { + background: #4caf50; + } + + .quota-bar-fill.warning { + background: #ff9800; + } + + .quota-bar-fill.danger { + background: #e53935; + } + + .quota-footer { + display: flex; + justify-content: space-between; + margin-top: 8px; + font-size: 0.85rem; + color: var(--text-muted, rgba(0, 0, 0, 0.6)); + } + .loading-state, .error-state, .empty-state {