This commit is contained in:
Daniel
2026-03-03 20:17:38 +08:00
parent 034c088bac
commit 09ec2e3a69
20 changed files with 395 additions and 19 deletions

View File

@@ -1,13 +1,29 @@
import { useState, useEffect } from 'react'
import { StatCard } from './StatCard'
import { useSituationStore } from '@/store/situationStore'
import { fetchAndSetSituation } from '@/store/situationStore'
import { useStatsStore } from '@/store/statsStore'
import { useReplaySituation } from '@/hooks/useReplaySituation'
import { usePlaybackStore } from '@/store/playbackStore'
import { Wifi, WifiOff, Clock, Share2, Heart, Eye, MessageSquare } from 'lucide-react'
import { Wifi, WifiOff, Clock, Share2, Heart, Eye, MessageSquare, RefreshCw } from 'lucide-react'
const STORAGE_LIKES = 'us-iran-dashboard-likes'
// 冲突时长显示在 TimelinePanel数据回放栏
// 开发环境下每标签一个 viewer-id便于本地验证「在看」开/关标签变化
const getViewerId = (): string | undefined => {
if (typeof import.meta !== 'undefined' && import.meta.env?.DEV && typeof crypto?.randomUUID === 'function') {
try {
let id = sessionStorage.getItem('us-iran-viewer-id')
if (!id) {
id = crypto.randomUUID()
sessionStorage.setItem('us-iran-viewer-id', id)
}
return id
} catch {
return undefined
}
}
return undefined
}
function getStoredLikes(): number {
try {
@@ -35,6 +51,7 @@ export function HeaderPanel() {
const [feedbackText, setFeedbackText] = useState('')
const [feedbackSending, setFeedbackSending] = useState(false)
const [feedbackDone, setFeedbackDone] = useState(false)
const [refreshing, setRefreshing] = useState(false)
useEffect(() => {
const timer = setInterval(() => setNow(new Date()), 1000)
@@ -43,7 +60,10 @@ export function HeaderPanel() {
const fetchStats = async () => {
try {
const res = await fetch('/api/visit', { method: 'POST' })
const headers: HeadersInit = {}
const vid = getViewerId()
if (vid) headers['X-Viewer-Id'] = vid
const res = await fetch('/api/visit', { method: 'POST', headers })
const data = await res.json()
setStats({
viewers: data.viewers,
@@ -58,7 +78,7 @@ export function HeaderPanel() {
useEffect(() => {
fetchStats()
const t = setInterval(fetchStats, 30000)
const t = setInterval(fetchStats, 15000)
return () => clearInterval(t)
}, [])
@@ -178,7 +198,7 @@ export function HeaderPanel() {
<Eye className="h-3.5 w-3.5" />
<span className="text-[10px]"> <b className="text-military-accent tabular-nums">{viewers}</b></span>
<span className="text-[10px] opacity-70">|</span>
<span className="text-[10px]"> <b className="tabular-nums">{cumulative}</b></span>
<span className="text-[10px]"> <b className="tabular-nums">{cumulative}</b></span>
</div>
<button
type="button"
@@ -208,6 +228,19 @@ export function HeaderPanel() {
<Heart className={`h-2.5 w-2.5 sm:h-3 sm:w-3 ${liked ? 'fill-current' : ''}`} />
{likes > 0 && <span className="tabular-nums">{likes}</span>}
</button>
<button
type="button"
onClick={async () => {
setRefreshing(true)
await fetchAndSetSituation().finally(() => setRefreshing(false))
}}
disabled={refreshing}
className="flex shrink-0 items-center gap-1 rounded border border-military-border px-1.5 py-0.5 text-[9px] text-military-text-secondary hover:bg-military-border/30 hover:text-cyan-400 disabled:opacity-50 sm:px-2 sm:py-1 sm:text-[10px]"
title="从服务器拉取最新态势数据"
>
<RefreshCw className={`h-3 w-3 sm:h-3.5 sm:w-3.5 ${refreshing ? 'animate-spin' : ''}`} />
</button>
<span className={`flex items-center gap-1 ${isConnected ? 'text-green-500' : 'text-military-text-secondary'}`}>
{isConnected ? <Wifi className="h-3.5 w-3.5" /> : <WifiOff className="h-3.5 w-3.5" />}
<span className="text-xs">{isConnected ? '实时' : '已断开'}</span>