为项目页增加返回主站入口
在项目页头部增加返回主站按钮。 将返回动作接入平台首页路由。 补充项目页返回主站测试与文档说明。
This commit is contained in:
@@ -15253,7 +15253,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
);
|
||||
const projectGalleryContent = (
|
||||
<Suspense fallback={<LazyPanelFallback label="正在加载项目..." />}>
|
||||
<ProjectGalleryView onOpenProject={openEditorProject} />
|
||||
<ProjectGalleryView
|
||||
onOpenProject={openEditorProject}
|
||||
onReturnToMainSite={() => {
|
||||
platformBootstrap.setPlatformTab('home');
|
||||
setSelectionStage('platform');
|
||||
pushAppHistoryPath('/');
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
const handlePlatformHomeTabChange = useCallback(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import type { ContextType } from 'react';
|
||||
import type { ComponentProps, ContextType } from 'react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ApiClientError } from '../../services/apiClient';
|
||||
@@ -72,6 +72,18 @@ const projectItems = [
|
||||
},
|
||||
];
|
||||
|
||||
function renderProjectGalleryView(
|
||||
props: Partial<ComponentProps<typeof ProjectGalleryView>> = {},
|
||||
) {
|
||||
return render(
|
||||
<ProjectGalleryView
|
||||
onOpenProject={vi.fn()}
|
||||
onReturnToMainSite={vi.fn()}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('ProjectGalleryView', () => {
|
||||
afterEach(() => {
|
||||
listEditorProjectsMock.mockReset();
|
||||
@@ -85,7 +97,7 @@ describe('ProjectGalleryView', () => {
|
||||
listEditorProjectsMock.mockResolvedValueOnce(projectItems);
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<ProjectGalleryView onOpenProject={onOpenProject} />);
|
||||
renderProjectGalleryView({ onOpenProject });
|
||||
|
||||
await screen.findByText('角色设定板');
|
||||
await user.click(screen.getByRole('button', { name: '打开项目角色设定板' }));
|
||||
@@ -93,6 +105,19 @@ describe('ProjectGalleryView', () => {
|
||||
expect(onOpenProject).toHaveBeenCalledWith('editor-project-1');
|
||||
});
|
||||
|
||||
it('returns to the main site from the project header', async () => {
|
||||
const onReturnToMainSite = vi.fn();
|
||||
listEditorProjectsMock.mockResolvedValueOnce(projectItems);
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderProjectGalleryView({ onReturnToMainSite });
|
||||
|
||||
await screen.findByText('角色设定板');
|
||||
await user.click(screen.getByRole('button', { name: '返回主站' }));
|
||||
|
||||
expect(onReturnToMainSite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('uses canvas-center layer composition as the project cover', async () => {
|
||||
listEditorProjectsMock.mockResolvedValueOnce([
|
||||
{
|
||||
@@ -143,7 +168,7 @@ describe('ProjectGalleryView', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
render(<ProjectGalleryView onOpenProject={vi.fn()} />);
|
||||
renderProjectGalleryView();
|
||||
|
||||
await screen.findByText('封面项目');
|
||||
const cover = document.querySelector('.project-gallery__canvas-cover');
|
||||
@@ -172,7 +197,10 @@ describe('ProjectGalleryView', () => {
|
||||
|
||||
render(
|
||||
<AuthUiContext.Provider value={createAuthValue({ openLoginModal })}>
|
||||
<ProjectGalleryView onOpenProject={vi.fn()} />
|
||||
<ProjectGalleryView
|
||||
onOpenProject={vi.fn()}
|
||||
onReturnToMainSite={vi.fn()}
|
||||
/>
|
||||
</AuthUiContext.Provider>,
|
||||
);
|
||||
|
||||
@@ -191,7 +219,10 @@ describe('ProjectGalleryView', () => {
|
||||
|
||||
render(
|
||||
<AuthUiContext.Provider value={createAuthValue({ requireAuth })}>
|
||||
<ProjectGalleryView onOpenProject={onOpenProject} />
|
||||
<ProjectGalleryView
|
||||
onOpenProject={onOpenProject}
|
||||
onReturnToMainSite={vi.fn()}
|
||||
/>
|
||||
</AuthUiContext.Provider>,
|
||||
);
|
||||
|
||||
@@ -205,7 +236,7 @@ describe('ProjectGalleryView', () => {
|
||||
it('renders project loading errors through the shared status message', async () => {
|
||||
listEditorProjectsMock.mockRejectedValueOnce(new Error('读取项目失败'));
|
||||
|
||||
render(<ProjectGalleryView onOpenProject={vi.fn()} />);
|
||||
renderProjectGalleryView();
|
||||
|
||||
const alert = await screen.findByRole('alert');
|
||||
|
||||
@@ -223,7 +254,7 @@ describe('ProjectGalleryView', () => {
|
||||
deleteEditorProjectMock.mockResolvedValueOnce('editor-project-2');
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<ProjectGalleryView onOpenProject={vi.fn()} />);
|
||||
renderProjectGalleryView();
|
||||
|
||||
await screen.findByText('角色设定板');
|
||||
await user.click(screen.getByRole('button', { name: '打开项目角色设定板菜单' }));
|
||||
@@ -252,7 +283,7 @@ describe('ProjectGalleryView', () => {
|
||||
deleteEditorProjectMock.mockResolvedValue('deleted');
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<ProjectGalleryView onOpenProject={vi.fn()} />);
|
||||
renderProjectGalleryView();
|
||||
|
||||
await screen.findByText('角色设定板');
|
||||
await user.click(screen.getByRole('button', { name: '选择' }));
|
||||
@@ -279,7 +310,7 @@ describe('ProjectGalleryView', () => {
|
||||
});
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<ProjectGalleryView onOpenProject={onOpenProject} />);
|
||||
renderProjectGalleryView({ onOpenProject });
|
||||
|
||||
const newProjectButton = await screen.findByRole('button', {
|
||||
name: '新建项目',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ArrowLeft,
|
||||
Check,
|
||||
CheckSquare,
|
||||
MoreHorizontal,
|
||||
@@ -34,6 +35,7 @@ import { ProjectCanvasCover } from './ProjectCanvasCover';
|
||||
|
||||
type ProjectGalleryViewProps = {
|
||||
onOpenProject: (projectId: string) => void;
|
||||
onReturnToMainSite: () => void;
|
||||
};
|
||||
|
||||
type RenameDraft = {
|
||||
@@ -58,7 +60,10 @@ function formatProjectUpdatedAt(value: string) {
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
export function ProjectGalleryView({ onOpenProject }: ProjectGalleryViewProps) {
|
||||
export function ProjectGalleryView({
|
||||
onOpenProject,
|
||||
onReturnToMainSite,
|
||||
}: ProjectGalleryViewProps) {
|
||||
const authUi = useAuthUi();
|
||||
const [projects, setProjects] = useState<EditorProjectSnapshot[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@@ -298,11 +303,19 @@ export function ProjectGalleryView({ onOpenProject }: ProjectGalleryViewProps) {
|
||||
return (
|
||||
<main className="project-gallery" aria-label="项目">
|
||||
<header className="project-gallery__header">
|
||||
<div>
|
||||
<div className="project-gallery__heading">
|
||||
<h1>项目</h1>
|
||||
<span>{projects.length} 个画布项目</span>
|
||||
</div>
|
||||
<div className="project-gallery__header-actions">
|
||||
<PlatformActionButton
|
||||
tone="secondary"
|
||||
size="sm"
|
||||
onClick={onReturnToMainSite}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
返回主站
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
tone="secondary"
|
||||
size="sm"
|
||||
|
||||
@@ -9251,6 +9251,22 @@ test('desktop project tab switch clears creation primary styling', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('project page header can return to main site', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockDesktopPlatformLayout();
|
||||
window.history.replaceState(null, '', '/project');
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
expect(await screen.findByRole('main', { name: '项目' })).toBeTruthy();
|
||||
|
||||
await user.click(screen.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 />);
|
||||
|
||||
@@ -3001,6 +3001,10 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.project-gallery__heading {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.project-gallery__header h1 {
|
||||
margin: 0;
|
||||
color: #111827;
|
||||
@@ -3019,6 +3023,8 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
.project-gallery__header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user