feat: 全站 PageShell 设计系统与专业 UI 统一
- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架 - index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩) - 全站路由页接入 PageShell,云账单三 Tab 为标杆 - 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化 - 登录页/侧栏/顶栏去渐变光球,企业克制风 - 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user