import { useState, useEffect, useRef } from 'react'; import '../styles/datascreen.css'; import Icon from '../components/Icons'; import ParkOverviewStrip from '../components/ParkOverviewStrip'; /* ========================================================= 昆明市大学生创业园 · OPC 智能园区数字运营中心 —— 数据大屏(深色科技风 · 实时模拟数据) --------------------------------------------------------- 基础数据来源: · 《昆明市创业园宣传册》:在园项目 150+ / 累计孵化 200+ / 带动就业 2000+ / 累计营收 2 亿+ / 企业估值 5 亿+ / 高企 32 家 / 重点研发项目 18 项 / 大赛奖项 47 项 · 《昆明市大学生创业园入驻企业信息表》:39 家本期入驻企业 (区域、行业、名称) 大屏实时数据为演示模拟(每 2.2s 跳动一次) ========================================================= */ /* ---------- 真实基础数据(入驻企业信息表) ---------- */ // [企业名, 区域, 行业] const PROJECTS = [ ['云南派音人工智能科技', '加速区', 'AI 科技'], ['米勒克尔蓝宝石珠宝', '加速区', '新材料'], ['中泰研学合作', '加速区', '教育培训'], ['云南宸中低空经济', '加速区', '低空经济'], ['昆明智海银高文化科技', '加速区', 'AI 科技'], ['AI 机器人大模型训练', '加速区', 'AI 科技'], ['云南廷秀文旅康养', '加速区', '文旅康养'], ['瀚颖 AI+ 教育咨询', '加速区', '教育培训'], ['云南大学 AI+ 创业平台', '加速区', 'AI 科技'], ['仰光客厅', '国际区', '文旅康养'], ['云南上古绝学文化', '国际区', '文化创意'], ['千楣之路', '国际区', '跨境电商'], ['中越生物医疗', '国际区', '生物医药'], ['中越文旅交流', '国际区', '文旅康养'], ['绿态环保科技', '国际区', '绿色环保'], ['联翼航空低空经济', '国际区', '低空经济'], ['酷享野农 AI 农业', '国际区', '现代农业'], ['滇缅国际设计', '国际区', '企业服务'], ['昆明舒诺生物科技', '国际区', '生物医药'], ['达岸教育管理', '国际区', '教育培训'], ['昆明滇油石化', '成长区', '绿色环保'], ['花仙子园艺肥料', '成长区', '现代农业'], ['Facebook 越商茶器', '成长区', '跨境电商'], ['研X 同行者网络', '成长区', 'AI 科技'], ['鬼才明 AI 创意工作室', '成长区', 'AI 科技'], ['朵哈·玫瑰特色产业链', '成长区', '文化创意'], ['昆明云韵体育', '成长区', '文化创意'], ['昆明舒护安养老服务', '成长区', '生物医药'], ['南菌优培食用菌', '成长区', '生物医药'], ['五华区丽裳文化', '成长区', '文化创意'], ['云南星瑞航空', '成长区', '低空经济'], ['云南直播私域平台', '成长区', '跨境电商'], ['昆明屿澈电商', 'OPC 空间', '跨境电商'], ['中外青少年研学', 'OPC 空间', '教育培训'], ['星杭商务信息咨询', 'OPC 空间', '企业服务'], ['蓝智科技', 'OPC 空间', '现代农业'], ['云品出滇·纸享万家', 'OPC 空间', '跨境电商'], ['云迹轻旅', 'OPC 空间', '文旅康养'], ['启元人工智能科技', 'OPC 空间', 'AI 科技'], ]; /* 区域分布(39 家本期企业) */ const ZONE_DIST = [ { name: '孵化加速区', value: 9, color: '#4c8dff' }, { name: '国际创客区', value: 7, color: '#22d3ee' }, { name: '成长苗圃区', value: 8, color: '#34d399' }, { name: 'OPC 创业空间', value: 6, color: '#fbbf24' }, { name: '待分配', value: 9, color: '#7c8db0' }, ]; /* 就业行业分布(累计 2186 人) */ const INDUSTRY = [ { name: 'AI 与数字经济', value: 520, color: '#4cc2ff' }, { name: '电商与贸易', value: 310, color: '#7c9bff' }, { name: '生物医药与大健康', value: 240, color: '#34d399' }, { name: '文创与农业', value: 260, color: '#f472b6' }, { name: '文旅与康养', value: 190, color: '#fbbf24' }, { name: '教育培训', value: 180, color: '#a78bfa' }, { name: '低空经济', value: 160, color: '#22d3ee' }, { name: '绿色新材料与企业服务', value: 326, color: '#9aa7c4' }, ]; /* 模型占比 */ const MODELS = [ { name: 'DeepSeek-V3', value: 38, color: '#4c8dff' }, { name: '通义千问', value: 21, color: '#22d3ee' }, { name: '智谱 GLM-4', value: 16, color: '#34d399' }, { name: '豆包', value: 12, color: '#fbbf24' }, { name: '讯飞星火', value: 8, color: '#a78bfa' }, { name: '其他', value: 5, color: '#64748b' }, ]; /* 工具调用占比 */ const TOOL_RANK = [ { name: '文档生成', value: 34 }, { name: '数据查询', value: 27 }, { name: '图像创作', value: 16 }, { name: '代码执行', value: 13 }, { name: '语音合成', value: 10 }, ]; const TOOL_NAMES = TOOL_RANK.map((t) => t.name); const MODEL_NAMES = MODELS.map((m) => m.name); const COMPANY_NAMES = PROJECTS.map((p) => p[0]); /* ---------- 工具函数 ---------- */ const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; const fmtInt = (n) => Math.round(n).toLocaleString('zh-CN'); const fmtWan = (n) => (n >= 10000 ? (n / 10000).toFixed(2) + '亿' : n >= 100 ? n.toFixed(1) + '万' : n.toFixed(1)); const nowTime = () => new Date().toLocaleTimeString('zh-CN', { hour12: false }); function makeSeries(base, growth, noise, n = 30) { const arr = []; let v = base; for (let i = 0; i < n; i++) { v = v * (1 + growth) + (Math.random() - 0.5) * noise; arr.push(Math.max(1, Math.round(v * 100) / 100)); } return arr; } /* 实时动态事件模板 */ function genEvent() { const r = Math.random; const pool = [ { icon: 'bolt', text: `AI 推理任务完成 · 消耗 ${Math.round(800 + r() * 9000).toLocaleString()} tokens(${pick(MODEL_NAMES)})` }, { icon: 'wrench', text: `工具「${pick(TOOL_NAMES)}」被调用 ${Math.round(10 + r() * 90)} 次` }, { icon: 'building', text: `「${pick(COMPANY_NAMES)}」提交入驻申请 · 进入评审流程` }, { icon: 'users', text: `「${pick(COMPANY_NAMES)}」新增招聘岗位 ${Math.round(1 + r() * 5)} 个` }, { icon: 'coin', text: `园区企业完成一笔 ¥${(0.5 + r() * 9).toFixed(1)}万 交易` }, { icon: 'broadcast', text: `创业沙龙「AI 一人公司实战课」报名开启` }, { icon: 'robot', text: `「${pick(MODEL_NAMES)}」模型完成一次微调任务` }, { icon: 'flask', text: `在孵项目完成实验验证 · 进入加速评审` }, ]; const e = pick(pool); return { id: Date.now() + Math.random(), icon: e.icon, text: e.text, time: nowTime() }; } function initFeed() { return [ { id: 1, icon: 'bolt', text: '云南派音AI 完成音频向量嵌入任务 · 消耗 12,480 tokens(DeepSeek-V3)', time: '09:02:14' }, { id: 2, icon: 'building', text: '「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间', time: '09:05:41' }, { id: 3, icon: 'wrench', text: '工具「文档生成」被调用 36 次', time: '09:11:03' }, { id: 4, icon: 'coin', text: '园区企业完成一笔 ¥3.6万 交易', time: '09:17:26' }, { id: 5, icon: 'users', text: '「昆明舒护安养老服务」新增招聘岗位 2 个', time: '09:23:58' }, ]; } /* ---------- 模拟数据引擎(每 2.2s 跳动) ---------- */ function initSnapshot() { return { t: 0, token: { today: 128.64, total: 12840, rate: 84.6, series: makeSeries(82, 0.012, 9) }, tools: { today: 3568, total: 365204, success: 98.7 }, projects: { inPark: 158, cum: 208, todayNew: 2 }, jobs: { total: 2186, todayNew: 3 }, revenue: { today: 38.6, total: 20800, growth: 8.2, series: makeSeries(30, 0.006, 4) }, park: { devices: 98.6, energy: 386, people: 127, desk: 76, meeting: 3, nodes: 12 }, feed: initFeed(), }; } function nextSnapshot(s) { const r = Math.random; const tDeltaWan = +(0.26 + r() * 0.34).toFixed(2); // 万 tokens const token = { today: +(s.token.today + tDeltaWan).toFixed(2), total: +(s.token.total + tDeltaWan).toFixed(2), rate: +(76 + r() * 20).toFixed(1), }; const toolDelta = Math.round(13 + r() * 22); const tools = { today: s.tools.today + toolDelta, total: s.tools.total + toolDelta, success: +(98.1 + r() * 1.2).toFixed(1), }; const revDelta = +(0.6 + r() * 1.7).toFixed(1); const revenue = { ...s.revenue, today: +(s.revenue.today + revDelta).toFixed(1), total: +(s.revenue.total + revDelta).toFixed(1), growth: +(7.2 + r() * 2.2).toFixed(1), }; const park = { devices: +(97.6 + r() * 1.6).toFixed(1), energy: Math.round(320 + r() * 130), people: Math.round(80 + r() * 95), desk: Math.round(62 + r() * 24), meeting: Math.round(2 + r() * 4), nodes: 12, }; const projects = { ...s.projects }; if (r() < 0.055) projects.todayNew += 1; if (r() < 0.035) { projects.inPark += 1; projects.cum += 1; } const jobs = { ...s.jobs }; if (r() < 0.08) jobs.todayNew += 1; if (r() < 0.05) jobs.total += 1; // 30 日曲线:今日点持续爬升,每 12 个 tick 滚动一天 let tSeries = s.token.series.slice(); tSeries[tSeries.length - 1] = +(tSeries[tSeries.length - 1] + tDeltaWan).toFixed(2); if (s.t % 12 === 11) tSeries = [...tSeries.slice(1), +(72 + r() * 30).toFixed(2)]; token.series = tSeries; let rSeries = s.revenue.series.slice(); rSeries[rSeries.length - 1] = +(rSeries[rSeries.length - 1] + revDelta).toFixed(1); if (s.t % 12 === 11) rSeries = [...rSeries.slice(1), +(26 + r() * 9).toFixed(1)]; revenue.series = rSeries; const feed = s.t % 3 === 2 ? [genEvent(), ...s.feed].slice(0, 7) : s.feed; return { ...s, t: s.t + 1, token, tools, revenue, park, projects, jobs, feed }; } function useParkSim() { const [snap, setSnap] = useState(initSnapshot); useEffect(() => { const iv = setInterval(() => setSnap((p) => nextSnapshot(p)), 2200); return () => clearInterval(iv); }, []); return snap; } /* ---------- 通用 Hooks ---------- */ /* 数字平滑滚动(rAF 缓动;进度钳制 [0,1],避免边缘环境时间戳异常导致发散) */ function useAnimatedNumber(target, duration = 900) { const [cur, setCur] = useState(target); const curRef = useRef(target); const raf = useRef(); useEffect(() => { const from = curRef.current; if (from === target) return; const t0 = performance.now(); cancelAnimationFrame(raf.current); const tick = (now) => { const p = Math.min(1, Math.max(0, (now - t0) / duration)); const e = 1 - Math.pow(1 - p, 3); const v = from + (target - from) * e; curRef.current = v; setCur(v); if (p < 1) raf.current = requestAnimationFrame(tick); }; raf.current = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf.current); }, [target, duration]); return cur; } /* ---------- 通用小组件 ---------- */ function CardHead({ title, right, icon }) { return (
{icon && } {title}
{right &&
{right}
}
); } function Pill({ children, tone = 'blue' }) { return {children}; } /* 面积图 + 折线(viewBox 高度动态匹配容器,满宽绘制、无留白错位) */ function AreaChart({ series, color, height = 100, unit = '', lastLabel }) { const W = 300, PL = 8, PR = 8, PT = 16, PB = 18; const ref = useRef(null); const [vh, setVh] = useState(height); // viewBox 高度(按容器宽高比计算) useEffect(() => { const el = ref.current; if (!el) return; const update = () => { const w = el.clientWidth, h = el.clientHeight; if (w > 0 && h > 0) setVh(Math.max(40, Math.round((W * h) / w))); }; update(); const ro = new ResizeObserver(update); ro.observe(el); return () => ro.disconnect(); }, []); const H = vh; const min = Math.min(...series), max = Math.max(...series); const lo = min * 0.9, hi = max * 1.08, span = hi - lo || 1; const px = (i) => PL + (i / (series.length - 1)) * (W - PL - PR); const py = (v) => H - PB - ((v - lo) / span) * (H - PT - PB); const line = series.map((v, i) => `${px(i).toFixed(1)},${py(v).toFixed(1)}`).join(' '); const area = `${line} ${px(series.length - 1).toFixed(1)},${(H - PB).toFixed(1)} ${px(0).toFixed(1)},${(H - PB).toFixed(1)}`; const gid = useRef('g' + Math.random().toString(36).slice(2, 8)).current; return ( {[0.25, 0.5, 0.75].map((t) => ( ))} 30 天前 今日 {lastLabel && ( {lastLabel} )} ); } /* 环形图 */ function Donut({ items, size = 148, thickness = 18, centerTop, centerBottom }) { const total = items.reduce((a, b) => a + b.value, 0); const R = (size - thickness) / 2; const C = 2 * Math.PI * R; const cx = size / 2; let acc = 0; return ( {items.map((it, i) => { const frac = it.value / total; const dash = Math.max(0.5, frac * C - 1.5); const off = -acc * C; acc += frac; return ( ); })} {centerTop} {centerBottom} ); } /* 横向排行条 */ function HBar({ items, color = '#4cc2ff', unit = '%' }) { const max = Math.max(...items.map((i) => i.value)); return (
{items.map((it, i) => (
{it.name}
{it.value}{unit}
))}
); } /* ========================================================= 面板组件 ========================================================= */ /* ---------- KPI 核心指标带(5 大必须指标) ---------- */ function KpiCard({ icon, label, value, unit, sub, tone }) { const v = useAnimatedNumber(value); return (
{label}
{tone === 'revenue' || tone === 'token' ? {fmtWan(v)} : {fmtInt(v)}} {unit && {unit}}
{sub}
); } /* ========================================================= 园区今日概览(标题右侧内联运行状态 + 五个指标一行) ┌──────────────────────────────────────────────┐ │ 园区今日概览 · [6 项运行状态内联] · 实时刷新 │ ├──────┬──────┬──────┬──────┬──────┤ │ TOKEN│ 营收 │ 入园 │ 工具 │ 就业 │ ← 五个卡片一行 └──────┴──────┴──────┴──────┴──────┘ (园区实时动态 → 整体页面右下方) ========================================================= */ function TodayOverview({ d }) { /* 园区运行状态 —— 内联在标题右侧,不作为独立卡片 */ const status = [ { icon: 'gauge', label: '设备在线率', value: `${d.park.devices}%`, color: '#17b26a' }, { icon: 'flame', label: '能耗', value: `${d.park.energy} kWh`, color: '#2f6bff' }, { icon: 'users', label: '人流量', value: `${d.park.people} 人`, color: '#f79009' }, { icon: 'briefcase', label: '工位', value: `${d.park.desk}%`, color: '#8b5cf6' }, { icon: 'radio', label: '会议室', value: `${d.park.meeting}/6`, color: '#f04438' }, { icon: 'cpu', label: '云桌面', value: `${Math.round(d.park.people * 0.42)} 个`, color: '#0ea5e9' }, ]; return (
园区今日概览
{status.map((s) => ( {s.label} {s.value} ))}
实时刷新 · 2s
{/* 五个指标卡片:必须一行展示 */}
); } /* ---------- 园区概览页脚:使用共享组件(与媒体播放页完全一致) ---------- */ /* ========================================================= 中部轮换大屏(一次展示一个面板,自动轮换,降低信息密度) ========================================================= */ /* ---------- TOKEN 消耗(大屏版) ---------- */ function TokenBig({ d }) { return (
今日消耗{fmtWan(d.token.today)} tokens 累计消耗{fmtWan(d.token.total)} tokens 实时速率{d.token.rate} t/s
} />
{MODELS.map((m) => (
{m.name}
))}
{MODELS.map((m) => (
{m.value}%
))}
); } /* ---------- 工具调用(大屏版) ---------- */ function ToolsBig({ d }) { return (
今日调用{fmtInt(d.tools.today)} 次 累计调用{(d.tools.total / 10000).toFixed(1)}万 次 成功率{d.tools.success}% 服务节点12 / 12 在线
} />
今日调用{fmtInt(d.tools.today)}
累计调用{(d.tools.total / 10000).toFixed(1)}万次
成功率{d.tools.success}%
AI 服务节点12/12 在线
热门工具调用占比
); } /* ---------- 入园项目(组视图 · 中密度) ---------- */ function ProjectsPanel({ d }) { return (
在园{d.projects.inPark} 家 累计孵化{d.projects.cum} 家 今日新增{d.projects.todayNew} 家 {/* 高企 / 研发32 / 18 */}
} />
{ZONE_DIST.map((z) => (
{z.name} {z.value} 家
))}
入驻企业名录 (39 家)
{[...COMPANY_NAMES, ...COMPANY_NAMES].map((c, i) => (
{c}
))}
); } /* ---------- 带动就业(组视图 · 中密度) ---------- */ function JobsPanel({ d }) { return (
累计{fmtInt(d.jobs.total)} 人 毕业生1,680 人 实习506 人 {/* 今日新增{d.jobs.todayNew} 人 */}
} />
行业就业分布
{INDUSTRY.slice(0, 6).map((it, i) => { const max = Math.max(...INDUSTRY.map((x) => x.value)); return (
{it.value} {it.name}
); })}
); } /* ---------- 营收总数(组视图 · 中密度) ---------- */ function RevenuePanel({ d }) { return (
累计营收{fmtWan(d.revenue.total)} 元 今日营收+{d.revenue.today} 万 企业估值5.2 亿元 环比+{d.revenue.growth}%
} />
大赛奖项 47 重点研发 18 高新技术企业 32
); } /* ---------- 中部轮换器:五项面板自由组织成两组,组间轮换 ---------- */ const ROTATE_MS = 10000; function MiddleRotator({ d }) { const views = [ { key: 'ai', label: 'OPC 智能资源', icon: 'cpu', node: (
), }, { key: 'park', label: '园区经营概况', icon: 'building', node: (
), }, ]; 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 (
{views.map((p, i) => ( ))}
{views[idx].node}
); } /* ---------- 实时动态(纯列表,外层自行包裹卡片) ---------- */ function LiveFeed({ feed, limit = 7 }) { return (
{feed.slice(0, limit).map((e) => (
{e.text} {e.time}
))}
); } /* ========================================================= 页面 ========================================================= */ export default function DataScreen() { const d = useParkSim(); return (
{/* 园区今日概览(五个指标一行 + 标题右侧运行状态) */} {/* 中部大屏:五项内容自动轮换展示(降低信息密度) */} {/* 园区实时动态(底部整行 · 园区概览条上方 · 内容三列) */}
● LIVE} />
{/* 园区概览(保持在最底部) */}
); }