diff --git a/docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md b/docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md index b89de6f1a..9767efbcc 100644 --- a/docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md +++ b/docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md @@ -27,7 +27,7 @@ - 社群入口:原地弹出“玩家社区”二维码弹窗,复用现有社区弹层能力,不跳转或切换到“我的 / 用户中心”。 - 营销点:`登录即送100泥点,可以免费制作50个素材` -`开始创作` 与“新建项目”使用同一项目创建链路。用户已登录时调用现有 `createEditorProject`,成功后进入 `/editor/canvas?projectid=xxx`;未登录或登录过期时打开登录弹窗,并在登录后重试创建。 +`开始创作` 与“新建项目”使用同一项目创建链路。用户已登录时调用现有 `createEditorProject`,成功后进入 `/editor/canvas?projectid=xxx&guide=toolbar`,画布只消费一次 `guide=toolbar` 并清理 query,用于显示新画布工具栏引导;未登录或登录过期时打开登录弹窗,并在登录后重试创建。 该区是页面第一屏的品牌与主操作区域,不使用外站品牌名。桌面端 `/creation` 继续保留现有平台左侧导航栏和上方栏,创作主页只替换中间内容区。 @@ -45,6 +45,16 @@ 8. 游戏功能编码:自然语言驱动游戏生成。 9. 一键导出:支持一键导出为微抖小游戏、Godot、Unity、UE 工程。 +交互口径: + +- 游戏视觉规范:创建新画布后进入 `/editor/canvas?projectid=xxx&tool=icon-spec`,画布消费 `tool=icon-spec` 后拉起图标规范生成面板。 +- 游戏角色:创建新画布后进入 `/editor/canvas?projectid=xxx&tool=character-spec`,画布消费后拉起角色规范生成面板。 +- 游戏 UI:创建新画布后进入 `/editor/canvas?projectid=xxx&tool=ui-design`,画布消费后拉起生成 UI 设计图面板。 +- 游戏音乐:创建新画布后进入 `/editor/canvas?projectid=xxx&tool=background-music`,画布消费后拉起生成游戏背景音乐面板。 +- 游戏场景:创建新画布后进入 `/editor/canvas?projectid=xxx&tool=image`,画布消费后拉起生成图片面板。 +- 游戏美宣:创建新画布后进入 `/editor/canvas?projectid=xxx&tool=publication-cover`,画布消费后拉起游戏首图宣发素材面板。 +- 游戏特效、游戏功能编码、一键导出:不创建项目,复用移动端欢迎弹窗样式展示 `抱歉 / 功能还在调试中 / 暂未开放`,按钮为 `期待一下`。 + ### 最近项目 仅登录后展示。模块位置在“陶泥儿精选”上方。 @@ -53,7 +63,7 @@ - 标题右侧:`查看全部`,跳转 `/project` - 左侧第一项:`新建项目` - 右侧项目卡:按 `updatedAt` 倒序展示最近 4 个项目。 -- 项目卡点击进入 `/editor/canvas?projectid=xxx`。 +- 项目卡点击进入 `/editor/canvas?projectid=xxx`;“新建项目”进入 `/editor/canvas?projectid=xxx&guide=toolbar` 并触发一次新画布工具栏引导。 - 项目卡点击必须直接写入最终 `/editor/canvas?projectid=xxx` 历史项,不允许先进入无参数 `/editor/canvas` 再二次跳转,否则浏览器返回需要点两次。 - 项目缩略图复用 `/project` 项目卡的画布中心封面逻辑,避免两套封面算法。 @@ -113,7 +123,7 @@ Tab: - `/creation` 是创作工具主页,不承接正式玩法业务真相。 - 创作入口配置继续从 `/api/creation-entry/config` 读取。 - 最近项目继续使用编辑器项目接口:`listEditorProjects`、`createEditorProject` 和 `/editor/canvas?projectid=xxx`。 -- 最近项目和项目页打开画布时,浏览器 history 只保留带 `projectid` 的最终画布路由。 +- 最近项目和项目页打开画布时,浏览器 history 只保留带 `projectid` 的最终画布路由;新建画布引导使用 `guide=toolbar` 一次性 query,主站九宫格直达生成器使用 `tool=` 一次性 query,画布消费后通过 `history.replaceState` 清理这两个参数。 - 项目封面逻辑复用 `/project` 项目卡已有的画布中心缩略图算法。 - `陶泥儿精选` 读取后端公开精选接口返回的全站公开画布生成资源;公开开关以后端 `editor_project_resource.public_showcase_enabled` 为准,前端只做展示过滤、同源同媒体副本兜底过滤和乐观交互回滚。 - `/creation/` 的玩法工作台、草稿、生成页、结果页、发布、运行态和作品架链路保持原状。 @@ -147,6 +157,7 @@ Tab: - 新增 `CreationLandingView`。 - 实现首屏主视觉、九大功能区、最近项目区和陶泥儿精选素材瀑布流。 +- 九大功能区卡片必须是可点击按钮;已开放卡片复用 `createEditorProject` 和画布现有生成器,不新增入口系统;暂未开放卡片复用移动端欢迎弹窗样式。 - 桌面端复用现有平台壳层,保留上方栏和左侧导航栏。 - 主页整体采用浅色布局,避免外站品牌文案。 - 视觉排版参考陶土质感创作工具工作台:首屏标题居中、主按钮与社区按钮使用大号圆角胶囊尺寸,九大创作工具以 3 列大卡片展示,卡片左侧保留大号陶土感图标区域,右侧展示原有标题与描述;桌面平台一级页的顶部栏和左侧导航统一压平成单层工作台 chrome,避免外框套内框;桌面端页面结构必须先分为“左侧完整栏 / 右侧内容区”,左侧完整栏同时包含顶部品牌 Logo 和下方导航页签,右侧内容区再拆为顶部搜索、钱包、账号栏与下方主屏,不能用顶部栏的第一列假装左侧品牌区;左侧栏采用紧凑但保留图标和文字的导航胶囊,顶部搜索栏、泥点入口和账号入口都保持精简宽度,提升推荐、发现、我的、创作主页和项目页的工具感与精致度;只调整样式与布局,不改文案、搜索、登录、导航、项目创建、社区弹层、最近项目、精选素材 Tab 或数据读取逻辑。 @@ -173,6 +184,7 @@ Tab: - `appPageRoutes.test.ts`:覆盖 `/creation -> creation-home`,并确认现有 `/creation/` 不回归。 - `RpgEntryHomeView.recharge.test.tsx`:桌面显示“创作”“项目”,不显示“草稿”;移动端不显示“创作”和“项目”;我的页不再出现项目快捷入口。 - `CreationLandingView.test.tsx`:覆盖首屏主视觉、九大功能、最近项目、新建入口、查看全部、未登录隐藏最近项目、素材 Tab。 +- `ImageCanvasEditorView.test.tsx`:覆盖新画布 `guide=toolbar` 显示工具栏引导并在打开生成器后关闭,以及 `tool=background-music` 等启动意图只消费一次。 - `PlatformEntryFlowShellImpl` 相关测试:点击“创作”进入 `/creation`,点击“项目”进入 `/project`,新建项目进入 `/editor/canvas?projectid=xxx`。 - `ProjectGalleryView.test.tsx`:封面抽取后项目封面、重命名、删除和选择模式保持通过。 @@ -194,6 +206,9 @@ git diff --check - 未登录时最近项目区不展示,点击“开始创作”弹登录。 - 登录后显示最近项目、新建项目、最多 4 个项目和“查看全部”。 - 点击新建项目进入 `/editor/canvas?projectid=xxx`。 +- 点击“开始创作”或项目页“新建项目”进入新画布后,工具栏上方显示 `工具栏在这里,快试着随便做点什么`,底部工具栏有高亮边框;点击任一生成入口并弹出生成面板后,引导和高亮关闭。 +- 点击“创作工具”中的游戏视觉规范、游戏角色、游戏 UI、游戏音乐、游戏场景、游戏美宣,分别创建新画布并直接拉起对应的图标规范、角色规范、UI 设计图、背景音乐、图片、游戏首图面板;URL 中的 `tool` 参数被消费后不保留。 +- 点击游戏特效、游戏功能编码、一键导出时弹出 `抱歉 / 功能还在调试中 / 暂未开放`,按钮为 `期待一下`,不创建项目。 - 桌面导航显示“项目”,不显示“草稿”。 - `/project` 头部不展示“返回主站”,右侧只保留选择和新建等项目管理动作。 - 移动视口底部不显示“创作”和“项目”。 diff --git a/src/components/creation-home/CreationLandingView.test.tsx b/src/components/creation-home/CreationLandingView.test.tsx index edf5968e1..0460537e1 100644 --- a/src/components/creation-home/CreationLandingView.test.tsx +++ b/src/components/creation-home/CreationLandingView.test.tsx @@ -195,10 +195,80 @@ describe('CreationLandingView', () => { await user.click(screen.getByRole('button', { name: /开始创作/u })); await waitFor(() => { - expect(onOpenProject).toHaveBeenCalledWith('created-project'); + expect(onOpenProject).toHaveBeenCalledWith('created-project', { + guide: true, + }); }); }); + it.each([ + ['游戏视觉规范', 'icon-spec'], + ['游戏角色', 'character-spec'], + ['游戏UI', 'ui-design'], + ['游戏音乐', 'background-music'], + ['游戏场景', 'image'], + ['游戏美宣', 'publication-cover'], + ])( + 'creates a new project from %s with its canvas tool intent', + async (label, expectedTool) => { + const user = userEvent.setup(); + const onOpenProject = vi.fn(); + listEditorProjectsMock.mockResolvedValueOnce([]); + createEditorProjectMock.mockResolvedValueOnce({ + projectId: 'visual-spec-project', + title: '未命名画布', + viewport: { x: 0, y: 0, scale: 1 }, + layers: [], + resources: [], + updatedAt: '2026-06-18T10:00:00.000Z', + }); + + render( + + + , + ); + + await user.click( + screen.getByRole('button', { name: new RegExp(label, 'u') }), + ); + + await waitFor(() => { + expect(onOpenProject).toHaveBeenCalledWith('visual-spec-project', { + tool: expectedTool, + }); + }); + }, + ); + + it('shows the pending feature dialog for unavailable creation tool cards', async () => { + const user = userEvent.setup(); + listEditorProjectsMock.mockResolvedValueOnce([]); + + render( + + + , + ); + + await user.click(screen.getByRole('button', { name: /游戏特效/u })); + + const dialog = screen.getByRole('dialog', { name: '抱歉' }); + expect(dialog.textContent).toContain('功能还在调试中'); + expect(dialog.textContent).toContain('暂未开放'); + expect(createEditorProjectMock).not.toHaveBeenCalled(); + + await user.click(screen.getByRole('button', { name: '期待一下' })); + + expect(screen.queryByRole('dialog', { name: '抱歉' })).toBeNull(); + }); + it('opens the community entry from the hero without starting project creation', async () => { const user = userEvent.setup(); const onOpenCommunity = vi.fn(); diff --git a/src/components/creation-home/CreationLandingView.tsx b/src/components/creation-home/CreationLandingView.tsx index a5a9a486f..e007fb68a 100644 --- a/src/components/creation-home/CreationLandingView.tsx +++ b/src/components/creation-home/CreationLandingView.tsx @@ -29,15 +29,27 @@ import { } from './creationShowcaseModel'; type CreationLandingViewProps = { - onOpenProject: (projectId: string) => void; + onOpenProject: ( + projectId: string, + options?: { guide?: boolean; tool?: CreationFeatureTool }, + ) => void; onOpenProjects: () => void; onOpenCommunity?: () => void; }; +type CreationFeatureTool = + | 'character-spec' + | 'icon-spec' + | 'ui-design' + | 'background-music' + | 'image' + | 'publication-cover'; + type CreationFeatureItem = { title: string; description: string; iconSrc: string; + action: { kind: 'tool'; tool: CreationFeatureTool } | { kind: 'pending' }; }; const CREATION_HOME_ASSETS = { @@ -51,46 +63,55 @@ const CREATION_FEATURES: CreationFeatureItem[] = [ title: '游戏视觉规范', description: '轻松约束多类素材视觉一致性', iconSrc: '/creation-home/feature-visual-spec.png', + action: { kind: 'tool', tool: 'icon-spec' }, }, { title: '游戏角色', description: '整套高完成度2D/3D角色素材及动画', iconSrc: '/creation-home/feature-character.png', + action: { kind: 'tool', tool: 'character-spec' }, }, { title: '游戏UI', description: '轻松生成高一致性的UI素材', iconSrc: '/creation-home/feature-ui.png', + action: { kind: 'tool', tool: 'ui-design' }, }, { title: '游戏音乐', description: '轻松制作游戏背景音乐、音效', iconSrc: '/creation-home/feature-music.png', + action: { kind: 'tool', tool: 'background-music' }, }, { title: '游戏特效', description: '低成本生成精品游戏特效', iconSrc: '/creation-home/feature-effects.png', + action: { kind: 'pending' }, }, { title: '游戏场景', description: '轻松生成各类游戏场景素材', iconSrc: '/creation-home/feature-scene.png', + action: { kind: 'tool', tool: 'image' }, }, { title: '游戏美宣', description: '轻松生成游戏上架所需图片和视频', iconSrc: '/creation-home/feature-marketing.png', + action: { kind: 'tool', tool: 'publication-cover' }, }, { title: '游戏功能编码', description: '自然语言驱动游戏生成', iconSrc: '/creation-home/feature-coding.png', + action: { kind: 'pending' }, }, { title: '一键导出', description: '支持导出为微抖小游戏、Godot、Unity、UE工程', iconSrc: '/creation-home/feature-export.png', + action: { kind: 'pending' }, }, ]; @@ -377,6 +398,8 @@ export function CreationLandingView({ const [selectedShowcaseItem, setSelectedShowcaseItem] = useState(null); const [selectedShowcaseIndex, setSelectedShowcaseIndex] = useState(0); + const [isPendingFeatureDialogOpen, setIsPendingFeatureDialogOpen] = + useState(false); const refreshRecentProjects = useCallback(() => { if (!isAuthenticated) { @@ -431,18 +454,20 @@ export function CreationLandingView({ refreshShowcaseResources(); }, [refreshShowcaseResources]); - const createProject = useCallback(() => { + const createProject = useCallback((tool?: CreationFeatureTool) => { if (authUi && !authUi.user) { - authUi.openLoginModal(createProject); + authUi.openLoginModal(() => createProject(tool)); return; } setIsCreatingProject(true); setProjectError(null); createEditorProject() - .then((project) => onOpenProject(project.projectId)) + .then((project) => + onOpenProject(project.projectId, tool ? { tool } : { guide: true }), + ) .catch((error: unknown) => { if (isUnauthorizedError(error)) { - authUi?.openLoginModal(createProject); + authUi?.openLoginModal(() => createProject(tool)); return; } setProjectError( @@ -452,6 +477,17 @@ export function CreationLandingView({ .finally(() => setIsCreatingProject(false)); }, [authUi, onOpenProject]); + const handleFeatureClick = useCallback( + (feature: CreationFeatureItem) => { + if (feature.action.kind === 'pending') { + setIsPendingFeatureDialogOpen(true); + return; + } + createProject(feature.action.tool); + }, + [createProject], + ); + const showcaseItems = useMemo( () => buildCreationShowcaseItems({ @@ -533,7 +569,7 @@ export function CreationLandingView({ size="md" shape="pill" className="creation-landing__hero-action creation-landing__hero-action--primary" - onClick={createProject} + onClick={() => createProject()} disabled={isCreatingProject} >
{CREATION_FEATURES.map((feature) => ( -
+
+ ))}
@@ -625,7 +667,7 @@ export function CreationLandingView({