统一领取流:Step1 填 UID,按平台分叉履约
领取页改为统一 UID 入口;lewan 强制角色 ID 匹配后才发货核销,feifei 将 uid 拼入 H5 并去掉 claim 短链,对外仍返回本站 claimUrl。
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Empty,
|
||||
Input,
|
||||
Space,
|
||||
Spin,
|
||||
Tooltip,
|
||||
@@ -34,6 +35,7 @@ import {
|
||||
fetchClaimDetail,
|
||||
rebindKuaishouCloudClaimRole,
|
||||
redeemKuaishouCloudClaim,
|
||||
submitClaimUid,
|
||||
} from '@/services/claim'
|
||||
import type {
|
||||
ClaimDetailData,
|
||||
@@ -50,9 +52,9 @@ const ROLE_SLOW_POLL_MS = 30_000
|
||||
const ROLE_IDLE_POLL_MS = 60_000
|
||||
const ROLE_FAST_WINDOW_MS = 3 * 60_000
|
||||
const ROLE_IDLE_WINDOW_MS = 10 * 60_000
|
||||
const FEIFEI_POLL_MS = 15_000
|
||||
|
||||
type ResultVariant = 'success' | 'warning' | 'info'
|
||||
|
||||
type ClaimSnapshot = ReturnType<typeof createClaimSnapshot>
|
||||
|
||||
export default function ClaimPage() {
|
||||
@@ -60,6 +62,8 @@ export default function ClaimPage() {
|
||||
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)
|
||||
@@ -68,7 +72,7 @@ export default function ClaimPage() {
|
||||
const [detail, setDetail] = useState<ClaimDetailData | null>(null)
|
||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('')
|
||||
|
||||
const pollTimerRef = useRef<number>(0)
|
||||
const pollTimerRef = useRef(0)
|
||||
const rolePollBaselineAtRef = useRef(0)
|
||||
const rolePollBaselineKeyRef = useRef('')
|
||||
|
||||
@@ -109,6 +113,10 @@ export default function ClaimPage() {
|
||||
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 || '').trim(),
|
||||
)
|
||||
@@ -174,19 +182,22 @@ export default function ClaimPage() {
|
||||
|
||||
const resolveNextPollDelay = useCallback(
|
||||
(nextSnapshot: ClaimSnapshot) => {
|
||||
if (
|
||||
!nextSnapshot.flow ||
|
||||
nextSnapshot.hasRedeemResult ||
|
||||
isClaimInactiveTaskStatus(nextSnapshot.task?.status)
|
||||
) {
|
||||
if (nextSnapshot.hasRedeemResult || isClaimInactiveTaskStatus(nextSnapshot.task?.status)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (!nextSnapshot.hasExpectedUid) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (nextSnapshot.isFeifeiFlow) {
|
||||
return FEIFEI_POLL_MS
|
||||
}
|
||||
|
||||
if (nextSnapshot.currentStep === 2) {
|
||||
if (!nextSnapshot.isBindingPrepared) {
|
||||
return BINDING_PREPARE_POLL_MS
|
||||
}
|
||||
|
||||
return resolveRolePollDelay(nextSnapshot)
|
||||
}
|
||||
|
||||
@@ -200,6 +211,7 @@ export default function ClaimPage() {
|
||||
rolePollBaselineKeyRef.current = ''
|
||||
setDetail(null)
|
||||
setQrCodeDataUrl('')
|
||||
setUidInput('')
|
||||
void loadDetail()
|
||||
|
||||
return () => {
|
||||
@@ -224,20 +236,40 @@ export default function ClaimPage() {
|
||||
}
|
||||
}, [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 refreshRole() {
|
||||
setRefreshingRole(true)
|
||||
try {
|
||||
const nextDetail = await loadDetail({ silent: true })
|
||||
const nextSnapshot = createClaimSnapshot(nextDetail, { rebindingRole })
|
||||
|
||||
if (nextSnapshot.isCustomerRoleReady) {
|
||||
showSuccess('角色信息已刷新')
|
||||
} else if (nextSnapshot.isDefaultRole) {
|
||||
showError('当前仍是虚拟机默认角色,请重新绑定自己的角色信息')
|
||||
if (nextSnapshot.isUidMatched) {
|
||||
showSuccess('角色信息已匹配,可确认下一步')
|
||||
} else if (nextSnapshot.roleId) {
|
||||
showError(
|
||||
`当前绑定角色 ID(${nextSnapshot.roleId})与填写 UID(${nextSnapshot.expectedUid || '-'})不一致`,
|
||||
)
|
||||
} else if (nextDetail) {
|
||||
showError(
|
||||
nextSnapshot.flow?.role.errorMessage ||
|
||||
nextSnapshot.flow?.role.defaultErrorMessage ||
|
||||
'暂时还没有识别到角色信息,请完成绑定后稍等片刻再试',
|
||||
)
|
||||
}
|
||||
@@ -261,7 +293,7 @@ export default function ClaimPage() {
|
||||
|
||||
async function confirmRedeem() {
|
||||
try {
|
||||
await showConfirm('兑换后不可取消,也不可退货。请确认角色和商品信息完全正确。', '确认兑换', {
|
||||
await showConfirm('兑换后不可取消,也不可退货。请确认 UID 与商品信息完全正确。', '确认兑换', {
|
||||
okText: '确认兑换',
|
||||
okButtonProps: { danger: true },
|
||||
})
|
||||
@@ -287,7 +319,7 @@ export default function ClaimPage() {
|
||||
async function rebindRole() {
|
||||
try {
|
||||
await showConfirm(
|
||||
'换绑后当前绑定链接会失效,系统会退还当前虚拟号并生成新的绑定二维码。领取商品不会改变,但需要重新扫码绑定角色。',
|
||||
'换绑后当前绑定链接会失效,系统会退还当前虚拟号并生成新的绑定二维码。请使用你填写的 UID 对应角色重新绑定。',
|
||||
'确认换绑角色',
|
||||
{
|
||||
okText: '确认换绑',
|
||||
@@ -330,9 +362,17 @@ export default function ClaimPage() {
|
||||
|
||||
function openFeifeiUrl(useCurrentPage = false) {
|
||||
const url = String(
|
||||
snapshot.feifei?.h5.rechargeUrl || snapshot.feifei?.h5.entryUrl || '',
|
||||
snapshot.feifei?.h5UrlWithUid ||
|
||||
snapshot.feifei?.h5.rechargeUrl ||
|
||||
snapshot.feifei?.h5.entryUrl ||
|
||||
'',
|
||||
).trim()
|
||||
|
||||
if (!snapshot.hasExpectedUid) {
|
||||
showError('请先填写游戏 UID')
|
||||
return
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
showError('领取链接还没准备好,请稍后刷新')
|
||||
return
|
||||
@@ -353,6 +393,7 @@ export default function ClaimPage() {
|
||||
product={snapshot.product}
|
||||
currentStep={snapshot.currentStep}
|
||||
progressText={snapshot.progressText}
|
||||
expectedUid={snapshot.expectedUid}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
@@ -365,15 +406,41 @@ export default function ClaimPage() {
|
||||
<ExclamationCircleOutlined className="claim-large-icon claim-icon-error" />
|
||||
<p>{errorMessage}</p>
|
||||
</Card>
|
||||
) : !detail ? (
|
||||
<Card className="claim-content-card claim-state-card">
|
||||
<Empty description="流程数据不完整,请联系客服处理" />
|
||||
</Card>
|
||||
) : snapshot.currentStep === 1 ? (
|
||||
<ClaimUidStep
|
||||
uidInput={uidInput}
|
||||
submitting={submittingUid}
|
||||
onChange={setUidInput}
|
||||
onSubmit={() => void handleSubmitUid()}
|
||||
product={snapshot.product}
|
||||
/>
|
||||
) : snapshot.hasRedeemResult ? (
|
||||
snapshot.isFeifeiFlow ? (
|
||||
<FeifeiResultStep snapshot={snapshot} />
|
||||
) : (
|
||||
<ClaimResultStep snapshot={snapshot} />
|
||||
)
|
||||
) : snapshot.isFeifeiFlow && snapshot.feifei ? (
|
||||
<FeifeiClaimPanel
|
||||
feifei={snapshot.feifei}
|
||||
expectedUid={snapshot.expectedUid}
|
||||
order={snapshot.order}
|
||||
product={snapshot.product}
|
||||
taskLastError={snapshot.task?.lastError}
|
||||
onOpen={() => openFeifeiUrl(false)}
|
||||
onEditUid={() => {
|
||||
/* stay on step2; allow re-submit via UID card if needed */
|
||||
}}
|
||||
uidInput={uidInput}
|
||||
submittingUid={submittingUid}
|
||||
onUidChange={setUidInput}
|
||||
onResubmitUid={() => void handleSubmitUid()}
|
||||
/>
|
||||
) : detail && snapshot.flow ? (
|
||||
) : snapshot.flow ? (
|
||||
<KuaishouCloudClaimSteps
|
||||
snapshot={snapshot}
|
||||
qrCodeDataUrl={qrCodeDataUrl}
|
||||
@@ -386,6 +453,10 @@ export default function ClaimPage() {
|
||||
onRebindRole={rebindRole}
|
||||
onConfirmRole={confirmRole}
|
||||
onConfirmRedeem={confirmRedeem}
|
||||
uidInput={uidInput}
|
||||
submittingUid={submittingUid}
|
||||
onUidChange={setUidInput}
|
||||
onResubmitUid={() => void handleSubmitUid()}
|
||||
/>
|
||||
) : (
|
||||
<Card className="claim-content-card claim-state-card">
|
||||
@@ -406,28 +477,28 @@ function createClaimSnapshot(
|
||||
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 isTicketVerified = flow?.ticket.status === 'verified'
|
||||
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 canEnterBindingStep = isTicketVerified
|
||||
const isRoleReady = Boolean(roleName || roleId)
|
||||
const hasDefaultRoleSnapshot = Boolean(flow?.role.defaultName || flow?.role.defaultRid)
|
||||
const isDefaultRole = flow?.role.isDefaultRole === true
|
||||
const isCustomerRoleReady = isRoleReady && hasDefaultRoleSnapshot && !isDefaultRole
|
||||
const isUidMatched =
|
||||
Boolean(expectedUid) && Boolean(roleId) && normalizeUid(expectedUid) === normalizeUid(roleId)
|
||||
const confirmRoleDisabledReason = resolveConfirmRoleDisabledReason({
|
||||
flow,
|
||||
isBindingPrepared,
|
||||
hasDefaultRoleSnapshot,
|
||||
isRoleReady,
|
||||
isDefaultRole,
|
||||
isUidMatched,
|
||||
expectedUid,
|
||||
roleId,
|
||||
rebindingRole: Boolean(options.rebindingRole),
|
||||
})
|
||||
const isRoleConfirmed = isKuaishouCloudRoleConfirmedStatus(task?.status)
|
||||
@@ -436,26 +507,40 @@ function createClaimSnapshot(
|
||||
const isRedeemFailed =
|
||||
normalizedStatus === TASK_STATUS.MANUAL_REVIEW ||
|
||||
normalizedStatus === TASK_STATUS.FAILED ||
|
||||
String(flow?.dispatch.status || '').trim() === 'failed'
|
||||
const isCompleted = isKuaishouCloudCompletedStatus(task?.status)
|
||||
const hasRedeemResult = isDispatched || hasKuaishouCloudRedeemResultStatus(task?.status)
|
||||
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({
|
||||
isFeifeiFlow,
|
||||
isCompleted,
|
||||
hasExpectedUid,
|
||||
hasRedeemResult,
|
||||
isRoleConfirmed,
|
||||
canEnterBindingStep,
|
||||
isFeifeiFlow,
|
||||
})
|
||||
const progressText = resolveProgressText({
|
||||
feifei,
|
||||
isFeifeiFlow,
|
||||
hasExpectedUid,
|
||||
expectedUid,
|
||||
hasRedeemResult,
|
||||
isRoleConfirmed,
|
||||
isTicketVerified,
|
||||
isBindingPrepared,
|
||||
isUidMatched,
|
||||
})
|
||||
const resultTitle = resolveResultTitle({ isRedeemFailed, isCompleted, isDispatched })
|
||||
const resultDescription = resolveResultDescription({ detail, flow, task, isRedeemFailed, isCompleted })
|
||||
const resultDescription = resolveResultDescription({
|
||||
detail,
|
||||
flow,
|
||||
feifei,
|
||||
task,
|
||||
isRedeemFailed,
|
||||
isCompleted,
|
||||
})
|
||||
const resultVariant: ResultVariant = isRedeemFailed
|
||||
? 'warning'
|
||||
: isCompleted || isDispatched
|
||||
@@ -469,18 +554,17 @@ function createClaimSnapshot(
|
||||
orderItem,
|
||||
product,
|
||||
task,
|
||||
claimIdentity,
|
||||
expectedUid,
|
||||
hasExpectedUid,
|
||||
roleName,
|
||||
roleId,
|
||||
isFeifeiFlow,
|
||||
isTicketVerified,
|
||||
isBindUrlExpired,
|
||||
isBindingPrepared,
|
||||
isBindingPreparing,
|
||||
canEnterBindingStep,
|
||||
isRoleReady,
|
||||
hasDefaultRoleSnapshot,
|
||||
isDefaultRole,
|
||||
isCustomerRoleReady,
|
||||
isUidMatched,
|
||||
confirmRoleDisabledReason,
|
||||
isRoleConfirmed,
|
||||
isDispatched,
|
||||
@@ -495,25 +579,28 @@ function createClaimSnapshot(
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeUid(value: unknown) {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.replace(/\s+/g, '')
|
||||
}
|
||||
|
||||
function resolveConfirmRoleDisabledReason(options: {
|
||||
flow: ClaimKuaishouCloudFlowInfo | null
|
||||
isBindingPrepared: boolean
|
||||
hasDefaultRoleSnapshot: boolean
|
||||
isRoleReady: boolean
|
||||
isDefaultRole: boolean
|
||||
isUidMatched: boolean
|
||||
expectedUid: string
|
||||
roleId: string
|
||||
rebindingRole: boolean
|
||||
}) {
|
||||
if (!options.isBindingPrepared) {
|
||||
return '绑定二维码还在准备中,请稍后自动刷新'
|
||||
}
|
||||
if (!options.hasDefaultRoleSnapshot) {
|
||||
return options.flow?.role.defaultErrorMessage || '系统还未获取到虚拟机默认角色信息,请稍后刷新'
|
||||
}
|
||||
if (!options.isRoleReady) {
|
||||
return '请先扫码绑定自己的角色,并刷新角色信息'
|
||||
}
|
||||
if (options.isDefaultRole) {
|
||||
return '当前仍是虚拟机默认角色,请重新绑定自己的角色信息'
|
||||
if (!options.isUidMatched) {
|
||||
return `当前绑定角色 ID(${options.roleId || '-'})与填写 UID(${options.expectedUid || '-'})不一致`
|
||||
}
|
||||
if (options.rebindingRole) {
|
||||
return '正在换绑角色,请稍候'
|
||||
@@ -522,37 +609,38 @@ function resolveConfirmRoleDisabledReason(options: {
|
||||
}
|
||||
|
||||
function resolveCurrentStep(options: {
|
||||
isFeifeiFlow: boolean
|
||||
isCompleted: boolean
|
||||
hasExpectedUid: boolean
|
||||
hasRedeemResult: boolean
|
||||
isRoleConfirmed: boolean
|
||||
canEnterBindingStep: boolean
|
||||
isFeifeiFlow: boolean
|
||||
}) {
|
||||
if (options.isFeifeiFlow) {
|
||||
return options.isCompleted ? 4 : 2
|
||||
if (!options.hasExpectedUid) {
|
||||
return 1
|
||||
}
|
||||
if (options.hasRedeemResult) {
|
||||
return 4
|
||||
}
|
||||
if (options.isRoleConfirmed) {
|
||||
if (!options.isFeifeiFlow && options.isRoleConfirmed) {
|
||||
return 3
|
||||
}
|
||||
if (options.canEnterBindingStep) {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
return 2
|
||||
}
|
||||
|
||||
function resolveProgressText(options: {
|
||||
feifei: ClaimKuaishouFeifeiFlowInfo | null
|
||||
isFeifeiFlow: boolean
|
||||
hasExpectedUid: boolean
|
||||
expectedUid: string
|
||||
hasRedeemResult: boolean
|
||||
isRoleConfirmed: boolean
|
||||
isTicketVerified: boolean
|
||||
isBindingPrepared: boolean
|
||||
isUidMatched: boolean
|
||||
}) {
|
||||
if (!options.hasExpectedUid) {
|
||||
return '请填写游戏 UID'
|
||||
}
|
||||
if (options.isFeifeiFlow) {
|
||||
return options.feifei?.rechargeStatusLabel || '请打开领取链接'
|
||||
return options.feifei?.rechargeStatusLabel || `UID ${options.expectedUid},请打开领取链接`
|
||||
}
|
||||
if (options.hasRedeemResult) {
|
||||
return '兑换结果已生成'
|
||||
@@ -560,10 +648,13 @@ function resolveProgressText(options: {
|
||||
if (options.isRoleConfirmed) {
|
||||
return '角色已确认,等待兑换'
|
||||
}
|
||||
if (options.isTicketVerified) {
|
||||
return options.isBindingPrepared ? '请完成扫码绑定' : '绑定链接刷新中,请稍候'
|
||||
if (!options.isBindingPrepared) {
|
||||
return '绑定链接准备中,请稍候'
|
||||
}
|
||||
return '等待提交并核销'
|
||||
if (options.isUidMatched) {
|
||||
return 'UID 已匹配,请确认下一步'
|
||||
}
|
||||
return '请完成扫码绑定并匹配 UID'
|
||||
}
|
||||
|
||||
function resolveResultTitle(options: {
|
||||
@@ -586,6 +677,7 @@ function resolveResultTitle(options: {
|
||||
function resolveResultDescription(options: {
|
||||
detail: ClaimDetailData | null
|
||||
flow: ClaimKuaishouCloudFlowInfo | null
|
||||
feifei: ClaimKuaishouFeifeiFlowInfo | null
|
||||
task: ClaimDetailData['task'] | null
|
||||
isRedeemFailed: boolean
|
||||
isCompleted: boolean
|
||||
@@ -594,6 +686,7 @@ function resolveResultDescription(options: {
|
||||
const message = String(
|
||||
options.task?.lastError ||
|
||||
options.flow?.dispatch.errorMessage ||
|
||||
options.feifei?.rechargeResultMessage ||
|
||||
options.detail?.result?.resultMessage ||
|
||||
'',
|
||||
).trim()
|
||||
@@ -601,10 +694,10 @@ function resolveResultDescription(options: {
|
||||
}
|
||||
|
||||
if (options.isCompleted) {
|
||||
return '当前兑换流程已经完成。发货、退号和核销结果会保存在后台任务界面。'
|
||||
return '当前兑换流程已经完成。'
|
||||
}
|
||||
|
||||
return '你的兑换请求已经提交。发货、退号和核销结果会由系统保存在后台任务界面,无需在此页面等待。'
|
||||
return '你的兑换请求已经提交。后续结果会由系统保存在后台任务界面。'
|
||||
}
|
||||
|
||||
function isDateExpired(value: string | null) {
|
||||
@@ -622,18 +715,21 @@ function ClaimHeaderCard({
|
||||
product,
|
||||
currentStep,
|
||||
progressText,
|
||||
expectedUid,
|
||||
}: {
|
||||
order: ClaimOrderInfo | null
|
||||
product: ClaimProductInfo | null
|
||||
currentStep: number
|
||||
progressText: string
|
||||
expectedUid: string
|
||||
}) {
|
||||
return (
|
||||
<Card className="claim-header-card">
|
||||
<div className="claim-header-copy">
|
||||
<span className="eyebrow">kuaishou-lewan 客户领取</span>
|
||||
<span className="eyebrow">商品领取</span>
|
||||
<h1>{product?.title || '商品领取'}</h1>
|
||||
<p>订单号:{order?.platformOrderId || '-'}</p>
|
||||
{expectedUid ? <p>游戏 UID:{expectedUid}</p> : null}
|
||||
<ClaimProductItems product={product} compact />
|
||||
</div>
|
||||
|
||||
@@ -651,6 +747,57 @@ function ClaimHeaderCard({
|
||||
)
|
||||
}
|
||||
|
||||
function ClaimUidStep({
|
||||
uidInput,
|
||||
submitting,
|
||||
onChange,
|
||||
onSubmit,
|
||||
product,
|
||||
}: {
|
||||
uidInput: string
|
||||
submitting: boolean
|
||||
onChange: (value: string) => void
|
||||
onSubmit: () => void
|
||||
product: ClaimProductInfo | null
|
||||
}) {
|
||||
return (
|
||||
<Card className="claim-content-card">
|
||||
<Typography.Title level={2}>第 1 步:填写游戏 UID</Typography.Title>
|
||||
<p className="claim-muted">
|
||||
请填写你要领取的游戏角色 UID。提交后系统会按平台继续绑定或跳转领取。
|
||||
</p>
|
||||
|
||||
<div className="claim-info-grid">
|
||||
<InfoTile label="领取商品" value={product?.title || '-'} />
|
||||
<InfoTile label="商品数量" value={String(product?.quantity || 0)} />
|
||||
</div>
|
||||
|
||||
<ClaimProductItems product={product} />
|
||||
|
||||
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder="请输入游戏 UID"
|
||||
value={uidInput}
|
||||
maxLength={64}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
onPressEnter={onSubmit}
|
||||
/>
|
||||
<Button type="primary" size="large" loading={submitting} onClick={onSubmit} block>
|
||||
确认 UID,下一步
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginTop: 16 }}
|
||||
message="UID 需与游戏内角色 ID 一致,填错将导致无法发货。"
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function KuaishouCloudClaimSteps({
|
||||
snapshot,
|
||||
qrCodeDataUrl,
|
||||
@@ -663,6 +810,10 @@ function KuaishouCloudClaimSteps({
|
||||
onRebindRole,
|
||||
onConfirmRole,
|
||||
onConfirmRedeem,
|
||||
uidInput,
|
||||
submittingUid,
|
||||
onUidChange,
|
||||
onResubmitUid,
|
||||
}: {
|
||||
snapshot: ClaimSnapshot
|
||||
qrCodeDataUrl: string
|
||||
@@ -675,36 +826,21 @@ function KuaishouCloudClaimSteps({
|
||||
onRebindRole: () => void
|
||||
onConfirmRole: () => void
|
||||
onConfirmRedeem: () => void
|
||||
uidInput: string
|
||||
submittingUid: boolean
|
||||
onUidChange: (value: string) => void
|
||||
onResubmitUid: () => void
|
||||
}) {
|
||||
if (!snapshot.flow) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (snapshot.currentStep === 1) {
|
||||
return <ClaimDeprecatedTicketStep />
|
||||
}
|
||||
|
||||
if (snapshot.currentStep === 2) {
|
||||
return (
|
||||
<ClaimBindingStep
|
||||
snapshot={snapshot}
|
||||
qrCodeDataUrl={qrCodeDataUrl}
|
||||
refreshingRole={refreshingRole}
|
||||
confirmingRole={confirmingRole}
|
||||
rebindingRole={rebindingRole}
|
||||
onOpenBindUrl={onOpenBindUrl}
|
||||
onRefreshRole={onRefreshRole}
|
||||
onRebindRole={onRebindRole}
|
||||
onConfirmRole={onConfirmRole}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (snapshot.currentStep === 3) {
|
||||
return (
|
||||
<ClaimConfirmStep
|
||||
roleName={snapshot.roleName}
|
||||
roleId={snapshot.roleId}
|
||||
expectedUid={snapshot.expectedUid}
|
||||
product={snapshot.product}
|
||||
redeeming={redeeming}
|
||||
rebindingRole={rebindingRole}
|
||||
@@ -714,25 +850,22 @@ function KuaishouCloudClaimSteps({
|
||||
)
|
||||
}
|
||||
|
||||
return <ClaimResultStep snapshot={snapshot} />
|
||||
}
|
||||
|
||||
function ClaimDeprecatedTicketStep() {
|
||||
return (
|
||||
<Card className="claim-content-card">
|
||||
<Typography.Title level={2}>旧领取流程已停用</Typography.Title>
|
||||
<p className="claim-muted">
|
||||
当前订单已改用行业电子凭证方案处理,不再支持快手小店核销码提交。请联系商家确认新的领取方式。
|
||||
</p>
|
||||
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
icon={<ExclamationCircleOutlined />}
|
||||
message="快手小店核销入口已下线"
|
||||
description="电子凭证发码、查询和核销请以新的电子凭证流程为准。"
|
||||
/>
|
||||
</Card>
|
||||
<ClaimBindingStep
|
||||
snapshot={snapshot}
|
||||
qrCodeDataUrl={qrCodeDataUrl}
|
||||
refreshingRole={refreshingRole}
|
||||
confirmingRole={confirmingRole}
|
||||
rebindingRole={rebindingRole}
|
||||
onOpenBindUrl={onOpenBindUrl}
|
||||
onRefreshRole={onRefreshRole}
|
||||
onRebindRole={onRebindRole}
|
||||
onConfirmRole={onConfirmRole}
|
||||
uidInput={uidInput}
|
||||
submittingUid={submittingUid}
|
||||
onUidChange={onUidChange}
|
||||
onResubmitUid={onResubmitUid}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -746,6 +879,10 @@ function ClaimBindingStep({
|
||||
onRefreshRole,
|
||||
onRebindRole,
|
||||
onConfirmRole,
|
||||
uidInput,
|
||||
submittingUid,
|
||||
onUidChange,
|
||||
onResubmitUid,
|
||||
}: {
|
||||
snapshot: ClaimSnapshot
|
||||
qrCodeDataUrl: string
|
||||
@@ -756,6 +893,10 @@ function ClaimBindingStep({
|
||||
onRefreshRole: () => void
|
||||
onRebindRole: () => void
|
||||
onConfirmRole: () => void
|
||||
uidInput: string
|
||||
submittingUid: boolean
|
||||
onUidChange: (value: string) => void
|
||||
onResubmitUid: () => void
|
||||
}) {
|
||||
const flow = snapshot.flow
|
||||
if (!flow) {
|
||||
@@ -764,6 +905,18 @@ function ClaimBindingStep({
|
||||
|
||||
return (
|
||||
<Card className="claim-content-card claim-binding-card">
|
||||
<Typography.Title level={2}>第 2 步:绑定角色</Typography.Title>
|
||||
<p className="claim-muted">
|
||||
请使用与 UID <strong>{snapshot.expectedUid || '-'}</strong> 对应的角色扫码绑定。
|
||||
</p>
|
||||
|
||||
<UidEditRow
|
||||
uidInput={uidInput}
|
||||
submitting={submittingUid}
|
||||
onChange={onUidChange}
|
||||
onSubmit={onResubmitUid}
|
||||
/>
|
||||
|
||||
{snapshot.isBindingPrepared && qrCodeDataUrl ? (
|
||||
<div className="claim-qr-block">
|
||||
<div className="claim-qr-guide-panel">
|
||||
@@ -786,13 +939,19 @@ function ClaimBindingStep({
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={snapshot.isCustomerRoleReady ? 'claim-role-panel' : 'claim-role-panel pending'}>
|
||||
<div className={snapshot.isUidMatched ? 'claim-role-panel' : 'claim-role-panel pending'}>
|
||||
<InfoRow label="填写 UID" value={snapshot.expectedUid || '-'} />
|
||||
<InfoRow label="当前角色" value={snapshot.roleName || '待识别'} />
|
||||
<InfoRow label="角色 ID" value={snapshot.roleId || '-'} />
|
||||
<InfoRow label="匹配状态" value={snapshot.isUidMatched ? '已匹配' : '未匹配'} />
|
||||
</div>
|
||||
|
||||
{flow.role.isDefaultRole ? (
|
||||
<Alert type="warning" showIcon message="当前仍是虚拟机默认角色,请重新绑定自己的角色信息。" />
|
||||
{!snapshot.isUidMatched && snapshot.roleId ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message={`当前绑定角色 ID(${snapshot.roleId})与填写 UID(${snapshot.expectedUid})不一致,请换绑正确角色。`}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Space size={12} wrap className="claim-action-row">
|
||||
@@ -818,7 +977,7 @@ function ClaimBindingStep({
|
||||
disabled={Boolean(snapshot.confirmRoleDisabledReason)}
|
||||
onClick={onConfirmRole}
|
||||
>
|
||||
我已完成绑定,下一步
|
||||
UID 已匹配,下一步
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
@@ -827,11 +986,6 @@ function ClaimBindingStep({
|
||||
<div className="claim-meta-footer">
|
||||
<span>最近刷新:{formatAdminDateTime(flow.role.refreshedAt)}</span>
|
||||
<span>链接有效期:{formatAdminDateTime(flow.binding.bindExpiresAt)}</span>
|
||||
{flow.binding.bindProbeMessage ? (
|
||||
<span>
|
||||
链接检测:{flow.binding.bindProbeStatus || '-'} / {flow.binding.bindProbeMessage}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
@@ -840,6 +994,7 @@ function ClaimBindingStep({
|
||||
function ClaimConfirmStep({
|
||||
roleName,
|
||||
roleId,
|
||||
expectedUid,
|
||||
product,
|
||||
redeeming,
|
||||
rebindingRole,
|
||||
@@ -848,6 +1003,7 @@ function ClaimConfirmStep({
|
||||
}: {
|
||||
roleName: string
|
||||
roleId: string
|
||||
expectedUid: string
|
||||
product: ClaimProductInfo | null
|
||||
redeeming: boolean
|
||||
rebindingRole: boolean
|
||||
@@ -857,9 +1013,10 @@ function ClaimConfirmStep({
|
||||
return (
|
||||
<Card className="claim-content-card">
|
||||
<Typography.Title level={2}>第 3 步:确认兑换信息</Typography.Title>
|
||||
<p className="claim-muted">请再次确认角色和商品信息,确认无误后再继续兑换。</p>
|
||||
<p className="claim-muted">请再次确认 UID 和商品信息,确认无误后再继续兑换。</p>
|
||||
|
||||
<div className="claim-info-grid">
|
||||
<InfoTile label="填写 UID" value={expectedUid || '-'} />
|
||||
<InfoTile label="角色名称" value={roleName || '-'} />
|
||||
<InfoTile label="角色 ID" value={roleId || '-'} />
|
||||
<InfoTile label="领取商品" value={product?.title || '-'} />
|
||||
@@ -897,9 +1054,6 @@ function ClaimConfirmStep({
|
||||
|
||||
function ClaimResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
|
||||
const flow = snapshot.flow
|
||||
if (!flow) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="claim-content-card">
|
||||
@@ -916,12 +1070,12 @@ function ClaimResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
|
||||
</div>
|
||||
|
||||
<div className="claim-info-grid">
|
||||
<InfoTile label="结果时间" value={formatAdminDateTime(flow.dispatch.dispatchAt)} />
|
||||
<InfoTile label="填写 UID" value={snapshot.expectedUid || '-'} />
|
||||
<InfoTile label="结果时间" value={formatAdminDateTime(flow?.dispatch.dispatchAt || null)} />
|
||||
<InfoTile label="领取商品" value={snapshot.product?.title || '-'} />
|
||||
<InfoTile label="角色名称" value={snapshot.roleName || '-'} />
|
||||
<InfoTile label="角色 ID" value={snapshot.roleId || '-'} />
|
||||
<InfoTile label="订单号" value={snapshot.order?.platformOrderId || '-'} />
|
||||
<InfoTile label="购买数量" value={String(snapshot.product?.quantity || 0)} />
|
||||
</div>
|
||||
|
||||
<ClaimProductItems product={snapshot.product} />
|
||||
@@ -941,45 +1095,128 @@ function ClaimResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
|
||||
|
||||
function FeifeiClaimPanel({
|
||||
feifei,
|
||||
expectedUid,
|
||||
order,
|
||||
product,
|
||||
taskLastError,
|
||||
onOpen,
|
||||
uidInput,
|
||||
submittingUid,
|
||||
onUidChange,
|
||||
onResubmitUid,
|
||||
}: {
|
||||
feifei: ClaimKuaishouFeifeiFlowInfo
|
||||
expectedUid: string
|
||||
order: ClaimOrderInfo | null
|
||||
product: ClaimProductInfo | null
|
||||
taskLastError?: string
|
||||
onOpen: () => void
|
||||
onEditUid?: () => void
|
||||
uidInput: string
|
||||
submittingUid: boolean
|
||||
onUidChange: (value: string) => void
|
||||
onResubmitUid: () => void
|
||||
}) {
|
||||
const openReady = Boolean(feifei.h5UrlWithUid || feifei.h5.rechargeUrl || feifei.h5.entryUrl)
|
||||
|
||||
return (
|
||||
<Card className="claim-content-card">
|
||||
<div className="claim-feifei-main">
|
||||
<span className="claim-feifei-label">kuaishou-feifei</span>
|
||||
<Typography.Title level={2}>{product?.title || '商品领取'}</Typography.Title>
|
||||
<Typography.Title level={2}>第 2 步:打开领取链接</Typography.Title>
|
||||
<p>{feifei.rechargeStatusLabel || '待领取'}</p>
|
||||
</div>
|
||||
|
||||
<UidEditRow
|
||||
uidInput={uidInput}
|
||||
submitting={submittingUid}
|
||||
onChange={onUidChange}
|
||||
onSubmit={onResubmitUid}
|
||||
/>
|
||||
|
||||
<div className="claim-info-grid">
|
||||
<InfoTile label="填写 UID" value={expectedUid || '-'} />
|
||||
<InfoTile label="订单号" value={order?.platformOrderId || '-'} />
|
||||
<InfoTile label="平台单号" value={feifei.orderNo || feifei.platformOrderNo || '-'} />
|
||||
<InfoTile label="领取商品" value={product?.title || '-'} />
|
||||
</div>
|
||||
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="将携带你填写的 UID 打开领取页,请确认游戏内角色一致。"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<LinkOutlined />}
|
||||
disabled={!feifei.h5.rechargeUrl && !feifei.h5.entryUrl}
|
||||
disabled={!openReady || !expectedUid}
|
||||
onClick={onOpen}
|
||||
block
|
||||
style={{ marginTop: 16 }}
|
||||
>
|
||||
打开领取链接
|
||||
</Button>
|
||||
|
||||
{taskLastError ? <Alert type="warning" showIcon message={taskLastError} /> : null}
|
||||
{taskLastError ? <Alert type="warning" showIcon message={taskLastError} style={{ marginTop: 12 }} /> : null}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function FeifeiResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
|
||||
return (
|
||||
<Card className="claim-content-card">
|
||||
<div className="claim-result-header">
|
||||
{snapshot.resultVariant === 'warning' ? (
|
||||
<ExclamationCircleOutlined className="claim-large-icon claim-icon-warning" />
|
||||
) : (
|
||||
<CheckCircleOutlined className={`claim-large-icon claim-icon-${snapshot.resultVariant}`} />
|
||||
)}
|
||||
<div>
|
||||
<Typography.Title level={2}>{snapshot.resultTitle}</Typography.Title>
|
||||
<p className="claim-muted">{snapshot.resultDescription}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="claim-info-grid">
|
||||
<InfoTile label="填写 UID" value={snapshot.expectedUid || '-'} />
|
||||
<InfoTile label="状态" value={snapshot.feifei?.rechargeStatusLabel || '-'} />
|
||||
<InfoTile label="订单号" value={snapshot.order?.platformOrderId || '-'} />
|
||||
<InfoTile label="领取商品" value={snapshot.product?.title || '-'} />
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function UidEditRow({
|
||||
uidInput,
|
||||
submitting,
|
||||
onChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
uidInput: string
|
||||
submitting: boolean
|
||||
onChange: (value: string) => void
|
||||
onSubmit: () => void
|
||||
}) {
|
||||
return (
|
||||
<Space.Compact style={{ width: '100%', marginBottom: 16 }}>
|
||||
<Input
|
||||
size="large"
|
||||
value={uidInput}
|
||||
maxLength={64}
|
||||
placeholder="游戏 UID"
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
onPressEnter={onSubmit}
|
||||
/>
|
||||
<Button size="large" loading={submitting} onClick={onSubmit}>
|
||||
更新 UID
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
)
|
||||
}
|
||||
|
||||
function ClaimProductItems({
|
||||
product,
|
||||
compact = false,
|
||||
|
||||
@@ -5,6 +5,10 @@ export function fetchClaimDetail(token: string) {
|
||||
return apiGet<ClaimDetailData>(`/api/v1/claim/${token}`)
|
||||
}
|
||||
|
||||
export function submitClaimUid(token: string, uid: string) {
|
||||
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/uid`, { uid })
|
||||
}
|
||||
|
||||
export function confirmKuaishouCloudClaimRole(token: string) {
|
||||
return apiPost<ClaimDetailData>(`/api/v1/claim/${token}/kuaishou-cloud/confirm-role`, {})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ export type ClaimTokenStatus = 'active' | 'used' | 'expired' | 'revoked' | (stri
|
||||
|
||||
export type ClaimTaskStatus = KnownTaskStatus | (string & {})
|
||||
|
||||
export interface ClaimIdentityInfo {
|
||||
expectedUid: string
|
||||
submittedAt: string | null
|
||||
source: string
|
||||
ready: boolean
|
||||
}
|
||||
|
||||
export interface ClaimTaskInfo {
|
||||
taskId: number
|
||||
taskNo: string
|
||||
@@ -150,6 +157,7 @@ export interface ClaimKuaishouFeifeiFlowInfo {
|
||||
entryUrl: string
|
||||
rechargeUrl: string
|
||||
}
|
||||
h5UrlWithUid: string
|
||||
lastSyncedAt: string | null
|
||||
}
|
||||
|
||||
@@ -157,6 +165,7 @@ export interface ClaimDetailData {
|
||||
tokenStatus: ClaimTokenStatus
|
||||
claimUrl: string
|
||||
flowType: 'kuaishou_cloud' | 'kuaishou_ct_assisted' | 'kuaishou_feifei' | (string & {})
|
||||
claimIdentity: ClaimIdentityInfo | null
|
||||
task: ClaimTaskInfo
|
||||
order: ClaimOrderInfo
|
||||
orderItem: ClaimOrderItemInfo
|
||||
|
||||
Reference in New Issue
Block a user