合并远端主分支更新
Project CI / Repository checks (pull_request) Successful in 1m32s
Project CI / Frontend tests (pull_request) Successful in 4m29s
Project CI / Backend tests (pull_request) Successful in 4m33s
Project CI / Native shell tests (pull_request) Successful in 13m39s

同步 AGC 开发端口分配与启动脚本修复
同步 Jenkins 跨任务产物权限修复
同步移动端提示与门禁恢复
解决实时清单测试竞态冲突
This commit is contained in:
2026-08-08 17:52:44 +08:00
35 changed files with 1108 additions and 145 deletions
@@ -0,0 +1,97 @@
/* @vitest-environment jsdom */
import { fireEvent, render, screen } from '@testing-library/react';
import { expect, test, vi } from 'vitest';
import {
PlatformActiveMobileWelcomeDialog,
shouldOpenActiveMobileWelcomeDialog,
shouldShowActiveMobileWelcomeDialog,
} from './PlatformActiveMobileWelcomeDialog';
test('shows the active mobile welcome when entering the mobile site shell', () => {
expect(
shouldShowActiveMobileWelcomeDialog({
isDesktopLayout: false,
selectionStage: 'platform',
platformTab: 'platform',
pathname: '/',
}),
).toBe(true);
expect(
shouldShowActiveMobileWelcomeDialog({
isDesktopLayout: true,
selectionStage: 'platform',
platformTab: 'platform',
pathname: '/',
}),
).toBe(false);
expect(
shouldShowActiveMobileWelcomeDialog({
isDesktopLayout: false,
selectionStage: 'profile',
platformTab: 'profile',
pathname: '/profile',
}),
).toBe(true);
expect(
shouldShowActiveMobileWelcomeDialog({
isDesktopLayout: false,
selectionStage: 'creation-home',
platformTab: 'creation-home',
pathname: '/creation',
}),
).toBe(true);
});
test('keeps the active mobile welcome dismissed across site navigation', () => {
expect(
shouldOpenActiveMobileWelcomeDialog({
isDesktopLayout: false,
selectionStage: 'platform',
platformTab: 'platform',
pathname: '/',
isDismissed: false,
}),
).toBe(true);
expect(
shouldOpenActiveMobileWelcomeDialog({
isDesktopLayout: false,
selectionStage: 'profile',
platformTab: 'profile',
pathname: '/profile',
isDismissed: true,
}),
).toBe(false);
});
test('renders the restored copy and closes with the acknowledge button', () => {
const onClose = vi.fn();
render(
<PlatformActiveMobileWelcomeDialog
open
platformThemeClass="platform-theme--light"
onClose={onClose}
/>,
);
expect(screen.getByRole('dialog', { name: '欢迎' })).toBeTruthy();
expect(
screen.getByRole('heading', { level: 2, name: '欢迎' }),
).toBeTruthy();
expect(
document.querySelector('.platform-mobile-home-welcome-dialog__copy')
?.textContent,
).toBe(
'尼嚎,新陶泥er!移动端仅支持作品展示体验创作工具请使用电脑端访问该地址',
);
expect(
document
.querySelector('.platform-mobile-home-welcome-dialog__icon')
?.getAttribute('src'),
).toBe('/branding/mobile-home-welcome-taonier-ip.png');
fireEvent.click(screen.getByRole('button', { name: '好' }));
expect(onClose).toHaveBeenCalledTimes(1);
});
@@ -0,0 +1,82 @@
import { PlatformActionButton } from '../common/PlatformActionButton';
import { UnifiedModal } from '../common/UnifiedModal';
type MobileHomeWelcomeDecisionInput = {
isDesktopLayout: boolean;
selectionStage: string;
platformTab: string;
pathname: string;
};
type MobileHomeWelcomeOpenInput = MobileHomeWelcomeDecisionInput & {
isDismissed: boolean;
};
type PlatformActiveMobileWelcomeDialogProps = {
open: boolean;
platformThemeClass: string;
onClose: () => void;
};
export function shouldShowActiveMobileWelcomeDialog({
isDesktopLayout,
}: MobileHomeWelcomeDecisionInput) {
return !isDesktopLayout;
}
export function shouldOpenActiveMobileWelcomeDialog({
isDismissed,
...decisionInput
}: MobileHomeWelcomeOpenInput) {
return (
!isDismissed && shouldShowActiveMobileWelcomeDialog(decisionInput)
);
}
export function PlatformActiveMobileWelcomeDialog({
open,
platformThemeClass,
onClose,
}: PlatformActiveMobileWelcomeDialogProps) {
return (
<UnifiedModal
open={open}
title="欢迎"
onClose={onClose}
showHeader={false}
showCloseButton={false}
closeOnBackdrop={false}
closeOnEscape={false}
size="sm"
overlayClassName={`platform-theme ${platformThemeClass} platform-mobile-home-welcome-overlay !items-center !p-4`}
panelClassName="platform-remap-surface platform-mobile-home-welcome-dialog"
bodyClassName="platform-mobile-home-welcome-dialog__body"
footerClassName="platform-mobile-home-welcome-dialog__footer"
footer={
<PlatformActionButton
onClick={onClose}
size="md"
shape="pill"
fullWidth
className="min-h-11"
>
</PlatformActionButton>
}
>
<img
src="/branding/mobile-home-welcome-taonier-ip.png"
alt=""
className="platform-mobile-home-welcome-dialog__icon"
/>
<h2 className="platform-mobile-home-welcome-dialog__title"></h2>
<p className="platform-mobile-home-welcome-dialog__copy">
er
<br />
<br />
使访
</p>
</UnifiedModal>
);
}
@@ -483,6 +483,15 @@ describe('PlatformEntryActiveFlowShell', () => {
.getAllByRole('button')
.map((button) => button.getAttribute('aria-label')),
).toEqual(['我的']);
expect(await screen.findByRole('main', { name: '我的' })).toBeTruthy();
expect(
within(navigation)
.getByRole('button', { name: '我的' })
.getAttribute('aria-current'),
).toBe('page');
expect(
screen.queryByRole('main', { name: '陶泥儿创作主页' }),
).toBeNull();
fireEvent.click(within(navigation).getByRole('button', { name: '我的' }));
expect(window.location.pathname).toBe('/profile');
@@ -491,6 +500,48 @@ describe('PlatformEntryActiveFlowShell', () => {
});
});
it('restores the original blocking welcome on mobile until acknowledged', async () => {
responsiveMock.isDesktopLayout = false;
const { rerender } = render(
<PlatformEntryFlowShellImpl
selectionStage="platform"
setSelectionStage={vi.fn()}
/>,
);
expect(screen.getByRole('dialog', { name: '欢迎' })).toBeTruthy();
expect(
screen.getByText(
'尼嚎,新陶泥er!移动端仅支持作品展示体验创作工具请使用电脑端访问该地址',
),
).toBeTruthy();
expect(
document
.querySelector('.platform-mobile-home-welcome-dialog__icon')
?.getAttribute('src'),
).toBe('/branding/mobile-home-welcome-taonier-ip.png');
fireEvent.click(screen.getByRole('button', { name: '好' }));
expect(screen.queryByRole('dialog', { name: '欢迎' })).toBeNull();
expect(await screen.findByRole('main', { name: '我的' })).toBeTruthy();
const navigation = screen.getByRole('navigation', {
name: '移动平台导航',
});
expect(
within(navigation)
.getByRole('button', { name: '我的' })
.getAttribute('aria-current'),
).toBe('page');
rerender(
<PlatformEntryFlowShellImpl
selectionStage="profile"
setSelectionStage={vi.fn()}
/>,
);
expect(screen.queryByRole('dialog', { name: '欢迎' })).toBeNull();
});
it.each(['creation-home', 'project', 'image-editor'] as const)(
'shows the desktop guide instead of mounting the %s page on mobile',
async (selectionStage) => {
@@ -506,25 +557,52 @@ describe('PlatformEntryActiveFlowShell', () => {
expect(
await screen.findByRole('main', { name: '桌面端创作提示' }),
).toBeTruthy();
expect(screen.queryByRole('main', { name: '陶泥儿创作主页' })).toBeNull();
expect(screen.getByText('请在桌面端打开创作主页')).toBeTruthy();
expect(
screen.queryByRole('main', { name: '陶泥儿创作主页' }),
).toBeNull();
expect(screen.queryByRole('main', { name: '项目' })).toBeNull();
expect(screen.queryByRole('main', { name: '图片画布编辑器' })).toBeNull();
expect(
screen.queryByRole('main', { name: '图片画布编辑器' }),
).toBeNull();
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'),
).toBeNull();
},
);
it('shows the desktop guide instead of the editor after opening a canvas tool from the mobile root', async () => {
it('returns from the mobile tool guide through the only profile tab', async () => {
responsiveMock.isDesktopLayout = false;
render(<StatefulPlatformEntryFlowShell initialStage="creation-home" />);
fireEvent.click(screen.getByRole('button', { name: '好' }));
const navigation = screen.getByRole('navigation', {
name: '移动平台导航',
});
fireEvent.click(within(navigation).getByRole('button', { name: '我的' }));
expect(window.location.pathname).toBe('/profile');
expect(await screen.findByRole('main', { name: '我的' })).toBeTruthy();
});
it('does not mount creation actions on 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();
expect(await screen.findByRole('main', { name: '我的' })).toBeTruthy();
expect(screen.queryByRole('button', { name: '打开音乐工具' })).toBeNull();
expect(screen.queryByRole('main', { name: '陶泥儿创作主页' })).toBeNull();
});
it('switches between creation and projects and passes search to the active page', async () => {
@@ -30,6 +30,10 @@ import { useAuthUi } from '../auth/AuthUiContext';
import { FLOATING_FEEDBACK_FORM_URL } from '../common/floatingFeedbackEntryModel';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import {
PlatformActiveMobileWelcomeDialog,
shouldOpenActiveMobileWelcomeDialog,
} from './PlatformActiveMobileWelcomeDialog';
import {
resolveActivePublicUserCode,
resolveActiveUserAvatarLabel,
@@ -137,6 +141,30 @@ function ActiveBottomNavButton({
);
}
function MobileProfileDock({
active,
onOpenProfile,
}: {
active: boolean;
onOpenProfile: () => void;
}) {
return (
<div className="platform-mobile-bottom-dock min-w-0 shrink-0 lg:hidden">
<nav
className="platform-bottom-nav grid grid-cols-1"
aria-label="移动平台导航"
>
<ActiveBottomNavButton
active={active}
icon={UserRound}
label="我的"
onClick={onOpenProfile}
/>
</nav>
</div>
);
}
function ActivePlatformBrand() {
return (
<span
@@ -191,7 +219,7 @@ function MobileCreationDesktopGuide({
<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)]">
@@ -224,7 +252,21 @@ export function PlatformEntryFlowShellImpl({
const [activeSearchKeyword, setActiveSearchKeyword] = useState('');
const [isMobileDesktopGuideOpen, setIsMobileDesktopGuideOpen] =
useState(false);
const [isMobileHomeWelcomeDismissed, setIsMobileHomeWelcomeDismissed] =
useState(false);
const isDesktopLayout = usePlatformDesktopLayout();
const platformThemeClass =
authUi?.platformTheme === 'dark'
? 'platform-theme--dark'
: 'platform-theme--light';
const shouldOpenMobileHomeWelcome = shouldOpenActiveMobileWelcomeDialog({
isDesktopLayout,
selectionStage,
platformTab: selectionStage,
pathname:
typeof window === 'undefined' ? '/' : window.location.pathname,
isDismissed: isMobileHomeWelcomeDismissed,
});
const currentWalletOwnerUserId =
authUi?.canAccessProtectedData && authUi.user?.id ? authUi.user.id : null;
const walletOwnerUserId = usePlatformWalletStore(
@@ -276,7 +318,9 @@ export function PlatformEntryFlowShellImpl({
void refreshDashboard();
}, [refreshDashboard]);
const isProfileStage = selectionStage === 'profile';
const isProfileStage =
selectionStage === 'profile' ||
(!isDesktopLayout && selectionStage === 'platform');
const profileCenter = usePlatformProfileCenterController({
activeTab: isProfileStage ? 'profile' : 'project',
@@ -348,15 +392,23 @@ export function PlatformEntryFlowShellImpl({
if ((!isDesktopLayout && isMobileDesktopGuideOpen) || isMobileToolStage) {
return (
<MobileCreationDesktopGuide
onReturnHome={() => {
setIsMobileDesktopGuideOpen(false);
if (selectionStage === 'platform') {
return;
}
setSelectionStage('platform', { path: '/' });
}}
/>
<>
<MobileCreationDesktopGuide
onReturnHome={() => {
setIsMobileDesktopGuideOpen(false);
if (selectionStage === 'platform') {
return;
}
setSelectionStage('platform', { path: '/' });
}}
/>
<MobileProfileDock active={false} onOpenProfile={openProfile} />
<PlatformActiveMobileWelcomeDialog
open={shouldOpenMobileHomeWelcome}
platformThemeClass={platformThemeClass}
onClose={() => setIsMobileHomeWelcomeDismissed(true)}
/>
</>
);
}
@@ -580,19 +632,10 @@ export function PlatformEntryFlowShellImpl({
</main>
</div>
</div>
<div className="platform-mobile-bottom-dock min-w-0 shrink-0 lg:hidden">
<nav
className="platform-bottom-nav grid grid-cols-1"
aria-label="移动平台导航"
>
<ActiveBottomNavButton
active={isProfileStage}
icon={UserRound}
label="我的"
onClick={openProfile}
/>
</nav>
</div>
<MobileProfileDock
active={isProfileStage}
onOpenProfile={openProfile}
/>
</div>
</div>
@@ -661,6 +704,11 @@ export function PlatformEntryFlowShellImpl({
orderId={profileCenter.wechatRechargeOrderConfirmationState.orderId}
/>
) : null}
<PlatformActiveMobileWelcomeDialog
open={shouldOpenMobileHomeWelcome}
platformThemeClass={platformThemeClass}
onClose={() => setIsMobileHomeWelcomeDismissed(true)}
/>
</>
);
}