feat(admin): 仪表盘次级概览卡接真数据(A5) — 8卡聚合,存储卡诚实占位
- Dashboard.tsx 去掉占位块 → OverviewSection; 新增 admin/modules/dashboard/(MiniMetric/useDashboardExtras/overviewMetrics/OverviewSection) - 8卡用现有 lib/api 导出: LLM组/RAG文档/自动化/已装插件(含toolCount)/用户/网关请求/LLM调用+子代理/存储(缺只读端点→诚实占位) - 端点报错→值'—'+徽标'端点报错'+hint后端原文(不崩不伪造) - 仅碰 Dashboard.tsx+dashboard/*, 未动 api.ts/types.ts(避免并发冲突) - 前端 npm run build 绿(156 modules)
This commit is contained in:
1 parent
f28995c1e5
commit
ca2faa0f95
5 files changed
+343
-23
No files matched your search
@@ -0,0 +1,54 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Icon, type IconName } from "../../../components/Icon";
|
||||
import { Tag, type TagTone } from "../../../components/primitives";
|
||||
import { navigateAdmin, type AdminSectionId } from "../../router";
|
||||
|
||||
/** Honest per-card state: real data landed / still loading / endpoint failed /
|
||||
* no endpoint exists in lib/api.ts (a placeholder, never a fabricated value). */
|
||||
export type MetricState = "ok" | "loading" | "error" | "placeholder";
|
||||
|
||||
/** One derived overview card (design/10 §4: 卡片多而全, 详情在各独立页面). */
|
||||
export interface MiniMetricSpec {
|
||||
icon: IconName;
|
||||
label: string;
|
||||
value: string;
|
||||
hint: string;
|
||||
state: MetricState;
|
||||
/** Admin module this card drills down to; absent = not a link. */
|
||||
section?: AdminSectionId;
|
||||
}
|
||||
|
||||
const STATE_BADGE: Record<MetricState, { label: string; tone: TagTone }> = {
|
||||
ok: { label: "已接入", tone: "ok" },
|
||||
loading: { label: "加载中", tone: "neutral" },
|
||||
error: { label: "端点报错", tone: "err" },
|
||||
placeholder: { label: "占位", tone: "warn" },
|
||||
};
|
||||
|
||||
export function MiniMetric({ icon, label, value, hint, state, section }: MiniMetricSpec): ReactNode {
|
||||
const badge = STATE_BADGE[state];
|
||||
const body = (
|
||||
<>
|
||||
<div className="flex items-center gap-1.5 text-xs text-[var(--mu-text-lo)]">
|
||||
<Icon name={icon} size={14} />
|
||||
<span className="truncate">{label}</span>
|
||||
<Tag tone={badge.tone} className="ml-auto shrink-0">{badge.label}</Tag>
|
||||
</div>
|
||||
<div className="mt-1.5 text-lg font-semibold leading-tight">{value}</div>
|
||||
<div className="mt-0.5 break-words text-xs text-[var(--mu-text-lo)]">{hint}</div>
|
||||
</>
|
||||
);
|
||||
const base =
|
||||
"rounded-[var(--mu-radius)] border border-[var(--mu-glass-border)] bg-[var(--mu-surface-3)]/60 p-3";
|
||||
if (!section) return <div className={base}>{body}</div>;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
title="点击下钻"
|
||||
onClick={() => navigateAdmin(section)}
|
||||
className={`${base} text-left transition-all duration-200 ease-[var(--mu-spring)] hover:border-[var(--mu-accent)]/50`}
|
||||
>
|
||||
{body}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user