恢复平台账号顶栏与我的页签

按新版创作页原布局恢复泥点入口与账号身份胶囊。

在桌面侧栏补回我的页签及游客、登录态个人页面。

个人页面只复用账号、钱包、统计、设置和法律信息等平台能力。

补充壳层与个人页面回归测试并更新退役边界文档。
This commit is contained in:
2026-07-18 16:15:40 +08:00
parent c56da8dc26
commit f3ab2f2ec0
10 changed files with 549 additions and 42 deletions
@@ -21,7 +21,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
- 小程序 WebView 外壳:`miniprogram/` - 小程序 WebView 外壳:`miniprogram/`
- 法律文本:`media/files/user_agreement.md``media/files/privacy_policy.md``media/files/disclaimer.md` - 法律文本:`media/files/user_agreement.md``media/files/privacy_policy.md``media/files/disclaimer.md`
移动端一级 Tab`推荐 / 发现 / 我的`。桌面端导航保留 `创作` 并新增 `项目`,其中 `/creation` 是独立创作工具主页,`/project` 是画布项目入口。 移动端一级 Tab`推荐 / 发现 / 我的`。桌面端导航保留 `创作 / 项目 / 我的`,其中 `/creation` 是独立创作工具主页,`/project` 是画布项目入口,“我的”继续承载账号、钱包、统计和通用设置等平台公共能力
## 当前后端路线 ## 当前后端路线
@@ -25,7 +25,7 @@
- `migration.rs` 中相关表的迁移白名单、表名兼容和字段目录。 - `migration.rs` 中相关表的迁移白名单、表名兼容和字段目录。
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。 - 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。 - 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
- 新版 `/creation` 创作工具主页、`creation-home` 展示组件与静态资产,以及桌面端“创作 / 项目”公共侧边栏;这些能力只读取编辑器项目和公开素材接口,不恢复旧模板入口。 - 新版 `/creation` 创作工具主页、`creation-home` 展示组件与静态资产,以及桌面端“创作 / 项目 / 我的”公共侧边栏;“我的”只承载账号、钱包、统计和通用设置等平台能力,不恢复旧模板入口或旧作品架
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。 - 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
## 编译边界 ## 编译边界
@@ -51,7 +51,7 @@
## 验收 ## 验收
- 旧 URL 不再命中旧页面或后端路由。 - 旧 URL 不再命中旧页面或后端路由。
- `/creation``/project` 显示公共侧边栏,新创作主页只调用编辑器项目和公开素材接口。 - `/creation``/project` 显示“创作 / 项目 / 我的”公共侧边栏,新创作主页只调用编辑器项目和公开素材接口;顶栏保持公共泥点入口与账号胶囊,不重新拼装平行账号按钮组
- Vite 构建产物和依赖图不包含旧前端业务目录。 - Vite 构建产物和依赖图不包含旧前端业务目录。
- `cargo tree` 中不存在纯模板 crate 或专属运行态 crate。 - `cargo tree` 中不存在纯模板 crate 或专属运行态 crate。
- `spacetime-module` 编译结果仍包含全部历史表,但不包含任何旧模板 reducer、procedure 和业务 view。 - `spacetime-module` 编译结果仍包含全部历史表,但不包含任何旧模板 reducer、procedure 和业务 view。
@@ -0,0 +1,65 @@
/* @vitest-environment jsdom */
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { PlatformActiveProfileView } from './PlatformActiveProfileView';
const callbacks = {
onLogin: vi.fn(),
onOpenAccount: vi.fn(),
onOpenRecharge: vi.fn(),
onOpenSettings: vi.fn(),
onOpenWalletLedger: vi.fn(),
};
describe('PlatformActiveProfileView', () => {
it('shows the login entry for guests', () => {
render(
<PlatformActiveProfileView
{...callbacks}
dashboard={null}
isLoadingDashboard={false}
user={null}
/>,
);
expect(screen.getByRole('main', { name: '我的' })).toBeTruthy();
expect(screen.getByText('尚未登录')).toBeTruthy();
expect(screen.getByRole('button', { name: '登录' })).toBeTruthy();
});
it('renders active account, wallet, and settings capabilities', () => {
render(
<PlatformActiveProfileView
{...callbacks}
dashboard={{
walletBalance: 108,
totalPlayTimeMs: 5_400_000,
playedWorldCount: 7,
updatedAt: '2026-07-18T00:00:00.000Z',
}}
isLoadingDashboard={false}
user={{
id: 'user-1',
publicUserCode: '100001',
displayName: '测试玩家',
avatarUrl: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
}}
/>,
);
expect(screen.getByText('测试玩家')).toBeTruthy();
expect(screen.getByText('陶泥号:100001')).toBeTruthy();
expect(screen.getByRole('button', { name: '泥点余额 108' })).toBeTruthy();
expect(screen.getByRole('button', { name: //u })).toBeTruthy();
expect(
screen.getAllByRole('button', { name: //u }),
).toHaveLength(2);
expect(screen.getByRole('button', { name: //u })).toBeTruthy();
});
});
@@ -0,0 +1,244 @@
import {
Coins,
History,
Settings,
ShieldCheck,
UserRound,
} from 'lucide-react';
import { useState } from 'react';
import profileClockImage from '../../../media/profile/_Image (1).png';
import profileGamepadImage from '../../../media/profile/_Image (2).png';
import profileStillLifeImage from '../../../media/profile/_Image (3).png';
import profileCoinsImage from '../../../media/profile/_Image (4).png';
import profileMascotImage from '../../../media/profile/_Image (9).png';
import profilePointImage from '../../../media/profile/_Image.png';
import type { AuthUser } from '../../../packages/shared/src/contracts/auth';
import type { ProfileDashboardSummary } from '../../../packages/shared/src/contracts/runtime';
import { LegalDocumentModal } from '../common/LegalDocumentModal';
import {
getLegalDocument,
type LegalDocumentId,
} from '../common/legalDocuments';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { resolveActivePublicUserCode } from './platformActiveProfileModel';
import {
ProfileLegalSection,
ProfileShortcutButton,
ProfileStatCard,
ProfileStatCardSkeleton,
} from './PlatformProfilePrimitives';
type PlatformActiveProfileViewProps = {
dashboard: ProfileDashboardSummary | null;
isLoadingDashboard: boolean;
onLogin: () => void;
onOpenAccount: () => void;
onOpenRecharge: () => void;
onOpenSettings: () => void;
onOpenWalletLedger: () => void;
user: AuthUser | null | undefined;
};
function formatDashboardCount(value: number) {
return Math.max(0, Math.round(value)).toLocaleString('zh-CN');
}
function formatTotalPlayTime(value: number) {
const hours = Math.max(0, Math.round(value / 360000) / 10);
return `${hours.toLocaleString('zh-CN', {
maximumFractionDigits: 1,
})}小时`;
}
export function PlatformActiveProfileView({
dashboard,
isLoadingDashboard,
onLogin,
onOpenAccount,
onOpenRecharge,
onOpenSettings,
onOpenWalletLedger,
user,
}: PlatformActiveProfileViewProps) {
const [activeLegalDocumentId, setActiveLegalDocumentId] =
useState<LegalDocumentId | null>(null);
const activeLegalDocument = activeLegalDocumentId
? getLegalDocument(activeLegalDocumentId)
: null;
if (!user) {
return (
<main
className="platform-profile-page platform-remap-surface mx-auto w-full max-w-4xl"
aria-label="我的"
>
<section className="platform-profile-header">
<img
src={profileStillLifeImage}
alt=""
aria-hidden="true"
className="platform-profile-scene-decor"
/>
<div className="platform-profile-header__identity">
<div className="platform-profile-header__identity-row flex min-w-0 items-center gap-4">
<img
src={profileMascotImage}
alt=""
aria-hidden="true"
className="platform-profile-avatar h-[5.15rem] w-[5.15rem] shrink-0 rounded-full object-cover"
/>
<div className="min-w-0">
<div className="text-base font-black text-[var(--platform-text-strong)]">
</div>
<PlatformActionButton className="mt-3" onClick={onLogin}>
</PlatformActionButton>
</div>
</div>
</div>
</section>
</main>
);
}
const avatarUrl = user.avatarUrl?.trim() || null;
const publicUserCode = resolveActivePublicUserCode(user);
return (
<main
className="platform-profile-page platform-remap-surface mx-auto w-full max-w-4xl space-y-4 pb-2"
aria-label="我的"
>
<section className="platform-profile-header">
<img
src={profileStillLifeImage}
alt=""
aria-hidden="true"
className="platform-profile-scene-decor"
/>
<div className="platform-profile-header__identity">
<div className="platform-profile-header__identity-row flex min-w-0 items-center gap-4">
<button
type="button"
className="platform-profile-avatar h-[5.15rem] w-[5.15rem] shrink-0 overflow-hidden rounded-full"
aria-label="账号与安全"
onClick={onOpenAccount}
>
{avatarUrl ? (
<img
src={avatarUrl}
alt=""
className="h-full w-full object-cover"
/>
) : (
<img
src={profileMascotImage}
alt=""
className="h-full w-full object-cover"
/>
)}
</button>
<div className="platform-profile-header__text min-w-0">
<div className="truncate text-[18px] font-black leading-tight text-[var(--platform-text-strong)]">
{user.displayName}
</div>
<div className="mt-2 text-[12px] text-[var(--platform-text-base)]">
{publicUserCode}
</div>
</div>
</div>
</div>
</section>
<section className="platform-profile-stats-panel" aria-label="我的数据">
<div className="platform-profile-stats-grid grid grid-cols-3 gap-3">
{isLoadingDashboard ? (
<>
<ProfileStatCardSkeleton />
<ProfileStatCardSkeleton />
<ProfileStatCardSkeleton />
</>
) : (
<>
<ProfileStatCard
cardKey="wallet"
label="泥点余额"
value={
dashboard
? formatDashboardCount(dashboard.walletBalance)
: '暂不可用'
}
icon={Coins}
imageSrc={profilePointImage}
onClick={onOpenWalletLedger}
/>
<ProfileStatCard
cardKey="playTime"
label="累计游戏时长"
value={
dashboard
? formatTotalPlayTime(dashboard.totalPlayTimeMs)
: '暂不可用'
}
icon={History}
imageSrc={profileClockImage}
/>
<ProfileStatCard
cardKey="playedWorks"
label="已玩游戏数量"
value={
dashboard
? `${formatDashboardCount(dashboard.playedWorldCount)}`
: '暂不可用'
}
icon={UserRound}
imageSrc={profileGamepadImage}
/>
</>
)}
</div>
</section>
<section className="platform-profile-shortcut-panel" aria-label="常用功能">
<div className="platform-profile-shortcut-grid grid w-full grid-cols-4">
<ProfileShortcutButton
label="泥点充值"
subLabel="充值泥点"
icon={Coins}
imageSrc={profileCoinsImage}
onClick={onOpenRecharge}
/>
<ProfileShortcutButton
label="使用详情"
subLabel="查看泥点账单"
icon={History}
onClick={onOpenWalletLedger}
/>
<ProfileShortcutButton
label="账号与安全"
subLabel="管理账号"
icon={ShieldCheck}
onClick={onOpenAccount}
/>
<ProfileShortcutButton
label="通用设置"
subLabel="偏好设置"
icon={Settings}
onClick={onOpenSettings}
/>
</div>
</section>
<ProfileLegalSection onOpenDocument={setActiveLegalDocumentId} />
<LegalDocumentModal
document={activeLegalDocument}
open={Boolean(activeLegalDocument)}
onClose={() => setActiveLegalDocumentId(null)}
/>
</main>
);
}
export default PlatformActiveProfileView;
@@ -0,0 +1,115 @@
/* @vitest-environment jsdom */
import { fireEvent, render, screen, within } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { PlatformEntryFlowShellImpl } from './PlatformEntryActiveFlowShell';
const authUiMock = vi.hoisted(() => ({
value: {
user: null,
canAccessProtectedData: false,
openLoginModal: vi.fn(),
openAccountModal: vi.fn(),
openSettingsModal: vi.fn(),
},
}));
vi.mock('../auth/AuthUiContext', () => ({
useAuthUi: () => authUiMock.value,
}));
vi.mock('../creation-home/CreationLandingView', () => ({
CreationLandingView: () => (
<main aria-label="陶泥儿创作主页"></main>
),
}));
vi.mock('../project/ProjectGalleryView', () => ({
ProjectGalleryView: () => <main aria-label="项目"></main>,
}));
vi.mock('../image-editor/ImageCanvasEditorView', () => ({
ImageCanvasEditorView: () => <main aria-label="图片画布编辑器" />,
}));
vi.mock('../../services/platform-entry/platformProfileClient', () => ({
getPlatformProfileDashboard: vi.fn(),
}));
vi.mock('./usePlatformProfileCenterController', () => ({
usePlatformProfileCenterController: () => ({
buyRechargeProduct: vi.fn(),
closeNativeWechatPayment: vi.fn(),
confirmNativeWechatPayment: vi.fn(),
isLoadingRechargeCenter: false,
isLoadingWalletLedger: false,
isRechargeOpen: false,
isWalletLedgerOpen: false,
loadRechargeCenter: vi.fn(),
nativeWechatPayment: null,
openWalletLedgerPanel: vi.fn(),
rechargeCenter: null,
rechargeError: null,
rechargePaymentResult: null,
setIsRechargeOpen: vi.fn(),
setIsWalletLedgerOpen: vi.fn(),
setRechargePaymentResult: vi.fn(),
submittingRechargeProductId: null,
walletLedger: null,
walletLedgerError: null,
wechatRechargeOrderConfirmationState: null,
}),
}));
describe('PlatformEntryActiveFlowShell', () => {
beforeEach(() => {
window.history.replaceState(null, '', '/creation');
authUiMock.value.openLoginModal.mockReset();
});
it('keeps the active desktop rail and the shared account capsule', async () => {
const setSelectionStage = vi.fn();
const { container } = render(
<PlatformEntryFlowShellImpl
selectionStage="creation-home"
setSelectionStage={setSelectionStage}
/>,
);
expect(
await screen.findByRole('main', { name: '陶泥儿创作主页' }),
).toBeTruthy();
const navigation = screen.getByRole('navigation', { name: '平台导航' });
expect(
within(navigation)
.getAllByRole('button')
.map((button) => button.getAttribute('aria-label')),
).toEqual(['创作', '项目', '我的']);
expect(
within(navigation)
.getByRole('button', { name: '创作' })
.getAttribute('aria-current'),
).toBe('page');
const topbar = container.querySelector('.platform-desktop-topbar');
expect(topbar).toBeTruthy();
expect(
topbar?.querySelectorAll('button.platform-desktop-search'),
).toHaveLength(1);
expect(
within(topbar as HTMLElement).queryByRole('button', { name: '设置' }),
).toBeNull();
fireEvent.click(within(navigation).getByRole('button', { name: '我的' }));
expect(await screen.findByRole('main', { name: '我的' })).toBeTruthy();
expect(window.location.pathname).toBe('/');
expect(setSelectionStage).toHaveBeenCalledWith('platform', { path: '/' });
expect(
within(navigation)
.getByRole('button', { name: '我的' })
.getAttribute('aria-current'),
).toBe('page');
});
});
@@ -1,8 +1,6 @@
import { import {
FolderKanban, FolderKanban,
LogIn,
Palette, Palette,
Settings,
UserRound, UserRound,
} from 'lucide-react'; } from 'lucide-react';
import { import {
@@ -22,8 +20,11 @@ import {
} from '../../routing/activeAppPageRoutes'; } from '../../routing/activeAppPageRoutes';
import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient'; import { getPlatformProfileDashboard } from '../../services/platform-entry/platformProfileClient';
import { useAuthUi } from '../auth/AuthUiContext'; import { useAuthUi } from '../auth/AuthUiContext';
import { PlatformActionButton } from '../common/PlatformActionButton'; import {
import { PlatformIconButton } from '../common/PlatformIconButton'; resolveActivePublicUserCode,
resolveActiveUserAvatarLabel,
} from './platformActiveProfileModel';
import { PlatformActiveProfileView } from './PlatformActiveProfileView';
import type { PlatformEntryFlowShellProps } from './platformEntryActiveTypes'; import type { PlatformEntryFlowShellProps } from './platformEntryActiveTypes';
import { PlatformProfileRechargeModal } from './PlatformProfileRechargeModal'; import { PlatformProfileRechargeModal } from './PlatformProfileRechargeModal';
import { PlatformProfileWalletLedgerModal } from './PlatformProfileWalletLedgerModal'; import { PlatformProfileWalletLedgerModal } from './PlatformProfileWalletLedgerModal';
@@ -136,6 +137,7 @@ export function PlatformEntryFlowShellImpl({
null, null,
); );
const [isLoadingDashboard, setIsLoadingDashboard] = useState(false); const [isLoadingDashboard, setIsLoadingDashboard] = useState(false);
const [isProfileStage, setIsProfileStage] = useState(false);
const refreshDashboard = useCallback(async () => { const refreshDashboard = useCallback(async () => {
if (!authUi?.user || !authUi.canAccessProtectedData) { if (!authUi?.user || !authUi.canAccessProtectedData) {
@@ -156,6 +158,12 @@ export function PlatformEntryFlowShellImpl({
void refreshDashboard(); void refreshDashboard();
}, [refreshDashboard]); }, [refreshDashboard]);
useEffect(() => {
if (selectionStage !== 'platform') {
setIsProfileStage(false);
}
}, [selectionStage]);
const profileCenter = usePlatformProfileCenterController({ const profileCenter = usePlatformProfileCenterController({
activeTab: 'project', activeTab: 'project',
isAuthenticated: Boolean(authUi?.user), isAuthenticated: Boolean(authUi?.user),
@@ -166,15 +174,23 @@ export function PlatformEntryFlowShellImpl({
}); });
const openCreation = useCallback(() => { const openCreation = useCallback(() => {
setIsProfileStage(false);
pushAppHistoryPath('/creation'); pushAppHistoryPath('/creation');
setSelectionStage('creation-home', { path: '/creation' }); setSelectionStage('creation-home', { path: '/creation' });
}, [setSelectionStage]); }, [setSelectionStage]);
const openProjects = useCallback(() => { const openProjects = useCallback(() => {
setIsProfileStage(false);
pushAppHistoryPath('/project'); pushAppHistoryPath('/project');
setSelectionStage('project', { path: '/project' }); setSelectionStage('project', { path: '/project' });
}, [setSelectionStage]); }, [setSelectionStage]);
const openProfile = useCallback(() => {
setIsProfileStage(true);
pushAppHistoryPath('/');
setSelectionStage('platform', { path: '/' });
}, [setSelectionStage]);
const openEditorProject = useCallback( const openEditorProject = useCallback(
(projectId: string, options?: { guide?: boolean; tool?: string }) => { (projectId: string, options?: { guide?: boolean; tool?: string }) => {
const params = new URLSearchParams(); const params = new URLSearchParams();
@@ -211,12 +227,27 @@ export function PlatformEntryFlowShellImpl({
const isAuthenticated = Boolean(authUi?.user); const isAuthenticated = Boolean(authUi?.user);
const balance = const balance =
dashboard?.walletBalance ??
profileCenter.rechargeCenter?.mudPointBalance?.totalPoints ?? profileCenter.rechargeCenter?.mudPointBalance?.totalPoints ??
profileCenter.rechargeCenter?.walletBalance ?? profileCenter.rechargeCenter?.walletBalance ??
dashboard?.walletBalance ??
null; null;
const isCreationStage = const isCreationStage =
selectionStage === 'platform' || selectionStage === 'creation-home'; !isProfileStage &&
(selectionStage === 'platform' || selectionStage === 'creation-home');
const avatarUrl = authUi?.user?.avatarUrl?.trim() || null;
const avatarLabel = resolveActiveUserAvatarLabel(authUi?.user);
const publicUserCode = resolveActivePublicUserCode(authUi?.user);
const openRecharge = () => {
profileCenter.setIsRechargeOpen(true);
profileCenter.loadRechargeCenter();
};
const openUserSurface = () => {
if (isAuthenticated) {
authUi?.openAccountModal();
return;
}
authUi?.openLoginModal();
};
return ( return (
<> <>
@@ -246,6 +277,13 @@ export function PlatformEntryFlowShellImpl({
label="项目" label="项目"
onClick={openProjects} onClick={openProjects}
/> />
<ActiveRailButton
active={isProfileStage}
icon={UserRound}
iconSrc="/creation-home/nav-profile.png"
label="我的"
onClick={openProfile}
/>
</nav> </nav>
</aside> </aside>
@@ -255,10 +293,14 @@ export function PlatformEntryFlowShellImpl({
<ActivePlatformBrand /> <ActivePlatformBrand />
</div> </div>
<span className="hidden truncate text-base font-black text-[var(--platform-text-strong)] lg:block"> <span className="hidden truncate text-base font-black text-[var(--platform-text-strong)] lg:block">
{isCreationStage ? '创作工具' : '项目'} {isProfileStage
? '我的'
: isCreationStage
? '创作工具'
: '项目'}
</span> </span>
<div className="platform-desktop-topbar__actions flex shrink-0 items-center gap-2"> <div className="platform-desktop-topbar__actions flex shrink-0 items-center gap-3">
{isAuthenticated ? ( {isAuthenticated ? (
<PlatformMudPointWalletEntry <PlatformMudPointWalletEntry
variant="desktop" variant="desktop"
@@ -271,40 +313,43 @@ export function PlatformEntryFlowShellImpl({
profileCenter.isLoadingRechargeCenter profileCenter.isLoadingRechargeCenter
} }
error={profileCenter.rechargeError} error={profileCenter.rechargeError}
className="platform-desktop-create-wallet-chip"
onRequestDetails={profileCenter.loadRechargeCenter} onRequestDetails={profileCenter.loadRechargeCenter}
onRecharge={() => { onRecharge={openRecharge}
profileCenter.setIsRechargeOpen(true);
profileCenter.loadRechargeCenter();
}}
onOpenLedger={profileCenter.openWalletLedgerPanel} onOpenLedger={profileCenter.openWalletLedgerPanel}
/> />
) : ( ) : null}
<PlatformActionButton <button
size="xs" type="button"
onClick={() => authUi?.openLoginModal()} onClick={openUserSurface}
className="platform-desktop-search flex items-center gap-3 px-3 py-2.5 text-left"
>
<span
className="flex h-11 w-11 items-center justify-center overflow-hidden rounded-full text-base font-black text-white"
style={{
background: 'var(--platform-profile-avatar-fill)',
boxShadow: 'var(--platform-profile-avatar-shadow)',
}}
> >
<LogIn className="h-4 w-4" aria-hidden="true" /> {avatarUrl ? (
<img
</PlatformActionButton> src={avatarUrl}
)} alt=""
<PlatformIconButton className="h-full w-full object-cover"
label={isAuthenticated ? '账号' : '登录'} />
icon={ ) : (
<UserRound className="h-5 w-5" aria-hidden="true" /> avatarLabel
} )}
onClick={() => { </span>
if (isAuthenticated) { <span className="min-w-0">
authUi?.openAccountModal(); <span className="block truncate text-sm font-semibold text-[var(--platform-text-strong)]">
return; {authUi?.user?.displayName || '登录'}
} </span>
authUi?.openLoginModal(); <span className="block truncate text-xs text-[var(--platform-text-soft)]">
}} {authUi?.user ? publicUserCode : '账号入口'}
/> </span>
<PlatformIconButton </span>
label="设置" </button>
icon={<Settings className="h-5 w-5" aria-hidden="true" />}
onClick={() => authUi?.openSettingsModal()}
/>
</div> </div>
</header> </header>
@@ -312,7 +357,20 @@ export function PlatformEntryFlowShellImpl({
id={isCreationStage ? 'platform-tab-panel-create' : undefined} id={isCreationStage ? 'platform-tab-panel-create' : undefined}
className={`platform-tab-panel min-h-0 min-w-0 flex-1 overflow-auto ${isCreationStage ? '' : 'px-3 py-4 sm:px-6 sm:py-6'}`} className={`platform-tab-panel min-h-0 min-w-0 flex-1 overflow-auto ${isCreationStage ? '' : 'px-3 py-4 sm:px-6 sm:py-6'}`}
> >
{isCreationStage ? ( {isProfileStage ? (
<PlatformActiveProfileView
dashboard={dashboard}
isLoadingDashboard={isLoadingDashboard}
user={authUi?.user}
onLogin={() => authUi?.openLoginModal()}
onOpenAccount={() => authUi?.openAccountModal()}
onOpenRecharge={openRecharge}
onOpenSettings={() => authUi?.openSettingsModal()}
onOpenWalletLedger={
profileCenter.openWalletLedgerPanel
}
/>
) : isCreationStage ? (
<Suspense <Suspense
fallback={<LoadingPanel label="正在加载创作主页..." />} fallback={<LoadingPanel label="正在加载创作主页..." />}
> >
@@ -0,0 +1,19 @@
import type { AuthUser } from '../../../packages/shared/src/contracts/auth';
export function resolveActivePublicUserCode(
user: AuthUser | null | undefined,
) {
if (user?.publicUserCode?.trim()) {
return user.publicUserCode.trim();
}
const raw =
user?.id.replace(/[^a-zA-Z0-9]/gu, '').toUpperCase() || '00000000';
return `SY-${raw.slice(-8).padStart(8, '0')}`;
}
export function resolveActiveUserAvatarLabel(
user: AuthUser | null | undefined,
) {
return (user?.displayName || '叙').slice(0, 1).toUpperCase();
}
+2
View File
@@ -22,6 +22,8 @@
"src/components/image-editor/**/*.test.tsx", "src/components/image-editor/**/*.test.tsx",
"src/components/project/**/*.test.ts", "src/components/project/**/*.test.ts",
"src/components/project/**/*.test.tsx", "src/components/project/**/*.test.tsx",
"src/components/platform-entry/PlatformActiveProfileView.test.tsx",
"src/components/platform-entry/PlatformEntryActiveFlowShell.test.tsx",
"src/services/image-editor/**/*.test.ts", "src/services/image-editor/**/*.test.ts",
"src/services/image-editor/**/*.test.tsx", "src/services/image-editor/**/*.test.tsx",
"src/services/external-generation/**/*.test.ts", "src/services/external-generation/**/*.test.ts",
+2
View File
@@ -20,6 +20,8 @@ const ACTIVE_TAILWIND_SOURCES = `@import 'tailwindcss' source(none);
@source "./components/project"; @source "./components/project";
@source "./components/platform-entry/PlatformEntryFlowShell.tsx"; @source "./components/platform-entry/PlatformEntryFlowShell.tsx";
@source "./components/platform-entry/PlatformEntryActiveFlowShell.tsx"; @source "./components/platform-entry/PlatformEntryActiveFlowShell.tsx";
@source "./components/platform-entry/PlatformActiveProfileView.tsx";
@source "./components/platform-entry/PlatformProfilePrimitives.tsx";
@source "./components/platform-entry/PlatformProfileModalShell.tsx"; @source "./components/platform-entry/PlatformProfileModalShell.tsx";
@source "./components/platform-entry/PlatformProfileRechargeModal.tsx"; @source "./components/platform-entry/PlatformProfileRechargeModal.tsx";
@source "./components/platform-entry/PlatformProfileWalletLedgerModal.tsx"; @source "./components/platform-entry/PlatformProfileWalletLedgerModal.tsx";
+2
View File
@@ -32,6 +32,8 @@ export default defineConfig({
'src/components/image-editor/**/*.test.tsx', 'src/components/image-editor/**/*.test.tsx',
'src/components/project/**/*.test.ts', 'src/components/project/**/*.test.ts',
'src/components/project/**/*.test.tsx', 'src/components/project/**/*.test.tsx',
'src/components/platform-entry/PlatformActiveProfileView.test.tsx',
'src/components/platform-entry/PlatformEntryActiveFlowShell.test.tsx',
'src/components/platform-entry/PlatformProfileModalShell.test.tsx', 'src/components/platform-entry/PlatformProfileModalShell.test.tsx',
'src/components/platform-entry/PlatformProfileQrScannerModal.test.tsx', 'src/components/platform-entry/PlatformProfileQrScannerModal.test.tsx',
'src/components/platform-entry/PlatformProfileRechargeModal.test.tsx', 'src/components/platform-entry/PlatformProfileRechargeModal.test.tsx',