diff --git a/src/components/AboutDialog.jsx b/src/components/AboutDialog.jsx new file mode 100644 index 0000000..d135ef7 --- /dev/null +++ b/src/components/AboutDialog.jsx @@ -0,0 +1,59 @@ +import { useEffect } from 'react'; + +/* ========================================================= + AboutDialog —— 应用关于弹窗(PineSound 品牌) + 页眉右上角「ⓘ」按钮打开;展示产品名/版本/技术栈/版权 + ========================================================= */ + +const APP_INFO = { + brand: 'PineSound', + product: '园区智能语音交互助手', + version: '0.1.0', + tagline: '面向大屏的数字园区智能体 —— 看得见 · 听得懂 · 说得出', + tech: [ + '前端:React + Vite + Tauri', + '后端:FastAPI + 实时语音栈(s2s)', + '识别:YOLO 人脸/姿态(举手开始·结束对话)', + '语音:qwen ASR / LLM / TTS(云端)', + ], + copyright: '© 2026 PineSound · 昆明大学生创业园', +}; + +export default function AboutDialog({ open, onClose }) { + // Esc 关闭 + useEffect(() => { + if (!open) return; + const onKey = (e) => { if (e.key === 'Escape') onClose(); }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [open, onClose]); + + if (!open) return null; + return ( +
+
e.stopPropagation()}> + + +
+
PS
+

{APP_INFO.brand}

+
{APP_INFO.product}
+
+ +

{APP_INFO.tagline}

+ +
+

技术栈

+
    + {APP_INFO.tech.map((t, i) =>
  • {t}
  • )} +
+
+ +
+ 版本 v{APP_INFO.version} + {APP_INFO.copyright} +
+
+
+ ); +} diff --git a/src/components/Icons.jsx b/src/components/Icons.jsx index dec5495..3b6c0bf 100644 --- a/src/components/Icons.jsx +++ b/src/components/Icons.jsx @@ -10,7 +10,7 @@ import { Heart, Pill, Dna, Microscope, Atom, MapPin, Flag, Award, Medal, History, Drone, Plane, Rocket, Satellite, Wallet, UserPlus, Activity, Volume, VolumeOff, ChevronLeft, ChevronRight, PlayerPlay, PlayerPause, - Send, Trash, Eraser, Headset, CircleDot, + Send, Trash, Eraser, Headset, CircleDot, AlertCircle, Microphone, MicrophoneFilled, MicrophoneOff, MessageCircle, } from '@appica/icons-react'; @@ -80,6 +80,7 @@ const NAME_MAP = { eraser: Eraser, headset: Headset, 'circle-dot': CircleDot, + info: AlertCircle, microphone: Microphone, 'microphone-filled': MicrophoneFilled, 'microphone-off': MicrophoneOff, @@ -116,6 +117,6 @@ export { Heart, Pill, Dna, Microscope, Atom, MapPin, Flag, Award, Medal, History, Drone, Plane, Rocket, Satellite, Wallet, UserPlus, Activity, Volume, VolumeOff, ChevronLeft, ChevronRight, PlayerPlay, PlayerPause, - Send, Trash, Eraser, Headset, CircleDot, + Send, Trash, Eraser, Headset, CircleDot, AlertCircle, Microphone, MicrophoneFilled, MicrophoneOff, MessageCircle, }; diff --git a/src/components/PageHeader.jsx b/src/components/PageHeader.jsx index e70bcbc..e1ad5d4 100644 --- a/src/components/PageHeader.jsx +++ b/src/components/PageHeader.jsx @@ -2,6 +2,7 @@ import { useEffect, useState } 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' @@ -21,6 +22,7 @@ export default function PageHeader() { const navigate = useNavigate(); const location = useLocation(); const [now, setNow] = useState(new Date()); + const [aboutOpen, setAboutOpen] = useState(false); const pageIdx = usePageIndex(); useEffect(() => { @@ -74,11 +76,23 @@ export default function PageHeader() { {status} +
{timeStr}
{dateStr} 周{week}
+ + {/* 关于 PineSound */} + setAboutOpen(false)} /> ); } diff --git a/src/styles/datascreen.css b/src/styles/datascreen.css index c66ec80..5a8a0c2 100644 --- a/src/styles/datascreen.css +++ b/src/styles/datascreen.css @@ -2554,3 +2554,134 @@ @media (max-width: 1280px) { .bd-hd-media-name { display: none; } } + +/* ==================== 关于 PineSound 弹窗 ==================== */ +.bd-about-btn { + flex: none; + width: 30px; + height: 30px; + border-radius: 50%; + border: 1px solid rgba(47, 107, 255, 0.25); + background: rgba(255, 255, 255, 0.7); + color: var(--bd-sub, #7d8fa9); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s; +} +.bd-about-btn:hover { + color: #2f6bff; + border-color: #2f6bff; + background: #fff; +} + +.about-overlay { + position: fixed; + inset: 0; + z-index: 300; + background: rgba(13, 28, 48, 0.45); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; +} +.about-modal { + position: relative; + width: min(420px, calc(100vw - 48px)); + background: #fff; + border-radius: 16px; + padding: 28px 30px 22px; + box-shadow: 0 24px 60px rgba(15, 42, 96, 0.28); + animation: about-pop 0.25s ease; +} +@keyframes about-pop { + from { opacity: 0; transform: translateY(12px) scale(0.97); } + to { opacity: 1; transform: none; } +} +.about-close { + position: absolute; + top: 12px; + right: 14px; + width: 28px; + height: 28px; + border: none; + background: transparent; + color: #9fb0c6; + font-size: 20px; + line-height: 1; + cursor: pointer; + border-radius: 8px; +} +.about-close:hover { color: #1d2c44; background: #f0f4fa; } + +.about-brand { text-align: center; margin-bottom: 6px; } +.about-logo { + width: 64px; + height: 64px; + margin: 0 auto 10px; + border-radius: 18px; + background: linear-gradient(135deg, #2f6bff 0%, #22d3ee 100%); + color: #fff; + font-size: 26px; + font-weight: 800; + letter-spacing: 0.02em; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 10px 24px rgba(47, 107, 255, 0.35); +} +.about-name { + margin: 0; + font-size: 24px; + font-weight: 800; + color: #1d2c44; + letter-spacing: 0.04em; +} +.about-product { + margin-top: 4px; + font-size: 13px; + color: #7d8fa9; +} +.about-tagline { + text-align: center; + margin: 14px 0 18px; + font-size: 12.5px; + color: #4a5b74; + line-height: 1.6; + padding: 10px 12px; + background: #f5f8ff; + border-radius: 10px; +} +.about-section h4 { + margin: 0 0 8px; + font-size: 12px; + color: #7d8fa9; + letter-spacing: 0.08em; +} +.about-section ul { + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 6px; +} +.about-section li { + font-size: 12.5px; + color: #33425c; + padding: 6px 10px; + background: #f8fafd; + border-radius: 8px; + border: 1px solid #eef2f8; +} +.about-meta { + margin-top: 18px; + padding-top: 14px; + border-top: 1px dashed #e3ecf7; + display: flex; + justify-content: space-between; + font-size: 11px; + color: #9fb0c6; +}