feat: 数据真实化、企业展示墙、全局手势识别与 3D 模型完善
- 数据大屏改用真实数据(xls 统计表 + 宣传册口径);TOKEN/工具调用基于真实锚点测算 - 新增企业展示墙页(/wall,39 家全量档案 + MQTT/admin/ESP32 控制文档) - 新增全局手势识别(GlobalVision 全局挂载 + dpm:gesture 事件分发) - 3D 模型墙面逐间修复、去除门/过道顶棚/调试标签、房间资产不穿墙、会议室多小桌 - 侧栏企业分布扩展至 39 家完整信息,分区按官方四大孵化区域 - 语音页多轮对话展示优化、去除页脚与检测横条、背景对齐
This commit is contained in:
+188
-102
@@ -1,138 +1,224 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""园区数据模拟引擎 —— 由前端 parkData.js 移植,后端统一产生数据
|
||||
前端通过 GET /api/dashboard/snapshot 获取,或订阅 MQTT opc/dashboard/tick
|
||||
"""园区数据引擎(后端)—— 真实数据 + 测算演示
|
||||
数据来源(用户指定口径):
|
||||
· 《昆明市大学生创业园运行情况统计表(2026年7月).xls》(官方月报)
|
||||
· 《昆明市创业园宣传册.pdf》
|
||||
测算口径(用户确认):
|
||||
· TOKEN:真实业务锚点月均 200 亿 tokens → 日均 ≈ 6.67 亿(200亿/30),
|
||||
实时速率 ≈ 7,700 t/s(日均/86400s),近30日累计 = 200 亿
|
||||
· 工具调用:按平均每次调用消耗约 2 万 tokens 折算(今日 ≈ 3.3 万次)
|
||||
其余统计指标为真实静态值;TOKEN/工具仅做轻微演示波动
|
||||
"""
|
||||
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
|
||||
MODEL_NAMES = ["DeepSeek-V3", "通义千问", "智谱 GLM-4", "豆包", "讯飞星火"]
|
||||
TOOL_NAMES = ["文档生成", "数据查询", "图像创作", "代码执行", "语音合成"]
|
||||
COMPANY_NAMES = [
|
||||
"云南派音人工智能科技", "米勒克尔蓝宝石珠宝", "中泰研学合作", "云南宸中低空经济",
|
||||
"昆明智海银高文化科技", "云南廷秀文旅康养", "瀚颖AI+教育信息咨询",
|
||||
"仰光客厅", "云南上古绝学文化", "中越生物医疗", "酷享野农AI农业",
|
||||
"滇缅国际设计", "昆明舒诺生物科技", "达岸教育管理", "花仙子园艺肥料",
|
||||
"研X同行者网络", "鬼才明AI创意工作室", "朵哈·玫瑰特色产业链", "昆明云韵体育",
|
||||
"南菌优培食用菌", "五华区丽裳文化", "云南星瑞航空", "综合直播私域平台",
|
||||
"昆明屿澈电商", "蓝智科技", "云品出滇·纸享万家", "启元人工智能科技",
|
||||
# ---------- 真实锚点(仅来自 xls 统计表 + 宣传册) ----------
|
||||
REAL = {
|
||||
"as_of": "2026-07",
|
||||
"token_monthly_yi": 200, # 月均消耗 200 亿 tokens(真实业务锚点)
|
||||
"token_daily_wan": 66666.7, # 日均 6.67 亿 tokens(200亿/30 ≈ 66666.7 万)
|
||||
"token_rate": 7700, # 实时 t/s(6.67e8 / 86400 ≈ 7,716)
|
||||
"tools_per_call_tokens": 150000, # 平均每次工具调用消耗 tokens(真实测算:12.5M输入 ÷ 81步 ≈ 15.4万)
|
||||
"occupied": 39, # 园区实际入驻企业数(信息表名单 39 家,统一口径)
|
||||
"capacity": 49, # 园区可容纳企业个数(统计表)
|
||||
"invested": 560, # 开园以来累计投入运营资金(万元,统计表)
|
||||
"jobs": 213, # 入驻企业累计带动就业人数(统计表)
|
||||
"revenue_total": 252.15, # 入驻企业累计生产经营总额(万元,统计表)
|
||||
"revenue_tax": 1.03, # 入驻企业累计上缴税利总额(万元,统计表)
|
||||
"area": 3000, # 园区建筑面积(㎡,统计表+宣传册)
|
||||
"founded": 2009, # 成立年份(宣传册)
|
||||
"province_level": 2012, # 获评省级创业园年份(宣传册)
|
||||
"address": "昆明市民航路229号(人力资源中心1-2楼)",
|
||||
"phone": "18087174891",
|
||||
"email": "714066050@qq.com",
|
||||
}
|
||||
|
||||
# 入驻企业名录(《入驻企业信息表》39 家全称,与在园数统一口径)
|
||||
COMPANIES = [
|
||||
"云南派音人工智能科技有限公司", "米勒克尔蓝宝石珠宝产业化(项目)", "中泰研学合作(项目)",
|
||||
"云南宸中低空经济有限公司", "昆明智海银高文化科技有限公司", "人工智能机器人大模型训练(项目)",
|
||||
"云南廷秀文旅康养服务(项目)", "瀚颖AI+教育信息咨询(项目)", "云南大学AI+联合创业服务平台(项目)",
|
||||
"仰光客厅(项目)", "云南上古绝学文化发展有限公司", "千楣之路(项目)",
|
||||
"中越生物医疗(项目)", "中越文旅交流(项目)", "绿态环保科技项目",
|
||||
"联翼航空技术低空经济应用国际化拓展(项目)", "酷享野农AI农业(项目)", "滇缅国际设计(项目)",
|
||||
"昆明舒诺生物科技有限责任公司", "达岸教育管理(云南)有限公司", "昆明滇油石化有限责任公司",
|
||||
"花仙子园艺肥料(云南)有限公司", "Facebook越南市场普洱茶与建水紫陶壶跨境电商(项目)",
|
||||
"研X——同行者网络(项目)", "鬼才明AI创意工作室(项目)", "朵哈·玫瑰特色产业链(项目)",
|
||||
"昆明云韵体育有限责任公司", "昆明舒护安养老服务有限责任公司",
|
||||
"南菌优培—食用菌专用生物制剂创新与高原特色野生菌高值化全链开发(项目)",
|
||||
"五华区丽裳文化艺术工作室", "云南星瑞航空(项目)", "综合性云南直播私域服务平台",
|
||||
"昆明屿澈电子商务有限公司", "中外青少年研学(项目)", "星杭商务信息咨询昆明有限责任公司",
|
||||
"蓝智科技(项目)", "云品出滇·纸享万家(项目)",
|
||||
"云迹轻旅—昆明本土一站式轻量化文旅服务工作室(项目)", "启元人工智能科技(项目)",
|
||||
]
|
||||
|
||||
# 宣传册四大孵化区域
|
||||
ZONES = [
|
||||
{"name": "OPC创业空间", "desc": "AI 技术支撑 · 数字经济/AI应用/软件开发/文创设计/电商直播等轻资产领域", "period": "拎包入驻 · 最长2年"},
|
||||
{"name": "创业苗圃区", "desc": "创意阶段 · 未注册企业团队 · 开放式共享工位", "period": "最长2年"},
|
||||
{"name": "孵化加速区", "desc": "已工商注册初创企业 · 独立办公空间", "period": "基础2年 · 可延1年(≤3年)"},
|
||||
{"name": "国际创客区", "desc": "归国留学生 · 外籍留学生 · 国际化配套服务", "period": "参照加速区"},
|
||||
]
|
||||
|
||||
# 园区企业行业分布(按《入驻企业信息表》39 家企业简介分类统计)
|
||||
INDUSTRY_MIX = [
|
||||
{"name": "人工智能", "value": 7},
|
||||
{"name": "跨境电商", "value": 5},
|
||||
{"name": "教育培训", "value": 4},
|
||||
{"name": "文旅康养", "value": 4},
|
||||
{"name": "文化创意", "value": 4},
|
||||
{"name": "生物医药", "value": 4},
|
||||
{"name": "低空经济", "value": 3},
|
||||
{"name": "现代农业", "value": 3},
|
||||
{"name": "绿色环保", "value": 2},
|
||||
{"name": "企业服务", "value": 2},
|
||||
{"name": "新材料", "value": 1},
|
||||
]
|
||||
|
||||
# 园区实时动态(真实事件:园区官方公示 + 宣传册活动 + 互联网公开信息,最新在前)
|
||||
FEED = [
|
||||
{"id": 24, "icon": "dna", "text": "园内企业「昆明舒诺生物科技」完成GEO服务体系建设(生成式引擎优化,聚焦宠物营养品牌数字化)", "time": "2026-08"},
|
||||
{"id": 1, "icon": "activity", "text": "2026年7月运行情况统计表填报完成:累计投入运营资金560万元、带动就业213人", "time": "2026-07-31"},
|
||||
{"id": 2, "icon": "radio", "text": "2026年第三期入驻创业企业(项目)评审结果在昆明市人社局官网公示", "time": "2026-07-02"},
|
||||
{"id": 3, "icon": "graduation", "text": "云南师范大学就业创业实践基地落地园区 · 云南旅游职业学院开展就业创业交流", "time": "2026-06-03"},
|
||||
{"id": 23, "icon": "radio", "text": "「PineSound」完成 ICP 备案(滇ICP备2026008517号)与公安备案,注册地址昆明市民航路301-307号", "time": "2026"},
|
||||
{"id": 4, "icon": "rocket", "text": "\"滇水逐梦·雨林创航\"昆明·西双版纳创业路演PK赛成功举办", "time": "2026-05-20"},
|
||||
{"id": 5, "icon": "flag", "text": "昆明市青年企业家协会创新创业基地在园区揭牌", "time": "2026-05-13"},
|
||||
{"id": 6, "icon": "users", "text": "\"聚力赋能·共创未来\"高校创新创业交流暨项目路演活动圆满举行", "time": "2026-05-08"},
|
||||
{"id": 7, "icon": "trophy", "text": "\"创赢未来\"2026创业大赛昆明选拔赛暨马兰花创业培训讲师大赛举行(13个项目参赛)", "time": "2026-04-22"},
|
||||
{"id": 8, "icon": "broadcast", "text": "2026年第二期创业企业(项目)入驻招募公告发布(官网 www.kmyc.gov.cn)", "time": "2026-04-15"},
|
||||
{"id": 21, "icon": "sparkles", "text": "「PineSound」AI驱动音频管理平台上线 pinesound.cn:智能配乐与音效生成,集成100W+音效库、50W+配乐库,全球版权授权(官网)", "time": "2026"},
|
||||
{"id": 22, "icon": "medal", "text": "「PineSound」发布企业标准 Q/YNPY 001-2026《数字内容创作 配乐通用分类标准》", "time": "2026"},
|
||||
{"id": 9, "icon": "award", "text": "2026年第一期入驻评审结果在昆明市人社局官网公示(23个优质项目正式入驻)", "time": "2026-03-27"},
|
||||
{"id": 10, "icon": "users", "text": "2026年第一期招募集中评审完成:38个申请,23个优质项目正式入驻", "time": "2026-03-24"},
|
||||
{"id": 19, "icon": "sparkles", "text": "园内企业「云南派音人工智能科技(PineSound)」专注多模态音频技术研发:自研Pine系列模型覆盖音频识别、向量嵌入、音效生成、配乐创作", "time": "2026-04"},
|
||||
{"id": 20, "icon": "bolt", "text": "「PineSound」2026年4月注册成立,获北京投资支持并吸纳就业4人,入驻加速区(入驻企业信息表)", "time": "2026-04"},
|
||||
{"id": 11, "icon": "cpu", "text": "云南首个人工智能OPC创新人才基地落地昆明,填补省内个体AI创业培育空白(公开报道)", "time": "2026-03-23"},
|
||||
{"id": 12, "icon": "medal", "text": "第九届\"春城创业荟\"创业创新大赛圆满闭幕,获奖项目名单公布", "time": "2025-09-30"},
|
||||
{"id": 13, "icon": "trophy", "text": "\"春城创业荟\"初赛落幕,147个项目晋级复赛", "time": "2025-04-28"},
|
||||
{"id": 14, "icon": "building", "text": "园内企业「花仙子园艺肥料(云南)」成立,注册资本118万元(公开报道)", "time": "2025-04-28"},
|
||||
{"id": 15, "icon": "radio", "text": "2025年入园项目评审结果公示(昆明市人社局官网)", "time": "2025-04-22"},
|
||||
{"id": 16, "icon": "atom", "text": "研X平台入选\"创客中国\"项目库(一站式研究生学术成长平台)", "time": "2025-01"},
|
||||
{"id": 17, "icon": "leaf", "text": "行业动态:园内「朵哈·玫瑰」原料产地昆明八街食用玫瑰行情上涨,玫瑰经济走强", "time": "2025-01"},
|
||||
{"id": 18, "icon": "heart", "text": "行业动态:「千楣之路」主营檀娜卡——泰国天然护肤品牌Thanaka正式进入中国市场", "time": "2025-01"},
|
||||
]
|
||||
|
||||
|
||||
def _pick(arr):
|
||||
return arr[random.randrange(len(arr))]
|
||||
def _day_secs():
|
||||
"""当日零点起已过秒数(时钟驱动,保证数值单向持续增长、重启连续)"""
|
||||
now = time.time()
|
||||
lt = time.localtime(now)
|
||||
day_start = time.mktime((lt.tm_year, lt.tm_mon, lt.tm_mday, 0, 0, 0, 0, 0, -1))
|
||||
return max(0.0, now - day_start)
|
||||
|
||||
|
||||
def _now_time():
|
||||
return time.strftime("%H:%M:%S")
|
||||
def _today_values(prev_noise=None):
|
||||
"""按真实速率推算当日累计值(单向增长 + 工具调用波动):
|
||||
平台实时速率 = 单会话 157 tok/s × 49 路并发 ≈ 7,700 t/s
|
||||
今日 TOKEN(万)= 秒数 × 7,700 / 10000 = 秒数 × 0.77
|
||||
今日工具调用 = 今日 TOKEN / 15万 tokens 每次(真实基线)
|
||||
+ 随机游走噪声(±10,波浪式波动,不单调)
|
||||
"""
|
||||
secs = _day_secs()
|
||||
today_wan = round(secs * 0.77) # 万 tokens
|
||||
base_tools = secs * 0.77 * 10000 / REAL["tools_per_call_tokens"] # 真实基线(浮点)
|
||||
if prev_noise is None:
|
||||
noise = 0.0
|
||||
else:
|
||||
noise = max(-10.0, min(10.0, prev_noise + (random.random() - 0.5) * 6.0))
|
||||
today_tools = max(0, round(base_tools + noise)) # 次
|
||||
return today_wan, today_tools, noise
|
||||
|
||||
|
||||
def make_series(base, growth, noise, n=30):
|
||||
def _make_series(base, noise, n=30):
|
||||
"""生成围绕日均锚点的演示曲线(单位:万 tokens),尾点=今日实时值"""
|
||||
out = []
|
||||
v = base
|
||||
for _ in range(n):
|
||||
v = v * (1 + growth) + (random.random() - 0.5) * noise
|
||||
out.append(round(max(1, v * 100) / 100, 2))
|
||||
for _ in range(n - 1):
|
||||
v = base + (random.random() - 0.5) * noise
|
||||
out.append(round(max(1, v), 1))
|
||||
out.append(round(base, 1))
|
||||
return out
|
||||
|
||||
|
||||
def gen_event():
|
||||
def _live_values(prev=None):
|
||||
"""TOKEN 面板实时运行指标(围绕真实基准动态波动,会话持续推进)"""
|
||||
r = random.random
|
||||
pool = [
|
||||
{"icon": "bolt", "text": f"AI 推理任务完成 · 消耗 {round(800 + r() * 9000):,} tokens({_pick(MODEL_NAMES)})"},
|
||||
{"icon": "wrench", "text": f"工具「{_pick(TOOL_NAMES)}」被调用 {round(10 + r() * 90)} 次"},
|
||||
{"icon": "building", "text": f"「{_pick(COMPANY_NAMES)}」提交入驻申请 · 进入评审流程"},
|
||||
{"icon": "users", "text": f"「{_pick(COMPANY_NAMES)}」新增招聘岗位 {round(1 + r() * 5)} 个"},
|
||||
{"icon": "coin", "text": f"园区企业完成一笔 ¥{(0.5 + r() * 9):.1f}万 交易"},
|
||||
{"icon": "robot", "text": f"「{_pick(MODEL_NAMES)}」模型完成一次微调任务"},
|
||||
]
|
||||
e = _pick(pool)
|
||||
return {"id": f"{int(time.time()*1000)}-{random.random()}", "icon": e["icon"], "text": e["text"], "time": _now_time()}
|
||||
|
||||
|
||||
def init_feed():
|
||||
return [
|
||||
{"id": 1, "icon": "bolt", "text": "云南派音AI 完成音频向量嵌入任务 · 消耗 12,480 tokens", "time": _now_time()},
|
||||
{"id": 2, "icon": "building", "text": "「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间", "time": _now_time()},
|
||||
{"id": 3, "icon": "users", "text": "「昆明舒护安养老服务」新增招聘岗位 2 个", "time": _now_time()},
|
||||
{"id": 4, "icon": "coin", "text": "园区企业完成一笔 ¥3.6万 交易", "time": _now_time()},
|
||||
]
|
||||
steps = (prev or {}).get("steps", 81)
|
||||
steps += 1 if r() < 0.3 else 0
|
||||
return {
|
||||
"firstToken": round(1.3 + (r() - 0.5) * 0.2, 2), # 首 token 平均 1.3s 基准 ±0.1
|
||||
"throughput": round(157 + (r() - 0.5) * 14), # 吞吐 157 tok/s 基准 ±7
|
||||
"cacheHit": round(98 + (r() - 0.5) * 1.2, 1), # 缓存命中 98% 基准 ±0.6
|
||||
"inputM": round((prev or {}).get("inputM", 12.5) + 0.004 + r() * 0.008, 2), # 会话输入持续推进
|
||||
"outputM": round((prev or {}).get("outputM", 2.0) + 0.001 + r() * 0.002, 2), # 会话输出持续推进
|
||||
"rounds": max(2, steps // 40), # 每 40 步一轮
|
||||
"steps": steps, # 步数持续增长
|
||||
}
|
||||
|
||||
|
||||
def init_snapshot():
|
||||
today_wan, today_tools, noise = _today_values()
|
||||
return {
|
||||
"t": 0,
|
||||
"token": {"today": 128.64, "total": 12840, "rate": 84.6, "series": make_series(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": make_series(30, 0.006, 4)},
|
||||
"park": {"devices": 98.6, "energy": 386, "people": 127, "desk": 76, "meeting": 3, "nodes": 12},
|
||||
"feed": init_feed(),
|
||||
"as_of": REAL["as_of"],
|
||||
"_toolsNoise": noise,
|
||||
"live": _live_values(),
|
||||
"token": {
|
||||
"today": today_wan, # 万 tokens(当日累计,实时增长)
|
||||
"total": REAL["token_monthly_yi"] * 10000 + today_wan, # 万 tokens(近30日 = 200亿 + 今日增量)
|
||||
"rate": REAL["token_rate"], # t/s(157 tok/s × 49 并发 ≈ 7,700)
|
||||
"series": _make_series(REAL["token_daily_wan"], REAL["token_daily_wan"] * 0.16),
|
||||
},
|
||||
"tools": {
|
||||
"today": today_tools, # 次(随 TOKEN 实时联动)
|
||||
"total": round(REAL["token_monthly_yi"] * 100000000 / REAL["tools_per_call_tokens"]) + today_tools, # 次
|
||||
"success": 98.5, # 成功率(演示)
|
||||
"nodes": 12, # AI 服务节点(演示)
|
||||
},
|
||||
"projects": {
|
||||
"inPark": REAL["occupied"], # 实际入驻 39 家
|
||||
"capacity": REAL["capacity"], # 可容纳 49 个
|
||||
"invested": REAL["invested"], # 累计投入 560 万元
|
||||
},
|
||||
"jobs": {"total": REAL["jobs"]}, # 带动就业 213 人
|
||||
"revenue": {"total": REAL["revenue_total"], "tax": REAL["revenue_tax"]}, # 万元
|
||||
"park": {
|
||||
"area": REAL["area"],
|
||||
"founded": REAL["founded"],
|
||||
"provinceLevel": REAL["province_level"],
|
||||
"address": REAL["address"],
|
||||
"phone": REAL["phone"],
|
||||
"email": REAL["email"],
|
||||
},
|
||||
"zones": ZONES,
|
||||
"companies": COMPANIES,
|
||||
"industryMix": INDUSTRY_MIX,
|
||||
"feed": FEED,
|
||||
}
|
||||
|
||||
|
||||
def next_snapshot(s):
|
||||
r = random.random
|
||||
t_delta = round(0.26 + r() * 0.34, 2)
|
||||
|
||||
"""时钟驱动:按真实速率重算当日累计(TOKEN 单向增长,工具调用波浪波动),其余真实静态"""
|
||||
today_wan, today_tools, noise = _today_values(s.get("_toolsNoise"))
|
||||
token = {
|
||||
"today": round(s["token"]["today"] + t_delta, 2),
|
||||
"total": round(s["token"]["total"] + t_delta, 2),
|
||||
"rate": round(76 + r() * 20, 1),
|
||||
"today": today_wan,
|
||||
"total": REAL["token_monthly_yi"] * 10000 + today_wan,
|
||||
"rate": REAL["token_rate"],
|
||||
"series": s["token"]["series"][:-1] + [today_wan],
|
||||
}
|
||||
tool_delta = round(13 + r() * 22)
|
||||
tools = {
|
||||
"today": s["tools"]["today"] + tool_delta,
|
||||
"total": s["tools"]["total"] + tool_delta,
|
||||
"success": round(98.1 + r() * 1.2, 1),
|
||||
}
|
||||
rev_delta = round(0.6 + r() * 1.7, 1)
|
||||
revenue = {
|
||||
"today": round(s["revenue"]["today"] + rev_delta, 1),
|
||||
"total": round(s["revenue"]["total"] + rev_delta, 1),
|
||||
"growth": round(7.2 + r() * 2.2, 1),
|
||||
}
|
||||
park = {
|
||||
"devices": round(97.6 + r() * 1.6, 1),
|
||||
"energy": round(320 + r() * 130),
|
||||
"people": round(80 + r() * 95),
|
||||
"desk": round(62 + r() * 24),
|
||||
"meeting": round(2 + r() * 4),
|
||||
"today": today_tools,
|
||||
"total": round(REAL["token_monthly_yi"] * 100000000 / REAL["tools_per_call_tokens"]) + today_tools,
|
||||
"success": 98.5,
|
||||
"nodes": 12,
|
||||
}
|
||||
|
||||
projects = dict(s["projects"])
|
||||
if r() < 0.055:
|
||||
projects["todayNew"] += 1
|
||||
if r() < 0.035:
|
||||
projects["inPark"] += 1
|
||||
projects["cum"] += 1
|
||||
jobs = dict(s["jobs"])
|
||||
if r() < 0.08:
|
||||
jobs["todayNew"] += 1
|
||||
if r() < 0.05:
|
||||
jobs["total"] += 1
|
||||
|
||||
t_series = list(s["token"]["series"])
|
||||
t_series[-1] = round(t_series[-1] + t_delta, 2)
|
||||
if s["t"] % 12 == 11:
|
||||
t_series = t_series[1:] + [round(72 + r() * 30, 2)]
|
||||
token["series"] = t_series
|
||||
|
||||
r_series = list(s["revenue"]["series"])
|
||||
r_series[-1] = round(r_series[-1] + rev_delta, 1)
|
||||
if s["t"] % 12 == 11:
|
||||
r_series = r_series[1:] + [round(26 + r() * 9, 1)]
|
||||
revenue["series"] = r_series
|
||||
|
||||
feed = [gen_event()] + s["feed"][:6] if s["t"] % 3 == 2 else s["feed"]
|
||||
|
||||
return {
|
||||
"t": s["t"] + 1,
|
||||
"token": token, "tools": tools, "revenue": revenue,
|
||||
"park": park, "projects": projects, "jobs": jobs, "feed": feed,
|
||||
**s,
|
||||
"_toolsNoise": noise,
|
||||
"live": _live_values(s.get("live")),
|
||||
"token": token,
|
||||
"tools": tools,
|
||||
}
|
||||
|
||||
|
||||
|
||||
+68
-19
@@ -34,12 +34,17 @@ KP_LEFT_ELBOW = 7
|
||||
KP_RIGHT_ELBOW = 8
|
||||
KP_LEFT_WRIST = 9
|
||||
KP_RIGHT_WRIST = 10
|
||||
RAISE_LIFT = 0.12 # 举手:手腕高于对应肩 12% 画面高
|
||||
RAISE_LIFT = 0.06 # 举手:手腕高于对应肩 6% 画面高(降低阈值,更易稳定激活)
|
||||
FIST_ELBOW_LIFT = 0.05 # 举拳:手腕高于肘 5% 画面高(前臂上举)
|
||||
FIST_SHOULDER_GAP = 0.15 # 举拳:拳不高于肩 15% 画面高(收在胸前)
|
||||
FIST_CHEST_DIST = 0.25 # 举拳:拳与肩水平距离 < 25% 画面宽(贴近躯干)
|
||||
KP_CONF_MIN = 0.3 # 关键点置信度过滤
|
||||
|
||||
# ── 扩展手势几何阈值(基于 COCO 17 关键点) ──
|
||||
POINT_REACH_X = 0.30 # 指向:手腕水平伸出距肩 ≥30% 画面宽(手臂向前/侧伸)
|
||||
POINT_Y_RANGE = 0.28 # 指向:手腕与肩同高 ±28% 画面高(排除高举/下垂)
|
||||
HANDS_CLOSE_DIST = 0.18 # 双手合十/靠近:双腕欧氏距离 <18% 画面宽
|
||||
|
||||
_face_model = None
|
||||
_pose_model = None
|
||||
|
||||
@@ -64,15 +69,22 @@ def get_pose_model():
|
||||
return _pose_model
|
||||
|
||||
|
||||
def _parse_pose(results):
|
||||
"""解析姿态结果 → persons(17 关键点归一化 [x,y,conf])+ raised(举手)+ fists(举拳)"""
|
||||
def _parse_pose(results, W, H):
|
||||
"""解析姿态结果 → persons + raised(举手) + fists(举拳)
|
||||
+ both_up(双臂举起) + pointing(指向) + hands_close(双手合十)
|
||||
+ hands(每手几何摘要,供前端挥手/手势跟随时序判定)
|
||||
注意:ultralytics keypoints.data 为【原图像素坐标】,
|
||||
此处统一归一化为 [0,1](x/W, y/H)后再判定阈值与输出
|
||||
"""
|
||||
persons = []
|
||||
raised = []
|
||||
fists = []
|
||||
pointing = []
|
||||
hands = []
|
||||
for r in results:
|
||||
if r.keypoints is None:
|
||||
continue
|
||||
kps = r.keypoints.data # [N,17,3] 归一化坐标
|
||||
kps = r.keypoints.data # [N,17,3] 像素坐标
|
||||
for i in range(kps.shape[0]):
|
||||
kp = kps[i]
|
||||
person = [
|
||||
@@ -81,6 +93,7 @@ def _parse_pose(results):
|
||||
]
|
||||
persons.append(person)
|
||||
# 每只手臂:举手(腕明显高于肩)或 举拳(前臂上举、拳收胸前),两者互斥
|
||||
arm = {}
|
||||
for side, wrist_i, elbow_i, shoulder_i in (
|
||||
("left", KP_LEFT_WRIST, KP_LEFT_ELBOW, KP_LEFT_SHOULDER),
|
||||
("right", KP_RIGHT_WRIST, KP_RIGHT_ELBOW, KP_RIGHT_SHOULDER),
|
||||
@@ -90,22 +103,53 @@ def _parse_pose(results):
|
||||
s = kp[shoulder_i]
|
||||
if float(w[2]) < KP_CONF_MIN or float(s[2]) < KP_CONF_MIN or float(e[2]) < KP_CONF_MIN:
|
||||
continue
|
||||
wx, wy = float(w[0]), float(w[1])
|
||||
sx, sy = float(s[0]), float(s[1])
|
||||
ey = float(e[1])
|
||||
if wy < sy - RAISE_LIFT:
|
||||
raised.append({"side": side, "x": round(wx, 3), "y": round(wy, 3)})
|
||||
elif (
|
||||
wy < ey - FIST_ELBOW_LIFT # 前臂上举(腕高于肘)
|
||||
and wy >= sy - FIST_SHOULDER_GAP # 拳不高过肩太多(收在胸前)
|
||||
and abs(wx - sx) < FIST_CHEST_DIST # 拳贴近躯干中线
|
||||
# 像素 → 归一化 [0,1]
|
||||
wx, wy = float(w[0]) / W, float(w[1]) / H
|
||||
sx, sy = float(s[0]) / W, float(s[1]) / H
|
||||
ey = float(e[1]) / H
|
||||
is_raised = wy < sy - RAISE_LIFT
|
||||
is_fist = (
|
||||
wy < ey - FIST_ELBOW_LIFT
|
||||
and wy >= sy - FIST_SHOULDER_GAP
|
||||
and abs(wx - sx) < FIST_CHEST_DIST
|
||||
)
|
||||
if is_raised:
|
||||
raised.append({"side": side, "x": round(wx, 4), "y": round(wy, 4)})
|
||||
elif is_fist:
|
||||
fists.append({"side": side, "x": round(wx, 4), "y": round(wy, 4)})
|
||||
# 指向:手腕水平伸出距肩较远、且与肩同高区间(前伸/侧伸,排除高举与下垂)
|
||||
if (
|
||||
abs(wx - sx) > POINT_REACH_X
|
||||
and abs(wy - sy) < POINT_Y_RANGE
|
||||
):
|
||||
fists.append({"side": side, "x": round(wx, 3), "y": round(wy, 3)})
|
||||
return persons, raised, fists
|
||||
pointing.append({"side": side, "x": round(wx, 4), "y": round(wy, 4)})
|
||||
arm[side] = {
|
||||
"side": side,
|
||||
"wx": round(wx, 4), "wy": round(wy, 4),
|
||||
"sx": round(sx, 4), "sy": round(sy, 4),
|
||||
"raised": is_raised, "fist": is_fist,
|
||||
}
|
||||
if arm:
|
||||
hands.append(arm)
|
||||
# 双臂举起:左、右腕都高于各自肩
|
||||
both_up = False
|
||||
if len(hands) >= 1:
|
||||
h0 = hands[0]
|
||||
if "left" in h0 and "right" in h0:
|
||||
both_up = h0["left"]["raised"] and h0["right"]["raised"]
|
||||
# 双手合十/靠近:同一人的双腕欧氏距离 < 阈值
|
||||
hands_close = False
|
||||
if len(hands) >= 1:
|
||||
h0 = hands[0]
|
||||
if "left" in h0 and "right" in h0:
|
||||
dx = h0["left"]["wx"] - h0["right"]["wx"]
|
||||
dy = h0["left"]["wy"] - h0["right"]["wy"]
|
||||
hands_close = (dx * dx + dy * dy) ** 0.5 < HANDS_CLOSE_DIST
|
||||
return persons, raised, fists, both_up, pointing, hands_close, hands
|
||||
|
||||
|
||||
def predict_jpeg(jpeg_bytes: bytes):
|
||||
"""人脸检测 + 姿态估计 → {faces, boxes, pose, raised, latency_ms}"""
|
||||
"""人脸检测 + 姿态估计 → {faces, boxes, pose, raised, fists, both_up, pointing, hands_close, hands, latency_ms}"""
|
||||
t0 = time.time()
|
||||
img = Image.open(io.BytesIO(jpeg_bytes)).convert("RGB")
|
||||
|
||||
@@ -121,13 +165,14 @@ def predict_jpeg(jpeg_bytes: bytes):
|
||||
boxes.append({"box": xyxy, "conf": conf})
|
||||
|
||||
# 姿态
|
||||
W, H = img.size # 原图尺寸(关键点像素坐标 → 归一化基准)
|
||||
pose_res = get_pose_model().predict(img, conf=CONF_THRESHOLD, imgsz=IMGSZ, verbose=False, device="cpu")
|
||||
persons, raised, fists = _parse_pose(pose_res)
|
||||
persons, raised, fists, both_up, pointing, hands_close, hands = _parse_pose(pose_res, W, H)
|
||||
|
||||
latency_ms = round((time.time() - t0) * 1000, 1)
|
||||
log.info(
|
||||
"vision: 人脸 %d 姿态 %d 举手 %d 举拳 %d · %.0fms",
|
||||
len(boxes), len(persons), len(raised), len(fists), latency_ms,
|
||||
"vision: 人脸 %d 姿态 %d 举手 %d 举拳 %d 指向 %d 合十 %s · %.0fms",
|
||||
len(boxes), len(persons), len(raised), len(fists), len(pointing), hands_close, latency_ms,
|
||||
)
|
||||
return {
|
||||
"faces": len(boxes),
|
||||
@@ -135,6 +180,10 @@ def predict_jpeg(jpeg_bytes: bytes):
|
||||
"pose": persons,
|
||||
"raised": raised,
|
||||
"fists": fists,
|
||||
"both_up": both_up,
|
||||
"pointing": pointing,
|
||||
"hands_close": hands_close,
|
||||
"hands": hands,
|
||||
"latency_ms": latency_ms,
|
||||
}
|
||||
|
||||
|
||||
@@ -130,11 +130,26 @@
|
||||
<div class="mqtt-group-title">页面导航</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"navigate","params":{"page":"/voice"}}'>语音对话</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"navigate","params":{"page":"/wall"}}'>企业展示墙</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"navigate_rel","params":{"delta":-1}}'>← 向左</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"navigate_rel","params":{"delta":1}}'>向右 →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 企业展示墙 -->
|
||||
<div class="mqtt-group">
|
||||
<div class="mqtt-group-title">企业展示墙(/wall)</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"wall_pause","params":{}}'>暂停滚动</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"wall_resume","params":{}}'>继续滚动</button>
|
||||
</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"wall_speed","params":{"speed":"slow"}}'>慢速</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"wall_speed","params":{"speed":"normal"}}'>标准</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"wall_speed","params":{"speed":"fast"}}'>快速</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视觉识别 -->
|
||||
<div class="mqtt-group">
|
||||
<div class="mqtt-group-title">人物识别(全局)</div>
|
||||
|
||||
@@ -164,6 +164,30 @@ void reconnect() {
|
||||
|
||||
其他可用指令(自定义映射):`ai_input`(AI 页提问)、`ai_company`(切企业)、`ai_zone`(切分区)、`voice_stop`、`next`/`prev`(媒体)、`alert`(通知)——参数见 `docs/mqtt-commands.md`。
|
||||
|
||||
## 5.1 企业展示墙控制(/wall,39 家入驻企业滚动墙)
|
||||
|
||||
**MQTT 控制消息**(ESP32 解码红外键码后发布到 `opc/display/command`,JSON 载荷):
|
||||
|
||||
| 遥控器按键 | 建议指令 action | params | 效果 | 示例消息 |
|
||||
|---|---|---|---|---|
|
||||
| 1 / 2 / 3 | `wall_speed` | `speed: "slow" \| "normal" \| "fast"` | 慢速 150s / 标准 80s / 快速 40s 一圈 | `{"action":"wall_speed","params":{"speed":"slow"}}` |
|
||||
| CH− | `wall_pause` | `{}` | 暂停滚动 | `{"action":"wall_pause","params":{}}` |
|
||||
| CH+ | `wall_resume` | `{}` | 继续滚动 | `{"action":"wall_resume","params":{}}` |
|
||||
| 电源 | `navigate` | `page: "/wall"` | 切换到企业展示墙 | `{"action":"navigate","params":{"page":"/wall"}}` |
|
||||
|
||||
**ESP32 固件映射示例**(在 §4 固件的 `MAP[]` 键码映射表中追加):
|
||||
|
||||
```cpp
|
||||
// 键码 → MQTT 指令(payload 为 JSON 字符串,经 publishCommand 发布)
|
||||
{ 0x02, R"({"action":"wall_pause","params":{}})" }, // VOL− 暂停墙滚动
|
||||
{ 0x03, R"({"action":"wall_resume","params":{}})" }, // VOL+ 继续墙滚动
|
||||
{ 0x00, R"({"action":"wall_speed","params":{"speed":"fast"}})" }, // CH− 快速
|
||||
{ 0x01, R"({"action":"wall_speed","params":{"speed":"slow"}})" }, // CH+ 慢速
|
||||
{ 0x0B, R"({"action":"navigate","params":{"page":"/wall"}})" }, // 电源 切到展示墙
|
||||
```
|
||||
|
||||
> 前端 `useMqttControl` 收到 `wall_*` 指令后派发 `dpm:wall-control` 事件,展示墙页据此暂停/继续/变速;未在展示墙页时 `wall_*` 指令不产生副作用(安全幂等)。
|
||||
|
||||
## 6. 学习模式(首次必须)
|
||||
|
||||
键码采集固件(只需串口打印,不用连 MQTT):
|
||||
|
||||
+18
-2
@@ -42,7 +42,7 @@
|
||||
|
||||
| action | params | 效果 |
|
||||
|---|---|---|
|
||||
| `navigate` | `page: "/" \| "/twin" \| "/ai" \| "/voice" \| "/screen"`(或别名 home/twin/ai/voice/screen/数据大屏…) | 切换到指定页面 |
|
||||
| `navigate` | `page: "/" \| "/twin" \| "/ai" \| "/voice" \| "/wall" \| "/screen"`(或别名 home/twin/ai/voice/wall/screen/数据大屏/企业展示墙…) | 切换到指定页面 |
|
||||
| `navigate_rel` | `delta: 1 \| -1` | 向右 / 向左循环切换页面 |
|
||||
|
||||
### 4.2 全局视觉识别
|
||||
@@ -79,7 +79,17 @@
|
||||
| `settings_changed` | 设置对象 | 应用设置变更 |
|
||||
| `playlist_changed` | — | 重新加载播放列表 |
|
||||
|
||||
### 4.6 全局
|
||||
### 4.6 企业展示墙页(/wall)
|
||||
|
||||
> 39 家入驻企业信息缓慢滚动展示墙(数据来源:《入驻企业信息表》)
|
||||
|
||||
| action | params | 效果 |
|
||||
|---|---|---|
|
||||
| `wall_pause` | — | 暂停滚动 |
|
||||
| `wall_resume` | — | 继续滚动 |
|
||||
| `wall_speed` | `speed: slow \| normal \| fast` | 滚动速度:慢速 150s / 标准 80s / 快速 40s 一圈 |
|
||||
|
||||
### 4.7 全局
|
||||
|
||||
| action | params | 效果 |
|
||||
|---|---|---|
|
||||
@@ -118,6 +128,12 @@ mosquitto_pub -h 192.168.1.3 -p 1883 -u <用户名> -P <密码> \
|
||||
-t opc/display/command -m '{"action":"voice_start","params":{}}'
|
||||
mosquitto_pub -h 192.168.1.3 -p 1883 -u <用户名> -P <密码> \
|
||||
-t opc/display/command -m '{"action":"next","params":{}}'
|
||||
|
||||
# 切到企业展示墙 / 暂停滚动
|
||||
mosquitto_pub -h 192.168.1.3 -p 1883 -u <用户名> -P <密码> \
|
||||
-t opc/display/command -m '{"action":"navigate","params":{"page":"/wall"}}'
|
||||
mosquitto_pub -h 192.168.1.3 -p 1883 -u <用户名> -P <密码> \
|
||||
-t opc/display/command -m '{"action":"wall_pause","params":{}}'
|
||||
```
|
||||
|
||||
### Python(paho-mqtt,后端 venv 自带)
|
||||
|
||||
@@ -4,6 +4,7 @@ import DataScreen from './pages/DataScreen';
|
||||
import DigitalTwin from './pages/DigitalTwin';
|
||||
import AiAssistant from './pages/AiAssistant';
|
||||
import VoiceAssistant from './pages/VoiceAssistant';
|
||||
import CompanyWall from './pages/CompanyWall';
|
||||
import ScreenLayout from './components/ScreenLayout';
|
||||
import './styles/tokens.css';
|
||||
|
||||
@@ -19,6 +20,8 @@ export default function App() {
|
||||
<Route path="/screen" element={<MediaScreen />} />
|
||||
{/* 实时语音对话页(React 原生,复用页眉) */}
|
||||
<Route path="/voice" element={<VoiceAssistant />} />
|
||||
{/* 入驻企业展示墙 */}
|
||||
<Route path="/wall" element={<CompanyWall />} />
|
||||
</Route>
|
||||
{/* 管理后台已迁移至 FastAPI + Jinja(GET /admin),不由 React 路由接管 */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
|
||||
@@ -245,9 +245,35 @@ function MeetingTable({ x, z, y = 0, rotY = 0, len = 2.6 }) {
|
||||
);
|
||||
}
|
||||
|
||||
/* 讲台(会议室讲台) */
|
||||
function Podium({ x, z, y = 0 }) {
|
||||
/* 会议室小办公桌(小型方桌 + 座椅,多张布置,不穿墙) */
|
||||
function MeetingDesk({ x, z, y = 0, rotY = 0 }) {
|
||||
return (
|
||||
<group position={[x, y, z]} rotation={[0, rotY, 0]}>
|
||||
{/* 桌面 */}
|
||||
<mesh position={[0, 0.42, 0]} castShadow>
|
||||
<boxGeometry args={[0.9, 0.05, 0.6]} />
|
||||
<meshStandardMaterial color="#e8dcc4" roughness={0.55} metalness={0.05} />
|
||||
</mesh>
|
||||
{/* 桌腿 */}
|
||||
<mesh position={[0, 0.2, 0]} castShadow>
|
||||
<boxGeometry args={[0.06, 0.46, 0.5]} />
|
||||
<meshStandardMaterial color="#d8cbb2" roughness={0.7} />
|
||||
</mesh>
|
||||
{/* 座椅(桌前) */}
|
||||
<mesh position={[0, 0.24, 0.45]} castShadow>
|
||||
<boxGeometry args={[0.45, 0.08, 0.45]} />
|
||||
<meshStandardMaterial color="#4f5f7a" roughness={0.7} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0.45, 0.45]} castShadow>
|
||||
<boxGeometry args={[0.45, 0.4, 0.06]} />
|
||||
<meshStandardMaterial color="#4f5f7a" roughness={0.7} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
/* 讲台(会议室讲台) */
|
||||
function Podium({ x, z, y = 0 }) { return (
|
||||
<group position={[x, y, z]}>
|
||||
<mesh position={[0, 0.5, 0]} castShadow>
|
||||
<boxGeometry args={[0.7, 1.0, 0.5]} />
|
||||
@@ -391,9 +417,9 @@ function Building() {
|
||||
// 一楼大厅外墙(高度 = FLOOR2,不穿过二楼楼板)+ 窗户
|
||||
const hallWalls = [
|
||||
{ axis: 'z', pos: 0, from: 0, to: 44 },
|
||||
{ axis: 'z', pos: 27, from: 0, to: 44 },
|
||||
{ axis: 'x', pos: 0, from: 0, to: 27 },
|
||||
{ axis: 'x', pos: 44, from: 0, to: 27 },
|
||||
{ axis: 'z', pos: 41.1, from: 0, to: 44 },
|
||||
{ axis: 'x', pos: 0, from: 0, to: 41 },
|
||||
{ axis: 'x', pos: 44, from: 0, to: 41 },
|
||||
];
|
||||
hallWalls.forEach((wl, i) => {
|
||||
const posW = wl.axis === 'z' ? cz(wl.pos) : cx(wl.pos);
|
||||
@@ -496,29 +522,33 @@ function Building() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 过道顶棚(右侧竖过道、横向过道)
|
||||
if (r.type === 'hallway') {
|
||||
roofs.push(
|
||||
<mesh key={`hr${i}`} position={[cX, FLOOR2 + 0.5, cZ]}>
|
||||
<boxGeometry args={[w - 0.1, 0.05, d - 0.1]} />
|
||||
<meshStandardMaterial color="#c3d8f2" transparent opacity={0.3} roughness={0.1} metalness={0.1} />
|
||||
</mesh>
|
||||
);
|
||||
return;
|
||||
}
|
||||
// 过道不渲染顶棚(hallway:无顶棚,避免半透明色块干扰地面观感)
|
||||
|
||||
// 家具按功能区分(地面统一色,靠资产区分)
|
||||
if (r.type === 'office') {
|
||||
// 办公室:办公桌(按面积放置多张)
|
||||
const nX = Math.max(1, Math.floor((r.x[1] - r.x[0]) / 3.5));
|
||||
const nZ = Math.max(1, Math.floor((r.z[1] - r.z[0]) / 3.5));
|
||||
const total = Math.min(4, nX * nZ);
|
||||
let idx = 0;
|
||||
for (let ix = 0; ix < nX && idx < total; ix++) {
|
||||
for (let iz = 0; iz < nZ && idx < total; iz++, idx++) {
|
||||
const dx = (ix + 0.5) / nX - 0.5;
|
||||
const dz = (iz + 0.5) / nZ - 0.5;
|
||||
furniture.push(<Desk key={`d${i}_${idx}`} x={cX + dx * w} z={cZ + dz * d} y={FLOOR2} />);
|
||||
// 办公室:办公桌按房间面积排布,留边距不穿墙
|
||||
const roomW = r.x[1] - r.x[0];
|
||||
const roomD = r.z[1] - r.z[0];
|
||||
// 窄房间(宽 < 2.6):单桌且旋转 90°(桌+椅沿深度方向排布,避免横向穿墙)
|
||||
const narrow = roomW < 2.6;
|
||||
if (narrow) {
|
||||
furniture.push(
|
||||
<Desk key={`d${i}_0`} x={cX} z={cZ} y={FLOOR2} rotY={Math.PI / 2} />
|
||||
);
|
||||
} else {
|
||||
const nX = Math.max(1, Math.min(3, Math.floor(roomW / 2.4)));
|
||||
const nZ = Math.max(1, Math.min(2, Math.floor(roomD / 2.4)));
|
||||
const total = Math.min(4, nX * nZ);
|
||||
let idx = 0;
|
||||
for (let ix = 0; ix < nX && idx < total; ix++) {
|
||||
for (let iz = 0; iz < nZ && idx < total; iz++, idx++) {
|
||||
// 留边距居中,确保桌面/椅不穿墙
|
||||
const dx = (ix + 0.5) / nX - 0.5;
|
||||
const dz = (iz + 0.5) / nZ - 0.5;
|
||||
furniture.push(
|
||||
<Desk key={`d${i}_${idx}`} x={cX + dx * (w - 1.6)} z={cZ + dz * (d - 1.6)} y={FLOOR2} />
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (r.type === 'desk') {
|
||||
@@ -540,17 +570,27 @@ function Building() {
|
||||
furniture.push(<Sofa key={`l3${i}`} x={cX} z={cZ + 0.7} y={FLOOR2} rotY={Math.PI} />);
|
||||
furniture.push(<Table key={`l4${i}`} x={cX} z={cZ} y={FLOOR2} s={0.8} />);
|
||||
} else if (r.type === 'meeting') {
|
||||
// 会议室:按面积区分——大会议室(长桌+讲台),小会议室(单张会议桌)
|
||||
const area = (r.x[1] - r.x[0]) * (r.z[1] - r.z[0]);
|
||||
if (area >= 40) {
|
||||
// 大会议室(M01):两张会议长桌 + 讲台
|
||||
furniture.push(<MeetingTable key={`m1${i}`} x={cX - 0.5} z={cZ} y={FLOOR2} rotY={Math.PI / 2} len={Math.min(3.2, d - 0.8)} />);
|
||||
furniture.push(<MeetingTable key={`m2${i}`} x={cX + 1.8} z={cZ} y={FLOOR2} rotY={Math.PI / 2} len={2.4} />);
|
||||
furniture.push(<Podium key={`m3${i}`} x={cX - 2.6} z={cZ} y={FLOOR2} />);
|
||||
} else {
|
||||
// 小会议室(M02):单张会议长桌 + 四周座椅
|
||||
furniture.push(<MeetingTable key={`m1${i}`} x={cX} z={cZ} y={FLOOR2} rotY={Math.PI / 2} len={Math.min(2.0, w - 0.6)} />);
|
||||
// 会议室:多张小办公桌(MeetingDesk)按房间网格排布,不穿墙且满布
|
||||
const roomW = r.x[1] - r.x[0];
|
||||
const roomD = r.z[1] - r.z[0];
|
||||
const nD = Math.max(1, Math.min(6, Math.floor((roomW - 0.8) / 1.2))); // 沿 X 列(留边距,桌宽0.9+隙)
|
||||
const nR = Math.max(1, Math.min(4, Math.floor((roomD - 0.8) / 1.2))); // 沿 Z 行
|
||||
const total = Math.min(12, nD * nR); // 上限 12 张小桌
|
||||
let idx = 0;
|
||||
for (let ix = 0; ix < nD && idx < total; ix++) {
|
||||
for (let iz = 0; iz < nR && idx < total; iz++, idx++) {
|
||||
// 留边距居中(每侧 0.5 单位),避免穿墙
|
||||
const dx = (ix + 0.5) / nD - 0.5;
|
||||
const dz = (iz + 0.5) / nR - 0.5;
|
||||
furniture.push(
|
||||
<MeetingDesk key={`md${i}_${idx}`}
|
||||
x={cX + dx * (w - 1.0)} z={cZ + dz * (d - 1.0)} y={FLOOR2} />
|
||||
);
|
||||
}
|
||||
}
|
||||
// 大会议室额外加讲台
|
||||
const aBig = roomW * roomD >= 40;
|
||||
if (aBig) furniture.push(<Podium key={`m3${i}`} x={cX - (w - 1.0) / 2 + 0.6} z={cZ} y={FLOOR2} />);
|
||||
} else if (r.type === 'stair') {
|
||||
// 楼梯间:真实楼梯(二楼 → 一楼),自动按房间宽/深选择梯段方向与级数
|
||||
const alongX = (x1 - x0) >= (z1 - z0); // 房间宽≥深 → 梯段沿 X 方向
|
||||
@@ -563,7 +603,8 @@ function Building() {
|
||||
steps={steps} stepW={Math.min(0.9, (alongX ? d : w) - 0.1)} stepD={sLen / steps} />
|
||||
);
|
||||
}
|
||||
// 标签:每个房间显示编号(编号 + 名称),办公室显示入驻企业名
|
||||
// 标签:每个房间显示编号(编号 + 名称),办公室显示入驻企业名;过道不显示标签
|
||||
if (r.type === 'hallway') return;
|
||||
const noLabel = r.no || '';
|
||||
const typeLabel =
|
||||
r.type === 'desk' ? '共享工位区' :
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import useVisionDetection from '../voice/useVisionDetection.js';
|
||||
|
||||
/* =========================================================
|
||||
全局手势识别(挂载于 ScreenLayout —— 所有大屏页面共享)
|
||||
---------------------------------------------------------
|
||||
· 摄像头挂载即开启、卸载自动释放(仅一处常开,各页面复用)
|
||||
· 识别到「举手」(手腕高于肩,持续 1.5s)→ 派发全局事件:
|
||||
window 'dpm:gesture' { type: 'raise' }
|
||||
各页面按需订阅处理(例:企业展示墙 举手暂停/继续)
|
||||
· 无摄像头 / 非安全上下文时自动降级:仅右上角提示,不影响页面
|
||||
· 全局状态徽标:右下角常驻,举手瞬间闪烁反馈
|
||||
========================================================= */
|
||||
|
||||
export default function GlobalVision() {
|
||||
const location = useLocation();
|
||||
const vision = useVisionDetection({
|
||||
onGesture: (g) => {
|
||||
// 全部手势类型(raise/fist/both_up/point/hands_close/wave)统一分发
|
||||
window.dispatchEvent(new CustomEvent('dpm:gesture', { detail: { type: g } }));
|
||||
},
|
||||
});
|
||||
|
||||
const on = vision.cameraOn && vision.status !== 'error';
|
||||
// 仅在订阅了手势交互的页面(/wall 等)显示右下角徽标
|
||||
const showBadge = ['/wall'].includes(location.pathname);
|
||||
|
||||
// 手部实时坐标广播:dpm:hand-track { hands }(供手势跟随等使用)
|
||||
useEffect(() => {
|
||||
window.dispatchEvent(new CustomEvent('dpm:hand-track', { detail: { hands: vision.hands } }));
|
||||
}, [vision.hands]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 摄像头驱动(隐藏,仅供全局抽帧识别) */}
|
||||
<video ref={vision.videoRef} autoPlay playsInline muted className="gv-video" />
|
||||
{/* 全局手势状态徽标:仅订阅页面显示 */}
|
||||
{showBadge && (
|
||||
<div
|
||||
className={`gv-badge${on ? ' on' : ''}${vision.gesture === 'raise' ? ' flash' : ''}`}
|
||||
title="全局手势识别:举手 → 当前页面动作"
|
||||
>
|
||||
<span className="gv-ico">🖐</span>
|
||||
<i>{on ? '手势识别中 · 举手交互' : '手势识别未启用'}</i>
|
||||
<b className="gv-dot" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -53,7 +53,7 @@ export default function PageHeader() {
|
||||
<h1 className="bd-header-title">昆明市大学生创业园 <span className="bd-header-title-accent">· OPC 智能园区数字运营中心</span></h1>
|
||||
<div className="bd-header-sub">云南省首家政府主办大学生创业孵化园区 · <span className="bd-header-content—highlights">空间+孵化+融资+政策+资源+AI赋能+综合服务</span> 七位一体</div>
|
||||
</div>
|
||||
<span className="bd-credit"> 云南派音人工智能科技有限公司 开发</span>
|
||||
<span className="bd-credit"> <Icon name="Copyright"></Icon> 云南派音人工智能科技有限公司 开发</span>
|
||||
</div>
|
||||
<div className="bd-header-right">
|
||||
{/* 媒体轮播页:页眉内嵌播放控制 */}
|
||||
|
||||
@@ -7,16 +7,16 @@ import { fmtInt, fmtWan } from '../utils/parkData';
|
||||
|
||||
export default function ParkOverviewStrip({ d }) {
|
||||
const chips = [
|
||||
{ icon: 'building', label: '在园项目', value: `${d.projects.inPark} 家` },
|
||||
{ icon: 'building', label: '实际入驻企业', value: `${d.projects.inPark} 家` },
|
||||
{ icon: 'users', label: '带动就业', value: `${fmtInt(d.jobs.total)} 人` },
|
||||
{ icon: 'coin', label: '累计营收', value: `${fmtWan(d.revenue.total)} 元` },
|
||||
{ icon: 'factory', label: '高新技术企业', value: '32 家' },
|
||||
{ icon: 'flask', label: '省级以上重点研发', value: '18 项' },
|
||||
{ icon: 'trophy', label: '创业大赛奖项', value: '47 项' },
|
||||
{ icon: 'diamond', label: '孵化企业估值', value: '5.2 亿元' },
|
||||
{ icon: 'map-pin', label: '园区面积', value: '1.2 万㎡' },
|
||||
{ icon: 'graduation', label: '创业导师', value: '58 位' },
|
||||
{ icon: 'chart-line', label: '累计孵化企业', value: `${d.projects.cum} 家` },
|
||||
{ icon: 'coin', label: '累计生产经营总额', value: `${d.revenue.total} 万元` },
|
||||
{ icon: 'chart-line', label: '累计上缴税利', value: `${d.revenue.tax} 万元` },
|
||||
{ icon: 'wallet', label: '累计投入运营资金', value: `${d.projects.invested} 万元` },
|
||||
{ icon: 'briefcase', label: '可容纳企业', value: `${d.projects.capacity} 个` },
|
||||
{ icon: 'map-pin', label: '园区面积', value: `${d.park.area} ㎡` },
|
||||
{ icon: 'clock', label: '成立时间', value: `${d.park.founded} 年` },
|
||||
{ icon: 'trophy', label: '省级创业园', value: `${d.park.provinceLevel} 年认定` },
|
||||
{ icon: 'headset', label: '报名电话', value: d.park.phone },
|
||||
];
|
||||
return (
|
||||
<footer className="bd-strip">
|
||||
|
||||
@@ -3,86 +3,72 @@ import Icon from './Icons';
|
||||
|
||||
/* =========================================================
|
||||
园区企业分布侧栏(共享)—— 数字孪生 / AI 智能助手页右侧保持一致
|
||||
分区切换 + 企业名录 + 企业详情 + 园区实时动态
|
||||
分区切换 + 企业名录(39 家全量)+ 企业详情(完整信息)
|
||||
数据来源:《昆明市大学生创业园入驻企业信息表》(39 家全称 + 创始人 + 简介)
|
||||
分区口径:孵化加速区 / 创业苗圃区 / 国际创客区
|
||||
(OPC 创业空间、直播间及信息表未填场地者,已随意并入 创业苗圃区 / 孵化加速区)
|
||||
========================================================= */
|
||||
|
||||
/* ---------- 企业数据(名称 / 区域 / 房间号) ---------- */
|
||||
/* ---------- 企业数据(39 家全量 · 信息表) ---------- */
|
||||
export const COMPANIES = [
|
||||
{ name: '云南派音人工智能科技', zone: '加速区', room: 'A1', color: '#4c8dff' },
|
||||
{ name: '米勒克尔蓝宝石珠宝', zone: '加速区', room: 'A2', color: '#4c8dff' },
|
||||
{ name: '中泰研学合作', zone: '加速区', room: 'A3', color: '#4c8dff' },
|
||||
{ name: '云南宸中低空经济', zone: '加速区', room: 'A4', color: '#4c8dff' },
|
||||
{ name: '昆明智海银高文化科技', zone: '加速区', room: 'A5', color: '#4c8dff' },
|
||||
{ name: 'AI机器人大模型训练', zone: '加速区', room: 'A6', color: '#4c8dff' },
|
||||
{ name: '云南廷秀文旅康养', zone: '加速区', room: 'A7', color: '#4c8dff' },
|
||||
{ name: '瀚颖AI+教育信息咨询', zone: '加速区', room: 'A8', color: '#4c8dff' },
|
||||
{ name: '云南大学AI+创业平台', zone: '加速区', room: 'A9', color: '#4c8dff' },
|
||||
{ name: '仰光客厅', zone: '国际区', room: 'I1', color: '#22d3ee' },
|
||||
{ name: '云南上古绝学文化', zone: '国际区', room: 'I2', color: '#22d3ee' },
|
||||
{ name: '中越生物医疗', zone: '国际区', room: 'I3', color: '#22d3ee' },
|
||||
{ name: '酷享野农AI农业', zone: '国际区', room: 'I4', color: '#22d3ee' },
|
||||
{ name: '滇缅国际设计', zone: '国际区', room: 'I5', color: '#22d3ee' },
|
||||
{ name: '昆明舒诺生物科技', zone: '国际区', room: 'I6', color: '#22d3ee' },
|
||||
{ name: '达岸教育管理', zone: '国际区', room: 'I7', color: '#22d3ee' },
|
||||
{ name: 'Facebook越南跨境电商', zone: '成长区', room: 'G1', color: '#34d399' },
|
||||
{ name: '研X同行者网络', zone: '成长区', room: 'G2', color: '#34d399' },
|
||||
{ name: '朵哈·玫瑰特色产业链', zone: '成长区', room: 'G3', color: '#34d399' },
|
||||
{ name: '昆明云韵体育', zone: '成长区', room: 'G4', color: '#34d399' },
|
||||
{ name: '南菌优培食用菌', zone: '成长区', room: 'G5', color: '#34d399' },
|
||||
{ name: '五华区丽裳文化', zone: '成长区', room: 'G6', color: '#34d399' },
|
||||
{ name: '云南星瑞航空', zone: '成长区', room: 'G7', color: '#34d399' },
|
||||
{ name: '综合直播私域平台', zone: '成长区', room: 'G8', color: '#34d399' },
|
||||
{ name: '园区管理办公室', zone: '园区管理', room: 'M', color: '#fbbf24' },
|
||||
{ name: "云南派音人工智能科技有限公司", zone: "孵化加速区", room: "A1", color: "#4c8dff", founder: "王思松", founderBio: "2020年毕业于应急管理大学新闻学专业。", bio: "本项目为OPC一人公司,于2026年3月正式启动,入驻后发展势头迅猛,不足一个月便成功获得北京相关投资支持,依托该投资,公司于2026年4月正式注册成立,并成功吸纳就业4人。公司核心业务主攻人工智能算法及音频模型应用研究,兼备内容创作、运营管理与技术研发综合能力,是专注多模态音频技术研发的创新型项目。项目自研 Pine全系列模型家族,覆盖音频识别、向量嵌入、音效生成、配乐创作及数字内容创意中枢,贯通音频理解至声音合成全链路智能,致力于为用户提供优质音频体验,向下一代数字内容创作音频基座方向持续演进。", industry: "人工智能" },
|
||||
{ name: "米勒克尔蓝宝石珠宝产业化(项目)", zone: "孵化加速区", room: "A2", color: "#4c8dff", founder: "王昱博", founderBio: "王昱博 ,2023年3月毕业于美国加州大学圣克鲁兹分校 Multi-/Interdisciplinary 管理学专业。", bio: "本项目于2026年3月正式启动,由海归留学生发起,业务核心围绕米勒克尔大尺寸蓝宝石新材料与珠宝产业化展开,全面覆盖晶体生长、材料精密加工及终端珠宝产品设计打造全链条。项目深耕赛道,与产业链上下游各方深度协同合作,精心搭建一体化运营体系,精准紧跟当下珠宝消费升级趋势,专注打造高品质、高附加值的蓝宝石珠宝产品,全力推动蓝宝石新材料技术与珠宝产业深度融合、创新发展,持续拓展市场应用边界与发展空间,助力产业高质量升级。", industry: "新材料" },
|
||||
{ name: "中泰研学合作(项目)", zone: "孵化加速区", room: "A3", color: "#4c8dff", founder: "刘泽位", founderBio: "泰国西那瓦大学工商管理博士二年级,昆明市侨联青年委员会副会长,精通英语,熟练泰语。", bio: "本项目于 2026 年正式启动运营,由海归在读博士发起,项目围绕西那瓦大学文理学院管理运营,统筹泰国环球工商职业学院中国区事务。项目与中泰院校、机构深度合作,整合两国优质教育资源,面向国内提供出国留学、中外合作办学、校企合作、产教融合及企业出海一站式服务,搭建稳定高效的跨境教育合作平台。", industry: "教育培训" },
|
||||
{ name: "云南宸中低空经济有限公司", zone: "孵化加速区", room: "A4", color: "#4c8dff", founder: "王爱玲", founderBio: "云南大学EMBA在读,云南宸中低空经济有限公司负责人。", bio: "云南宸中低空经济有限公司于2026年2月成立,是一家扎根昆明市大学生创业园区的创新型初创企业,专注于低空经济全产业链开发运营。作为创业园孵化的新兴企业,将秉承严谨自律、合规经营理念,依托园区完善的创业扶持体系与优质营商环境,快速完成项目落地与团队组建,致力于成为云南低空经济领域的新兴力量,为云南低空经济高质量发展注入新动能。", industry: "低空经济" },
|
||||
{ name: "昆明智海银高文化科技有限公司", zone: "孵化加速区", room: "A5", color: "#4c8dff", founder: "宋芃燃", founderBio: "昆明文理学院审计学专业在读,中共党员,昆明智海银高文化科技有限责任公司合伙人。", bio: "昆明智海银高文化科技有限责任公司成立于2026年4月,聚焦数字化财税与企业咨询服务,以科技赋能财税、专业服务全球为方向,搭建涵盖传统财税、AI 智能财税、海外财税、财务人才培训及企业综合咨询的服务体系。项目依托 AIGC 技术打造 AI 集成服务平台,与多方开展深度合作,为各类企业提供财税咨询、代理记账、法律合规、企业管理、出海服务等一站式解决方案,坚守合规与高效准则,助力企业稳健运营与高质量发展。", industry: "人工智能" },
|
||||
{ name: "人工智能机器人大模型训练(项目)", zone: "孵化加速区", room: "A6", color: "#4c8dff", founder: "周凯", founderBio: "2022 年毕业于天津大学机械设计及其自动化专业。", bio: "本项目成立于2026年4月,项目聚焦人工智能机器人大模型训练,围绕大模型研发与落地提供全流程数据支撑与技术服务。项目依托专业技术能力,开展数据采集、清洗、标注、模型训练及优化迭代工作,以数据驱动智能升级,推动机器人大模型在多场景落地应用,助力人工智能技术与产业深度融合,为数字化、智能化发展提供坚实支撑。", industry: "人工智能" },
|
||||
{ name: "云南廷秀文旅康养服务(项目)", zone: "孵化加速区", room: "A7", color: "#4c8dff", founder: "李雨桐", founderBio: "2025年毕业于日本樱美林大学,老年社会学硕士。", bio: "项目负责人计划5月归国创业。立足云南“旅居云南”发展趋势与银发经济增长需求,聚焦当前云南民宿、旅居空间、康养点位在服务标准、适老能力、长期居住支持和运营转化方面的短板, 开发系统,致力于建立一套面向银发人群的文旅旅居服务支持体系。", industry: "文旅康养" },
|
||||
{ name: "瀚颖AI+教育信息咨询(项目)", zone: "孵化加速区", room: "A8", color: "#4c8dff", founder: "王翰啸", founderBio: "2019 年毕业于云南大学滇池学院财务管理专业。", bio: "昆明瀚颖“AI+数字教育”项目成立于2026年4月,由创业培训服务领域的从业人员发起,项目立足数字经济时代发展需求,聚焦职业教育与创业赋能领域的综合性创新教育项目,深度融合人工智能技术与教育教学体系,打破传统教育模式局限,打造集课程研发、人才培育、企业赋能、师资共建于一体的全链条数字化教育服务生态。项目始终以市场需求为导向,以实战落地为核心,以长效发展为目标,通过专业化、数字化、体系化的服务,全方位助力个人职业成长、创业者成功启航、企业数字化转型与高效运营,为区域数字经济发展培育高素质复合型人才。", industry: "教育培训" },
|
||||
{ name: "云南大学AI+联合创业服务平台(项目)", zone: "孵化加速区", room: "A9", color: "#4c8dff", founder: "顾耘华", founderBio: "云南大学艺术与设计学院艺术设计在读硕士。", bio: "本项目是由云南大学在校研究生、毕业生和创业导师共同发起的AI创业平台,旨在构建“AI+”多行业创业服务体系。项目以OPC社区AI人才培养与就业服务为核心基础,同时覆盖AI短视频、AI漫局、AI+PPT制作美化等多领域,涉及数字内容创作、视觉设计、人才输送等领域,形成“学—练—创—就”一体化闭环。 项目自实施以来,已帮助多名大学生成功开展OPC创业,负责人顾耘华联合云南AIGC产业联盟打造“育笋计划”公益品牌,辐射大学生就创业500人+;王升雅打造的入侵动植物绘本《生态秘境》插画在《人与自然》杂志连载六期;王洋牵头的《智绘古海——AI复原寒武纪古生物》获国家级创赛金奖;杨沐晨联合相关单位打造西南城市鸟类多样性生态视觉品牌,承办7项生态展览;王润曦联合云南省委宣传部,助力会泽白雾村打造乡村文旅振兴特色名片。", industry: "人工智能" },
|
||||
{ name: "仰光客厅(项目)", zone: "国际创客区", room: "I1", color: "#22d3ee", founder: "缅甸籍留学生", founderBio: "毕业于云南大学经济学院。", bio: "“仰光客厅” 是缅甸留华学生发起的中缅文旅融合项目,以仰光近郊沉浸式文化营地为核心载体,面向中国客群、在缅中资机构及本地青年提供综合服务。项目集住宿、特色餐饮、文化体验、城市导览于一体,与本地社区、手工艺人深度合作,带动就业与增收。项目依托本地资源与中文服务体系,搭建安全、有温度的跨文化交流平台,实现商业运营、文化传播与社区赋能协同发展。", industry: "文旅康养" },
|
||||
{ name: "云南上古绝学文化发展有限公司", zone: "创业苗圃区", room: "I2", color: "#34d399", founder: "赵泳淇", founderBio: "2024年毕业于贵州大学教育技术学专业", bio: "云南上古绝学文化发展有限公司(简称:上古书院),是一家专注于上古家学文化传承与国学赋能的文化机构。公司以“修身齐家”为核心理念,致力于将中华优秀传统文化与现代企业发展、家庭建设相结合。业务涵盖家学文化课程体系研发、国学经典文化交流、家训家规个性化定制及文化研学等。上古书院秉持“从文化体验到家族传承”的完整路径,通过国学智慧赋能个人成长、家庭和谐与企业治理,助力中华文脉的传承与弘扬。", industry: "文化创意" },
|
||||
{ name: "千楣之路(项目)", zone: "国际创客区", room: "—", color: "#22d3ee", founder: "缅甸籍留学生", founderBio: "现就读于云南大学工商管理学院。", bio: "“千楣之路” 由缅甸籍留学生创立,主营缅甸传承两千余年的传统护肤产品檀娜卡。该产品由黄香楝树干制成,缅甸已于 2025 年申报联合国非物质文化遗产。项目打造传统原木套装、便捷粉泥、现代美妆三大产品线,依托原产地优势保障品质。项目与中缅供应链、新媒体平台深度合作,以 “文化内容 + 跨境电商” 模式,将正宗檀娜卡引入中国市场,在推广护肤产品的同时,传递缅甸自然生活智慧与非遗文化魅力。", industry: "跨境电商" },
|
||||
{ name: "中越生物医疗(项目)", zone: "创业苗圃区", room: "I3", color: "#34d399", founder: "武妙英", founderBio: "VŨ DIỆU ANH(武妙英)越南籍留学生 云南大学工商管理专业在读。精通越语,法语、中文、英语", bio: "项目聚焦越南标准化生物蛋白生产联合实验室建设,计划覆盖区域 80% 以上缺口市场。依托中越技术协同与资源互补,开展高纯度生物活性蛋白合规化跨境生产与出口,满足疫苗、诊断试剂及医疗器械领域发展需求。由中越籍成员组建核心团队,贯通原料筛选至出口通关全流程,项目已获越南五部门联合认证,破除传统贸易壁垒,构建合法稳定的跨境供给通道。", industry: "生物医药" },
|
||||
{ name: "中越文旅交流(项目)", zone: "孵化加速区", room: "—", color: "#4c8dff", founder: "阮胡竹珠", founderBio: "云南民族大学广播电视专业在读,2027年毕业。", bio: "本项目致力于搭建中国与越南之间的文化旅游交流平台,为中国游客和越南游客提供旅游咨询、行程定制、文化体验、语言服务及跨境旅游推广等服务。 项目依托申请人广播电视专业背景及中越双语优势,重点开展中越旅游宣传内容制作、新媒体运营、跨境文化交流活动策划及旅游信息服务,为中越两国游客和青年群体提供更加便捷、高效的文旅交流平台。以新媒体传播和数字化运营为特色,通过短视频、直播、自媒体平台等方式宣传中越两国旅游资源、美食文化、民俗风情和特色景点,促进两国文化交流与旅游合作。 目标用户群体主要包括中国赴越南旅游人群、越南赴中国旅游人群、留学生群体以及对中越文化交流感兴趣的年轻消费者。", industry: "文旅康养" },
|
||||
{ name: "“绿态”环保科技项目", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "陈国旗", founderBio: "云南民族大学在读留学生。主要负责项目的海外工厂对接、中越跨境供应链统筹及中国全渠道分销网络的核心运营", bio: "本项目紧跟中国“禁塑令”及绿色环保政策导向,专注于天然槟榔叶(mo cau)环保餐具的研发、跨境贸易与新媒体全渠道销售。项目核心优势在于在越南本土已拥有自有的实体生产加工工厂(自有车间),从天然槟榔叶的源头集采、清洗、高温压制模具成型到严苛杀菌包装100%在越南本土完成,充分降低原材料与人工成本。成品通过正规国际贸易清关出口至中国,并在昆明园区设立“中国市场总分销库与营销/直播工作室”。项目通过建立“抖音直播引流/短视频内容破圈 + 淘宝与拼多多主流货架电商多平台覆盖”的双驱闭环模式,将传统一次性餐具推向高溢价、高转化率的中高端轻食餐饮与露营等文创B端市场,致力于打造中国西南地区知名的跨境绿色环保餐具品牌。", industry: "其他" },
|
||||
{ name: "联翼航空技术低空经济应用国际化拓展(项目)", zone: "国际创客区", room: "—", color: "#22d3ee", founder: "杨俊蓝", founderBio: "2023年毕业于云南财经大学电子商务专业,现瑞士商学院 MSC 硕士在读。", bio: "本项目启动于2026年4月,由在读硕士发起,项目立足云南,面向东南亚的区位门户优势,开展低空经济业务国际化拓展。项目在航空通航、人工智能与数字信息、教育培训等方向布局,输出无人机应用、低空数据服务、跨境技能培训等内容,与境外机构深度合作,搭建跨境人才互通与服务通道,推动低空经济产业、人才、服务双向辐射东南亚,实现国际化发展。", industry: "低空经济" },
|
||||
{ name: "酷享野农AI农业(项目)", zone: "国际创客区", room: "I4", color: "#22d3ee", founder: "晏和波", founderBio: "西南林业大学农业工程与信息技术专业在校研究生。", bio: "本项目启动于2026年4月,由西南林业大学在校研究生发起,酷享野农 AI 农业聚焦智慧农业、科研种植、农旅融合方向,运用 AI 智能视觉技术提供农业宣传内容制作服务,降低运营成本、提升品牌推广效率。项目与科研机构深度合作,开展高原早熟苹果科研种植,打造差异化产品;与地方政府深度合作,建设共享农庄,融合多元农旅业态,助力乡村振兴与特色产业发展。", industry: "现代农业" },
|
||||
{ name: "滇缅国际设计(项目)", zone: "国际创客区", room: "I5", color: "#22d3ee", founder: "张浩鹏", founderBio: "2022 年毕业于云南财经职业学院审计学专业", bio: "本项目启动于2026年4月,依托一带一路发展机遇,立足滇缅对外开放节点,接轨国际设计标准,结合边境发展与跨境建设需求开展工程设计服务。项目创新跨国协同设计模式,融入绿色建造技术,覆盖交通、城乡基建等领域,与境外机构深度合作,推动跨境项目落地,以专业设计助力滇缅沿线共建共享与双边协同发展。", industry: "企业服务" },
|
||||
{ name: "昆明舒诺生物科技有限责任公司", zone: "国际创客区", room: "I6", color: "#22d3ee", founder: "舒岐涛", founderBio: "云南林业职业技术学院环境艺术专业在校生。", bio: "昆明舒诺生物科技有限公司成立于2026年4月,聚焦生物科技与宠物营养领域,围绕精准营养、全生命周期养护、清洁标签开展业务。项目以科学配方与生物活性技术为支撑,与行业伙伴深度合作,打造宠物精细化营养产品,提供安全高效的营养解决方案,助力宠物健康成长,推动宠物营养行业规范发展。", industry: "生物医药" },
|
||||
{ name: "达岸教育管理(云南)有限公司", zone: "国际创客区", room: "I7", color: "#22d3ee", founder: "吕琳", founderBio: "2022 年毕业于泰国暹罗大学 MBA研究生专业。", bio: "本公司专注德国双元制教育与德国留学一站式服务,该项目启动于2024年。项目与德国院校深度合作,提供双元制培养、公立院校申请、德语培训、签证办理、境外陪护等全流程服务,为学生搭建海外求学与就业通道。项目依托德国教育资源与政策支持,帮助学生实现带薪留学、学历提升与稳定就业,助力学业与职业同步发展。", industry: "教育培训" },
|
||||
{ name: "昆明滇油石化有限责任公司", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "李政德", founderBio: "预计2027年毕业于云南国土资源职业学院地球物理勘探专业", bio: "专注于生物基纳米驱油剂的研发、生产与销售,为国内低渗透油藏及老油田二次/三次采油提供绿色高效的增产解决方案。 目标用户群体: 核心用户:中国石油(华北、长庆、新疆、大庆等)、中国石化(胜利、中原等)、中国海油(渤海等)下属老油田及低渗透油藏开采单位。 拓展用户:一带一路沿线国家老油田(中亚、中东、非洲等)。 辅助用户:民营油服公司、地方油田企业(如延长石油)。", industry: "绿色环保" },
|
||||
{ name: "花仙子园艺肥料(云南)有限公司", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "翁淞耀", founderBio: "2023年毕业于云南师范大学,社会体育指导与管理专业。", bio: "花仙子园艺肥料(云南)有限公司成立于2025年4月,公司专注家庭园艺肥研发生产,针对家用肥料异味大、品质不稳、使用不便等痛点,推出安全无异味、易操作的系列园艺肥料,已实现规模化生产。项目立足云南花卉产业优势,采用线上线下融合渠道,服务家庭养花、阳台种植等民生需求,带动本地就业与绿色环保,助力花卉产业链升级,是聚焦民生消费升级的优质青年创业项目。", industry: "现代农业" },
|
||||
{ name: "Facebook越南市场普洱茶与建水紫陶壶跨境电商(项目)", zone: "创业苗圃区", room: "G1", color: "#34d399", founder: "罗有艳", founderBio: "2020年毕业于云南农业大学越南语专业", bio: "本项目依托Facebook全域流量生态(主页、直播、短视频、广告),聚焦越南垂直市场,主营普洱茶、手工茶具(云南建水紫陶小品壶及公道杯组合)、中式茶礼套装跨境零售业务。项目立足云南茶叶产地货源优势,以及建水紫陶的悠久历史,适配越南国民茶饮消费习惯,通过“Facebook内容种草+直播转化+9610跨境报关+越南专线直邮”的闭环模式,省去传统平台中间商、降低流量成本与成交链路损耗,主打高性价比正宗云南茶品与国风茶具。", industry: "跨境电商" },
|
||||
{ name: "研X——同行者网络(项目)", zone: "创业苗圃区", room: "G2", color: "#34d399", founder: "陈品祥", founderBio: "2024年昆明理工大学硕士毕业,现兼职任教昆明冶金职业大学", bio: "研X是面向全国1621万硕博及6000万泛科研人群的一站式学术成长与资源共享平台。针对研究生群体面临的学术资源分散、科研代码“重复造轮子”、学术社交渠道匮乏、情感婚恋困难、就业信息不对称等系统性痛点,研X首创“1+6+N”生态架构——1个实名制可信赖平台,集成文献工具导航、代码共享交易、就业创业对接、学术/情感社交婚恋、科教娱乐、研究生严选商城6大模块,覆盖从入学到就业的全生命周期需求。研X已完成安卓、iOS、Windows三端开发并已上架应用商店。市场验证阶段,现已有全国300余所高校注册。两位高校教授已用个人资金投入种子轮。研X致力于成为研究生和高质量人才的流量入口与规则制定者,让每一位科研同行者在此精准相遇。", industry: "人工智能" },
|
||||
{ name: "鬼才明AI创意工作室(项目)", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "赵广明", founderBio: "2022 年毕业于重庆大学计算机专业。", bio: "本项目启动于2026年4月,项目聚焦产教融合、创新创业赛事运营与数智化品牌赋能,围绕 AI 技术商业化落地、新商科专业规划、全品类活动策展及职业技能人才培育开展服务。项目搭建 AI 智能交互体系与 AI 数字人内容输出矩阵,为政校企提供 AI 场景应用、全域获客、职业技能培训、赛事集训、资格认证、品牌全案、项目申报等全链路解决方案,与多方深度合作,以数智化能力助力人才培养、品牌增长与产业创新升级。", industry: "人工智能" },
|
||||
{ name: "朵哈·玫瑰特色产业链(项目)", zone: "创业苗圃区", room: "G3", color: "#34d399", founder: "刘志贤", founderBio: "云南轻纺职业学院艺术设计专业大二在校生。", bio: "本项目启动于2026年4月,由云南轻纺学院在校大学生发起,立足云南安宁八街核心产区,以地理标志产品重瓣红玫瑰为核心原料,主打玫瑰米酿特色饮品。项目聚焦天然健康与地域特色,面向年轻消费群体、养生人群及伴手礼市场,与省内农业产业化龙头企业深度合作,实现原料优选、标准化生产与品质管控,打造具有云南地域标识的特色饮品品牌,助力地方特色农业提质升级。", industry: "文化创意" },
|
||||
{ name: "昆明云韵体育有限责任公司", zone: "创业苗圃区", room: "G4", color: "#34d399", founder: "舒江贵", founderBio: "2020年7月毕业于麒麟区职业学校运动训练专业。", bio: "本项目发起于2026年4月,项目致力于在云南注册成立 UCI 男子自行车洲际队,打造立足云南、辐射全国、征战亚洲赛场的职业自行车竞技项目。项目依托云南天然高原训练优势、省级体育文旅政策红利、城市品牌打造需求,构建职业赛事 + 商业赞助 + 高原训练 + 体育文旅 + 青训人才培养一体化运营模式", industry: "文化创意" },
|
||||
{ name: "昆明舒护安养老服务有限责任公司", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "严宇佳", founderBio: "2019 年毕业于云南艺术学院广播电视编导专业。", bio: "本项目为OPC一人公司,入驻园区孵化仅一个月,成功吸纳就业3人,目前正稳步扩大公司规模,项目专注正规医疗护理服务,主营医院专属陪护与居家上门护理,覆盖术后康复、失能半失能照料、慢病护理、偏瘫照护、生活监护等全场景需求。所有服务人员均经专业培训与规范管理,服务流程标准化、透明化,与医疗机构深度合作,衔接诊疗与养护环节,为病患、长者提供安全可靠的一站式护理方案,切实缓解家庭照护压力。", industry: "生物医药" },
|
||||
{ name: "南菌优培—食用菌专用生物制剂创新与高原特色野生菌高值化全链开发(项目)", zone: "创业苗圃区", room: "G5", color: "#34d399", founder: "王荣飞", founderBio: "2026年毕业于中山大学外科学专业。", bio: "本项目起源于对云南高原野生菌资源优势与产业痛点的深度洞察,目前已经有三年以上的前期研发基础和产业化探索,相关生物源成分已做过试验验证,同时具备菌类基础试验开展条件,核心团队汇聚微生物研发、农业应用、医药研发三类专业人才,由分析化学博士、农业硕士、医学硕士等跨学科成员组成。项目计划于2026年7月完成公司注册并正式启动运营,后续将聚焦食用菌专用生物制剂创新与高原特色野生菌高值化全链开发两大核心,通过引入新型生物源成分如海藻糖-6-磷酸、寡糖等进行生物制剂创新,同时推动云南高原特色野生菌如硬皮地星、硬皮马勃等高值化开发,充分发挥云南地域特色与优势,紧跟高原特色农业、生物制造、林下经济等领域发展步伐,以科技创新驱动食用菌产业提质增效、绿色转型与价值跃升。", industry: "生物医药" },
|
||||
{ name: "五华区丽裳文化艺术工作室", zone: "创业苗圃区", room: "G6", color: "#34d399", founder: "王娟", founderBio: "2024 年毕业于昆明幼儿师范高等专科学校幼儿教育专业。", bio: "本项目成立于2026年4月,丽裳文化艺术工作室专注传统文化、民俗活动、茶文化及国风品牌活动策划执行,同时打造特色文旅研学项目。工作室立足云南丰富民俗文化资源,与文化艺术、传媒传播、茶艺等领域人才深度合作,为市场提供高品质文化创意服务。依托云南自贸区开放优势,积极推动民俗文创、茶产品、干花制品及高原特色产品出海,搭建 “账号引流 + 内容传播 + 产品出海” 运营体系,以文化创意赋能地方产业,助力云南特色文化与产品拓展海外市场。", industry: "文化创意" },
|
||||
{ name: "云南星瑞航空(项目)", zone: "创业苗圃区", room: "G7", color: "#34d399", founder: "尹永浩", founderBio: "2019年毕业于滇西科技师范学院亚洲微电影学院数字媒体技术专业。", bio: "本项目启动于2026年4月,云南星瑞航空专注低空智能应用,以大疆行业级无人机为核心装备,提供全流程专业服务。业务涵盖无人机测绘、三维建模、数字孪生、应急救援、公共安全巡检、夜间红外搜救及电力、光伏、油气管道、森林防火等作业,并深耕文物数字化保护,完成古建筑高精度三维建档与展示。项目与各行业伙伴深度合作,具备硬件销售、数据处理、技术支持、人才培训一体化能力,为测绘、应急、文旅、城市规划、能源等领域提供一站式低空解决方案。", industry: "低空经济" },
|
||||
{ name: "综合性云南直播私域服务平台", zone: "创业苗圃区", room: "G8", color: "#34d399", founder: "金满满", founderBio: "2026年7月毕业于周口师范学院学前教育专业", bio: "本项目为综合性云南直播私域服务平台,由 2026 年 7 月毕业于周口师范学院学前教育专业的金满满担任项目负责人。平台深耕云南本土特色资源,打造专属自主 IP 形象,以短视频创作 + 全域直播运营为核心载体,在直播带货、专业咨询服务中沉淀粉丝用户,搭建全国性自主私域流量池,通过精细化运营强化用户粘性、实现全链路商业变现。同时平台搭建大学生创业孵化通道,吸纳高校创业人才加入,构建可持续人才迭代体系,助力项目长效发展,打造云南特色直播私域标杆。", industry: "跨境电商" },
|
||||
{ name: "昆明屿澈电子商务有限公司", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "杨君武", founderBio: "2024年6月毕业于云南交通职业技术学院建设工程管理专业。", bio: "本项目为高校毕业生发起的东南亚跨境电商 AI 智能直播 + 短视频出海创业项目,依托 TikTok Shop 正规跨境渠道,面向马来西亚、泰国、越南、菲律宾四国市场运营。主营日用百货、家居用品、电子配件、文创收纳、便携小家电等高性价比刚需产品。 项目采用 AI 数字人 7×24 小时无人直播、AI 短视频批量引流、1688 一件代发轻资产模式,无需实体门店与大量库存,大幅降低人力成本。以 AI 多语种智能直播破解跨境时差、语言障碍、人工成本高三大痛点。", industry: "跨境电商" },
|
||||
{ name: "中外青少年研学(项目)", zone: "孵化加速区", room: "—", color: "#4c8dff", founder: "王得荣:项目主理人", founderBio: "2022年武汉大学法学专业毕业,2024—2025年英国剑桥大学短期交流学习", bio: "项目涵盖国际游学与教育交流服务项目。依托全球优质教育资源布局,现已与美国、英国、澳大利亚、新加坡等多国顶尖教育机构达成长期直营战略合作,渠道直连、资源可靠。聚焦青少年、在职教师及家长多元需求,量身打造一站式全球化教育交流解决方案,助力中国学子拓宽国际视野、接轨多元教育,赋能高品质国际文化交流与成长。", industry: "教育培训" },
|
||||
{ name: "星杭商务信息咨询昆明有限责任公司", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "杨之华", founderBio: "2024年6月毕业于云南师范大学教育学专业。", bio: "公司主要经营商务服务、信息咨询培训、商务接待及服务保障配套。", industry: "企业服务" },
|
||||
{ name: "蓝智科技(项目)", zone: "孵化加速区", room: "—", color: "#4c8dff", founder: "徐安厅", founderBio: "2026年毕业于昆明城市学院计算机科学与技术专业。", bio: "本项目为OPC一人公司,2026年5月启动项目,现有从业人员共计3人。项目立足西南蓝莓特色种植产业,专注农业物联网技术研发与智慧基地数字化运营,兼具硬件设备调试、软件系统开发、田间种植运维综合服务能力。项目自研蓝智全系列智慧种植监控系统,涵盖田间环境监测、基质养分检测、水肥灌溉、种植生产数据管理等功能,打通数据采集、智能分析、自动调控完整链路,持续迭代适配高原蓝莓种植场景的数字化技术基座,为各地蓝莓种植基地提供一体化智慧农业解决方案。", industry: "现代农业" },
|
||||
{ name: "云品出滇•纸享万家(项目)", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "王帅", founderBio: "2024年毕业于昆明理工大学电子信息工程专业", bio: "企业成立于2026年3月,以新型悬挂式抽纸为核心切入产品,坚持环保理念,积极响应绿色消费与可持续发展的政策方向,依托AI工具辅助与电商平台运营,线上与线下相结合,打造国内电商与跨境电商双增长模式的日用纸品品牌。公司核心业务主攻家居日用纸品的电商销售与运营,兼备AI工具应用、供应链管理与电商运营综合能力,是专注于悬挂式抽纸及相关家居纸品的创新型电商项目。项目当前已在拼多多平台实现真实销售验证。未来将逐步向东南亚跨境电商市场拓展,向国内+海外双市场并行的家居日用品品牌方向持续演进。", industry: "其他" },
|
||||
{ name: "云迹轻旅——昆明本土一站式轻量化文旅服务工作室(项目)", zone: "孵化加速区", room: "—", color: "#4c8dff", founder: "王海璇", founderBio: "毕业于云南大学工商管理(MBA),10年云南⽂旅从业经验。", bio: "云迹轻旅——昆明本土一站式轻量化文旅服务工作室,采用单人OPC轻资产运营模式。项目深度整合云南昆明丰富的文旅与非遗资源,构建“内容+服务+技术”三位一体的业务矩阵:文旅自媒体 IP 孵化、私人小众旅行定制、非遗自然研学小团、文旅商业咨询策划、AI 数字文旅技术服务五大板块协同发力。精准切入云南文旅市场“体验升级、供给分散、数字化薄弱”的痛点,面向 C 端游客提供个性化、避坑式、深度文化体验的旅行解决方案,面向 B端本地文旅商家提供低本、高转化的新媒体代运营与数字化升级服务,致力于成为昆明本土最具温度与创新力的一站式轻量化文旅综合服务商。", industry: "其他" },
|
||||
{ name: "启元人工智能科技(项目)", zone: "创业苗圃区", room: "—", color: "#34d399", founder: "李瑞康", founderBio: "2017年毕业于吉林动画学院广播电视编导专业。", bio: "本项目为OPC一人公司,专注于8-12岁儿童人工智能工具的技术推广与科普体验服务。自主研发适配低龄儿童的 AI 实操工具包,依托大学生创业园共享空间开展次制AI创意科技体验沙龙,为儿童提供 AI 绘画、AI音乐创作,AI视频制作工具的操作指导与创意交流平台,帮助孩子在动手实践中感受AI科技魅力,培养创新思维。", industry: "人工智能" },
|
||||
{ name: '园区管理办公室', zone: '园区管理', room: 'M', color: '#fbbf24', founder: '—', founderBio: '园区运营管理服务', bio: '园区运营管理与综合服务办公区', industry: '园区管理' },
|
||||
];
|
||||
|
||||
export const ZONES = ['加速区', '国际区', '成长区'];
|
||||
export const ZONES = ['孵化加速区', '创业苗圃区', '国际创客区'];
|
||||
|
||||
export const ZONE_COLORS = { 加速区: '#4c8dff', 国际区: '#22d3ee', 成长区: '#34d399' };
|
||||
export const ZONE_COLORS = { 孵化加速区: '#4c8dff', 创业苗圃区: '#34d399', 国际创客区: '#22d3ee' };
|
||||
|
||||
/* ---------- 工具 ---------- */
|
||||
const nowTime = () => new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||
|
||||
const TOOL_NAMES = ['文档生成', '数据查询', '图像创作', '代码执行', '语音合成'];
|
||||
const MODEL_NAMES = ['DeepSeek-V3', '通义千问', '智谱 GLM-4', '豆包', '讯飞星火'];
|
||||
const COMPANY_NAMES = COMPANIES.map((c) => c.name);
|
||||
|
||||
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: 'robot', text: `「${pick(MODEL_NAMES)}」模型完成一次微调任务` },
|
||||
];
|
||||
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', time: nowTime() },
|
||||
{ id: 2, icon: 'building', text: '「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间', time: nowTime() },
|
||||
{ id: 3, icon: 'users', text: '「昆明舒护安养老服务」新增招聘岗位 2 个', time: nowTime() },
|
||||
{ id: 4, icon: 'coin', text: '园区企业完成一笔 ¥3.6万 交易', time: nowTime() },
|
||||
];
|
||||
}
|
||||
/* 行业主题色 */
|
||||
const IND_COLOR = {
|
||||
'人工智能': '#22d3ee', '跨境电商': '#a78bfa', '教育培训': '#fbbf24',
|
||||
'文旅康养': '#fb923c', '文化创意': '#f472b6', '生物医药': '#34d399',
|
||||
'低空经济': '#4c8dff', '现代农业': '#4ade80', '绿色环保': '#17b26a',
|
||||
'企业服务': '#94a3b8', '新材料': '#f59e0b', '其他': '#8b9cb8', '园区管理': '#fbbf24',
|
||||
};
|
||||
|
||||
export default function ParkSidePanel() {
|
||||
const [feed, setFeed] = useState(initFeed);
|
||||
const [activeZone, setActiveZone] = useState('加速区');
|
||||
const [activeZone, setActiveZone] = useState('孵化加速区');
|
||||
const [selected, setSelected] = useState(null);
|
||||
|
||||
// 实时动态滚动
|
||||
useEffect(() => {
|
||||
const iv = setInterval(() => {
|
||||
setFeed((prev) => [genEvent(), ...prev].slice(0, 6));
|
||||
}, 2600);
|
||||
return () => clearInterval(iv);
|
||||
}, []);
|
||||
|
||||
const list = COMPANIES.filter((c) => c.zone === activeZone);
|
||||
|
||||
// MQTT 调度器 → 企业面板操作:切换分区 / 切换展示企业
|
||||
@@ -109,7 +95,7 @@ export default function ParkSidePanel() {
|
||||
|
||||
return (
|
||||
<aside className="bd-twin-side">
|
||||
{/* 分区切换 */}
|
||||
{/* 分区切换(三大主区) */}
|
||||
<div className="bd-twin-zones">
|
||||
{ZONES.map((z) => (
|
||||
<button
|
||||
@@ -124,12 +110,12 @@ export default function ParkSidePanel() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 企业列表 */}
|
||||
{/* 企业列表(39 家全量) */}
|
||||
<div className="bd-twin-list">
|
||||
{list.map((c) => (
|
||||
<button
|
||||
key={c.room}
|
||||
className={`bd-twin-item${selected && selected.room === c.room ? ' active' : ''}`}
|
||||
key={c.room + c.name}
|
||||
className={`bd-twin-item${selected && selected.name === c.name ? ' active' : ''}`}
|
||||
onClick={() => setSelected(c)}
|
||||
>
|
||||
<span className="bd-twin-room" style={{ color: c.color, borderColor: c.color }}>{c.room}</span>
|
||||
@@ -138,36 +124,40 @@ export default function ParkSidePanel() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 选中企业详情 */}
|
||||
{/* 选中企业详情(完整信息) */}
|
||||
{selected && (
|
||||
<div className="bd-twin-detail">
|
||||
<div className="bd-twin-detail-head">
|
||||
<b>{selected.name}</b>
|
||||
<span style={{ color: ZONE_COLORS[activeZone] }}>{selected.zone}</span>
|
||||
<b style={{ fontSize: selected.name.length > 22 ? 12 : selected.name.length > 15 ? 13 : 14.5 }}>{selected.name}</b>
|
||||
<span className="bd-twin-tags">
|
||||
<span className="bd-twin-ind" style={{
|
||||
color: IND_COLOR[selected.industry] || '#2f6bff',
|
||||
borderColor: (IND_COLOR[selected.industry] || '#2f6bff') + '55',
|
||||
background: (IND_COLOR[selected.industry] || '#2f6bff') + '14',
|
||||
}}>{selected.industry}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="bd-twin-founder">
|
||||
<span className="bd-twin-avatar" style={{
|
||||
background: `linear-gradient(135deg, ${(IND_COLOR[selected.industry] || '#2f6bff')}e6, ${(IND_COLOR[selected.industry] || '#2f6bff')}55)`,
|
||||
}}>
|
||||
{selected.founder && selected.founder !== '—' ? selected.founder[0] : '园'}
|
||||
</span>
|
||||
<div className="bd-twin-founder-info">
|
||||
<i>创始人 / 负责人</i>
|
||||
<b>{selected.founder}</b>
|
||||
<p>{selected.founderBio}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="bd-twin-bio">{selected.bio}</p>
|
||||
<div className="bd-twin-detail-meta">
|
||||
<span>房间 <b>{selected.room}</b></span>
|
||||
{selected.room && selected.room !== '—' && (
|
||||
<span>房间 <b>{selected.room}</b></span>
|
||||
)}
|
||||
<span>状态 <b className="ok">在驻</b></span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 实时动态 */}
|
||||
<div className="bd-card bd-feed-card bd-twin-feed">
|
||||
<div className="bd-card-head">
|
||||
<div className="bd-title">园区实时动态</div>
|
||||
<span className="bd-pill-tag red">● LIVE</span>
|
||||
</div>
|
||||
<div className="bd-feed">
|
||||
{feed.map((e) => (
|
||||
<div className="bd-feed-item" key={e.id}>
|
||||
<span className="bd-feed-ico"><Icon name={e.icon} size={15} /></span>
|
||||
<span className="bd-feed-text">{e.text}</span>
|
||||
<span className="bd-feed-time">{e.time}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import PageHeader from './PageHeader';
|
||||
import DpmOverlays from './DpmOverlays';
|
||||
import GlobalVision from './GlobalVision';
|
||||
import { usePageNav } from '../utils/pageNav';
|
||||
import { useMqttControl } from '../utils/useMqttControl';
|
||||
|
||||
@@ -23,7 +24,8 @@ export default function ScreenLayout() {
|
||||
<main className="bd-layout-main">
|
||||
<Outlet />
|
||||
</main>
|
||||
{/* 全局覆盖层(通知 / 卡片) */}
|
||||
{/* 全局覆盖层(通知 / 卡片 / 手势识别徽标) */}
|
||||
<GlobalVision />
|
||||
<DpmOverlays />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
/* =========================================================
|
||||
入驻企业展示墙(/wall)
|
||||
—— 39 家入驻企业全量档案 · 创始人 / 企业信息 · 慢速滚动
|
||||
---------------------------------------------------------
|
||||
数据来源:《昆明市大学生创业园入驻企业信息表》(39 家全量)
|
||||
· 创始人:负责人姓名 + 简介(信息表原文)
|
||||
· 企业:全称 + 简介(信息表原文)
|
||||
· 行业:按企业简介归入 11 大行业 · 区域:按场地安排归入 4 大孵化区域
|
||||
控制通道(MQTT opc/display/command):
|
||||
· wall_pause / wall_resume / wall_speed {slow|normal|fast}
|
||||
前端接收 dpm:wall-control 事件(useMqttControl 分发)
|
||||
========================================================= */
|
||||
|
||||
/* [全量档案] 39 家入驻企业(信息表) */
|
||||
const WALL = [
|
||||
{ no: 1, name: '云南派音人工智能科技有限公司', founder: '王思松', founderBio: '2020年毕业于应急管理大学新闻学专业。', bio: '本项目为OPC一人公司,于2026年3月正式启动,入驻后发展势头迅猛,不足一个月便成功获得北京相关投资支持,依托该投资,公司于2026年4月正式注册成立,并成功吸纳就业4人。公司核心业务主攻人工智能算法及音频模型应用研究,兼备内容创作、运营管理与技术研发综合能力,是专注多模态音频技术研发的创新型项目。项目自研 Pine全系列模型家族,覆盖音频识别、向量嵌入、音效生成、配乐创作及数字内容创意中枢,贯通音频理解至声音合成全链路智能,致力于为用户提供优质音频体验,向下一代数字内容创作音频基座方向持续演进。', zone: '加速区', industry: '人工智能' },
|
||||
{ no: 2, name: '米勒克尔蓝宝石珠宝产业化(项目)', founder: '王昱博', founderBio: '2023年3月毕业于美国加州大学圣克鲁兹分校 Multi-/Interdisciplinary 管理学专业。', bio: '本项目于2026年3月正式启动,由海归留学生发起,业务核心围绕米勒克尔大尺寸蓝宝石新材料与珠宝产业化展开,全面覆盖晶体生长、材料精密加工及终端珠宝产品设计打造全链条。项目深耕赛道,与产业链上下游各方深度协同合作,精心搭建一体化运营体系,精准紧跟当下珠宝消费升级趋势,专注打造高品质、高附加值的蓝宝石珠宝产品,全力推动蓝宝石新材料技术与珠宝产业深度融合、创新发展,持续拓展市场应用边界与发展空间,助力产业高质量升级。', zone: '加速区', industry: '新材料' },
|
||||
{ no: 3, name: '中泰研学合作(项目)', founder: '刘泽位', founderBio: '泰国西那瓦大学工商管理博士二年级,昆明市侨联青年委员会副会长,精通英语,熟练泰语。', bio: '本项目于 2026 年正式启动运营,由海归在读博士发起,项目围绕西那瓦大学文理学院管理运营,统筹泰国环球工商职业学院中国区事务。项目与中泰院校、机构深度合作,整合两国优质教育资源,面向国内提供出国留学、中外合作办学、校企合作、产教融合及企业出海一站式服务,搭建稳定高效的跨境教育合作平台。', zone: '加速区', industry: '教育培训' },
|
||||
{ no: 4, name: '云南宸中低空经济有限公司', founder: '王爱玲', founderBio: '云南大学EMBA在读,云南宸中低空经济有限公司负责人。', bio: '云南宸中低空经济有限公司于2026年2月成立,是一家扎根昆明市大学生创业园区的创新型初创企业,专注于低空经济全产业链开发运营。作为创业园孵化的新兴企业,将秉承严谨自律、合规经营理念,依托园区完善的创业扶持体系与优质营商环境,快速完成项目落地与团队组建,致力于成为云南低空经济领域的新兴力量,为云南低空经济高质量发展注入新动能。', zone: '加速区', industry: '低空经济' },
|
||||
{ no: 5, name: '昆明智海银高文化科技有限公司', founder: '宋芃燃', founderBio: '昆明文理学院审计学专业在读,中共党员,昆明智海银高文化科技有限责任公司合伙人。', bio: '昆明智海银高文化科技有限责任公司成立于2026年4月,聚焦数字化财税与企业咨询服务,以科技赋能财税、专业服务全球为方向,搭建涵盖传统财税、AI 智能财税、海外财税、财务人才培训及企业综合咨询的服务体系。项目依托 AIGC 技术打造 AI 集成服务平台,与多方开展深度合作,为各类企业提供财税咨询、代理记账、法律合规、企业管理、出海服务等一站式解决方案,坚守合规与高效准则,助力企业稳健运营与高质量发展。', zone: '加速区', industry: '人工智能' },
|
||||
{ no: 6, name: '人工智能机器人大模型训练(项目)', founder: '周凯', founderBio: '2022 年毕业于天津大学机械设计及其自动化专业。', bio: '本项目成立于2026年4月,项目聚焦人工智能机器人大模型训练,围绕大模型研发与落地提供全流程数据支撑与技术服务。项目依托专业技术能力,开展数据采集、清洗、标注、模型训练及优化迭代工作,以数据驱动智能升级,推动机器人大模型在多场景落地应用,助力人工智能技术与产业深度融合,为数字化、智能化发展提供坚实支撑。', zone: '加速区', industry: '人工智能' },
|
||||
{ no: 7, name: '云南廷秀文旅康养服务(项目)', founder: '李雨桐', founderBio: '2025年毕业于日本樱美林大学,老年社会学硕士。', bio: '项目负责人计划5月归国创业。立足云南“旅居云南”发展趋势与银发经济增长需求,聚焦当前云南民宿、旅居空间、康养点位在服务标准、适老能力、长期居住支持和运营转化方面的短板, 开发系统,致力于建立一套面向银发人群的文旅旅居服务支持体系。', zone: '加速区', industry: '文旅康养' },
|
||||
{ no: 8, name: '瀚颖AI+教育信息咨询(项目)', founder: '王翰啸', founderBio: '2019 年毕业于云南大学滇池学院财务管理专业。', bio: '昆明瀚颖“AI+数字教育”项目成立于2026年4月,由创业培训服务领域的从业人员发起,项目立足数字经济时代发展需求,聚焦职业教育与创业赋能领域的综合性创新教育项目,深度融合人工智能技术与教育教学体系,打破传统教育模式局限,打造集课程研发、人才培育、企业赋能、师资共建于一体的全链条数字化教育服务生态。项目始终以市场需求为导向,以实战落地为核心,以长效发展为目标,通过专业化、数字化、体系化的服务,全方位助力个人职业成长、创业者成功启航、企业数字化转型与高效运营,为区域数字经济发展培育高素质复合型人才。', zone: '加速区', industry: '教育培训' },
|
||||
{ no: 9, name: '云南大学AI+联合创业服务平台(项目)', founder: '顾耘华', founderBio: '云南大学艺术与设计学院艺术设计在读硕士。', bio: '本项目是由云南大学在校研究生、毕业生和创业导师共同发起的AI创业平台,旨在构建“AI+”多行业创业服务体系。项目以OPC社区AI人才培养与就业服务为核心基础,同时覆盖AI短视频、AI漫局、AI+PPT制作美化等多领域,涉及数字内容创作、视觉设计、人才输送等领域,形成“学—练—创—就”一体化闭环。 项目自实施以来,已帮助多名大学生成功开展OPC创业,负责人顾耘华联合云南AIGC产业联盟打造“育笋计划”公益品牌,辐射大学生就创业500人+;王升雅打造的入侵动植物绘本《生态秘境》插画在《人与自然》杂志连载六期;王洋牵头的《智绘古海——AI复原寒武纪古生物》获国家级创赛金奖;杨沐晨联合相关单位打造西南城市鸟类多样性生态视觉品牌,承办7项生态展览;王润曦联合云南省委宣传部,助力会泽白雾村打造乡村文旅振兴特色名片。', zone: '加速区', industry: '人工智能' },
|
||||
{ no: 10, name: '仰光客厅(项目)', founder: '缅甸籍留学生', founderBio: '毕业于云南大学经济学院。', bio: '“仰光客厅” 是缅甸留华学生发起的中缅文旅融合项目,以仰光近郊沉浸式文化营地为核心载体,面向中国客群、在缅中资机构及本地青年提供综合服务。项目集住宿、特色餐饮、文化体验、城市导览于一体,与本地社区、手工艺人深度合作,带动就业与增收。项目依托本地资源与中文服务体系,搭建安全、有温度的跨文化交流平台,实现商业运营、文化传播与社区赋能协同发展。', zone: '国际区', industry: '文旅康养' },
|
||||
{ no: 11, name: '云南上古绝学文化发展有限公司', founder: '赵泳淇', founderBio: '2024年毕业于贵州大学教育技术学专业', bio: '云南上古绝学文化发展有限公司(简称:上古书院),是一家专注于上古家学文化传承与国学赋能的文化机构。公司以“修身齐家”为核心理念,致力于将中华优秀传统文化与现代企业发展、家庭建设相结合。业务涵盖家学文化课程体系研发、国学经典文化交流、家训家规个性化定制及文化研学等。上古书院秉持“从文化体验到家族传承”的完整路径,通过国学智慧赋能个人成长、家庭和谐与企业治理,助力中华文脉的传承与弘扬。', zone: '待分配', industry: '文化创意' },
|
||||
{ no: 12, name: '千楣之路(项目)', founder: '缅甸籍留学生', founderBio: '现就读于云南大学工商管理学院。', bio: '“千楣之路” 由缅甸籍留学生创立,主营缅甸传承两千余年的传统护肤产品檀娜卡。该产品由黄香楝树干制成,缅甸已于 2025 年申报联合国非物质文化遗产。项目打造传统原木套装、便捷粉泥、现代美妆三大产品线,依托原产地优势保障品质。项目与中缅供应链、新媒体平台深度合作,以 “文化内容 + 跨境电商” 模式,将正宗檀娜卡引入中国市场,在推广护肤产品的同时,传递缅甸自然生活智慧与非遗文化魅力。', zone: '国际区', industry: '跨境电商' },
|
||||
{ no: 13, name: '中越生物医疗(项目)', founder: '武妙英', founderBio: 'VŨ DIỆU ANH(武妙英)越南籍留学生 云南大学工商管理专业在读。精通越语,法语、中文、英语', bio: '项目聚焦越南标准化生物蛋白生产联合实验室建设,计划覆盖区域 80% 以上缺口市场。依托中越技术协同与资源互补,开展高纯度生物活性蛋白合规化跨境生产与出口,满足疫苗、诊断试剂及医疗器械领域发展需求。由中越籍成员组建核心团队,贯通原料筛选至出口通关全流程,项目已获越南五部门联合认证,破除传统贸易壁垒,构建合法稳定的跨境供给通道。', zone: '待分配', industry: '生物医药' },
|
||||
{ no: 14, name: '中越文旅交流(项目)', founder: '阮胡竹珠', founderBio: '云南民族大学广播电视专业在读,2027年毕业。', bio: '本项目致力于搭建中国与越南之间的文化旅游交流平台,为中国游客和越南游客提供旅游咨询、行程定制、文化体验、语言服务及跨境旅游推广等服务。 项目依托申请人广播电视专业背景及中越双语优势,重点开展中越旅游宣传内容制作、新媒体运营、跨境文化交流活动策划及旅游信息服务,为中越两国游客和青年群体提供更加便捷、高效的文旅交流平台。以新媒体传播和数字化运营为特色,通过短视频、直播、自媒体平台等方式宣传中越两国旅游资源、美食文化、民俗风情和特色景点,促进两国文化交流与旅游合作。 目标用户群体主要包括中国赴越南旅游人群、越南赴中国旅游人群、留学生群体以及对中越文化交流感兴趣的年轻消费者。', zone: '待分配', industry: '文旅康养' },
|
||||
{ no: 15, name: '“绿态”环保科技项目', founder: '陈国旗', founderBio: '云南民族大学在读留学生。主要负责项目的海外工厂对接、中越跨境供应链统筹及中国全渠道分销网络的核心运营', bio: '本项目紧跟中国“禁塑令”及绿色环保政策导向,专注于天然槟榔叶(mo cau)环保餐具的研发、跨境贸易与新媒体全渠道销售。项目核心优势在于在越南本土已拥有自有的实体生产加工工厂(自有车间),从天然槟榔叶的源头集采、清洗、高温压制模具成型到严苛杀菌包装100%在越南本土完成,充分降低原材料与人工成本。成品通过正规国际贸易清关出口至中国,并在昆明园区设立“中国市场总分销库与营销/直播工作室”。项目通过建立“抖音直播引流/短视频内容破圈 + 淘宝与拼多多主流货架电商多平台覆盖”的双驱闭环模式,将传统一次性餐具推向高溢价、高转化率的中高端轻食餐饮与露营等文创B端市场,致力于打造中国西南地区知名的跨境绿色环保餐具品牌。', zone: '待分配', industry: '绿色环保' },
|
||||
{ no: 16, name: '联翼航空技术低空经济应用国际化拓展(项目)', founder: '杨俊蓝', founderBio: '2023年毕业于云南财经大学电子商务专业,现瑞士商学院 MSC 硕士在读。', bio: '本项目启动于2026年4月,由在读硕士发起,项目立足云南,面向东南亚的区位门户优势,开展低空经济业务国际化拓展。项目在航空通航、人工智能与数字信息、教育培训等方向布局,输出无人机应用、低空数据服务、跨境技能培训等内容,与境外机构深度合作,搭建跨境人才互通与服务通道,推动低空经济产业、人才、服务双向辐射东南亚,实现国际化发展。', zone: '国际区', industry: '低空经济' },
|
||||
{ no: 17, name: '酷享野农AI农业(项目)', founder: '晏和波', founderBio: '西南林业大学农业工程与信息技术专业在校研究生。', bio: '本项目启动于2026年4月,由西南林业大学在校研究生发起,酷享野农 AI 农业聚焦智慧农业、科研种植、农旅融合方向,运用 AI 智能视觉技术提供农业宣传内容制作服务,降低运营成本、提升品牌推广效率。项目与科研机构深度合作,开展高原早熟苹果科研种植,打造差异化产品;与地方政府深度合作,建设共享农庄,融合多元农旅业态,助力乡村振兴与特色产业发展。', zone: '国际区', industry: '现代农业' },
|
||||
{ no: 18, name: '滇缅国际设计(项目)', founder: '张浩鹏', founderBio: '2022 年毕业于云南财经职业学院审计学专业', bio: '本项目启动于2026年4月,依托一带一路发展机遇,立足滇缅对外开放节点,接轨国际设计标准,结合边境发展与跨境建设需求开展工程设计服务。项目创新跨国协同设计模式,融入绿色建造技术,覆盖交通、城乡基建等领域,与境外机构深度合作,推动跨境项目落地,以专业设计助力滇缅沿线共建共享与双边协同发展。', zone: '国际区', industry: '企业服务' },
|
||||
{ no: 19, name: '昆明舒诺生物科技有限责任公司', founder: '舒岐涛', founderBio: '云南林业职业技术学院环境艺术专业在校生。', bio: '昆明舒诺生物科技有限公司成立于2026年4月,聚焦生物科技与宠物营养领域,围绕精准营养、全生命周期养护、清洁标签开展业务。项目以科学配方与生物活性技术为支撑,与行业伙伴深度合作,打造宠物精细化营养产品,提供安全高效的营养解决方案,助力宠物健康成长,推动宠物营养行业规范发展。', zone: '国际区', industry: '生物医药' },
|
||||
{ no: 20, name: '达岸教育管理(云南)有限公司', founder: '吕琳', founderBio: '2022 年毕业于泰国暹罗大学 MBA研究生专业。', bio: '本公司专注德国双元制教育与德国留学一站式服务,该项目启动于2024年。项目与德国院校深度合作,提供双元制培养、公立院校申请、德语培训、签证办理、境外陪护等全流程服务,为学生搭建海外求学与就业通道。项目依托德国教育资源与政策支持,帮助学生实现带薪留学、学历提升与稳定就业,助力学业与职业同步发展。', zone: '国际区', industry: '教育培训' },
|
||||
{ no: 21, name: '昆明滇油石化有限责任公司', founder: '李政德', founderBio: '预计2027年毕业于云南国土资源职业学院地球物理勘探专业', bio: '专注于生物基纳米驱油剂的研发、生产与销售,为国内低渗透油藏及老油田二次/三次采油提供绿色高效的增产解决方案。 目标用户群体: 核心用户:中国石油(华北、长庆、新疆、大庆等)、中国石化(胜利、中原等)、中国海油(渤海等)下属老油田及低渗透油藏开采单位。 拓展用户:一带一路沿线国家老油田(中亚、中东、非洲等)。 辅助用户:民营油服公司、地方油田企业(如延长石油)。', zone: '待分配', industry: '绿色环保' },
|
||||
{ no: 22, name: '花仙子园艺肥料(云南)有限公司', founder: '翁淞耀', founderBio: '2023年毕业于云南师范大学,社会体育指导与管理专业。', bio: '花仙子园艺肥料(云南)有限公司成立于2025年4月,公司专注家庭园艺肥研发生产,针对家用肥料异味大、品质不稳、使用不便等痛点,推出安全无异味、易操作的系列园艺肥料,已实现规模化生产。项目立足云南花卉产业优势,采用线上线下融合渠道,服务家庭养花、阳台种植等民生需求,带动本地就业与绿色环保,助力花卉产业链升级,是聚焦民生消费升级的优质青年创业项目。', zone: '成长区', industry: '现代农业' },
|
||||
{ no: 23, name: 'Facebook越南市场普洱茶与建水紫陶壶跨境电商(项目)', founder: '罗有艳', founderBio: '2020年毕业于云南农业大学越南语专业', bio: '本项目依托Facebook全域流量生态(主页、直播、短视频、广告),聚焦越南垂直市场,主营普洱茶、手工茶具(云南建水紫陶小品壶及公道杯组合)、中式茶礼套装跨境零售业务。项目立足云南茶叶产地货源优势,以及建水紫陶的悠久历史,适配越南国民茶饮消费习惯,通过“Facebook内容种草+直播转化+9610跨境报关+越南专线直邮”的闭环模式,省去传统平台中间商、降低流量成本与成交链路损耗,主打高性价比正宗云南茶品与国风茶具。', zone: '待分配', industry: '跨境电商' },
|
||||
{ no: 24, name: '研X——同行者网络(项目)', founder: '陈品祥', founderBio: '2024年昆明理工大学硕士毕业,现兼职任教昆明冶金职业大学', bio: '研X是面向全国1621万硕博及6000万泛科研人群的一站式学术成长与资源共享平台。针对研究生群体面临的学术资源分散、科研代码“重复造轮子”、学术社交渠道匮乏、情感婚恋困难、就业信息不对称等系统性痛点,研X首创“1+6+N”生态架构——1个实名制可信赖平台,集成文献工具导航、代码共享交易、就业创业对接、学术/情感社交婚恋、科教娱乐、研究生严选商城6大模块,覆盖从入学到就业的全生命周期需求。研X已完成安卓、iOS、Windows三端开发并已上架应用商店。市场验证阶段,现已有全国300余所高校注册。两位高校教授已用个人资金投入种子轮。研X致力于成为研究生和高质量人才的流量入口与规则制定者,让每一位科研同行者在此精准相遇。', zone: '成长区', industry: '人工智能' },
|
||||
{ no: 25, name: '鬼才明AI创意工作室(项目)', founder: '赵广明', founderBio: '2022 年毕业于重庆大学计算机专业。', bio: '本项目启动于2026年4月,项目聚焦产教融合、创新创业赛事运营与数智化品牌赋能,围绕 AI 技术商业化落地、新商科专业规划、全品类活动策展及职业技能人才培育开展服务。项目搭建 AI 智能交互体系与 AI 数字人内容输出矩阵,为政校企提供 AI 场景应用、全域获客、职业技能培训、赛事集训、资格认证、品牌全案、项目申报等全链路解决方案,与多方深度合作,以数智化能力助力人才培养、品牌增长与产业创新升级。', zone: '待分配', industry: '人工智能' },
|
||||
{ no: 26, name: '朵哈·玫瑰特色产业链(项目)', founder: '刘志贤', founderBio: '云南轻纺职业学院艺术设计专业大二在校生。', bio: '本项目启动于2026年4月,由云南轻纺学院在校大学生发起,立足云南安宁八街核心产区,以地理标志产品重瓣红玫瑰为核心原料,主打玫瑰米酿特色饮品。项目聚焦天然健康与地域特色,面向年轻消费群体、养生人群及伴手礼市场,与省内农业产业化龙头企业深度合作,实现原料优选、标准化生产与品质管控,打造具有云南地域标识的特色饮品品牌,助力地方特色农业提质升级。', zone: '成长区', industry: '文化创意' },
|
||||
{ no: 27, name: '昆明云韵体育有限责任公司', founder: '舒江贵', founderBio: '2020年7月毕业于麒麟区职业学校运动训练专业。', bio: '本项目发起于2026年4月,项目致力于在云南注册成立 UCI 男子自行车洲际队,打造立足云南、辐射全国、征战亚洲赛场的职业自行车竞技项目。项目依托云南天然高原训练优势、省级体育文旅政策红利、城市品牌打造需求,构建职业赛事 + 商业赞助 + 高原训练 + 体育文旅 + 青训人才培养一体化运营模式', zone: '待分配', industry: '文化创意' },
|
||||
{ no: 28, name: '昆明舒护安养老服务有限责任公司', founder: '严宇佳', founderBio: '2019 年毕业于云南艺术学院广播电视编导专业。', bio: '本项目为OPC一人公司,入驻园区孵化仅一个月,成功吸纳就业3人,目前正稳步扩大公司规模,项目专注正规医疗护理服务,主营医院专属陪护与居家上门护理,覆盖术后康复、失能半失能照料、慢病护理、偏瘫照护、生活监护等全场景需求。所有服务人员均经专业培训与规范管理,服务流程标准化、透明化,与医疗机构深度合作,衔接诊疗与养护环节,为病患、长者提供安全可靠的一站式护理方案,切实缓解家庭照护压力。', zone: '成长区', industry: '生物医药' },
|
||||
{ no: 29, name: '南菌优培—食用菌专用生物制剂创新与高原特色野生菌高值化全链开发(项目)', founder: '王荣飞', founderBio: '2026年毕业于中山大学外科学专业。', bio: '本项目起源于对云南高原野生菌资源优势与产业痛点的深度洞察,目前已经有三年以上的前期研发基础和产业化探索,相关生物源成分已做过试验验证,同时具备菌类基础试验开展条件,核心团队汇聚微生物研发、农业应用、医药研发三类专业人才,由分析化学博士、农业硕士、医学硕士等跨学科成员组成。项目计划于2026年7月完成公司注册并正式启动运营,后续将聚焦食用菌专用生物制剂创新与高原特色野生菌高值化全链开发两大核心,通过引入新型生物源成分如海藻糖-6-磷酸、寡糖等进行生物制剂创新,同时推动云南高原特色野生菌如硬皮地星、硬皮马勃等高值化开发,充分发挥云南地域特色与优势,紧跟高原特色农业、生物制造、林下经济等领域发展步伐,以科技创新驱动食用菌产业提质增效、绿色转型与价值跃升。', zone: '成长区', industry: '生物医药' },
|
||||
{ no: 30, name: '五华区丽裳文化艺术工作室', founder: '王娟', founderBio: '2024 年毕业于昆明幼儿师范高等专科学校幼儿教育专业。', bio: '本项目成立于2026年4月,丽裳文化艺术工作室专注传统文化、民俗活动、茶文化及国风品牌活动策划执行,同时打造特色文旅研学项目。工作室立足云南丰富民俗文化资源,与文化艺术、传媒传播、茶艺等领域人才深度合作,为市场提供高品质文化创意服务。依托云南自贸区开放优势,积极推动民俗文创、茶产品、干花制品及高原特色产品出海,搭建 “账号引流 + 内容传播 + 产品出海” 运营体系,以文化创意赋能地方产业,助力云南特色文化与产品拓展海外市场。', zone: '成长区', industry: '文化创意' },
|
||||
{ no: 31, name: '云南星瑞航空(项目)', founder: '尹永浩', founderBio: '2019年毕业于滇西科技师范学院亚洲微电影学院数字媒体技术专业。', bio: '本项目启动于2026年4月,云南星瑞航空专注低空智能应用,以大疆行业级无人机为核心装备,提供全流程专业服务。业务涵盖无人机测绘、三维建模、数字孪生、应急救援、公共安全巡检、夜间红外搜救及电力、光伏、油气管道、森林防火等作业,并深耕文物数字化保护,完成古建筑高精度三维建档与展示。项目与各行业伙伴深度合作,具备硬件销售、数据处理、技术支持、人才培训一体化能力,为测绘、应急、文旅、城市规划、能源等领域提供一站式低空解决方案。', zone: '成长区', industry: '低空经济' },
|
||||
{ no: 32, name: '综合性云南直播私域服务平台', founder: '金满满', founderBio: '2026年7月毕业于周口师范学院学前教育专业', bio: '本项目为综合性云南直播私域服务平台,由 2026 年 7 月毕业于周口师范学院学前教育专业的金满满担任项目负责人。平台深耕云南本土特色资源,打造专属自主 IP 形象,以短视频创作 + 全域直播运营为核心载体,在直播带货、专业咨询服务中沉淀粉丝用户,搭建全国性自主私域流量池,通过精细化运营强化用户粘性、实现全链路商业变现。同时平台搭建大学生创业孵化通道,吸纳高校创业人才加入,构建可持续人才迭代体系,助力项目长效发展,打造云南特色直播私域标杆。', zone: '成长区', industry: '跨境电商' },
|
||||
{ no: 33, name: '昆明屿澈电子商务有限公司', founder: '杨君武', founderBio: '2024年6月毕业于云南交通职业技术学院建设工程管理专业。', bio: '本项目为高校毕业生发起的东南亚跨境电商 AI 智能直播 + 短视频出海创业项目,依托 TikTok Shop 正规跨境渠道,面向马来西亚、泰国、越南、菲律宾四国市场运营。主营日用百货、家居用品、电子配件、文创收纳、便携小家电等高性价比刚需产品。 项目采用 AI 数字人 7×24 小时无人直播、AI 短视频批量引流、1688 一件代发轻资产模式,无需实体门店与大量库存,大幅降低人力成本。以 AI 多语种智能直播破解跨境时差、语言障碍、人工成本高三大痛点。', zone: '直播间', industry: '跨境电商' },
|
||||
{ no: 34, name: '中外青少年研学(项目)', founder: '王得荣', founderBio: '2022年武汉大学法学专业毕业,2024—2025年英国剑桥大学短期交流学习', bio: '项目涵盖国际游学与教育交流服务项目。依托全球优质教育资源布局,现已与美国、英国、澳大利亚、新加坡等多国顶尖教育机构达成长期直营战略合作,渠道直连、资源可靠。聚焦青少年、在职教师及家长多元需求,量身打造一站式全球化教育交流解决方案,助力中国学子拓宽国际视野、接轨多元教育,赋能高品质国际文化交流与成长。', zone: 'OPC 空间', industry: '教育培训' },
|
||||
{ no: 35, name: '星杭商务信息咨询昆明有限责任公司', founder: '杨之华', founderBio: '2024年6月毕业于云南师范大学教育学专业。', bio: '公司主要经营商务服务、信息咨询培训、商务接待及服务保障配套。', zone: 'OPC 空间', industry: '企业服务' },
|
||||
{ no: 36, name: '蓝智科技(项目)', founder: '徐安厅', founderBio: '2026年毕业于昆明城市学院计算机科学与技术专业。', bio: '本项目为OPC一人公司,2026年5月启动项目,现有从业人员共计3人。项目立足西南蓝莓特色种植产业,专注农业物联网技术研发与智慧基地数字化运营,兼具硬件设备调试、软件系统开发、田间种植运维综合服务能力。项目自研蓝智全系列智慧种植监控系统,涵盖田间环境监测、基质养分检测、水肥灌溉、种植生产数据管理等功能,打通数据采集、智能分析、自动调控完整链路,持续迭代适配高原蓝莓种植场景的数字化技术基座,为各地蓝莓种植基地提供一体化智慧农业解决方案。', zone: 'OPC 空间', industry: '现代农业' },
|
||||
{ no: 37, name: '云品出滇•纸享万家(项目)', founder: '王帅', founderBio: '2024年毕业于昆明理工大学电子信息工程专业', bio: '企业成立于2026年3月,以新型悬挂式抽纸为核心切入产品,坚持环保理念,积极响应绿色消费与可持续发展的政策方向,依托AI工具辅助与电商平台运营,线上与线下相结合,打造国内电商与跨境电商双增长模式的日用纸品品牌。公司核心业务主攻家居日用纸品的电商销售与运营,兼备AI工具应用、供应链管理与电商运营综合能力,是专注于悬挂式抽纸及相关家居纸品的创新型电商项目。项目当前已在拼多多平台实现真实销售验证。未来将逐步向东南亚跨境电商市场拓展,向国内+海外双市场并行的家居日用品品牌方向持续演进。', zone: 'OPC 空间', industry: '跨境电商' },
|
||||
{ no: 38, name: '云迹轻旅——昆明本土一站式轻量化文旅服务工作室(项目)', founder: '王海璇', founderBio: '毕业于云南大学工商管理(MBA),10年云南⽂旅从业经验。', bio: '云迹轻旅——昆明本土一站式轻量化文旅服务工作室,采用单人OPC轻资产运营模式。项目深度整合云南昆明丰富的文旅与非遗资源,构建“内容+服务+技术”三位一体的业务矩阵:文旅自媒体 IP 孵化、私人小众旅行定制、非遗自然研学小团、文旅商业咨询策划、AI 数字文旅技术服务五大板块协同发力。精准切入云南文旅市场“体验升级、供给分散、数字化薄弱”的痛点,面向 C 端游客提供个性化、避坑式、深度文化体验的旅行解决方案,面向 B端本地文旅商家提供低本、高转化的新媒体代运营与数字化升级服务,致力于成为昆明本土最具温度与创新力的一站式轻量化文旅综合服务商。', zone: 'OPC 空间', industry: '文旅康养' },
|
||||
{ no: 39, name: '启元人工智能科技(项目)', founder: '李瑞康', founderBio: '2017年毕业于吉林动画学院广播电视编导专业。', bio: '本项目为OPC一人公司,专注于8-12岁儿童人工智能工具的技术推广与科普体验服务。自主研发适配低龄儿童的 AI 实操工具包,依托大学生创业园共享空间开展次制AI创意科技体验沙龙,为儿童提供 AI 绘画、AI音乐创作,AI视频制作工具的操作指导与创意交流平台,帮助孩子在动手实践中感受AI科技魅力,培养创新思维。', zone: 'OPC 空间', industry: '人工智能' },
|
||||
];
|
||||
|
||||
/* 滚动速度档位(慢速为默认) */
|
||||
const SPEED_DUR = { slow: '180s', normal: '100s', fast: '60s' };
|
||||
const SPEED_LABEL = { slow: '慢速', normal: '标准', fast: '快速' };
|
||||
|
||||
/* 行业主题色 */
|
||||
const IND_COLOR = {
|
||||
'人工智能': '#22d3ee', '跨境电商': '#a78bfa', '教育培训': '#fbbf24',
|
||||
'文旅康养': '#fb923c', '文化创意': '#f472b6', '生物医药': '#34d399',
|
||||
'低空经济': '#4c8dff', '现代农业': '#4ade80', '绿色环保': '#17b26a',
|
||||
'企业服务': '#94a3b8', '新材料': '#f59e0b', '其他': '#8b9cb8',
|
||||
};
|
||||
|
||||
const initials = (n) => (n ? n.slice(0, 1) : '企');
|
||||
|
||||
export default function CompanyWall() {
|
||||
const [paused, setPaused] = useState(false);
|
||||
const [speed, setSpeed] = useState('slow');
|
||||
|
||||
// MQTT 控制:dpm:wall-control(useMqttControl 分发 opc/display/command)
|
||||
useEffect(() => {
|
||||
const onCtrl = (e) => {
|
||||
const { action, speed: spd } = e.detail || {};
|
||||
if (action === 'pause') setPaused(true);
|
||||
else if (action === 'resume') setPaused(false);
|
||||
else if (action === 'speed' && SPEED_DUR[spd]) setSpeed(spd);
|
||||
};
|
||||
window.addEventListener('dpm:wall-control', onCtrl);
|
||||
return () => window.removeEventListener('dpm:wall-control', onCtrl);
|
||||
}, []);
|
||||
|
||||
// 全局手势识别(GlobalVision 挂载 → dpm:gesture 事件)示例绑定:
|
||||
// 举手/挥手 暂停-继续 · 举拳 加速 · 双臂举起 紧急暂停 · 指向 循环变速 · 合十 恢复慢速
|
||||
useEffect(() => {
|
||||
const onGesture = (e) => {
|
||||
const type = e.detail?.type;
|
||||
if (type === 'raise' || type === 'wave') setPaused((p) => !p);
|
||||
else if (type === 'fist') setSpeed('fast');
|
||||
else if (type === 'both_up') setPaused(true);
|
||||
else if (type === 'hands_close') setSpeed('slow');
|
||||
else if (type === 'point') setSpeed((sp) => (sp === 'slow' ? 'normal' : sp === 'normal' ? 'fast' : 'slow'));
|
||||
};
|
||||
window.addEventListener('dpm:gesture', onGesture);
|
||||
return () => window.removeEventListener('dpm:gesture', onGesture);
|
||||
}, []);
|
||||
|
||||
const dur = SPEED_DUR[speed];
|
||||
const label = SPEED_LABEL[speed];
|
||||
|
||||
return (
|
||||
<div className="cw-page">
|
||||
<div className="cw-glow cw-glow-a" />
|
||||
<div className="cw-glow cw-glow-b" />
|
||||
|
||||
<header className="cw-head">
|
||||
<div className="cw-title">
|
||||
<span className="cw-eyebrow">KUNMING UNIVERSITY STUDENT ENTREPRENEURSHIP PARK</span>
|
||||
<h1>入驻企业展示墙</h1>
|
||||
<p className="cw-sub">昆明市大学生创业园 · {WALL.length} 家入驻企业全量档案 · 11 大行业 · 4 大孵化区域</p>
|
||||
</div>
|
||||
<div className="cw-stats">
|
||||
<div className="cw-stat"><b>{WALL.length}</b><i>入驻企业</i></div>
|
||||
<div className="cw-stat"><b>11</b><i>行业类别</i></div>
|
||||
<div className="cw-stat"><b>4</b><i>孵化区域</i></div>
|
||||
<div className={`cw-status${paused ? ' paused' : ''}`}>
|
||||
<span className={`cw-dot${paused ? ' idle' : ''}`} />{paused ? '已暂停' : '滚动中'} · {label}
|
||||
</div>
|
||||
<div className="cw-vision" title="举手/挥手 暂停-继续 · 举拳 加速 · 指向 变速 · 合十 慢速(全局手势识别)">
|
||||
<span className="cw-vision-ico">🖐</span>
|
||||
<i>举手 暂停/继续 · 举拳 加速 · 合十 慢速</i>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="cw-wall">
|
||||
<div className="cw-track" style={{ animationDuration: dur, animationPlayState: paused ? 'paused' : 'running' }}>
|
||||
{[...WALL, ...WALL].map((c, i) => {
|
||||
const color = IND_COLOR[c.industry] || '#4c8dff';
|
||||
return (
|
||||
<article className="cw-card" key={i} style={{ '--ind': color }}>
|
||||
<span className="cw-idx">{String((i % WALL.length) + 1).padStart(2, '0')}</span>
|
||||
<div className="cw-body">
|
||||
<div className="cw-top">
|
||||
<h3 className="cw-name">{c.name}</h3>
|
||||
<div className="cw-tags">
|
||||
<span className="cw-tag cw-tag-ind" style={{ color, borderColor: color + '55', background: color + '14' }}>{c.industry}</span>
|
||||
<span className="cw-tag cw-tag-zone">{c.zone}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cw-founder">
|
||||
<span className="cw-avatar" style={{ background: `linear-gradient(135deg, ${color}e6, ${color}55)` }}>{initials(c.founder)}</span>
|
||||
<div className="cw-finfo">
|
||||
<span className="cw-flabel">创始人 / 负责人</span>
|
||||
<b className="cw-fname">{c.founder}</b>
|
||||
<p className="cw-fbio">{c.founderBio}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="cw-bio">{c.bio}</p>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <footer className="cw-foot">数据来源:《昆明市大学生创业园入驻企业信息表》 · 39 家入驻企业全量档案 · 悬停暂停查看</footer> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+113
-152
@@ -6,83 +6,21 @@ import { useParkSim } from '../utils/parkData';
|
||||
|
||||
/* =========================================================
|
||||
昆明市大学生创业园 · OPC 智能园区数字运营中心
|
||||
—— 数据大屏(浅色科技风 · 数据来自 FastAPI 后端)
|
||||
—— 数据大屏(真实数据展示)
|
||||
---------------------------------------------------------
|
||||
基础数据来源:
|
||||
· 《昆明市创业园宣传册》:在园项目 150+ / 累计孵化 200+ /
|
||||
带动就业 2000+ / 累计营收 2 亿+ / 企业估值 5 亿+ /
|
||||
高企 32 家 / 重点研发项目 18 项 / 大赛奖项 47 项
|
||||
· 《昆明市大学生创业园入驻企业信息表》:39 家本期入驻企业
|
||||
(区域、行业、名称)
|
||||
大屏实时数据由后端 GET /api/dashboard/snapshot 提供(每 2.2s 刷新)
|
||||
数据口径(用户指定,仅两个权威来源):
|
||||
· 《昆明市大学生创业园运行情况统计表(2026年7月).xls》
|
||||
实际入驻 39 家 / 可容纳 49 个 / 累计投入 560 万 /
|
||||
带动就业 213 人 / 累计生产经营总额 252.15 万 / 累计税利 1.03 万 / 面积 3000㎡
|
||||
· 《昆明市创业园宣传册.pdf》
|
||||
2009 年成立 / 2012 年省级创业园 / 民航路 229 号 / 四大孵化区域 / 28 家企业名录
|
||||
测算口径(用户确认):
|
||||
· TOKEN:月均 200 亿 tokens → 日均 ≈ 6.67 亿,实时 ≈ 7,700 t/s,近30日累计 200 亿
|
||||
· 工具调用:按每次调用约 2 万 tokens 折算(今日 ≈ 3.3 万次)
|
||||
TOKEN/工具为基于真实锚点的测算演示,其余为真实统计值
|
||||
========================================================= */
|
||||
|
||||
/* ---------- 真实基础数据(入驻企业信息表) ---------- */
|
||||
// [企业名, 区域, 行业]
|
||||
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' },
|
||||
];
|
||||
|
||||
/* 模型占比 */
|
||||
/* ---------- 模型占比(TOKEN 构成演示分布) ---------- */
|
||||
const MODELS = [
|
||||
{ name: 'DeepSeek-V3', value: 38, color: '#4c8dff' },
|
||||
{ name: '通义千问', value: 21, color: '#22d3ee' },
|
||||
@@ -92,7 +30,7 @@ const MODELS = [
|
||||
{ name: '其他', value: 5, color: '#64748b' },
|
||||
];
|
||||
|
||||
/* 工具调用占比 */
|
||||
/* 工具调用占比(演示分布) */
|
||||
const TOOL_RANK = [
|
||||
{ name: '文档生成', value: 34 },
|
||||
{ name: '数据查询', value: 27 },
|
||||
@@ -101,8 +39,6 @@ const TOOL_RANK = [
|
||||
{ name: '语音合成', value: 10 },
|
||||
];
|
||||
|
||||
const COMPANY_NAMES = PROJECTS.map((p) => p[0]);
|
||||
|
||||
/* ---------- 工具函数 ---------- */
|
||||
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));
|
||||
@@ -154,7 +90,7 @@ function Pill({ children, tone = 'blue' }) {
|
||||
|
||||
/* 面积图 + 折线(viewBox 高度动态匹配容器,满宽绘制、无留白错位) */
|
||||
function AreaChart({ series, color, height = 100, unit = '', lastLabel }) {
|
||||
const W = 300, PL = 8, PR = 8, PT = 16, PB = 18;
|
||||
const W = 300, PL = 8, PR = 8, PT = 26, PB = 26;
|
||||
const ref = useRef(null);
|
||||
const [vh, setVh] = useState(height); // viewBox 高度(按容器宽高比计算)
|
||||
useEffect(() => {
|
||||
@@ -193,10 +129,10 @@ function AreaChart({ series, color, height = 100, unit = '', lastLabel }) {
|
||||
<polyline points={line} fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
||||
style={{ filter: `drop-shadow(0 0 5px ${color}88)` }} />
|
||||
<circle cx={px(series.length - 1)} cy={py(series[series.length - 1])} r="3" fill={color} />
|
||||
<text x={px(0)} y={H - 5} fontSize="8.5" fill="#8fa1ba">30 天前</text>
|
||||
<text x={px(series.length - 1)} y={H - 5} textAnchor="end" fontSize="8.5" fill="#8fa1ba">今日</text>
|
||||
<text x={px(0)} y={H - 7} fontSize="8.5" fill="#8fa1ba">30 天前</text>
|
||||
<text x={px(series.length - 1)} y={H - 7} textAnchor="end" fontSize="8.5" fill="#8fa1ba">今日</text>
|
||||
{lastLabel && (
|
||||
<text x={W - PR} y={PT - 6} textAnchor="end" fontSize="10.5" fontWeight="700" fill="#1d2c44">{lastLabel}</text>
|
||||
<text x={W - PR} y={PT - 9} textAnchor="end" fontSize="11" fontWeight="700" fill="#1d2c44">{lastLabel}</text>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
@@ -262,7 +198,7 @@ function KpiCard({ icon, label, value, unit, sub, tone }) {
|
||||
<div className="bd-kpi-body">
|
||||
<div className="bd-kpi-label">{label}</div>
|
||||
<div className="bd-kpi-value">
|
||||
{tone === 'revenue' || tone === 'token'
|
||||
{tone === 'revenue'
|
||||
? <span>{fmtWan(v)}</span>
|
||||
: <span>{fmtInt(v)}</span>}
|
||||
{unit && <em>{unit}</em>}
|
||||
@@ -284,14 +220,14 @@ function KpiCard({ icon, label, value, unit, sub, tone }) {
|
||||
(园区实时动态 → 整体页面右下方)
|
||||
========================================================= */
|
||||
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' },
|
||||
{ icon: 'gauge', label: '成立时间', value: `${d.park.founded} 年`, color: '#17b26a' },
|
||||
{ icon: 'trophy', label: '省级创业园', value: `${d.park.provinceLevel} 年认定`, color: '#2f6bff' },
|
||||
{ icon: 'building', label: '可容纳企业', value: `${d.projects.capacity} 个`, color: '#f79009' },
|
||||
{ icon: 'coin', label: '累计税利', value: `${fmtWan(d.revenue.tax)}万元`, color: '#8b5cf6' },
|
||||
{ icon: 'headset', label: '报名电话', value: d.park.phone, color: '#f04438' },
|
||||
{ icon: 'map-pin', label: '园区地址', value: d.park.address, color: '#0ea5e9' },
|
||||
];
|
||||
return (
|
||||
<section className="bd-overview">
|
||||
@@ -309,23 +245,20 @@ function TodayOverview({ d }) {
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="bd-head-right">
|
||||
<Pill tone="green">实时刷新 · 2s</Pill>
|
||||
</div>
|
||||
</div>
|
||||
{/* 五个指标卡片:必须一行展示 */}
|
||||
<div className="bd-overview-grid">
|
||||
<div className="bd-cell bd-cell-token">
|
||||
<KpiCard tone="token" icon="bolt" label="TOKEN 消耗" value={d.token.today}
|
||||
sub={`累计 ${fmtWan(d.token.total)} · 实时 ${d.token.rate} t/s`} />
|
||||
<KpiCard tone="token" icon="bolt" label="TOKEN 消耗" value={d.token.today} unit="万 tokens"
|
||||
sub={`实时 ${d.token.rate} t/s · 近30日 ${fmtWan(d.token.total)}`} />
|
||||
</div>
|
||||
<div className="bd-cell bd-cell-revenue">
|
||||
<KpiCard tone="revenue" icon="coin" label="营收总数" value={d.revenue.total}
|
||||
sub={`今日 +${d.revenue.today}万 · 环比 +${d.revenue.growth}%`} />
|
||||
<KpiCard tone="revenue" icon="coin" label="营收总额" value={d.revenue.total} unit="元"
|
||||
sub={`累计生产经营总额 · 税利 ${fmtWan(d.revenue.tax)}元`} />
|
||||
</div>
|
||||
<div className="bd-cell bd-cell-projects">
|
||||
<KpiCard tone="project" icon="building" label="入园项目" value={d.projects.inPark} unit="家"
|
||||
sub={`累计孵化 ${d.projects.cum} 家 · 今日新增 ${d.projects.todayNew}`} />
|
||||
sub={`可容纳 ${d.projects.capacity} 个 · 累计投入 ${d.projects.invested} 万`} />
|
||||
</div>
|
||||
<div className="bd-cell bd-cell-tools">
|
||||
<KpiCard tone="tool" icon="wrench" label="工具调用" value={d.tools.today} unit="次"
|
||||
@@ -333,7 +266,7 @@ function TodayOverview({ d }) {
|
||||
</div>
|
||||
<div className="bd-cell bd-cell-jobs">
|
||||
<KpiCard tone="job" icon="users" label="带动就业" value={d.jobs.total} unit="人"
|
||||
sub={`今日新增 ${d.jobs.todayNew} 人 · 高企 32 家`} />
|
||||
sub={`入驻企业累计带动就业 · 2026年7月统计`} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -352,19 +285,26 @@ function TokenBig({ d }) {
|
||||
<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>{fmtInt(d.token.today)} 万 tokens</b></span>
|
||||
<span className="bd-ost-chip"><i>近30日</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-real-metrics">
|
||||
<span>首 token 平均 <b>{d.live.firstToken.toFixed(1)}s</b></span>
|
||||
<span>吞吐 <b>{d.live.throughput}</b> tok/s</span>
|
||||
<span>缓存命中 <b>{d.live.cacheHit.toFixed(1)}%</b></span>
|
||||
<span>会话输入 <b>{d.live.inputM.toFixed(1)}M</b> / 输出 <b>{d.live.outputM.toFixed(1)}M</b> tok</span>
|
||||
<span><b>{d.live.rounds}</b> 轮 · <b>{d.live.steps}</b> 步</span>
|
||||
</div>
|
||||
<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)}万/日`} />
|
||||
lastLabel={`近30日 ${fmtWan(d.token.series[d.token.series.length - 1])}/日`} />
|
||||
</div>
|
||||
<div className="bd-big-right">
|
||||
<div className="bd-donut-block">
|
||||
<Donut items={MODELS} size={150} thickness={16} centerTop="1.28亿" centerBottom="累计 tokens" />
|
||||
<Donut items={MODELS} size={150} thickness={16} centerTop="200亿" centerBottom="月均 tokens" />
|
||||
</div>
|
||||
<div className="bd-model-list">
|
||||
{MODELS.map((m) => (
|
||||
@@ -397,7 +337,7 @@ function ToolsBig({ d }) {
|
||||
<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>
|
||||
<span className="bd-ost-chip"><i>服务节点</i><b>{d.tools.nodes} / {d.tools.nodes} 在线</b></span>
|
||||
</div>
|
||||
} />
|
||||
<div className="bd-big-body">
|
||||
@@ -412,6 +352,12 @@ function ToolsBig({ d }) {
|
||||
<HBar items={TOOL_RANK} color="#2f6bff" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="bd-real-metrics">
|
||||
<span>LLM 推理 <b>15m27s</b></span>
|
||||
<span>工具调用 <b>9m56s</b></span>
|
||||
<span><b>81</b> 步 / <b>2</b> 轮会话</span>
|
||||
<span>每次调用 ≈ <b>15万</b> tokens</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -422,28 +368,27 @@ function ProjectsPanel({ d }) {
|
||||
<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> */}
|
||||
<span className="bd-ost-chip"><i>实际入驻</i><b>{d.projects.inPark} 家</b></span>
|
||||
<span className="bd-ost-chip"><i>可容纳</i><b>{d.projects.capacity} 个</b></span>
|
||||
<span className="bd-ost-chip"><i>累计投入</i><b>{d.projects.invested} 万</b></span>
|
||||
</div>
|
||||
} />
|
||||
<div className="bd-zone-wrap">
|
||||
<Donut items={ZONE_DIST} size={96} thickness={13} centerTop="39" centerBottom="本期企业" />
|
||||
<div className="bd-hbar-title">四大孵化区域(宣传册)</div>
|
||||
<div className="bd-zone-legend">
|
||||
{ZONE_DIST.map((z) => (
|
||||
{d.zones.map((z) => (
|
||||
<div className="bd-zone-row" key={z.name}>
|
||||
<span className="bd-legend-dot" style={{ background: z.color }} />
|
||||
<span className="bd-zone-name">{z.name}</span>
|
||||
<span className="bd-zone-val">{z.value} 家</span>
|
||||
<span className="bd-legend-dot" style={{ background: '#4c8dff' }} />
|
||||
<span className="bd-zone-name" title={z.desc}>{z.name}</span>
|
||||
<span className="bd-zone-val">{z.period}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bd-ticker-title">入驻企业名录 <span>(39 家)</span></div>
|
||||
<div className="bd-ticker-title">入驻企业名录 <span>({d.companies.length} 家)</span></div>
|
||||
<div className="bd-ticker">
|
||||
<div className="bd-ticker-inner">
|
||||
{[...COMPANY_NAMES, ...COMPANY_NAMES].map((c, i) => (
|
||||
{[...d.companies, ...d.companies].map((c, i) => (
|
||||
<div className="bd-ticker-item" key={i}>
|
||||
<span className="bd-ticker-dot" />
|
||||
{c}
|
||||
@@ -457,30 +402,23 @@ function ProjectsPanel({ d }) {
|
||||
|
||||
/* ---------- 带动就业(组视图 · 中密度) ---------- */
|
||||
function JobsPanel({ d }) {
|
||||
const mixTotal = d.industryMix.reduce((a, b) => a + b.value, 0);
|
||||
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> */}
|
||||
<span className="bd-ost-chip"><i>累计带动就业</i><b>{fmtInt(d.jobs.total)} 人</b></span>
|
||||
<span className="bd-ost-chip"><i>统计时间</i><b>2026年7月</b></span>
|
||||
<span className="bd-ost-chip"><i>来源</i><b>运行情况统计表</b></span>
|
||||
</div>
|
||||
} />
|
||||
<div className="bd-hbar-title">行业就业分布</div>
|
||||
<div className="bd-jobs-bars">
|
||||
{INDUSTRY.slice(0, 6).map((it, i) => {
|
||||
const max = Math.max(...INDUSTRY.map((x) => x.value));
|
||||
return (
|
||||
<div className="bd-vbar" key={i}>
|
||||
<div className="bd-vbar-track">
|
||||
<div className="bd-vbar-fill" style={{ height: `${(it.value / max) * 100}%`, background: `linear-gradient(180deg, ${it.color}, ${it.color}33)` }} />
|
||||
</div>
|
||||
<span className="bd-vbar-val">{it.value}</span>
|
||||
<span className="bd-vbar-name">{it.name}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="bd-hbar-title">园区企业行业比例 <span>({mixTotal} 家)</span></div>
|
||||
<HBar items={d.industryMix} color="#2f6bff" unit="家" />
|
||||
<div className="bd-big-stats bd-jobs-grid">
|
||||
<div className="bd-big-stat"><span>累计带动就业</span><b>{fmtInt(d.jobs.total)}<em>人</em></b></div>
|
||||
<div className="bd-big-stat"><span>累计生产经营总额</span><b>{d.revenue.total}<em>万元</em></b></div>
|
||||
<div className="bd-big-stat"><span>累计上缴税利</span><b>{d.revenue.tax}<em>万元</em></b></div>
|
||||
<div className="bd-big-stat"><span>可容纳企业</span><b>{d.projects.capacity}<em>个</em></b></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -490,22 +428,34 @@ function JobsPanel({ d }) {
|
||||
function RevenuePanel({ d }) {
|
||||
return (
|
||||
<div className="bd-card bd-revenue-panel">
|
||||
<CardHead title="营收总数" icon="coin" right={
|
||||
<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>
|
||||
<span className="bd-ost-chip"><i>累计生产经营总额</i><b>{fmtWan(d.revenue.total)}元</b></span>
|
||||
<span className="bd-ost-chip"><i>累计税利</i><b>{fmtWan(d.revenue.tax)}元</b></span>
|
||||
<span className="bd-ost-chip"><i>统计时间</i><b>2026年7月</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)}万/日`} />
|
||||
<div className="bd-big-stats bd-revenue-nums">
|
||||
<div className="bd-big-stat"><span>累计生产经营总额</span><b>{d.revenue.total}<em>万元</em></b></div>
|
||||
<div className="bd-big-stat"><span>累计上缴税利总额</span><b>{d.revenue.tax}<em>万元</em></b></div>
|
||||
<div className="bd-big-stat"><span>带动就业</span><b>{fmtInt(d.jobs.total)}<em>人</em></b></div>
|
||||
<div className="bd-big-stat"><span>实际入驻企业</span><b>{d.projects.inPark}<em>家</em></b></div>
|
||||
<div className="bd-big-stat"><span>累计投入运营资金</span><b>{d.projects.invested}<em>万元</em></b></div>
|
||||
<div className="bd-big-stat"><span>可容纳企业</span><b>{d.projects.capacity}<em>个</em></b></div>
|
||||
<div className="bd-big-stat"><span>园区面积</span><b>{d.park.area}<em>㎡</em></b></div>
|
||||
<div className="bd-big-stat"><span>成立时间</span><b>{d.park.founded}<em>年</em></b></div>
|
||||
</div>
|
||||
<div className="bd-revenue-tags">
|
||||
<span><Icon name="trophy" size={14} /> 大赛奖项 <b>47</b> 项</span>
|
||||
<span><Icon name="flask" size={14} /> 重点研发 <b>18</b> 项</span>
|
||||
<span><Icon name="factory" size={14} /> 高新技术企业 <b>32</b> 家</span>
|
||||
<span><Icon name="building" size={14} /> 免费办公空间 <b>≥20</b> ㎡</span>
|
||||
<span><Icon name="coin" size={14} /> 一次性创业补贴 <b>最高10万</b></span>
|
||||
<span><Icon name="wallet" size={14} /> 担保贷款贴息 <b>最高50万</b></span>
|
||||
<span><Icon name="flask" size={14} /> 科技计划经费 <b>最高30万</b></span>
|
||||
</div>
|
||||
<div className="bd-real-metrics">
|
||||
<span>单位负责人 <b>李云鹏</b></span>
|
||||
{/* <span>填表人 <b>李禹贤</b></span>
|
||||
<span>填报时间 <b>2026-07-31</b></span> */}
|
||||
<span>报名电话 <b>{d.park.phone}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -568,17 +518,28 @@ function MiddleRotator({ d }) {
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------- 实时动态(纯列表,外层自行包裹卡片) ---------- */
|
||||
function LiveFeed({ feed, limit = 7 }) {
|
||||
/* ---------- 实时动态(3 列 × 2 行,每几秒整体轮换加载) ---------- */
|
||||
function LiveFeed({ feed, limit = 6 }) {
|
||||
const [page, setPage] = useState(0);
|
||||
const chunks = [];
|
||||
for (let i = 0; i < feed.length; i += limit) chunks.push(feed.slice(i, i + limit));
|
||||
useEffect(() => {
|
||||
if (chunks.length <= 1) return;
|
||||
const iv = setInterval(() => setPage((p) => (p + 1) % chunks.length), 5000);
|
||||
return () => clearInterval(iv);
|
||||
}, [chunks.length]);
|
||||
const chunk = chunks[page % chunks.length] || [];
|
||||
return (
|
||||
<div className="bd-feed">
|
||||
{feed.slice(0, limit).map((e) => (
|
||||
<div className="bd-feed-item" key={e.id}>
|
||||
<span className="bd-feed-ico"><Icon name={e.icon} size={15} /></span>
|
||||
<span className="bd-feed-text">{e.text}</span>
|
||||
<span className="bd-feed-time">{e.time}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="bd-feed-grid" key={page}>
|
||||
{chunk.map((e) => (
|
||||
<div className="bd-feed-item" key={e.id}>
|
||||
<span className="bd-feed-ico"><Icon name={e.icon} size={15} /></span>
|
||||
<span className="bd-feed-text">{e.text}</span>
|
||||
<span className="bd-feed-time">{e.time}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { S2sWsRealtimeClient } from '../voice/s2s-ws-client.js';
|
||||
import useVisionDetection, { VISION_DWELL_MS } from '../voice/useVisionDetection.js';
|
||||
import ParkOverviewStrip from '../components/ParkOverviewStrip';
|
||||
import { useParkSim } from '../utils/parkData';
|
||||
import { API_BASE } from '../config';
|
||||
import '../styles/datascreen.css';
|
||||
import './voice-original.css';
|
||||
@@ -97,14 +95,14 @@ const icons = { micOn: <svg className="mic-on" viewBox="0 0 24 24" fill="none"
|
||||
};
|
||||
|
||||
// 单条气泡:挂载后 rAF 加 `.in` 触发淡入(复刻原版 _spawnBubble 的进场动画)
|
||||
function Bubble({ m }) {
|
||||
function Bubble({ m, latest }) {
|
||||
const [show, setShow] = useState(false);
|
||||
useEffect(() => {
|
||||
const raf = requestAnimationFrame(() => setShow(true));
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, []);
|
||||
return (
|
||||
<div className={`bubble ${m.role}${show ? ' in' : ''}${m.leaving ? ' out' : ''}`}>
|
||||
<div className={`bubble ${m.role}${show ? ' in' : ''}${latest ? ' latest' : ' history'}`}>
|
||||
<div className="bubble-role">{m.role === 'user' ? 'You' : 'Assistant'}</div>
|
||||
<div className={`bubble-body${m.partial ? ' partial' : ''}`}>{m.text}</div>
|
||||
</div>
|
||||
@@ -118,8 +116,6 @@ export default function VoiceAssistant() {
|
||||
const [errorMsg, setErrorMsg] = useState('');
|
||||
// 全局人物识别开关(MQTT vision_set 控制)
|
||||
const [visionEnabled, setVisionEnabled] = useState(true);
|
||||
// 园区数据(页脚概览条;后端 /api/dashboard/snapshot,离线本地模拟兜底)
|
||||
const d = useParkSim();
|
||||
|
||||
const clientRef = useRef(null);
|
||||
const audioCtxRef = useRef(null);
|
||||
@@ -203,32 +199,18 @@ export default function VoiceAssistant() {
|
||||
const key = role === 'user' ? 'user' : 'assistant';
|
||||
let next = [...messagesRef.current];
|
||||
const last = next.length ? next[next.length - 1] : null;
|
||||
if (partial && last && last.role === key && !last.leaving) {
|
||||
// 同一条逐字流:更新文本 + 续期 4s(气泡保持显示直到说完)
|
||||
scheduleBubbleDismiss(last.id);
|
||||
if (partial && last && last.role === key) {
|
||||
// 同一条逐字流:更新文本,持续展示不消失
|
||||
next[next.length - 1] = { ...last, text, partial: true };
|
||||
} else {
|
||||
const id = `${Date.now()}-${Math.random()}`;
|
||||
next.push({ id, role: key, text, partial });
|
||||
// 上限 8 条:最旧优先淡出(保留正在更新的用户气泡)
|
||||
const visible = next.filter((m) => !m.leaving);
|
||||
if (visible.length > 8) {
|
||||
const activeUser = [...visible].reverse().find((m) => m.role === 'user' && m.partial);
|
||||
const victim = visible.find((m) => m !== activeUser) ?? visible[0];
|
||||
if (victim) {
|
||||
next = next.map((m) => (m.id === victim.id ? { ...m, leaving: true } : m));
|
||||
const remove = setTimeout(() => {
|
||||
setMessages((prev) => prev.filter((x) => x.id !== victim.id));
|
||||
bubbleTimersRef.current.delete(victim.id);
|
||||
}, 400);
|
||||
bubbleTimersRef.current.set(victim.id, { remove });
|
||||
}
|
||||
}
|
||||
scheduleBubbleDismiss(id);
|
||||
// 最多保留 4 条(最新大字 + 向上历史小字;正在逐字更新的消息位于末位,必然保留)
|
||||
next = next.slice(-4);
|
||||
}
|
||||
messagesRef.current = next;
|
||||
setMessages(next);
|
||||
}, [scheduleBubbleDismiss, markLeaving]);
|
||||
}, []);
|
||||
|
||||
// ── 工具调用执行器:LLM 请求工具 → 转发后端 /api/tools/exec → 结果回传 ──
|
||||
const onToolCall = useCallback(async (e) => {
|
||||
@@ -596,12 +578,11 @@ export default function VoiceAssistant() {
|
||||
<p id="circle-subcaption" className="circle-subcaption" hidden />
|
||||
</main>
|
||||
|
||||
{/* 页脚:园区概览条(与数据大屏 / 媒体播放页一致) */}
|
||||
<ParkOverviewStrip d={d} />
|
||||
|
||||
{/* 右下角气泡(复刻原版:进场 in / 4s 自动淡出 out / 阶梯字号) */}
|
||||
<div id="bubble-stack" className="bubble-stack" aria-live="polite" aria-atomic="false">
|
||||
{messages.map((m) => <Bubble key={m.id} m={m} />)}
|
||||
{messages.slice(-4).map((m, i, arr) => (
|
||||
<Bubble key={m.id} m={m} latest={i === arr.length - 1} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 视觉识别:摄像头默认开启;video 始终渲染(ref 先于启动存在),未开启时隐藏 */}
|
||||
@@ -621,12 +602,7 @@ export default function VoiceAssistant() {
|
||||
</div>
|
||||
</div>
|
||||
{vision.status === 'error' && !vision.cameraOn && (
|
||||
<div className="voice-vision-status" style={{ bottom: 62 }}>⚠️ {visionErrorText}</div>
|
||||
)}
|
||||
{vision.status === 'facing' && (
|
||||
<div className="voice-vision-status">
|
||||
检测到 <b>{vision.faces} 人</b>,正对摄像头 <b>{(vision.dwellMs / 1000).toFixed(1)}s</b>,停留 {VISION_DWELL_MS / 1000}s 后自动问候
|
||||
</div>
|
||||
<div className="voice-vision-status">⚠️ {visionErrorText}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -57,14 +57,14 @@
|
||||
.voice-cam-toggle:hover { border-color: #2f6bff; }
|
||||
.voice-cam-toggle.on { background: #2f6bff; color: #fff; border-color: #2f6bff; }
|
||||
|
||||
/* 底部左侧摄像头实时窗口(放大,实时画面) */
|
||||
/* 底部左侧摄像头实时窗口(缩小,页脚已移除贴底) */
|
||||
.voice-cam-pip {
|
||||
position: fixed;
|
||||
left: 18px;
|
||||
bottom: 62px; /* 上移避开底部园区概览条(bd-strip 约 44px 高 + 间隙) */
|
||||
bottom: 16px;
|
||||
z-index: 50;
|
||||
width: 380px;
|
||||
border-radius: 14px;
|
||||
width: 250px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(47, 107, 255, 0.45);
|
||||
box-shadow: 0 10px 28px rgba(29, 79, 216, 0.22);
|
||||
@@ -72,7 +72,7 @@
|
||||
}
|
||||
.voice-cam-pip video {
|
||||
width: 100%;
|
||||
height: 216px;
|
||||
height: 140px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transform: scaleX(-1); /* 镜像自拍视角 */
|
||||
@@ -82,8 +82,8 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 7px 10px;
|
||||
font-size: 12px;
|
||||
padding: 5px 9px;
|
||||
font-size: 11px;
|
||||
color: #e6e8eb;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
@@ -100,20 +100,19 @@
|
||||
.voice-cam-pip .voice-cam-dot.face { background: #4cc2ff; box-shadow: 0 0 0 3px rgba(76, 194, 255, 0.25); }
|
||||
.voice-cam-pip .voice-cam-dot.trigger { background: #f79009; box-shadow: 0 0 0 3px rgba(247, 144, 9, 0.3); }
|
||||
|
||||
/* 识别状态横幅(左下角预览上方) */
|
||||
/* 摄像头异常提示:页脚位置纯文字(非芯片样式) */
|
||||
.voice-vision-status {
|
||||
position: fixed;
|
||||
left: 18px;
|
||||
bottom: 200px; /* 跟随预览上移 44px,保持相对位置 */
|
||||
bottom: 16px;
|
||||
z-index: 50;
|
||||
font-size: 12px;
|
||||
color: #1d2c44;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(47, 107, 255, 0.25);
|
||||
border-radius: 8px;
|
||||
padding: 6px 10px;
|
||||
box-shadow: 0 4px 12px rgba(29, 79, 216, 0.12);
|
||||
font-size: 11px;
|
||||
color: #b42318;
|
||||
line-height: 1.5;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.voice-vision-status b { color: #2f6bff; }
|
||||
|
||||
@@ -133,3 +132,53 @@
|
||||
box-shadow: 0 4px 12px rgba(180, 35, 24, 0.12);
|
||||
}
|
||||
.voice-cam-pip.hidden { display: none; }
|
||||
|
||||
/* ============ 背景体系:与企业展示墙(/wall)一致的网格 + 光晕渐变 ============ */
|
||||
#app {
|
||||
background:
|
||||
radial-gradient(ellipse 55% 45% at 12% -6%, rgba(76, 194, 255, 0.16), transparent 62%),
|
||||
radial-gradient(ellipse 50% 42% at 90% 112%, rgba(47, 107, 255, 0.13), transparent 60%);
|
||||
}
|
||||
#app::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background-image:
|
||||
linear-gradient(rgba(47, 107, 255, 0.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(47, 107, 255, 0.045) 1px, transparent 1px);
|
||||
background-size: 44px 44px;
|
||||
mask-image: radial-gradient(ellipse 90% 80% at 50% 30%, #000 30%, transparent 100%);
|
||||
-webkit-mask-image: radial-gradient(ellipse 90% 80% at 50% 30%, #000 30%, transparent 100%);
|
||||
}
|
||||
#app > *:not(.bubble-stack):not(.voice-cam-pip):not(.voice-cam-toggle):not(.voice-vision-status):not(.voice-secure-warn) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ============ 语音气泡:持续展示 · 最新大字 · 历史小字 ============ */
|
||||
.bubble {
|
||||
color: #1d2c44; /* 浅色背景适配(原版深色文字在浅色下不可见) */
|
||||
}
|
||||
.bubble .bubble-role {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.08em;
|
||||
color: #7d8fa9;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
/* 刚说完(最新一条):大字 */
|
||||
.bubble.latest .bubble-body {
|
||||
font-size: 21px;
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
color: #1d2c44;
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
/* 历史消息:小字,向上排列 */
|
||||
.bubble.history .bubble-body {
|
||||
font-size: 12.5px;
|
||||
line-height: 1.5;
|
||||
color: #4a5b76;
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
+524
-2
@@ -300,12 +300,21 @@
|
||||
height: 128px;
|
||||
}
|
||||
.bd-feed-row .bd-feed {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
/* 每屏一组:3 列 × 2 行,切换时整体淡入(bdSlideIn) */
|
||||
.bd-feed-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 4px 10px;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
align-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.bd-feed-row .bd-feed-item {
|
||||
padding: 4px 10px;
|
||||
@@ -2700,3 +2709,516 @@
|
||||
margin-left: 4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* ============ 真实数据面板补充样式 ============ */
|
||||
.bd-jobs-panel,
|
||||
.bd-revenue-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.bd-jobs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 14px;
|
||||
row-gap: 12px;
|
||||
padding: 8px 2px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.bd-jobs-grid .bd-big-stat {
|
||||
padding: 9px 14px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.bd-jobs-grid .bd-big-stat b { font-size: 19px; }
|
||||
.bd-jobs-grid .bd-big-stat span { font-size: 11.5px; }
|
||||
.bd-jobs-grid .bd-big-stat b em { font-size: 11.5px; }
|
||||
.bd-jobs-panel > .bd-hbars {
|
||||
flex: 0 1 auto;
|
||||
min-height: 0;
|
||||
gap: 5px;
|
||||
}
|
||||
/* 营收总额面板:8 卡紧凑布局,确保不溢出面板 */
|
||||
.bd-revenue-nums {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
column-gap: 14px;
|
||||
row-gap: 12px;
|
||||
padding: 8px 2px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
.bd-revenue-nums .bd-big-stat {
|
||||
padding: 9px 14px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.bd-revenue-nums .bd-big-stat b { font-size: 19px; }
|
||||
.bd-revenue-nums .bd-big-stat span { font-size: 11.5px; }
|
||||
.bd-revenue-nums .bd-big-stat b em { font-size: 11.5px; }
|
||||
.bd-revenue-panel .bd-revenue-tags,
|
||||
.bd-revenue-panel .bd-real-metrics { flex-shrink: 0; }
|
||||
.bd-revenue-panel .bd-real-metrics { margin-top: 6px; }
|
||||
.bd-revenue-panel .bd-real-metrics span { padding: 2px 8px; font-size: 11px; }
|
||||
.bd-jobs-note {
|
||||
margin-top: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 10px;
|
||||
background: rgba(47, 107, 255, 0.06);
|
||||
border: 1px solid rgba(47, 107, 255, 0.12);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
color: var(--bd-sub, #7d8fa9);
|
||||
}
|
||||
.bd-real-metrics {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 8px 0 0;
|
||||
padding: 0 2px;
|
||||
}
|
||||
.bd-real-metrics span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(47, 107, 255, 0.06);
|
||||
border: 1px solid rgba(47, 107, 255, 0.14);
|
||||
font-size: 12px;
|
||||
color: var(--bd-sub, #7d8fa9);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.bd-real-metrics b { color: #2f6bff; font-weight: 700; }
|
||||
/* ============ 入驻企业展示墙(/wall · 全量档案版 · 配色与整体一致) ============ */
|
||||
.cw-page {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: clamp(12px, 1.4vw, 24px) clamp(16px, 2vw, 34px);
|
||||
gap: clamp(10px, 1.2vw, 18px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: var(--bd-ink);
|
||||
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
.cw-page::before {
|
||||
content: '';
|
||||
position: absolute; inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(47, 107, 255, 0.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(47, 107, 255, 0.045) 1px, transparent 1px);
|
||||
background-size: 44px 44px;
|
||||
mask-image: radial-gradient(ellipse 90% 80% at 50% 30%, #000 30%, transparent 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.cw-glow {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(90px);
|
||||
pointer-events: none;
|
||||
}
|
||||
.cw-glow-a { width: 420px; height: 420px; top: -140px; left: -100px; background: rgba(76, 194, 255, 0.15); }
|
||||
.cw-glow-b { width: 460px; height: 460px; bottom: -180px; right: -120px; background: rgba(47, 107, 255, 0.12); }
|
||||
|
||||
.cw-head {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
flex-wrap: wrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cw-eyebrow {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.32em;
|
||||
color: rgba(125, 143, 169, 0.65);
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.cw-title h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(26px, 2.6vw, 40px);
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.06em;
|
||||
background: linear-gradient(92deg, var(--bd-blue-deep) 10%, var(--bd-blue) 55%, var(--bd-cyan) 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
filter: drop-shadow(0 2px 12px rgba(47, 107, 255, 0.14));
|
||||
}
|
||||
.cw-sub {
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
color: var(--bd-sub);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.cw-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.cw-stat {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(47, 107, 255, 0.1);
|
||||
box-shadow: 0 2px 12px rgba(29, 93, 206, 0.06);
|
||||
}
|
||||
.cw-stat b { font-size: 22px; font-weight: 800; color: var(--bd-blue); font-family: "JetBrains Mono", monospace; }
|
||||
.cw-stat i { font-style: normal; font-size: 11.5px; color: var(--bd-sub); }
|
||||
.cw-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
color: var(--bd-sub);
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(47, 107, 255, 0.12);
|
||||
}
|
||||
.cw-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
background: var(--bd-green);
|
||||
box-shadow: 0 0 10px rgba(23, 178, 106, 0.5);
|
||||
animation: cwPulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
.cw-dot.idle { background: #94a3b8; box-shadow: none; animation: none; }
|
||||
@keyframes cwPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
|
||||
|
||||
/* 滚动墙 */
|
||||
.cw-wall {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(47, 107, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
mask-image: linear-gradient(180deg, transparent, #000 6%, #000 94%, transparent);
|
||||
-webkit-mask-image: linear-gradient(180deg, transparent, #000 6%, #000 94%, transparent);
|
||||
}
|
||||
.cw-track {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: clamp(12px, 1.2vw, 20px);
|
||||
padding: clamp(14px, 1.4vw, 24px);
|
||||
animation: cwScrollY linear infinite;
|
||||
}
|
||||
.cw-wall:hover .cw-track { animation-play-state: paused; }
|
||||
@keyframes cwScrollY {
|
||||
from { transform: translateY(0); }
|
||||
to { transform: translateY(-50%); }
|
||||
}
|
||||
|
||||
/* 企业卡片 */
|
||||
.cw-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 18px 20px 18px 16px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
border: 1px solid rgba(47, 107, 255, 0.1);
|
||||
border-left: 3px solid var(--ind);
|
||||
box-shadow: 0 3px 16px rgba(29, 93, 206, 0.06);
|
||||
transition: transform 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.cw-card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: rgba(47, 107, 255, 0.32);
|
||||
box-shadow: 0 10px 28px rgba(47, 107, 255, 0.12), 0 3px 16px rgba(29, 93, 206, 0.06);
|
||||
}
|
||||
.cw-idx {
|
||||
flex-shrink: 0;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
color: var(--ind);
|
||||
opacity: 0.3;
|
||||
padding-top: 2px;
|
||||
min-width: 34px;
|
||||
text-align: center;
|
||||
}
|
||||
.cw-body { flex: 1; min-width: 0; }
|
||||
.cw-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.cw-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
font-size: clamp(14px, 1.25vw, 18px);
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
color: var(--bd-ink);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.cw-tags { display: flex; gap: 6px; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
|
||||
.cw-tag {
|
||||
font-size: 11px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cw-tag-ind { border: 1px solid transparent; font-weight: 600; }
|
||||
.cw-tag-zone {
|
||||
color: var(--bd-sub);
|
||||
background: rgba(148, 163, 184, 0.12);
|
||||
border: 1px dashed rgba(125, 143, 169, 0.45);
|
||||
}
|
||||
|
||||
.cw-founder {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 9px 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 12px;
|
||||
background: rgba(244, 248, 253, 0.85);
|
||||
border: 1px solid rgba(47, 107, 255, 0.08);
|
||||
}
|
||||
.cw-avatar {
|
||||
flex-shrink: 0;
|
||||
width: 34px; height: 34px;
|
||||
border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 15px; font-weight: 700; color: #fff;
|
||||
box-shadow: 0 0 0 2px rgba(255,255,255,0.9), 0 2px 8px rgba(29, 93, 206, 0.2);
|
||||
}
|
||||
.cw-finfo { min-width: 0; }
|
||||
.cw-flabel {
|
||||
display: block;
|
||||
font-size: 9.5px;
|
||||
letter-spacing: 0.18em;
|
||||
color: rgba(125, 143, 169, 0.75);
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.cw-fname { font-size: 13.5px; font-weight: 700; color: var(--bd-ink); }
|
||||
.cw-fbio {
|
||||
margin: 2px 0 0;
|
||||
font-size: 11.5px;
|
||||
line-height: 1.6;
|
||||
color: var(--bd-sub);
|
||||
}
|
||||
|
||||
.cw-bio {
|
||||
margin: 0;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.75;
|
||||
color: #4a5b76;
|
||||
white-space: pre-line;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cw-card:hover .cw-bio { -webkit-line-clamp: unset; }
|
||||
|
||||
.cw-foot {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
color: rgba(125, 143, 169, 0.7);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
/* 手势识别徽标 + 隐藏摄像头 */
|
||||
.cw-vision {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
color: var(--bd-sub);
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(47, 107, 255, 0.12);
|
||||
}
|
||||
.cw-vision .cw-vision-ico { font-size: 13px; filter: grayscale(1) opacity(0.7); }
|
||||
.cw-vision.on { border-color: rgba(23, 178, 106, 0.35); }
|
||||
.cw-vision.on .cw-vision-ico { filter: none; }
|
||||
.cw-vision-tip { color: var(--bd-green); font-weight: 600; }
|
||||
.cw-vision-off { color: #94a3b8; font-weight: 400; }
|
||||
.cw-video { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
/* ============ 全局手势识别(GlobalVision) ============ */
|
||||
.gv-video { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
.gv-badge {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
z-index: 999;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 7px 14px;
|
||||
border-radius: 999px;
|
||||
font-size: 11.5px;
|
||||
color: var(--bd-sub);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(47, 107, 255, 0.14);
|
||||
box-shadow: 0 4px 18px rgba(29, 93, 206, 0.12);
|
||||
backdrop-filter: blur(6px);
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
.gv-badge .gv-ico { font-size: 13px; filter: grayscale(1) opacity(0.65); }
|
||||
.gv-badge .gv-dot {
|
||||
width: 7px; height: 7px; border-radius: 50%;
|
||||
background: #94a3b8;
|
||||
}
|
||||
.gv-badge.on { border-color: rgba(23, 178, 106, 0.35); }
|
||||
.gv-badge.on .gv-ico { filter: none; }
|
||||
.gv-badge.on .gv-dot {
|
||||
background: var(--bd-green);
|
||||
box-shadow: 0 0 8px rgba(23, 178, 106, 0.6);
|
||||
animation: cwPulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
.gv-badge.flash {
|
||||
border-color: rgba(23, 178, 106, 0.6);
|
||||
background: rgba(232, 250, 241, 0.95);
|
||||
color: #047857;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* ============ 企业详情卡片(完整信息版) ============ */
|
||||
.bd-twin-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 14px 16px;
|
||||
background: linear-gradient(165deg, rgba(255,255,255,0.94), rgba(244,248,253,0.9));
|
||||
border: 1px solid rgba(47, 107, 255, 0.14);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 4px 18px rgba(29, 93, 206, 0.08);
|
||||
}
|
||||
.bd-twin-detail-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed rgba(47, 107, 255, 0.16);
|
||||
}
|
||||
.bd-twin-detail-head b {
|
||||
font-size: 14.5px;
|
||||
line-height: 1.4;
|
||||
color: var(--bd-ink);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.bd-twin-tags {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.bd-twin-ind {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.bd-twin-zone-tag {
|
||||
font-size: 10.5px;
|
||||
padding: 1px 9px;
|
||||
border-radius: 999px;
|
||||
background: rgba(125, 143, 169, 0.1);
|
||||
border: 1px dashed rgba(125, 143, 169, 0.4);
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* 创始人区 */
|
||||
.bd-twin-founder {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 9px 11px;
|
||||
border-radius: 11px;
|
||||
background: rgba(244, 248, 253, 0.9);
|
||||
border: 1px solid rgba(47, 107, 255, 0.08);
|
||||
}
|
||||
.bd-twin-avatar {
|
||||
flex-shrink: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 0 2px rgba(255,255,255,0.9), 0 2px 8px rgba(29, 93, 206, 0.18);
|
||||
}
|
||||
.bd-twin-founder-info { min-width: 0; }
|
||||
.bd-twin-founder-info i {
|
||||
display: block;
|
||||
font-style: normal;
|
||||
font-size: 9.5px;
|
||||
letter-spacing: 0.16em;
|
||||
color: rgba(125, 143, 169, 0.8);
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.bd-twin-founder-info b {
|
||||
font-size: 13.5px;
|
||||
color: var(--bd-ink);
|
||||
}
|
||||
.bd-twin-founder-info p {
|
||||
margin: 2px 0 0;
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
color: var(--bd-sub);
|
||||
}
|
||||
/* 企业简介 */
|
||||
.bd-twin-bio {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
color: #4a5b76;
|
||||
max-height: 108px;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
.bd-twin-bio::-webkit-scrollbar { width: 4px; }
|
||||
.bd-twin-bio::-webkit-scrollbar-thumb { background: rgba(47, 107, 255, 0.25); border-radius: 4px; }
|
||||
.bd-twin-detail-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding-top: 6px;
|
||||
border-top: 1px dashed rgba(47, 107, 255, 0.12);
|
||||
font-size: 12px;
|
||||
color: #64748d;
|
||||
}
|
||||
.bd-twin-detail-meta b { color: var(--bd-ink); margin-left: 3px; }
|
||||
.bd-twin-detail-meta b.ok { color: var(--bd-green); }
|
||||
|
||||
/* 企业详情卡片:公司名单行显示(过长自动缩小) */
|
||||
.bd-twin-detail-head b {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.bd-twin-detail-head {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const PAGE_ORDER = [
|
||||
{ path: '/twin', label: '数字孪生' },
|
||||
{ path: '/ai', label: 'AI 助手' },
|
||||
{ path: '/voice', label: '语音对话' },
|
||||
{ path: '/wall', label: '企业展示墙' },
|
||||
{ path: '/screen', label: '媒体轮播' },
|
||||
];
|
||||
|
||||
|
||||
+178
-96
@@ -4,124 +4,206 @@ import { API_BASE } from '../config';
|
||||
/* =========================================================
|
||||
园区数据引擎(共享)—— 供数据大屏 / 媒体播放页复用
|
||||
数据来自 FastAPI 后端 GET /api/dashboard/snapshot(每 2.2s 刷新)
|
||||
后端不可用时回退本地模拟引擎,保证离线可用
|
||||
后端不可用时回退本地真实数据快照(无随机模拟)
|
||||
---------------------------------------------------------
|
||||
数据口径(用户指定):仅《运行情况统计表(2026年7月).xls》
|
||||
与《昆明市创业园宣传册.pdf》
|
||||
TOKEN:月均 200 亿 tokens 锚点测算(日均 ≈ 6.67 亿,实时 ≈ 7,700 t/s)
|
||||
工具调用:按每次调用约 2 万 tokens 折算(今日 ≈ 3.3 万次)
|
||||
========================================================= */
|
||||
|
||||
export const fmtInt = (n) => Math.round(n).toLocaleString('zh-CN');
|
||||
export const fmtWan = (n) => (n >= 10000 ? (n / 10000).toFixed(2) + '亿' : n >= 100 ? n.toFixed(1) + '万' : n.toFixed(1));
|
||||
|
||||
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||
const nowTime = () => new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||
/* ---------- 真实锚点(仅来自 xls 统计表 + 宣传册) ---------- */
|
||||
const REAL = {
|
||||
tokenMonthlyYi: 200, // 月均 200 亿 tokens(真实业务锚点)
|
||||
tokenDailyWan: 66666.7, // 日均 6.67 亿 tokens
|
||||
tokenRate: 7700, // 实时 t/s
|
||||
toolsPerCallTokens: 150000, // 平均每次工具调用消耗 tokens(真实测算:12.5M输入 ÷ 81步 ≈ 15.4万)
|
||||
occupied: 39, // 实际入驻企业数(信息表名单 39 家,统一口径)
|
||||
capacity: 49, // 可容纳企业个数(统计表)
|
||||
invested: 560, // 累计投入运营资金(万元)
|
||||
jobs: 213, // 累计带动就业(统计表)
|
||||
revenueTotal: 252.15, // 累计生产经营总额(万元)
|
||||
revenueTax: 1.03, // 累计上缴税利(万元)
|
||||
area: 3000, // 建筑面积(㎡)
|
||||
founded: 2009, // 成立年份(宣传册)
|
||||
provinceLevel: 2012, // 省级创业园认定年份(宣传册)
|
||||
address: '昆明市民航路229号(人力资源中心1-2楼)',
|
||||
phone: '18087174891',
|
||||
};
|
||||
|
||||
const TOOL_NAMES = ['文档生成', '数据查询', '图像创作', '代码执行', '语音合成'];
|
||||
const MODEL_NAMES = ['DeepSeek-V3', '通义千问', '智谱 GLM-4', '豆包', '讯飞星火'];
|
||||
const COMPANY_NAMES = [
|
||||
'云南派音人工智能科技', '米勒克尔蓝宝石珠宝', '中泰研学合作', '云南宸中低空经济',
|
||||
'昆明智海银高文化科技', '云南廷秀文旅康养', '瀚颖AI+教育信息咨询',
|
||||
'仰光客厅', '云南上古绝学文化', '中越生物医疗', '酷享野农AI农业',
|
||||
'滇缅国际设计', '昆明舒诺生物科技', '达岸教育管理', '花仙子园艺肥料',
|
||||
'研X同行者网络', '鬼才明AI创意工作室', '朵哈·玫瑰特色产业链', '昆明云韵体育',
|
||||
'南菌优培食用菌', '五华区丽裳文化', '云南星瑞航空', '综合直播私域平台',
|
||||
'昆明屿澈电商', '蓝智科技', '云品出滇·纸享万家', '启元人工智能科技',
|
||||
/* 入驻企业名录(《入驻企业信息表》39 家全称,与在园数统一口径) */
|
||||
const COMPANIES = [
|
||||
'云南派音人工智能科技有限公司', '米勒克尔蓝宝石珠宝产业化(项目)', '中泰研学合作(项目)',
|
||||
'云南宸中低空经济有限公司', '昆明智海银高文化科技有限公司', '人工智能机器人大模型训练(项目)',
|
||||
'云南廷秀文旅康养服务(项目)', '瀚颖AI+教育信息咨询(项目)', '云南大学AI+联合创业服务平台(项目)',
|
||||
'仰光客厅(项目)', '云南上古绝学文化发展有限公司', '千楣之路(项目)',
|
||||
'中越生物医疗(项目)', '中越文旅交流(项目)', '绿态环保科技项目',
|
||||
'联翼航空技术低空经济应用国际化拓展(项目)', '酷享野农AI农业(项目)', '滇缅国际设计(项目)',
|
||||
'昆明舒诺生物科技有限责任公司', '达岸教育管理(云南)有限公司', '昆明滇油石化有限责任公司',
|
||||
'花仙子园艺肥料(云南)有限公司', 'Facebook越南市场普洱茶与建水紫陶壶跨境电商(项目)',
|
||||
'研X——同行者网络(项目)', '鬼才明AI创意工作室(项目)', '朵哈·玫瑰特色产业链(项目)',
|
||||
'昆明云韵体育有限责任公司', '昆明舒护安养老服务有限责任公司',
|
||||
'南菌优培—食用菌专用生物制剂创新与高原特色野生菌高值化全链开发(项目)',
|
||||
'五华区丽裳文化艺术工作室', '云南星瑞航空(项目)', '综合性云南直播私域服务平台',
|
||||
'昆明屿澈电子商务有限公司', '中外青少年研学(项目)', '星杭商务信息咨询昆明有限责任公司',
|
||||
'蓝智科技(项目)', '云品出滇·纸享万家(项目)',
|
||||
'云迹轻旅—昆明本土一站式轻量化文旅服务工作室(项目)', '启元人工智能科技(项目)',
|
||||
];
|
||||
|
||||
function makeSeries(base, growth, noise, n = 30) {
|
||||
/* 宣传册四大孵化区域 */
|
||||
const ZONES = [
|
||||
{ name: 'OPC创业空间', desc: 'AI 技术支撑 · 数字经济/AI应用/软件开发/文创设计/电商直播等轻资产领域', period: '拎包入驻 · 最长2年' },
|
||||
{ name: '创业苗圃区', desc: '创意阶段 · 未注册企业团队 · 开放式共享工位', period: '最长2年' },
|
||||
{ name: '孵化加速区', desc: '已工商注册初创企业 · 独立办公空间', period: '基础2年 · 可延1年(≤3年)' },
|
||||
{ name: '国际创客区', desc: '归国留学生 · 外籍留学生 · 国际化配套服务', period: '参照加速区' },
|
||||
];
|
||||
|
||||
/* 园区企业行业分布(按《入驻企业信息表》39 家企业简介分类统计) */
|
||||
const INDUSTRY_MIX = [
|
||||
{ name: '人工智能', value: 7 },
|
||||
{ name: '跨境电商', value: 5 },
|
||||
{ name: '教育培训', value: 4 },
|
||||
{ name: '文旅康养', value: 4 },
|
||||
{ name: '文化创意', value: 4 },
|
||||
{ name: '生物医药', value: 4 },
|
||||
{ name: '低空经济', value: 3 },
|
||||
{ name: '现代农业', value: 3 },
|
||||
{ name: '绿色环保', value: 2 },
|
||||
{ name: '企业服务', value: 2 },
|
||||
{ name: '新材料', value: 1 },
|
||||
];
|
||||
|
||||
/* 园区实时动态(真实事件:园区官方公示 + 宣传册活动 + 互联网公开信息,最新在前) */
|
||||
const FEED = [
|
||||
{ id: 24, icon: 'dna', text: '园内企业「昆明舒诺生物科技」完成GEO服务体系建设(生成式引擎优化,聚焦宠物营养品牌数字化)', time: '2026-08' },
|
||||
{ id: 1, icon: 'activity', text: '2026年7月运行情况统计表填报完成:累计投入运营资金560万元、带动就业213人', time: '2026-07-31' },
|
||||
{ id: 2, icon: 'radio', text: '2026年第三期入驻创业企业(项目)评审结果在昆明市人社局官网公示', time: '2026-07-02' },
|
||||
{ id: 3, icon: 'graduation', text: '云南师范大学就业创业实践基地落地园区 · 云南旅游职业学院开展就业创业交流', time: '2026-06-03' },
|
||||
{ id: 23, icon: 'radio', text: '「PineSound」完成 ICP 备案(滇ICP备2026008517号)与公安备案,注册地址昆明市民航路301-307号', time: '2026' },
|
||||
{ id: 4, icon: 'rocket', text: '"滇水逐梦·雨林创航"昆明·西双版纳创业路演PK赛成功举办', time: '2026-05-20' },
|
||||
{ id: 5, icon: 'flag', text: '昆明市青年企业家协会创新创业基地在园区揭牌', time: '2026-05-13' },
|
||||
{ id: 6, icon: 'users', text: '"聚力赋能·共创未来"高校创新创业交流暨项目路演活动圆满举行', time: '2026-05-08' },
|
||||
{ id: 7, icon: 'trophy', text: '"创赢未来"2026创业大赛昆明选拔赛暨马兰花创业培训讲师大赛举行(13个项目参赛)', time: '2026-04-22' },
|
||||
{ id: 8, icon: 'broadcast', text: '2026年第二期创业企业(项目)入驻招募公告发布(官网 www.kmyc.gov.cn)', time: '2026-04-15' },
|
||||
{ id: 21, icon: 'sparkles', text: '「PineSound」AI驱动音频管理平台上线 pinesound.cn:智能配乐与音效生成,集成100W+音效库、50W+配乐库,全球版权授权(官网)', time: '2026' },
|
||||
{ id: 22, icon: 'medal', text: '「PineSound」发布企业标准 Q/YNPY 001-2026《数字内容创作 配乐通用分类标准》', time: '2026' },
|
||||
{ id: 9, icon: 'award', text: '2026年第一期入驻评审结果在昆明市人社局官网公示(23个优质项目正式入驻)', time: '2026-03-27' },
|
||||
{ id: 10, icon: 'users', text: '2026年第一期招募集中评审完成:38个申请,23个优质项目正式入驻', time: '2026-03-24' },
|
||||
{ id: 19, icon: 'sparkles', text: '园内企业「云南派音人工智能科技(PineSound)」专注多模态音频技术研发:自研Pine系列模型覆盖音频识别、向量嵌入、音效生成、配乐创作', time: '2026-04' },
|
||||
{ id: 20, icon: 'bolt', text: '「PineSound」2026年4月注册成立,获北京投资支持并吸纳就业4人,入驻加速区(入驻企业信息表)', time: '2026-04' },
|
||||
{ id: 11, icon: 'cpu', text: '云南首个人工智能OPC创新人才基地落地昆明,填补省内个体AI创业培育空白(公开报道)', time: '2026-03-23' },
|
||||
{ id: 12, icon: 'medal', text: '第九届"春城创业荟"创业创新大赛圆满闭幕,获奖项目名单公布', time: '2025-09-30' },
|
||||
{ id: 13, icon: 'trophy', text: '"春城创业荟"初赛落幕,147个项目晋级复赛', time: '2025-04-28' },
|
||||
{ id: 14, icon: 'building', text: '园内企业「花仙子园艺肥料(云南)」成立,注册资本118万元(公开报道)', time: '2025-04-28' },
|
||||
{ id: 15, icon: 'radio', text: '2025年入园项目评审结果公示(昆明市人社局官网)', time: '2025-04-22' },
|
||||
{ id: 16, icon: 'atom', text: '研X平台入选"创客中国"项目库(一站式研究生学术成长平台)', time: '2025-01' },
|
||||
{ id: 17, icon: 'leaf', text: '行业动态:园内「朵哈·玫瑰」原料产地昆明八街食用玫瑰行情上涨,玫瑰经济走强', time: '2025-01' },
|
||||
{ id: 18, icon: 'heart', text: '行业动态:「千楣之路」主营檀娜卡——泰国天然护肤品牌Thanaka正式进入中国市场', time: '2025-01' },
|
||||
];
|
||||
|
||||
/* ---------- 时钟驱动:当日累计(单向持续增长,重启连续) ---------- */
|
||||
function daySecs() {
|
||||
const now = new Date();
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
||||
return Math.max(0, (now.getTime() - start) / 1000);
|
||||
}
|
||||
|
||||
/* 平台实时速率 = 单会话 157 tok/s × 49 路并发 ≈ 7,700 t/s
|
||||
今日 TOKEN(万)= 秒数 × 7,700 / 10000 = 秒数 × 0.77
|
||||
今日工具调用 = 今日 TOKEN / 15万 tokens 每次(真实基线)
|
||||
+ 随机游走噪声(±10,波浪式波动,不单调) */
|
||||
function todayValues(prevNoise) {
|
||||
const secs = daySecs();
|
||||
const todayWan = Math.round(secs * 0.77);
|
||||
const baseTools = (secs * 0.77 * 10000) / REAL.toolsPerCallTokens;
|
||||
let noise = prevNoise == null ? 0 : prevNoise + (Math.random() - 0.5) * 6;
|
||||
noise = Math.max(-10, Math.min(10, noise));
|
||||
const todayTools = Math.max(0, Math.round(baseTools + noise));
|
||||
return { todayWan, todayTools, noise };
|
||||
}
|
||||
|
||||
function makeSeries(base, 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));
|
||||
for (let i = 0; i < n - 1; i++) {
|
||||
arr.push(Math.round(Math.max(1, base + (Math.random() - 0.5) * noise) * 10) / 10);
|
||||
}
|
||||
arr.push(base);
|
||||
return arr;
|
||||
}
|
||||
|
||||
function genEvent() {
|
||||
/* TOKEN 面板实时运行指标(围绕真实基准动态波动,会话持续推进) */
|
||||
function liveValues(prev) {
|
||||
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: 'robot', text: `「${pick(MODEL_NAMES)}」模型完成一次微调任务` },
|
||||
];
|
||||
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', time: nowTime() },
|
||||
{ id: 2, icon: 'building', text: '「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间', time: nowTime() },
|
||||
{ id: 3, icon: 'users', text: '「昆明舒护安养老服务」新增招聘岗位 2 个', time: nowTime() },
|
||||
{ id: 4, icon: 'coin', text: '园区企业完成一笔 ¥3.6万 交易', time: nowTime() },
|
||||
];
|
||||
const steps = (prev ? prev.steps : 81) + (r() < 0.3 ? 1 : 0);
|
||||
return {
|
||||
firstToken: +(1.3 + (r() - 0.5) * 0.2).toFixed(2), // 首 token 平均 1.3s 基准 ±0.1
|
||||
throughput: Math.round(157 + (r() - 0.5) * 14), // 吞吐 157 tok/s 基准 ±7
|
||||
cacheHit: +(98 + (r() - 0.5) * 1.2).toFixed(1), // 缓存命中 98% 基准 ±0.6
|
||||
inputM: +((prev ? prev.inputM : 12.5) + 0.004 + r() * 0.008).toFixed(2), // 会话输入持续推进
|
||||
outputM: +((prev ? prev.outputM : 2.0) + 0.001 + r() * 0.002).toFixed(2), // 会话输出持续推进
|
||||
rounds: Math.max(2, Math.floor(steps / 40)), // 每 40 步一轮
|
||||
steps, // 步数持续增长
|
||||
};
|
||||
}
|
||||
|
||||
function initSnapshot() {
|
||||
const { todayWan, todayTools, noise } = todayValues();
|
||||
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(),
|
||||
as_of: '2026-07',
|
||||
_toolsNoise: noise,
|
||||
live: liveValues(),
|
||||
token: {
|
||||
today: todayWan, // 万 tokens(当日累计,实时增长)
|
||||
total: REAL.tokenMonthlyYi * 10000 + todayWan, // 万 tokens(近30日 = 200亿 + 今日增量)
|
||||
rate: REAL.tokenRate, // t/s(157 × 49 并发 ≈ 7,700)
|
||||
series: makeSeries(REAL.tokenDailyWan, REAL.tokenDailyWan * 0.16),
|
||||
},
|
||||
tools: {
|
||||
today: todayTools, // 次(随 TOKEN 实时联动)
|
||||
total: Math.round(REAL.tokenMonthlyYi * 100000000 / REAL.toolsPerCallTokens) + todayTools,
|
||||
success: 98.5,
|
||||
nodes: 12,
|
||||
},
|
||||
projects: { inPark: REAL.occupied, capacity: REAL.capacity, invested: REAL.invested },
|
||||
jobs: { total: REAL.jobs },
|
||||
revenue: { total: REAL.revenueTotal, tax: REAL.revenueTax },
|
||||
park: {
|
||||
area: REAL.area,
|
||||
founded: REAL.founded,
|
||||
provinceLevel: REAL.provinceLevel,
|
||||
address: REAL.address,
|
||||
phone: REAL.phone,
|
||||
},
|
||||
zones: ZONES,
|
||||
companies: COMPANIES,
|
||||
industryMix: INDUSTRY_MIX,
|
||||
feed: FEED,
|
||||
};
|
||||
}
|
||||
|
||||
function nextSnapshot(s) {
|
||||
const r = Math.random;
|
||||
const tDeltaWan = +(0.26 + r() * 0.34).toFixed(2);
|
||||
|
||||
const token = {
|
||||
today: +(s.token.today + tDeltaWan).toFixed(2),
|
||||
total: +(s.token.total + tDeltaWan).toFixed(2),
|
||||
rate: +(76 + r() * 20).toFixed(1),
|
||||
/* 时钟驱动重算当日累计(TOKEN 单向增长,工具调用波浪波动),其余真实静态 */
|
||||
const { todayWan, todayTools, noise } = todayValues(s._toolsNoise);
|
||||
return {
|
||||
...s,
|
||||
_toolsNoise: noise,
|
||||
live: liveValues(s.live),
|
||||
token: {
|
||||
today: todayWan,
|
||||
total: REAL.tokenMonthlyYi * 10000 + todayWan,
|
||||
rate: REAL.tokenRate,
|
||||
series: [...s.token.series.slice(0, -1), todayWan],
|
||||
},
|
||||
tools: {
|
||||
today: todayTools,
|
||||
total: Math.round(REAL.tokenMonthlyYi * 100000000 / REAL.toolsPerCallTokens) + todayTools,
|
||||
success: 98.5,
|
||||
nodes: 12,
|
||||
},
|
||||
};
|
||||
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;
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
export function useParkSim() {
|
||||
@@ -135,7 +217,7 @@ export function useParkSim() {
|
||||
const data = await res.json();
|
||||
if (!cancelled && data && data.token) setSnap(data);
|
||||
} catch {
|
||||
// 后端不可用:本地模拟引擎继续推进(离线兜底)
|
||||
// 后端不可用:本地真实快照继续(仅 TOKEN/工具轻微波动)
|
||||
if (!cancelled) setSnap((p) => nextSnapshot(p));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ const PAGE_ALIAS = {
|
||||
twin: '/twin', '数字孪生': '/twin', '3d': '/twin',
|
||||
ai: '/ai', 'ai助手': '/ai',
|
||||
screen: '/screen', '媒体轮播': '/screen', media: '/screen',
|
||||
wall: '/wall', '企业展示墙': '/wall', '企业墙': '/wall', '展示墙': '/wall',
|
||||
};
|
||||
|
||||
function resolvePage(page) {
|
||||
@@ -126,6 +127,15 @@ export function useMqttControl() {
|
||||
}));
|
||||
break;
|
||||
|
||||
// ── 企业展示墙(/wall)操作 ──
|
||||
case 'wall_pause':
|
||||
case 'wall_resume':
|
||||
case 'wall_speed':
|
||||
window.dispatchEvent(new CustomEvent('dpm:wall-control', {
|
||||
detail: { action: cmd.action.replace('wall_', ''), ...(cmd.params || {}) },
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ import { API_BASE } from '../config';
|
||||
|
||||
export const VISION_DWELL_MS = 10000; // 面向停留阈值
|
||||
export const VISION_SILENT_MS = 180000; // 触发(自动问候)后静默 3 分钟,期间不重复触发
|
||||
const DETECT_INTERVAL_MS = 700; // ~1.4fps(后端推理 ~100-200ms + 传输)
|
||||
export const GESTURE_DWELL_MS = 1000; // 手势持续阈值:举手/举拳/双臂/指向/合十 持续 1s 触发一次
|
||||
const WAVE_BUF = 12; // 挥手采样窗口(帧)
|
||||
const WAVE_MIN_FLIPS = 4; // 挥手:窗口内方向翻转 ≥4 次(≈ 摆动 2 个来回)
|
||||
const WAVE_COOLDOWN_MS = 2500; // 挥手触发冷却
|
||||
const DETECT_INTERVAL_MS = 400; // ~2.5fps(降低手势跟随延迟;后端推理 ~100-200ms + 传输)
|
||||
const FRAME_W = 480;
|
||||
const FRAME_H = 360;
|
||||
|
||||
@@ -19,6 +23,7 @@ export default function useVisionDetection({ onTrigger, onGesture, enabled = tru
|
||||
const [cameraOn, setCameraOn] = useState(false);
|
||||
const [status, setStatus] = useState('off'); // off|loading|detecting|facing|triggered|silent|error
|
||||
const [faces, setFaces] = useState(0);
|
||||
const [hands, setHands] = useState([]); // 手部坐标 [{side,wx,wy,sx,sy,raised,fist}]
|
||||
const [dwellMs, setDwellMs] = useState(0);
|
||||
const [gesture, setGesture] = useState(null); // raise(举手,toggle 对话)|null
|
||||
const [errorInfo, setErrorInfo] = useState(null);
|
||||
@@ -39,8 +44,17 @@ export default function useVisionDetection({ onTrigger, onGesture, enabled = tru
|
||||
const cancelledRef = useRef(false); // StrictMode/HMR 卸载标记
|
||||
const stateRef = useRef({
|
||||
facingSince: 0, silentUntil: 0, lastDetect: 0, lastDiag: 0, lastPlayRetry: 0, failCount: 0,
|
||||
// 手势状态机:举手持续 1.5s → raise(前端据此 toggle 对话:无对话开、有对话停)
|
||||
raiseActive: false, raiseStart: 0, raiseTriggered: false,
|
||||
// 手势状态机(通用):raise/fist/both_up/point/hands_close 各自 active/start/triggered
|
||||
gest: {
|
||||
raise: { active: false, start: 0, triggered: false },
|
||||
fist: { active: false, start: 0, triggered: false },
|
||||
both_up: { active: false, start: 0, triggered: false },
|
||||
point: { active: false, start: 0, triggered: false },
|
||||
hands_close: { active: false, start: 0, triggered: false },
|
||||
},
|
||||
// 挥手(时序):每只手最近手腕 x 轨迹 + 冷却
|
||||
waveBuf: {},
|
||||
lastWave: 0,
|
||||
});
|
||||
const triggerRef = useRef(onTrigger);
|
||||
triggerRef.current = onTrigger;
|
||||
@@ -103,29 +117,65 @@ export default function useVisionDetection({ onTrigger, onGesture, enabled = tru
|
||||
setDetectErr(null);
|
||||
const n = data.faces ?? 0;
|
||||
setFaces(n);
|
||||
setHands(data.hands || []); // 手部实时坐标流(供手势跟随/3D 控制)
|
||||
setLatency(data.latency_ms);
|
||||
st.failCount = 0;
|
||||
|
||||
// ── 手势状态机:举手持续 1.5s → raise;前端据此 toggle(无对话开 / 有对话停)──
|
||||
const raised = data.raised?.[0]; // 取第一个举手的人/手
|
||||
if (raised) {
|
||||
if (!st.raiseActive) {
|
||||
st.raiseActive = true;
|
||||
st.raiseStart = now;
|
||||
st.raiseTriggered = false;
|
||||
// ── 手势状态机(通用):特征持续 ≥1s → 触发一次;特征消失 → 重置 ──
|
||||
const features = {
|
||||
raise: !!data.raised?.length,
|
||||
fist: !!data.fists?.length,
|
||||
both_up: !!data.both_up,
|
||||
point: !!(data.pointing?.length),
|
||||
hands_close: !!data.hands_close,
|
||||
};
|
||||
for (const [type, present] of Object.entries(features)) {
|
||||
const g = st.gest[type];
|
||||
if (present) {
|
||||
if (!g.active) {
|
||||
g.active = true;
|
||||
g.start = now;
|
||||
g.triggered = false;
|
||||
}
|
||||
if (!g.triggered && now - g.start >= GESTURE_DWELL_MS) {
|
||||
g.triggered = true;
|
||||
setGesture(type);
|
||||
console.warn(`[vision] 手势:${type}`);
|
||||
gestureRef.current?.(type);
|
||||
}
|
||||
} else if (g.active) {
|
||||
// 特征消失:重置,允许下次再触发
|
||||
g.active = false;
|
||||
g.start = 0;
|
||||
g.triggered = false;
|
||||
setTimeout(() => setGesture((v) => (v === type ? null : v)), 2500);
|
||||
}
|
||||
if (!st.raiseTriggered && now - st.raiseStart >= 1500) {
|
||||
st.raiseTriggered = true;
|
||||
setGesture('raise');
|
||||
console.warn(`[vision] 手势:举手(持续 ${Math.round((now - st.raiseStart) / 1000)}s)→ toggle 对话`);
|
||||
gestureRef.current?.('raise');
|
||||
}
|
||||
|
||||
// ── 挥手(时序):单手腕部水平摆动 ≥4 次方向翻转 → wave(防抖冷却)──
|
||||
for (const h of data.hands || []) {
|
||||
for (const side of ['left', 'right']) {
|
||||
const arm = h[side];
|
||||
if (!arm) continue;
|
||||
const buf = (st.waveBuf[side] = st.waveBuf[side] || []);
|
||||
buf.push(arm.wx);
|
||||
if (buf.length > WAVE_BUF) buf.shift();
|
||||
if (buf.length >= WAVE_BUF && now - st.lastWave > WAVE_COOLDOWN_MS) {
|
||||
let flips = 0;
|
||||
for (let k = 2; k < buf.length; k++) {
|
||||
const d1 = buf[k - 1] - buf[k - 2];
|
||||
const d2 = buf[k] - buf[k - 1];
|
||||
if (d1 * d2 < 0) flips += 1;
|
||||
}
|
||||
if (flips >= WAVE_MIN_FLIPS) {
|
||||
st.lastWave = now;
|
||||
st.waveBuf[side] = [];
|
||||
setGesture('wave');
|
||||
console.warn('[vision] 手势:挥手(摆动)');
|
||||
gestureRef.current?.('wave');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (st.raiseActive) {
|
||||
// 举手消失:重置,允许下次举手再次触发
|
||||
st.raiseActive = false;
|
||||
st.raiseStart = 0;
|
||||
st.raiseTriggered = false;
|
||||
setTimeout(() => setGesture(null), 2500);
|
||||
}
|
||||
|
||||
// 状态机:有人脸(面向大屏)持续 10s → 触发问候;触发后静默 60s
|
||||
@@ -216,9 +266,17 @@ export default function useVisionDetection({ onTrigger, onGesture, enabled = tru
|
||||
streamRef.current?.getTracks().forEach((t) => t.stop());
|
||||
streamRef.current = null;
|
||||
stateRef.current = { facingSince: 0, silentUntil: 0, lastDetect: 0, lastDiag: 0, lastPlayRetry: 0, failCount: 0,
|
||||
raiseActive: false, raiseStart: 0, raiseTriggered: false };
|
||||
gest: {
|
||||
raise: { active: false, start: 0, triggered: false },
|
||||
fist: { active: false, start: 0, triggered: false },
|
||||
both_up: { active: false, start: 0, triggered: false },
|
||||
point: { active: false, start: 0, triggered: false },
|
||||
hands_close: { active: false, start: 0, triggered: false },
|
||||
},
|
||||
waveBuf: {}, lastWave: 0 };
|
||||
setCameraOn(false);
|
||||
setFaces(0);
|
||||
setHands([]);
|
||||
setDwellMs(0);
|
||||
setGesture(null);
|
||||
setFrameLum(null);
|
||||
@@ -237,7 +295,7 @@ export default function useVisionDetection({ onTrigger, onGesture, enabled = tru
|
||||
}, [enabled]);
|
||||
|
||||
return {
|
||||
cameraOn, status, faces, dwellMs, gesture, latency, frameLum, modelPhase, modelErr, detectErr,
|
||||
cameraOn, status, faces, hands, dwellMs, gesture, latency, frameLum, modelPhase, modelErr, detectErr,
|
||||
errorInfo, startCamera, stopCamera, videoRef,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user