继续收口平台分段与泥点确认

新增泥点确认状态机共享 hook 并接入拼图与抓大鹅工作台

将首页发现页与个人中心剩余切换条收口到 PlatformSegmentedTabs

统一平台弹窗 header 关闭入口并补齐相关测试

更新前端组件收口文档与团队决策记录
This commit is contained in:
2026-06-11 01:30:13 +08:00
parent 94122583ac
commit 0a4ccdf45c
19 changed files with 511 additions and 242 deletions
@@ -1606,7 +1606,7 @@ test('profile recharge modal posts membership goods virtual payment params in mi
renderProfileView();
await openRechargeModal(user);
await user.click(screen.getByRole('button', { name: '会员卡' }));
await user.click(screen.getByRole('tab', { name: '会员卡' }));
await user.click(await screen.findByRole('button', { name: /月卡/u }));
await waitFor(() => {
@@ -3766,6 +3766,14 @@ test('mobile discover keeps edutainment works in the last dedicated channel only
true,
);
expect(discoverStage?.classList.contains('platform-page-stage')).toBe(false);
expect(
within(discoverPanel).getByRole('tablist', { name: '发现频道' }),
).toBeTruthy();
expect(
within(discoverPanel).getByRole('tab', { name: '推荐' }).getAttribute(
'aria-selected',
),
).toBe('true');
const channels = Array.from(
discoverPanel.querySelectorAll('.platform-mobile-home-channel'),
@@ -3774,18 +3782,23 @@ test('mobile discover keeps edutainment works in the last dedicated channel only
expect(within(discoverPanel).getByText('普通拼图作品')).toBeTruthy();
expect(within(discoverPanel).queryByText('儿童动作热身 Demo')).toBeNull();
await user.click(screen.getByRole('button', { name: '今日' }));
await user.click(screen.getByRole('tab', { name: '今日' }));
expect(
within(discoverPanel).getByRole('tab', { name: '今日' }).getAttribute(
'aria-selected',
),
).toBe('true');
expect(within(discoverPanel).queryByText('儿童动作热身 Demo')).toBeNull();
await user.click(screen.getByRole('button', { name: '分类' }));
await user.click(screen.getByRole('tab', { name: '分类' }));
expect(screen.getByRole('button', { name: '儿童教育' })).toBeTruthy();
expect(screen.queryByRole('button', { name: '寓教于乐' })).toBeTruthy();
expect(screen.queryByRole('tab', { name: '寓教于乐' })).toBeTruthy();
expect(within(discoverPanel).queryByText('儿童动作热身 Demo')).toBeNull();
await user.click(screen.getByRole('button', { name: '排行' }));
await user.click(screen.getByRole('tab', { name: '排行' }));
expect(within(discoverPanel).queryByText('儿童动作热身 Demo')).toBeNull();
await user.click(screen.getByRole('button', { name: '寓教于乐' }));
await user.click(screen.getByRole('tab', { name: '寓教于乐' }));
expect(
within(discoverPanel).getByRole('button', {
name: /儿童动作热身 Demo/u,
@@ -3816,7 +3829,7 @@ test('desktop discover shows child motion demo in edutainment channel', async ()
onOpenChildMotionDemo,
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('button', { name: '寓教于乐' }));
await user.click(screen.getByRole('tab', { name: '寓教于乐' }));
const warmupButton = screen.getByRole('button', { name: /热身关卡/u });
expect(warmupButton).toBeTruthy();
@@ -3889,7 +3902,7 @@ test('mobile discover keeps baby object match works in edutainment channel only'
expect(within(discoverPanel).getByText('普通拼图作品')).toBeTruthy();
expect(within(discoverPanel).queryByText('宝贝识物水果篮')).toBeNull();
await user.click(screen.getByRole('button', { name: '寓教于乐' }));
await user.click(screen.getByRole('tab', { name: '寓教于乐' }));
const babyObjectMatchButton = within(discoverPanel).getByRole('button', {
name: /宝贝识物水果篮/u,
});
@@ -5190,7 +5203,7 @@ test('mobile today channel only shows newly published works from today', async (
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('button', { name: '今日' }));
await user.click(screen.getByRole('tab', { name: '今日' }));
const discoverPanel = document.getElementById('platform-tab-panel-category');
if (!discoverPanel) {
throw new Error('缺少发现面板');
@@ -5295,7 +5308,7 @@ test('mobile home moves category shelf into game category channel', async () =>
expect(screen.queryByRole('button', { name: '即点即玩' })).toBeNull();
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('button', { name: '分类' }));
await user.click(screen.getByRole('tab', { name: '分类' }));
expect(screen.getAllByText('分类').length).toBeGreaterThan(0);
expect(screen.getByRole('button', { name: /筛选/u })).toBeTruthy();
@@ -5317,7 +5330,7 @@ test('mobile game category list orders works by composite public metric', async
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('button', { name: '分类' }));
await user.click(screen.getByRole('tab', { name: '分类' }));
await user.click(screen.getByRole('button', { name: '奇幻' }));
const gameItems = Array.from(
@@ -5349,7 +5362,7 @@ test('mobile game category filter dialog filters by play type', async () => {
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('button', { name: '分类' }));
await user.click(screen.getByRole('tab', { name: '分类' }));
await user.click(screen.getByRole('button', { name: '奇幻' }));
expect(screen.getByRole('button', { name: /奇幻拼图,试玩/u })).toBeTruthy();
@@ -5384,7 +5397,7 @@ test('bottom category tab becomes ranking and switches ranking metrics', async (
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('button', { name: '排行' }));
await user.click(screen.getByRole('tab', { name: '排行' }));
expect(await screen.findByRole('tab', { name: '热门榜' })).toBeTruthy();
expect(screen.getByRole('tab', { name: '改造榜' })).toBeTruthy();
@@ -5418,7 +5431,7 @@ test('ranking rows limit displayed work name and show two short tags on the thir
});
await user.click(screen.getByRole('button', { name: '发现' }));
await user.click(screen.getByRole('button', { name: '排行' }));
await user.click(screen.getByRole('tab', { name: '排行' }));
const rankingPanel = document.getElementById('platform-tab-panel-category');
expect(rankingPanel).toBeTruthy();
+127 -88
View File
@@ -2743,11 +2743,27 @@ export function RpgEntryHomeView({
: DISCOVER_CHANNELS,
[edutainmentEntryEnabled],
);
const discoverChannelTabs = useMemo(
() =>
visibleDiscoverChannels.map((channel) => ({
id: channel.id,
label: channel.label,
})),
[visibleDiscoverChannels],
);
const categoryGroups = useMemo(
() =>
buildPublicCategoryGroups(generalFeaturedEntries, generalLatestEntries),
[generalFeaturedEntries, generalLatestEntries],
);
const categoryGroupTabs = useMemo(
() =>
categoryGroups.map((group) => ({
id: group.tag,
label: group.tag,
})),
[categoryGroups],
);
const publicEntries = useMemo(
() =>
getPlatformPublicEntries(generalFeaturedEntries, generalLatestEntries),
@@ -2821,6 +2837,13 @@ export function RpgEntryHomeView({
const activeCategoryFilterCount = activeCategoryEntries.length;
const categoryFilterApplied =
categoryKindFilter !== DEFAULT_PLATFORM_CATEGORY_KIND_FILTER;
const handleDiscoverChannelChange = useCallback((channel: DiscoverChannel) => {
setDiscoverChannel(channel);
}, []);
const handleCategoryGroupChange = useCallback((tag: string) => {
hasManualCategoryTagSelectionRef.current = true;
setSelectedCategoryTag(tag);
}, []);
const visibleTabs = useMemo<PlatformHomeTab[]>(
() =>
isAuthenticated
@@ -3684,21 +3707,28 @@ export function RpgEntryHomeView({
/>
) : (
<>
<div className="platform-mobile-home-channelbar flex min-w-0 gap-4 overflow-x-auto pb-1 scrollbar-hide">
{visibleDiscoverChannels.map((channel) => {
const active = discoverChannel === channel.id;
return (
<button
key={channel.id}
type="button"
onClick={() => setDiscoverChannel(channel.id)}
className={`platform-mobile-home-channel shrink-0 ${active ? 'platform-mobile-home-channel--active' : ''}`}
>
{channel.label}
</button>
);
})}
</div>
<PlatformSegmentedTabs
items={discoverChannelTabs}
activeId={discoverChannel}
onChange={handleDiscoverChannelChange}
layout="scroll"
gap="md"
frame="bare"
surface="transparent"
size="sm"
tone="neutral"
semantics="tabs"
ariaLabel="发现频道"
className="platform-mobile-home-channelbar pb-1"
itemClassName={(_, active) =>
[
'platform-mobile-home-channel shrink-0 !min-h-8 !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
active ? 'platform-mobile-home-channel--active' : null,
]
.filter(Boolean)
.join(' ')
}
/>
{platformError ? (
<PlatformStatusMessage
@@ -3737,25 +3767,26 @@ export function RpgEntryHomeView({
</span>
</button>
<span className="platform-category-filter-divider" />
<div className="platform-category-chip-scroll scrollbar-hide">
{categoryGroups.map((group) => {
const active = group.tag === activeCategoryGroup.tag;
return (
<button
key={group.tag}
type="button"
onClick={() => {
hasManualCategoryTagSelectionRef.current = true;
setSelectedCategoryTag(group.tag);
}}
className={`platform-category-chip ${active ? 'platform-category-chip--active' : ''}`}
>
{group.tag}
</button>
);
})}
</div>
<PlatformSegmentedTabs
items={categoryGroupTabs}
activeId={activeCategoryGroup.tag}
onChange={handleCategoryGroupChange}
layout="scroll"
gap="md"
frame="bare"
surface="transparent"
size="sm"
tone="neutral"
className="platform-category-chip-scroll min-w-0 flex-1"
itemClassName={(_, active) =>
[
'platform-category-chip shrink-0 !min-h-[2.35rem] !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
active ? 'platform-category-chip--active' : null,
]
.filter(Boolean)
.join(' ')
}
/>
</div>
<button
@@ -3901,22 +3932,28 @@ export function RpgEntryHomeView({
const desktopDiscoverContent: ReactNode = (
<div className={DESKTOP_DISCOVER_PAGE_STAGE_CLASS}>
<div className="platform-mobile-home-channelbar flex min-w-0 gap-4 overflow-x-auto pb-1 scrollbar-hide">
{visibleDiscoverChannels.map((channel) => {
const active = discoverChannel === channel.id;
return (
<button
key={`desktop-${channel.id}`}
type="button"
onClick={() => setDiscoverChannel(channel.id)}
className={`platform-mobile-home-channel shrink-0 ${active ? 'platform-mobile-home-channel--active' : ''}`}
>
{channel.label}
</button>
);
})}
</div>
<PlatformSegmentedTabs
items={discoverChannelTabs}
activeId={discoverChannel}
onChange={handleDiscoverChannelChange}
layout="scroll"
gap="md"
frame="bare"
surface="transparent"
size="sm"
tone="neutral"
semantics="tabs"
ariaLabel="发现频道"
className="platform-mobile-home-channelbar pb-1"
itemClassName={(_, active) =>
[
'platform-mobile-home-channel shrink-0 !min-h-8 !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
active ? 'platform-mobile-home-channel--active' : null,
]
.filter(Boolean)
.join(' ')
}
/>
{platformError ? (
<PlatformStatusMessage
@@ -3953,25 +3990,26 @@ export function RpgEntryHomeView({
{activeCategoryFilterCount}
</span>
</button>
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
{categoryGroups.map((group) => {
const active = group.tag === activeCategoryGroup.tag;
return (
<button
key={`${group.tag}:desktop-discover-category`}
type="button"
onClick={() => {
hasManualCategoryTagSelectionRef.current = true;
setSelectedCategoryTag(group.tag);
}}
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
>
{group.tag}
</button>
);
})}
</div>
<PlatformSegmentedTabs
items={categoryGroupTabs}
activeId={activeCategoryGroup.tag}
onChange={handleCategoryGroupChange}
layout="scroll"
gap="md"
frame="bare"
surface="transparent"
size="sm"
tone="neutral"
className="min-w-0 flex-1 pb-1"
itemClassName={(_, active) =>
[
'platform-category-chip shrink-0 !min-h-[2.35rem] !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
active ? 'platform-category-chip--active' : null,
]
.filter(Boolean)
.join(' ')
}
/>
<button
type="button"
onClick={cycleCategorySortMode}
@@ -4768,25 +4806,26 @@ export function RpgEntryHomeView({
{activeCategoryFilterCount}
</span>
</button>
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
{categoryGroups.map((group) => {
const active = group.tag === activeCategoryGroup.tag;
return (
<button
key={`${group.tag}:desktop-category`}
type="button"
onClick={() => {
hasManualCategoryTagSelectionRef.current = true;
setSelectedCategoryTag(group.tag);
}}
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
>
{group.tag}
</button>
);
})}
</div>
<PlatformSegmentedTabs
items={categoryGroupTabs}
activeId={activeCategoryGroup.tag}
onChange={handleCategoryGroupChange}
layout="scroll"
gap="md"
frame="bare"
surface="transparent"
size="sm"
tone="neutral"
className="min-w-0 flex-1 pb-1"
itemClassName={(_, active) =>
[
'platform-category-chip shrink-0 !min-h-[2.35rem] !rounded-none !border-0 !bg-transparent !px-0 !shadow-none hover:!bg-transparent',
active ? 'platform-category-chip--active' : null,
]
.filter(Boolean)
.join(' ')
}
/>
<button
type="button"
onClick={cycleCategorySortMode}