feat: 新增中部轮换大屏组件,优化信息展示方式

This commit is contained in:
Pine
2026-08-17 19:04:59 +08:00
parent af820dd2c2
commit 435627070f
2 changed files with 365 additions and 123 deletions
+145 -120
View File
@@ -486,99 +486,94 @@ function ParkOverviewStrip({ d }) {
);
}
/* ---------- TOKEN 消耗 ---------- */
function TokenPanel({ d }) {
/* =========================================================
中部轮换大屏(一次展示一个面板,自动轮换,降低信息密度)
========================================================= */
/* ---------- TOKEN 消耗(大屏版) ---------- */
function TokenBig({ d }) {
return (
<div className="bd-card bd-token-panel">
{/* 概括数据内联在标题右侧 */}
<CardHead
title="TOKEN 消耗"
icon="bolt"
right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>今日</i><b>{fmtWan(d.token.today)} tokens</b></span>
<span className="bd-ost-chip"><i>累计</i><b>{fmtWan(d.token.total)} tokens</b></span>
<span className="bd-ost-chip"><i>速率</i><b>{d.token.rate} t/s</b></span>
</div>
}
/>
{/* 四列布局:环形图(最左) · 模型(左) · 折线图(中) · 数值(右) */}
<div className="bd-split bd-split-tri">
<div className="bd-donut-block">
<Donut items={MODELS} size={96} thickness={13} centerTop="1.28亿" centerBottom="累计 tokens" />
<div className="bd-card bd-big bd-big-token">
<CardHead title="TOKEN 消耗" icon="bolt" right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>今日消耗</i><b>{fmtWan(d.token.today)} tokens</b></span>
<span className="bd-ost-chip"><i>累计消耗</i><b>{fmtWan(d.token.total)} tokens</b></span>
<span className="bd-ost-chip"><i>实时速率</i><b>{d.token.rate} t/s</b></span>
</div>
<div className="bd-model-list">
{MODELS.map((m) => (
<div className="bd-legend-row" key={m.name}>
<span className="bd-legend-dot" style={{ background: m.color }} />
<span className="bd-legend-name">{m.name}</span>
</div>
))}
</div>
<div className="bd-chart-block bd-chart-mid">
<AreaChart series={d.token.series} color="#4cc2ff" height={72}
} />
<div className="bd-big-body">
<div className="bd-big-left bd-big-chart">
<AreaChart series={d.token.series} color="#4cc2ff" height={110}
lastLabel={`近30日 ${d.token.series[d.token.series.length - 1].toFixed(1)}万/日`} />
</div>
<div className="bd-value-list">
{MODELS.map((m) => (
<div className="bd-legend-row" key={m.name}>
<span className="bd-legend-dot" style={{ background: m.color }} />
<b className="bd-value-num">{m.value}%</b>
</div>
))}
<div className="bd-big-right">
<div className="bd-donut-block">
<Donut items={MODELS} size={150} thickness={16} centerTop="1.28亿" centerBottom="累计 tokens" />
</div>
<div className="bd-model-list">
{MODELS.map((m) => (
<div className="bd-legend-row" key={m.name}>
<span className="bd-legend-dot" style={{ background: m.color }} />
<span className="bd-legend-name">{m.name}</span>
</div>
))}
</div>
<div className="bd-value-list">
{MODELS.map((m) => (
<div className="bd-legend-row" key={m.name}>
<span className="bd-legend-dot" style={{ background: m.color }} />
<b className="bd-value-num">{m.value}%</b>
</div>
))}
</div>
</div>
</div>
</div>
);
}
/* ---------- 工具调用 ---------- */
function ToolsPanel({ d }) {
/* ---------- 工具调用(大屏版) ---------- */
function ToolsBig({ d }) {
return (
<div className="bd-card bd-tools-panel">
{/* 概括数据内联在标题右侧 */}
<CardHead
title="工具调用"
icon="wrench"
right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>今日调用</i><b>{fmtInt(d.tools.today)} </b></span>
<span className="bd-ost-chip"><i>累计调用</i><b>{(d.tools.total / 10000).toFixed(1)} </b></span>
<span className="bd-ost-chip"><i>成功</i><b>{d.tools.success}%</b></span>
{/* <span className="bd-ost-chip"><i>服务节点</i><b>12 / 12 在线</b></span> */}
</div>
}
/>
<div className="bd-hbar-title">热门工具调用占比</div>
<HBar items={TOOL_RANK} color="#2f6bff" />
<div className="bd-tools-foot">
<span>· 文本创作 34%</span>
<span>· 智能分析 27%</span>
<span>· 多模态生成 16%</span>
<div className="bd-card bd-big bd-big-tools">
<CardHead title="工具调用" icon="wrench" right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>今日调用</i><b>{fmtInt(d.tools.today)} </b></span>
<span className="bd-ost-chip"><i>累计调用</i><b>{(d.tools.total / 10000).toFixed(1)} </b></span>
<span className="bd-ost-chip"><i>成功率</i><b>{d.tools.success}%</b></span>
<span className="bd-ost-chip"><i>服务节点</i><b>12 / 12 在线</b></span>
</div>
} />
<div className="bd-big-body">
<div className="bd-big-right bd-big-stats">
<div className="bd-big-stat"><span>今日调用</span><b>{fmtInt(d.tools.today)}<em></em></b></div>
<div className="bd-big-stat"><span>累计调用</span><b>{(d.tools.total / 10000).toFixed(1)}<em>万次</em></b></div>
<div className="bd-big-stat"><span>成功率</span><b>{d.tools.success}<em>%</em></b></div>
<div className="bd-big-stat"><span>AI 服务节点</span><b>12<em>/12 在线</em></b></div>
</div>
<div className="bd-big-left bd-big-bars">
<div className="bd-hbar-title">热门工具调用占比</div>
<HBar items={TOOL_RANK} color="#2f6bff" />
</div>
</div>
</div>
);
}
/* ---------- 入园项目 ---------- */
/* ---------- 入园项目(组视图 · 中密度) ---------- */
function ProjectsPanel({ d }) {
return (
<div className="bd-card bd-projects-panel">
{/* 概括数据内联在标题右侧 */}
<CardHead
title="入园项目"
icon="building"
right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>在园</i><b>{d.projects.inPark} </b></span>
<span className="bd-ost-chip"><i>累计孵化</i><b>{d.projects.cum} </b></span>
<span className="bd-ost-chip"><i>本月新增</i><b>{d.projects.todayNew} </b></span>
{/* <span className="bd-ost-chip"><i>高企 / 研发</i><b>32 / 18</b></span> */}
</div>
}
/>
<CardHead title="入园项目" icon="building" right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>在园</i><b>{d.projects.inPark} </b></span>
<span className="bd-ost-chip"><i>累计孵化</i><b>{d.projects.cum} </b></span>
<span className="bd-ost-chip"><i>今日新增</i><b>{d.projects.todayNew} </b></span>
{/* <span className="bd-ost-chip"><i>高企 / 研发</i><b>32 / 18</b></span> */}
</div>
} />
<div className="bd-zone-wrap">
<Donut items={ZONE_DIST} size={92} thickness={13} centerTop="39" centerBottom="本期企业" />
<Donut items={ZONE_DIST} size={96} thickness={13} centerTop="39" centerBottom="本期企业" />
<div className="bd-zone-legend">
{ZONE_DIST.map((z) => (
<div className="bd-zone-row" key={z.name}>
@@ -604,23 +599,18 @@ function ProjectsPanel({ d }) {
);
}
/* ---------- 带动就业 ---------- */
/* ---------- 带动就业(组视图 · 中密度) ---------- */
function JobsPanel({ d }) {
return (
<div className="bd-card bd-jobs-panel">
{/* 概括数据内联在标题右侧 */}
<CardHead
title="带动就业"
icon="users"
right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>就业</i><b>{fmtInt(d.jobs.total)} </b></span>
<span className="bd-ost-chip"><i>毕业生</i><b>1,680 </b></span>
<span className="bd-ost-chip"><i>实习</i><b>506 </b></span>
<span className="bd-ost-chip"><i>新增</i><b>{d.jobs.todayNew} </b></span>
</div>
}
/>
<CardHead title="带动就业" icon="users" right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>累计</i><b>{fmtInt(d.jobs.total)} </b></span>
<span className="bd-ost-chip"><i>毕业生</i><b>1,680 </b></span>
<span className="bd-ost-chip"><i>实习</i><b>506 </b></span>
{/* <span className="bd-ost-chip"><i>今日新增</i><b>{d.jobs.todayNew} 人</b></span> */}
</div>
} />
<div className="bd-hbar-title">行业就业分布</div>
<div className="bd-jobs-bars">
{INDUSTRY.slice(0, 6).map((it, i) => {
@@ -640,23 +630,18 @@ function JobsPanel({ d }) {
);
}
/* ---------- 营收总数 ---------- */
/* ---------- 营收总数(组视图 · 中密度) ---------- */
function RevenuePanel({ d }) {
return (
<div className="bd-card bd-revenue-panel">
{/* 概括数据内联在标题右侧 */}
<CardHead
title="营收总数"
icon="coin"
right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>累计</i><b>{fmtWan(d.revenue.total)} </b></span>
<span className="bd-ost-chip"><i>今日</i><b>+{d.revenue.today} </b></span>
<span className="bd-ost-chip"><i>估值</i><b>5.2 亿元</b></span>
<span className="bd-ost-chip"><i>环比</i><b>+{d.revenue.growth}%</b></span>
</div>
}
/>
<CardHead title="营收总数" icon="coin" right={
<div className="bd-head-stats">
<span className="bd-ost-chip"><i>累计营收</i><b>{fmtWan(d.revenue.total)} </b></span>
<span className="bd-ost-chip"><i>今日营收</i><b>+{d.revenue.today} </b></span>
<span className="bd-ost-chip"><i>企业估值</i><b>5.2 亿元</b></span>
<span className="bd-ost-chip"><i>环比</i><b>+{d.revenue.growth}%</b></span>
</div>
} />
<div className="bd-chart-block">
<AreaChart series={d.revenue.series} color="#17b26a" height={76}
lastLabel={`近30日 ${d.revenue.series[d.revenue.series.length - 1].toFixed(1)}万/日`} />
@@ -670,6 +655,63 @@ function RevenuePanel({ d }) {
);
}
/* ---------- 中部轮换器:五项面板自由组织成两组,组间轮换 ---------- */
const ROTATE_MS = 10000;
function MiddleRotator({ d }) {
const views = [
{
key: 'ai',
label: 'AI 智能应用',
icon: 'cpu',
node: (
<div className="bd-view bd-view-2">
<TokenBig d={d} />
<ToolsBig d={d} />
</div>
),
},
{
key: 'park',
label: '园区经营概况',
icon: 'building',
node: (
<div className="bd-view bd-view-3">
<ProjectsPanel d={d} />
<JobsPanel d={d} />
<RevenuePanel d={d} />
</div>
),
},
];
const [idx, setIdx] = useState(0);
// 组间自动轮换:每次切换后重新计时;点击 Tab 也可切换
useEffect(() => {
const iv = setInterval(() => setIdx((i) => (i + 1) % views.length), ROTATE_MS);
return () => clearInterval(iv);
}, [idx, views.length]);
return (
<section className="bd-rotator">
<div className="bd-rotator-tabs">
{views.map((p, i) => (
<button key={p.key} className={`bd-rot-tab${i === idx ? ' active' : ''}`} onClick={() => setIdx(i)}>
<Icon name={p.icon} size={13} />
<span>{p.label}</span>
<i key={`${p.key}-${i === idx ? 'on' : 'off'}`} className="bd-rot-progress" />
</button>
))}
</div>
<div className="bd-rotator-stage">
<div className="bd-rotator-slide" key={views[idx].key}>
{views[idx].node}
</div>
</div>
</section>
);
}
/* ---------- 实时动态(纯列表,外层自行包裹卡片) ---------- */
function LiveFeed({ feed, limit = 7 }) {
return (
@@ -713,25 +755,8 @@ export default function DataScreen() {
{/* 园区今日概览(五个指标一行 + 标题右侧运行状态) */}
<TodayOverview d={d} />
{/* 主体:下方内容三列显示 */}
<main className="bd-main bd-main-3col">
{/* 第 1 列:TOKEN 消耗 · 工具调用 */}
<section className="bd-col bd-col-left">
<TokenPanel d={d} />
<ToolsPanel d={d} />
</section>
{/* 第 2 列:入园项目 · 带动就业 */}
<section className="bd-col bd-col-mid">
<ProjectsPanel d={d} />
<JobsPanel d={d} />
</section>
{/* 第 3 列:营收总数 */}
<section className="bd-col bd-col-right">
<RevenuePanel d={d} />
</section>
</main>
{/* 中部大屏:五项内容自动轮换展示(降低信息密度) */}
<MiddleRotator d={d} />
{/* 园区实时动态(底部整行 · 园区概览条上方 · 内容三列) */}
<section className="bd-feed-row">
+220 -3
View File
@@ -264,7 +264,7 @@
position: relative;
z-index: 2;
flex-shrink: 0;
padding: 0 clamp(12px, 1.5vw, 22px);
padding: 0 clamp(12px, 1.5vw, 22px) 10px;
}
.bd-feed-row .bd-feed-card {
height: 128px;
@@ -294,6 +294,187 @@
font-size: 9.5px;
}
/* ============ 中部轮换大屏(自动轮换 · 降低信息密度) ============ */
.bd-rotator {
position: relative;
z-index: 2;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: 8px;
padding: clamp(6px, 0.7vw, 10px) clamp(12px, 1.5vw, 22px);
}
.bd-rotator-tabs {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
flex-shrink: 0;
}
.bd-rot-tab {
position: relative;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 16px;
font-size: 12.5px;
font-weight: 600;
color: var(--bd-sub);
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(47, 107, 255, 0.12);
border-radius: 10px;
cursor: pointer;
overflow: hidden;
transition: all 0.2s;
white-space: nowrap;
}
.bd-rot-tab .appica-ico { color: var(--bd-blue); }
.bd-rot-tab:hover { border-color: rgba(47, 107, 255, 0.4); color: var(--bd-ink); }
.bd-rot-tab.active {
color: #fff;
background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan));
border-color: transparent;
box-shadow: 0 4px 14px rgba(47, 107, 255, 0.35);
}
.bd-rot-tab.active .appica-ico { color: #fff; }
.bd-rot-progress {
position: absolute;
left: 0;
bottom: 0;
height: 2.5px;
width: 0;
background: rgba(255, 255, 255, 0.85);
opacity: 0;
}
.bd-rot-tab.active .bd-rot-progress {
opacity: 1;
animation: bdRotProgress 10s linear forwards;
}
@keyframes bdRotProgress { from { width: 0; } to { width: 100%; } }
.bd-rotator-stage {
flex: 1;
min-height: 0;
position: relative;
}
.bd-rotator-slide {
position: absolute;
inset: 0;
animation: bdRotIn 0.5s ease both;
}
@keyframes bdRotIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 组视图:2 列 / 3 列网格 */
.bd-view {
position: absolute;
inset: 0;
display: grid;
gap: clamp(10px, 1vw, 16px);
}
.bd-view-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.bd-view-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.bd-view > .bd-card { min-width: 0; min-height: 0; }
/* 大屏面板(一次只展示一个,布局舒展) */
.bd-big {
padding: clamp(14px, 1.4vw, 22px);
}
.bd-big-body {
flex: 1;
min-height: 0;
display: flex;
gap: clamp(16px, 2vw, 32px);
}
.bd-big-left {
flex: 1.5;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
}
.bd-big-right {
flex: 1;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
justify-content: center;
gap: 12px;
}
.bd-big-chart { justify-content: center; }
.bd-big-chart .bd-chart-svg { height: 100%; }
.bd-big-right .bd-donut-block { justify-content: center; }
/* 大数字统计块 */
.bd-big-stats { gap: 12px; }
.bd-big-stat {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 14px 20px;
border-radius: 14px;
background: rgba(244, 248, 253, 0.8);
border: 1px solid rgba(47, 107, 255, 0.08);
}
.bd-big-stat span { font-size: 13px; color: var(--bd-sub); white-space: nowrap; }
.bd-big-stat b {
font-family: "JetBrains Mono", "Oswald", monospace;
font-size: 26px;
font-weight: 700;
color: var(--bd-ink);
white-space: nowrap;
}
.bd-big-stat b em {
font-style: normal;
font-size: 13px;
color: var(--bd-sub);
margin-left: 4px;
}
/* TOKEN 大屏:左图表 + 右环形图/模型/数值 */
.bd-big-token .bd-big-right {
flex-direction: row;
align-items: center;
gap: 16px;
}
.bd-big-token .bd-model-list { flex: 1; gap: 8px; }
.bd-big-token .bd-value-list { flex: 0 0 64px; gap: 8px; }
/* 工具调用大屏:左统计 + 右大条形 */
.bd-big-bars { justify-content: center; }
.bd-big-bars .bd-hbars { gap: 16px; }
.bd-big-bars .bd-hbar { grid-template-columns: 120px minmax(0, 1fr) 56px; gap: 14px; }
.bd-big-bars .bd-hbar-name { font-size: 14px; }
.bd-big-bars .bd-hbar-track { height: 16px; }
.bd-big-bars .bd-hbar-val { font-size: 14px; }
/* 入园项目大屏:左环形图+图例 · 右名录滚动 */
.bd-big-zone {
flex-direction: row;
align-items: center;
gap: 28px;
}
.bd-big-zone .bd-zone-legend { grid-template-columns: 1fr; gap: 6px; }
.bd-big-zone .bd-zone-row { font-size: 13px; }
.bd-big-ticker { gap: 6px; }
.bd-big-ticker .bd-ticker { flex: 1; }
.bd-big-ticker .bd-ticker-item { padding: 6px 12px; font-size: 13px; }
/* 带动就业大屏:左大柱状 + 右统计 */
.bd-big-jobsbars { justify-content: center; }
.bd-big-jobsbars .bd-jobs-bars { gap: 16px; }
.bd-big-jobsbars .bd-vbar-val { font-size: 15px; }
.bd-big-jobsbars .bd-vbar-name { font-size: 12px; }
/* 营收大屏:左大折线 + 右统计/标签 */
.bd-big-revenue .bd-big-right { gap: 12px; }
.bd-big-revenue .bd-revenue-tags { margin-top: 2px; }
/* ============ 卡片(通透玻璃,弱化卡片感) ============ */
.bd-card {
position: relative;
@@ -351,6 +532,8 @@
align-items: center;
gap: 6px;
white-space: nowrap;
min-width: 0;
overflow: hidden;
}
/* 标签(轻量徽标) */
@@ -1137,6 +1320,19 @@
.bd-model-list { flex-basis: 88px; }
.bd-value-list { flex-basis: 44px; }
.bd-split-tri .bd-donut { width: 64px; height: 64px; }
.bd-rot-tab { padding: 4px 11px; font-size: 11.5px; }
.bd-big { padding: 10px 14px; }
.bd-big-stat { padding: 9px 14px; }
.bd-big-stat b { font-size: 19px; }
.bd-big-stat span { font-size: 11.5px; }
.bd-big-body { gap: 14px; }
.bd-big .bd-donut { width: 108px; height: 108px; }
.bd-big-zone .bd-zone-row { font-size: 11.5px; }
.bd-big-ticker .bd-ticker-item { padding: 4px 10px; font-size: 11.5px; }
.bd-big-bars .bd-hbar-name { font-size: 12px; }
.bd-big-bars .bd-hbar-track { height: 12px; }
.bd-big-bars .bd-hbar-val { font-size: 12px; }
.bd-big-jobsbars .bd-vbar-name { font-size: 10px; }
}
/* 窄屏:收缩 TOKEN 卡内部列宽 + 概览条压缩 */
@media (max-width: 1500px) {
@@ -1576,12 +1772,33 @@
gap: 5px;
}
/* 卡片标题右侧的概括数据内联 */
/* 卡片标题右侧的概括数据内联(强制一行,不允许超出) */
.bd-head-stats {
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 5px;
flex-wrap: nowrap;
gap: 4px;
min-width: 0;
}
.bd-head-stats .bd-ost-chip {
flex-shrink: 1;
min-width: 0;
padding: 2px 7px;
font-size: 9.5px;
gap: 3px;
overflow: hidden;
white-space: nowrap;
}
.bd-head-stats .bd-ost-chip i {
overflow: hidden;
text-overflow: ellipsis;
max-width: 70px;
}
.bd-head-stats .bd-ost-chip b {
overflow: hidden;
text-overflow: ellipsis;
max-width: 120px;
}
.bd-ost-chip {
display: inline-flex;