From dd82a3b97544d0aac8b6e866c863972808372bdb Mon Sep 17 00:00:00 2001 From: zxyszx <299979470+zxyszx@users.noreply.github.com> Date: Mon, 3 Aug 2026 00:37:05 +0800 Subject: [PATCH] fix: compact stats dashboard layout --- apps/web/src/pages/profile.tsx | 135 ++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 63 deletions(-) diff --git a/apps/web/src/pages/profile.tsx b/apps/web/src/pages/profile.tsx index 0db4a49..f87d281 100644 --- a/apps/web/src/pages/profile.tsx +++ b/apps/web/src/pages/profile.tsx @@ -1,7 +1,7 @@ import * as React from "react" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useNavigate, useSearchParams } from "react-router-dom" -import { Archive, ArrowLeft, BarChart3, Ban, BookOpen, ChevronDown, Clock3, Code2, Contact, Copy, ExternalLink, HardDrive, Image, Info, KeyRound, Laptop, Link2, LogOut, Mail, MailCheck, MailX, MessageSquare, Moon, PanelLeftOpen, PencilLine, Plus, RefreshCcw, Search, SendHorizontal, Settings, ShieldCheck, SlidersHorizontal, Star, Sun, Trash2, Users, X } from "lucide-react" +import { ArrowLeft, BarChart3, Ban, BookOpen, ChevronDown, Clock3, Code2, Contact, Copy, ExternalLink, HardDrive, Image, Info, KeyRound, Laptop, Link2, LogOut, Mail, MailCheck, MailX, MessageSquare, Moon, PanelLeftOpen, PencilLine, Plus, RefreshCcw, Search, SendHorizontal, Settings, ShieldCheck, SlidersHorizontal, Sun, Trash2, Users, X } from "lucide-react" import { QRCodeSVG } from "qrcode.react" import { api, APIToken, ExternalImapAccount, ExternalImapAccountPayload, ExternalImapFolder, ExternalImapOAuthProvider, ExternalImapStorageMode, ExternalImapSyncRun, ExternalImapTlsMode, ForwardingSettings, ForwardingVerifiedEmail, MailLabel, MailRule, MailRuleAction, MailRuleCondition, Mailbox, MailboxApplyOptions, MailSignature, MailStats, PermissionLimits } from "@/lib/api" import { cn, formatBytes } from "@/lib/utils" @@ -369,7 +369,7 @@ export function ProfilePage() {
-
{renderTab()}
+
{renderTab()}
@@ -379,7 +379,7 @@ export function ProfilePage() {
-
{renderTab()}
+
{renderTab()}
@@ -2988,7 +2988,7 @@ function StatsSection({ stats, mailbox, rangeDays, onRangeChange, onRefresh }: { { label: "平均邮件大小", value: formatBytes(stats?.averageMessageBytes || 0) }, ] return ( -
+

查看邮件收发趋势、分布情况和常用联系人。

@@ -3015,50 +3015,65 @@ function StatsSection({ stats, mailbox, rangeDays, onRangeChange, onRefresh }: {
-
+
{primaryCards.map((card) => ( -
-
-
{card.icon}
- {card.detail && {card.detail}} +
+
{card.icon}
+
+
+
{card.label}
+ {card.detail && {card.detail}} +
+
{card.value}
-
{card.value}
-
{card.label}
))}
-
+
{secondaryStats.map((item) => ( -
-
{item.value}
-
{item.label}
+
+
{item.label}
+
{item.value}
))}
- - - -
- +
+ + + + - - - - + +
+
+ + + + + +
- - -
) } +function StatsPanel({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+
+

{title}

+
+
{children}
+
+ ) +} + function StatsTrendChart({ points }: { points: MailStats["trend"] }) { const data = points.length ? points : [{ date: "", incoming: 0, outgoing: 0 }] const maxValue = Math.max(...data.flatMap((item) => [item.incoming, item.outgoing]), 1) const width = 520 - const height = 210 - const padding = { top: 18, right: 14, bottom: 32, left: 34 } + const height = 148 + const padding = { top: 12, right: 10, bottom: 22, left: 32 } const plotWidth = width - padding.left - padding.right const plotHeight = height - padding.top - padding.bottom const xFor = (index: number) => padding.left + (data.length === 1 ? plotWidth / 2 : (index / (data.length - 1)) * plotWidth) @@ -3066,12 +3081,12 @@ function StatsTrendChart({ points }: { points: MailStats["trend"] }) { const pathFor = (key: "incoming" | "outgoing") => data.map((item, index) => `${index === 0 ? "M" : "L"} ${xFor(index).toFixed(1)} ${yFor(item[key]).toFixed(1)}`).join(" ") const ticks = trendTicks(data) return ( -
-
+
+
收件 发件
- + {[0, 0.25, 0.5, 0.75, 1].map((step) => { const y = padding.top + plotHeight * step return @@ -3087,7 +3102,7 @@ function StatsTrendChart({ points }: { points: MailStats["trend"] }) { {maxValue} 0 {ticks.map((tick) => ( - {tick.label} + {tick.label} ))}
@@ -3125,44 +3140,38 @@ function StatsDistribution({ items }: { items: MailStats["distribution"] }) { ] const maxCount = Math.max(...rows.map((row) => row.count), 1) return ( -
+
{rows.map((row) => ( -
-
-
- {distributionIcon(row.key)} - {row.label} -
- {row.count} -
-
-
+
+
{row.label}
+
+
0 ? 6 : 0, Math.round((row.count / maxCount) * 100))}%` }} />
+
{row.count}
))}
) } -function distributionIcon(key: string) { - const cls = "h-4 w-4" - if (key === "archive") return - if (key === "spam") return - if (key === "trash") return - if (key === "attachments") return - if (key === "starred") return - return +function distributionBarTone(key: string) { + if (key === "archive") return "bg-sky-500" + if (key === "spam") return "bg-amber-400" + if (key === "trash") return "bg-red-500" + if (key === "attachments") return "bg-violet-500" + if (key === "starred") return "bg-slate-600" + return "bg-slate-900" } function StatsStorage({ quotaLabel, quotaPct, hasQuota }: { quotaLabel: string; quotaPct: number; hasQuota: boolean }) { return ( -
-
-
{quotaLabel}
-
{hasQuota ? `${quotaPct.toFixed(0)}%` : "不限"}
+
+
+
{quotaLabel}
+
{hasQuota ? `${quotaPct.toFixed(0)}%` : "不限"}
-
+

{quotaPct >= 90 ? "存储容量接近上限,请及时清理。" : "存储容量使用正常。"}

@@ -3170,14 +3179,14 @@ function StatsStorage({ quotaLabel, quotaPct, hasQuota }: { quotaLabel: string; } function StatsContacts({ contacts }: { contacts: MailStats["topContacts"] }) { - if (contacts.length === 0) return } text="暂无常用联系人" description="有邮件往来后会显示联系人排行" /> + if (contacts.length === 0) return } text="暂无常用联系人" description="有邮件往来后会显示联系人排行" className="h-[136px] min-h-0 py-4" /> return ( -
- {contacts.map((item, index) => ( -
-
{index + 1}
+
+ {contacts.slice(0, 7).map((item, index) => ( +
+
{index + 1}
{item.email}
- {item.count} 封 +
{item.count} 封
))}