修复在线游玩加载体验:加载期黑屏/空白与发行资源传输开销
- packages/shared 新增共享游戏加载面 PlatformGameLoadingSurface:封面/标题 + 动效进度 + 按时长推进的阶段文案(准备/资源/引擎)+ 慢加载提示,附用例 - 网页游玩页用共享加载面替换近黑壳上的一行小字,加载上限改为共享常量 PLATFORM_GAME_LOADING_TIMEOUT_MS - 后台审核试玩补加载面、20 秒超时与「重新创建试玩会话」入口,新会话重置加载态;补两条用例 - 发行网关(api-server)对文本类发行资源按 Accept-Encoding 下发 gzip,并下发强 ETag、命中 If-None-Match 返回 304;补 5 条 Rust 用例 - E2E 脚本 check-game-distribution-ops-rollback-e2e 补压缩/Vary/ETag/304/不接受 gzip 四条断言 - 同步 pitfalls、开发运维文档、nginx README 与两份里程碑记录
This commit is contained in:
@@ -464,6 +464,28 @@ describe('GamePlayPage', () => {
|
||||
expect(recordGamePlayMock).toHaveBeenCalledWith('game-1');
|
||||
});
|
||||
|
||||
it('shows the shared loading surface until the release frame is ready', async () => {
|
||||
render(<GamePlayPage gameId="game-1" onBack={vi.fn()} />);
|
||||
|
||||
await screen.findByRole('button', { name: /开始游戏/u });
|
||||
fireEvent.click(screen.getByRole('button', { name: /开始游戏/u }));
|
||||
const iframe = await screen.findByTitle('星轨防线 在线游玩');
|
||||
|
||||
// 发行包首屏要下完整个引擎:加载期必须有看得见的加载面,而不是一行小字或黑屏。
|
||||
const loadingSurface = screen.getByRole('status');
|
||||
expect(loadingSurface.textContent).toContain('星轨防线');
|
||||
expect(
|
||||
loadingSurface.classList.contains('platform-game-loading--player'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
loadingSurface.querySelector('.platform-game-loading__track'),
|
||||
).not.toBeNull();
|
||||
|
||||
fireEvent.load(iframe);
|
||||
expect(screen.queryByRole('status')).toBeNull();
|
||||
expect(iframe.className.includes('game-player-frame--ready')).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects an unsafe release entry URL before rendering an iframe', async () => {
|
||||
getGameMock.mockResolvedValueOnce(
|
||||
createGame({
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import './gameDistribution.css';
|
||||
|
||||
import {
|
||||
PLATFORM_GAME_LOADING_TIMEOUT_MS,
|
||||
PlatformGameLoadingSurface,
|
||||
} from '@genarrative/shared/components';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Expand,
|
||||
@@ -26,8 +30,11 @@ import { GameCover } from './GameGalleryPage';
|
||||
|
||||
type GamePlayPageProps = { gameId: string | null; onBack: () => void };
|
||||
|
||||
/** 发行包首屏加载上限:超过后给出显式超时与重试,不用无限期“正在启动”。 */
|
||||
export const GAME_PLAY_STARTUP_TIMEOUT_MS = 20_000;
|
||||
/**
|
||||
* 发行包首屏加载上限:超过后给出显式超时与重试,不用无限期等待。
|
||||
* 与后台审核试玩共用同一口径(`PLATFORM_GAME_LOADING_TIMEOUT_MS`)。
|
||||
*/
|
||||
export const GAME_PLAY_STARTUP_TIMEOUT_MS = PLATFORM_GAME_LOADING_TIMEOUT_MS;
|
||||
|
||||
type GameStartupState = 'idle' | 'loading' | 'ready' | 'timeout';
|
||||
|
||||
@@ -212,7 +219,12 @@ export function GamePlayPage({ gameId, onBack }: GamePlayPageProps) {
|
||||
</div>
|
||||
) : null}
|
||||
{hasStarted && startupState === 'loading' ? (
|
||||
<div className="game-player-loading">游戏正在启动…</div>
|
||||
<PlatformGameLoadingSurface
|
||||
key={`game-loading-${frameRunId}`}
|
||||
tone="player"
|
||||
title={game.title}
|
||||
cover={<GameCover game={game} compact />}
|
||||
/>
|
||||
) : null}
|
||||
{hasStarted && startupState === 'timeout' ? (
|
||||
<PlatformStatusMessage
|
||||
|
||||
@@ -517,15 +517,6 @@
|
||||
.game-player-frame--ready {
|
||||
opacity: 1;
|
||||
}
|
||||
.game-player-loading {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: rgba(255, 241, 227, 0.75);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.game-play-safety-note {
|
||||
margin: 0;
|
||||
color: var(--platform-text-soft);
|
||||
@@ -645,7 +636,6 @@
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
.game-player-loading,
|
||||
.game-player-timeout {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user