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 (
-
-
+
+
收件
发件
-
@@ -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} 封
))}