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 (