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

新增泥点确认状态机共享 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

View File

@@ -97,4 +97,52 @@ describe('PlatformProfileRechargeModal', () => {
expect(screen.getByText('暂无可购买套餐')).toBeTruthy();
});
test('uses shared segmented tabs for recharge type switching', async () => {
const user = userEvent.setup();
const onTabChange = vi.fn();
render(
<PlatformProfileRechargeModal
center={{
walletBalance: 0,
membership: {
status: 'normal',
tier: 'normal',
startedAt: null,
expiresAt: null,
updatedAt: null,
},
pointProducts: [],
membershipProducts: [],
benefits: [],
latestOrder: null,
hasPointsRecharged: false,
}}
isLoading={false}
error={null}
submittingProductId={null}
nativePayment={null}
activeTab="points"
onTabChange={onTabChange}
onClose={vi.fn()}
onRetry={vi.fn()}
onBuy={vi.fn()}
onConfirmNativePayment={vi.fn()}
/>,
);
const tablist = screen.getByRole('tablist', { name: '充值类型' });
const pointsTab = screen.getByRole('tab', { name: '泥点充值' });
const membershipTab = screen.getByRole('tab', { name: '会员卡' });
expect(tablist.className).toContain('grid');
expect(tablist.className).toContain('grid-cols-2');
expect(pointsTab.getAttribute('aria-selected')).toBe('true');
expect(membershipTab.getAttribute('aria-selected')).toBe('false');
await user.click(membershipTab);
expect(onTabChange).toHaveBeenCalledWith('membership');
});
});

View File

@@ -9,6 +9,7 @@ import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformProfileModalShell } from './PlatformProfileModalShell';
@@ -24,6 +25,10 @@ import {
} from '../rpg-entry/rpgEntryProfileFundsViewModel';
const WECHAT_NATIVE_PAY_QR_IMAGE_SIZE = 180;
const RECHARGE_TAB_ITEMS: Array<{ id: RechargeTab; label: string }> = [
{ id: 'points', label: '泥点充值' },
{ id: 'membership', label: '会员卡' },
];
export type PlatformProfileRechargeModalProps = {
center: ProfileRechargeCenterResponse | null;
@@ -167,22 +172,30 @@ export function PlatformProfileRechargeModal({
panelClassName="platform-recharge-modal !max-w-[34rem] rounded-[1.4rem]"
bodyClassName="max-h-[min(76vh,36rem)] overflow-y-auto px-5 py-5"
>
<div className="grid grid-cols-2 gap-2">
<button
type="button"
onClick={() => onTabChange('points')}
className={`platform-category-chip justify-center ${activeTab === 'points' ? 'platform-category-chip--active' : ''}`}
>
</button>
<button
type="button"
onClick={() => onTabChange('membership')}
className={`platform-category-chip justify-center ${activeTab === 'membership' ? 'platform-category-chip--active' : ''}`}
>
</button>
</div>
<PlatformSegmentedTabs
items={RECHARGE_TAB_ITEMS}
activeId={activeTab}
onChange={onTabChange}
columns="two"
layout="grid"
gap="sm"
frame="bare"
surface="transparent"
size="sm"
tone="neutral"
semantics="tabs"
ariaLabel="充值类型"
itemClassName={(_, active) =>
[
'w-full !min-h-[2.25rem] !rounded-[0.78rem] !border !px-3 !text-sm !font-extrabold !shadow-none',
active
? '!border-[var(--platform-cool-border)] !bg-[var(--platform-cool-bg)] !text-[var(--platform-cool-text)]'
: '!border-[var(--platform-subpanel-border)] !bg-[rgba(255,255,255,0.04)] !text-[var(--platform-text-base)] hover:!bg-[rgba(255,255,255,0.08)]',
]
.filter(Boolean)
.join(' ')
}
/>
<PlatformAsyncStatePanel
errorState={