diff --git a/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md b/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md
index d9b9fa6a4..ca79346e7 100644
--- a/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md
+++ b/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md
@@ -320,3 +320,8 @@
| 同上路径但带 `Cookie` | **403 Forbidden** + `application/json`(`x-api-version: 2026-06-16`、`x-response-time-ms`)——发行读取**拒绝携带账号凭证**的请求,与本地用例断言一致 |
**生产环境**(`genarrative.world`)同一时刻 `/api/game-distribution/games` 返回 **0 条**:说明当前生产还没有任何已发布的游戏,条目 ④「真实环境整链路」需要在生产发布一个作品后再跑。也就是说此刻能拿到的真实环境证据是「dev 部署的公开读路径 + 发行隔离」,写路径与审核链路的真实环境验收仍需生产账号。
+
+## 网页侧交互修正(2026-09-30)
+
+- **广场首屏「立即试玩」不再替用户选中作品**:`src/components/game-distribution/GameGalleryPage.tsx` 原来是 `onClick={() => featuredGame && openDetail(featuredGame.id)}`,点一下等于直接打开第一款游戏的详情。现在按钮只把列表滚进视野(按平台页签面板计算相对偏移,`scrollTo({ behavior: 'smooth' })`,无 `scrollTo` 时退化成直接赋值),不再触发 `onOpenDetail`;`featuredGame` 派生随之退役。回归用例 `GameDistributionPages.test.tsx`「首屏「立即试玩」滚动到列表,而不是打开第一款游戏详情」断言 `onOpenDetail` 未被调用且滚动落在页签面板上。
+- 验证:`npx vitest run src/components/game-distribution/GameDistributionPages.test.tsx`(21 passed)、`npm run typecheck`、`npx eslint`、`npm run check:encoding`、`git diff --check` 通过。视觉与真机走查仍需人工/截图评审(沿用阶段 C 第 8 条未取证口径)。
diff --git a/src/components/game-distribution/GameDistributionPages.test.tsx b/src/components/game-distribution/GameDistributionPages.test.tsx
index 15b3b8720..f550408c7 100644
--- a/src/components/game-distribution/GameDistributionPages.test.tsx
+++ b/src/components/game-distribution/GameDistributionPages.test.tsx
@@ -375,6 +375,50 @@ describe('GameGalleryPage 筛选与返回上下文', () => {
).toBe('180'),
);
});
+
+ it('首屏「立即试玩」滚动到列表,而不是打开第一款游戏详情', async () => {
+ resetGalleryContext();
+ const onOpenDetail = vi.fn();
+ listGamesMock.mockResolvedValue([
+ createGame({ id: 'game-1', title: '星轨防线' }),
+ createGame({ id: 'game-2', title: '第二款' }),
+ ]);
+ const scrollTo = vi.fn();
+ const originalScrollTo = Object.getOwnPropertyDescriptor(
+ Element.prototype,
+ 'scrollTo',
+ );
+ Object.defineProperty(Element.prototype, 'scrollTo', {
+ configurable: true,
+ writable: true,
+ value: scrollTo,
+ });
+ try {
+ const { container } = render(
+
+
+
,
+ );
+ expect(await screen.findByText('星轨防线')).toBeTruthy();
+
+ fireEvent.click(screen.getByRole('button', { name: /立即试玩/u }));
+
+ expect(onOpenDetail).not.toHaveBeenCalled();
+ expect(scrollTo).toHaveBeenCalledTimes(1);
+ expect(scrollTo.mock.instances[0]).toBe(
+ container.querySelector('.platform-tab-panel'),
+ );
+ expect(scrollTo.mock.calls[0]?.[0]).toMatchObject({
+ behavior: 'smooth',
+ });
+ } finally {
+ if (originalScrollTo) {
+ Object.defineProperty(Element.prototype, 'scrollTo', originalScrollTo);
+ } else {
+ delete (Element.prototype as { scrollTo?: unknown }).scrollTo;
+ }
+ }
+ });
});
describe('GamePlayPage 启动超时与旋转提示', () => {
diff --git a/src/components/game-distribution/GameGalleryPage.tsx b/src/components/game-distribution/GameGalleryPage.tsx
index 11636ccfc..36d888e05 100644
--- a/src/components/game-distribution/GameGalleryPage.tsx
+++ b/src/components/game-distribution/GameGalleryPage.tsx
@@ -145,6 +145,7 @@ export function GameGalleryPage({
const [error, setError] = useState('');
const requestSerialRef = useRef(0);
const scrollContainerRef = useRef(null);
+ const gameListRef = useRef(null);
const hasRestoredScrollRef = useRef(false);
/**
* 顶栏搜索词只在「真的变了」时覆盖目录筛选。
@@ -167,6 +168,31 @@ export function GameGalleryPage({
container instanceof HTMLElement ? container : null;
}, []);
+ /**
+ * 首屏「立即试玩」只把列表滚进视野,不替用户选定某一款游戏。
+ *
+ * 滚动容器是平台页签面板而不是窗口,所以按容器相对位置计算,缺少
+ * `scrollTo` 时退化成直接赋值,保证无动画环境也能生效。
+ */
+ const scrollToGameList = useCallback(() => {
+ const anchor = gameListRef.current;
+ if (!anchor) return;
+ const container = scrollContainerRef.current;
+ if (!container) {
+ anchor.scrollIntoView?.({ behavior: 'smooth', block: 'start' });
+ return;
+ }
+ const nextScrollTop =
+ container.scrollTop +
+ anchor.getBoundingClientRect().top -
+ container.getBoundingClientRect().top;
+ if (typeof container.scrollTo === 'function') {
+ container.scrollTo({ top: nextScrollTop, behavior: 'smooth' });
+ return;
+ }
+ container.scrollTop = nextScrollTop;
+ }, []);
+
const loadGames = useCallback(() => {
const requestSerial = ++requestSerialRef.current;
setIsLoading(true);
@@ -231,7 +257,6 @@ export function GameGalleryPage({
() => games.filter((game) => matchesGameDeviceFilter(game, device)),
[device, games],
);
- const featuredGame = useMemo(() => visibleGames[0] ?? null, [visibleGames]);
const hasActiveFilters =
activeKeyword.trim().length > 0 || category !== '全部' || device !== '全部';
const resetFilters = () => {
@@ -270,7 +295,7 @@ export function GameGalleryPage({
featuredGame && openDetail(featuredGame.id)}
+ onClick={scrollToGameList}
>
立即试玩
@@ -282,7 +307,7 @@ export function GameGalleryPage({
-