恢复移动端创作工具门禁
移动端底部导航仅保留我的入口 移动端创作、项目与画布直达统一展示桌面端提示 补充移动端入口与画布阻断回归测试 纠正旧模板退役时越权扩大的移动端产品口径
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { useState } from 'react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { PlatformEntryFlowShellImpl } from './PlatformEntryActiveFlowShell';
|
||||
import type { SelectionStage } from './platformEntryActiveTypes';
|
||||
|
||||
const authUiMock = vi.hoisted(() => ({
|
||||
value: {
|
||||
@@ -15,10 +17,18 @@ const authUiMock = vi.hoisted(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const responsiveMock = vi.hoisted(() => ({
|
||||
isDesktopLayout: true,
|
||||
}));
|
||||
|
||||
vi.mock('../auth/AuthUiContext', () => ({
|
||||
useAuthUi: () => authUiMock.value,
|
||||
}));
|
||||
|
||||
vi.mock('./platformEntryResponsive', () => ({
|
||||
usePlatformDesktopLayout: () => responsiveMock.isDesktopLayout,
|
||||
}));
|
||||
|
||||
vi.mock('../creation-home/CreationLandingView', () => ({
|
||||
CreationLandingView: ({
|
||||
onOpenProject,
|
||||
@@ -102,10 +112,26 @@ vi.mock('./usePlatformProfileCenterController', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
function StatefulPlatformEntryFlowShell({
|
||||
initialStage,
|
||||
}: {
|
||||
initialStage: SelectionStage;
|
||||
}) {
|
||||
const [selectionStage, setSelectionStage] = useState(initialStage);
|
||||
|
||||
return (
|
||||
<PlatformEntryFlowShellImpl
|
||||
selectionStage={selectionStage}
|
||||
setSelectionStage={(nextStage) => setSelectionStage(nextStage)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
describe('PlatformEntryActiveFlowShell', () => {
|
||||
beforeEach(() => {
|
||||
window.history.replaceState(null, '', '/creation');
|
||||
authUiMock.value.openLoginModal.mockReset();
|
||||
responsiveMock.isDesktopLayout = true;
|
||||
});
|
||||
|
||||
it('keeps the active desktop rail and the shared account capsule', async () => {
|
||||
@@ -174,11 +200,12 @@ describe('PlatformEntryActiveFlowShell', () => {
|
||||
).toBe('page');
|
||||
});
|
||||
|
||||
it('keeps creation, project, and profile reachable from the mobile dock', async () => {
|
||||
it('keeps only profile reachable from the mobile dock', async () => {
|
||||
responsiveMock.isDesktopLayout = false;
|
||||
const setSelectionStage = vi.fn();
|
||||
render(
|
||||
<PlatformEntryFlowShellImpl
|
||||
selectionStage="project"
|
||||
selectionStage="platform"
|
||||
setSelectionStage={setSelectionStage}
|
||||
/>,
|
||||
);
|
||||
@@ -190,12 +217,7 @@ describe('PlatformEntryActiveFlowShell', () => {
|
||||
within(navigation)
|
||||
.getAllByRole('button')
|
||||
.map((button) => button.getAttribute('aria-label')),
|
||||
).toEqual(['创作', '项目', '我的']);
|
||||
expect(
|
||||
within(navigation)
|
||||
.getByRole('button', { name: '项目' })
|
||||
.getAttribute('aria-current'),
|
||||
).toBe('page');
|
||||
).toEqual(['我的']);
|
||||
|
||||
fireEvent.click(within(navigation).getByRole('button', { name: '我的' }));
|
||||
expect(window.location.pathname).toBe('/profile');
|
||||
@@ -204,6 +226,48 @@ describe('PlatformEntryActiveFlowShell', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['creation-home', 'project', 'image-editor'] as const)(
|
||||
'shows the desktop guide instead of mounting the %s page on mobile',
|
||||
async (selectionStage) => {
|
||||
responsiveMock.isDesktopLayout = false;
|
||||
|
||||
render(
|
||||
<PlatformEntryFlowShellImpl
|
||||
selectionStage={selectionStage}
|
||||
setSelectionStage={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByRole('main', { name: '桌面端创作提示' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
screen.queryByRole('main', { name: '陶泥儿创作主页' }),
|
||||
).toBeNull();
|
||||
expect(screen.queryByRole('main', { name: '项目' })).toBeNull();
|
||||
expect(
|
||||
screen.queryByRole('main', { name: '图片画布编辑器' }),
|
||||
).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it('shows the desktop guide instead of the editor after opening a canvas tool from the mobile root', async () => {
|
||||
responsiveMock.isDesktopLayout = false;
|
||||
|
||||
render(<StatefulPlatformEntryFlowShell initialStage="platform" />);
|
||||
|
||||
fireEvent.click(
|
||||
await screen.findByRole('button', { name: '打开音乐工具' }),
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByRole('main', { name: '桌面端创作提示' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
screen.queryByRole('main', { name: '图片画布编辑器' }),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('switches between creation and projects and passes search to the active page', async () => {
|
||||
const setSelectionStage = vi.fn();
|
||||
const { rerender } = render(
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { FolderKanban, Palette, Search, UserRound } from 'lucide-react';
|
||||
import {
|
||||
FolderKanban,
|
||||
Home,
|
||||
Monitor,
|
||||
Palette,
|
||||
Search,
|
||||
UserRound,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
type ComponentType,
|
||||
type FormEvent,
|
||||
@@ -20,6 +27,8 @@ import {
|
||||
import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import { FLOATING_FEEDBACK_FORM_URL } from '../common/floatingFeedbackEntryModel';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
||||
import {
|
||||
resolveActivePublicUserCode,
|
||||
resolveActiveUserAvatarLabel,
|
||||
@@ -161,6 +170,45 @@ function LoadingPanel({ label }: { label: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function MobileCreationDesktopGuide({
|
||||
onReturnHome,
|
||||
}: {
|
||||
onReturnHome: () => void;
|
||||
}) {
|
||||
return (
|
||||
<main
|
||||
className="flex min-h-full flex-1 items-center justify-center px-4 py-8"
|
||||
aria-label="桌面端创作提示"
|
||||
>
|
||||
<PlatformSubpanel
|
||||
as="section"
|
||||
radius="xl"
|
||||
padding="none"
|
||||
className="platform-remap-surface w-full max-w-sm px-5 py-6 text-center"
|
||||
>
|
||||
<span className="mx-auto flex h-14 w-14 items-center justify-center rounded-[1.05rem] bg-[rgba(199,101,50,0.12)] text-[var(--platform-accent-strong)]">
|
||||
<Monitor aria-hidden="true" className="h-7 w-7" />
|
||||
</span>
|
||||
<h1 className="mt-4 text-xl font-black leading-tight text-[var(--platform-text-strong)]">
|
||||
请在桌面端打开创作工具
|
||||
</h1>
|
||||
<p className="mt-3 text-sm font-semibold leading-6 text-[var(--platform-text-base)]">
|
||||
图片画布、项目管理和素材库需要更大的操作空间。
|
||||
</p>
|
||||
<PlatformActionButton
|
||||
onClick={onReturnHome}
|
||||
size="md"
|
||||
shape="pill"
|
||||
className="mt-5 min-h-11 w-full"
|
||||
>
|
||||
<Home aria-hidden="true" className="h-4 w-4" />
|
||||
返回首页
|
||||
</PlatformActionButton>
|
||||
</PlatformSubpanel>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlatformEntryFlowShellImpl({
|
||||
selectionStage,
|
||||
setSelectionStage,
|
||||
@@ -173,6 +221,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
const [isApiKeysOpen, setIsApiKeysOpen] = useState(false);
|
||||
const [searchInput, setSearchInput] = useState('');
|
||||
const [activeSearchKeyword, setActiveSearchKeyword] = useState('');
|
||||
const [isMobileDesktopGuideOpen, setIsMobileDesktopGuideOpen] =
|
||||
useState(false);
|
||||
const isDesktopLayout = usePlatformDesktopLayout();
|
||||
|
||||
const refreshDashboard = useCallback(async () => {
|
||||
@@ -206,14 +256,22 @@ export function PlatformEntryFlowShellImpl({
|
||||
});
|
||||
|
||||
const openCreation = useCallback(() => {
|
||||
if (!isDesktopLayout) {
|
||||
setIsMobileDesktopGuideOpen(true);
|
||||
return;
|
||||
}
|
||||
pushAppHistoryPath('/creation');
|
||||
setSelectionStage('creation-home', { path: '/creation' });
|
||||
}, [setSelectionStage]);
|
||||
}, [isDesktopLayout, setSelectionStage]);
|
||||
|
||||
const openProjects = useCallback(() => {
|
||||
if (!isDesktopLayout) {
|
||||
setIsMobileDesktopGuideOpen(true);
|
||||
return;
|
||||
}
|
||||
pushAppHistoryPath('/project');
|
||||
setSelectionStage('project', { path: '/project' });
|
||||
}, [setSelectionStage]);
|
||||
}, [isDesktopLayout, setSelectionStage]);
|
||||
|
||||
const openProfile = useCallback(() => {
|
||||
pushAppHistoryPath('/profile');
|
||||
@@ -222,6 +280,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
const openEditorProject = useCallback(
|
||||
(projectId: string, options?: { guide?: boolean; tool?: string }) => {
|
||||
if (!isDesktopLayout) {
|
||||
setIsMobileDesktopGuideOpen(true);
|
||||
return;
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
params.set('projectid', projectId);
|
||||
if (options?.guide) {
|
||||
@@ -234,7 +296,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
pushAppHistoryPath(path);
|
||||
setSelectionStage('image-editor', { path });
|
||||
},
|
||||
[setSelectionStage],
|
||||
[isDesktopLayout, setSelectionStage],
|
||||
);
|
||||
|
||||
const replaceWithProjectGallery = useCallback(() => {
|
||||
@@ -242,6 +304,26 @@ export function PlatformEntryFlowShellImpl({
|
||||
setSelectionStage('project', { path: '/project' });
|
||||
}, [setSelectionStage]);
|
||||
|
||||
const isMobileToolStage =
|
||||
!isDesktopLayout &&
|
||||
(selectionStage === 'creation-home' ||
|
||||
selectionStage === 'project' ||
|
||||
selectionStage === 'image-editor');
|
||||
|
||||
if ((!isDesktopLayout && isMobileDesktopGuideOpen) || isMobileToolStage) {
|
||||
return (
|
||||
<MobileCreationDesktopGuide
|
||||
onReturnHome={() => {
|
||||
setIsMobileDesktopGuideOpen(false);
|
||||
if (selectionStage === 'platform') {
|
||||
return;
|
||||
}
|
||||
setSelectionStage('platform', { path: '/' });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectionStage === 'image-editor') {
|
||||
return (
|
||||
<div className="image-editor-stage-shell flex h-full min-h-0 min-w-0 flex-col overflow-hidden">
|
||||
@@ -469,22 +551,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
</div>
|
||||
<div className="platform-mobile-bottom-dock min-w-0 shrink-0 lg:hidden">
|
||||
<nav
|
||||
className="platform-bottom-nav grid grid-cols-3"
|
||||
className="platform-bottom-nav grid grid-cols-1"
|
||||
aria-label="移动平台导航"
|
||||
>
|
||||
<ActiveBottomNavButton
|
||||
active={isCreationStage}
|
||||
emphasized={isCreationStage}
|
||||
icon={Palette}
|
||||
label="创作"
|
||||
onClick={openCreation}
|
||||
/>
|
||||
<ActiveBottomNavButton
|
||||
active={selectionStage === 'project'}
|
||||
icon={FolderKanban}
|
||||
label="项目"
|
||||
onClick={openProjects}
|
||||
/>
|
||||
<ActiveBottomNavButton
|
||||
active={isProfileStage}
|
||||
icon={UserRound}
|
||||
|
||||
Reference in New Issue
Block a user