feat: 全站 PageShell 设计系统与专业 UI 统一

- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架
- index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩)
- 全站路由页接入 PageShell,云账单三 Tab 为标杆
- 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化
- 登录页/侧栏/顶栏去渐变光球,企业克制风
- 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
Logan committed 2026-07-01 12:45:38 +08:00
1 parent 1acb0c49af
commit e363ded445
213 files changed
+7148 -6818

No files matched your search

@@ -10,7 +10,16 @@ export const providerLabels: Record<string, string> = {
azure: 'Azure',
};
// 云厂商品牌色(主组件 + 弹窗共享) // Logan.Li
// 云厂商品牌色(跨主题固定,用 hex 避免 Tailwind 硬编码) // Logan.Li
export const providerHex: Record<string, string> = {
aliyun: '#FF6A00',
tencent: '#006EFF',
aws: '#FF9900',
huawei: '#CF0A2C',
azure: '#0078D4',
};
/** @deprecated 请改用 providerHex + style */
export const providerColors: Record<string, string> = {
aliyun: 'bg-orange-500',
tencent: 'bg-blue-500',
@@ -19,6 +28,15 @@ export const providerColors: Record<string, string> = {
azure: 'bg-sky-500',
};
export function ProviderDot({ provider, className = 'w-2 h-2' }: { provider: string; className?: string }) {
return (
<span
className={`rounded-full shrink-0 ${className}`}
style={{ backgroundColor: providerHex[provider] || 'var(--surface-active)' }}
/>
);
}
export function formatCurrency(n: number) {
return `¥${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}
@@ -32,17 +50,17 @@ export function StatCard({ icon, label, value, trend, trendLabel, alert }: {
alert?: boolean;
}) {
return (
<div className={`rounded-lg border p-4 ${alert ? 'border-amber-500/30 bg-amber-500/5' : 'border-slate-800 bg-slate-900/50'}`}>
<div className={alert ? 'dg-alert-banner' : 'rounded-lg border border-[var(--border-default)] bg-[var(--surface-raised)]/50 p-4'}>
<div className="flex items-center justify-between mb-2">
<span className="text-slate-400 text-sm">{label}</span>
<span className={alert ? 'text-amber-400' : 'text-slate-500'}>{icon}</span>
<span className="dg-text-secondary text-sm">{label}</span>
<span className={alert ? 'dg-text-yellow' : 'dg-text-muted'}>{icon}</span>
</div>
<div className={`text-2xl font-bold ${alert ? 'text-amber-300' : 'text-slate-100'}`}>{value}</div>
<div className={`text-2xl font-bold ${alert ? 'dg-text-yellow' : 'text-[var(--text-primary)]'}`}>{value}</div>
{trendLabel && (
<div className="flex items-center gap-1 mt-1 text-xs">
{trend !== undefined && trend > 0 && <TrendingUp className="h-3 w-3 text-red-400" />}
{trend !== undefined && trend < 0 && <TrendingDown className="h-3 w-3 text-green-400" />}
<span className="text-slate-500">{trendLabel}</span>
{trend !== undefined && trend > 0 && <TrendingUp className="h-3 w-3 dg-text-red" />}
{trend !== undefined && trend < 0 && <TrendingDown className="h-3 w-3 dg-text-green" />}
<span className="dg-text-muted">{trendLabel}</span>
</div>
)}
</div>
@@ -51,7 +69,7 @@ export function StatCard({ icon, label, value, trend, trendLabel, alert }: {
export function TrendChart({ data }: { data: { date: string; cost: number }[] }) {
if (data.length === 0) {
return <p className="text-sm text-slate-500 py-8 text-center">暂无数据</p>;
return <p className="text-sm dg-text-muted py-8 text-center">暂无数据</p>;
}
const max = Math.max(...data.map((d) => d.cost), 1);
const recent = data.slice(-30);
@@ -68,20 +86,23 @@ export function TrendChart({ data }: { data: { date: string; cost: number }[] })
style={{ height: '100%' }}
>
<div
className="absolute bottom-0 w-full rounded-t-sm bg-gradient-to-t from-blue-600/60 to-blue-400/80 group-hover:from-blue-500 group-hover:to-blue-300 transition-colors"
style={{ height: `${h}%` }}
className="absolute bottom-0 w-full rounded-t-sm transition-opacity group-hover:opacity-90"
style={{
height: `${h}%`,
background: `linear-gradient(to top, color-mix(in srgb, var(--brand) 55%, transparent), color-mix(in srgb, var(--brand) 85%, transparent))`,
}}
/>
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block whitespace-nowrap text-xs bg-slate-800 text-slate-200 px-2 py-1 rounded border border-slate-700 z-10">
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 hidden group-hover:block whitespace-nowrap text-xs bg-[var(--surface-inset)] text-[var(--text-primary)] px-2 py-1 rounded border border-[var(--border-default)] z-10">
{d.date}: {formatCurrency(d.cost)}
</div>
</div>
);
})}
</div>
<div className="flex justify-between mt-2 text-xs text-slate-500">
<div className="flex justify-between mt-2 text-xs dg-text-muted">
<span>{recent[0]?.date}</span>
<span>{recent[recent.length - 1]?.date}</span>
</div>
</div>
);
}
}