调整桌面首页进入创作主页

桌面端打开根路径默认进入创作主页

移动端根路径继续保持推荐首页

补充首页初始路由测试与创作主页文档说明
This commit is contained in:
2026-06-21 20:37:01 +08:00
parent 7e414ec37c
commit 47023cd9be
5 changed files with 108 additions and 16 deletions
@@ -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/<play>` 仍进入对应玩法工作台;桌面导航显示“创作 / 项目”且不显示“草稿”;移动端底部不显示“创作 / 项目”;页面不出现外站品牌或 `Discord` 字样;新建项目进入 `/editor/canvas?projectid=xxx`
- 验证方式:桌面 `/` 初始阶段和 `/creation` 解析为创作主页,移动端 `/` 仍是推荐首页,`/creation/<play>` 仍进入对应玩法工作台;桌面导航显示“创作 / 项目”且不显示“草稿”;移动端底部不显示“创作 / 项目”;页面不出现外站品牌或 `Discord` 字样;新建项目进入 `/editor/canvas?projectid=xxx`
- 关联文档:`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 2026-06-18 图片画布 Seedance 2.0 参考媒体提交边界
@@ -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/<play>` 现有玩法工作台路由保持原有 stage 映射。
- 桌面端点击“创作”时调用 `pushAppHistoryPath('/creation')`
- 桌面端原“草稿”入口改为“项目”,点击 `pushAppHistoryPath('/project')`
@@ -124,6 +126,7 @@ Tab
### 路由与导航
- 增加 `/creation` 路由映射和测试。
- 增加桌面 `/` 初始阶段映射测试,确认不会影响移动端首页。
- 调整桌面导航文案与点击行为。
- 移动端导航移除“创作”和“项目”入口。
- 保持现有 `/creation/<play>` 工作台、生成页、结果页和运行态路由不回归。
@@ -172,6 +175,7 @@ git diff --check
浏览器回归:
- 桌面打开站点首页 `/`,首屏显示新版创作主页。
- 桌面打开首页,点击“创作”进入 `/creation`
- `/creation` 显示 `陶泥儿 - 开启全民精品游戏创作``陶泥儿精选`
- 页面不出现外站品牌或 `Discord` 字样。
+78 -11
View File
@@ -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: () => <div></div>,
}));
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(<App />);
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();
+10 -3
View File
@@ -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<SelectionStage>(() =>
resolveSelectionStageFromPath(window.location.pathname),
resolveInitialSelectionStageFromPath(
window.location.pathname,
getInitialPlatformDesktopLayout(),
),
);
const [runtimeReturnStage, setRuntimeReturnStage] =
useState<SelectionStage>('platform');
@@ -84,7 +88,10 @@ export default function App() {
setIsRuntimeActive(false);
setRawSelectionStage(
resolveSelectionStageFromPath(window.location.pathname),
resolveInitialSelectionStageFromPath(
window.location.pathname,
getInitialPlatformDesktopLayout(),
),
);
};
+14
View File
@@ -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;
}