From 47023cd9be58a04a17ab8411f7261e1c4571138f Mon Sep 17 00:00:00 2001 From: kdletters Date: Sun, 21 Jun 2026 20:37:01 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A1=8C=E9=9D=A2=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E8=BF=9B=E5=85=A5=E5=88=9B=E4=BD=9C=E4=B8=BB=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 桌面端打开根路径默认进入创作主页 移动端根路径继续保持推荐首页 补充首页初始路由测试与创作主页文档说明 --- .../shared-memory/decision-log.md | 4 +- ...作】创作主页与项目入口改版计划-2026-06-18.md | 4 + src/App.test.tsx | 89 ++++++++++++++++--- src/App.tsx | 13 ++- src/routing/appPageRoutes.ts | 14 +++ 5 files changed, 108 insertions(+), 16 deletions(-) diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 0d0b7a56e..f307a9e37 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -59,9 +59,9 @@ ## 2026-06-18 `/creation` 独立为陶泥儿创作工具主页 - 背景:图片画布项目已经成为独立项目资产,旧“创作”站内 Tab 和一级“草稿”入口不能清晰表达桌面端创作工具主页与项目管理入口。 -- 决策:桌面端新增独立 `/creation` 创作工具主页;顶级“创作”入口跳转 `/creation`,原“草稿”入口替换为“项目”并跳转 `/project`。移动端隐藏“创作”和“项目”入口,但不拦截直接 URL。页面文案统一使用“陶泥儿”,外部品牌只作为设计参考,不进入用户可见 UI、测试名、产品文案或验收口径。`陶泥儿精选` 是用户素材瀑布流,不展示玩法入口列表;创作入口事实源继续来自 `/api/creation-entry/config`,项目入口通过 `createEditorProject` 和 `/editor/canvas?projectid=xxx` 链路进入画布。 +- 决策:桌面端新增独立 `/creation` 创作工具主页,桌面端直接打开站点首页 `/` 时也默认展示新版创作主页;顶级“创作”入口跳转 `/creation`,原“草稿”入口替换为“项目”并跳转 `/project`。移动端首页 `/` 继续保持原推荐首页,移动端隐藏“创作”和“项目”入口,但不拦截直接 URL。页面文案统一使用“陶泥儿”,外部品牌只作为设计参考,不进入用户可见 UI、测试名、产品文案或验收口径。`陶泥儿精选` 是用户素材瀑布流,不展示玩法入口列表;创作入口事实源继续来自 `/api/creation-entry/config`,项目入口通过 `createEditorProject` 和 `/editor/canvas?projectid=xxx` 链路进入画布。 - 影响范围:平台入口导航、`SelectionStage` 路由、`/creation` 首页、`/project` 项目入口、`/editor/canvas` 项目打开链路、“我的”页快捷入口和创作入口相关测试。 -- 验证方式:`/creation` 解析为创作主页,`/creation/` 仍进入对应玩法工作台;桌面导航显示“创作 / 项目”且不显示“草稿”;移动端底部不显示“创作 / 项目”;页面不出现外站品牌或 `Discord` 字样;新建项目进入 `/editor/canvas?projectid=xxx`。 +- 验证方式:桌面 `/` 初始阶段和 `/creation` 解析为创作主页,移动端 `/` 仍是推荐首页,`/creation/` 仍进入对应玩法工作台;桌面导航显示“创作 / 项目”且不显示“草稿”;移动端底部不显示“创作 / 项目”;页面不出现外站品牌或 `Discord` 字样;新建项目进入 `/editor/canvas?projectid=xxx`。 - 关联文档:`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 ## 2026-06-18 图片画布 Seedance 2.0 参考媒体提交边界 diff --git a/docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md b/docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md index 805937126..857fb3680 100644 --- a/docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md +++ b/docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md @@ -11,6 +11,7 @@ ## 目标 - 新增桌面端独立 `/creation` 创作工具主页。 +- 桌面端直接打开站点首页 `/` 时默认展示新版创作主页内容;移动端继续保持原推荐首页。 - 顶级导航中“创作”进入 `/creation`,“草稿”改为“项目”并进入 `/project`。 - 移动端隐藏“创作”和“项目”入口,只保留浏览和个人相关入口。 - 登录后在创作主页展示最近项目,并通过新建项目进入 `/editor/canvas?projectid=xxx`。 @@ -112,6 +113,7 @@ Tab: - 新增 `SelectionStage = 'creation-home'`。 - `/creation` 映射到 `creation-home`。 +- 桌面端初始打开 `/` 时也进入 `creation-home`;移动端初始打开 `/` 仍进入 `platform` 推荐首页。 - `/creation/` 现有玩法工作台路由保持原有 stage 映射。 - 桌面端点击“创作”时调用 `pushAppHistoryPath('/creation')`。 - 桌面端原“草稿”入口改为“项目”,点击 `pushAppHistoryPath('/project')`。 @@ -124,6 +126,7 @@ Tab: ### 路由与导航 - 增加 `/creation` 路由映射和测试。 +- 增加桌面 `/` 初始阶段映射测试,确认不会影响移动端首页。 - 调整桌面导航文案与点击行为。 - 移动端导航移除“创作”和“项目”入口。 - 保持现有 `/creation/` 工作台、生成页、结果页和运行态路由不回归。 @@ -172,6 +175,7 @@ git diff --check 浏览器回归: +- 桌面打开站点首页 `/`,首屏显示新版创作主页。 - 桌面打开首页,点击“创作”进入 `/creation`。 - `/creation` 显示 `陶泥儿 - 开启全民精品游戏创作` 和 `陶泥儿精选`。 - 页面不出现外站品牌或 `Discord` 字样。 diff --git a/src/App.test.tsx b/src/App.test.tsx index bd1bac462..1bbffb369 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -3,9 +3,27 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { ComponentProps } from 'react'; -import { describe, expect, it, vi } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import App from './App'; +import { resolveInitialSelectionStageFromPath } from './routing/appPageRoutes'; + +function mockMatchMedia(matches: boolean) { + Object.defineProperty(window, 'matchMedia', { + configurable: true, + writable: true, + value: vi.fn().mockImplementation((query: string) => ({ + matches, + media: query, + onchange: null, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + addListener: vi.fn(), + removeListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); +} vi.mock('./components/platform-entry/PlatformEntryFlowShell', async () => { const React = await import('react'); @@ -14,18 +32,30 @@ vi.mock('./components/platform-entry/PlatformEntryFlowShell', async () => { >; return { - PlatformEntryFlowShell: ({ setSelectionStage }: PlatformEntryFlowShellProps) => + PlatformEntryFlowShell: ({ + selectionStage, + setSelectionStage, + }: PlatformEntryFlowShellProps) => React.createElement( - 'button', - { - type: 'button', - onClick: () => { - setSelectionStage('image-editor', { - path: '/editor/canvas?projectid=project-from-test', - }); + 'div', + null, + React.createElement( + 'div', + { 'data-testid': 'selection-stage' }, + selectionStage, + ), + React.createElement( + 'button', + { + type: 'button', + onClick: () => { + setSelectionStage('image-editor', { + path: '/editor/canvas?projectid=project-from-test', + }); + }, }, - }, - '打开最近项目', + '打开最近项目', + ), ), }; }); @@ -34,8 +64,45 @@ vi.mock('./RpgRuntimeApp', () => ({ RpgRuntimeApp: () =>
运行态
, })); +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe('resolveInitialSelectionStageFromPath', () => { + it('opens the desktop root route on the creation home stage', () => { + expect(resolveInitialSelectionStageFromPath('/', true)).toBe( + 'creation-home', + ); + }); + + it('keeps the mobile root route on the platform stage', () => { + expect(resolveInitialSelectionStageFromPath('/', false)).toBe('platform'); + }); + + it('keeps explicit routes mapped to their own stages on desktop', () => { + expect(resolveInitialSelectionStageFromPath('/creation/puzzle', true)).toBe( + 'puzzle-agent-workspace', + ); + expect(resolveInitialSelectionStageFromPath('/creation', true)).toBe( + 'creation-home', + ); + }); +}); + describe('App navigation history', () => { + it('renders the desktop home URL with the creation home stage', () => { + mockMatchMedia(true); + window.history.replaceState(null, '', '/'); + + render(); + + expect(screen.getByTestId('selection-stage').textContent).toBe( + 'creation-home', + ); + }); + it('keeps project canvas navigation as one history entry with projectid', async () => { + mockMatchMedia(true); window.history.replaceState(null, '', '/creation'); const pushStateSpy = vi.spyOn(window.history, 'pushState'); const user = userEvent.setup(); diff --git a/src/App.tsx b/src/App.tsx index 237fafde3..7fd3d6412 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import { import { useAuthUi } from './components/auth/AuthUiContext'; import { PlatformEntryFlowShell } from './components/platform-entry/PlatformEntryFlowShell'; +import { getInitialPlatformDesktopLayout } from './components/platform-entry/platformEntryResponsive'; import type { CustomWorldRuntimeLaunchOptions, SelectionStage, @@ -19,8 +20,8 @@ import { normalizeAppPath, pushAppHistoryPath, readPublicWorkCodeFromLocationSearch, + resolveInitialSelectionStageFromPath, resolvePathForSelectionStage, - resolveSelectionStageFromPath, } from './routing/appPageRoutes'; import type { RpgRuntimeAppIntent } from './RpgRuntimeApp'; import type { CustomWorldProfile } from './types'; @@ -59,7 +60,10 @@ export default function App() { isRpgRuntimeRoute(window.location.pathname), ); const [selectionStage, setRawSelectionStage] = useState(() => - resolveSelectionStageFromPath(window.location.pathname), + resolveInitialSelectionStageFromPath( + window.location.pathname, + getInitialPlatformDesktopLayout(), + ), ); const [runtimeReturnStage, setRuntimeReturnStage] = useState('platform'); @@ -84,7 +88,10 @@ export default function App() { setIsRuntimeActive(false); setRawSelectionStage( - resolveSelectionStageFromPath(window.location.pathname), + resolveInitialSelectionStageFromPath( + window.location.pathname, + getInitialPlatformDesktopLayout(), + ), ); }; diff --git a/src/routing/appPageRoutes.ts b/src/routing/appPageRoutes.ts index 9ffec8560..0d8b349dc 100644 --- a/src/routing/appPageRoutes.ts +++ b/src/routing/appPageRoutes.ts @@ -101,6 +101,20 @@ export function resolveSelectionStageFromPath( return ROUTE_STAGE_BY_PATH.get(normalizeAppPath(pathname)) ?? 'platform'; } +export function resolveInitialSelectionStageFromPath( + pathname: string, + isDesktopLayout: boolean, +): SelectionStage { + const normalizedPath = normalizeAppPath(pathname); + const selectionStage = resolveSelectionStageFromPath(pathname); + + if (normalizedPath === '/' && isDesktopLayout) { + return 'creation-home'; + } + + return selectionStage; +} + export function resolvePathForSelectionStage(stage: SelectionStage) { return APP_STAGE_ROUTES[stage] ?? APP_STAGE_ROUTES.platform; }