新增 affiliate_dash 统一领取页流程(阶段 4)

- 后端:claim 详情分流 affiliate_dash 分支 + 详情轮询刷新 bind-result
- 输 UID 触发 bind(拿 bindUuid/二维码, task→waiting_binding)
- 新增 POST /claim/:token/affiliate-dash/submit(submit→redeeming + 事件)
- 前端:ClaimAffiliateDashSteps(二维码/绑定状态/mismatch警告/提交发货)
- claim-snapshot/claim-poll 按 flowType 分流(step2 绑定中/step3 已绑定待提交/step4 结果)
- 前后端 typecheck + 后端 212 测试通过
This commit is contained in:
yml2213
2026-08-05 15:56:51 +08:00
parent 3284fed7a1
commit 5ef1ed6018
11 changed files with 614 additions and 9 deletions
@@ -6,6 +6,7 @@ import {
normalizeTaskStatus,
} from '@/domain/task-status'
import type {
ClaimAffiliateDashFlowInfo,
ClaimDetailData,
ClaimKuaishouCloudFlowInfo,
ClaimKuaishouFeifeiFlowInfo,
@@ -20,6 +21,7 @@ export function createClaimSnapshot(
) {
const flow = detail?.kuaishouCloudFulfillment || null
const feifei = detail?.kuaishouFeifei || null
const affiliateDash = detail?.affiliateDash || null
const order = detail?.order || null
const orderItem = detail?.orderItem || null
const product = detail?.product || null
@@ -31,6 +33,7 @@ export function createClaimSnapshot(
const roleName = flow?.role.name || flow?.binding.roleName || ''
const roleId = flow?.role.rid || flow?.binding.roleId || ''
const isFeifeiFlow = detail?.flowType === 'kuaishou_feifei'
const isAffiliateDashFlow = detail?.flowType === 'affiliate_dash'
const isBindUrlExpired = isDateExpired(flow?.binding.bindExpiresAt || '')
const isBindingPrepared =
flow?.binding.prepareStatus === 'ready' &&
@@ -54,25 +57,33 @@ export function createClaimSnapshot(
const isRedeemFailed =
normalizedStatus === TASK_STATUS.MANUAL_REVIEW ||
normalizedStatus === TASK_STATUS.FAILED ||
normalizedStatus === TASK_STATUS.CLOSED ||
String(flow?.dispatch.status || '').trim() === 'failed' ||
(isFeifeiFlow && [40, 50].includes(Number(feifei?.rechargeStatus || 0)))
(isFeifeiFlow && [40, 50].includes(Number(feifei?.rechargeStatus || 0))) ||
(isAffiliateDashFlow &&
['ship_failed', 'cancelled'].includes(String(affiliateDash?.orderStatus || '').trim()))
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)))
(isFeifeiFlow && [30, 40, 50, 60].includes(Number(feifei?.rechargeStatus || 0))) ||
(isAffiliateDashFlow &&
['delivered', 'ship_failed', 'cancelled'].includes(String(affiliateDash?.orderStatus || '').trim()))
const currentStep = resolveCurrentStep({
hasExpectedUid,
hasRedeemResult,
isRoleConfirmed,
isFeifeiFlow,
isUidMatched,
isAffiliateDashFlow,
affiliateDash,
})
const progressText = resolveProgressText({
feifei,
isFeifeiFlow,
isAffiliateDashFlow,
affiliateDash,
hasExpectedUid,
expectedUid,
hasRedeemResult,
@@ -80,11 +91,18 @@ export function createClaimSnapshot(
isBindingPrepared,
isUidMatched,
})
const resultTitle = resolveResultTitle({ isRedeemFailed, isCompleted, isDispatched })
const resultTitle = resolveResultTitle({
isRedeemFailed,
isCompleted,
isDispatched,
isAffiliateDashFlow,
affiliateDash,
})
const resultDescription = resolveResultDescription({
detail,
flow,
feifei,
affiliateDash,
task,
isRedeemFailed,
isCompleted,
@@ -98,6 +116,7 @@ export function createClaimSnapshot(
return {
flow,
feifei,
affiliateDash,
order,
orderItem,
product,
@@ -108,6 +127,7 @@ export function createClaimSnapshot(
roleName,
roleId,
isFeifeiFlow,
isAffiliateDashFlow,
isBindUrlExpired,
isBindingPrepared,
isBindingPreparing,
@@ -161,6 +181,8 @@ function resolveCurrentStep(options: {
hasRedeemResult: boolean
isRoleConfirmed: boolean
isFeifeiFlow: boolean
isAffiliateDashFlow: boolean
affiliateDash: ClaimAffiliateDashFlowInfo | null
isUidMatched?: boolean
}) {
if (!options.hasExpectedUid) {
@@ -169,6 +191,12 @@ function resolveCurrentStep(options: {
if (options.hasRedeemResult) {
return 4
}
if (options.isAffiliateDashFlow) {
if (!options.affiliateDash?.bound) {
return 2
}
return 3
}
if (!options.isFeifeiFlow && options.isRoleConfirmed) {
return 3
}
@@ -177,7 +205,9 @@ function resolveCurrentStep(options: {
function resolveProgressText(options: {
feifei: ClaimKuaishouFeifeiFlowInfo | null
affiliateDash: ClaimAffiliateDashFlowInfo | null
isFeifeiFlow: boolean
isAffiliateDashFlow: boolean
hasExpectedUid: boolean
expectedUid: string
hasRedeemResult: boolean
@@ -188,6 +218,17 @@ function resolveProgressText(options: {
if (!options.hasExpectedUid) {
return '请填写游戏编号'
}
if (options.isAffiliateDashFlow) {
if (options.hasRedeemResult) {
return '发货结果已生成'
}
if (!options.affiliateDash?.bound) {
return options.affiliateDash?.bindUrl || options.affiliateDash?.qrUrl
? '请扫码绑定账号'
: '绑定链接生成中,请稍候'
}
return '已绑定,请提交发货'
}
if (options.isFeifeiFlow) {
return options.feifei?.rechargeStatusLabel || `UID ${options.expectedUid},请打开领取链接`
}
@@ -210,9 +251,11 @@ function resolveResultTitle(options: {
isRedeemFailed: boolean
isCompleted: boolean
isDispatched: boolean
isAffiliateDashFlow: boolean
affiliateDash: ClaimAffiliateDashFlowInfo | null
}) {
if (options.isRedeemFailed) {
return '兑换遇到问题'
return '发货遇到问题'
}
if (options.isCompleted) {
return '兑换成功'
@@ -220,6 +263,9 @@ function resolveResultTitle(options: {
if (options.isDispatched) {
return '兑换请求已提交'
}
if (options.isAffiliateDashFlow && options.affiliateDash?.orderStatus === 'delivered') {
return '发货已完成'
}
return '结果已记录'
}
@@ -227,6 +273,7 @@ function resolveResultDescription(options: {
detail: ClaimDetailData | null
flow: ClaimKuaishouCloudFlowInfo | null
feifei: ClaimKuaishouFeifeiFlowInfo | null
affiliateDash: ClaimAffiliateDashFlowInfo | null
task: ClaimDetailData['task'] | null
isRedeemFailed: boolean
isCompleted: boolean
@@ -236,16 +283,21 @@ function resolveResultDescription(options: {
options.task?.lastError ||
options.flow?.dispatch.errorMessage ||
options.feifei?.rechargeResultMessage ||
options.affiliateDash?.failureReason ||
options.detail?.result?.resultMessage ||
'',
).trim()
return message || '当前兑换流程需要客服处理,后续结果请以后台任务界面为准。'
return message || '当前流程需要客服处理,后续结果请以后台任务界面为准。'
}
if (options.isCompleted) {
return '当前兑换流程已经完成。'
}
if (options.affiliateDash) {
return '你的发货请求已经提交。后续结果会由系统保存在后台任务界面。'
}
return '你的兑换请求已经提交。后续结果会由系统保存在后台任务界面。'
}