Files
XQKqueue/web/src/pages/PublicDisplayCenterPage.tsx
brother7 01d261e893 优化:公屏叫号默认播放并移除声音控制
需求:服务端下发音频已验证可用,公屏无需再显示开启声音按钮和状态提示。

实现:移除声音控制组件与首次点击门槛,新叫号到达后直接播放内置音频队列。

验证:前端 65 项测试通过,生产构建通过。
2026-08-01 11:40:58 +08:00

67 lines
3.3 KiB
TypeScript

import { useEffect, useRef } from "react";
import { api } from "../api";
import { FeedbackBanner, FreshnessBanner, LoadingState } from "../components/Feedback";
import { useAudioAnnouncements } from "../hooks/useAudioAnnouncements";
import { usePollingResource } from "../hooks/usePollingResource";
import { formatCallAnnouncement, isTimestampStale } from "../lib/format";
import type { CallBatchDto } from "../types";
import { DisplayCenter } from "./AdminPage";
function projectCurrentBatch(value: unknown): CallBatchDto | null {
return value && typeof value === "object" ? value as CallBatchDto : null;
}
export function PublicDisplayCenterPage() {
const { announce, audioRef } = useAudioAnnouncements();
const resource = usePollingResource((signal) => api.displayOverview(signal), { intervalMs: 5_000 });
const data = resource.data;
const stale = Boolean(data) && isTimestampStale(resource.lastClientSuccessAt, 30_000);
const lastCallsRef = useRef<Map<string, string | null> | null>(null);
useEffect(() => {
if (!data) return;
const previousCalls = lastCallsRef.current;
const nextCalls = new Map<string, string | null>();
const announcements: string[] = [];
for (const project of data.projects) {
const batch = projectCurrentBatch(project.current_batch);
const key = batch ? `${batch.batch_number}:${batch.called_at ?? ""}` : null;
nextCalls.set(project.id, key);
if (!previousCalls?.has(project.id) || !key || previousCalls.get(project.id) === key) continue;
const announcement = formatCallAnnouncement(batch);
if (announcement) announcements.push(announcement);
}
lastCallsRef.current = nextCalls;
announce(announcements);
}, [announce, data]);
return (
<div className="app-shell app-shell--admin app-shell--no-primary-action">
<a className="skip-link" href="#main-content"></a>
<audio ref={audioRef} preload="auto" aria-hidden="true" />
<header className="app-header">
<div className="brand-lockup" aria-label="景区排队叫号系统">
<span className="brand-logo-frame" aria-hidden="true"><img className="brand-logo" src="/xiaoqikong-logo.jpg" alt="" /></span>
<span className="brand-lockup__text"><strong></strong></span>
</div>
<div className="account-menu"><span><strong></strong></span></div>
</header>
<main id="main-content" className="app-main">
<section className="admin-content" aria-label="公开大屏内容">
{resource.loading && !data ? <LoadingState label="正在汇总项目状态" /> : null}
{resource.error && !data ? (
<FeedbackBanner tone="danger" title="大屏数据暂时不可用" action={<button className="button button--secondary button--small" onClick={resource.refresh}></button>}>
{resource.error.message}
</FeedbackBanner>
) : null}
{data ? (
<>
<FreshnessBanner offline={resource.offline} stale={stale} timestamp={resource.lastClientSuccessAt} refreshing={resource.refreshing} errorMessage={resource.error?.message} onRetry={resource.refresh} />
<DisplayCenter projects={data.projects} />
</>
) : null}
</section>
</main>
</div>
);
}