恢复移动端创作工具门禁
移动端底部导航仅保留我的入口 移动端创作、项目与画布直达统一展示桌面端提示 补充移动端入口与画布阻断回归测试 纠正旧模板退役时越权扩大的移动端产品口径
This commit is contained in:
@@ -5706,7 +5706,7 @@
|
||||
- 背景:旧创作模板退役时误把新版 `/creation`、桌面公共侧边栏和“我的”完整资料页一起缩减;只恢复视觉后,现役 profile client 又经 `rpg-entry` barrel 把旧作品库、旧 runtime request 和展示模型重新带入 Vite 与 TypeScript 图。
|
||||
- 决策:桌面端继续使用原平台公共结构,一级导航固定为 `创作 / 项目 / 我的`;顶栏保留编辑器项目 / 素材搜索、泥点入口和账号胶囊;“我的”全宽保留资料编辑、陶泥号、三项统计、充值、兑换码、社区、反馈、通用设置、API Key 和法律信息。搜索只面向编辑器项目与公开编辑器素材,不恢复旧公开作品搜索。
|
||||
- 依赖边界:公共 dashboard、钱包、充值、兑换码、邀请码、API Key 和设置请求迁入 `services/platform-entry`,公共账单展示迁入现役 profile model。Vite 新增退役模块 graph 门禁,ESLint 对现役源码禁止导入旧目录;目录 watch ignore、Tailwind source、tsconfig include 和 tree-shaking 都不能作为依赖隔离证明。
|
||||
- 路由与响应式边界:`/creation`、`/project`、`/profile` 都是可刷新、可前进 / 后退的稳定路由;桌面端使用侧边栏,移动端必须提供同样 `创作 / 项目 / 我的` 的三项底部 dock,不得因隐藏桌面侧边栏而丢失移动导航。
|
||||
- 路由与响应式边界(2026-08-03 纠正):`/creation`、`/project`、`/profile` 都是稳定路由,但旧模板退役不授权扩大移动端创作范围。桌面端使用 `创作 / 项目 / 我的` 侧边栏;移动端底部 dock 只保留“我的”,直达 `/creation`、`/project`、`/editor/canvas` 或从首页触发项目 / 画布动作时统一显示桌面端提示,不挂载创作主页、项目列表或图片画布。2026-07-18 同批加入的移动端三入口口径无效,不作为产品决策依据。
|
||||
- 公共设置边界:`runtime_setting` 保持原表结构与历史数据,但它是音乐音量和平台主题的现役账号级公共能力,不归入旧玩法数据壳。鉴权后的 `GET/PUT /api/runtime/settings` 必须经 `spacetime-client` 调用 `get_runtime_setting_or_default` / `upsert_runtime_setting_and_return`;保留该路由不构成恢复旧 runtime API 的先例。
|
||||
- 编译门禁:除旧业务目录外,`src/uiAssets.ts`、`src/types.ts`、`src/types/**`、`src/services/runtimeAudioFeedback.ts` 和 `src/services/publicWorkCode.ts` 也是顶层退役 module,必须同时退出 Vite module graph、TypeScript、ESLint 和 Vitest;`/audio/**`、`/chat.png`、`/fusion-pixel.ttf` 及旧 pixel / story-tab / 玩法 CSS 不得进入 dev 服务或生产产物。验收时必须同时检查 `tsc --listFilesOnly`、Vite 依赖图 / 产物和退役资产路径,不能只依赖 tree-shaking。
|
||||
- Rust 产物边界:`module-runtime` 继续承载账号、钱包、公共设置、追踪和 feature gate,但 `CreationEntry*`、旧公开作品、存档、浏览历史与游玩统计 DTO / command / mapper / 规则必须退出实际 rlib;只保留历史表需要的 `RuntimeBrowseHistoryThemeMode`、完整保序的钱包流水来源枚举等持久化 ABI。`check:server-rs-ddd` 必须执行 `check:module-runtime-artifact`,同时验证旧符号和字面量为零、必要 ABI 仍存在,不能以源码存在 `#[cfg(any())]` 或路由未挂载代替产物证明。
|
||||
|
||||
@@ -595,7 +595,7 @@ npm run check:server-rs-ddd
|
||||
|
||||
- 移动端优先,再兼容网页端。
|
||||
- 页面只展示后端返回的状态,不自行计算结论型业务状态。
|
||||
- 现役一级入口为 `/creation`、`/project`、`/profile`,桌面侧边栏和移动端底部 dock 都固定显示“创作 / 项目 / 我的”。`/creation` 只读取图片编辑器项目与 `GET /api/editor/showcase/resources`,不得重新接入旧模板入口配置、旧作品架或专属运行态。
|
||||
- 现役稳定路由为 `/creation`、`/project`、`/profile`。桌面侧边栏固定显示“创作 / 项目 / 我的”;移动端底部 dock 只显示“我的”,并对 `/creation`、`/project`、`/editor/canvas` 及页面内项目 / 画布动作统一显示桌面端提示,不挂载创作工具、项目列表或图片画布。桌面端 `/creation` 只读取图片编辑器项目与 `GET /api/editor/showcase/resources`,不得重新接入旧模板入口配置、旧作品架或专属运行态。
|
||||
- 旧创作模板目录和顶层旧业务模块必须持续退出 Vite、TypeScript、ESLint 与 Vitest;旧 `/api/creation-entry/config`、模板 API、公开作品详情和运行态 API 必须保持未挂载。SpacetimeDB 历史表、迁移白名单与必要兼容类型只作为数据壳保留,不得据此恢复业务逻辑。
|
||||
- 优先复用现有面板、抽屉、弹窗,不新建独立大系统。
|
||||
- 不在 UI 中默认写功能说明类文本。
|
||||
|
||||
@@ -21,7 +21,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
|
||||
- 小程序 WebView 外壳:`miniprogram/`。
|
||||
- 法律文本:`media/files/user_agreement.md`、`media/files/privacy_policy.md`、`media/files/disclaimer.md`。
|
||||
|
||||
桌面端侧边栏和移动端底部 dock 的一级入口统一为 `创作 / 项目 / 我的`。`/creation` 是独立创作工具主页,`/project` 是画布项目入口,`/profile` 是“我的”稳定路由,继续承载账号、钱包、统计和通用设置等平台公共能力;刷新及浏览器前进 / 后退必须保持当前入口与选中态一致。
|
||||
桌面端侧边栏的一级入口为 `创作 / 项目 / 我的`;移动端底部 dock 只保留 `我的`。`/creation` 是桌面端独立创作工具主页,`/project` 是桌面端画布项目入口,`/profile` 是桌面端和移动端共用的“我的”稳定路由,继续承载账号、钱包、统计和通用设置等平台公共能力。移动端直达 `/creation`、`/project` 或 `/editor/canvas`,以及从首页触发项目 / 画布动作时,只显示桌面端创作提示,不挂载对应工具页面。
|
||||
|
||||
## 当前后端路线
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
|
||||
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
|
||||
- 通用 `feature_gate_config`、`GET/PUT /admin/api/feature-gates` 与后台 `#gray-release` 控制页。灰度页只读取通用 gate,不再请求旧 `/admin/api/creation-entry/config`,固定目标只登记现役功能;不得恢复 `creation-entry:*` 动态目标。
|
||||
- 新版 `/creation` 创作工具主页、`/project` 项目入口、稳定的 `/profile` 个人页路由、`creation-home` 展示组件与现役静态资产。桌面端保留“创作 / 项目 / 我的”公共侧边栏,移动端保留同样三项的底部 dock;“我的”保留头像 / 昵称编辑、陶泥号复制、钱包与账单、统计、充值、兑换码、玩家社区、反馈、通用设置、开发者 API Key 和法律信息,不恢复旧模板入口、旧作品架或生成队列。
|
||||
- 新版 `/creation` 创作工具主页、`/project` 项目入口、稳定的 `/profile` 个人页路由、`creation-home` 展示组件与现役静态资产。桌面端保留“创作 / 项目 / 我的”公共侧边栏;移动端底部 dock 只保留“我的”,不得因退役旧模板而扩大移动端创作范围。“我的”保留头像 / 昵称编辑、陶泥号复制、钱包与账单、统计、充值、兑换码、玩家社区、反馈、通用设置、开发者 API Key 和法律信息,不恢复旧模板入口、旧作品架或生成队列。
|
||||
- `runtime_setting` 是账号级公共设置事实,不属于旧模板运行态。原表结构和数据不变,继续由鉴权后的 `GET/PUT /api/runtime/settings`、`get_runtime_setting_or_default` 与 `upsert_runtime_setting_and_return` procedure 支撑音乐音量和平台主题读写。
|
||||
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
## 验收
|
||||
|
||||
- 旧 URL 不再命中旧页面或后端路由。
|
||||
- `/creation`、`/project` 与 `/profile` 在桌面端显示“创作 / 项目 / 我的”公共侧边栏,在 `390x844` 等移动视口显示同样三项的底部 dock;点击、刷新及浏览器前进 / 后退均保持路由与选中态一致。新创作主页只调用编辑器项目和公开编辑器素材接口;顶栏保持现役搜索、公共泥点入口与账号胶囊,不重新拼装平行账号按钮组。
|
||||
- `/creation`、`/project` 与 `/profile` 在桌面端显示“创作 / 项目 / 我的”公共侧边栏;在 `390x844` 等移动视口,底部 dock 只显示“我的”。移动端直达 `/creation`、`/project`、`/editor/canvas` 时显示桌面端创作提示,且不得挂载创作主页、项目列表或图片画布;移动端首页触发项目或画布动作时使用同一门禁。桌面端新创作主页只调用编辑器项目和公开编辑器素材接口;顶栏保持现役搜索、公共泥点入口与账号胶囊,不重新拼装平行账号按钮组。
|
||||
- “我的”桌面布局按原平台公共资料页全宽展示四个常用入口、两行设置和法律栏;头像、昵称、复制、充值、兑换码、社区、反馈、API Key 等入口可用,但不发起旧模板、旧公开作品或旧运行态请求。
|
||||
- 鉴权访问 `GET/PUT /api/runtime/settings` 不得返回 404,读写必须经 `spacetime-client` 调用现役 settings procedure;未鉴权请求返回 401,不恢复任何旧运行态设置路由。
|
||||
- `tsc --listFilesOnly` 与 Vite 干净加载均不得出现旧业务目录、上述顶层退役 module 或小程序旧订阅授权实现。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 创作主页与项目入口改版计划
|
||||
|
||||
> 2026-07-18 退役覆盖:本文关于旧模板入口、`/creation/<play>`、移动端隐藏“创作 / 项目”和 `/api/creation-entry/config` 的内容均已被后续实现替代,只保留为阶段设计记录。现役口径是桌面侧边栏与移动端底部 dock 都显示“创作 / 项目 / 我的”,稳定路由为 `/creation`、`/project`、`/profile`;旧模板业务只保留历史数据壳。当前实现与验收以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
|
||||
> 2026-08-03 纠正:2026-07-18 的旧模板退役只替代本文关于旧模板入口、`/creation/<play>` 和 `/api/creation-entry/config` 的内容,不替代“移动端隐藏创作 / 项目并阻止进入画布”的既有边界。现役稳定路由仍为 `/creation`、`/project`、`/profile`,但创作主页、项目管理和图片画布只在桌面端挂载;移动端底部 dock 只保留“我的”,触发创作工具时显示桌面端提示。当前实现与验收以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
|
||||
|
||||
日期:2026-06-18
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
- `/project` 展示当前账号的图片编辑器项目,项目卡继续进入 `/editor/canvas`。
|
||||
- `/profile` 是“我的”稳定路由,保留头像与昵称编辑、陶泥号复制、泥点余额与账单、累计统计、泥点充值、兑换码、玩家社区、反馈与建议、通用设置、开发者 API Key 和法律信息等平台公共能力。
|
||||
- 桌面顶栏保留现役项目 / 素材搜索、泥点入口和账号胶囊。搜索只筛选当前编辑器项目与已读取的公开编辑器素材,不恢复旧公开作品号搜索、旧广场、旧作品详情或旧运行态。
|
||||
- 桌面端使用公共侧边栏,移动端使用同样包含“创作 / 项目 / 我的”的三项底部 dock;点击、刷新及浏览器前进 / 后退都必须保持 URL、标题和选中态一致。
|
||||
- 桌面端公共侧边栏固定显示“创作 / 项目 / 我的”;移动端底部 dock 只保留“我的”,不暴露“创作 / 项目”。移动端直达 `/creation`、`/project` 或 `/editor/canvas` 时显示桌面端创作提示,不挂载创作主页、项目列表或图片画布;从移动端首页触发项目或画布动作时也只显示同一提示。
|
||||
|
||||
现役入口和公共资料能力只能依赖 `creation-home`、`project`、`image-editor`、公共组件及 `services/platform-entry` 等现役模块。Vite 模块门禁会拒绝 `components/rpg-entry`、`services/rpg-entry`、旧玩法目录和旧平台业务模块进入依赖图;Tailwind `@source`、TypeScript `include`、ESLint ignore 或 Vite watch ignore 都不能替代这条运行时依赖门禁。
|
||||
|
||||
|
||||
@@ -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