import { useEffect, useState, useRef } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import Icon from './Icons'; import MediaHeaderControls from './MediaHeaderControls'; import AboutDialog from './AboutDialog'; import { PAGE_ORDER, usePageIndex } from '../utils/pageNav'; import { Avatar, AvatarImage, AvatarFallback } from '@appica/ui-react/avatar' import { Sfx } from '../utils/sounds'; import { getApiBase as API_BASE } from '../config'; /* ========================================================= 共享页眉 —— 所有大屏页面统一(由 ScreenLayout 注入) 状态文案按当前页面自动识别;左右键循环 + 可点击按钮切换 · 音量面板:媒体音量 + 音效音量(写回后端设置并 MQTT 广播同步) ========================================================= */ const STATUS_BY_PATH = { '/': '数据实时', '/twin': '3D 实时', '/ai': 'AI 助手', '/screen': '媒体播放', }; export default function PageHeader() { const navigate = useNavigate(); const location = useLocation(); const [now, setNow] = useState(new Date()); const [aboutOpen, setAboutOpen] = useState(false); const [gestureHit, setGestureHit] = useState(false); // 识别到手势 → 惊喜图标 3s const [vision, setVision] = useState({ status: 'off', cameraOn: false, gesture: null }); // 视觉识别状态 const [volOpen, setVolOpen] = useState(false); const [mediaVol, setMediaVol] = useState(80); // 媒体音量 0-100 const [sfxVol, setSfxVol] = useState(Sfx.getVolume()); // 音效音量 0-100 const pageIdx = usePageIndex(); const gestureTimer = useRef(null); // 启动时拉取后端设置(媒体/音效音量),并监听 MQTT settings_changed 同步 useEffect(() => { fetch(`${API_BASE()}/api/settings`, { cache: 'no-store' }) .then((r) => r.json()) .then((s) => { if (typeof s.volume === 'number') setMediaVol(s.volume); if (typeof s.sfx_volume === 'number') { setSfxVol(s.sfx_volume); Sfx.setVolume(s.sfx_volume); } }) .catch(() => {}); const onSettings = (e) => { const p = (e.detail && (e.detail.params || e.detail)) || {}; if (typeof p.volume === 'number') setMediaVol(p.volume); if (typeof p.sfx_volume === 'number') { setSfxVol(p.sfx_volume); Sfx.setVolume(p.sfx_volume); } }; // 后端/MQTT settings_changed → 同步音量(SSE 走 api/events,MQTT 走 dpm:media-control) window.addEventListener('dpm:mqtt-local', onSettings); window.addEventListener('dpm:media-control', onSettings); return () => { window.removeEventListener('dpm:mqtt-local', onSettings); window.removeEventListener('dpm:media-control', onSettings); }; }, []); // 点击音量容器(按钮+面板)外部 → 关闭面板; // 用「目标是否在容器内」判断,避免面板内滑块交互(pointerdown)误关弹窗 const volWrapRef = useRef(null); useEffect(() => { if (!volOpen) return undefined; const close = (e) => { if (volWrapRef.current && !volWrapRef.current.contains(e.target)) { setVolOpen(false); } }; document.addEventListener('pointerdown', close); return () => document.removeEventListener('pointerdown', close); }, [volOpen]); // 音量修改 → 写回后端(MQTT settings_changed 广播给所有大屏) const changeVolume = (key, value) => { const v = Math.max(0, Math.min(100, Number(value) || 0)); if (key === 'sfx_volume') { setSfxVol(v); Sfx.setVolume(v); Sfx.notify(); } else { setMediaVol(v); } fetch(`${API_BASE()}/api/settings`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ [key]: v }), }).catch(() => {}); }; useEffect(() => { const iv = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(iv); }, []); // 全局手势事件 → 惊喜图标 3s useEffect(() => { const onGesture = (e) => { if (!e.detail || !e.detail.type) return; setGestureHit(true); if (gestureTimer.current) clearTimeout(gestureTimer.current); gestureTimer.current = setTimeout(() => setGestureHit(false), 3000); }; window.addEventListener('dpm:gesture', onGesture); return () => { window.removeEventListener('dpm:gesture', onGesture); if (gestureTimer.current) clearTimeout(gestureTimer.current); }; }, []); // 视觉识别状态(GlobalVision 广播):off/loading/detecting/facing/triggered/silent/error useEffect(() => { const onStatus = (e) => setVision(e.detail || {}); window.addEventListener('dpm:vision-status', onStatus); return () => window.removeEventListener('dpm:vision-status', onStatus); }, []); // 图标状态:手势(绿) > 视觉识别中(浅蓝) > 默认(灰) const visionActive = vision.cameraOn && vision.status !== 'error' && vision.status !== 'off'; const visionError = vision.status === 'error'; const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; const week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()]; const timeStr = now.toLocaleTimeString('zh-CN', { hour12: false }); const status = STATUS_BY_PATH[location.pathname] || '在线'; // 循环中的相邻页面(左/右) const n = PAGE_ORDER.length; const left = pageIdx >= 0 ? PAGE_ORDER[(pageIdx - 1 + n) % n] : null; const right = pageIdx >= 0 ? PAGE_ORDER[(pageIdx + 1) % n] : null; return (
{/* Logo */} window.dispatchEvent(new CustomEvent('dpm:check-update'))} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') window.dispatchEvent(new CustomEvent('dpm:check-update')); }} >

昆明市大学生创业园 · OPC 智能园区数字运营中心

云南省首家政府主办大学生创业孵化园区 · 空间+孵化+融资+政策+资源+AI赋能+综合服务
{location.pathname !== '/screen' && ( 云南派音人工智能科技有限公司 开发 )}
{/* 媒体轮播页:页眉内嵌播放控制 */} {location.pathname === '/screen' && } {/* 音量控制(媒体 + 音效) */}
{volOpen && (
e.stopPropagation()}>
媒体音量
changeVolume('volume', e.target.value)} /> {mediaVol}%
音效音量
changeVolume('sfx_volume', e.target.value)} /> {sfxVol}%
)}
{left && ( )} · {right && ( )}
{status}
{timeStr}
{dateStr} 周{week}
{/* 关于 PineSound */} setAboutOpen(false)} />
); }