拆分领取页 ClaimPage 为多模块

将 snapshot、轮询、共享 UI、UID/lewan/feifei 步骤组件物理拆分,主文件只保留页面编排与状态。
This commit is contained in:
yml2213
2026-07-10 13:55:43 +08:00
parent 8de56b0354
commit c70c3ff6a5
8 changed files with 968 additions and 940 deletions
@@ -0,0 +1,260 @@
import {
TASK_STATUS,
hasKuaishouCloudRedeemResultStatus,
isKuaishouCloudCompletedStatus,
isKuaishouCloudRoleConfirmedStatus,
normalizeTaskStatus,
} from '@/domain/task-status'
import type {
ClaimDetailData,
ClaimKuaishouCloudFlowInfo,
ClaimKuaishouFeifeiFlowInfo,
} from '@/types/claim'
export type ResultVariant = 'success' | 'warning' | 'info'
export type ClaimSnapshot = ReturnType<typeof createClaimSnapshot>
export function createClaimSnapshot(
detail: ClaimDetailData | null,
options: { rebindingRole?: boolean } = {},
) {
const flow = detail?.kuaishouCloudFulfillment || null
const feifei = detail?.kuaishouFeifei || null
const order = detail?.order || null
const orderItem = detail?.orderItem || null
const product = detail?.product || null
const task = detail?.task || null
const claimIdentity = detail?.claimIdentity || null
const expectedUid = String(claimIdentity?.expectedUid || '').trim()
const hasExpectedUid = Boolean(expectedUid || claimIdentity?.ready)
const roleName = flow?.role.name || flow?.binding.roleName || ''
const roleId = flow?.role.rid || flow?.binding.roleId || ''
const isFeifeiFlow = detail?.flowType === 'kuaishou_feifei'
const isBindUrlExpired = isDateExpired(flow?.binding.bindExpiresAt || '')
const isBindingPrepared =
flow?.binding.prepareStatus === 'ready' &&
Boolean(String(flow?.binding.bindUrl || '').trim()) &&
!isBindUrlExpired
const isBindingPreparing = flow?.binding.prepareStatus === 'pending'
const isRoleReady = Boolean(roleName || roleId)
const isUidMatched =
Boolean(expectedUid) && Boolean(roleId) && normalizeUid(expectedUid) === normalizeUid(roleId)
const confirmRoleDisabledReason = resolveConfirmRoleDisabledReason({
isBindingPrepared,
isRoleReady,
isUidMatched,
expectedUid,
roleId,
rebindingRole: Boolean(options.rebindingRole),
})
const isRoleConfirmed = isKuaishouCloudRoleConfirmedStatus(task?.status)
const isDispatched = String(flow?.dispatch.status || '').trim() === 'success'
const normalizedStatus = normalizeTaskStatus(task?.status)
const isRedeemFailed =
normalizedStatus === TASK_STATUS.MANUAL_REVIEW ||
normalizedStatus === TASK_STATUS.FAILED ||
String(flow?.dispatch.status || '').trim() === 'failed' ||
(isFeifeiFlow && [40, 50].includes(Number(feifei?.rechargeStatus || 0)))
const isCompleted =
isKuaishouCloudCompletedStatus(task?.status) ||
(isFeifeiFlow && Number(feifei?.rechargeStatus || 0) === 30)
const hasRedeemResult =
isDispatched ||
hasKuaishouCloudRedeemResultStatus(task?.status) ||
(isFeifeiFlow && [30, 40, 50, 60].includes(Number(feifei?.rechargeStatus || 0)))
const currentStep = resolveCurrentStep({
hasExpectedUid,
hasRedeemResult,
isRoleConfirmed,
isFeifeiFlow,
isUidMatched,
})
const progressText = resolveProgressText({
feifei,
isFeifeiFlow,
hasExpectedUid,
expectedUid,
hasRedeemResult,
isRoleConfirmed,
isBindingPrepared,
isUidMatched,
})
const resultTitle = resolveResultTitle({ isRedeemFailed, isCompleted, isDispatched })
const resultDescription = resolveResultDescription({
detail,
flow,
feifei,
task,
isRedeemFailed,
isCompleted,
})
const resultVariant: ResultVariant = isRedeemFailed
? 'warning'
: isCompleted || isDispatched
? 'success'
: 'info'
return {
flow,
feifei,
order,
orderItem,
product,
task,
claimIdentity,
expectedUid,
hasExpectedUid,
roleName,
roleId,
isFeifeiFlow,
isBindUrlExpired,
isBindingPrepared,
isBindingPreparing,
isRoleReady,
isUidMatched,
confirmRoleDisabledReason,
isRoleConfirmed,
isDispatched,
isRedeemFailed,
isCompleted,
hasRedeemResult,
currentStep,
progressText,
resultTitle,
resultDescription,
resultVariant,
}
}
export function normalizeUid(value: unknown) {
return String(value || '')
.trim()
.replace(/\s+/g, '')
}
function resolveConfirmRoleDisabledReason(options: {
isBindingPrepared: boolean
isRoleReady: boolean
isUidMatched: boolean
expectedUid: string
roleId: string
rebindingRole: boolean
}) {
if (!options.isBindingPrepared) {
return '绑定二维码还在准备中,请稍后自动刷新'
}
if (!options.isRoleReady) {
return '请先扫码绑定自己的角色,并刷新角色信息'
}
if (!options.isUidMatched) {
return `当前绑定角色 ID${options.roleId || '-'})与填写 UID${options.expectedUid || '-'})不一致`
}
if (options.rebindingRole) {
return '正在换绑角色,请稍候'
}
return ''
}
function resolveCurrentStep(options: {
hasExpectedUid: boolean
hasRedeemResult: boolean
isRoleConfirmed: boolean
isFeifeiFlow: boolean
isUidMatched?: boolean
}) {
if (!options.hasExpectedUid) {
return 1
}
if (options.hasRedeemResult) {
return 4
}
if (!options.isFeifeiFlow && options.isRoleConfirmed) {
return 3
}
return 2
}
function resolveProgressText(options: {
feifei: ClaimKuaishouFeifeiFlowInfo | null
isFeifeiFlow: boolean
hasExpectedUid: boolean
expectedUid: string
hasRedeemResult: boolean
isRoleConfirmed: boolean
isBindingPrepared: boolean
isUidMatched: boolean
}) {
if (!options.hasExpectedUid) {
return '请填写游戏 UID'
}
if (options.isFeifeiFlow) {
return options.feifei?.rechargeStatusLabel || `UID ${options.expectedUid},请打开领取链接`
}
if (options.hasRedeemResult) {
return '兑换结果已生成'
}
if (options.isRoleConfirmed) {
return '角色已确认,等待兑换'
}
if (!options.isBindingPrepared) {
return '绑定链接准备中,请稍候'
}
if (options.isUidMatched) {
return 'UID 已匹配,请确认兑换'
}
return '请完成扫码绑定并匹配 UID'
}
function resolveResultTitle(options: {
isRedeemFailed: boolean
isCompleted: boolean
isDispatched: boolean
}) {
if (options.isRedeemFailed) {
return '兑换遇到问题'
}
if (options.isCompleted) {
return '兑换成功'
}
if (options.isDispatched) {
return '兑换请求已提交'
}
return '结果已记录'
}
function resolveResultDescription(options: {
detail: ClaimDetailData | null
flow: ClaimKuaishouCloudFlowInfo | null
feifei: ClaimKuaishouFeifeiFlowInfo | null
task: ClaimDetailData['task'] | null
isRedeemFailed: boolean
isCompleted: boolean
}) {
if (options.isRedeemFailed) {
const message = String(
options.task?.lastError ||
options.flow?.dispatch.errorMessage ||
options.feifei?.rechargeResultMessage ||
options.detail?.result?.resultMessage ||
'',
).trim()
return message || '当前兑换流程需要客服处理,后续结果请以后台任务界面为准。'
}
if (options.isCompleted) {
return '当前兑换流程已经完成。'
}
return '你的兑换请求已经提交。后续结果会由系统保存在后台任务界面。'
}
function isDateExpired(value: string | null) {
const normalized = String(value || '').trim()
if (!normalized) {
return false
}
const expiresTime = Date.parse(normalized)
return Number.isFinite(expiresTime) && expiresTime <= Date.now()
}