import { ExclamationCircleOutlined, LoadingOutlined } from '@ant-design/icons' import { Card, Empty, Spin } from 'antd' import QRCode from 'qrcode' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useParams } from 'react-router' import { isFeedbackDismissed, showConfirm, showError, showSuccess } from '@/lib/feedback' import { confirmKuaishouCloudClaimRole, fetchClaimDetail, rebindKuaishouCloudClaimRole, redeemKuaishouCloudClaim, submitAffiliateDashClaim, submitClaimUid, } from '@/services/claim' import type { ClaimDetailData } from '@/types/claim' import { AffiliateDashClaimPanel, AffiliateDashResultStep } from './ClaimAffiliateDashSteps' import { FeifeiClaimPanel, FeifeiResultStep } from './ClaimFeifeiSteps' import { ClaimHeaderCard } from './ClaimHeaderCard' import { ClaimResultStep, KuaishouCloudClaimSteps } from './ClaimLewanSteps' import { ClaimUidStep } from './ClaimUidStep' import { resolveNextPollDelayMs, resolveRolePollDelayMs } from './claim-poll' import { createClaimSnapshot, type ClaimSnapshot } from './claim-snapshot' export default function ClaimPage() { const { token: routeToken } = useParams<{ token: string }>() const token = String(routeToken || '').trim() const [loading, setLoading] = useState(true) const [submittingUid, setSubmittingUid] = useState(false) const [uidInput, setUidInput] = useState('') const [refreshingRole, setRefreshingRole] = useState(false) const [confirmingRole, setConfirmingRole] = useState(false) const [rebindingRole, setRebindingRole] = useState(false) const [redeeming, setRedeeming] = useState(false) const [submittingSubmit, setSubmittingSubmit] = useState(false) const [errorMessage, setErrorMessage] = useState('') const [detail, setDetail] = useState(null) const [qrCodeDataUrl, setQrCodeDataUrl] = useState('') const pollTimerRef = useRef(0) const rolePollBaselineRef = useRef({ roleKey: '', at: 0 }) const snapshot = useMemo( () => createClaimSnapshot(detail, { rebindingRole }), [detail, rebindingRole], ) const stopPolling = useCallback(() => { if (pollTimerRef.current) { window.clearTimeout(pollTimerRef.current) pollTimerRef.current = 0 } }, []) const generateQRCode = useCallback(async (url: string) => { if (!url) { setQrCodeDataUrl('') return } try { const dataUrl = await QRCode.toDataURL(url, { width: 280, margin: 2, color: { dark: '#0f172a', light: '#ffffff', }, }) setQrCodeDataUrl(dataUrl) } catch (error) { setQrCodeDataUrl('') console.error('生成二维码失败:', error) } }, []) const applyDetail = useCallback( async (nextDetail: ClaimDetailData) => { setDetail(nextDetail) const expectedUid = String(nextDetail.claimIdentity?.expectedUid || '').trim() if (expectedUid) { setUidInput(expectedUid) } await generateQRCode( String( nextDetail.kuaishouCloudFulfillment?.binding.bindUrl || nextDetail.affiliateDash?.bindUrl || '', ).trim(), ) }, [generateQRCode], ) const loadDetail = useCallback( async (options: { silent?: boolean } = {}) => { if (!token) { setLoading(false) setErrorMessage('领取链接无效,请检查链接是否完整') stopPolling() return null } if (!options.silent) { setLoading(true) } setErrorMessage('') try { const response = await fetchClaimDetail(token) await applyDetail(response.data) return response.data } catch (error) { setErrorMessage(error instanceof Error ? error.message : '读取领取信息失败') stopPolling() return null } finally { if (!options.silent) { setLoading(false) } } }, [applyDetail, stopPolling, token], ) const resolveNextPollDelay = useCallback((nextSnapshot: ClaimSnapshot) => { const rolePoll = resolveRolePollDelayMs(nextSnapshot, rolePollBaselineRef.current) rolePollBaselineRef.current = rolePoll.baseline return resolveNextPollDelayMs(nextSnapshot, rolePoll.delay) }, []) useEffect(() => { rolePollBaselineRef.current = { roleKey: '', at: 0 } setDetail(null) setQrCodeDataUrl('') setUidInput('') void loadDetail() return () => { stopPolling() } }, [loadDetail, stopPolling]) useEffect(() => { stopPolling() const delay = resolveNextPollDelay(snapshot) if (!delay) { return undefined } pollTimerRef.current = window.setTimeout(() => { pollTimerRef.current = 0 void loadDetail({ silent: true }) }, delay) return () => { stopPolling() } }, [loadDetail, resolveNextPollDelay, snapshot, stopPolling]) async function handleSubmitUid() { const uid = uidInput.trim() if (!uid) { showError('请输入游戏 UID') return } setSubmittingUid(true) try { const response = await submitClaimUid(token, uid) await applyDetail(response.data) showSuccess('UID 已提交') } catch (error) { showError(error instanceof Error ? error.message : '提交 UID 失败') } finally { setSubmittingUid(false) } } async function handleSubmitAffiliateDash() { const affiliateDash = snapshot.affiliateDash const uid = uidInput.trim() || affiliateDash?.gameAccount || '' const bindUuid = affiliateDash?.bindUuid || '' if (!uid || !bindUuid) { showError('绑定尚未完成,请先绑定账号') return } setSubmittingSubmit(true) try { const response = await submitAffiliateDashClaim(token, { gameAccount: uid, bindUuid }) await applyDetail(response.data) showSuccess('发货已提交') } catch (error) { showError(error instanceof Error ? error.message : '提交发货失败') } finally { setSubmittingSubmit(false) } } async function refreshRole() { setRefreshingRole(true) try { const nextDetail = await loadDetail({ silent: true }) const nextSnapshot = createClaimSnapshot(nextDetail, { rebindingRole }) if (nextSnapshot.isUidMatched) { showSuccess('角色信息已匹配,可确认兑换') } else if (nextSnapshot.roleId) { showError( `当前绑定角色 ID(${nextSnapshot.roleId})与填写 UID(${nextSnapshot.expectedUid || '-'})不一致`, ) } else if (nextDetail) { showError( nextSnapshot.flow?.role.errorMessage || '暂时还没有识别到角色信息,请完成绑定后稍等片刻再试', ) } } finally { setRefreshingRole(false) } } async function confirmRole() { setConfirmingRole(true) try { const response = await confirmKuaishouCloudClaimRole(token) await applyDetail(response.data) showSuccess('角色已确认,进入下一步') } catch (error) { showError(error instanceof Error ? error.message : '确认角色失败') } finally { setConfirmingRole(false) } } async function confirmRedeem() { try { await showConfirm('兑换后不可取消,也不可退货。请确认 UID 与商品信息完全正确。', '确认兑换', { okText: '确认兑换', okButtonProps: { danger: true }, }) } catch (error) { if (!isFeedbackDismissed(error)) { showError('确认弹窗已关闭') } return } setRedeeming(true) try { const response = await redeemKuaishouCloudClaim(token) await applyDetail(response.data) showSuccess('兑换请求已提交') } catch (error) { showError(error instanceof Error ? error.message : '兑换失败') } finally { setRedeeming(false) } } async function rebindRole() { try { await showConfirm( '换绑后当前绑定链接会失效,系统会退还当前虚拟号并生成新的绑定二维码。请使用你填写的 UID 对应角色重新绑定。', '确认换绑角色', { okText: '确认换绑', okButtonProps: { danger: true }, }, ) } catch (error) { if (!isFeedbackDismissed(error)) { showError('确认弹窗已关闭') } return } setRebindingRole(true) try { const response = await rebindKuaishouCloudClaimRole(token) await applyDetail(response.data) showSuccess('新的绑定二维码已生成,请重新绑定角色') } catch (error) { showError(error instanceof Error ? error.message : '换绑角色失败') } finally { setRebindingRole(false) } } function openBindUrl(useCurrentPage = false) { const bindUrl = String( snapshot.flow?.binding.bindUrl || snapshot.affiliateDash?.bindUrl || '', ).trim() if (!bindUrl) { showError('绑定链接还没准备好,请稍后刷新') return } if (useCurrentPage) { window.location.assign(bindUrl) return } window.open(bindUrl, '_blank', 'noopener,noreferrer') } function openFeifeiUrl(useCurrentPage = false) { const url = String( snapshot.feifei?.h5UrlWithUid || snapshot.feifei?.h5.rechargeUrl || snapshot.feifei?.h5.entryUrl || '', ).trim() if (!snapshot.hasExpectedUid) { showError('请先填写游戏 UID') return } if (!url) { showError('领取链接还没准备好,请稍后刷新') return } if (useCurrentPage) { window.location.assign(url) return } window.open(url, '_blank', 'noopener,noreferrer') } return (
{loading ? ( } size="large" />

正在读取当前领取进度...

) : errorMessage ? (

{errorMessage}

) : !detail ? ( ) : snapshot.currentStep === 1 ? ( void handleSubmitUid()} /> ) : snapshot.hasRedeemResult ? ( snapshot.isFeifeiFlow ? ( ) : snapshot.isAffiliateDashFlow ? ( ) : ( ) ) : snapshot.isAffiliateDashFlow && snapshot.affiliateDash ? ( void handleSubmitUid()} onSubmit={() => void handleSubmitAffiliateDash()} onOpenBindUrl={openBindUrl} /> ) : snapshot.isFeifeiFlow && snapshot.feifei ? ( openFeifeiUrl(false)} uidInput={uidInput} submittingUid={submittingUid} onUidChange={setUidInput} onResubmitUid={() => void handleSubmitUid()} /> ) : snapshot.flow ? ( void handleSubmitUid()} /> ) : ( )}
) }