项目界面无法跳转的问题修复
让 /project 复用创作主页桌面导航壳 为项目页补充保留左侧导航并切回推荐页的回归测试 同步项目入口文档约束
This commit is contained in:
@@ -115,6 +115,7 @@ Tab:
|
||||
- `/creation/<play>` 现有玩法工作台路由保持原有 stage 映射。
|
||||
- 桌面端点击“创作”时调用 `pushAppHistoryPath('/creation')`。
|
||||
- 桌面端原“草稿”入口改为“项目”,点击 `pushAppHistoryPath('/project')`。
|
||||
- 桌面端 `/project` 必须复用 `/creation` 的平台桌面 chrome,保留左侧完整导航栏和右侧顶部栏;`ProjectGalleryView` 只替换右侧主内容区,不能作为脱离导航的独立全屏页渲染,避免用户进入项目页后无法返回或切换页签。
|
||||
- 移动端导航列表不展示“创作”和“项目”。
|
||||
- 移动端不拦截用户直接访问 `/creation` 或 `/project`。
|
||||
|
||||
|
||||
@@ -15251,10 +15251,18 @@ export function PlatformEntryFlowShellImpl({
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
const projectGalleryContent = (
|
||||
<Suspense fallback={<LazyPanelFallback label="正在加载项目..." />}>
|
||||
<ProjectGalleryView onOpenProject={openEditorProject} />
|
||||
</Suspense>
|
||||
);
|
||||
const handlePlatformHomeTabChange = useCallback(
|
||||
(tab: PlatformHomeTab) => {
|
||||
platformBootstrap.setPlatformTab(tab);
|
||||
if (selectionStage === 'creation-home' && tab !== 'create') {
|
||||
if (
|
||||
(selectionStage === 'creation-home' && tab !== 'create') ||
|
||||
selectionStage === 'project'
|
||||
) {
|
||||
setSelectionStage('platform');
|
||||
pushAppHistoryPath('/');
|
||||
}
|
||||
@@ -15264,11 +15272,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
const platformHomeView = (
|
||||
<PlatformEntryHomeView
|
||||
activeTab={
|
||||
selectionStage === 'creation-home'
|
||||
selectionStage === 'creation-home' || selectionStage === 'project'
|
||||
? 'create'
|
||||
: platformBootstrap.platformTab
|
||||
}
|
||||
allowHiddenActiveTab={selectionStage === 'creation-home'}
|
||||
activeDesktopNavTab={selectionStage === 'project' ? 'projects' : undefined}
|
||||
allowHiddenActiveTab={
|
||||
selectionStage === 'creation-home' || selectionStage === 'project'
|
||||
}
|
||||
onTabChange={handlePlatformHomeTabChange}
|
||||
hasSavedGame={hasSavedGame}
|
||||
savedSnapshot={savedSnapshot}
|
||||
@@ -15298,9 +15309,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
: platformBootstrap.dashboardError
|
||||
}
|
||||
createTabContent={
|
||||
selectionStage === 'creation-home'
|
||||
? creationLandingContent
|
||||
: creationStartContent
|
||||
selectionStage === 'project'
|
||||
? projectGalleryContent
|
||||
: selectionStage === 'creation-home'
|
||||
? creationLandingContent
|
||||
: creationStartContent
|
||||
}
|
||||
draftTabContent={draftHubContent}
|
||||
onContinueGame={handleContinueGame}
|
||||
@@ -15408,21 +15421,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
)}
|
||||
{selectionStage === 'project' && (
|
||||
<motion.div
|
||||
key="project-gallery"
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -12 }}
|
||||
className="project-gallery-stage-shell flex h-full min-h-0 min-w-0 flex-col overflow-hidden"
|
||||
>
|
||||
<Suspense fallback={<LazyPanelFallback label="正在加载项目..." />}>
|
||||
<ProjectGalleryView
|
||||
onOpenProject={openEditorProject}
|
||||
/>
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
)}
|
||||
{selectionStage === 'creation-home' && (
|
||||
<motion.div
|
||||
key="creation-home"
|
||||
@@ -15434,6 +15432,17 @@ export function PlatformEntryFlowShellImpl({
|
||||
{platformHomeView}
|
||||
</motion.div>
|
||||
)}
|
||||
{selectionStage === 'project' && (
|
||||
<motion.div
|
||||
key="project-gallery"
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -12 }}
|
||||
className="creation-home-stage flex h-full min-h-0 min-w-0 flex-col overflow-hidden"
|
||||
>
|
||||
{platformHomeView}
|
||||
</motion.div>
|
||||
)}
|
||||
{selectionStage === 'platform' && (
|
||||
<motion.div
|
||||
key="platform-home"
|
||||
|
||||
@@ -94,6 +94,10 @@ import {
|
||||
regenerateBabyObjectMatchDraftAssets,
|
||||
saveBabyObjectMatchDraft,
|
||||
} from '../../services/edutainment-baby-object';
|
||||
import {
|
||||
listEditorProjects,
|
||||
loadEditorAssetLibrary,
|
||||
} from '../../services/image-editor/editorProjectClient';
|
||||
import { jumpHopClient } from '../../services/jump-hop/jumpHopClient';
|
||||
import { match3dCreationClient } from '../../services/match3d-creation';
|
||||
import { createServerMatch3DRuntimeAdapter } from '../../services/match3d-runtime';
|
||||
@@ -703,6 +707,14 @@ vi.mock('../../services/edutainment-baby-object', () => ({
|
||||
saveBabyObjectMatchDraft: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/image-editor/editorProjectClient', () => ({
|
||||
createEditorProject: vi.fn(),
|
||||
deleteEditorProject: vi.fn(),
|
||||
listEditorProjects: vi.fn(),
|
||||
loadEditorAssetLibrary: vi.fn(async () => ({ assets: [] })),
|
||||
renameEditorProject: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/wooden-fish/woodenFishClient', () => ({
|
||||
woodenFishClient: {
|
||||
checkpointRun: vi.fn(),
|
||||
@@ -2717,6 +2729,11 @@ beforeEach(() => {
|
||||
});
|
||||
vi.mocked(listRpgEntryWorldLibrary).mockResolvedValue([]);
|
||||
vi.mocked(listRpgEntryWorldGallery).mockResolvedValue([]);
|
||||
vi.mocked(listEditorProjects).mockResolvedValue([]);
|
||||
vi.mocked(loadEditorAssetLibrary).mockResolvedValue({
|
||||
assets: [],
|
||||
folders: [],
|
||||
});
|
||||
vi.mocked(getRpgEntryWorldGalleryDetail).mockImplementation(
|
||||
async (ownerUserId, profileId) =>
|
||||
buildMockRpgGalleryDetail({
|
||||
@@ -9169,6 +9186,27 @@ test('creation home desktop rail can return to platform tabs', async () => {
|
||||
expect(window.location.pathname).toBe('/');
|
||||
});
|
||||
|
||||
test('project page keeps desktop rail and can return to platform tabs', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockDesktopPlatformLayout();
|
||||
window.history.replaceState(null, '', '/project');
|
||||
const { container } = render(<TestWrapper withAuth />);
|
||||
|
||||
expect(await screen.findByRole('main', { name: '项目' })).toBeTruthy();
|
||||
const nav = container.querySelector('.platform-desktop-rail');
|
||||
expect(nav).toBeTruthy();
|
||||
expect(
|
||||
within(nav as HTMLElement).getByRole('button', { name: '项目' }),
|
||||
).toBeTruthy();
|
||||
|
||||
await user.click(within(nav as HTMLElement).getByRole('button', { name: '推荐' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('main', { name: '项目' })).toBeNull();
|
||||
});
|
||||
expect(window.location.pathname).toBe('/');
|
||||
});
|
||||
|
||||
test('direct mobile creation home keeps landing content without mobile nav entry', async () => {
|
||||
window.history.replaceState(null, '', '/creation');
|
||||
const { container } = render(<TestWrapper withAuth />);
|
||||
|
||||
@@ -228,6 +228,7 @@ export type PlatformHomeTab =
|
||||
type PlatformNavTab = PlatformHomeTab | 'projects';
|
||||
export interface RpgEntryHomeViewProps {
|
||||
activeTab: PlatformHomeTab;
|
||||
activeDesktopNavTab?: PlatformNavTab;
|
||||
isDesktopLayout?: boolean;
|
||||
onTabChange: (tab: PlatformHomeTab) => void;
|
||||
hasSavedGame: boolean;
|
||||
@@ -1939,10 +1940,6 @@ function DesktopTabButton({
|
||||
);
|
||||
}
|
||||
|
||||
function isPlatformHomeTab(tab: PlatformNavTab): tab is PlatformHomeTab {
|
||||
return tab !== 'projects';
|
||||
}
|
||||
|
||||
function PlatformTabPanel({
|
||||
tab,
|
||||
activeTab,
|
||||
@@ -2584,6 +2581,7 @@ function RechargePaymentConfirmationMask({ orderId }: { orderId: string }) {
|
||||
|
||||
export function RpgEntryHomeView({
|
||||
activeTab,
|
||||
activeDesktopNavTab,
|
||||
isDesktopLayout: isDesktopLayoutProp,
|
||||
onTabChange,
|
||||
saveEntries,
|
||||
@@ -2973,6 +2971,7 @@ export function RpgEntryHomeView({
|
||||
saves: '草稿',
|
||||
profile: '我的',
|
||||
} as const satisfies Record<PlatformNavTab, string>;
|
||||
const resolvedDesktopNavTab = activeDesktopNavTab ?? activeTab;
|
||||
|
||||
useEffect(() => {
|
||||
if (allowHiddenActiveTab) {
|
||||
@@ -5034,7 +5033,7 @@ export function RpgEntryHomeView({
|
||||
{visibleDesktopNavTabs.map((tab) => (
|
||||
<DesktopTabButton
|
||||
key={tab}
|
||||
active={isPlatformHomeTab(tab) && activeTab === tab}
|
||||
active={resolvedDesktopNavTab === tab}
|
||||
label={tabLabels[tab]}
|
||||
icon={tabIcons[tab]}
|
||||
creationHomeIconSrc={CREATION_HOME_NAV_ICON_SRC[tab]}
|
||||
|
||||
Reference in New Issue
Block a user